refactor: removing useAsyncData for tanstack query (#5262)

* refactor: most places with useAsyncData replaced with tanstack query

* refactor report list and report view

* refactor organization page to use tanstack query

* fix types

* refactor collection page and include proper loading state

* fix followed projects proper loading state

* fix 404 handling

* fix organization loading and 404 states

* pnpm prepr

* refactor: remove useAsyncData on newsletter button

* refactor: remove useAsyncData on auth globals fetch

* refactor: settings/billing/index.vue to useQuery instead of useAsyncData

* refactor: user page to remove useAsyncData

* pnpm prepr

* fix reports pages

* fix notifications page

* fix billing page cannot read properties of null and prop warnings

* fix refresh causing 404 by removing useBaseFetch and use api-client

* fix stale data after removing organization from project

* pnpm prepr

* fix news erroring in build

* fix: project page loads header only after content

* fix: user page tanstack problems (start on migrating away from useBaseFetch)

* fix: start swapping useBaseFetch usages to api-client

* Revert "fix: start swapping useBaseFetch usages to api-client"

This reverts commit 3df3fab11d535159132b1288dd7cacc38282b553.

* fix: remove debug logging

* fix: lint

---------

Co-authored-by: Calum H. <calum@modrinth.com>
Co-authored-by: Calum H. (IMB11) <contact@cal.engineer>
This commit is contained in:
Truman Gao
2026-03-16 12:10:29 -07:00
committed by GitHub
parent d0c7575a23
commit 681ae5d1d8
53 changed files with 1686 additions and 1079 deletions

View File

@@ -282,7 +282,7 @@
<div v-if="navLinks.length > 2" class="mb-4 max-w-full overflow-x-auto">
<NavTabs :links="navLinks" replace />
</div>
<div v-if="projects.length > 0">
<div v-if="projects?.length > 0">
<ProjectCardList
v-if="route.params.projectType !== 'collections'"
:layout="cosmetics.searchDisplayMode.user"
@@ -331,7 +331,7 @@
<div
v-else-if="
(route.params.projectType && route.params.projectType !== 'collections') ||
(!route.params.projectType && collections.length === 0)
(!route.params.projectType && collections?.length === 0)
"
class="error"
>
@@ -353,7 +353,7 @@
class="collections-grid"
>
<nuxt-link
v-for="collection in collections.sort(
v-for="collection in (collections ?? []).sort(
(a, b) => new Date(b.created) - new Date(a.created),
)"
:key="collection.id"
@@ -404,7 +404,7 @@
</nuxt-link>
</div>
<div
v-if="route.params.projectType === 'collections' && collections.length === 0"
v-if="route.params.projectType === 'collections' && collections?.length === 0"
class="error"
>
<UpToDate class="icon" />
@@ -425,7 +425,7 @@
</div>
</div>
<div class="normal-page__sidebar">
<div v-if="organizations.length > 0" class="card flex-card">
<div v-if="organizations?.length > 0" class="card flex-card">
<h2 class="text-lg text-contrast">
{{ formatMessage(messages.profileOrganizations) }}
</h2>
@@ -492,6 +492,7 @@ import {
commonMessages,
ContentPageHeader,
defineMessages,
injectModrinthClient,
injectNotificationManager,
IntlFormatted,
NewModal,
@@ -506,6 +507,8 @@ import {
useVIntl,
} from '@modrinth/ui'
import { isAdmin, isStaff, UserBadge } from '@modrinth/utils'
import { useQuery, useQueryClient } from '@tanstack/vue-query'
import { onServerPrefetch } from 'vue'
import TenMClubBadge from '~/assets/images/badges/10m-club.svg?component'
import AlphaTesterBadge from '~/assets/images/badges/alpha-tester.svg?component'
@@ -527,6 +530,7 @@ const auth = await useAuth()
const cosmetics = useCosmetics()
const tags = useGeneratedState()
const config = useRuntimeConfig()
const queryClient = useQueryClient()
const { formatMessage } = useVIntl()
const formatNumber = useFormatNumber()
@@ -679,74 +683,81 @@ const messages = defineMessages({
},
})
let user, projects, organizations, collections, refreshUser
try {
;[
{ data: user, refresh: refreshUser },
{ data: projects },
{ data: organizations },
{ data: collections },
] = await Promise.all([
useAsyncData(`user/${route.params.id}`, () => useBaseFetch(`user/${route.params.id}`)),
useAsyncData(
`user/${route.params.id}/projects`,
() => useBaseFetch(`user/${route.params.id}/projects`),
{
transform: (projects) => {
for (const project of projects) {
project.categories = project.categories.concat(project.loaders)
project.project_type = data.$getProjectTypeForUrl(
project.project_type,
project.categories,
tags.value,
)
}
const client = injectModrinthClient()
return projects
},
},
),
useAsyncData(`user/${route.params.id}/organizations`, () =>
useBaseFetch(`user/${route.params.id}/organizations`, {
apiVersion: 3,
}),
),
useAsyncData(`user/${route.params.id}/collections`, () =>
useBaseFetch(`user/${route.params.id}/collections`, { apiVersion: 3 }),
),
const {
data: user,
error: userError,
suspense: userSuspense,
} = useQuery({
queryKey: computed(() => ['user', route.params.id]),
queryFn: () => client.labrinth.users_v2.get(route.params.id),
})
watch(
userError,
(error) => {
if (error) {
const status = error.statusCode ?? error.status ?? 404
showError({
fatal: true,
statusCode: status,
message: formatMessage(messages.userNotFoundError),
})
}
},
{ immediate: true },
)
const { data: projects, suspense: projectsSuspense } = useQuery({
queryKey: computed(() => ['user', route.params.id, 'projects']),
queryFn: async () => {
const projects = await client.labrinth.users_v2.getProjects(route.params.id)
for (const project of projects) {
project.categories = project.categories.concat(project.loaders)
project.project_type = data.$getProjectTypeForUrl(
project.project_type,
project.categories,
tags.value,
)
}
return projects
},
})
const { data: organizations, suspense: orgsSuspense } = useQuery({
queryKey: computed(() => ['user', route.params.id, 'organizations']),
queryFn: () => client.labrinth.users_v2.getOrganizations(route.params.id),
})
const { data: collections, suspense: collectionsSuspense } = useQuery({
queryKey: computed(() => ['user', route.params.id, 'collections']),
queryFn: () => client.labrinth.users_v2.getCollections(route.params.id),
})
onServerPrefetch(async () => {
await Promise.allSettled([
userSuspense(),
projectsSuspense(),
orgsSuspense(),
collectionsSuspense(),
])
} catch {
throw createError({
fatal: true,
statusCode: 404,
message: formatMessage(messages.userNotFoundError),
})
}
})
const sortedOrgs = computed(() =>
organizations.value ? [...organizations.value].sort((a, b) => a.name.localeCompare(b.name)) : [],
)
if (!user.value) {
throw createError({
fatal: true,
statusCode: 404,
message: formatMessage(messages.userNotFoundError),
})
}
if (user.value.username !== route.params.id) {
await navigateTo(`/user/${user.value.username}`, { redirectCode: 301 })
}
const title = computed(() => `${user.value.username} - Modrinth`)
const title = computed(() => (user.value ? `${user.value.username} - Modrinth` : 'Modrinth'))
const description = computed(() =>
user.value.bio
user.value?.bio
? formatMessage(messages.profileMetaDescriptionWithBio, {
bio: user.value.bio,
username: user.value.username,
})
: formatMessage(messages.profileMetaDescription, { username: user.value.username }),
: user.value
? formatMessage(messages.profileMetaDescription, { username: user.value.username })
: '',
)
useSeoMeta({
@@ -754,7 +765,7 @@ useSeoMeta({
description: () => description.value,
ogTitle: () => title.value,
ogDescription: () => description.value,
ogImage: () => user.value.avatar_url ?? 'https://cdn.modrinth.com/placeholder.png',
ogImage: () => user.value?.avatar_url ?? 'https://cdn.modrinth.com/placeholder.png',
})
const projectTypes = computed(() => {
@@ -838,15 +849,12 @@ async function copyPermalink() {
await navigator.clipboard.writeText(`${config.public.siteUrl}/user/${user.value.id}`)
}
const isAffiliate = computed(() => user.value.badges & UserBadge.AFFILIATE)
const isAffiliate = computed(() => user.value?.badges & UserBadge.AFFILIATE)
const isAdminViewing = computed(() => isAdmin(auth.value.user))
async function toggleAffiliate(id) {
await useBaseFetch(`user/${id}`, {
method: 'PATCH',
body: { badges: user.value.badges ^ (1 << 7) },
})
refreshUser()
await client.labrinth.users_v2.patch(id, { badges: user.value.badges ^ (1 << 7) })
queryClient.invalidateQueries({ queryKey: ['user', route.params.id] })
}
const navLinks = computed(() => [
@@ -865,7 +873,7 @@ const navLinks = computed(() => [
.sort((a, b) => a.label.localeCompare(b.label)),
])
const selectedRole = ref(user.value.role)
const selectedRole = ref(user.value?.role)
const isSavingRole = ref(false)
const roleOptions = [
@@ -893,12 +901,8 @@ function saveRoleEdit() {
isSavingRole.value = true
useBaseFetch(`user/${user.value.id}`, {
method: 'PATCH',
body: {
role: selectedRole.value,
},
})
client.labrinth.users_v2
.patch(user.value.id, { role: selectedRole.value })
.then(() => {
user.value.role = selectedRole.value