feat: complete auth flow - email confirmation, login state, route guards

This commit is contained in:
2026-05-21 00:21:28 +08:00
parent c49ac7a42e
commit d4b07fba58
6 changed files with 258 additions and 78 deletions
+106
View File
@@ -0,0 +1,106 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { createClient } from '@supabase/supabase-js'
const router = useRouter()
const state = ref<'loading' | 'success' | 'failed'>('loading')
const countdown = ref(5)
let countdownTimer: ReturnType<typeof setInterval> | null = null
function startCountdown() {
countdownTimer = setInterval(() => {
countdown.value--
if (countdown.value <= 0) {
if (countdownTimer) clearInterval(countdownTimer)
router.push('/login')
}
}, 1000)
}
onMounted(async () => {
try {
const hash = window.location.hash.slice(1)
const params = new URLSearchParams(hash)
const accessToken = params.get('access_token')
const refreshToken = params.get('refresh_token')
const type = params.get('type')
const error = params.get('error')
if (error) {
state.value = 'failed'
return
}
if (type === 'signup' && accessToken && refreshToken) {
const confirmClient = createClient(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY,
)
const { error: setSessionError } = await confirmClient.auth.setSession({
access_token: accessToken,
refresh_token: refreshToken,
})
if (setSessionError) {
state.value = 'failed'
return
}
await confirmClient.auth.signOut()
window.history.replaceState(null, '', window.location.pathname)
state.value = 'success'
startCountdown()
return
}
state.value = 'failed'
} catch {
state.value = 'failed'
}
})
onUnmounted(() => {
if (countdownTimer) clearInterval(countdownTimer)
})
</script>
<template>
<div class="flex items-center justify-center min-h-[calc(100vh-4rem)] px-4">
<div class="w-full max-w-sm text-center">
<template v-if="state === 'success'">
<span class="text-5xl block mb-4"></span>
<h1 class="text-2xl font-bold text-gray-900 mb-2">邮箱认证成功</h1>
<p class="text-gray-500 mb-2">你的邮箱已确认现在可以登录了</p>
<p class="text-sm text-gray-400 mb-6">{{ countdown }} 秒后自动跳转登录页面...</p>
<RouterLink
to="/login"
class="inline-flex items-center px-6 py-2.5 bg-sky-500 text-white font-medium rounded-lg hover:bg-sky-600 transition-colors"
>
立即登录
</RouterLink>
</template>
<template v-else-if="state === 'failed'">
<span class="text-5xl block mb-4"></span>
<h1 class="text-2xl font-bold text-gray-900 mb-2">认证失败</h1>
<p class="text-gray-500 mb-6">邮箱确认链接无效或已过期请重新注册</p>
<RouterLink
to="/register"
class="inline-flex items-center px-6 py-2.5 bg-sky-500 text-white font-medium rounded-lg hover:bg-sky-600 transition-colors"
>
重新注册
</RouterLink>
</template>
<template v-else>
<span class="text-5xl block mb-4 animate-pulse"></span>
<h1 class="text-2xl font-bold text-gray-900 mb-2">正在验证...</h1>
<p class="text-gray-500">请稍候</p>
</template>
</div>
</div>
</template>