fix: resolve auth initialization race and username flicker on login
Always initialize auth before mounting the app so loading state completes and onAuthStateChange is registered on every route. Gate header auth UI on !loading to avoid flicker during email confirm and password reset flows. Load profile before setting user ref so displayUsername shows the correct name immediately instead of flashing the email.
This commit is contained in:
@@ -179,20 +179,21 @@ async function handleLogout() {
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<NSpace align="center" :size="8" class="shrink-0 md:[&_.n-button]:px-4">
|
<NSpace align="center" :size="8" class="shrink-0 md:[&_.n-button]:px-4">
|
||||||
<RouterLink
|
<template v-if="!authStore.loading">
|
||||||
v-if="authStore.isLoggedIn"
|
<RouterLink
|
||||||
to="/upload"
|
v-if="authStore.isLoggedIn"
|
||||||
class="no-underline"
|
to="/upload"
|
||||||
>
|
class="no-underline"
|
||||||
<span
|
|
||||||
class="inline-flex h-8 items-center border border-sky-200 bg-sky-100 px-3 text-sm font-medium uppercase tracking-[0.12em] text-sky-800 shadow-[4px_4px_0_0_rgba(14,165,233,0.14)] transition-colors hover:bg-sky-50 hover:text-sky-900 md:h-10 md:px-4"
|
|
||||||
:class="route.name === 'upload' ? 'ring-1 ring-sky-300' : ''"
|
|
||||||
>
|
>
|
||||||
上传
|
<span
|
||||||
</span>
|
class="inline-flex h-8 items-center border border-sky-200 bg-sky-100 px-3 text-sm font-medium uppercase tracking-[0.12em] text-sky-800 shadow-[4px_4px_0_0_rgba(14,165,233,0.14)] transition-colors hover:bg-sky-50 hover:text-sky-900 md:h-10 md:px-4"
|
||||||
</RouterLink>
|
:class="route.name === 'upload' ? 'ring-1 ring-sky-300' : ''"
|
||||||
|
>
|
||||||
|
上传
|
||||||
|
</span>
|
||||||
|
</RouterLink>
|
||||||
|
|
||||||
<template v-if="authStore.isLoggedIn">
|
<template v-if="authStore.isLoggedIn">
|
||||||
<div
|
<div
|
||||||
class="relative"
|
class="relative"
|
||||||
@mouseenter="openAccountCard"
|
@mouseenter="openAccountCard"
|
||||||
@@ -281,29 +282,30 @@ async function handleLogout() {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<RouterLink
|
<RouterLink
|
||||||
to="/login"
|
to="/login"
|
||||||
class="no-underline"
|
class="no-underline"
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="inline-flex h-8 items-center border border-slate-200 bg-white/80 px-3 text-sm font-medium text-slate-700 shadow-[3px_3px_0_0_rgba(15,23,42,0.06)] transition-colors hover:border-teal-200 hover:bg-teal-50 hover:text-teal-800 md:h-10 md:px-4"
|
|
||||||
:class="route.name === 'login' ? 'bg-teal-50 ring-1 ring-teal-200 text-teal-800' : ''"
|
|
||||||
>
|
>
|
||||||
登录
|
<span
|
||||||
</span>
|
class="inline-flex h-8 items-center border border-slate-200 bg-white/80 px-3 text-sm font-medium text-slate-700 shadow-[3px_3px_0_0_rgba(15,23,42,0.06)] transition-colors hover:border-teal-200 hover:bg-teal-50 hover:text-teal-800 md:h-10 md:px-4"
|
||||||
</RouterLink>
|
:class="route.name === 'login' ? 'bg-teal-50 ring-1 ring-teal-200 text-teal-800' : ''"
|
||||||
<RouterLink
|
>
|
||||||
to="/register"
|
登录
|
||||||
class="no-underline"
|
</span>
|
||||||
>
|
</RouterLink>
|
||||||
<span
|
<RouterLink
|
||||||
class="inline-flex h-8 items-center border border-sky-200 bg-sky-100 px-3 text-sm font-medium text-sky-800 shadow-[4px_4px_0_0_rgba(14,165,233,0.14)] transition-colors hover:bg-sky-50 hover:text-sky-900 md:h-10 md:px-4"
|
to="/register"
|
||||||
:class="route.name === 'register' ? 'ring-1 ring-sky-300' : ''"
|
class="no-underline"
|
||||||
>
|
>
|
||||||
注册
|
<span
|
||||||
</span>
|
class="inline-flex h-8 items-center border border-sky-200 bg-sky-100 px-3 text-sm font-medium text-sky-800 shadow-[4px_4px_0_0_rgba(14,165,233,0.14)] transition-colors hover:bg-sky-50 hover:text-sky-900 md:h-10 md:px-4"
|
||||||
</RouterLink>
|
:class="route.name === 'register' ? 'ring-1 ring-sky-300' : ''"
|
||||||
|
>
|
||||||
|
注册
|
||||||
|
</span>
|
||||||
|
</RouterLink>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</div>
|
</div>
|
||||||
@@ -338,7 +340,7 @@ async function handleLogout() {
|
|||||||
图鉴
|
图鉴
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<RouterLink
|
<RouterLink
|
||||||
v-if="authStore.isLoggedIn"
|
v-if="!authStore.loading && authStore.isLoggedIn"
|
||||||
to="/profile"
|
to="/profile"
|
||||||
class="shrink-0 px-3 py-2 text-sm font-medium tracking-[0.12em] uppercase transition-colors"
|
class="shrink-0 px-3 py-2 text-sm font-medium tracking-[0.12em] uppercase transition-colors"
|
||||||
:class="route.name === 'profile' ? activeNavClass : inactiveNavClass"
|
:class="route.name === 'profile' ? activeNavClass : inactiveNavClass"
|
||||||
|
|||||||
+2
-7
@@ -14,12 +14,7 @@ app.use(pinia)
|
|||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
if (['/auth/confirm', '/auth/reset-password'].includes(window.location.pathname)) {
|
authStore.initialize().then(() => {
|
||||||
app.use(router)
|
app.use(router)
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
} else {
|
})
|
||||||
authStore.initialize().then(() => {
|
|
||||||
app.use(router)
|
|
||||||
app.mount('#app')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
+17
-9
@@ -12,12 +12,13 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
const isLoggedIn = computed(() => !!user.value)
|
const isLoggedIn = computed(() => !!user.value)
|
||||||
const isAdmin = computed(() => profile.value?.role === 'admin')
|
const isAdmin = computed(() => profile.value?.role === 'admin')
|
||||||
|
|
||||||
async function fetchProfile() {
|
async function fetchProfile(userId?: string) {
|
||||||
if (!user.value) return
|
const id = userId ?? user.value?.id
|
||||||
|
if (!id) return
|
||||||
const { data } = await supabase
|
const { data } = await supabase
|
||||||
.from('profiles')
|
.from('profiles')
|
||||||
.select('*')
|
.select('*')
|
||||||
.eq('id', user.value.id)
|
.eq('id', id)
|
||||||
.single()
|
.single()
|
||||||
if (data) {
|
if (data) {
|
||||||
profile.value = data as Profile
|
profile.value = data as Profile
|
||||||
@@ -53,8 +54,8 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
}
|
}
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
await fetchProfile(data.user.id)
|
||||||
user.value = data.user
|
user.value = data.user
|
||||||
await fetchProfile()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function register(email: string, password: string, username: string) {
|
async function register(email: string, password: string, username: string) {
|
||||||
@@ -122,14 +123,21 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
|
|
||||||
async function initialize() {
|
async function initialize() {
|
||||||
const { data: { session } } = await supabase.auth.getSession()
|
const { data: { session } } = await supabase.auth.getSession()
|
||||||
user.value = session?.user ?? null
|
const initialUser = session?.user ?? null
|
||||||
if (user.value) await fetchProfile()
|
if (initialUser) await fetchProfile(initialUser.id)
|
||||||
|
user.value = initialUser
|
||||||
loading.value = false
|
loading.value = false
|
||||||
|
|
||||||
supabase.auth.onAuthStateChange((_event, session) => {
|
supabase.auth.onAuthStateChange((_event, session) => {
|
||||||
user.value = session?.user ?? null
|
const nextUser = session?.user ?? null
|
||||||
if (user.value) fetchProfile()
|
if (nextUser) {
|
||||||
else profile.value = null
|
fetchProfile(nextUser.id).then(() => {
|
||||||
|
user.value = nextUser
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
user.value = null
|
||||||
|
profile.value = null
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user