diff --git a/apps/frontend/src/components/ui/project-settings/disclosures/ArchivedDisclosureCard.vue b/apps/frontend/src/components/ui/project-settings/disclosures/ArchivedDisclosureCard.vue new file mode 100644 index 0000000000..d2903528e5 --- /dev/null +++ b/apps/frontend/src/components/ui/project-settings/disclosures/ArchivedDisclosureCard.vue @@ -0,0 +1,82 @@ + + + diff --git a/apps/frontend/src/components/ui/project-settings/disclosures/form.ts b/apps/frontend/src/components/ui/project-settings/disclosures/form.ts index 89bb99c91a..9556e59662 100644 --- a/apps/frontend/src/components/ui/project-settings/disclosures/form.ts +++ b/apps/frontend/src/components/ui/project-settings/disclosures/form.ts @@ -16,7 +16,11 @@ function findDisclosure( return disclosures.find((disclosure): disclosure is DisclosureOf => disclosure.type === type) } -type NoteDisclosureType = 'advertisements' | 'epilepsy_triggers' | 'system_interactions' +type NoteDisclosureType = + | 'advertisements' + | 'epilepsy_triggers' + | 'system_interactions' + | 'archived' function createNoteModel( disclosures: ProjectDisclosureData[], @@ -61,6 +65,7 @@ export function disclosuresToForm(disclosures: ProjectDisclosureData[]): Disclos }, photosensitivity: createNoteModel(disclosures, 'epilepsy_triggers'), systemInteractions: createNoteModel(disclosures, 'system_interactions'), + archived: createNoteModel(disclosures, 'archived'), } } @@ -113,6 +118,10 @@ export function formToDisclosures(form: DisclosureFormState): ProjectDisclosure[ set.push({ type: 'system_interactions', note: form.systemInteractions.note.trim() || null }) } + if (form.archived.enabled) { + set.push({ type: 'archived', note: form.archived.note.trim() || null }) + } + return set } diff --git a/apps/frontend/src/components/ui/project-settings/disclosures/index.ts b/apps/frontend/src/components/ui/project-settings/disclosures/index.ts index cb172e45b7..97185705d2 100644 --- a/apps/frontend/src/components/ui/project-settings/disclosures/index.ts +++ b/apps/frontend/src/components/ui/project-settings/disclosures/index.ts @@ -1,5 +1,6 @@ export { default as AdvertisingDisclosureCard } from './AdvertisingDisclosureCard.vue' export { default as AiDisclosureCard } from './AiDisclosureCard.vue' +export { default as ArchivedDisclosureCard } from './ArchivedDisclosureCard.vue' export { default as DerivativeDisclosureCard } from './DerivativeDisclosureCard.vue' export * from './form' export { default as PaidFeaturesDisclosureCard } from './PaidFeaturesDisclosureCard.vue' diff --git a/apps/frontend/src/components/ui/project-settings/disclosures/types.ts b/apps/frontend/src/components/ui/project-settings/disclosures/types.ts index 21619bb2ae..2aeb67410a 100644 --- a/apps/frontend/src/components/ui/project-settings/disclosures/types.ts +++ b/apps/frontend/src/components/ui/project-settings/disclosures/types.ts @@ -43,4 +43,5 @@ export type DisclosureFormState = { derivative: DerivativeDisclosure photosensitivity: NoteDisclosure systemInteractions: NoteDisclosure + archived: NoteDisclosure } diff --git a/apps/frontend/src/composables/featureFlags.ts b/apps/frontend/src/composables/featureFlags.ts index f19838f321..d4c256d254 100644 --- a/apps/frontend/src/composables/featureFlags.ts +++ b/apps/frontend/src/composables/featureFlags.ts @@ -38,7 +38,6 @@ export const DEFAULT_FEATURE_FLAGS = validateValues({ showProjectPageCreateServersTooltip: true, showProjectPageQuickServerButton: false, newProjectGeneralSettings: false, - newProjectEnvironmentSettings: true, serverRamAsBytesAlwaysOn: false, archonSentryCapture: false, hideRussiaCensorshipBanner: false, diff --git a/apps/frontend/src/composables/project-settings-head.ts b/apps/frontend/src/composables/project-settings-head.ts index 346e83a764..9c9c94ae87 100644 --- a/apps/frontend/src/composables/project-settings-head.ts +++ b/apps/frontend/src/composables/project-settings-head.ts @@ -8,7 +8,7 @@ import { const messages = defineMessages({ headTitle: { id: 'project.settings.head-title', - defaultMessage: '⚙️{section} - {project}', + defaultMessage: '⚙️ {section} - {project}', }, }) diff --git a/apps/frontend/src/composables/queries/project.ts b/apps/frontend/src/composables/queries/project.ts index 8612e516f5..7c5e7bd274 100644 --- a/apps/frontend/src/composables/queries/project.ts +++ b/apps/frontend/src/composables/queries/project.ts @@ -1,15 +1,57 @@ import type { AbstractModrinthClient } from '@modrinth/api-client' +import type { QueryClient } from '@tanstack/query-core' export const STALE_TIME = 1000 * 60 * 5 // 5 minutes export const STALE_TIME_LONG = 1000 * 60 * 10 // 10 minutes +/** Anything with a canonical project ID and optional slug (search hits, full projects, etc.). */ +export type ProjectCheckIdentity = { + id?: string | null + project_id?: string | null + slug?: string | null +} + +/** + * Warm `['project', 'check', …]` so navigating to a project by slug skips the check network round-trip. + * Safe to call with search hits, full project objects, or mixed lists. + */ +export function warmProjectCheckCaches( + queryClient: QueryClient, + projects: ProjectCheckIdentity | readonly ProjectCheckIdentity[] | null | undefined, +) { + const list = projects == null ? [] : Array.isArray(projects) ? projects : [projects] + for (const project of list) { + const id = project.id ?? project.project_id + if (!id) continue + queryClient.setQueryData(['project', 'check', id], { id }) + if (project.slug) { + queryClient.setQueryData(['project', 'check', project.slug], { id }) + } + } +} + +/** + * Project query options. + * + * `v2` / `v3` / members / etc. must be keyed by the **canonical project ID**. + * When you only have a route slug (or unknown id-or-slug), resolve with `check` first. + */ export const projectQueryOptions = { + /** Resolve a slug or ID to the canonical project ID. */ + check: (idOrSlug: string, client: AbstractModrinthClient) => ({ + queryKey: ['project', 'check', idOrSlug] as const, + queryFn: () => client.labrinth.projects_v2.check(idOrSlug), + staleTime: STALE_TIME, + }), + + /** @param projectId Canonical project ID (not slug) */ v2: (projectId: string, client: AbstractModrinthClient) => ({ queryKey: ['project', 'v2', projectId] as const, queryFn: () => client.labrinth.projects_v2.get(projectId), staleTime: STALE_TIME, }), + /** @param projectId Canonical project ID (not slug) */ v3: (projectId: string, client: AbstractModrinthClient) => ({ queryKey: ['project', 'v3', projectId] as const, queryFn: () => client.labrinth.projects_v3.get(projectId), diff --git a/apps/frontend/src/locales/da-DK/index.json b/apps/frontend/src/locales/da-DK/index.json index 2c23b92d3b..1caf2a6fcb 100644 --- a/apps/frontend/src/locales/da-DK/index.json +++ b/apps/frontend/src/locales/da-DK/index.json @@ -2486,9 +2486,6 @@ "project.settings.tags.upload-version-first": { "message": "Upload venligst en version først for at kunne vælge tags!" }, - "project.status.archived.message": { - "message": "{title} er blevet arkiveret. {title} vil ikke modtage flere opdateringer medmindre forfatteren beslutter sig for at fjerne projektet fra arkivet." - }, "project.versions.copy-id-option": { "message": "Kopier ID" }, diff --git a/apps/frontend/src/locales/de-CH/index.json b/apps/frontend/src/locales/de-CH/index.json index 38699f0755..f597a9b449 100644 --- a/apps/frontend/src/locales/de-CH/index.json +++ b/apps/frontend/src/locales/de-CH/index.json @@ -3974,9 +3974,6 @@ "project.settings.tags.upload-version-first": { "message": "Bitte lade zuerst eine Version hoch, um Tags auswählen zu können!" }, - "project.status.archived.message": { - "message": "{title} wurde archiviert. {title} wird keine weiteren Updates erhalten, es sei denn, der Autor entscheided sich, das Projekt zu Dearchivieren." - }, "project.versions.copy-id-option": { "message": "ID kopieren" }, diff --git a/apps/frontend/src/locales/de-DE/index.json b/apps/frontend/src/locales/de-DE/index.json index a1b923f9e4..e8cf4ad437 100644 --- a/apps/frontend/src/locales/de-DE/index.json +++ b/apps/frontend/src/locales/de-DE/index.json @@ -3974,9 +3974,6 @@ "project.settings.tags.upload-version-first": { "message": "Bitte lade zuerst eine Version hoch, um Tags auswählen zu können!" }, - "project.status.archived.message": { - "message": "{title} wurde archiviert. {title} wird keine weiteren Updates erhalten, es sei denn, der Autor entscheidet sich das Projekt zu dearchivieren." - }, "project.versions.copy-id-option": { "message": "ID kopieren" }, diff --git a/apps/frontend/src/locales/en-US/index.json b/apps/frontend/src/locales/en-US/index.json index e91fed890e..098f68d084 100644 --- a/apps/frontend/src/locales/en-US/index.json +++ b/apps/frontend/src/locales/en-US/index.json @@ -3761,6 +3761,30 @@ "project.settings.back-to-project-page": { "message": "Back to project page" }, + "project.settings.danger-zone": { + "message": "Danger zone" + }, + "project.settings.delete-project.button": { + "message": "Delete project" + }, + "project.settings.delete-project.confirmation.description": { + "message": "If you proceed, all of this project's information and all of its versions will be immediately deleted from our database. None of it is recoverable later if you change your mind. Consider just setting your project to be Private for a less permanent option." + }, + "project.settings.delete-project.confirmation.proceed-text": { + "message": "Permanently delete project" + }, + "project.settings.delete-project.confirmation.title": { + "message": "Are you sure you want to delete this project?" + }, + "project.settings.delete-project.description.1": { + "message": "Permanently deletes this project from Modrinth. Deleted projects cannot be recovered by Modrinth staff or support." + }, + "project.settings.delete-project.description.2": { + "message": "Files uploaded to this project that are actively used in Modpacks hosted on Modrinth may continue to exist." + }, + "project.settings.delete-project.title": { + "message": "Delete project" + }, "project.settings.disclosures.advertising.description.1": { "message": "You must enable this if your project contains advertisements, sponsorships, or promotions of other works." }, @@ -3806,6 +3830,24 @@ "project.settings.disclosures.ai.types-text": { "message": "Text" }, + "project.settings.disclosures.archived.banner-preview": { + "message": "Banner preview" + }, + "project.settings.disclosures.archived.description.1": { + "message": "Mark your project as archived to let users know that you are no longer working on it." + }, + "project.settings.disclosures.archived.description.2": { + "message": "Archived projects remain discoverable when their visibility is set to Public. If you wish to de-list your project, set its visibility to Unlisted" + }, + "project.settings.disclosures.archived.note-label": { + "message": "Optionally explain why this project is archived." + }, + "project.settings.disclosures.archived.note-placeholder": { + "message": "e.g. I don't have time to maintain this project anymore, feel free to fork it!" + }, + "project.settings.disclosures.archived.title": { + "message": "Archive project" + }, "project.settings.disclosures.content-disclosures": { "message": "Content disclosures" }, @@ -3969,7 +4011,16 @@ "message": "URL" }, "project.settings.head-title": { - "message": "⚙️{section} - {project}" + "message": "⚙️ {section} - {project}" + }, + "project.settings.monetization.description": { + "message": "Projects on Modrinth are automatically enrolled in the Rewards Program. If you don't want to (or can't for legal reasons) earn revenue from this project, you can turn it off here." + }, + "project.settings.monetization.disabled-description": { + "message": "This project is not eligible for monetization. If you think this is a mistake, please contact support." + }, + "project.settings.monetization.title": { + "message": "Monetization" }, "project.settings.permissions.attention-needed.description.proj-approved": { "message": "Please provide proof that you have permission to redistribute all of the following files. Once completed, withheld versions will be automatically published." @@ -4154,9 +4205,6 @@ "project.settings.tags.upload-version-first": { "message": "Please upload a version first in order to select tags!" }, - "project.status.archived.message": { - "message": "{title} has been archived. {title} will not receive any further updates unless the author decides to unarchive the project." - }, "project.versions.copy-id-option": { "message": "Copy ID" }, diff --git a/apps/frontend/src/locales/es-419/index.json b/apps/frontend/src/locales/es-419/index.json index e0501b41da..7f1e7e5fa3 100644 --- a/apps/frontend/src/locales/es-419/index.json +++ b/apps/frontend/src/locales/es-419/index.json @@ -3974,9 +3974,6 @@ "project.settings.tags.upload-version-first": { "message": "Por favor, ¡sube una versión primero para poder seleccionar etiquetas!" }, - "project.status.archived.message": { - "message": "{title} se ha archivado. {title} no recibirá más actualizaciones hasta que el autor decida desarchivar el proyecto." - }, "project.versions.copy-id-option": { "message": "Copiar ID" }, diff --git a/apps/frontend/src/locales/es-ES/index.json b/apps/frontend/src/locales/es-ES/index.json index 3f1982c547..d147207704 100644 --- a/apps/frontend/src/locales/es-ES/index.json +++ b/apps/frontend/src/locales/es-ES/index.json @@ -3329,9 +3329,6 @@ "project.settings.permissions.sort.status": { "message": "Estado" }, - "project.status.archived.message": { - "message": "{title} ha sido archivado. {title} no recibirá ninguna futura actualización excepto que el autor decida desarchivar el proyecto." - }, "project.versions.title": { "message": "Versiones" }, diff --git a/apps/frontend/src/locales/fil-PH/index.json b/apps/frontend/src/locales/fil-PH/index.json index e3a1e2acee..ddf870632e 100644 --- a/apps/frontend/src/locales/fil-PH/index.json +++ b/apps/frontend/src/locales/fil-PH/index.json @@ -2411,9 +2411,6 @@ "project.settings.permissions.sort.status": { "message": "Estado" }, - "project.status.archived.message": { - "message": "Inarkibo ang {title}. Ang {title} ay hindi na tatanggap ng mga update maliban kung iwawala nila ang pagkaarkibo ng proyekto." - }, "project.versions.title": { "message": "Mga bersiyon" }, diff --git a/apps/frontend/src/locales/fr-FR/index.json b/apps/frontend/src/locales/fr-FR/index.json index 568739fda2..af7438269d 100644 --- a/apps/frontend/src/locales/fr-FR/index.json +++ b/apps/frontend/src/locales/fr-FR/index.json @@ -3974,9 +3974,6 @@ "project.settings.tags.upload-version-first": { "message": "Merci d'upload une version en premier afin de pouvoir sélectionner des tags !" }, - "project.status.archived.message": { - "message": "{title} a été archivé. {title} ne recevra plus de mises à jour jusqu'à ce que l'auteur du projet décide de désarchiver le projet." - }, "project.versions.copy-id-option": { "message": "Copier l'ID" }, diff --git a/apps/frontend/src/locales/he-IL/index.json b/apps/frontend/src/locales/he-IL/index.json index 2e68a18674..26c013f216 100644 --- a/apps/frontend/src/locales/he-IL/index.json +++ b/apps/frontend/src/locales/he-IL/index.json @@ -2021,9 +2021,6 @@ "project.settings.general.url.title": { "message": "URL" }, - "project.status.archived.message": { - "message": "הפרויקט {title} הועבר לארכיון. {title} לא יקבל עדכונים נוספים, אלא אם המחבר יחליט להוציא את הפרויקט מהארכיון." - }, "project.versions.title": { "message": "גרסאות" }, diff --git a/apps/frontend/src/locales/hu-HU/index.json b/apps/frontend/src/locales/hu-HU/index.json index eaa0658bdd..9f73377c32 100644 --- a/apps/frontend/src/locales/hu-HU/index.json +++ b/apps/frontend/src/locales/hu-HU/index.json @@ -3680,9 +3680,6 @@ "project.settings.tags.upload-version-first": { "message": "A címkék kiválasztásához előbb tölts fel egy verziót!" }, - "project.status.archived.message": { - "message": "A(z) {title} archiválásra került. A(z) {title} nem kap további frissítéseket, kivéve, ha a fejlesztő úgy dönt, hogy visszavonja a projekt archiválását." - }, "project.versions.copy-id-option": { "message": "Azonosító másolása" }, diff --git a/apps/frontend/src/locales/id-ID/index.json b/apps/frontend/src/locales/id-ID/index.json index 31671b421a..032f7078f1 100644 --- a/apps/frontend/src/locales/id-ID/index.json +++ b/apps/frontend/src/locales/id-ID/index.json @@ -2414,9 +2414,6 @@ "project.settings.general.url.title": { "message": "URL" }, - "project.status.archived.message": { - "message": "{title} telah diarsip. {title} tidak akan menerima pembaruan di masa mendatang kecuali pembuat memutuskan untuk membuka arsip proyek ini." - }, "project.versions.title": { "message": "Versi" }, diff --git a/apps/frontend/src/locales/it-IT/index.json b/apps/frontend/src/locales/it-IT/index.json index d53096d3c2..5f13f93ef8 100644 --- a/apps/frontend/src/locales/it-IT/index.json +++ b/apps/frontend/src/locales/it-IT/index.json @@ -3950,9 +3950,6 @@ "project.settings.tags.upload-version-first": { "message": "Carica una vesione prima di selezionare i tag!" }, - "project.status.archived.message": { - "message": "{title} è stato archiviato. {title} non riceverà più aggiornamenti a meno che l'autore decida di rimuoverlo dall'archivio." - }, "project.versions.copy-id-option": { "message": "Copia ID" }, diff --git a/apps/frontend/src/locales/ja-JP/index.json b/apps/frontend/src/locales/ja-JP/index.json index c59ddfefb8..7828035588 100644 --- a/apps/frontend/src/locales/ja-JP/index.json +++ b/apps/frontend/src/locales/ja-JP/index.json @@ -3005,9 +3005,6 @@ "project.settings.permissions.sort.status": { "message": "ステータス" }, - "project.status.archived.message": { - "message": "{title} はアーカイブされました。作者がプロジェクトのアーカイブを解除しない限り、今後 {title} に更新はありません。" - }, "project.versions.title": { "message": "バージョン" }, diff --git a/apps/frontend/src/locales/ko-KR/index.json b/apps/frontend/src/locales/ko-KR/index.json index ad122d6e08..ef7a66bdcf 100644 --- a/apps/frontend/src/locales/ko-KR/index.json +++ b/apps/frontend/src/locales/ko-KR/index.json @@ -3974,9 +3974,6 @@ "project.settings.tags.upload-version-first": { "message": "먼저 버전을 업로드해야 태그를 선택할 수 있습니다!" }, - "project.status.archived.message": { - "message": "{title} 은(는) 보관되었습니다. 작성자가 프로젝트 보관 해제를 결정하지 않는 한 더 이상 업데이트가 제공되지 않습니다." - }, "project.versions.copy-id-option": { "message": "ID 복사" }, diff --git a/apps/frontend/src/locales/ms-MY/index.json b/apps/frontend/src/locales/ms-MY/index.json index 1fb14fa267..5e7baf7018 100644 --- a/apps/frontend/src/locales/ms-MY/index.json +++ b/apps/frontend/src/locales/ms-MY/index.json @@ -3068,9 +3068,6 @@ "project.settings.tags.title": { "message": "Tag" }, - "project.status.archived.message": { - "message": "{title} telah diarkibkan. {title} tidak akan menerima sebarang kemas kini lanjut melainkan pengarang memutuskan untuk menyaharkibkan projek." - }, "project.versions.share-option": { "message": "Kongsi" }, diff --git a/apps/frontend/src/locales/nl-NL/index.json b/apps/frontend/src/locales/nl-NL/index.json index d7bd0e3836..3389ec38b2 100644 --- a/apps/frontend/src/locales/nl-NL/index.json +++ b/apps/frontend/src/locales/nl-NL/index.json @@ -3971,9 +3971,6 @@ "project.settings.tags.upload-version-first": { "message": "Upload eerst een versie om tags te kunnen selecteren!" }, - "project.status.archived.message": { - "message": "{title} is gearchiveerd. {title} zal geen verdere updates krijgen tenzij de auteur het project de-archiveerd." - }, "project.versions.copy-id-option": { "message": "ID kopiëren" }, diff --git a/apps/frontend/src/locales/no-NO/index.json b/apps/frontend/src/locales/no-NO/index.json index f044f865dd..e5bac3c1c6 100644 --- a/apps/frontend/src/locales/no-NO/index.json +++ b/apps/frontend/src/locales/no-NO/index.json @@ -2888,9 +2888,6 @@ "project.settings.permissions.sort.status": { "message": "Status" }, - "project.status.archived.message": { - "message": "{title} har blitt arkivert. {title} kommer ikke til å få noen nye oppdateringer, hvis ikke forfatteren bestemmer seg for å dearkivere prosjektet." - }, "project.versions.title": { "message": "Versjoner" }, diff --git a/apps/frontend/src/locales/pl-PL/index.json b/apps/frontend/src/locales/pl-PL/index.json index 9f360190eb..5dce4e6a1b 100644 --- a/apps/frontend/src/locales/pl-PL/index.json +++ b/apps/frontend/src/locales/pl-PL/index.json @@ -3899,9 +3899,6 @@ "project.settings.tags.upload-version-first": { "message": "Prześlij wersję, by móc wybierać tagi!" }, - "project.status.archived.message": { - "message": "{title} zostało zarchiwizowane. {title} nie będzie otrzymywać żadnych dalszych aktualizacji, chyba że autor zdecyduje się przywrócić projekt." - }, "project.versions.copy-id-option": { "message": "Kopiuj ID" }, diff --git a/apps/frontend/src/locales/pt-BR/index.json b/apps/frontend/src/locales/pt-BR/index.json index 6da093a9f8..92c5499117 100644 --- a/apps/frontend/src/locales/pt-BR/index.json +++ b/apps/frontend/src/locales/pt-BR/index.json @@ -3974,9 +3974,6 @@ "project.settings.tags.upload-version-first": { "message": "Por favor, envie uma versão primeiro para que as etiquetas possam ser selecionadas!" }, - "project.status.archived.message": { - "message": "{title} foi arquivado. {title} não receberá atualizações a menos que o autor decida desarquivar o projeto." - }, "project.versions.copy-id-option": { "message": "Copiar ID" }, diff --git a/apps/frontend/src/locales/pt-PT/index.json b/apps/frontend/src/locales/pt-PT/index.json index 4b3a18d8c3..294f6bf5e9 100644 --- a/apps/frontend/src/locales/pt-PT/index.json +++ b/apps/frontend/src/locales/pt-PT/index.json @@ -2756,9 +2756,6 @@ "project.settings.general.url.title": { "message": "URL" }, - "project.status.archived.message": { - "message": "{title} foi arquivado. {title} não receberá mais atualizações, a menos que o autor decida desarquivar o projeto." - }, "project.versions.title": { "message": "Versões" }, diff --git a/apps/frontend/src/locales/ro-RO/index.json b/apps/frontend/src/locales/ro-RO/index.json index 8146721562..93c098754f 100644 --- a/apps/frontend/src/locales/ro-RO/index.json +++ b/apps/frontend/src/locales/ro-RO/index.json @@ -1448,9 +1448,6 @@ "project.settings.general.url.title": { "message": "URL" }, - "project.status.archived.message": { - "message": "Proiectul {title} a fost arhivat. Proiectul {title} nu va mai primi actualizări, cu excepția cazului în care autorul decide să-l dezarhiveze." - }, "project.versions.title": { "message": "Versiuni" }, diff --git a/apps/frontend/src/locales/ru-RU/index.json b/apps/frontend/src/locales/ru-RU/index.json index 620a3e15fc..195a9b8374 100644 --- a/apps/frontend/src/locales/ru-RU/index.json +++ b/apps/frontend/src/locales/ru-RU/index.json @@ -3953,9 +3953,6 @@ "project.settings.tags.upload-version-first": { "message": "Загрузите версию перед тем, как выбрать теги!" }, - "project.status.archived.message": { - "message": "{title} помещён в архив. {title} больше не будет получать обновления, если только автор не решит разархивировать проект." - }, "project.versions.copy-id-option": { "message": "Копировать ID" }, diff --git a/apps/frontend/src/locales/sv-SE/index.json b/apps/frontend/src/locales/sv-SE/index.json index 334fb62095..a0352c9d20 100644 --- a/apps/frontend/src/locales/sv-SE/index.json +++ b/apps/frontend/src/locales/sv-SE/index.json @@ -3227,9 +3227,6 @@ "project.settings.tags.resolutions-description": { "message": "Välj upplösning(en/arna) på dina texturer i ditt {type}." }, - "project.status.archived.message": { - "message": "{title} har arkiverats. {title} kommer inte få några ytterligare uppdateringar såvida inte författaren bestämmer sig för att avarkivera projektet." - }, "project.versions.copy-id-option": { "message": "Kopiera ID" }, diff --git a/apps/frontend/src/locales/tr-TR/index.json b/apps/frontend/src/locales/tr-TR/index.json index 355e3182a7..25590be2f7 100644 --- a/apps/frontend/src/locales/tr-TR/index.json +++ b/apps/frontend/src/locales/tr-TR/index.json @@ -3539,9 +3539,6 @@ "project.settings.tags.title": { "message": "Etiketler" }, - "project.status.archived.message": { - "message": "{title} arşivlenmiş. {title}, yapımcı fikrini değiştirmediği sürece daha fazla güncelleme almayacak." - }, "project.versions.copy-id-option": { "message": "ID'yi Kopyala" }, diff --git a/apps/frontend/src/locales/uk-UA/index.json b/apps/frontend/src/locales/uk-UA/index.json index 6d1fc745f0..89002f292d 100644 --- a/apps/frontend/src/locales/uk-UA/index.json +++ b/apps/frontend/src/locales/uk-UA/index.json @@ -3971,9 +3971,6 @@ "project.settings.tags.upload-version-first": { "message": "Спершу завантажте версію, щоб обирати теґи!" }, - "project.status.archived.message": { - "message": "«{title}» було архівовано. «{title}» не отримуватиме подальших оновлень допоки автор не вирішить розархівувати проєкт." - }, "project.versions.copy-id-option": { "message": "Копіювати ID" }, diff --git a/apps/frontend/src/locales/vi-VN/index.json b/apps/frontend/src/locales/vi-VN/index.json index e155143aa2..4c743d2cd9 100644 --- a/apps/frontend/src/locales/vi-VN/index.json +++ b/apps/frontend/src/locales/vi-VN/index.json @@ -3179,9 +3179,6 @@ "project.settings.permissions.learn-more": { "message": "Tìm hiểu thêm" }, - "project.status.archived.message": { - "message": "{title} đã được lưu trữ. {title} sẽ không nhận được bất kỳ bản cập nhật nào trong tương lai trừ khi tác giả quyết định hủy lưu trữ dự án." - }, "project.versions.title": { "message": "Phiên bản" }, diff --git a/apps/frontend/src/locales/zh-CN/index.json b/apps/frontend/src/locales/zh-CN/index.json index b8068cff14..8e3ebae881 100644 --- a/apps/frontend/src/locales/zh-CN/index.json +++ b/apps/frontend/src/locales/zh-CN/index.json @@ -3974,9 +3974,6 @@ "project.settings.tags.upload-version-first": { "message": "请先上传一个版本再选择标签!" }, - "project.status.archived.message": { - "message": "{title} 已归档。除非作者决定取消归档,否则 {title} 将不再更新。" - }, "project.versions.copy-id-option": { "message": "复制 ID" }, diff --git a/apps/frontend/src/locales/zh-TW/index.json b/apps/frontend/src/locales/zh-TW/index.json index f69ad2d755..999a28b3b6 100644 --- a/apps/frontend/src/locales/zh-TW/index.json +++ b/apps/frontend/src/locales/zh-TW/index.json @@ -3974,9 +3974,6 @@ "project.settings.tags.upload-version-first": { "message": "請先上傳一個版本再選擇標籤!" }, - "project.status.archived.message": { - "message": "{title} 已封存。{title} 將不會再收到任何後續更新,除非作者決定解除封存該專案。" - }, "project.versions.copy-id-option": { "message": "複製 ID" }, diff --git a/apps/frontend/src/middleware/project.global.ts b/apps/frontend/src/middleware/project.global.ts index f8872b153a..789933b73b 100644 --- a/apps/frontend/src/middleware/project.global.ts +++ b/apps/frontend/src/middleware/project.global.ts @@ -1,5 +1,5 @@ import { useGeneratedState } from '~/composables/generated' -import { projectQueryOptions } from '~/composables/queries/project' +import { projectQueryOptions, warmProjectCheckCaches } from '~/composables/queries/project' import { useAppQueryClient } from '~/composables/query-client' import { createModrinthClient } from '~/helpers/api.ts' import { getProjectTypeForUrlShorthand } from '~/helpers/projects.js' @@ -20,11 +20,11 @@ const PROJECT_TYPES = [ export default defineNuxtRouteMiddleware(async (to) => { const routeProjectParam = to.params.project - const projectId = Array.isArray(routeProjectParam) ? routeProjectParam[0] : routeProjectParam + const routeParam = Array.isArray(routeProjectParam) ? routeProjectParam[0] : routeProjectParam const routeType = Array.isArray(to.params.type) ? to.params.type[0] : to.params.type // Only handle project routes - if (!projectId || !routeType || !PROJECT_TYPES.includes(routeType)) { + if (!routeParam || !routeType || !PROJECT_TYPES.includes(routeType)) { return } @@ -35,7 +35,13 @@ export default defineNuxtRouteMiddleware(async (to) => { if (import.meta.client) startLoading() try { - // Fetch v2 and v3 in parallel — cache both for the page's useQuery calls + // Resolve slug/ID to the canonical project ID, then fetch by ID only + const { id: projectId } = await queryClient.fetchQuery( + projectQueryOptions.check(routeParam, client), + ) + + warmProjectCheckCaches(queryClient, { id: projectId }) + const [project, projectV3] = await Promise.all([ queryClient.fetchQuery(projectQueryOptions.v2(projectId, client)), queryClient.fetchQuery(projectQueryOptions.v3(projectId, client)), @@ -44,15 +50,7 @@ export default defineNuxtRouteMiddleware(async (to) => { // Let page handle 404 if (!project) return - // Cache by slug if we looked up by ID (or vice versa) - if (projectId !== project.slug) { - queryClient.setQueryData(['project', 'v2', project.slug], project) - queryClient.setQueryData(['project', 'v3', project.slug], projectV3) - } - if (projectId !== project.id) { - queryClient.setQueryData(['project', 'v2', project.id], project) - queryClient.setQueryData(['project', 'v3', project.id], projectV3) - } + warmProjectCheckCaches(queryClient, project) const projectType = projectV3.minecraft_server != null ? 'server' : project.project_type // Determine the correct URL type diff --git a/apps/frontend/src/pages/[type]/[project].vue b/apps/frontend/src/pages/[type]/[project].vue index 577f590668..f14dbc4393 100644 --- a/apps/frontend/src/pages/[type]/[project].vue +++ b/apps/frontend/src/pages/[type]/[project].vue @@ -90,7 +90,7 @@ @@ -136,7 +136,7 @@ v-if=" projectV3 && currentMember && - (project.status === 'draft' || tags.rejectedStatuses.includes(project.status)) + (projectV3.status === 'draft' || tags.rejectedStatuses.includes(projectV3.status)) " :project="project" :project-v3="projectV3" @@ -153,7 +153,7 @@ v-if="projectV3Loaded" :project="project" :project-v3="projectV3" - :show-status-badge="!!currentMember || project.status !== 'approved'" + :show-status-badge="!!currentMember || projectV3.status !== 'approved'" @category="(category) => router.push(`${projectSearchUrl}?f=categories:${category}`)" > -
+ -
-
- - -
-
-