fix: invalidate tanstack caches on user auth (#5341)

* fix: invalidate tanstack caches on user auth

* refactor: clean up invalidate flow

* fix: lint
This commit is contained in:
Calum H.
2026-02-09 14:43:33 +00:00
committed by GitHub
parent e962521492
commit 90438a1ad5
16 changed files with 105 additions and 198 deletions

View File

@@ -1,6 +1,7 @@
import type { AbstractModrinthClient } from '@modrinth/api-client' import type { AbstractModrinthClient } from '@modrinth/api-client'
const STALE_TIME = 1000 * 60 * 5 // 5 minutes export const STALE_TIME = 1000 * 60 * 5 // 5 minutes
export const STALE_TIME_LONG = 1000 * 60 * 10 // 10 minutes
export const projectQueryOptions = { export const projectQueryOptions = {
v2: (projectId: string, client: AbstractModrinthClient) => ({ v2: (projectId: string, client: AbstractModrinthClient) => ({

View File

@@ -1,3 +1,5 @@
import { useAppQueryClient } from '@/composables/query-client'
export const useUser = async (force = false) => { export const useUser = async (force = false) => {
const user = useState('user', () => {}) const user = useState('user', () => {})
@@ -158,5 +160,6 @@ export const logout = async () => {
await useAuth('none') await useAuth('none')
useCookie('auth-token').value = null useCookie('auth-token').value = null
useAppQueryClient().clear()
stopLoading() stopLoading()
} }

View File

@@ -1,4 +1,5 @@
import { useGeneratedState } from '~/composables/generated' import { useGeneratedState } from '~/composables/generated'
import { projectQueryOptions } from '~/composables/queries/project'
import { useAppQueryClient } from '~/composables/query-client' import { useAppQueryClient } from '~/composables/query-client'
import { getProjectTypeForUrlShorthand } from '~/helpers/projects.js' import { getProjectTypeForUrlShorthand } from '~/helpers/projects.js'
import { useServerModrinthClient } from '~/server/utils/api-client' import { useServerModrinthClient } from '~/server/utils/api-client'
@@ -21,11 +22,7 @@ export default defineNuxtRouteMiddleware(async (to) => {
try { try {
// Fetch v2 project for redirect check AND cache it for the page // Fetch v2 project for redirect check AND cache it for the page
// Using fetchQuery ensures the page's useQuery gets this cached result // Using fetchQuery ensures the page's useQuery gets this cached result
const project = await queryClient.fetchQuery({ const project = await queryClient.fetchQuery(projectQueryOptions.v2(projectId, client))
queryKey: ['project', 'v2', projectId],
queryFn: () => client.labrinth.projects_v2.get(projectId),
staleTime: 1000 * 60 * 5,
})
// Let page handle 404 // Let page handle 404
if (!project) return if (!project) return

View File

@@ -35,7 +35,7 @@
:is-settings="route.name.startsWith('type-id-settings')" :is-settings="route.name.startsWith('type-id-settings')"
:set-processing="setProcessing" :set-processing="setProcessing"
:all-members="allMembers" :all-members="allMembers"
:update-members="refreshMembers" :update-members="invalidateProject"
:auth="auth" :auth="auth"
:tags="tags" :tags="tags"
/> />
@@ -747,7 +747,7 @@
:collapsed="collapsedChecklist" :collapsed="collapsedChecklist"
:toggle-collapsed="() => (collapsedChecklist = !collapsedChecklist)" :toggle-collapsed="() => (collapsedChecklist = !collapsedChecklist)"
:all-members="allMembers" :all-members="allMembers"
:update-members="updateMembers" :update-members="invalidateProject"
:auth="auth" :auth="auth"
:tags="tags" :tags="tags"
/> />
@@ -1008,6 +1008,7 @@ import ModerationChecklist from '~/components/ui/moderation/checklist/Moderation
import NavTabs from '~/components/ui/NavTabs.vue' import NavTabs from '~/components/ui/NavTabs.vue'
import ProjectMemberHeader from '~/components/ui/ProjectMemberHeader.vue' import ProjectMemberHeader from '~/components/ui/ProjectMemberHeader.vue'
import { saveFeatureFlags } from '~/composables/featureFlags.ts' import { saveFeatureFlags } from '~/composables/featureFlags.ts'
import { STALE_TIME, STALE_TIME_LONG } from '~/composables/queries/project'
import { userCollectProject, userFollowProject } from '~/composables/user.js' import { userCollectProject, userFollowProject } from '~/composables/user.js'
import { useModerationStore } from '~/store/moderation.ts' import { useModerationStore } from '~/store/moderation.ts'
import { reportProject } from '~/utils/report-helpers.ts' import { reportProject } from '~/utils/report-helpers.ts'
@@ -1472,14 +1473,10 @@ const client = injectModrinthClient()
const queryClient = useQueryClient() const queryClient = useQueryClient()
// V2 Project - hits middleware cache (uses route param for lookup) // V2 Project - hits middleware cache (uses route param for lookup)
const { const { data: projectRaw, error: projectV2Error } = useQuery({
data: projectRaw,
error: projectV2Error,
refetch: resetProjectV2,
} = useQuery({
queryKey: computed(() => ['project', 'v2', routeProjectId.value]), queryKey: computed(() => ['project', 'v2', routeProjectId.value]),
queryFn: () => client.labrinth.projects_v2.get(routeProjectId.value), queryFn: () => client.labrinth.projects_v2.get(routeProjectId.value),
staleTime: 1000 * 60 * 5, staleTime: STALE_TIME,
}) })
// Handle project not found - use showError since watch runs outside Nuxt context // Handle project not found - use showError since watch runs outside Nuxt context
@@ -1522,26 +1519,18 @@ const project = computed(() => {
const projectId = computed(() => projectRaw.value?.id) const projectId = computed(() => projectRaw.value?.id)
// V3 Project // V3 Project
const { const { data: projectV3, error: _projectV3Error } = useQuery({
data: projectV3,
error: _projectV3Error,
refetch: resetProjectV3,
} = useQuery({
queryKey: computed(() => ['project', 'v3', projectId.value]), queryKey: computed(() => ['project', 'v3', projectId.value]),
queryFn: () => client.labrinth.projects_v3.get(projectId.value), queryFn: () => client.labrinth.projects_v3.get(projectId.value),
staleTime: 1000 * 60 * 5, staleTime: STALE_TIME,
enabled: computed(() => !!projectId.value), enabled: computed(() => !!projectId.value),
}) })
// Members // Members
const { const { data: allMembersRaw, error: _membersError } = useQuery({
data: allMembersRaw,
error: _membersError,
refetch: _resetMembers,
} = useQuery({
queryKey: computed(() => ['project', projectId.value, 'members']), queryKey: computed(() => ['project', projectId.value, 'members']),
queryFn: () => client.labrinth.projects_v3.getMembers(projectId.value), queryFn: () => client.labrinth.projects_v3.getMembers(projectId.value),
staleTime: 1000 * 60 * 5, staleTime: STALE_TIME,
enabled: computed(() => !!projectId.value), enabled: computed(() => !!projectId.value),
}) })
@@ -1556,25 +1545,26 @@ const allMembers = computed(() => {
}) })
// Dependencies - lazy loaded client-side only // Dependencies - lazy loaded client-side only
const dependenciesEnabled = ref(false)
const { const {
data: dependenciesRaw, data: dependenciesRaw,
error: _dependenciesError, error: _dependenciesError,
isFetching: dependenciesLoading, isFetching: dependenciesLoading,
refetch: refetchDependencies,
} = useQuery({ } = useQuery({
queryKey: computed(() => ['project', projectId.value, 'dependencies']), queryKey: computed(() => ['project', projectId.value, 'dependencies']),
queryFn: () => client.labrinth.projects_v2.getDependencies(projectId.value), queryFn: () => client.labrinth.projects_v2.getDependencies(projectId.value),
staleTime: 1000 * 60 * 10, staleTime: STALE_TIME_LONG,
enabled: computed(() => !!projectId.value && dependenciesEnabled.value),
}) })
const dependencies = computed(() => dependenciesRaw.value ?? null) const dependencies = computed(() => dependenciesRaw.value ?? null)
// V3 Versions - lazy loaded client-side only // V3 Versions - lazy loaded client-side only
const versionsEnabled = ref(false)
const { const {
data: versionsV3, data: versionsV3,
error: _versionsV3Error, error: _versionsV3Error,
isFetching: versionsV3Loading, isFetching: versionsV3Loading,
refetch: resetVersionsV3,
} = useQuery({ } = useQuery({
queryKey: computed(() => ['project', projectId.value, 'versions', 'v3']), queryKey: computed(() => ['project', projectId.value, 'versions', 'v3']),
queryFn: () => queryFn: () =>
@@ -1582,15 +1572,16 @@ const {
include_changelog: false, include_changelog: false,
apiVersion: 3, apiVersion: 3,
}), }),
staleTime: 1000 * 60 * 10, staleTime: STALE_TIME_LONG,
enabled: computed(() => !!projectId.value && versionsEnabled.value),
}) })
// Organization // Organization
// Only fetch organization if project belongs to one // Only fetch organization if project belongs to one
const { data: organization, refetch: _resetOrganization } = useQuery({ const { data: organization } = useQuery({
queryKey: computed(() => ['project', projectId.value, 'organization']), queryKey: computed(() => ['project', projectId.value, 'organization']),
queryFn: () => client.labrinth.projects_v3.getOrganization(projectId.value), queryFn: () => client.labrinth.projects_v3.getOrganization(projectId.value),
staleTime: 1000 * 60 * 5, staleTime: STALE_TIME,
enabled: computed(() => !!projectId.value && !!projectRaw.value?.organization), enabled: computed(() => !!projectId.value && !!projectRaw.value?.organization),
}) })
@@ -1616,17 +1607,13 @@ const versions = computed(() => {
const versionsLoading = computed(() => versionsV3Loading.value) const versionsLoading = computed(() => versionsV3Loading.value)
// Load versions on demand (client-side only) // Load versions on demand (client-side only)
async function loadVersions() { function loadVersions() {
// Skip if already loaded or loading versionsEnabled.value = true
if (versionsV3.value || versionsV3Loading.value) return
await resetVersionsV3()
} }
// Load dependencies on demand (client-side only) // Load dependencies on demand (client-side only)
async function loadDependencies() { function loadDependencies() {
// Skip if already loaded or loading dependenciesEnabled.value = true
if (dependenciesRaw.value || dependenciesLoading.value) return
await refetchDependencies()
} }
// Check if project has versions using the ID array from the V2 project // Check if project has versions using the ID array from the V2 project
@@ -1654,25 +1641,13 @@ async function updateProjectRoute() {
} }
} }
async function resetProject() { async function invalidateProject() {
await invalidateProjectQueries(projectId.value)
await resetProjectV2()
await resetProjectV3()
}
async function resetVersions() {
await invalidateProjectQueries(projectId.value)
await resetVersionsV3()
}
// Helper to invalidate project queries after mutations settle
async function invalidateProjectQueries(projectId) {
await queryClient.invalidateQueries({ queryKey: ['project', 'v2', routeProjectId.value] }) await queryClient.invalidateQueries({ queryKey: ['project', 'v2', routeProjectId.value] })
if (routeProjectId.value !== projectId) { if (routeProjectId.value !== projectId.value) {
await queryClient.invalidateQueries({ queryKey: ['project', 'v2', projectId] }) await queryClient.invalidateQueries({ queryKey: ['project', 'v2', projectId.value] })
} }
await queryClient.invalidateQueries({ queryKey: ['project', 'v3', projectId] }) // Prefix match — invalidates members, versions, dependencies, organization
await queryClient.invalidateQueries({ queryKey: ['project', projectId, 'versions', 'v3'] }) await queryClient.invalidateQueries({ queryKey: ['project', projectId.value] })
} }
// Mutation for patching project data // Mutation for patching project data
@@ -1718,8 +1693,8 @@ const patchProjectMutation = useMutation({
window.scrollTo({ top: 0, behavior: 'smooth' }) window.scrollTo({ top: 0, behavior: 'smooth' })
}, },
onSettled: async (_data, _error, { projectId }) => { onSettled: async () => {
await invalidateProjectQueries(projectId) await invalidateProject()
}, },
}) })
@@ -1763,8 +1738,8 @@ const patchStatusMutation = useMutation({
}) })
}, },
onSettled: async (_data, _error, { projectId }) => { onSettled: async () => {
await invalidateProjectQueries(projectId) await invalidateProject()
}, },
}) })
@@ -1797,8 +1772,8 @@ const patchIconMutation = useMutation({
window.scrollTo({ top: 0, behavior: 'smooth' }) window.scrollTo({ top: 0, behavior: 'smooth' })
}, },
onSettled: async (_data, _error, { projectId }) => { onSettled: async () => {
await invalidateProjectQueries(projectId) await invalidateProject()
}, },
}) })
@@ -1860,8 +1835,8 @@ const createGalleryItemMutation = useMutation({
}) })
}, },
onSettled: async (_data, _error, { projectId }) => { onSettled: async () => {
await invalidateProjectQueries(projectId) await invalidateProject()
}, },
}) })
@@ -1922,8 +1897,8 @@ const editGalleryItemMutation = useMutation({
}) })
}, },
onSettled: async (_data, _error, { projectId }) => { onSettled: async () => {
await invalidateProjectQueries(projectId) await invalidateProject()
}, },
}) })
@@ -1961,8 +1936,8 @@ const deleteGalleryItemMutation = useMutation({
}) })
}, },
onSettled: async (_data, _error, { projectId }) => { onSettled: async () => {
await invalidateProjectQueries(projectId) await invalidateProject()
}, },
}) })
@@ -2202,15 +2177,6 @@ async function deleteGalleryItem(imageUrl) {
}) })
} }
async function refreshMembers() {
// Simply invalidate and refetch - the computed allMembers will auto-update
await queryClient.invalidateQueries({ queryKey: ['project', projectId.value, 'members'] })
}
async function refreshOrganization() {
await queryClient.invalidateQueries({ queryKey: ['project', projectId.value, 'organization'] })
}
async function copyId() { async function copyId() {
await navigator.clipboard.writeText(project.value.id) await navigator.clipboard.writeText(project.value.id)
} }
@@ -2266,8 +2232,7 @@ async function deleteVersion(id) {
method: 'DELETE', method: 'DELETE',
}) })
// Refetch versions to reflect deletion (versions is a computed ref) await invalidateProject()
await resetVersions()
stopLoading() stopLoading()
} }
@@ -2320,11 +2285,8 @@ provideProjectPageContext({
dependencies, dependencies,
dependenciesLoading: computed(() => dependenciesLoading.value), dependenciesLoading: computed(() => dependenciesLoading.value),
// Refresh functions (invalidate + refetch) // Invalidate all project queries (auto-refetches active ones)
refreshProject: resetProject, invalidate: invalidateProject,
refreshVersions: resetVersions,
refreshMembers,
refreshOrganization,
// Lazy loading // Lazy loading
loadVersions, loadVersions,

View File

@@ -340,7 +340,6 @@ import {
ConfirmModal, ConfirmModal,
DropArea, DropArea,
FileInput, FileInput,
injectNotificationManager,
injectProjectPageContext, injectProjectPageContext,
NewModal as Modal, NewModal as Modal,
} from '@modrinth/ui' } from '@modrinth/ui'
@@ -352,8 +351,13 @@ import { isPermission } from '~/utils/permissions.ts'
const router = useRouter() const router = useRouter()
// Single DI injection // Single DI injection
const { addNotification } = injectNotificationManager() const {
const { projectV2: project, currentMember, refreshProject } = injectProjectPageContext() projectV2: project,
currentMember,
createGalleryItem: contextCreateGalleryItem,
editGalleryItem: contextEditGalleryItem,
deleteGalleryItem: contextDeleteGalleryItem,
} = injectProjectPageContext()
// Template refs // Template refs
const modalEditItem = useTemplateRef('modal_edit_item') const modalEditItem = useTemplateRef('modal_edit_item')
@@ -486,37 +490,16 @@ async function createGalleryItem() {
shouldPreventActions.value = true shouldPreventActions.value = true
startLoading() startLoading()
try { const success = await contextCreateGalleryItem(
let url = `project/${project.value.id}/gallery?ext=${ editFile.value!,
editFile.value editTitle.value || undefined,
? editFile.value.type.split('/')[editFile.value.type.split('/').length - 1] editDescription.value || undefined,
: null editFeatured.value,
}&featured=${editFeatured.value}` editOrder.value ? Number(editOrder.value) : undefined,
)
if (editTitle.value) {
url += `&title=${encodeURIComponent(editTitle.value)}`
}
if (editDescription.value) {
url += `&description=${encodeURIComponent(editDescription.value)}`
}
if (editOrder.value) {
url += `&ordering=${editOrder.value}`
}
await useBaseFetch(url, {
method: 'POST',
body: editFile.value,
})
await refreshProject()
if (success) {
modalEditItem.value?.hide() modalEditItem.value?.hide()
} catch (err: unknown) {
const error = err as { data?: { description?: string } }
addNotification({
title: 'An error occurred',
text: error.data?.description ?? String(err),
type: 'error',
})
} }
stopLoading() stopLoading()
@@ -526,34 +509,18 @@ async function createGalleryItem() {
async function editGalleryItem() { async function editGalleryItem() {
shouldPreventActions.value = true shouldPreventActions.value = true
startLoading() startLoading()
try {
let url = `project/${project.value.id}/gallery?url=${encodeURIComponent(
project.value!.gallery![editIndex.value].url,
)}&featured=${editFeatured.value}`
if (editTitle.value) { const imageUrl = project.value!.gallery![editIndex.value].url
url += `&title=${encodeURIComponent(editTitle.value)}` const success = await contextEditGalleryItem(
} imageUrl,
if (editDescription.value) { editTitle.value || undefined,
url += `&description=${encodeURIComponent(editDescription.value)}` editDescription.value || undefined,
} editFeatured.value,
if (editOrder.value) { editOrder.value ? Number(editOrder.value) : undefined,
url += `&ordering=${editOrder.value}` )
}
await useBaseFetch(url, { if (success) {
method: 'PATCH',
})
await refreshProject()
modalEditItem.value?.hide() modalEditItem.value?.hide()
} catch (err: unknown) {
const error = err as { data?: { description?: string } }
addNotification({
title: 'An error occurred',
text: error.data?.description ?? String(err),
type: 'error',
})
} }
stopLoading() stopLoading()
@@ -563,25 +530,8 @@ async function editGalleryItem() {
async function deleteGalleryImage() { async function deleteGalleryImage() {
startLoading() startLoading()
try { const imageUrl = project.value!.gallery![deleteIndex.value].url!
await useBaseFetch( await contextDeleteGalleryItem(imageUrl)
`project/${project.value.id}/gallery?url=${encodeURIComponent(
project.value!.gallery![deleteIndex.value].url!,
)}`,
{
method: 'DELETE',
},
)
await refreshProject()
} catch (err: unknown) {
const error = err as { data?: { description?: string } }
addNotification({
title: 'An error occurred',
text: error.data?.description ?? String(err),
type: 'error',
})
}
stopLoading() stopLoading()
} }

View File

@@ -113,7 +113,7 @@ import {
} from '~/helpers/projects.js' } from '~/helpers/projects.js'
const { addNotification } = injectNotificationManager() const { addNotification } = injectNotificationManager()
const { projectV2: project, currentMember, refreshProject } = injectProjectPageContext() const { projectV2: project, currentMember, invalidate } = injectProjectPageContext()
const auth = await useAuth() const auth = await useAuth()
@@ -134,7 +134,7 @@ async function setStatus(status) {
}) })
project.value.status = status project.value.status = status
await refreshProject() await invalidate()
thread.value = await useBaseFetch(`thread/${thread.value.id}`) thread.value = await useBaseFetch(`thread/${thread.value.id}`)
} catch (err) { } catch (err) {
addNotification({ addNotification({

View File

@@ -274,7 +274,7 @@ const {
currentMember, currentMember,
patchProject, patchProject,
patchIcon, patchIcon,
refreshProject, invalidate,
} = injectProjectPageContext() } = injectProjectPageContext()
const flags = useFeatureFlags() const flags = useFeatureFlags()
@@ -407,7 +407,7 @@ const deleteIcon = async () => {
await useBaseFetch(`project/${project.value.id}/icon`, { await useBaseFetch(`project/${project.value.id}/icon`, {
method: 'DELETE', method: 'DELETE',
}) })
await refreshProject() await invalidate()
addNotification({ addNotification({
title: 'Project icon removed', title: 'Project icon removed',
text: "Your project's icon has been removed.", text: "Your project's icon has been removed.",

View File

@@ -562,9 +562,7 @@ const {
organization, organization,
allMembers, allMembers,
currentMember, currentMember,
refreshProject, invalidate,
refreshOrganization,
refreshMembers,
} = injectProjectPageContext() } = injectProjectPageContext()
const cosmetics = useCosmetics() const cosmetics = useCosmetics()
@@ -838,7 +836,7 @@ async function updateOrgMember(index) {
} }
const updateMembers = async () => { const updateMembers = async () => {
await Promise.all([refreshProject(), refreshOrganization(), refreshMembers()]) await invalidate()
} }
</script> </script>

View File

@@ -315,7 +315,7 @@ const {
projectV2: project, projectV2: project,
currentMember, currentMember,
versions, versions,
refreshVersions, invalidate,
loadVersions, loadVersions,
} = injectProjectPageContext() } = injectProjectPageContext()
@@ -387,7 +387,7 @@ async function deleteVersion() {
}) })
} }
refreshVersions() await invalidate()
selectedVersion.value = null selectedVersion.value = null
stopLoading() stopLoading()

View File

@@ -1,10 +1,6 @@
<template> <template>
<div v-if="version" class="version-page"> <div v-if="version" class="version-page">
<CreateProjectVersionModal <CreateProjectVersionModal v-if="currentMember" ref="createProjectVersionModal" />
v-if="currentMember"
ref="createProjectVersionModal"
@save="handleVersionSaved"
/>
<ConfirmModal <ConfirmModal
v-if="currentMember" v-if="currentMember"
ref="modal_confirm" ref="modal_confirm"
@@ -470,8 +466,7 @@ const {
loadVersions, loadVersions,
dependencies: contextDependencies, dependencies: contextDependencies,
loadDependencies, loadDependencies,
refreshVersions, invalidate,
refreshProject,
} = injectProjectPageContext() } = injectProjectPageContext()
// Load versions and dependencies in parallel // Load versions and dependencies in parallel
@@ -619,8 +614,8 @@ if (route.params.version === 'create') {
)) as any )) as any
if (versionV3) { if (versionV3) {
version.value = versionV3 version.value = versionV3
// Refresh versions cache to include this version // Refresh cache to include this version
await refreshVersions() await invalidate()
} }
} catch { } catch {
// API fetch failed - version truly doesn't exist, will 404 below // API fetch failed - version truly doesn't exist, will 404 below
@@ -733,10 +728,6 @@ function handleOpenEditVersionModal(versionId: string, projectId: string, stageI
createProjectVersionModal.value?.openEditVersionModal(versionId, projectId, stageId) createProjectVersionModal.value?.openEditVersionModal(versionId, projectId, stageId)
} }
async function handleVersionSaved() {
router.go(0) // reload page for new data
}
async function _onImageUpload(file: File) { async function _onImageUpload(file: File) {
const response = await useImageUpload(file, { context: 'version' }) const response = await useImageUpload(file, { context: 'version' })
@@ -1070,7 +1061,7 @@ async function createDataPackVersionHandler() {
} }
async function resetProjectVersions() { async function resetProjectVersions() {
await Promise.all([refreshVersions(), refreshProject()]) await invalidate()
} }
</script> </script>

View File

@@ -313,7 +313,7 @@ const { addNotification } = injectNotificationManager()
const { const {
projectV2: project, projectV2: project,
currentMember, currentMember,
refreshVersions, invalidate,
versions, versions,
versionsLoading, versionsLoading,
loadVersions, loadVersions,
@@ -379,7 +379,7 @@ async function deleteVersion() {
}) })
} }
refreshVersions() await invalidate()
selectedVersion.value = null selectedVersion.value = null
stopLoading() stopLoading()

