diff --git a/src/apis/users/index.ts b/src/apis/users/index.ts new file mode 100644 index 0000000..012f68f --- /dev/null +++ b/src/apis/users/index.ts @@ -0,0 +1,18 @@ +import axiosHelper from '@/utils/network/axiosHelper'; +import { SignupFormData } from './types'; +import { isAxiosError } from 'axios'; +import { SignupResponse } from './types'; +import { isError } from 'es-toolkit/compat'; + +export const signup = async (signupFormData: SignupFormData): SignupResponse => { + try { + const response = await axiosHelper.post('/users', signupFormData); + return response.data; + } catch (error) { + if (isAxiosError(error)) return error.response?.data; + + return { + message: isError(error) ? error.message : String(error), + }; + } +}; diff --git a/src/apis/users/types.ts b/src/apis/users/types.ts new file mode 100644 index 0000000..8b52f9e --- /dev/null +++ b/src/apis/users/types.ts @@ -0,0 +1,36 @@ +import { z } from 'zod'; +import { SINGUP_FORM_VALID_LENGTH, SIGNUP_FORM_ERROR_MESSAGE } from '@/constants/auth'; + +export const signupSchema = z + .object({ + email: z.string().min(SINGUP_FORM_VALID_LENGTH.EMAIL.MIN, SIGNUP_FORM_ERROR_MESSAGE.EMAIL.MIN).email(SIGNUP_FORM_ERROR_MESSAGE.EMAIL.NOT_FORM), + nickname: z.string().min(SINGUP_FORM_VALID_LENGTH.NICKNAME.MIN, SIGNUP_FORM_ERROR_MESSAGE.NICKNAME.MIN).max(SINGUP_FORM_VALID_LENGTH.NICKNAME.MAX, SIGNUP_FORM_ERROR_MESSAGE.NICKNAME.MAX), + password: z.string().min(SINGUP_FORM_VALID_LENGTH.PASSWORD.MIN, SIGNUP_FORM_ERROR_MESSAGE.PASSWORD.MIN), + passwordConfirm: z.string(), + terms: z.boolean(), + }) + .refine((check) => check.password === check.passwordConfirm, { + message: SIGNUP_FORM_ERROR_MESSAGE.PASSWORD_CONFIRM.NOT_MATCH, + path: ['passwordConfirm'], + }) + .refine((check) => check.terms, { + message: SIGNUP_FORM_ERROR_MESSAGE.TERMS.NOT_TOS, + path: ['terms'], + }); + +export type SignupFormData = z.infer; + +export interface SignupSuccessResponse { + id: number; + email: string; + nickname: string; + profileImageUrl: string | null; + createdAt: string | Date; + updatedAt: string | Date; +} + +export interface SignupFailResponse { + message: string; +} + +export type SignupResponse = Promise; diff --git a/src/app/(auth)/_components/Header.tsx b/src/app/(auth)/_components/Header.tsx new file mode 100644 index 0000000..88a0828 --- /dev/null +++ b/src/app/(auth)/_components/Header.tsx @@ -0,0 +1,17 @@ +import Image from 'next/image'; +import Link from 'next/link'; +import LogoCi from '@/assets/images/logo_ci.png'; +import LogoBi from '@/assets/images/logo_bi.png'; +import { ReactNode } from 'react'; + +export default function Header({ children }: { children: ReactNode }) { + return ( +
+ + 로고 CI 이미지 + 로고 BI 이미지 + +

{children}

+
+ ); +} diff --git a/src/app/(auth)/layout.tsx b/src/app/(auth)/layout.tsx index 70922cb..a981957 100644 --- a/src/app/(auth)/layout.tsx +++ b/src/app/(auth)/layout.tsx @@ -1,17 +1,7 @@ -function Header() { - //TODO: Header 레이아웃 작업 - return
; -} - export default function Layout({ children, }: Readonly<{ children: React.ReactNode; }>) { - return ( - <> -
- {children} - - ); + return
{children}
; } diff --git a/src/app/(auth)/login/_components/LoginForm.tsx b/src/app/(auth)/login/_components/LoginForm.tsx index efa08cb..144f548 100644 --- a/src/app/(auth)/login/_components/LoginForm.tsx +++ b/src/app/(auth)/login/_components/LoginForm.tsx @@ -19,7 +19,7 @@ export default function LoginForm() { const { register, handleSubmit, - formState: { errors, isValid }, + formState: { errors, isValid, isSubmitting }, } = useForm({ resolver: zodResolver(loginSchema), mode: 'onBlur', @@ -37,7 +37,7 @@ export default function LoginForm() {
- + ); } diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx index b3db7d7..5088777 100644 --- a/src/app/(auth)/login/page.tsx +++ b/src/app/(auth)/login/page.tsx @@ -1,9 +1,19 @@ +import Link from 'next/link'; import LoginForm from './_components/LoginForm'; -//TODO: 로그인 페이지 작업 +import Header from '../_components/Header'; export default function Login() { return ( -
- -
+ <> +
오늘도 만나서 반가워요!
+
+ + +
+ ); } diff --git a/src/app/(auth)/signup/_components/SignupForm.tsx b/src/app/(auth)/signup/_components/SignupForm.tsx index d39526d..2c9817d 100644 --- a/src/app/(auth)/signup/_components/SignupForm.tsx +++ b/src/app/(auth)/signup/_components/SignupForm.tsx @@ -1,39 +1,20 @@ 'use client'; -import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; import Field from '@/components/auth/Field'; import SubmitButton from '@/components/auth/SubmitButton'; import Checkbox from './Checkbox'; -import { SIGNUP_FORM_PLACEHOLDER, SINGUP_FORM_VALID_LENGTH, SIGNUP_FORM_ERROR_MESSAGE } from '@/constants/auth'; - -//TODO: API 함수 구현 후 스키마와 타입 정의 옮길 예정 -const signupSchema = z - .object({ - email: z.string().min(SINGUP_FORM_VALID_LENGTH.EMAIL.MIN, SIGNUP_FORM_ERROR_MESSAGE.EMAIL.MIN).email(SIGNUP_FORM_ERROR_MESSAGE.EMAIL.NOT_FORM), - nickname: z.string().min(SINGUP_FORM_VALID_LENGTH.NICKNAME.MIN, SIGNUP_FORM_ERROR_MESSAGE.NICKNAME.MIN).max(SINGUP_FORM_VALID_LENGTH.NICKNAME.MAX, SIGNUP_FORM_ERROR_MESSAGE.NICKNAME.MAX), - password: z.string().min(SINGUP_FORM_VALID_LENGTH.PASSWORD.MIN, SIGNUP_FORM_ERROR_MESSAGE.PASSWORD.MIN), - passwordConfirm: z.string(), - terms: z.boolean(), - }) - .refine((check) => check.password === check.passwordConfirm, { - message: SIGNUP_FORM_ERROR_MESSAGE.PASSWORD_CONFIRM.NOT_MATCH, - path: ['passwordConfirm'], - }) - .refine((check) => check.terms, { - message: SIGNUP_FORM_ERROR_MESSAGE.TERMS.NOT_TOS, - path: ['terms'], - }); - -type SignupFormData = z.infer; +import { SIGNUP_FORM_PLACEHOLDER } from '@/constants/auth'; +import { signupSchema, SignupFormData } from '@/apis/users/types'; +import { signup } from '@/apis/users'; export default function SignupForm() { const { register, trigger, handleSubmit, - formState: { errors, isValid }, + formState: { errors, isValid, isSubmitting }, } = useForm({ resolver: zodResolver(signupSchema), mode: 'onBlur', @@ -45,9 +26,14 @@ export default function SignupForm() { terms: false, }, }); - const onSubmit = (signupFormData: SignupFormData) => { - // TODO : 디버깅 용으로 남겼습니다. API 함수 구현이 완료되면 로직 수정 예정입니다. - console.log(signupFormData); + const onSubmit = async (signupFormData: SignupFormData) => { + const response = await signup(signupFormData); + // TODO: 디버깅 용으로 alert로 구현했습니다. 모달 기능 구현 후 로직 수정 예정입니다. + if ('message' in response) { + alert(response.message); + } else { + alert('가입이 완료되었습니다!'); + } }; return ( @@ -70,7 +56,7 @@ export default function SignupForm() { })} errorMessage={errors.terms?.message} /> - + ); } diff --git a/src/app/(auth)/signup/page.tsx b/src/app/(auth)/signup/page.tsx index 16aa449..89c536f 100644 --- a/src/app/(auth)/signup/page.tsx +++ b/src/app/(auth)/signup/page.tsx @@ -1,9 +1,19 @@ +import Link from 'next/link'; import SignupForm from './_components/SignupForm'; -//TODO: 회원가입 페이지 작업 +import Header from '../_components/Header'; export default function Signup() { return ( -
- -
+ <> +
첫 방문을 환영합니다!
+
+ + +
+ ); } diff --git a/src/components/auth/SubmitButton.tsx b/src/components/auth/SubmitButton.tsx index 5c38f54..a838db8 100644 --- a/src/components/auth/SubmitButton.tsx +++ b/src/components/auth/SubmitButton.tsx @@ -1,12 +1,26 @@ interface SubmitButtonProps { isValid: boolean; text: string; + isSubmitting: boolean; } -export default function SubmitButton({ isValid, text }: SubmitButtonProps) { +function BouncingLoader() { return ( - ); } diff --git a/src/utils/network/axiosHelper.ts b/src/utils/network/axiosHelper.ts new file mode 100644 index 0000000..bdf7d85 --- /dev/null +++ b/src/utils/network/axiosHelper.ts @@ -0,0 +1,7 @@ +import axios from 'axios'; + +const axiosHelper = axios.create({ + baseURL: process.env.NEXT_PUBLIC_API_URL, +}); + +export default axiosHelper;