mirror of
https://github.com/snowykami/neo-blog.git
synced 2025-09-26 11:06:23 +00:00
refactor: restructure authentication components and routes
All checks were successful
Push to Helm Chart Repository / build (push) Successful in 13s
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:
187
web/src/components/auth/register/register-form.tsx
Normal file
187
web/src/components/auth/register/register-form.tsx
Normal 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>
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user