diff --git a/apps/frontend/src/components/ui/moderation/ModerationProjectNags.vue b/apps/frontend/src/components/ui/moderation/ModerationProjectNags.vue index 5f316b31d..44a50f083 100644 --- a/apps/frontend/src/components/ui/moderation/ModerationProjectNags.vue +++ b/apps/frontend/src/components/ui/moderation/ModerationProjectNags.vue @@ -233,7 +233,7 @@ const visibleNags = computed(() => { link: { path: 'moderation', title: messages.visitModerationPage, - shouldShow: () => props.routeName !== 'type-id-moderation', + shouldShow: () => props.routeName !== 'type-project-moderation', }, }) } diff --git a/apps/frontend/src/components/ui/moderation/checklist/ModerationChecklist.vue b/apps/frontend/src/components/ui/moderation/checklist/ModerationChecklist.vue index 1ec2bc93f..39d98855e 100644 --- a/apps/frontend/src/components/ui/moderation/checklist/ModerationChecklist.vue +++ b/apps/frontend/src/components/ui/moderation/checklist/ModerationChecklist.vue @@ -739,8 +739,8 @@ async function navigateToNextUnlockedProject(): Promise { } else { // Fallback: use project ID (will trigger middleware redirect) navigateTo({ - name: 'type-id', - params: { type: 'project', id: next.projectId }, + name: 'type-project', + params: { type: 'project', project: next.projectId }, state: { showChecklist: true }, }) } @@ -1067,8 +1067,8 @@ async function skipToNextProject() { } else { // Fallback: use project ID navigateTo({ - name: 'type-id', - params: { type: 'project', id }, + name: 'type-project', + params: { type: 'project', project: id }, state: { showChecklist: true }, }) } @@ -2127,8 +2127,8 @@ async function endChecklist(status?: string) { } else { // Fallback: use project ID navigateTo({ - name: 'type-id', - params: { type: 'project', id }, + name: 'type-project', + params: { type: 'project', project: id }, state: { showChecklist: true }, }) } diff --git a/apps/frontend/src/layouts/default.vue b/apps/frontend/src/layouts/default.vue index 9ccf69797..a89554236 100644 --- a/apps/frontend/src/layouts/default.vue +++ b/apps/frontend/src/layouts/default.vue @@ -1148,7 +1148,7 @@ const isDiscovering = computed( ) const isDiscoveringSubpage = computed( - () => route.name && route.name.startsWith('type-id') && !route.query.sid, + () => route.name && route.name.startsWith('type-project') && !route.query.sid, ) const isRussia = computed(() => country.value === 'ru') diff --git a/apps/frontend/src/pages/[type]/[id].vue b/apps/frontend/src/pages/[type]/[project].vue similarity index 97% rename from apps/frontend/src/pages/[type]/[id].vue rename to apps/frontend/src/pages/[type]/[project].vue index 0e4ae8577..c980f3271 100644 --- a/apps/frontend/src/pages/[type]/[id].vue +++ b/apps/frontend/src/pages/[type]/[project].vue @@ -485,7 +485,7 @@ v-if="!isServerProject" size="large" :color=" - (auth.user && currentMember) || route.name === 'type-id-version-version' + (auth.user && currentMember) || route.name === 'type-project-version-version' ? `standard` : `brand` " @@ -507,7 +507,7 @@ v-else size="large" :color=" - (auth.user && currentMember) || route.name === 'type-id-version-version' + (auth.user && currentMember) || route.name === 'type-project-version-version' ? `standard` : `brand` " @@ -529,7 +529,7 @@ size="large" circular :color=" - route.name === 'type-id-version-version' || (auth.user && currentMember) + route.name === 'type-project-version-version' || (auth.user && currentMember) ? `standard` : `brand` " @@ -547,7 +547,7 @@ size="large" circular :color=" - route.name === 'type-id-version-version' || (auth.user && currentMember) + route.name === 'type-project-version-version' || (auth.user && currentMember) ? `standard` : `brand` " @@ -1136,7 +1136,7 @@ import { useModerationQueue } from '~/services/moderation-queue.ts' import { getReportPath, reportProject } from '~/utils/report-helpers.ts' definePageMeta({ - key: (route) => `${route.params.id}`, + key: (route) => `${route.params.project}`, }) const data = useNuxtApp() @@ -1151,6 +1151,9 @@ const { addNotification } = notifications const auth = await useAuth() const user = await useUser() +// Route param for initial lookup (middleware caches by both slug and ID) +const routeProjectId = useRouteId('project') + const { createProjectDownloadUrl } = useCdnDownloadContext() const downloadReason = ref('standalone') @@ -1637,7 +1640,7 @@ const collections = computed(() => ) if ( - !route.params.id || + !routeProjectId || !( tags.value.projectTypes.find((x) => x.id === route.params.type) || route.params.type === 'project' @@ -1650,17 +1653,14 @@ if ( }) } -// Route param for initial lookup (middleware caches by both slug and ID) -const routeProjectId = computed(() => route.params.id) - // Use DI client for TanStack Query const client = injectModrinthClient() const queryClient = useQueryClient() // V2 Project - hits middleware cache (uses route param for lookup) const { data: projectRaw, error: projectV2Error } = useQuery({ - queryKey: computed(() => ['project', 'v2', routeProjectId.value]), - queryFn: () => client.labrinth.projects_v2.get(routeProjectId.value), + queryKey: computed(() => ['project', 'v2', routeProjectId]), + queryFn: () => client.labrinth.projects_v2.get(routeProjectId), staleTime: STALE_TIME, }) @@ -1709,8 +1709,8 @@ const { error: _projectV3Error, isPending: projectV3Pending, } = useQuery({ - queryKey: computed(() => ['project', 'v3', routeProjectId.value]), - queryFn: () => client.labrinth.projects_v3.get(routeProjectId.value), + queryKey: computed(() => ['project', 'v3', routeProjectId]), + queryFn: () => client.labrinth.projects_v3.get(routeProjectId), staleTime: STALE_TIME, }) @@ -1870,7 +1870,7 @@ const { data: organizationRaw } = useQuery({ // Return null when the project no longer belongs to an organization. const organization = computed(() => (projectRaw.value?.organization ? organizationRaw.value : null)) -const isSettings = computed(() => route.name.startsWith('type-id-settings')) +const isSettings = computed(() => route.name.startsWith('type-project-settings')) // Transform versionsV3 to be same shape as versionsV2 for compatibility in project pages const versionsRaw = computed(() => { @@ -1914,7 +1914,7 @@ const hasVersions = computed(() => (project.value?.versions?.length ?? 0) > 0) async function updateProjectRoute() { if ( project.value && - route.params.id !== project.value.slug && + routeProjectId !== project.value.slug && !flags.value.disablePrettyProjectUrlRedirects ) { await navigateTo( @@ -1933,9 +1933,9 @@ async function updateProjectRoute() { } async function invalidateProject() { - await queryClient.invalidateQueries({ queryKey: ['project', 'v2', routeProjectId.value] }) - await queryClient.invalidateQueries({ queryKey: ['project', 'v3', routeProjectId.value] }) - if (routeProjectId.value !== projectId.value) { + await queryClient.invalidateQueries({ queryKey: ['project', 'v2', routeProjectId] }) + await queryClient.invalidateQueries({ queryKey: ['project', 'v3', routeProjectId] }) + if (routeProjectId !== projectId.value) { await queryClient.invalidateQueries({ queryKey: ['project', 'v2', projectId.value] }) await queryClient.invalidateQueries({ queryKey: ['project', 'v3', projectId.value] }) } @@ -1953,16 +1953,16 @@ const patchProjectMutation = useMutation({ onMutate: async ({ projectId, data }) => { // Cancel outgoing refetches for both slug-based and ID-based cache keys // The query may be keyed by slug (routeProjectId) but we also have the actual UUID (projectId) - await queryClient.cancelQueries({ queryKey: ['project', 'v2', routeProjectId.value] }) - if (routeProjectId.value !== projectId) { + await queryClient.cancelQueries({ queryKey: ['project', 'v2', routeProjectId] }) + if (routeProjectId !== projectId) { await queryClient.cancelQueries({ queryKey: ['project', 'v2', projectId] }) } // Snapshot previous value from the active query (uses route param as key) - const previousProject = queryClient.getQueryData(['project', 'v2', routeProjectId.value]) + const previousProject = queryClient.getQueryData(['project', 'v2', routeProjectId]) // Optimistic update on the active query key - queryClient.setQueryData(['project', 'v2', routeProjectId.value], (old) => { + queryClient.setQueryData(['project', 'v2', routeProjectId], (old) => { if (!old) return old return { ...old, ...data } }) @@ -1973,7 +1973,7 @@ const patchProjectMutation = useMutation({ onError: (err, _variables, context) => { // Rollback on error using the active query key if (context?.previousProject) { - queryClient.setQueryData(['project', 'v2', routeProjectId.value], context.previousProject) + queryClient.setQueryData(['project', 'v2', routeProjectId], context.previousProject) } addNotification({ title: formatMessage(commonMessages.errorNotificationTitle), @@ -1995,16 +1995,16 @@ const patchStatusMutation = useMutation({ onMutate: async ({ projectId, status }) => { // Cancel outgoing refetches for both slug-based and ID-based cache keys - await queryClient.cancelQueries({ queryKey: ['project', 'v2', routeProjectId.value] }) - if (routeProjectId.value !== projectId) { + await queryClient.cancelQueries({ queryKey: ['project', 'v2', routeProjectId] }) + if (routeProjectId !== projectId) { await queryClient.cancelQueries({ queryKey: ['project', 'v2', projectId] }) } // Snapshot previous value from the active query (uses route param as key) - const previousProject = queryClient.getQueryData(['project', 'v2', routeProjectId.value]) + const previousProject = queryClient.getQueryData(['project', 'v2', routeProjectId]) // Optimistic update on the active query key - queryClient.setQueryData(['project', 'v2', routeProjectId.value], (old) => { + queryClient.setQueryData(['project', 'v2', routeProjectId], (old) => { if (!old) return old return { ...old, status } }) @@ -2015,7 +2015,7 @@ const patchStatusMutation = useMutation({ onError: (err, _variables, context) => { // Rollback on error using the active query key if (context?.previousProject) { - queryClient.setQueryData(['project', 'v2', routeProjectId.value], context.previousProject) + queryClient.setQueryData(['project', 'v2', routeProjectId], context.previousProject) } addNotification({ title: formatMessage(commonMessages.errorNotificationTitle), @@ -2121,11 +2121,11 @@ const createGalleryItemMutation = useMutation({ }, onMutate: async ({ title, description, featured, ordering }) => { - await queryClient.cancelQueries({ queryKey: ['project', 'v2', routeProjectId.value] }) + await queryClient.cancelQueries({ queryKey: ['project', 'v2', routeProjectId] }) - const previousProject = queryClient.getQueryData(['project', 'v2', routeProjectId.value]) + const previousProject = queryClient.getQueryData(['project', 'v2', routeProjectId]) - queryClient.setQueryData(['project', 'v2', routeProjectId.value], (old) => { + queryClient.setQueryData(['project', 'v2', routeProjectId], (old) => { if (!old) return old const newItem = { url: '', @@ -2147,7 +2147,7 @@ const createGalleryItemMutation = useMutation({ onError: (err, _variables, context) => { if (context?.previousProject) { - queryClient.setQueryData(['project', 'v2', routeProjectId.value], context.previousProject) + queryClient.setQueryData(['project', 'v2', routeProjectId], context.previousProject) } addNotification({ title: formatMessage(commonMessages.errorNotificationTitle), @@ -2172,11 +2172,11 @@ const editGalleryItemMutation = useMutation({ }, onMutate: async ({ imageUrl, title, description, featured, ordering }) => { - await queryClient.cancelQueries({ queryKey: ['project', 'v2', routeProjectId.value] }) + await queryClient.cancelQueries({ queryKey: ['project', 'v2', routeProjectId] }) - const previousProject = queryClient.getQueryData(['project', 'v2', routeProjectId.value]) + const previousProject = queryClient.getQueryData(['project', 'v2', routeProjectId]) - queryClient.setQueryData(['project', 'v2', routeProjectId.value], (old) => { + queryClient.setQueryData(['project', 'v2', routeProjectId], (old) => { if (!old) return old return { ...old, @@ -2200,7 +2200,7 @@ const editGalleryItemMutation = useMutation({ onError: (err, _variables, context) => { if (context?.previousProject) { - queryClient.setQueryData(['project', 'v2', routeProjectId.value], context.previousProject) + queryClient.setQueryData(['project', 'v2', routeProjectId], context.previousProject) } addNotification({ title: formatMessage(commonMessages.errorNotificationTitle), @@ -2220,11 +2220,11 @@ const deleteGalleryItemMutation = useMutation({ }, onMutate: async ({ imageUrl }) => { - await queryClient.cancelQueries({ queryKey: ['project', 'v2', routeProjectId.value] }) + await queryClient.cancelQueries({ queryKey: ['project', 'v2', routeProjectId] }) - const previousProject = queryClient.getQueryData(['project', 'v2', routeProjectId.value]) + const previousProject = queryClient.getQueryData(['project', 'v2', routeProjectId]) - queryClient.setQueryData(['project', 'v2', routeProjectId.value], (old) => { + queryClient.setQueryData(['project', 'v2', routeProjectId], (old) => { if (!old) return old return { ...old, @@ -2237,7 +2237,7 @@ const deleteGalleryItemMutation = useMutation({ onError: (err, _variables, context) => { if (context?.previousProject) { - queryClient.setQueryData(['project', 'v2', routeProjectId.value], context.previousProject) + queryClient.setQueryData(['project', 'v2', routeProjectId], context.previousProject) } addNotification({ title: formatMessage(commonMessages.errorNotificationTitle), @@ -2372,7 +2372,7 @@ useHead({ ], }) -if (!route.name.startsWith('type-id-settings')) { +if (!route.name.startsWith('type-project-settings')) { useSeoMeta({ title: () => title.value, description: () => description.value, diff --git a/apps/frontend/src/pages/[type]/[id]/changelog.vue b/apps/frontend/src/pages/[type]/[project]/changelog.vue similarity index 100% rename from apps/frontend/src/pages/[type]/[id]/changelog.vue rename to apps/frontend/src/pages/[type]/[project]/changelog.vue diff --git a/apps/frontend/src/pages/[type]/[id]/gallery.vue b/apps/frontend/src/pages/[type]/[project]/gallery.vue similarity index 100% rename from apps/frontend/src/pages/[type]/[id]/gallery.vue rename to apps/frontend/src/pages/[type]/[project]/gallery.vue diff --git a/apps/frontend/src/pages/[type]/[id]/index.vue b/apps/frontend/src/pages/[type]/[project]/index.vue similarity index 100% rename from apps/frontend/src/pages/[type]/[id]/index.vue rename to apps/frontend/src/pages/[type]/[project]/index.vue diff --git a/apps/frontend/src/pages/[type]/[id]/moderation.vue b/apps/frontend/src/pages/[type]/[project]/moderation.vue similarity index 100% rename from apps/frontend/src/pages/[type]/[id]/moderation.vue rename to apps/frontend/src/pages/[type]/[project]/moderation.vue diff --git a/apps/frontend/src/pages/[type]/[id]/settings.vue b/apps/frontend/src/pages/[type]/[project]/settings.vue similarity index 96% rename from apps/frontend/src/pages/[type]/[id]/settings.vue rename to apps/frontend/src/pages/[type]/[project]/settings.vue index 45ea37ca3..08e08b124 100644 --- a/apps/frontend/src/pages/[type]/[id]/settings.vue +++ b/apps/frontend/src/pages/[type]/[project]/settings.vue @@ -47,10 +47,8 @@ const navItems = computed(() => { projectV3.value?.project_types?.some((type) => ['mod', 'modpack'].includes(type)) && isStaff(currentMember.value?.user) - const hasPermissionsPage = computed( - () => - flags.value.modpackPermissionsPage && - projectV3.value?.project_types?.some((type) => ['modpack'].includes(type)), + const hasPermissionsPage = computed(() => + projectV3.value?.project_types?.some((type) => ['modpack'].includes(type)), ) const items = [ diff --git a/apps/frontend/src/pages/[type]/[id]/settings/analytics.vue b/apps/frontend/src/pages/[type]/[project]/settings/analytics.vue similarity index 100% rename from apps/frontend/src/pages/[type]/[id]/settings/analytics.vue rename to apps/frontend/src/pages/[type]/[project]/settings/analytics.vue diff --git a/apps/frontend/src/pages/[type]/[id]/settings/description.vue b/apps/frontend/src/pages/[type]/[project]/settings/description.vue similarity index 100% rename from apps/frontend/src/pages/[type]/[id]/settings/description.vue rename to apps/frontend/src/pages/[type]/[project]/settings/description.vue diff --git a/apps/frontend/src/pages/[type]/[id]/settings/environment.vue b/apps/frontend/src/pages/[type]/[project]/settings/environment.vue similarity index 100% rename from apps/frontend/src/pages/[type]/[id]/settings/environment.vue rename to apps/frontend/src/pages/[type]/[project]/settings/environment.vue diff --git a/apps/frontend/src/pages/[type]/[id]/settings/gallery.vue b/apps/frontend/src/pages/[type]/[project]/settings/gallery.vue similarity index 100% rename from apps/frontend/src/pages/[type]/[id]/settings/gallery.vue rename to apps/frontend/src/pages/[type]/[project]/settings/gallery.vue diff --git a/apps/frontend/src/pages/[type]/[id]/settings/general.vue b/apps/frontend/src/pages/[type]/[project]/settings/general.vue similarity index 100% rename from apps/frontend/src/pages/[type]/[id]/settings/general.vue rename to apps/frontend/src/pages/[type]/[project]/settings/general.vue diff --git a/apps/frontend/src/pages/[type]/[id]/settings/index.vue b/apps/frontend/src/pages/[type]/[project]/settings/index.vue similarity index 100% rename from apps/frontend/src/pages/[type]/[id]/settings/index.vue rename to apps/frontend/src/pages/[type]/[project]/settings/index.vue diff --git a/apps/frontend/src/pages/[type]/[id]/settings/license.vue b/apps/frontend/src/pages/[type]/[project]/settings/license.vue similarity index 100% rename from apps/frontend/src/pages/[type]/[id]/settings/license.vue rename to apps/frontend/src/pages/[type]/[project]/settings/license.vue diff --git a/apps/frontend/src/pages/[type]/[id]/settings/links.vue b/apps/frontend/src/pages/[type]/[project]/settings/links.vue similarity index 100% rename from apps/frontend/src/pages/[type]/[id]/settings/links.vue rename to apps/frontend/src/pages/[type]/[project]/settings/links.vue diff --git a/apps/frontend/src/pages/[type]/[id]/settings/members.vue b/apps/frontend/src/pages/[type]/[project]/settings/members.vue similarity index 100% rename from apps/frontend/src/pages/[type]/[id]/settings/members.vue rename to apps/frontend/src/pages/[type]/[project]/settings/members.vue diff --git a/apps/frontend/src/pages/[type]/[id]/settings/permissions.vue b/apps/frontend/src/pages/[type]/[project]/settings/permissions.vue similarity index 100% rename from apps/frontend/src/pages/[type]/[id]/settings/permissions.vue rename to apps/frontend/src/pages/[type]/[project]/settings/permissions.vue diff --git a/apps/frontend/src/pages/[type]/[id]/settings/server.vue b/apps/frontend/src/pages/[type]/[project]/settings/server.vue similarity index 100% rename from apps/frontend/src/pages/[type]/[id]/settings/server.vue rename to apps/frontend/src/pages/[type]/[project]/settings/server.vue diff --git a/apps/frontend/src/pages/[type]/[id]/settings/tags.vue b/apps/frontend/src/pages/[type]/[project]/settings/tags.vue similarity index 100% rename from apps/frontend/src/pages/[type]/[id]/settings/tags.vue rename to apps/frontend/src/pages/[type]/[project]/settings/tags.vue diff --git a/apps/frontend/src/pages/[type]/[id]/settings/versions.vue b/apps/frontend/src/pages/[type]/[project]/settings/versions.vue similarity index 100% rename from apps/frontend/src/pages/[type]/[id]/settings/versions.vue rename to apps/frontend/src/pages/[type]/[project]/settings/versions.vue diff --git a/apps/frontend/src/pages/[type]/[id]/version/[version].vue b/apps/frontend/src/pages/[type]/[project]/version/[version].vue similarity index 100% rename from apps/frontend/src/pages/[type]/[id]/version/[version].vue rename to apps/frontend/src/pages/[type]/[project]/version/[version].vue diff --git a/apps/frontend/src/pages/[type]/[id]/versions.vue b/apps/frontend/src/pages/[type]/[project]/versions.vue similarity index 100% rename from apps/frontend/src/pages/[type]/[id]/versions.vue rename to apps/frontend/src/pages/[type]/[project]/versions.vue diff --git a/apps/frontend/src/pages/admin/billing/[id].vue b/apps/frontend/src/pages/admin/billing/[charge].vue similarity index 99% rename from apps/frontend/src/pages/admin/billing/[id].vue rename to apps/frontend/src/pages/admin/billing/[charge].vue index 5582c7ff1..220da9129 100644 --- a/apps/frontend/src/pages/admin/billing/[id].vue +++ b/apps/frontend/src/pages/admin/billing/[charge].vue @@ -360,7 +360,6 @@ const formatDateTimeShort = useFormatDateTime({ minute: 'numeric', }) -const route = useRoute() const vintl = useVIntl() const { formatMessage } = vintl @@ -373,13 +372,15 @@ const messages = defineMessages({ }, }) +const chargeId = useRouteId('charge') + const { data: user, error: userError, suspense: userSuspense, } = useQuery({ - queryKey: ['user', route.params.id], - queryFn: () => labrinth.users_v2.get(route.params.id), + queryKey: ['user', chargeId], + queryFn: () => labrinth.users_v2.get(chargeId), }) onServerPrefetch(userSuspense) diff --git a/apps/frontend/src/pages/collection/[id].vue b/apps/frontend/src/pages/collection/[collection].vue similarity index 98% rename from apps/frontend/src/pages/collection/[id].vue rename to apps/frontend/src/pages/collection/[collection].vue index 0a67a4fe1..3ce5b0d05 100644 --- a/apps/frontend/src/pages/collection/[id].vue +++ b/apps/frontend/src/pages/collection/[collection].vue @@ -529,8 +529,8 @@ const returnLink = computed(() => { return null }) -const collectionId = computed(() => route.params.id) -const isFollowingCollection = computed(() => collectionId.value === 'following') +const collectionId = useRouteId('collection') +const isFollowingCollection = computed(() => collectionId === 'following') // Static collection for "following" page const followingCollection = computed(() => @@ -555,15 +555,15 @@ const { error: collectionError, isPending: collectionIsPending, } = useQuery({ - queryKey: computed(() => ['collection', collectionId.value]), - queryFn: () => api.labrinth.collections.get(collectionId.value), - enabled: computed(() => !!collectionId.value && !isFollowingCollection.value), + queryKey: computed(() => ['collection', collectionId]), + queryFn: () => api.labrinth.collections.get(collectionId), + enabled: computed(() => !!collectionId && !isFollowingCollection.value), }) watch( collectionError, (error) => { - if (error && collectionId.value && !isFollowingCollection.value) { + if (error && collectionId && !isFollowingCollection.value) { const status = error.statusCode ?? error.status ?? 404 showError({ fatal: true, diff --git a/apps/frontend/src/pages/collection/[id]/[projectType].vue b/apps/frontend/src/pages/collection/[collection]/[projectType].vue similarity index 100% rename from apps/frontend/src/pages/collection/[id]/[projectType].vue rename to apps/frontend/src/pages/collection/[collection]/[projectType].vue diff --git a/apps/frontend/src/pages/dashboard/report/[id].vue b/apps/frontend/src/pages/dashboard/report/[report].vue similarity index 80% rename from apps/frontend/src/pages/dashboard/report/[id].vue rename to apps/frontend/src/pages/dashboard/report/[report].vue index b52b15eed..d050da582 100644 --- a/apps/frontend/src/pages/dashboard/report/[id].vue +++ b/apps/frontend/src/pages/dashboard/report/[report].vue @@ -1,7 +1,7 @@