From a3ef5d88e661460e58e36dbbd30c6aafbc131d04 Mon Sep 17 00:00:00 2001 From: cfngc4594 Date: Mon, 5 May 2025 18:21:08 +0800 Subject: [PATCH] refactor(auth)!: remove components and rewrite sign-in page --- src/actions/auth.ts | 78 ------------- src/app/(auth)/layout.tsx | 37 ------ src/app/(auth)/sign-in/page.tsx | 99 +++++++++++++++- src/app/(auth)/sign-up/page.tsx | 5 - src/components/credentials-sign-in-form.tsx | 121 ------------------- src/components/credentials-sign-up-form.tsx | 123 -------------------- src/components/github-sign-in-form.tsx | 27 ----- src/components/sign-in-form.tsx | 44 ------- src/components/sign-up-form.tsx | 44 ------- src/lib/zod.ts | 13 --- 10 files changed, 96 insertions(+), 495 deletions(-) delete mode 100644 src/actions/auth.ts delete mode 100644 src/app/(auth)/layout.tsx delete mode 100644 src/app/(auth)/sign-up/page.tsx delete mode 100644 src/components/credentials-sign-in-form.tsx delete mode 100644 src/components/credentials-sign-up-form.tsx delete mode 100644 src/components/github-sign-in-form.tsx delete mode 100644 src/components/sign-in-form.tsx delete mode 100644 src/components/sign-up-form.tsx delete mode 100644 src/lib/zod.ts diff --git a/src/actions/auth.ts b/src/actions/auth.ts deleted file mode 100644 index ceb5340..0000000 --- a/src/actions/auth.ts +++ /dev/null @@ -1,78 +0,0 @@ -"use server"; - -import bcrypt from "bcrypt"; -import prisma from "@/lib/prisma"; -import { signIn } from "@/lib/auth"; -import { authSchema } from "@/lib/zod"; -import { getTranslations } from "next-intl/server"; -import { CredentialsSignInFormValues } from "@/components/credentials-sign-in-form"; -import { CredentialsSignUpFormValues } from "@/components/credentials-sign-up-form"; - -const saltRounds = 10; - -export async function signInWithCredentials(formData: CredentialsSignInFormValues, redirectTo?: string) { - const t = await getTranslations("signInWithCredentials"); - - try { - // Parse credentials using authSchema for validation - const { email, password } = await authSchema.parseAsync(formData); - - // Find user by email - const user = await prisma.user.findUnique({ where: { email } }); - - // Check if the user exists - if (!user) { - throw new Error(t("userNotFound")); - } - - // Check if the user has a password - if (!user.password) { - throw new Error(t("invalidCredentials")); - } - - // Check if the password matches - const passwordMatch = await bcrypt.compare(password, user.password); - if (!passwordMatch) { - throw new Error(t("incorrectPassword")); - } - - await signIn("credentials", { ...formData, redirectTo, redirect: !!redirectTo }); - return { success: true }; - } catch (error) { - return { error: error instanceof Error ? error.message : t("signInFailedFallback") }; - } -} - -export async function signUpWithCredentials(formData: CredentialsSignUpFormValues) { - const t = await getTranslations("signUpWithCredentials"); - - try { - const validatedData = await authSchema.parseAsync(formData); - - // Check if user already exists - const existingUser = await prisma.user.findUnique({ where: { email: validatedData.email } }); - if (existingUser) { - throw new Error(t("userAlreadyExists")); - } - - // Hash password and create user - const pwHash = await bcrypt.hash(validatedData.password, saltRounds); - const user = await prisma.user.create({ - data: { email: validatedData.email, password: pwHash }, - }); - - // Assign admin role if first user - const userCount = await prisma.user.count(); - if (userCount === 1) { - await prisma.user.update({ where: { id: user.id }, data: { role: "ADMIN" } }); - } - - return { success: true }; - } catch (error) { - return { error: error instanceof Error ? error.message : t("registrationFailedFallback") }; - } -} - -export async function signInWithGithub(redirectTo?: string) { - await signIn("github", { redirectTo, redirect: !!redirectTo }); -} diff --git a/src/app/(auth)/layout.tsx b/src/app/(auth)/layout.tsx deleted file mode 100644 index f62b2a5..0000000 --- a/src/app/(auth)/layout.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import Link from "next/link"; -import Image from "next/image"; -import { CodeIcon } from "lucide-react"; - -interface AuthLayoutProps { - children: React.ReactNode; -} - -export default async function AuthLayout({ - children -}: AuthLayoutProps) { - return ( -
-
-
- -
- -
- Judge4c - -
-
-
{children}
-
-
-
- Image -
-
- ); -} diff --git a/src/app/(auth)/sign-in/page.tsx b/src/app/(auth)/sign-in/page.tsx index f8c93d3..6293983 100644 --- a/src/app/(auth)/sign-in/page.tsx +++ b/src/app/(auth)/sign-in/page.tsx @@ -1,5 +1,98 @@ -import { SignInForm } from "@/components/sign-in-form"; +import Link from "next/link"; +import Image from "next/image"; +import { CodeIcon } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { providerMap, signIn } from "@/lib/auth"; +import { getTranslations } from "next-intl/server"; +import { FaGithub, FaGoogle } from "react-icons/fa"; -export default function SignInPage() { - return ; +interface ProviderIconProps { + providerId: string; +} + +const ProviderIcon = ({ providerId }: ProviderIconProps) => { + switch (providerId) { + case "github": + return ; + case "google": + return ; + default: + return null; + } +}; + +interface SignInPageProps { + searchParams: Promise<{ + callbackUrl: string | undefined; + }>; +} + +export default async function SignInPage({ searchParams }: SignInPageProps) { + const { callbackUrl } = await searchParams; + const t = await getTranslations("SignInForm"); + + return ( +
+
+
+ +
+ +
+ Judge4c + +
+
+
+
+
+

{t("title")}

+

+ {t("description")} +

+
+
+ + {t("or")} + +
+ {Object.values(providerMap).map((provider) => { + return ( +
{ + "use server"; + await signIn(provider.id, { + redirectTo: callbackUrl ?? "", + }); + }} + > + +
+ ); + })} +
+
+
+
+
+ Image +
+
+ ); } diff --git a/src/app/(auth)/sign-up/page.tsx b/src/app/(auth)/sign-up/page.tsx deleted file mode 100644 index b71518b..0000000 --- a/src/app/(auth)/sign-up/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { SignUpForm } from "@/components/sign-up-form"; - -export default function SignUpPage() { - return ; -} diff --git a/src/components/credentials-sign-in-form.tsx b/src/components/credentials-sign-in-form.tsx deleted file mode 100644 index 84e370d..0000000 --- a/src/components/credentials-sign-in-form.tsx +++ /dev/null @@ -1,121 +0,0 @@ -"use client"; - -import { z } from "zod"; -import { - Form, - FormField, - FormItem, - FormControl, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { toast } from "sonner"; -import { authSchema } from "@/lib/zod"; -import { useForm } from "react-hook-form"; -import { useTranslations } from "next-intl"; -import { Input } from "@/components/ui/input"; -import { Button } from "@/components/ui/button"; -import { useState, useTransition } from "react"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { signInWithCredentials } from "@/actions/auth"; -import { EyeIcon, EyeOffIcon, MailIcon } from "lucide-react"; -import { useRouter, useSearchParams } from "next/navigation"; - -export type CredentialsSignInFormValues = z.infer; - -export function CredentialsSignInForm() { - const router = useRouter(); - const searchParams = useSearchParams(); - const redirectTo = searchParams.get("redirectTo"); - const t = useTranslations("CredentialsSignInForm"); - const [isPending, startTransition] = useTransition(); - const [isVisible, setIsVisible] = useState(false); - - const form = useForm({ - resolver: zodResolver(authSchema), - defaultValues: { - email: "", - password: "", - }, - }); - - const toggleVisibility = () => setIsVisible((prev) => !prev); - - const onSubmit = (data: CredentialsSignInFormValues) => { - startTransition(async () => { - const result = await signInWithCredentials(data); - - if (result?.error) { - toast.error(t("signInFailed"), { - description: result.error, - }); - } else { - toast.success(t("signInSuccess")); - router.push(redirectTo || "/"); - } - }); - }; - - return ( -
- - ( - - {t("email")} - -
- -
-
-
-
- -
- )} - /> - - ( - - {t("password")} - -
- - -
-
- -
- )} - /> - - - - - ); -} diff --git a/src/components/credentials-sign-up-form.tsx b/src/components/credentials-sign-up-form.tsx deleted file mode 100644 index af9750c..0000000 --- a/src/components/credentials-sign-up-form.tsx +++ /dev/null @@ -1,123 +0,0 @@ -"use client"; - -import { z } from "zod"; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { toast } from "sonner"; -import { authSchema } from "@/lib/zod"; -import { useForm } from "react-hook-form"; -import { useTranslations } from "next-intl"; -import { Input } from "@/components/ui/input"; -import { Button } from "@/components/ui/button"; -import { useState, useTransition } from "react"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { signUpWithCredentials } from "@/actions/auth"; -import { EyeIcon, EyeOffIcon, MailIcon } from "lucide-react"; -import { useRouter, useSearchParams } from "next/navigation"; - -export type CredentialsSignUpFormValues = z.infer; - -export function CredentialsSignUpForm() { - const router = useRouter(); - const searchParams = useSearchParams(); - const redirectTo = searchParams.get("redirectTo"); - const t = useTranslations("CredentialsSignUpForm"); - const [isPending, startTransition] = useTransition(); - const [isVisible, setIsVisible] = useState(false); - - const form = useForm({ - resolver: zodResolver(authSchema), - defaultValues: { - email: "", - password: "", - }, - }); - - const toggleVisibility = () => setIsVisible((prev) => !prev); - - const onSubmit = (data: CredentialsSignUpFormValues) => { - startTransition(async () => { - const result = await signUpWithCredentials(data); - - if (result?.error) { - toast.error(t("signUpFailed"), { - description: result.error, - }); - } else { - toast.success(t("signUpSuccess"), { - description: t("signUpSuccessDescription"), - }); - router.push(`/sign-in?${redirectTo}`) - } - }); - }; - - return ( -
- - ( - - {t("email")} - -
- -
-
-
-
- -
- )} - /> - - ( - - {t("password")} - -
- - -
-
- -
- )} - /> - - - - - ); -} diff --git a/src/components/github-sign-in-form.tsx b/src/components/github-sign-in-form.tsx deleted file mode 100644 index 30d2022..0000000 --- a/src/components/github-sign-in-form.tsx +++ /dev/null @@ -1,27 +0,0 @@ -"use client"; - -import { useTranslations } from "next-intl"; -import { Button } from "@/components/ui/button"; -import { signInWithGithub } from "@/actions/auth"; -import { useSearchParams } from "next/navigation"; - -export function GithubSignInForm() { - const t = useTranslations(); - const searchParams = useSearchParams(); - const redirectTo = searchParams.get("redirectTo"); - const signInAction = signInWithGithub.bind(null, redirectTo || "/"); - - return ( -
- -
- ); -} diff --git a/src/components/sign-in-form.tsx b/src/components/sign-in-form.tsx deleted file mode 100644 index db8213c..0000000 --- a/src/components/sign-in-form.tsx +++ /dev/null @@ -1,44 +0,0 @@ -"use client"; - -import { useTranslations } from "next-intl"; -import { useRouter, useSearchParams } from "next/navigation"; -import { GithubSignInForm } from "@/components/github-sign-in-form"; -import { CredentialsSignInForm } from "@/components/credentials-sign-in-form"; - -export function SignInForm() { - const router = useRouter(); - const searchParams = useSearchParams(); - const t = useTranslations("SignInForm"); - - const handleSignUp = () => { - const params = new URLSearchParams(searchParams.toString()); - router.push(`/sign-up?${params.toString()}`); - }; - - return ( -
-
-

{t("title")}

-

- {t("description")} -

-
- -
- - {t("or")} - -
- -
- {t("noAccount")}{" "} - -
-
- ); -} diff --git a/src/components/sign-up-form.tsx b/src/components/sign-up-form.tsx deleted file mode 100644 index 5a54453..0000000 --- a/src/components/sign-up-form.tsx +++ /dev/null @@ -1,44 +0,0 @@ -"use client"; - -import { useTranslations } from "next-intl"; -import { useRouter, useSearchParams } from "next/navigation"; -import { GithubSignInForm } from "@/components/github-sign-in-form"; -import { CredentialsSignUpForm } from "@/components/credentials-sign-up-form"; - -export function SignUpForm() { - const router = useRouter(); - const searchParams = useSearchParams(); - const t = useTranslations("SignUpForm"); - - const handleSignIn = () => { - const params = new URLSearchParams(searchParams.toString()); - router.push(`/sign-in?${params.toString()}`); - }; - - return ( -
-
-

{t("title")}

-

- {t("description")} -

-
- -
- - {t("or")} - -
- -
- {t("haveAccount")}{" "} - -
-
- ); -} diff --git a/src/lib/zod.ts b/src/lib/zod.ts deleted file mode 100644 index 89246d1..0000000 --- a/src/lib/zod.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { z } from "zod"; - -export const authSchema = z.object({ - email: z - .string() - .nonempty("Email is required") - .email("Invalid email"), - password: z - .string() - .nonempty("Password is required") - .min(8, "Password must be at least 8 characters") - .max(32, "Password must be less than 32 characters"), -});