From 5812b7b0ed32fee42cdafc70d9fd55a8ff66a52c Mon Sep 17 00:00:00 2001 From: zetaloop Date: Wed, 25 Feb 2026 15:49:37 +0800 Subject: [PATCH] feat(auth): redesign auth pages with brand panel, IconInput and forgot-password --- app/(auth)/forgot-password/page.tsx | 64 ++++++++++ app/(auth)/layout.tsx | 74 ++++++++--- app/(auth)/login/page.tsx | 107 +++++++++------- app/(auth)/register/page.tsx | 185 ++++++++++++++++++---------- app/globals.css | 9 ++ components/ui/icon-input.tsx | 27 ++++ 6 files changed, 344 insertions(+), 122 deletions(-) create mode 100644 app/(auth)/forgot-password/page.tsx create mode 100644 components/ui/icon-input.tsx diff --git a/app/(auth)/forgot-password/page.tsx b/app/(auth)/forgot-password/page.tsx new file mode 100644 index 0000000..94d3cd7 --- /dev/null +++ b/app/(auth)/forgot-password/page.tsx @@ -0,0 +1,64 @@ +"use client" + +import { Button } from "@/components/ui/button" +import { IconInput } from "@/components/ui/icon-input" +import { Label } from "@/components/ui/label" +import { notifySuccess } from "@/lib/toast" +import { standardSchemaResolver } from "@hookform/resolvers/standard-schema" +import { Mail } from "lucide-react" +import Link from "next/link" +import { useForm } from "react-hook-form" +import { z } from "zod" + +const forgotSchema = z.object({ + email: z.string().email("请输入正确的邮箱地址"), +}) + +export default function ForgotPasswordPage() { + const { + register, + handleSubmit, + formState: { errors, isSubmitting }, + } = useForm({ + resolver: standardSchemaResolver(forgotSchema), + }) + + const onSubmit = async (_data: z.infer) => { + await new Promise((r) => setTimeout(r, 500)) + notifySuccess("重置链接已发送到你的邮箱") + } + + return ( + <> +
+

找回密码

+

输入注册邮箱,我们将发送重置链接

+
+ +
+
+ + } + type="email" + placeholder="输入注册邮箱" + {...register("email")} + /> + {errors.email &&

{errors.email.message}

} +
+ + +
+ +

+ 想起密码了?{" "} + + 返回登录 + +

+ + ) +} diff --git a/app/(auth)/layout.tsx b/app/(auth)/layout.tsx index 4910471..a542051 100644 --- a/app/(auth)/layout.tsx +++ b/app/(auth)/layout.tsx @@ -1,27 +1,67 @@ -import { Gamepad2 } from "lucide-react" +import { ArrowLeft, Gamepad2 } from "lucide-react" +import Link from "next/link" + +const stats = [ + { value: "12,000+", label: "认证打手" }, + { value: "98.6%", label: "好评率" }, + { value: "50,000+", label: "完成订单" }, +] export default function AuthLayout({ children }: { children: React.ReactNode }) { return ( -
-
-
-
-
-
-
-
- -
- 聚玩 JuWan +
+ {/* Left: Brand Panel */} +
+ +
+
-
-

找到你的游戏搭档

-

找人一起打游戏,从这里开始

+ 聚玩 + + +
+

+ 发现你的 +
+ 专属游戏搭档 +

+

+ 聚玩连接玩家与专业打手,提供安全、透明、高效的游戏陪玩服务。无论你是寻找搭档还是展示技能,这里都是你的舞台。 +

+ +
+ {stats.map((stat) => ( +
+
{stat.value}
+
{stat.label}
+
+ ))}
+ +
© 2025 聚玩. All rights reserved.
-
-
{children}
+ + {/* Right: Auth Form */} +
+ {/* Mobile header */} +
+ + + 返回首页 + +
+
+ +
+ 聚玩 +
+
+ +
{children}
) diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx index 7831e7f..5c911c1 100644 --- a/app/(auth)/login/page.tsx +++ b/app/(auth)/login/page.tsx @@ -1,15 +1,16 @@ "use client" import { Button } from "@/components/ui/button" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { Input } from "@/components/ui/input" +import { Checkbox } from "@/components/ui/checkbox" +import { IconInput } from "@/components/ui/icon-input" import { Label } from "@/components/ui/label" import { getCurrentUserForLogin } from "@/lib/api" import { useAuthStore } from "@/store/auth" import { standardSchemaResolver } from "@hookform/resolvers/standard-schema" -import { Gamepad2 } from "lucide-react" +import { Eye, EyeOff, Lock, Phone } from "lucide-react" import Link from "next/link" import { useRouter } from "next/navigation" +import { useState } from "react" import { useForm } from "react-hook-form" import { z } from "zod" @@ -21,6 +22,7 @@ const loginSchema = z.object({ export default function LoginPage() { const router = useRouter() const { login } = useAuthStore() + const [showPassword, setShowPassword] = useState(false) const { register, handleSubmit, @@ -36,47 +38,68 @@ export default function LoginPage() { } return ( - - -
-
- -
- 聚玩 + <> +
+

欢迎回来

+

登录你的聚玩账号,继续游戏之旅

+
+ +
+
+ + } placeholder="输入手机号" {...register("phone")} /> + {errors.phone &&

{errors.phone.message}

}
- 欢迎回来 - 登录你的账号以继续 - - - -
- - - {errors.phone &&

{errors.phone.message}

} -
-
+ +
+
- - {errors.password && ( -

{errors.password.message}

- )} + + 忘记密码? +
- - -

- 还没有账号?{" "} - - 立即注册 - -

- - + } + type={showPassword ? "text" : "password"} + placeholder="输入密码" + rightElement={ + + } + {...register("password")} + /> + {errors.password &&

{errors.password.message}

} +
+ +
+ + +
+ + + + +

+ 还没有账号?{" "} + + 立即注册 + +

+ ) } diff --git a/app/(auth)/register/page.tsx b/app/(auth)/register/page.tsx index 0cd9e45..8aec424 100644 --- a/app/(auth)/register/page.tsx +++ b/app/(auth)/register/page.tsx @@ -1,16 +1,17 @@ "use client" import { Button } from "@/components/ui/button" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { Input } from "@/components/ui/input" +import { Checkbox } from "@/components/ui/checkbox" +import { IconInput } from "@/components/ui/icon-input" import { Label } from "@/components/ui/label" import { getCurrentUserForLogin } from "@/lib/api" import { useAuthStore } from "@/store/auth" import { standardSchemaResolver } from "@hookform/resolvers/standard-schema" -import { Gamepad2 } from "lucide-react" +import { Eye, EyeOff, Lock, Phone, User } from "lucide-react" import Link from "next/link" import { useRouter } from "next/navigation" -import { useForm } from "react-hook-form" +import { useState } from "react" +import { Controller, useForm } from "react-hook-form" import { z } from "zod" const registerSchema = z @@ -19,21 +20,30 @@ const registerSchema = z phone: z.string().min(11, "请输入正确的手机号"), password: z.string().min(6, "密码至少6位"), confirmPassword: z.string(), + agreeTerms: z.boolean(), }) .refine((data) => data.password === data.confirmPassword, { message: "两次密码不一致", path: ["confirmPassword"], }) + .refine((data) => data.agreeTerms, { + message: "请同意用户协议和隐私政策", + path: ["agreeTerms"], + }) export default function RegisterPage() { const router = useRouter() const { login } = useAuthStore() + const [showPassword, setShowPassword] = useState(false) + const [showConfirmPassword, setShowConfirmPassword] = useState(false) const { register, handleSubmit, + control, formState: { errors, isSubmitting }, } = useForm({ resolver: standardSchemaResolver(registerSchema), + defaultValues: { agreeTerms: false }, }) const onSubmit = async (_data: z.infer) => { @@ -43,66 +53,115 @@ export default function RegisterPage() { } return ( - - -
-
- -
- 聚玩 + <> +
+

创建账号

+

注册成为聚玩用户,开始探索

+
+ +
+
+ + } + placeholder="输入你的昵称" + {...register("nickname")} + /> + {errors.nickname &&

{errors.nickname.message}

}
- 创建账号 - 创建你的聚玩账号 - - - -
- - - {errors.nickname && ( -

{errors.nickname.message}

+ +
+ + } placeholder="输入手机号" {...register("phone")} /> + {errors.phone &&

{errors.phone.message}

} +
+ +
+ + } + type={showPassword ? "text" : "password"} + placeholder="设置密码 (至少6位)" + rightElement={ + + } + {...register("password")} + /> + {errors.password &&

{errors.password.message}

} +
+ +
+ + } + type={showConfirmPassword ? "text" : "password"} + placeholder="再次输入密码" + rightElement={ + + } + {...register("confirmPassword")} + /> + {errors.confirmPassword && ( +

{errors.confirmPassword.message}

+ )} +
+ +
+ ( + field.onChange(v === true)} + className="mt-0.5" + /> )} -
-
- - - {errors.phone &&

{errors.phone.message}

} -
-
- - - {errors.password && ( -

{errors.password.message}

- )} -
-
- - - {errors.confirmPassword && ( -

{errors.confirmPassword.message}

- )} -
- - -

