diff --git a/src/app/(before-login)/(without-navbar)/layout.tsx b/src/app/(before-login)/(without-navbar)/layout.tsx index 42e417d..c1beb79 100644 --- a/src/app/(before-login)/(without-navbar)/layout.tsx +++ b/src/app/(before-login)/(without-navbar)/layout.tsx @@ -9,6 +9,8 @@ type AuthLayoutProps = { buttonText: string; linkText: string; linkPath: string; + isLoading?: boolean; + isFormValid?: boolean; }; export const AuthLayout: React.FC = ({ @@ -16,7 +18,10 @@ export const AuthLayout: React.FC = ({ buttonText, linkText, linkPath, + isLoading = false, + isFormValid = false, }) => { + const isButtonDisabled = isLoading || !isFormValid; return (
@@ -41,8 +46,9 @@ export const AuthLayout: React.FC = ({ className="w-full" form="auth-form" type="submit" + disabled={isButtonDisabled} > - {buttonText} + {isLoading ? "처리 중 입니다." : buttonText} diff --git a/src/app/(before-login)/(without-navbar)/login/page.tsx b/src/app/(before-login)/(without-navbar)/login/page.tsx index ca5c699..57565b0 100644 --- a/src/app/(before-login)/(without-navbar)/login/page.tsx +++ b/src/app/(before-login)/(without-navbar)/login/page.tsx @@ -1,28 +1,59 @@ "use client"; -import Input from "@/components/common/input/Input"; -import { AuthLayout } from "@/app/(before-login)/(without-navbar)/layout"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; +import Input from "@/components/common/input/Input"; +import { AuthLayout } from "@/app/(before-login)/(without-navbar)/layout"; import { loginSchema, LoginFormData } from "@/lib/utils/validationSchema"; +import { useAlertStore } from "@/lib/store/useAlertStore"; +import { fetchLogin } from "@/lib/apis/authApi"; +import { useState } from "react"; +import { useRouter } from "next/navigation"; export default function Page() { + const { openAlert } = useAlertStore(); + const [isLoading, setIsLoading] = useState(false); + const router = useRouter(); + const { register, handleSubmit, - formState: { errors }, + formState: { errors, isValid }, } = useForm({ resolver: zodResolver(loginSchema), mode: "onBlur", }); - const onSubmit = (data: LoginFormData) => { - console.log("로그인 데이터:", data); + const onSubmit = async (data: LoginFormData) => { + setIsLoading(true); + try { + const response = await fetchLogin(data); + setIsLoading(false); + localStorage.setItem("accessToken", response.accessToken); + openAlert("loginSuccess"); + router.push("/mydashboard"); + } catch (error: unknown) { + setIsLoading(false); + if (error instanceof Error) { + const errorInfo: { status: number; message: string } = JSON.parse( + error.message + ); + if (errorInfo.status === 400) { + openAlert("wrongPassword"); + } + if (errorInfo.status === 404) { + openAlert("userNotFound"); + } + } + } }; + return (
diff --git a/src/app/(before-login)/(without-navbar)/signup/page.tsx b/src/app/(before-login)/(without-navbar)/signup/page.tsx index b3faa3c..2c33439 100644 --- a/src/app/(before-login)/(without-navbar)/signup/page.tsx +++ b/src/app/(before-login)/(without-navbar)/signup/page.tsx @@ -4,12 +4,20 @@ import { zodResolver } from "@hookform/resolvers/zod"; import Input from "@/components/common/input/Input"; import { AuthLayout } from "@/app/(before-login)/(without-navbar)/layout"; import { signupSchema, SignupFormData } from "@/lib/utils/validationSchema"; +import { useAlertStore } from "@/lib/store/useAlertStore"; +import { fetchSignup } from "@/lib/apis/authApi"; +import { useState } from "react"; +import { useRouter } from "next/navigation"; export default function Page() { + const { openAlert } = useAlertStore(); + const [isLoading, setIsLoading] = useState(false); + const router = useRouter(); + const { register, handleSubmit, - formState: { errors }, + formState: { errors, isValid }, } = useForm({ resolver: zodResolver(signupSchema), mode: "onBlur", @@ -17,9 +25,24 @@ export default function Page() { terms: false, }, }); - - const onSubmit = (data: SignupFormData) => { - console.log("회원가입 데이터:", data); + const onSubmit = async (data: SignupFormData) => { + setIsLoading(true); + try { + await fetchSignup(data); + setIsLoading(false); + openAlert("signupSuccess"); + router.push("/login"); + } catch (error: unknown) { + setIsLoading(false); + if (error instanceof Error) { + const errorInfo: { status: number; message: string } = JSON.parse( + error.message + ); + if (errorInfo.status === 409) { + openAlert("emailDuplicated"); + } + } + } }; return ( @@ -27,12 +50,10 @@ export default function Page() { buttonText="가입하기" linkText="이미 회원이신가요?" linkPath="/login" + isLoading={isLoading} + isFormValid={isValid} > - +
)} + {currentAlert === "loginSuccess" && ( + router.push(ROUTE.MYDASHBOARD)} /> + )} + {currentAlert === "userNotFound" && } + {currentAlert === "wrongPassword" && } ); } diff --git a/src/components/common/alert/alertData.ts b/src/components/common/alert/alertData.ts index f382851..804a2b5 100644 --- a/src/components/common/alert/alertData.ts +++ b/src/components/common/alert/alertData.ts @@ -3,4 +3,7 @@ export const alertMessages: Record = { emailDuplicated: "이미 사용 중인 이메일입니다.", signupSuccess: "가입이 완료되었습니다!", deleteColumn: "컬럼의 모든 카드가 삭제됩니다.", + loginSuccess: "로그인 되었습니다.", + userNotFound: "존재하지 않는 유저입니다.", + wrongPassword: "현재 비밀번호가 틀렸습니다.", }; diff --git a/src/lib/apis/authApi.ts b/src/lib/apis/authApi.ts new file mode 100644 index 0000000..ec5be71 --- /dev/null +++ b/src/lib/apis/authApi.ts @@ -0,0 +1,61 @@ +import { LoginFormData, SignupFormData } from "@/lib/utils/validationSchema"; +import { BASE_URL } from "@/lib/constants/urls"; + +export async function fetchLogin(data: LoginFormData) { + const requestBody = { + email: data.email, + password: data.password, + }; + + const response = await fetch(`${BASE_URL}/auth/login`, { + method: "POST", + headers: { + "Content-Type": "application/json", + accept: "application/json", + }, + body: JSON.stringify(requestBody), + cache: "no-store", + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error( + JSON.stringify({ + status: response.status, + message: errorData.message || "로그인에 실패했습니다.", + }) + ); + } + + return response.json(); +} + +export async function fetchSignup(data: SignupFormData) { + const requestBody = { + email: data.email, + password: data.password, + nickname: data.nickname, + }; + + const response = await fetch(`${BASE_URL}/users`, { + method: "POST", + headers: { + "Content-Type": "application/json", + accept: "application/json", + }, + body: JSON.stringify(requestBody), + cache: "no-store", + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error( + JSON.stringify({ + status: response.status, + message: errorData.message || "회원가입에 실패했습니다.", + }) + ); + } + + return response.json(); +} diff --git a/src/lib/store/useAlertStore.ts b/src/lib/store/useAlertStore.ts index 8440591..3a6bb39 100644 --- a/src/lib/store/useAlertStore.ts +++ b/src/lib/store/useAlertStore.ts @@ -5,6 +5,9 @@ export type AlertKey = | "emailDuplicated" | "signupSuccess" | "deleteColumn" + | "loginSuccess" + | "userNotFound" + | "wrongPassword" | null; type AlertState = { diff --git a/src/lib/utils/validationSchema.ts b/src/lib/utils/validationSchema.ts index 7f2488d..c93ee90 100644 --- a/src/lib/utils/validationSchema.ts +++ b/src/lib/utils/validationSchema.ts @@ -3,11 +3,11 @@ import { z } from "zod"; export const loginSchema = z.object({ email: z .string() - .nonempty("이메일을 입력해주세요.") + .min(1, "이메일을 입력해주세요.") .email("이메일 형식으로 작성해 주세요."), password: z .string() - .nonempty("비밀번호를 입력해주세요.") + .min(1, "비밀번호를 입력해주세요.") .min(8, "8자 이상 작성해 주세요."), }); @@ -15,17 +15,17 @@ export const signupSchema = z .object({ email: z .string() - .nonempty("이메일을 입력해주세요.") + .min(1, "이메일을 입력해주세요.") .email("이메일 형식으로 작성해 주세요."), nickname: z .string() - .nonempty("닉네임을 입력해주세요.") + .min(1, "닉네임을 입력해주세요.") .max(10, "열 자 이하로 작성해주세요."), password: z .string() - .nonempty("비밀번호를 입력해주세요.") + .min(1, "비밀번호를 입력해주세요.") .min(8, "8자 이상 입력해주세요."), - confirmPassword: z.string().nonempty("비밀번호 확인을 입력해주세요."), + confirmPassword: z.string().min(1, "비밀번호 확인을 입력해주세요."), terms: z .boolean() .refine((val) => val === true, "이용약관에 동의해 주세요."), @@ -34,5 +34,6 @@ export const signupSchema = z message: "비밀번호가 일치하지 않습니다.", path: ["confirmPassword"], }); + export type LoginFormData = z.infer; export type SignupFormData = z.infer;