Skip to content

Commit

Permalink
Merge pull request #167 from epigram5-9/merge/FE-29
Browse files Browse the repository at this point in the history
FE-29 🔀 브랜치 최신화
  • Loading branch information
jangmoonwon authored Aug 2, 2024
2 parents b5a041e + 0ade3af commit e66c659
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 61 deletions.
Binary file added public/horizen.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/components/ui/input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(({ className, type,
<input
type={type}
className={cn(
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
ref={ref}
Expand Down
2 changes: 1 addition & 1 deletion src/components/ui/textarea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextArea
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(({ className, ...props }, ref) => (
<textarea
className={cn(
'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
ref={ref}
Expand Down
84 changes: 46 additions & 38 deletions src/hooks/useRegisterMutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,72 +4,80 @@ import { useMutation } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import { isAxiosError } from 'axios';

const useRegisterMutation = () => {
const useRegisterMutation = (onRegisterError: (field: 'email' | 'nickname') => void) => {
const router = useRouter();

return useMutation({
mutationFn: postSignup,
onSuccess: (data) => {
localStorage.setItem('accessToken', data.accessToken);
localStorage.setItem('refreshToken', data.refreshToken);
router.push('/');
router.push('/auth/SignIn');
toast({
title: '회원가입 성공!',
description: '로그인 후 이용해주세요.',
className: 'bg-illust-green text-white font-semibold',
});
},
onError: (error) => {
if (isAxiosError(error)) {
const { status, data } = error.response || {};

if (!status) return;
if (!isAxiosError(error)) {
return;
}

if (status === 400) {
const errorMessage = data?.message || '잘못된 요청입니다. 입력 값을 확인해주세요.';
const { status, data } = error.response || {};

if (errorMessage.includes('이미 사용중인 이메일')) {
toast({
description: '이미 사용중인 이메일입니다.',
className: 'border-state-error text-state-error font-semibold',
});
return;
}
if (status === 400) {
const errorMessage = data?.message || '잘못된 요청입니다. 입력 값을 확인해주세요.';

if (errorMessage.includes('이미 사용중인 이메일')) {
toast({
description: errorMessage,
className: 'border-state-error text-state-error font-semibold',
description: '이미 사용중인 이메일입니다.',
className: 'bg-state-error text-white font-semibold',
});
onRegisterError('email');
return;
}

if (status === 500) {
const errorMessage = data?.message || '서버 오류가 발생했습니다. 잠시 후 다시 시도해주세요.';
toast({
description: errorMessage,
className: 'bg-state-error text-white font-semibold',
});
return;
}

// NOTE: swagger 문서에서 중복된 닉네임은 500에러와 함께 "Internal Server Error" 메시지로 응답 옴
if (errorMessage.includes('Internal Server Error')) {
toast({
description: '이미 존재하는 닉네임입니다.',
className: 'border-state-error text-state-error font-semibold',
});
return;
}
if (status === 500) {
const errorMessage = data?.message || '서버 오류가 발생했습니다. 잠시 후 다시 시도해주세요.';

// NOTE: swagger 문서에서 중복된 닉네임은 500에러와 함께 "Internal Server Error" 메시지로 응답 옴
if (errorMessage.includes('Internal Server Error')) {
toast({
description: errorMessage,
className: 'border-state-error text-state-error font-semibold',
description: '이미 존재하는 닉네임입니다.',
className: 'bg-state-error text-white font-semibold',
});
onRegisterError('nickname');
return;
}

if (status >= 500) {
toast({
description: '서버 오류가 발생했습니다. 잠시 후 다시 시도해주세요.',
className: 'border-state-error text-state-error font-semibold',
});
return;
}
toast({
description: errorMessage,
className: 'bg-state-error text-white font-semibold',
});
return;
}

// NOTE: status값은 항상 있으며 undefined와 숫자를 비교연산 할 수 없어 Number로 설정
if (Number(status) >= 500) {
toast({
description: '알 수 없는 오류가 발생했습니다. 잠시 후 다시 시도해주세요.',
className: 'border-state-error text-state-error font-semibold',
description: '서버 오류가 발생했습니다. 잠시 후 다시 시도해주세요.',
className: 'bg-state-error text-white font-semibold',
});
return;
}

toast({
description: '알 수 없는 오류가 발생했습니다. 잠시 후 다시 시도해주세요.',
className: 'bg-state-error text-white font-semibold',
});
},
});
};
Expand Down
83 changes: 63 additions & 20 deletions src/pages/auth/SignUp.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useState } from 'react';
import Image from 'next/image';
import Link from 'next/link';
import { zodResolver } from '@hookform/resolvers/zod';
Expand All @@ -9,7 +10,7 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '
import useRegisterMutation from '@/hooks/useRegisterMutation';

export default function SignUp() {
const mutationRegister = useRegisterMutation();
const [focusedField, setFocusedField] = useState<string | null>(null);

const form = useForm<PostSignUpRequestType>({
resolver: zodResolver(PostSignUpRequest),
Expand All @@ -22,6 +23,20 @@ export default function SignUp() {
},
});

const { setFocus, setValue, trigger } = form;

const handleFieldError = (field: 'email' | 'nickname') => {
setFocus(field);
setFocusedField(field);
};

const mutationRegister = useRegisterMutation(handleFieldError);

const trimWhitespace = (fieldName: keyof PostSignUpRequestType, value: string) => {
setValue(fieldName, value.trim(), { shouldValidate: true, shouldDirty: true });
trigger(fieldName);
};

return (
<div className='flex flex-col justify-center items-center bg-background-100 w-full min-h-screen'>
<header className='h-full mb-[50px] md:mb-[60px]'>
Expand All @@ -37,13 +52,20 @@ export default function SignUp() {
name='email'
render={({ field, fieldState }) => (
<FormItem className='flex flex-col w-full lg:max-w-[640px] md:max-w-[384px] space-y-0 md:mb-10 mb-5'>
<FormLabel className={`md:mb-5 mb-4 font-pretendard lg:text-xl md:text-base sm:text-sm ${fieldState.invalid ? 'text-state-error' : 'text-blue-900'}`}>이메일</FormLabel>
<FormLabel className={`md:mb-5 mb-4 font-pretendard lg:text-xl md:text-base sm:text-sm ${fieldState.invalid || focusedField === 'email' ? 'text-state-error' : 'text-blue-900'}`}>
이메일
</FormLabel>
<FormControl>
<Input
{...field}
type='text'
placeholder='이메일'
className={`lg:h-16 h-11 px-4 lg:text-xl md:text-base placeholder-blue-400 rounded-xl bg-blue-200 font-pretendard ${fieldState.invalid ? 'border-2 border-state-error' : ''}`}
{...field}
onBlur={(e) => {
trimWhitespace('email', e.target.value);
}}
className={`lg:h-16 h-11 px-4 lg:text-xl md:text-base placeholder-blue-400 rounded-xl bg-blue-200 font-pretendard ${
fieldState.invalid || focusedField === 'email' ? 'border-2 border-state-error' : 'focus:border-blue-500'
}`}
/>
</FormControl>
<FormMessage className='flex justify-end text-[13px] text-state-error' />
Expand All @@ -58,10 +80,11 @@ export default function SignUp() {
<FormLabel className={`md:mb-5 mb-4 font-pretendard lg:text-xl md:text-base sm:text-sm ${fieldState.invalid ? 'text-state-error' : 'text-blue-900'}`}>비밀번호</FormLabel>
<FormControl>
<Input
{...field}
type='password'
placeholder='비밀번호'
className={`lg:h-16 h-11 px-4 lg:text-xl md:text-base placeholder-blue-400 rounded-xl bg-blue-200 font-pretendard ${fieldState.invalid ? 'border-2 border-state-error' : ''}`}
{...field}
onBlur={(e) => trimWhitespace('password', e.target.value)}
className={`lg:h-16 h-11 px-4 lg:text-xl md:text-base placeholder-blue-400 rounded-xl bg-blue-200 font-pretendard ${fieldState.invalid ? 'border-2 border-state-error' : 'focus:border-blue-500'}`}
/>
</FormControl>
<FormMessage className='flex justify-end text-[13px] text-state-error' />
Expand All @@ -75,10 +98,11 @@ export default function SignUp() {
<FormItem className='flex flex-col w-full lg:max-w-[640px] md:max-w-[384px] space-y-0 md:mb-10 mb-5'>
<FormControl>
<Input
{...field}
type='password'
placeholder='비밀번호 확인'
className={`lg:h-16 h-11 px-4 lg:text-xl md:text-base placeholder-blue-400 rounded-xl bg-blue-200 font-pretendard ${fieldState.invalid ? 'border-2 border-state-error' : ''}`}
{...field}
onBlur={(e) => trimWhitespace('passwordConfirmation', e.target.value)}
className={`lg:h-16 h-11 px-4 lg:text-xl md:text-base placeholder-blue-400 rounded-xl bg-blue-200 font-pretendard ${fieldState.invalid ? 'border-2 border-state-error' : 'focus:border-blue-500'}`}
/>
</FormControl>
<FormMessage className='flex justify-end text-[13px] text-state-error' />
Expand All @@ -90,13 +114,16 @@ export default function SignUp() {
name='nickname'
render={({ field, fieldState }) => (
<FormItem className='flex flex-col w-full lg:max-w-[640px] md:max-w-[384px] md:mb-10 mb-[30px] space-y-0'>
<FormLabel className={`md:mb-5 mb-4 font-pretendard lg:text-xl md:text-base sm:text-sm ${fieldState.invalid ? 'text-state-error' : 'text-blue-900'}`}>닉네임</FormLabel>
<FormLabel className={`md:mb-5 mb-4 font-pretendard lg:text-xl md:text-base sm:text-sm ${fieldState.invalid || focusedField === 'nickname' ? 'text-state-error' : 'text-blue-900'}`}>
닉네임
</FormLabel>
<FormControl>
<Input
{...field}
type='text'
placeholder='닉네임'
className={`lg:h-16 h-11 px-4 lg:text-xl md:text-base placeholder-blue-400 rounded-xl bg-blue-200 font-pretendard ${fieldState.invalid ? 'border-2 border-state-error' : ''}`}
{...field}
onBlur={(e) => trimWhitespace('nickname', e.target.value)}
className={`lg:h-16 h-11 px-4 lg:text-xl md:text-base placeholder-blue-400 rounded-xl bg-blue-200 font-pretendard ${fieldState.invalid || focusedField === 'nickname' ? 'border-2 border-state-error' : 'focus:border-blue-500'}`}
/>
</FormControl>
<FormMessage className='flex justify-end text-[13px] text-state-error' />
Expand All @@ -113,16 +140,32 @@ export default function SignUp() {
</form>
</Form>
</div>
<div className='flex justify-center gap-4'>
<Button type='button' className='md:size-[60px] p-0'>
<Image src='/logo-naver.svg' alt='logo-naver' width={60} height={60} className='md:size-[60px] size-10' />
</Button>
<Button type='button' className='md:size-[60px] p-0'>
<div className='flex flex-col items-center w-full lg:gap-10 gap-6'>
<div className='flex justify-center items-center lg:gap-6 gap-[14px] w-full lg:max-w-[640px] md:max-w-[384px] lg:px-0 md:px-0 px-6'>
<div className='flex-grow'>
<Image src='/horizen.png' alt='horizen' width={180} height={0} className='w-full h-[2px]' />
</div>
<h3 className='lg:text-xl text-xs font-pretendard text-blue-400 whitespace-nowrap'>SNS 계정으로 로그인하기</h3>
<div className='flex-grow'>
<Image src='/horizen.png' alt='horizen' width={180} height={0} className='w-full h-[2px]' />
</div>
</div>
<div className='flex justify-center gap-4'>
<Link
href={`https://nid.naver.com/oauth2.0/authorize?response_type=code&client_id=${process.env.NEXT_PUBLIC_NAVER_CLIENT_ID}&state=${'test'}&redirect_uri=${process.env.NEXT_PUBLIC_NAVER_REDIRECT_URI}`}
>
<Image src='/logo-naver.svg' alt='logo-naver' width={60} height={60} className='md:size-[60px] size-10' />
</Link>
{/* // FIXME: 구글 간편 로그인 리다이렉트시 500에러가 발생하는 부분으로 주석 처리하였음 */}
{/* <Link
href={`https://accounts.google.com/o/oauth2/v2/auth?client_id=${process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID}&redirect_uri=${process.env.NEXT_PUBLIC_GOOGLE_REDIRECT_URI}&response_type=code&scope=email%20profile`}
> */}
<Image src='/logo-google.svg' alt='logo-google' width={60} height={60} className='md:size-[60px] size-10' />
</Button>
<Button type='button' className='md:size-[60px] p-0'>
<Image src='/logo-kakao.svg' alt='logo-kakao' width={60} height={60} className='md:size-[60px] size-10' />
</Button>
{/* </Link> */}
<Link href={`https://kauth.kakao.com/oauth/authorize?client_id=${process.env.NEXT_PUBLIC_KAKAO_CLIENT_ID}&redirect_uri=${process.env.NEXT_PUBLIC_REDIRECT_URI}&response_type=code`}>
<Image src='/logo-kakao.svg' alt='logo-kakao' width={60} height={60} className='md:size-[60px] size-10' />
</Link>
</div>
</div>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion src/schema/auth.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as z from 'zod';

const PWD_VALIDATION_REGEX = /^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^&*])[a-zA-Z0-9!@#$%^&*]{8,}$/;
const PWD_VALIDATION_REGEX = /^([a-z]|[A-Z]|[0-9]|[!@#$%^&])+$/;

// NOTE: 회원가입 스키마
export const PostSignUpRequest = z
Expand Down

0 comments on commit e66c659

Please sign in to comment.