- 已有账号?{" "} - - 立即登录 - -

- - + /> + +
+ {errors.agreeTerms && ( +

{errors.agreeTerms.message}

+ )} + + + + +

+ 已有账号?{" "} + + 立即登录 + +

+ ) } diff --git a/app/globals.css b/app/globals.css index 8f8d6e5..3eb8b3e 100644 --- a/app/globals.css +++ b/app/globals.css @@ -131,3 +131,12 @@ @apply bg-background text-foreground; } } + +@layer utilities { + .text-gradient-brand { + background: linear-gradient(135deg, oklch(0.701 0.189 46.65), oklch(0.78 0.16 55)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + } +} diff --git a/components/ui/icon-input.tsx b/components/ui/icon-input.tsx new file mode 100644 index 0000000..17ca542 --- /dev/null +++ b/components/ui/icon-input.tsx @@ -0,0 +1,27 @@ +import type * as React from "react" + +import { cn } from "@/lib/utils" +import { Input } from "./input" + +interface IconInputProps extends React.ComponentProps<"input"> { + icon?: React.ReactNode + rightElement?: React.ReactNode +} + +function IconInput({ icon, rightElement, className, ...props }: IconInputProps) { + return ( +
+ {icon && ( +
+ {icon} +
+ )} + + {rightElement && ( +
{rightElement}
+ )} +
+ ) +} + +export { IconInput }