View File

@@ -149,10 +149,12 @@ import {
IntlFormatted, IntlFormatted,
useVIntl, useVIntl,
} from '@modrinth/ui' } from '@modrinth/ui'
import { useQueryClient } from '@tanstack/vue-query'
import HCaptcha from '@/components/ui/HCaptcha.vue' import HCaptcha from '@/components/ui/HCaptcha.vue'
import { getAuthUrl, getLauncherRedirectUrl } from '@/composables/auth.js' import { getAuthUrl, getLauncherRedirectUrl } from '@/composables/auth.js'
const queryClient = useQueryClient()
const { addNotification } = injectNotificationManager() const { addNotification } = injectNotificationManager()
const { formatMessage } = useVIntl() const { formatMessage } = useVIntl()
@@ -306,6 +308,7 @@ async function finishSignIn(token) {
if (token) { if (token) {
await useAuth(token) await useAuth(token)
await useUser() await useUser()
queryClient.clear()
} }
if (route.query.redirect) { if (route.query.redirect) {

View File

@@ -57,7 +57,9 @@ import {
normalizeChildren, normalizeChildren,
useVIntl, useVIntl,
} from '@modrinth/ui' } from '@modrinth/ui'
import { useQueryClient } from '@tanstack/vue-query'
const queryClient = useQueryClient()
const route = useRoute() const route = useRoute()
const { formatMessage } = useVIntl() const { formatMessage } = useVIntl()
@@ -96,6 +98,7 @@ const subscribe = ref(true)
onMounted(async () => { onMounted(async () => {
await useAuth(route.query.authToken) await useAuth(route.query.authToken)
await useUser() await useUser()
queryClient.clear()
}) })
async function continueSignUp() { async function continueSignUp() {

View File

@@ -166,7 +166,7 @@ export function createManageVersionContext(
): ManageVersionContextValue { ): ManageVersionContextValue {
const { labrinth } = injectModrinthClient() const { labrinth } = injectModrinthClient()
const { addNotification } = injectNotificationManager() const { addNotification } = injectNotificationManager()
const { refreshVersions, projectV2 } = injectProjectPageContext() const { invalidate, projectV2 } = injectProjectPageContext()
// State // State
const draftVersion = ref<Labrinth.Versions.v3.DraftVersion>(structuredClone(EMPTY_DRAFT_VERSION)) const draftVersion = ref<Labrinth.Versions.v3.DraftVersion>(structuredClone(EMPTY_DRAFT_VERSION))
@@ -660,7 +660,7 @@ export function createManageVersionContext(
text: 'The version has been successfully added to your project.', text: 'The version has been successfully added to your project.',
type: 'success', type: 'success',
}) })
await refreshVersions() await invalidate()
onSave?.() onSave?.()
} catch (err: any) { } catch (err: any) {
addNotification({ addNotification({
@@ -734,7 +734,7 @@ export function createManageVersionContext(
text: 'The version has been successfully saved to your project.', text: 'The version has been successfully saved to your project.',
type: 'success', type: 'success',
}) })
await refreshVersions() await invalidate()
onSave?.() onSave?.()
} catch (err: any) { } catch (err: any) {
addNotification({ addNotification({

View File

@@ -140,7 +140,7 @@ export class NuxtModrinthClient extends XHRUploadClient {
protected async executeRequest<T>(url: string, options: RequestOptions): Promise<T> { protected async executeRequest<T>(url: string, options: RequestOptions): Promise<T> {
try { try {
// @ts-expect-error - $fetch is provided by Nuxt runtime // @ts-expect-error - $fetch is provided by Nuxt
const response = await $fetch<T>(url, { const response = await $fetch<T>(url, {
method: options.method ?? 'GET', method: options.method ?? 'GET',
headers: options.headers, headers: options.headers,
@@ -148,6 +148,8 @@ export class NuxtModrinthClient extends XHRUploadClient {
params: options.params, params: options.params,
timeout: options.timeout, timeout: options.timeout,
signal: options.signal, signal: options.signal,
// @ts-expect-error - import.meta is provided by Nuxt
cache: import.meta.server ? undefined : 'no-store',
}) })
return response return response

View File

@@ -17,15 +17,12 @@ export interface ProjectPageContext {
dependencies: Ref<Labrinth.Projects.v2.DependencyInfo | null> dependencies: Ref<Labrinth.Projects.v2.DependencyInfo | null>
dependenciesLoading: Ref<boolean> dependenciesLoading: Ref<boolean>
// Refresh functions (invalidate + refetch) // Invalidate all project queries (auto-refetches active ones)
refreshProject: () => Promise<void> invalidate: () => Promise<void>
refreshVersions: () => Promise<void>
refreshMembers: () => Promise<void>
refreshOrganization: () => Promise<void>
// Lazy loading // Lazy loading
loadVersions: () => Promise<void> loadVersions: () => void
loadDependencies: () => Promise<void> loadDependencies: () => void
// Mutation functions // Mutation functions
patchProject: (data: Record<string, unknown>, quiet?: boolean) => Promise<boolean> patchProject: (data: Record<string, unknown>, quiet?: boolean) => Promise<boolean>