diff --git a/frontend/src/api/modpack.ts b/frontend/src/api/modpack.ts new file mode 100644 index 0000000..214b753 --- /dev/null +++ b/frontend/src/api/modpack.ts @@ -0,0 +1,60 @@ +import apiClient from './client' +import type { Modpack, ModpackState, PublicModpack } from '@/types' + +export async function getModpackState(): Promise { + const response = await apiClient.get('/api/modpack') + return response.data.data +} + +export async function saveModpackDraft(payload: { + name: string + version: string + modList: string[] + description: string | null +}): Promise { + const response = await apiClient.put('/api/modpack', payload) + return response.data.data +} + +export async function buildModpack(): Promise { + const response = await apiClient.post('/api/modpack/build') + return response.data.data +} + +export async function publishModpack(): Promise { + const response = await apiClient.post('/api/modpack/publish') + return response.data.data +} + +export async function archiveModpack(): Promise { + const response = await apiClient.post('/api/modpack/archive') + return response.data.data +} + +export async function unarchiveModpack(): Promise { + const response = await apiClient.post('/api/modpack/unarchive') + return response.data.data +} + +export async function deleteModpack(): Promise { + await apiClient.delete('/api/modpack') +} + +/** + * Public metadata for the currently-published modpack. Returns null if none + * is published. Doesn't require auth — used by the login page to decide + * whether to show the download CTA. + */ +export async function getPublishedModpack(): Promise { + try { + const response = await apiClient.get('/api/modpack/public') + const env = response.data + if (env?.code !== 200) return null + return env.data as PublicModpack + } catch { + return null + } +} + +/** Public download URL — anchor href, not fetched programmatically. */ +export const publicModpackDownloadUrl = '/api/modpack/public/download' diff --git a/frontend/src/i18n/locales/de.ts b/frontend/src/i18n/locales/de.ts index 043b583..7bf9e43 100644 --- a/frontend/src/i18n/locales/de.ts +++ b/frontend/src/i18n/locales/de.ts @@ -1037,6 +1037,53 @@ const de = { modsFound: 'Mods gefunden', }, + modpack: { + tabLabel: 'Modpack', + intro: 'Bündeln Sie installierte Mods in eine einzelne ZIP-Datei, die Spieler von der Anmeldeseite herunterladen können. Nur das veröffentlichte Paket ist öffentlich verfügbar — Entwürfe und archivierte Pakete bleiben privat.', + status: { + draft: 'Entwurf', + published: 'Veröffentlicht', + archived: 'Archiviert', + }, + modCount: '{n} Mod(s)', + downloadsCount: '{n} Download(s)', + publicDownloadLink: 'Öffentlichen Download-Link öffnen', + fields: { + name: 'Paketname', + version: 'Version', + modList: 'Enthaltene Mods', + description: 'Beschreibung', + }, + placeholders: { + name: 'z. B. KitsuneDen Client Pack', + modList: 'Mods zum Einschließen auswählen...', + description: 'Optional — was im Paket ist, für wen, Versions-Notizen.', + }, + modListHint: 'Nur Mods, die derzeit auf dem Server installiert sind, erscheinen hier. Fügen Sie zuerst ein Mod über die Registerkarte „Installiert“ hinzu, dann kehren Sie zurück.', + actions: { + saveDraft: 'Entwurf speichern', + build: 'ZIP erstellen', + publish: 'Veröffentlichen', + archive: 'Archivieren', + unarchive: 'Aus Archiv wiederherstellen', + delete: 'Paket löschen', + }, + confirmDelete: 'Modpack-Datensatz und ZIP-Datei löschen? Spieler verlieren den Download-Link, bis Sie ein neues veröffentlichen.', + ctaTooltip: '{size} · {n} Mod(s)', + saved: 'Modpack als Entwurf gespeichert', + built: 'Modpack-ZIP erstellt', + published: 'Modpack veröffentlicht — Download ist jetzt auf der Anmeldeseite verfügbar', + archived: 'Modpack archiviert', + unarchived: 'Modpack als Entwurf wiederhergestellt', + deleted: 'Modpack gelöscht', + failedToLoad: 'Modpack konnte nicht geladen werden', + failedToSave: 'Modpack konnte nicht gespeichert werden', + failedToBuild: 'Modpack-ZIP konnte nicht erstellt werden', + failedToPublish: 'Modpack konnte nicht veröffentlicht werden', + failedToArchive: 'Modpack konnte nicht archiviert werden', + failedToDelete: 'Modpack konnte nicht gelöscht werden', + }, + backups: { title: 'Sicherungen', createNow: 'Sicherung erstellen', diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index 1fc4df3..a16434c 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -1034,6 +1034,53 @@ const en = { modsFound: 'mods found', }, + modpack: { + tabLabel: 'Modpack', + intro: 'Bundle installed mods into a single zip players can download from the login page. Only the published pack is publicly downloadable — drafts and archived packs stay private.', + status: { + draft: 'Draft', + published: 'Published', + archived: 'Archived', + }, + modCount: '{n} mod(s)', + downloadsCount: '{n} download(s)', + publicDownloadLink: 'Open public download link', + fields: { + name: 'Pack name', + version: 'Version', + modList: 'Included mods', + description: 'Description', + }, + placeholders: { + name: 'e.g. KitsuneDen Client Pack', + modList: 'Pick mods to include...', + description: 'Optional — what\'s in this pack, who it\'s for, version notes.', + }, + modListHint: 'Only mods currently installed on the server appear here. Add a mod via the Installed tab first, then come back to include it.', + actions: { + saveDraft: 'Save Draft', + build: 'Build Zip', + publish: 'Publish', + archive: 'Archive', + unarchive: 'Restore from Archive', + delete: 'Delete Pack', + }, + confirmDelete: 'Delete the modpack record and its zip file? Players will lose the download link until you publish a new one.', + ctaTooltip: '{size} · {n} mod(s)', + saved: 'Modpack saved as draft', + built: 'Modpack zip built', + published: 'Modpack published — download is now live on the login page', + archived: 'Modpack archived', + unarchived: 'Modpack restored to draft', + deleted: 'Modpack deleted', + failedToLoad: 'Failed to load modpack', + failedToSave: 'Failed to save modpack', + failedToBuild: 'Failed to build modpack zip', + failedToPublish: 'Failed to publish modpack', + failedToArchive: 'Failed to archive modpack', + failedToDelete: 'Failed to delete modpack', + }, + backups: { title: 'Backups', createNow: 'Create Backup', diff --git a/frontend/src/i18n/locales/es.ts b/frontend/src/i18n/locales/es.ts index 6f90f63..0a7c67d 100644 --- a/frontend/src/i18n/locales/es.ts +++ b/frontend/src/i18n/locales/es.ts @@ -1037,6 +1037,53 @@ const es = { modsFound: 'mods encontrados', }, + modpack: { + tabLabel: 'Modpack', + intro: 'Agrupe los mods instalados en un único archivo ZIP que los jugadores pueden descargar desde la página de inicio de sesión. Solo el pack publicado se puede descargar públicamente — los borradores y los packs archivados permanecen privados.', + status: { + draft: 'Borrador', + published: 'Publicado', + archived: 'Archivado', + }, + modCount: '{n} mod(s)', + downloadsCount: '{n} descarga(s)', + publicDownloadLink: 'Abrir enlace de descarga pública', + fields: { + name: 'Nombre del pack', + version: 'Versión', + modList: 'Mods incluidos', + description: 'Descripción', + }, + placeholders: { + name: 'p. ej. KitsuneDen Client Pack', + modList: 'Elija mods para incluir...', + description: 'Opcional — qué contiene este pack, a quién va dirigido, notas de versión.', + }, + modListHint: 'Solo aparecen aquí los mods actualmente instalados en el servidor. Añada primero un mod desde la pestaña Instalados, y luego regrese para incluirlo.', + actions: { + saveDraft: 'Guardar borrador', + build: 'Construir ZIP', + publish: 'Publicar', + archive: 'Archivar', + unarchive: 'Restaurar desde archivo', + delete: 'Eliminar pack', + }, + confirmDelete: '¿Eliminar el registro del modpack y su archivo ZIP? Los jugadores perderán el enlace de descarga hasta que publique uno nuevo.', + ctaTooltip: '{size} · {n} mod(s)', + saved: 'Modpack guardado como borrador', + built: 'ZIP del modpack construido', + published: 'Modpack publicado — la descarga está activa en la página de inicio de sesión', + archived: 'Modpack archivado', + unarchived: 'Modpack restaurado a borrador', + deleted: 'Modpack eliminado', + failedToLoad: 'No se pudo cargar el modpack', + failedToSave: 'No se pudo guardar el modpack', + failedToBuild: 'No se pudo construir el ZIP del modpack', + failedToPublish: 'No se pudo publicar el modpack', + failedToArchive: 'No se pudo archivar el modpack', + failedToDelete: 'No se pudo eliminar el modpack', + }, + backups: { title: 'Copias de seguridad', createNow: 'Crear copia de seguridad', diff --git a/frontend/src/i18n/locales/fr.ts b/frontend/src/i18n/locales/fr.ts index 4794ed2..321db8b 100644 --- a/frontend/src/i18n/locales/fr.ts +++ b/frontend/src/i18n/locales/fr.ts @@ -1037,6 +1037,53 @@ const fr = { modsFound: 'mods trouvés', }, + modpack: { + tabLabel: 'Modpack', + intro: 'Regroupez les mods installés dans une seule archive ZIP que les joueurs peuvent télécharger depuis la page de connexion. Seul le pack publié est téléchargeable publiquement — les brouillons et les packs archivés restent privés.', + status: { + draft: 'Brouillon', + published: 'Publié', + archived: 'Archivé', + }, + modCount: '{n} mod(s)', + downloadsCount: '{n} téléchargement(s)', + publicDownloadLink: 'Ouvrir le lien de téléchargement public', + fields: { + name: 'Nom du pack', + version: 'Version', + modList: 'Mods inclus', + description: 'Description', + }, + placeholders: { + name: 'p. ex. KitsuneDen Client Pack', + modList: 'Choisissez les mods à inclure...', + description: 'Optionnel — ce qui est dans ce pack, à qui il s\'adresse, notes de version.', + }, + modListHint: 'Seuls les mods actuellement installés sur le serveur apparaissent ici. Ajoutez d\'abord un mod via l\'onglet Installés, puis revenez pour l\'inclure.', + actions: { + saveDraft: 'Enregistrer le brouillon', + build: 'Construire le ZIP', + publish: 'Publier', + archive: 'Archiver', + unarchive: 'Restaurer depuis l\'archive', + delete: 'Supprimer le pack', + }, + confirmDelete: 'Supprimer l\'enregistrement du modpack et son fichier ZIP ? Les joueurs perdront le lien de téléchargement jusqu\'à ce que vous publiiez un nouveau pack.', + ctaTooltip: '{size} · {n} mod(s)', + saved: 'Modpack enregistré comme brouillon', + built: 'ZIP du modpack construit', + published: 'Modpack publié — le téléchargement est désormais actif sur la page de connexion', + archived: 'Modpack archivé', + unarchived: 'Modpack restauré en brouillon', + deleted: 'Modpack supprimé', + failedToLoad: 'Impossible de charger le modpack', + failedToSave: 'Impossible d\'enregistrer le modpack', + failedToBuild: 'Impossible de construire le ZIP du modpack', + failedToPublish: 'Impossible de publier le modpack', + failedToArchive: 'Impossible d\'archiver le modpack', + failedToDelete: 'Impossible de supprimer le modpack', + }, + backups: { title: 'Sauvegardes', createNow: 'Créer une sauvegarde', diff --git a/frontend/src/i18n/locales/ja.ts b/frontend/src/i18n/locales/ja.ts index e5ba366..05647a8 100644 --- a/frontend/src/i18n/locales/ja.ts +++ b/frontend/src/i18n/locales/ja.ts @@ -1022,6 +1022,45 @@ const ja: Messages = { modsFound: '件のMODが見つかりました', }, + modpack: { + // English placeholders — translations TBD + tabLabel: 'Modpack', + intro: 'Bundle installed mods into a single zip players can download from the login page. Only the published pack is publicly downloadable — drafts and archived packs stay private.', + status: { draft: 'Draft', published: 'Published', archived: 'Archived' }, + modCount: '{n} mod(s)', + downloadsCount: '{n} download(s)', + publicDownloadLink: 'Open public download link', + fields: { name: 'Pack name', version: 'Version', modList: 'Included mods', description: 'Description' }, + placeholders: { + name: 'e.g. KitsuneDen Client Pack', + modList: 'Pick mods to include...', + description: 'Optional — what\'s in this pack, who it\'s for, version notes.', + }, + modListHint: 'Only mods currently installed on the server appear here. Add a mod via the Installed tab first, then come back to include it.', + actions: { + saveDraft: 'Save Draft', + build: 'Build Zip', + publish: 'Publish', + archive: 'Archive', + unarchive: 'Restore from Archive', + delete: 'Delete Pack', + }, + confirmDelete: 'Delete the modpack record and its zip file? Players will lose the download link until you publish a new one.', + ctaTooltip: '{size} · {n} mod(s)', + saved: 'Modpack saved as draft', + built: 'Modpack zip built', + published: 'Modpack published — download is now live on the login page', + archived: 'Modpack archived', + unarchived: 'Modpack restored to draft', + deleted: 'Modpack deleted', + failedToLoad: 'Failed to load modpack', + failedToSave: 'Failed to save modpack', + failedToBuild: 'Failed to build modpack zip', + failedToPublish: 'Failed to publish modpack', + failedToArchive: 'Failed to archive modpack', + failedToDelete: 'Failed to delete modpack', + }, + backups: { title: 'バックアップ', createNow: 'バックアップを作成', diff --git a/frontend/src/i18n/locales/ko.ts b/frontend/src/i18n/locales/ko.ts index 19c7893..e5c668e 100644 --- a/frontend/src/i18n/locales/ko.ts +++ b/frontend/src/i18n/locales/ko.ts @@ -1038,6 +1038,45 @@ const ko: Messages = { modsFound: '개의 모드를 찾았습니다', }, + modpack: { + // English placeholders — translations TBD + tabLabel: 'Modpack', + intro: 'Bundle installed mods into a single zip players can download from the login page. Only the published pack is publicly downloadable — drafts and archived packs stay private.', + status: { draft: 'Draft', published: 'Published', archived: 'Archived' }, + modCount: '{n} mod(s)', + downloadsCount: '{n} download(s)', + publicDownloadLink: 'Open public download link', + fields: { name: 'Pack name', version: 'Version', modList: 'Included mods', description: 'Description' }, + placeholders: { + name: 'e.g. KitsuneDen Client Pack', + modList: 'Pick mods to include...', + description: 'Optional — what\'s in this pack, who it\'s for, version notes.', + }, + modListHint: 'Only mods currently installed on the server appear here. Add a mod via the Installed tab first, then come back to include it.', + actions: { + saveDraft: 'Save Draft', + build: 'Build Zip', + publish: 'Publish', + archive: 'Archive', + unarchive: 'Restore from Archive', + delete: 'Delete Pack', + }, + confirmDelete: 'Delete the modpack record and its zip file? Players will lose the download link until you publish a new one.', + ctaTooltip: '{size} · {n} mod(s)', + saved: 'Modpack saved as draft', + built: 'Modpack zip built', + published: 'Modpack published — download is now live on the login page', + archived: 'Modpack archived', + unarchived: 'Modpack restored to draft', + deleted: 'Modpack deleted', + failedToLoad: 'Failed to load modpack', + failedToSave: 'Failed to save modpack', + failedToBuild: 'Failed to build modpack zip', + failedToPublish: 'Failed to publish modpack', + failedToArchive: 'Failed to archive modpack', + failedToDelete: 'Failed to delete modpack', + }, + backups: { title: '백업', createNow: '백업 생성', diff --git a/frontend/src/i18n/locales/zh-CN.ts b/frontend/src/i18n/locales/zh-CN.ts index 5dbc766..175d091 100644 --- a/frontend/src/i18n/locales/zh-CN.ts +++ b/frontend/src/i18n/locales/zh-CN.ts @@ -1038,6 +1038,45 @@ const zhCN: Messages = { modsFound: '个模组', }, + modpack: { + // English placeholders — translations TBD + tabLabel: 'Modpack', + intro: 'Bundle installed mods into a single zip players can download from the login page. Only the published pack is publicly downloadable — drafts and archived packs stay private.', + status: { draft: 'Draft', published: 'Published', archived: 'Archived' }, + modCount: '{n} mod(s)', + downloadsCount: '{n} download(s)', + publicDownloadLink: 'Open public download link', + fields: { name: 'Pack name', version: 'Version', modList: 'Included mods', description: 'Description' }, + placeholders: { + name: 'e.g. KitsuneDen Client Pack', + modList: 'Pick mods to include...', + description: 'Optional — what\'s in this pack, who it\'s for, version notes.', + }, + modListHint: 'Only mods currently installed on the server appear here. Add a mod via the Installed tab first, then come back to include it.', + actions: { + saveDraft: 'Save Draft', + build: 'Build Zip', + publish: 'Publish', + archive: 'Archive', + unarchive: 'Restore from Archive', + delete: 'Delete Pack', + }, + confirmDelete: 'Delete the modpack record and its zip file? Players will lose the download link until you publish a new one.', + ctaTooltip: '{size} · {n} mod(s)', + saved: 'Modpack saved as draft', + built: 'Modpack zip built', + published: 'Modpack published — download is now live on the login page', + archived: 'Modpack archived', + unarchived: 'Modpack restored to draft', + deleted: 'Modpack deleted', + failedToLoad: 'Failed to load modpack', + failedToSave: 'Failed to save modpack', + failedToBuild: 'Failed to build modpack zip', + failedToPublish: 'Failed to publish modpack', + failedToArchive: 'Failed to archive modpack', + failedToDelete: 'Failed to delete modpack', + }, + backups: { title: '备份', createNow: '创建备份', diff --git a/frontend/src/i18n/locales/zh-TW.ts b/frontend/src/i18n/locales/zh-TW.ts index 67bd012..0d32452 100644 --- a/frontend/src/i18n/locales/zh-TW.ts +++ b/frontend/src/i18n/locales/zh-TW.ts @@ -1038,6 +1038,45 @@ const zhTW: Messages = { modsFound: '個模組', }, + modpack: { + // English placeholders — translations TBD + tabLabel: 'Modpack', + intro: 'Bundle installed mods into a single zip players can download from the login page. Only the published pack is publicly downloadable — drafts and archived packs stay private.', + status: { draft: 'Draft', published: 'Published', archived: 'Archived' }, + modCount: '{n} mod(s)', + downloadsCount: '{n} download(s)', + publicDownloadLink: 'Open public download link', + fields: { name: 'Pack name', version: 'Version', modList: 'Included mods', description: 'Description' }, + placeholders: { + name: 'e.g. KitsuneDen Client Pack', + modList: 'Pick mods to include...', + description: 'Optional — what\'s in this pack, who it\'s for, version notes.', + }, + modListHint: 'Only mods currently installed on the server appear here. Add a mod via the Installed tab first, then come back to include it.', + actions: { + saveDraft: 'Save Draft', + build: 'Build Zip', + publish: 'Publish', + archive: 'Archive', + unarchive: 'Restore from Archive', + delete: 'Delete Pack', + }, + confirmDelete: 'Delete the modpack record and its zip file? Players will lose the download link until you publish a new one.', + ctaTooltip: '{size} · {n} mod(s)', + saved: 'Modpack saved as draft', + built: 'Modpack zip built', + published: 'Modpack published — download is now live on the login page', + archived: 'Modpack archived', + unarchived: 'Modpack restored to draft', + deleted: 'Modpack deleted', + failedToLoad: 'Failed to load modpack', + failedToSave: 'Failed to save modpack', + failedToBuild: 'Failed to build modpack zip', + failedToPublish: 'Failed to publish modpack', + failedToArchive: 'Failed to archive modpack', + failedToDelete: 'Failed to delete modpack', + }, + backups: { title: '備份', createNow: '建立備份', diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 76c3876..acce5e7 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -265,6 +265,47 @@ export interface ChatCommandSettings { voteCooldownSeconds: number } +// ─── Modpack Types ─────────────────────────────────── + +export type ModpackStatus = 'draft' | 'published' | 'archived' + +export interface Modpack { + id: number + name: string + version: string + status: ModpackStatus + filename: string | null + sizeBytes: number + modCount: number + modList: string // JSON string of mod folder names + description: string | null + createdAt: string + updatedAt: string + downloadCount: number +} + +export interface ModpackInstalledMod { + name: string // folder name + displayName: string + version: string +} + +/** Admin-facing state envelope returned by GET /api/modpack. */ +export interface ModpackState { + modpack: Modpack | null + modList: string[] // parsed-out folder names included in the pack + installedMods: ModpackInstalledMod[] +} + +/** Public-facing trimmed envelope returned by GET /api/modpack/public. */ +export interface PublicModpack { + name: string + version: string + sizeBytes: number + modCount: number + description: string | null +} + // ─── Vote Rewards Types ────────────────────────────── /** Reward delivery shape — mirrors the C# VoteRewardType static class. */ diff --git a/frontend/src/views/LoginView.vue b/frontend/src/views/LoginView.vue index 18fcd26..109eb0c 100644 --- a/frontend/src/views/LoginView.vue +++ b/frontend/src/views/LoginView.vue @@ -1,9 +1,11 @@