refactor: restructure authentication components and routes
All checks were successful
Push to Helm Chart Repository / build (push) Successful in 13s

- Removed the old reset password form component and replaced it with a new implementation.
- Updated routing paths for login, registration, and reset password to be under a common auth path.
- Added new login and registration pages with corresponding forms.
- Introduced a common auth header component for consistent branding across auth pages.
- Implemented a current logged-in user display component.
- Enhanced the register form to include email verification and captcha.
- Updated translations for new and modified components.
- Refactored the navigation bar to include user avatar dropdown and improved menu structure.
This commit is contained in:
2025-09-23 02:21:03 +08:00
parent 0f7cbb385a
commit 349cf5a5b7
23 changed files with 380 additions and 112 deletions

View File

@ -0,0 +1,187 @@
"use client"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { useEffect, useState } from "react"
import { getCaptchaConfig, requestEmailVerifyCode, userRegister } from "@/api/user"
import { useRouter, useSearchParams } from "next/navigation"
import { useTranslations } from "next-intl"
import Captcha from "@/components/common/captcha"
import { CaptchaProvider } from "@/models/captcha"
import { toast } from "sonner"
import { CurrentLogged } from "@/components/auth/common/current-logged"
import { SectionDivider } from "@/components/common/section-divider"
import { InputOTPControlled } from "@/components/common/input-otp"
import { BaseErrorResponse } from "@/models/resp"
import { useAuth } from "@/contexts/auth-context"
export function RegisterForm({
className,
...props
}: React.ComponentProps<"div">) {
const { setUser } = useAuth();
const t = useTranslations('Register')
const commonT = useTranslations('Common')
const router = useRouter()
const searchParams = useSearchParams()
const redirectBack = searchParams.get("redirect_back") || "/"
const [captchaProps, setCaptchaProps] = useState<{
provider: CaptchaProvider
siteKey: string
url?: string
} | null>(null)
const [captchaToken, setCaptchaToken] = useState<string | null>(null)
const [isLogging, setIsLogging] = useState(false)
const [refreshCaptchaKey, setRefreshCaptchaKey] = useState(0)
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [email, setEmail] = useState('')
const [verifyCode, setVerifyCode] = useState('')
useEffect(() => {
getCaptchaConfig()
.then((res) => {
setCaptchaProps(res.data)
})
.catch((error) => {
toast.error(t("fetch_captcha_config_failed") + (error?.message ? `: ${error.message}` : ""))
setCaptchaProps(null)
})
}, [refreshCaptchaKey, t])
const handleCaptchaError = (error: string) => {
toast.error(t("captcha_error") + (error ? `: ${error}` : ""));
setTimeout(() => {
setRefreshCaptchaKey(k => k + 1);
}, 1500);
}
const handleSendVerifyCode = () => {
requestEmailVerifyCode(email)
.then(() => {
toast.success(t("send_verify_code_success"))
})
.catch((error: BaseErrorResponse) => {
toast.error(`${t("send_verify_code_failed")}: ${error.response.data.message}`)
})
}
const handleRegister = () => {
if (!username || !password || !email) {
toast.error(t("please_fill_in_all_required_fields"));
return;
}
if (!captchaToken) {
toast.error(t("please_complete_captcha_verification"));
return;
}
userRegister({ username, password, email, verifyCode, captchaToken })
.then(res => {
toast.success(t("register_success") + ` ${res.data.user.nickname || res.data.user.username}`);
setUser(res.data.user);
router.push(redirectBack)
})
.catch((error: BaseErrorResponse) => {
toast.error(t("register_failed") + (error?.response?.data?.message ? `: ${error.response.data.message}` : ""))
setRefreshCaptchaKey(k => k + 1)
setCaptchaToken(null)
})
.finally(() => {
setIsLogging(false)
})
}
return (
<div className={cn("flex flex-col gap-6", className)} {...props}>
<Card>
<CardHeader className="text-center">
<CardTitle className="text-xl">{t("title")}</CardTitle>
</CardHeader>
<CardContent>
<CurrentLogged />
<form>
<div className="grid gap-6">
<SectionDivider className="mt-4">{t("register_a_new_account")}</SectionDivider>
<div className="grid gap-6">
{/* 用户名 */}
<div className="grid gap-3">
<div className="flex items-center">
<Label htmlFor="username">{commonT("username")}</Label>
</div>
<Input
id="username"
type="text"
required
value={username}
onChange={e => setUsername(e.target.value)}
/>
</div>
{/* 密码 */}
<div className="grid gap-3">
<div className="flex items-center">
<Label htmlFor="password">{commonT("password")}</Label>
</div>
<Input
id="password"
type="password"
required
value={password}
onChange={e => setPassword(e.target.value)}
/>
</div>
{/* 邮箱 */}
<div className="grid gap-3">
<Label htmlFor="email">{commonT("email")}</Label>
<div className="flex gap-3">
<Input
id="email"
type="text"
placeholder="example@liteyuki.org"
required
value={email}
onChange={e => setEmail(e.target.value)}
/>
<Button onClick={handleSendVerifyCode} disabled={!email} variant="outline" className="border-2" type="button">
{commonT("send_verify_code")}
</Button>
</div>
</div>
{/* 邮箱验证码 */}
<div className="grid gap-3">
<Label htmlFor="email">{commonT("verify_code")}</Label>
<InputOTPControlled
onChange={value => setVerifyCode(value)}
/>
</div>
{captchaProps &&
<div className="flex justify-center items-center w-full">
<Captcha {...captchaProps} onSuccess={setCaptchaToken} onError={handleCaptchaError} key={refreshCaptchaKey} />
</div>
}
<Button
type="button"
className="w-full"
onClick={handleRegister}
disabled={!captchaToken || isLogging || !username || !password || !email}
>
{isLogging ? t("registering") : t("register")}
</Button>
</div>
</div>
</form>
</CardContent>
</Card>
</div>
)
}