-
Notifications
You must be signed in to change notification settings - Fork 3
Feat/118/auth api #137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat/118/auth api #137
Changes from all commits
082b0b4
c63c880
f710f78
51f3f04
808053b
8dd04ff
d242fa8
f29d1c2
2fa147d
548464a
f761eb1
c581727
32a3ea8
b58a460
acb0d3b
35898d7
1ca2b4c
986cc57
8205092
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import { fetchApi } from '@/src/utils/api'; | ||
|
|
||
| export function logout(): Promise<void> { | ||
| return fetchApi('/auths/logout', { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| }); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import { fetchApi } from '@/src/utils/api'; | ||
|
|
||
| export async function reissue(): Promise<{ token: string | null }> { | ||
| return fetchApi<{ headers: Headers }>( | ||
| '/auths/reissue', | ||
| { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| }, | ||
| 5000, | ||
| true, | ||
| ).then((response) => { | ||
| const token = response.headers.get('Authorization'); | ||
| return { token }; | ||
| }); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import { useMutation } from '@tanstack/react-query'; | ||
| import { logout } from '@/src/_apis/auth/logout-apis'; | ||
| import { ApiError } from '@/src/utils/api'; | ||
|
|
||
| export function usePostLogoutQuery() { | ||
| return useMutation<void, ApiError>({ | ||
| mutationFn: logout, | ||
| }); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import { useMutation } from '@tanstack/react-query'; | ||
| import { reissue } from '@/src/_apis/auth/reissue-apis'; | ||
| import { useHandleAuthSuccess } from '@/src/hooks/use-handle-auth-success'; | ||
| import { ApiError } from '@/src/utils/api'; | ||
|
|
||
| export function usePostReissueQuery() { | ||
| const handleAuthSuccess = useHandleAuthSuccess(); | ||
|
|
||
| return useMutation<{ token: string | null }, ApiError>({ | ||
| mutationFn: reissue, | ||
| onSuccess: async (response) => { | ||
| await handleAuthSuccess(response.token); | ||
| }, | ||
| }); | ||
|
Comment on lines
+9
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ ๏ธ Refactor suggestion ์๋ฌ ์ฒ๋ฆฌ ๋ก์ง ์ถ๊ฐ๋ฅผ ๊ณ ๋ คํด์ฃผ์ธ์. ํ์ฌ ์ฑ๊ณต ์ผ์ด์ค๋ง ์ฒ๋ฆฌ๋๊ณ ์์ต๋๋ค. ํ ํฐ ์ฌ๋ฐ๊ธ ์คํจ ์ ์ฌ์ฉ์์๊ฒ ์ ์ ํ ํผ๋๋ฐฑ์ ์ ๊ณตํ๊ณ ํ์ํ ํ์ ์กฐ์น๋ฅผ ์ทจํ๋๋ก ๋ค์๊ณผ ๊ฐ์ด ์๋ฌ ์ฒ๋ฆฌ๋ฅผ ์ถ๊ฐํด๋ณด์ธ์: return useMutation<{ token: string | null }, ApiError>({
mutationFn: reissue,
onSuccess: async (response) => {
await handleAuthSuccess(response.token);
},
+ onError: (error) => {
+ // ์๋ฌ ์ํฉ์ ๋ฐ๋ฅธ ์ฒ๋ฆฌ
+ if (error.status === 401) {
+ // ์ธ์ฆ ๋ง๋ฃ ์ฒ๋ฆฌ
+ } else {
+ // ๊ธฐํ ์๋ฌ ์ฒ๋ฆฌ
+ }
+ },
});
|
||
| } | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import { useQuery } from '@tanstack/react-query'; | ||
| import { getUser } from '@/src/_apis/auth/user-apis'; | ||
| import { authStore } from '@/src/store/use-auth-store'; | ||
|
|
||
| export function useUser() { | ||
| const { token } = authStore.getState(); | ||
| return useQuery({ | ||
| queryKey: ['user'], | ||
| queryFn: getUser, | ||
| enabled: !!token, | ||
| }); | ||
| } | ||
|
Comment on lines
+5
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ ๏ธ Refactor suggestion ํ ํฐ ์ ํจ์ฑ ๊ฒ์ฆ ๊ฐํ ํ์ ํ์ฌ ๊ตฌํ์ ํ ํฐ์ ์กด์ฌ ์ฌ๋ถ๋ง ํ์ธํ๊ณ ์์ต๋๋ค. ๋ณด์ ๊ฐํ๋ฅผ ์ํด ๋ค์ ์ฌํญ๋ค์ ๊ณ ๋ คํด์ฃผ์ธ์:
๋ค์๊ณผ ๊ฐ์ด ๊ฐ์ ํ ๊ฒ์ ์ ์๋๋ฆฝ๋๋ค: export function useUser() {
const { token } = authStore.getState();
+ const isValidToken = token && !isTokenExpired(token);
return useQuery({
queryKey: ['user'],
queryFn: getUser,
- enabled: !!token,
+ enabled: isValidToken,
+ retry: false,
+ onError: (error) => {
+ if (error.response?.status === 401) {
+ authStore.getState().logout();
+ }
+ },
});
}
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,3 +1,4 @@ | ||||||||||||||||||||||||||
| import { Suspense } from 'react'; | ||||||||||||||||||||||||||
| import type { Metadata } from 'next'; | ||||||||||||||||||||||||||
| import Image from 'next/image'; | ||||||||||||||||||||||||||
| import '@mantine/core/styles.css'; | ||||||||||||||||||||||||||
|
|
@@ -30,7 +31,9 @@ export default function AuthLayout({ | |||||||||||||||||||||||||
| <div className="mt-4 text-center text-sm font-semibold lg:text-base"> | ||||||||||||||||||||||||||
| ํจ๊ปํ ์ฌ๋์ด์๋์? ์ง๊ธ ํฌ๋ฃจ์ ์ฐธ์ฌํด๋ณด์ธ์ | ||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||
| <div className="mt-6 w-full md:mt-12 md:w-2/3 lg:w-1/2">{children}</div> | ||||||||||||||||||||||||||
| <div className="mt-6 w-full md:mt-12 md:w-2/3 lg:w-1/2"> | ||||||||||||||||||||||||||
| <Suspense>{children}</Suspense> | ||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||
|
Comment on lines
+34
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ ๏ธ Refactor suggestion Suspense์ fallback UI ์ถ๊ฐ ํ์ ๋ก๋ฉ ์ํ๋ฅผ ์ฌ์ฉ์์๊ฒ ํ์ํ๊ธฐ ์ํด fallback prop์ ์ถ๊ฐํ๋ ๊ฒ์ด ์ข์ต๋๋ค. ๋ค์๊ณผ ๊ฐ์ด ์์ ํ๋ ๊ฒ์ ์ ์๋๋ฆฝ๋๋ค: <div className="mt-6 w-full md:mt-12 md:w-2/3 lg:w-1/2">
- <Suspense>{children}</Suspense>
+ <Suspense fallback={
+ <div className="flex justify-center items-center h-full">
+ <div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-primary"></div>
+ </div>
+ }>
+ {children}
+ </Suspense>
</div>๐ Committable suggestion
Suggested change
|
||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,7 @@ | ||||||||||||||||||||||
| import { UseFormReturn } from 'react-hook-form'; | ||||||||||||||||||||||
| import { Button } from '@mantine/core'; | ||||||||||||||||||||||
| import { useDebouncedCallback } from '@mantine/hooks'; | ||||||||||||||||||||||
| import { useEffect } from 'react'; | ||||||||||||||||||||||
| import { UseFormReturn, useWatch } from 'react-hook-form'; | ||||||||||||||||||||||
| import { useDebouncedValue } from '@mantine/hooks'; | ||||||||||||||||||||||
| import Button from '@/src/components/common/input/button'; | ||||||||||||||||||||||
| import PasswordInput from '@/src/components/common/input/password-input'; | ||||||||||||||||||||||
| import TextInput from '@/src/components/common/input/text-input'; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
@@ -18,15 +19,29 @@ export default function LoginForm({ onSubmit, formMethods }: LoginFormProps) { | |||||||||||||||||||||
| register, | ||||||||||||||||||||||
| handleSubmit, | ||||||||||||||||||||||
| trigger, | ||||||||||||||||||||||
| control, | ||||||||||||||||||||||
| formState: { errors, isValid }, | ||||||||||||||||||||||
| } = formMethods; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const debouncedTrigger = useDebouncedCallback(async (field: keyof LoginFormValues) => { | ||||||||||||||||||||||
| await trigger(field); | ||||||||||||||||||||||
| }, 1000); | ||||||||||||||||||||||
| const email = useWatch({ control, name: 'email' }); | ||||||||||||||||||||||
| const password = useWatch({ control, name: 'password' }); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const [debouncedEmail, cancelDebouncedEmail] = useDebouncedValue(email, 1000); | ||||||||||||||||||||||
| const [debouncedPassword, cancelDebouncedPassword] = useDebouncedValue(password, 1000); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||
| if (debouncedEmail) trigger('email'); | ||||||||||||||||||||||
| if (debouncedPassword) trigger('password'); | ||||||||||||||||||||||
| }, [debouncedEmail, debouncedPassword, trigger]); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const handleFormSubmit = handleSubmit(async (data) => { | ||||||||||||||||||||||
| onSubmit(data); | ||||||||||||||||||||||
| cancelDebouncedEmail(); | ||||||||||||||||||||||
| cancelDebouncedPassword(); | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
|
Comment on lines
+37
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ํผ ์ ์ถ ํธ๋ค๋ฌ ๋ก์ง ๊ฐ์ ํ์ ๋๋ฐ์ด์ค ์ทจ์ ๋ก์ง์ด ํผ ์ ์ถ ํ์ ์คํ๋๊ณ ์์ต๋๋ค. ์ด๋ ์ ์ฌ์ ์ผ๋ก ๊ฒฝ์ ์ํ๋ฅผ ๋ฐ์์ํฌ ์ ์์ต๋๋ค. ๋ค์๊ณผ ๊ฐ์ด ์์ ์ ์ ์ํฉ๋๋ค: const handleFormSubmit = handleSubmit(async (data) => {
+ cancelDebouncedEmail();
+ cancelDebouncedPassword();
onSubmit(data);
- cancelDebouncedEmail();
- cancelDebouncedPassword();
});๐ Committable suggestion
Suggested change
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return ( | ||||||||||||||||||||||
| <form onSubmit={handleSubmit(onSubmit)}> | ||||||||||||||||||||||
| <form onSubmit={handleFormSubmit}> | ||||||||||||||||||||||
| <TextInput | ||||||||||||||||||||||
| label="์์ด๋" | ||||||||||||||||||||||
| placeholder="์ด๋ฉ์ผ์ ์ ๋ ฅํด์ฃผ์ธ์" | ||||||||||||||||||||||
|
|
@@ -38,7 +53,6 @@ export default function LoginForm({ onSubmit, formMethods }: LoginFormProps) { | |||||||||||||||||||||
| message: '์ด๋ฉ์ผ ํ์์ผ๋ก ์ ๋ ฅํด์ฃผ์ธ์', | ||||||||||||||||||||||
| }, | ||||||||||||||||||||||
| onBlur: () => trigger('email'), | ||||||||||||||||||||||
| onChange: async () => debouncedTrigger('email'), | ||||||||||||||||||||||
| }), | ||||||||||||||||||||||
| }} | ||||||||||||||||||||||
| error={errors.email?.message} | ||||||||||||||||||||||
|
|
@@ -56,7 +70,6 @@ export default function LoginForm({ onSubmit, formMethods }: LoginFormProps) { | |||||||||||||||||||||
| message: '๋น๋ฐ๋ฒํธ๊ฐ 8์ ์ด์์ด ๋๋๋ก ํด์ฃผ์ธ์.', | ||||||||||||||||||||||
| }, | ||||||||||||||||||||||
| onBlur: () => trigger('password'), | ||||||||||||||||||||||
| onChange: async () => debouncedTrigger('password'), | ||||||||||||||||||||||
| }), | ||||||||||||||||||||||
| }} | ||||||||||||||||||||||
| error={errors.password?.message} | ||||||||||||||||||||||
|
|
@@ -65,8 +78,11 @@ export default function LoginForm({ onSubmit, formMethods }: LoginFormProps) { | |||||||||||||||||||||
| /> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <div className="mt-10"> | ||||||||||||||||||||||
| {/* TODO: Button ๋ฐ๊พธ๊ธฐ */} | ||||||||||||||||||||||
| <Button disabled={!isValid} type="submit" fullWidth> | ||||||||||||||||||||||
| <Button | ||||||||||||||||||||||
| disabled={!isValid} | ||||||||||||||||||||||
| type="submit" | ||||||||||||||||||||||
| className={`w-full font-semibold ${isValid ? 'bg-blue-500 text-white' : 'bg-gray-100 text-gray-300'}`} | ||||||||||||||||||||||
| > | ||||||||||||||||||||||
| ๋ก๊ทธ์ธ | ||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||
| </div> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,22 +1,29 @@ | ||||||||||||||||||
| 'use client'; | ||||||||||||||||||
|
|
||||||||||||||||||
| import React from 'react'; | ||||||||||||||||||
| import React, { useEffect, useState } from 'react'; | ||||||||||||||||||
| import { useForm } from 'react-hook-form'; | ||||||||||||||||||
| import Link from 'next/link'; | ||||||||||||||||||
| import { useRouter } from 'next/navigation'; | ||||||||||||||||||
| import { useRouter, useSearchParams } from 'next/navigation'; | ||||||||||||||||||
| import { usePostLoginQuery } from '@/src/_queries/auth/login-queries'; | ||||||||||||||||||
| import LoginForm, { LoginFormValues } from './_component/login-form'; | ||||||||||||||||||
|
|
||||||||||||||||||
| export default function LoginPage() { | ||||||||||||||||||
| const [redirect, setRedirect] = useState('/'); | ||||||||||||||||||
| const searchParams = useSearchParams(); | ||||||||||||||||||
| const router = useRouter(); | ||||||||||||||||||
| const formMethods = useForm<LoginFormValues>(); | ||||||||||||||||||
| const { setError } = formMethods; | ||||||||||||||||||
| const { mutate: postLogin } = usePostLoginQuery(); | ||||||||||||||||||
|
|
||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||
| const redirectParam = searchParams.get('redirect'); | ||||||||||||||||||
| if (redirectParam) setRedirect(redirectParam); | ||||||||||||||||||
| }, [searchParams]); | ||||||||||||||||||
|
|
||||||||||||||||||
| const handleSubmit = async (data: LoginFormValues) => { | ||||||||||||||||||
| postLogin(data, { | ||||||||||||||||||
| onSuccess: () => { | ||||||||||||||||||
| router.push('/'); | ||||||||||||||||||
| router.push(redirect); | ||||||||||||||||||
| }, | ||||||||||||||||||
| onError: (error) => { | ||||||||||||||||||
| if (error.status === 401) { | ||||||||||||||||||
|
|
@@ -38,7 +45,10 @@ export default function LoginPage() { | |||||||||||||||||
| <LoginForm formMethods={formMethods} onSubmit={handleSubmit} /> | ||||||||||||||||||
| <div className="mt-6 flex justify-center space-x-1 text-sm font-medium"> | ||||||||||||||||||
| <div>ํฌ๋ฃจ๊ฐ ์ฒ์์ด์ ๊ฐ์?</div> | ||||||||||||||||||
| <Link href="/signup" className="text-blue-500 underline"> | ||||||||||||||||||
| <Link | ||||||||||||||||||
| href={redirect !== '/' ? `/signup?redirect=${redirect}` : '/signup'} | ||||||||||||||||||
| className="text-blue-500 underline" | ||||||||||||||||||
| > | ||||||||||||||||||
|
Comment on lines
+48
to
+51
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๋ฆฌ๋ค์ด๋ ํธ URL ์ธ์ฝ๋ฉ ํ์ ๋ฆฌ๋ค์ด๋ ํธ ํ๋ผ๋ฏธํฐ์ ๋ํ URL ์ธ์ฝ๋ฉ์ด ๋๋ฝ๋์ด ์์ด ๋ณด์ ์ทจ์ฝ์ ์ด ๋ฐ์ํ ์ ์์ต๋๋ค. ๋ค์๊ณผ ๊ฐ์ด ์์ ํ๋ ๊ฒ์ ๊ถ์ฅ๋๋ฆฝ๋๋ค: <Link
- href={redirect !== '/' ? `/signup?redirect=${redirect}` : '/signup'}
+ href={redirect !== '/' ? `/signup?redirect=${encodeURIComponent(redirect)}` : '/signup'}
className="text-blue-500 underline"
>๐ Committable suggestion
Suggested change
|
||||||||||||||||||
| ํ์๊ฐ์ | ||||||||||||||||||
| </Link> | ||||||||||||||||||
| </div> | ||||||||||||||||||
|
|
||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,7 @@ | ||
| import React, { useEffect } from 'react'; | ||
| import { UseFormReturn } from 'react-hook-form'; | ||
| import { Button } from '@mantine/core'; | ||
| import { useDebouncedCallback } from '@mantine/hooks'; | ||
| import { UseFormReturn, useWatch } from 'react-hook-form'; | ||
| import { useDebouncedValue } from '@mantine/hooks'; | ||
| import Button from '@/src/components/common/input/button'; | ||
| import PasswordInput from '@/src/components/common/input/password-input'; | ||
| import TextInput from '@/src/components/common/input/text-input'; | ||
|
|
||
|
|
@@ -22,32 +22,59 @@ export default function SignupForm({ formMethods, onSubmit }: SignupFormProps) { | |
| register, | ||
| handleSubmit, | ||
| trigger, | ||
| control, | ||
| formState: { errors, isValid }, | ||
| watch, | ||
| } = formMethods; | ||
|
|
||
| const debouncedTrigger = useDebouncedCallback(async (field: keyof SignupFormValues) => { | ||
| await trigger(field); | ||
| }, 1000); | ||
| const [nickname, email, password, confirmPassword] = useWatch({ | ||
| control, | ||
| name: ['nickname', 'email', 'password', 'confirmPassword'], | ||
| }); | ||
|
|
||
| const password = watch('password'); | ||
| const [debouncedNickname, cancelDebouncedNickname] = useDebouncedValue(nickname, 1000); | ||
| const [debouncedEmail, cancelDebouncedEmail] = useDebouncedValue(email, 1000); | ||
| const [debouncedPassword, cancelDebouncedPassword] = useDebouncedValue(password, 1000); | ||
| const [debouncedConfirmPassword, cancelDebouncedConfirmPassword] = useDebouncedValue( | ||
| confirmPassword, | ||
| 1000, | ||
| ); | ||
|
|
||
| useEffect(() => { | ||
| if (debouncedNickname) trigger('nickname'); | ||
| }, [nickname, trigger]); | ||
|
|
||
| useEffect(() => { | ||
| if (debouncedEmail) trigger('email'); | ||
| }, [email, trigger]); | ||
|
|
||
| useEffect(() => { | ||
| if (password) { | ||
| if (debouncedPassword) { | ||
| trigger('password'); | ||
| trigger('confirmPassword'); | ||
| } | ||
| }, [password, trigger]); | ||
|
|
||
| useEffect(() => { | ||
| if (debouncedConfirmPassword) trigger('confirmPassword'); | ||
| }, [confirmPassword, trigger]); | ||
|
Comment on lines
+42
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ ๏ธ Refactor suggestion ์ ํจ์ฑ ๊ฒ์ฆ ๋ก์ง์ด ์ค๋ณต๋์ด ์์ต๋๋ค. ๊ฐ ํ๋๋ง๋ค ๋น์ทํ ๋ค์๊ณผ ๊ฐ์ด ํ๋์ useEffect(() => {
const fields = {
nickname: debouncedNickname,
email: debouncedEmail,
password: debouncedPassword,
confirmPassword: debouncedConfirmPassword,
};
Object.entries(fields).forEach(([field, value]) => {
if (value) {
trigger(field as keyof SignupFormValues);
if (field === 'password') {
trigger('confirmPassword');
}
}
});
}, [debouncedNickname, debouncedEmail, debouncedPassword, debouncedConfirmPassword, trigger]); |
||
|
|
||
| const handleFormSubmit = handleSubmit(async (data) => { | ||
| onSubmit(data); | ||
| cancelDebouncedNickname(); | ||
| cancelDebouncedEmail(); | ||
| cancelDebouncedPassword(); | ||
| cancelDebouncedConfirmPassword(); | ||
| }); | ||
|
|
||
| return ( | ||
| <form onSubmit={handleSubmit(onSubmit)}> | ||
| <form onSubmit={handleFormSubmit}> | ||
| <TextInput | ||
| label="๋๋ค์" | ||
| placeholder="๋๋ค์์ ์ ๋ ฅํด์ฃผ์ธ์" | ||
| register={{ | ||
| ...register('nickname', { | ||
| required: '๋๋ค์์ ์ ๋ ฅํด์ฃผ์ธ์', | ||
| onBlur: () => trigger('nickname'), | ||
| onChange: async () => debouncedTrigger('nickname'), | ||
| }), | ||
| }} | ||
| error={errors.nickname?.message} | ||
|
|
@@ -65,7 +92,6 @@ export default function SignupForm({ formMethods, onSubmit }: SignupFormProps) { | |
| message: '์ด๋ฉ์ผ ํ์์ผ๋ก ์ ๋ ฅํด์ฃผ์ธ์', | ||
| }, | ||
| onBlur: () => trigger('email'), | ||
| onChange: async () => debouncedTrigger('email'), | ||
| }), | ||
| }} | ||
| error={errors.email?.message} | ||
|
|
@@ -84,7 +110,6 @@ export default function SignupForm({ formMethods, onSubmit }: SignupFormProps) { | |
| message: '๋น๋ฐ๋ฒํธ๊ฐ 8์ ์ด์์ด ๋๋๋ก ํด์ฃผ์ธ์.', | ||
| }, | ||
| onBlur: () => trigger('password'), | ||
| onChange: async () => debouncedTrigger('password'), | ||
| }), | ||
| }} | ||
| error={errors.password?.message} | ||
|
|
@@ -100,7 +125,6 @@ export default function SignupForm({ formMethods, onSubmit }: SignupFormProps) { | |
| required: '๋น๋ฐ๋ฒํธ๋ฅผ ๋ค์ ์ ๋ ฅํด์ฃผ์ธ์', | ||
| validate: (value) => value === password || '๋น๋ฐ๋ฒํธ๊ฐ ์ผ์นํ์ง ์์ต๋๋ค.', | ||
| onBlur: () => trigger('confirmPassword'), | ||
| onChange: async () => debouncedTrigger('confirmPassword'), | ||
| }), | ||
| }} | ||
| error={errors.confirmPassword?.message} | ||
|
|
@@ -109,8 +133,11 @@ export default function SignupForm({ formMethods, onSubmit }: SignupFormProps) { | |
| /> | ||
|
|
||
| <div className="mt-10"> | ||
| {/* TODO: button ๋ฐ๊พธ๊ธฐ */} | ||
| <Button disabled={!isValid} type="submit" fullWidth> | ||
| <Button | ||
| disabled={!isValid} | ||
| type="submit" | ||
| className={`w-full font-semibold ${isValid ? 'bg-blue-500 text-white' : 'bg-gray-100 text-gray-300'}`} | ||
| > | ||
| ํ์๊ฐ์ | ||
| </Button> | ||
| </div> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
๐ ๏ธ Refactor suggestion
ํ ํฐ ์ฌ๋ฐ๊ธ ๋ก์ง ๊ฐ์ ํ์
๋ค์ ์ฌํญ๋ค์ ๊ฐ์ ์ด ํ์ํฉ๋๋ค:
๋ค์๊ณผ ๊ฐ์ด ๊ฐ์ ํ๋ ๊ฒ์ ์ ์๋๋ฆฝ๋๋ค:
์ถ๊ฐ๋ก ๊ณ ๋ คํด์ผ ํ ์ฌํญ:
๐ Committable suggestion