feat: add admin console and account settings
Implement the admin dashboard with review, user, and image management workflows. Add profile settings, password reset, pending upload defaults, community placeholder routing, Vercel SPA rewrites, refreshed header styling, and the OpenCloud color-system skill.
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { NAlert, NButton, NCard, NForm, NFormItem, NInput, NResult } from 'naive-ui'
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const password = ref('')
|
||||
const confirmPassword = ref('')
|
||||
const error = ref('')
|
||||
const success = ref(false)
|
||||
const loading = ref(false)
|
||||
|
||||
const canSubmit = computed(() => password.value.length >= 6 && password.value === confirmPassword.value)
|
||||
|
||||
function getRecoveryTokens() {
|
||||
const hash = new URLSearchParams(window.location.hash.slice(1))
|
||||
return {
|
||||
accessToken: hash.get('access_token'),
|
||||
refreshToken: hash.get('refresh_token'),
|
||||
type: hash.get('type'),
|
||||
error: hash.get('error'),
|
||||
}
|
||||
}
|
||||
|
||||
async function handleResetPassword() {
|
||||
error.value = ''
|
||||
|
||||
if (password.value.length < 6) {
|
||||
error.value = '新密码至少需要 6 位。'
|
||||
return
|
||||
}
|
||||
if (password.value !== confirmPassword.value) {
|
||||
error.value = '两次输入的密码不一致。'
|
||||
return
|
||||
}
|
||||
|
||||
const { accessToken, refreshToken, type, error: recoveryError } = getRecoveryTokens()
|
||||
if (recoveryError || type !== 'recovery' || !accessToken || !refreshToken) {
|
||||
error.value = '密码重置链接无效或已过期,请重新发送邮件。'
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const resetClient = createClient(
|
||||
import.meta.env.VITE_SUPABASE_URL,
|
||||
import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY,
|
||||
)
|
||||
|
||||
const { error: sessionError } = await resetClient.auth.setSession({
|
||||
access_token: accessToken,
|
||||
refresh_token: refreshToken,
|
||||
})
|
||||
if (sessionError) throw sessionError
|
||||
|
||||
const { error: updateError } = await resetClient.auth.updateUser({ password: password.value })
|
||||
if (updateError) throw updateError
|
||||
|
||||
await resetClient.auth.signOut()
|
||||
window.history.replaceState(null, '', window.location.pathname)
|
||||
success.value = true
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : '密码重置失败,请稍后重试。'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-[calc(100vh-4rem)] px-4 py-10">
|
||||
<div class="mx-auto max-w-2xl">
|
||||
<NCard class="shadow-[12px_12px_0_0_rgba(15,23,42,0.08)]">
|
||||
<NResult
|
||||
v-if="success"
|
||||
status="success"
|
||||
title="密码已重置"
|
||||
description="现在可以使用新密码登录。"
|
||||
>
|
||||
<template #footer>
|
||||
<NButton type="primary" @click="router.push('/login')">返回登录</NButton>
|
||||
</template>
|
||||
</NResult>
|
||||
|
||||
<template v-else>
|
||||
<div class="mb-8">
|
||||
<div class="text-sm uppercase tracking-[0.22em] text-slate-500">Password Recovery</div>
|
||||
<h1 class="mt-3 text-3xl font-bold text-slate-900">设置新密码</h1>
|
||||
<p class="mt-2 text-sm text-slate-500">请输入新的登录密码,提交后当前重置链接会失效。</p>
|
||||
</div>
|
||||
|
||||
<NForm @submit.prevent="handleResetPassword">
|
||||
<NFormItem label="新密码">
|
||||
<NInput
|
||||
v-model:value="password"
|
||||
type="password"
|
||||
required
|
||||
show-password-on="click"
|
||||
autocomplete="new-password"
|
||||
placeholder="至少 6 位"
|
||||
/>
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="确认新密码">
|
||||
<NInput
|
||||
v-model:value="confirmPassword"
|
||||
type="password"
|
||||
required
|
||||
show-password-on="click"
|
||||
autocomplete="new-password"
|
||||
placeholder="再次输入新密码"
|
||||
/>
|
||||
</NFormItem>
|
||||
|
||||
<NAlert v-if="error" type="error" class="mb-4">
|
||||
{{ error }}
|
||||
</NAlert>
|
||||
|
||||
<NButton
|
||||
attr-type="submit"
|
||||
type="primary"
|
||||
block
|
||||
size="large"
|
||||
:disabled="!canSubmit"
|
||||
:loading="loading"
|
||||
>
|
||||
保存新密码
|
||||
</NButton>
|
||||
</NForm>
|
||||
</template>
|
||||
</NCard>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user