diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx new file mode 100644 index 0000000..bf56382 --- /dev/null +++ b/src/app/(auth)/login/page.tsx @@ -0,0 +1,66 @@ +"use client"; + +import { authClient } from "@/lib/auth-client"; +import Link from "next/link"; +import { useRouter, useSearchParams } from "next/navigation"; +import { useEffect } from "react"; +import { toast } from "sonner"; + +export default function LoginPage() { + const searchParams = useSearchParams(); + const redirectTo = searchParams.get("redirect"); + + const session = authClient.useSession().data; + const router = useRouter(); + + useEffect(() => { + if (session) { + router.push(redirectTo ?? "/profile"); + } + }); + + function login() { + const username = (document.getElementById("username") as HTMLInputElement).value; + const password = (document.getElementById("password") as HTMLInputElement).value; + console.log(username, password); + if (username.includes("@")) { + authClient.signIn.email({ + email: username, + password: username + }); + } else { + authClient.signIn.username({ + username: username, + password: password, + fetchOptions: { + onError: (ctx) => { + toast.error(ctx.error.message); + } + } + }); + } + } + + return ( +
+
+

登录

+ + + + 没有账号?去注册 +
+
+ ); +} diff --git a/src/app/(auth)/logout/page.tsx b/src/app/(auth)/logout/page.tsx new file mode 100644 index 0000000..e748151 --- /dev/null +++ b/src/app/(auth)/logout/page.tsx @@ -0,0 +1,25 @@ +import { auth } from "@/auth"; +import { headers } from "next/headers"; +import { redirect } from "next/navigation"; + +export default async function LogoutPage( + props: { + searchParams: Promise<{ [key: string]: string | undefined; }>; + } +) { + const searchParams = await props.searchParams; + const redirectTo = props.searchParams ?? null; + + const session = await auth.api.getSession({ + headers: await headers() + }); + if (session) { + await auth.api.signOut({ + headers: await headers() + }); + redirect("/login" + (redirectTo ? `?redirect=${redirectTo}` : "")); + } else { + redirect("/profile"); + } + return (<>); +} \ No newline at end of file diff --git a/src/app/(auth)/profile/page.tsx b/src/app/(auth)/profile/page.tsx new file mode 100644 index 0000000..6cc92d6 --- /dev/null +++ b/src/app/(auth)/profile/page.tsx @@ -0,0 +1,13 @@ +import { auth } from "@/auth"; +import { redirect } from "next/navigation"; +import { headers } from "next/headers"; + +export default async function ProfilePage() { + const session = await auth.api.getSession({ headers: await headers() }); + + if (!session) { + redirect("/login?redirect=/profile"); + } + + redirect(`/users/${session.user.username}`); +} diff --git a/src/app/(auth)/signup/page.tsx b/src/app/(auth)/signup/page.tsx new file mode 100644 index 0000000..3d78235 --- /dev/null +++ b/src/app/(auth)/signup/page.tsx @@ -0,0 +1,67 @@ +"use client"; + +import { authClient } from "@/lib/auth-client"; +import Link from "next/link"; +import { useRouter, useSearchParams } from "next/navigation"; +import { useEffect } from "react"; +import { toast } from "sonner"; + +export default function SignUpPage() { + const searchParams = useSearchParams(); + const redirectTo = searchParams.get("redirect"); + + const session = authClient.useSession().data; + const router = useRouter(); + + console.log(JSON.stringify({ re: redirectTo })); + + useEffect(() => { + if (session) { + router.push(redirectTo ?? "/profile"); + } + }); + + function login() { + const username = (document.getElementById("username") as HTMLInputElement).value; + const email = (document.getElementById("email") as HTMLInputElement).value; + const password = (document.getElementById("password") as HTMLInputElement).value; + authClient.signUp.email({ + email: email, + name: username, + username: username, + password: password, + fetchOptions: { + onError: (ctx) => { + toast.error(ctx.error.message); + } + } + }); + } + + return ( +
+
+

注册

+ + + + + 已有账号?去登录 +
+
+ ); +} diff --git a/src/app/users/[username]/page.tsx b/src/app/(auth)/users/[username]/page.tsx similarity index 98% rename from src/app/users/[username]/page.tsx rename to src/app/(auth)/users/[username]/page.tsx index 9ae3839..153aad2 100644 --- a/src/app/users/[username]/page.tsx +++ b/src/app/(auth)/users/[username]/page.tsx @@ -1,14 +1,14 @@ import Image from "next/image"; import Link from "next/link"; import { PageLayout } from "@/components/ui/PageLayout"; -import { LinkButton } from "@/design-system/base/button"; +import { LightButton, LinkButton } from "@/design-system/base/button"; import { actionGetUserProfileByUsername } from "@/modules/auth/auth-action"; import { repoGetFoldersWithTotalPairsByUserId } from "@/modules/folder/folder-repository"; import { notFound } from "next/navigation"; import { getTranslations } from "next-intl/server"; import { auth } from "@/auth"; import { headers } from "next/headers"; -import { LogoutButton } from "@/app/users/[username]/LogoutButton"; +// import { LogoutButton } from "./LogoutButton"; interface UserPageProps { params: Promise<{ username: string; }>; @@ -42,7 +42,7 @@ export default async function UserPage({ params }: UserPageProps) {
- {isOwnProfile && } + {isOwnProfile && 登出}
{/* Avatar */} diff --git a/src/app/auth/AuthForm.tsx b/src/app/auth/AuthForm.tsx deleted file mode 100644 index a44404b..0000000 --- a/src/app/auth/AuthForm.tsx +++ /dev/null @@ -1,285 +0,0 @@ -"use client"; - -import { useState, useActionState, startTransition } from "react"; -import { useTranslations } from "next-intl"; -import { PageLayout } from "@/components/ui/PageLayout"; -import { Input } from "@/design-system/base/input"; -import { LightButton, LinkButton } from "@/design-system/base/button"; -import { authClient } from "@/lib/auth-client"; -import { actionSignIn, actionSignUp, ActionOutputAuth } from "@/modules/auth/auth-action"; - -interface AuthFormProps { - redirectTo?: string; -} - -export function AuthForm({ redirectTo }: AuthFormProps) { - const t = useTranslations("auth"); - const [mode, setMode] = useState<'signin' | 'signup'>('signin'); - const [clearSignIn, setClearSignIn] = useState(false); - const [clearSignUp, setClearSignUp] = useState(false); - - const [signInState, signInActionForm, isSignInPending] = useActionState( - async (_prevState: ActionOutputAuth | undefined, formData: FormData) => { - if (clearSignIn) { - setClearSignIn(false); - return undefined; - } - return actionSignIn(undefined, formData); - }, - undefined - ); - const [signUpState, signUpActionForm, isSignUpPending] = useActionState( - async (_prevState: ActionOutputAuth | undefined, formData: FormData) => { - if (clearSignUp) { - setClearSignUp(false); - return undefined; - } - return actionSignUp(undefined, formData); - }, - undefined - ); - - const [errors, setErrors] = useState>({}); - - const validateForm = (formData: FormData): boolean => { - const newErrors: Record = {}; - - const identifier = formData.get("identifier") as string; - const email = formData.get("email") as string; - const username = formData.get("username") as string; - const password = formData.get("password") as string; - const confirmPassword = formData.get("confirmPassword") as string; - - // 登录模式验证 - if (mode === 'signin') { - if (!identifier) { - newErrors.identifier = t("identifierRequired"); - } - } else { - // 注册模式验证 - if (!email) { - newErrors.email = t("emailRequired"); - } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { - newErrors.email = t("invalidEmail"); - } - - if (!username) { - newErrors.username = t("usernameRequired"); - } else if (username.length < 3) { - newErrors.username = t("usernameTooShort"); - } else if (!/^[a-zA-Z0-9_]+$/.test(username)) { - newErrors.username = t("usernameInvalid"); - } - } - - if (!password) { - newErrors.password = t("passwordRequired"); - } else if (password.length < 8) { - newErrors.password = t("passwordTooShort"); - } - - if (mode === 'signup') { - if (!confirmPassword) { - newErrors.confirmPassword = t("confirmPasswordRequired"); - } else if (password !== confirmPassword) { - newErrors.confirmPassword = t("passwordsNotMatch"); - } - } - - setErrors(newErrors); - return Object.keys(newErrors).length === 0; - }; - - const handleFormSubmit = (e: React.FormEvent) => { - e.preventDefault(); - const formData = new FormData(e.currentTarget); - - // 基本客户端验证 - if (!validateForm(formData)) { - return; - } - - // 添加 redirectTo 到 formData - if (redirectTo) { - formData.append("redirectTo", redirectTo); - } - - // 使用 startTransition 包装 action 调用 - startTransition(() => { - // 根据模式调用相应的 action - if (mode === 'signin') { - signInActionForm(formData); - } else { - signUpActionForm(formData); - } - }); - }; - - const handleGitHubSignIn = async () => { - await authClient.signIn.social({ - provider: "github", - callbackURL: redirectTo || "/" - }); - }; - - const currentError = mode === 'signin' ? signInState : signUpState; - - return ( - - {/* 页面标题 */} -
-

{t(mode === 'signin' ? 'signIn' : 'signUp')}

-
- - {/* 服务器端错误提示 */} - {currentError?.message && ( -
- {currentError.message} -
- )} - - {/* 登录/注册表单 */} -
- {/* 邮箱/用户名输入(登录模式)或 用户名输入(注册模式) */} - {mode === 'signin' ? ( -
- - {errors.identifier && ( -

{errors.identifier}

- )} - {currentError?.errors?.email && ( -

{currentError.errors.email[0]}

- )} -
- ) : ( - <> - {/* 用户名输入(仅注册模式) */} -
- - {errors.username && ( -

{errors.username}

- )} - {currentError?.errors?.username && ( -

{currentError.errors.username[0]}

- )} -
- - {/* 邮箱输入(仅注册模式) */} -
- - {errors.email && ( -

{errors.email}

- )} - {currentError?.errors?.email && ( -

{currentError.errors.email[0]}

- )} -
- - )} - - {/* 密码输入 */} -
- - {errors.password && ( -

{errors.password}

- )} - {currentError?.errors?.password && ( -

{currentError.errors.password[0]}

- )} -
- - {/* 确认密码输入(仅注册模式显示) */} - {mode === 'signup' && ( -
- - {errors.confirmPassword && ( -

{errors.confirmPassword}

- )} -
- )} - - {/* 提交按钮 */} - - {isSignInPending || isSignUpPending - ? t("loading") - : t(mode === 'signin' ? 'signInButton' : 'signUpButton') - } - -
- - {/* 第三方登录区域 */} -
- {/* 分隔线 */} -
-
-
-
-
- -
-
- - {/* GitHub 登录按钮 */} - - - - - {t(mode === 'signin' ? 'signInWithGitHub' : 'signUpWithGitHub')} - -
- - {/* 模式切换链接 */} -
- { - setMode(mode === 'signin' ? 'signup' : 'signin'); - setErrors({}); - // 清除服务器端错误状态 - if (mode === 'signin') { - setClearSignIn(true); - } else { - setClearSignUp(true); - } - }} - > - {mode === 'signin' - ? `${t("noAccount")} ${t("signUp")}` - : `${t("hasAccount")} ${t("signIn")}` - } - -
-
- ); -} diff --git a/src/app/auth/page.tsx b/src/app/auth/page.tsx deleted file mode 100644 index 81f0286..0000000 --- a/src/app/auth/page.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { auth } from "@/auth"; -import { headers } from "next/headers"; -import { redirect } from "next/navigation"; -import { AuthForm } from "./AuthForm"; - -export default async function AuthPage( - props: { - searchParams: Promise<{ [key: string]: string | string[] | undefined; }>; - } -) { - const searchParams = await props.searchParams; - const redirectTo = searchParams.redirect as string | undefined; - - const session = await auth.api.getSession({ headers: await headers() }); - if (session) { - redirect(redirectTo || '/'); - } - - return ; -} diff --git a/src/app/profile/page.tsx b/src/app/profile/page.tsx deleted file mode 100644 index a672e00..0000000 --- a/src/app/profile/page.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { auth } from "@/auth"; -import { redirect } from "next/navigation"; -import { headers } from "next/headers"; - -export default async function ProfilePage() { - const session = await auth.api.getSession({ headers: await headers() }); - - if (!session) { - redirect("/auth?redirect=/profile"); - } - - // 已登录,跳转到用户资料页面 - // 优先使用 username,如果没有则使用 email - const username = (session.user.username as string) || (session.user.email as string); - redirect(`/users/${username}`); -} diff --git a/src/app/users/[username]/LogoutButton.tsx b/src/app/users/[username]/LogoutButton.tsx deleted file mode 100644 index 448c769..0000000 --- a/src/app/users/[username]/LogoutButton.tsx +++ /dev/null @@ -1,20 +0,0 @@ -"use client"; - -import { LightButton } from "@/design-system/base/button"; -import { authClient } from "@/lib/auth-client"; -import { useTranslations } from "next-intl"; -import { useRouter } from "next/navigation"; - -export function LogoutButton() { - const t = useTranslations("profile"); - const router = useRouter(); - return { - authClient.signOut({ - fetchOptions: { - onSuccess: () => { - router.push("/auth?redirect=/profile"); - } - } - }); - }}> {t("logout")}; -} \ No newline at end of file diff --git a/src/components/layout/Navbar.tsx b/src/components/layout/Navbar.tsx index 3f008ec..312f09d 100644 --- a/src/components/layout/Navbar.tsx +++ b/src/components/layout/Navbar.tsx @@ -58,8 +58,8 @@ export async function Navbar() { || <> - {t("sign_in")} - + {t("sign_in")} + ;