Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
12 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added public/assets/google_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/naver_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/constants/message-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ export const AUTH_MESSAGE = {
EMAIL_INVALID: '์˜ฌ๋ฐ”๋ฅธ ์ด๋ฉ”์ผ ํ˜•์‹์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.',
PASSWORD_LENGTH: '๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” 6์ž ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.',
PASSWORD_SPECIAL_CHAR: '๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ์ตœ์†Œ ํ•˜๋‚˜์˜ ํŠน์ˆ˜ ๋ฌธ์ž๋ฅผ ํฌํ•จํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.',
EMAIL_EMPTY_FIELD: '์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.',
PASSWORD_EMPTY_FIELD: '๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.'
},
RESULT: {
SIGN_UP_SUCCESS: 'ํšŒ์› ๊ฐ€์ž…์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค.',
SIGN_UP_FAILED: 'ํšŒ์› ๊ฐ€์ž…์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.',
SIGN_UP_EXIST_ERROR: '์ด๋ฏธ ์กด์žฌํ•˜๋Š” ์ด๋ฉ”์ผ์ž…๋‹ˆ๋‹ค.',
SIGN_UP_EMPTY_FIELD: '๋ชจ๋“  ๊ฐ’์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.',
SIGN_IN_SUCCESS: '๋กœ๊ทธ์ธ์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค.',
SIGN_IN_FAILED: '์ด๋ฉ”์ผ ํ˜น์€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”.'
},
};
13 changes: 6 additions & 7 deletions src/features/auth/auth-input.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { UseFormRegister } from 'react-hook-form';
import { FormData } from './sign-up/data/schema';
import { Path, UseFormRegister } from 'react-hook-form';

type Props = {
type Props<T> = {
label: string;
id: keyof FormData;
register: UseFormRegister<FormData>;
id: Path<T>;
register: UseFormRegister<T>;
error?: { message?: string };
type: string;
};

const AuthInput = ({ label, id, register, error, type }: Props) => (
const AuthInput = <T,>({ label, id, register, error, type }: Props<T>) => (
<div className='flex w-full flex-col px-3 py-2'>
<label>{label}</label>
<input {...register(id)} type={type} className='border-b border-gray-300' />
<input {...register(id)} type={type} className='border-b border-gray-300 focus:outline-0' />
<div className='h-6 text-sm'>{error && <p className='text-primary'>{error.message}</p>}</div>
</div>
);
Expand Down
11 changes: 7 additions & 4 deletions src/features/auth/sign-up/api/client-services.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { ROUTE_HANDLER_PATH } from '@/constants/path-constant';

type SignUpProps = {
type Props = {
name: string;
email: string;
password: string;
};

export const postSignUp = async (sign_up_data: SignUpProps) => {
const { AUTH } = ROUTE_HANDLER_PATH;
const { SIGN_UP } = AUTH;

export const postSignUp = async (signUpData: Props) => {
try {
const res = await fetch(ROUTE_HANDLER_PATH.AUTH.SIGN_UP, {
const res = await fetch(SIGN_UP, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(sign_up_data),
body: JSON.stringify(signUpData),
});

const data = await res.json();
Expand Down
17 changes: 10 additions & 7 deletions src/features/auth/sign-up/data/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,22 @@ const NAME_VALIDATION_MAX = 8;
const NAME_VALIDATION_REGEX = /^[a-zA-Z0-9๊ฐ€-ํžฃ]+$/;
const PASSWORD_VALIDATION_MIN = 6;

const { VALIDATION } = AUTH_MESSAGE;
const { NAME_LENGTH, NAME_SPECIAL_CHAR, EMAIL_INVALID, PASSWORD_LENGTH, PASSWORD_SPECIAL_CHAR } = VALIDATION;

export const schema = z.object({
name: z
.string()
.trim()
.min(NAME_VALIDATION_MIN, AUTH_MESSAGE.VALIDATION.NAME_LENGTH)
.max(NAME_VALIDATION_MAX, AUTH_MESSAGE.VALIDATION.NAME_LENGTH)
.regex(NAME_VALIDATION_REGEX, AUTH_MESSAGE.VALIDATION.NAME_SPECIAL_CHAR),
email: z.string().trim().email(AUTH_MESSAGE.VALIDATION.EMAIL_INVALID),
.min(NAME_VALIDATION_MIN, NAME_LENGTH)
.max(NAME_VALIDATION_MAX, NAME_LENGTH)
.regex(NAME_VALIDATION_REGEX, NAME_SPECIAL_CHAR),
email: z.string().trim().email(EMAIL_INVALID),
password: z
.string()
.trim()
.min(PASSWORD_VALIDATION_MIN, AUTH_MESSAGE.VALIDATION.PASSWORD_LENGTH)
.regex(/[^a-zA-Z0-9]/, AUTH_MESSAGE.VALIDATION.PASSWORD_SPECIAL_CHAR),
.min(PASSWORD_VALIDATION_MIN, PASSWORD_LENGTH)
.regex(/[^a-zA-Z0-9]/, PASSWORD_SPECIAL_CHAR),
});

export type FormData = z.infer<typeof schema>;
export type SignUpFormData = z.infer<typeof schema>;
14 changes: 7 additions & 7 deletions src/features/auth/sign-up/sign-up-auth-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,28 @@

import { PATH } from '@/constants/path-constant';
import { useRouter } from 'next/navigation';
import { postSignUp } from './api/client-services';
import { postSignUp } from '@/features/auth/sign-up/api/client-services';
import { AUTH_MESSAGE } from '@/constants/message-constants';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import AuthInput from '@/features/auth/auth-input';
import { FormData, schema } from './data/schema';
import { SignUpFormData, schema } from '@/features/auth/sign-up/data/schema';

const SignUpAuthForm = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormData>({
} = useForm<SignUpFormData>({
resolver: zodResolver(schema),
mode: 'onBlur',
defaultValues: { name: '', email: '', password: '' } as FormData,
defaultValues: { name: '', email: '', password: '' } as SignUpFormData,
});
const router = useRouter();

const onSubmit = async (data: FormData) => {
const onSubmit = async (data: SignUpFormData) => {
try {
await postSignUp(data as Required<FormData>);
await postSignUp(data as Required<SignUpFormData>);
router.push(PATH.AUTH.SIGN_IN);
alert(AUTH_MESSAGE.RESULT.SIGN_UP_SUCCESS);
} catch (error) {
Expand All @@ -38,7 +38,7 @@ const SignUpAuthForm = () => {
</p>
<p className='mb-4 text-center font-extralight'>์šฐ๋ฆฌ ๊ฐ™์ด ์ทจ์—…์„ ํ–ฅํ•œ ์—ฌ์ •์„ ๋– ๋‚˜๋ณผ๊นŒ์š”?</p>
<p className='mb-10 text-center font-extralight text-black/30'>์›ํ• ํ•œ ์„œ๋น„์Šค ์ด์šฉ์„ ์œ„ํ•ด ํšŒ์›๊ฐ€์ž… ํ•ด์ฃผ์„ธ์š”.</p>
<form onSubmit={handleSubmit(onSubmit)}>
<form onSubmit={handleSubmit(onSubmit)} noValidate>
<AuthInput label='NAME' id='name' register={register} error={errors.name} type='text' />
<AuthInput label='EMAIL' id='email' register={register} error={errors.email} type='email' />
<AuthInput label='PASSWORD' id='password' register={register} error={errors.password} type='password' />
Expand Down
14 changes: 14 additions & 0 deletions src/features/auth/sing-in/data/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { AUTH_MESSAGE } from '@/constants/message-constants';
import { z } from 'zod';

const PASSWORD_VALIDATION_MIN = 1;

const { VALIDATION } = AUTH_MESSAGE;
const { EMAIL_EMPTY_FIELD, PASSWORD_EMPTY_FIELD } = VALIDATION;

export const schema = z.object({
email: z.string().trim().email(EMAIL_EMPTY_FIELD),
password: z.string().trim().min(PASSWORD_VALIDATION_MIN, PASSWORD_EMPTY_FIELD),
});

export type SignInFormData = z.infer<typeof schema>;
91 changes: 46 additions & 45 deletions src/features/auth/sing-in/sign-in-auth-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,40 @@ import { PATH } from '@/constants/path-constant';
import { signIn } from 'next-auth/react';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import AuthInput from '@/features/auth/auth-input';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { schema, SignInFormData } from '@/features/auth/sing-in/data/schema';
import Image from 'next/image';
import { AUTH_MESSAGE } from '@/constants/message-constants';

const callback_url = `${process.env.NEXT_PUBLIC_BASE_URL}/${PATH.ON_BOARDING}`;

const { RESULT } = AUTH_MESSAGE;
const { SIGN_IN_SUCCESS, SIGN_IN_FAILED } = RESULT;

const SignInAuthForm = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<SignInFormData>({
resolver: zodResolver(schema),
mode: 'onBlur',
defaultValues: { email: '', password: '' } as SignInFormData,
});
const router = useRouter();

const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();

const formData = new FormData(e.target as HTMLFormElement);

const sign_in_data = {
email: String(formData.get('email')),
password: String(formData.get('password')),
};
const onSubmit = async (data: SignInFormData) => {
try {
const res = await signIn('credentials', {
...sign_in_data,
...data,
redirect: false,
});
if (!res.ok) {
throw new Error('๋กœ๊ทธ์ธ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.');
throw new Error(SIGN_IN_FAILED);
} else {
alert('๋กœ๊ทธ์ธ์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค.');
alert(SIGN_IN_SUCCESS);
router.replace(PATH.ON_BOARDING);
}
} catch (error) {
Expand All @@ -37,55 +47,46 @@ const SignInAuthForm = () => {

return (
<div className='mx-auto w-full max-w-md rounded-lg bg-white p-6 shadow-md'>
<h2 className='mb-6 text-center text-2xl font-bold'>๋กœ๊ทธ์ธ</h2>
<form className='space-y-4' onSubmit={handleSubmit}>
<div>
<label htmlFor='email' className='mb-1 block text-sm font-medium text-gray-700'>
์ด๋ฉ”์ผ
</label>
<input
id='email'
name='email'
type='email'
className='w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500'
required
/>
</div>
<div>
<label htmlFor='password' className='mb-1 block text-sm font-medium text-gray-700'>
๋น„๋ฐ€๋ฒˆํ˜ธ
</label>
<input
id='password'
name='password'
type='password'
className='w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500'
required
/>
</div>
<p className='mb-1 text-center text-2xl font-light'>
๋งŒ๋‚˜์„œ ๋ฐ˜๊ฐ€์›Œ์š”.<span className='font-normal'>๋ณ‘์•„๋ฆฌ</span>์”จ!
</p>
<p className='mb-4 text-center font-extralight'>์šฐ๋ฆฌ ๊ฐ™์ด ์ทจ์—…์„ ํ–ฅํ•œ ์—ฌ์ •์„ ๋– ๋‚˜๋ณผ๊นŒ์š”?</p>
<p className='mb-10 text-center font-extralight text-black/30'>์›ํ• ํ•œ ์„œ๋น„์Šค ์ด์šฉ์„ ์œ„ํ•ด ๋กœ๊ทธ์ธ ํ•ด์ฃผ์„ธ์š”.</p>
<form onSubmit={handleSubmit(onSubmit)} noValidate>
<AuthInput label='EMAIL' id='email' register={register} error={errors.email} type='email' />
<AuthInput label='PASSWORD' id='password' register={register} error={errors.password} type='password' />
<button
type='submit'
className='w-full rounded-md border border-transparent bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:bg-blue-300'
className='bg-blue-white mt-2 w-full rounded-md border border-gray-400 px-4 py-2 text-sm font-medium text-black shadow-sm'
>
๋กœ๊ทธ์ธ
</button>
</form>
<div className='mt-4 flex flex-col gap-2 text-center'>
<Link href={PATH.AUTH.SIGN_UP}>๊ณ„์ •์ด ์—†์œผ์‹ ๊ฐ€์š”?</Link>
<div className='mt-2 flex flex-col gap-2 text-center'>
<>
<button
onClick={() => signIn('google', { callbackUrl: callback_url })}
className='w-full rounded-md border border-transparent bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:bg-blue-300'
className='flex w-full items-center justify-center gap-2 rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm'
>
๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ
<Image src='/assets/google_icon.png' alt='๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ' width={14} height={14} />
๊ตฌ๊ธ€ ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธ
</button>
<button
onClick={() => signIn('naver', { callbackUrl: callback_url })}
className='w-full rounded-md border border-transparent bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:bg-blue-300'
className='flex w-full items-center justify-center gap-2 rounded-md bg-[#03C75A] px-4 py-2 text-sm font-bold text-white shadow-sm'
>
๋„ค์ด๋ฒ„
<Image src='/assets/naver_icon.png' alt='๋„ค์ด๋ฒ„ ๋กœ๊ทธ์ธ' width={24} height={24} />
๋„ค์ด๋ฒ„ ์•„์ด๋””๋กœ ๋กœ๊ทธ์ธ
</button>
</>
<div className='my-4 flex w-full items-center'>
<div className='flex-1 border-t'></div>
<span className='px-3 text-sm font-extralight text-gray-500'>OR</span>
<div className='flex-1 border-t'></div>
</div>
<Link href={PATH.AUTH.SIGN_UP} className='font-extralight'>
30์ดˆ๋งŒ์— ํšŒ์›๊ฐ€์ž…ํ•˜๊ธฐ
</Link>
</div>
</div>
);
Expand Down