-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
67 changed files
with
1,039 additions
and
504 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,23 @@ | ||
import axios from 'axios'; | ||
|
||
import { | ||
CreateUserDTO, | ||
VerifyEmailDTO, | ||
VerifyNicknameDTO, | ||
LoginRequestDTO, | ||
LoginResponseDTO, | ||
RefreshResponseDTO, | ||
} from '@/features/auth/auth.dto'; | ||
|
||
const USER_BASE_URL = `/api/users`; | ||
const AUTH_BASE_URL = '/api/auth'; | ||
|
||
export const createUser = (createUserDTO: CreateUserDTO) => | ||
axios.post(USER_BASE_URL, createUserDTO); | ||
|
||
export const verifyEmail = (email: string) => | ||
export const login = (loginDTO: LoginRequestDTO) => | ||
axios | ||
.get<VerifyEmailDTO>(`${USER_BASE_URL}/emails/${email}`) | ||
.post<LoginResponseDTO>(`${AUTH_BASE_URL}/login`, loginDTO) | ||
.then((res) => res.data); | ||
|
||
export const verifyNickname = (nickname: string) => | ||
export const logout = () => axios.post(`${AUTH_BASE_URL}/logout`); | ||
|
||
export const refresh = () => | ||
axios | ||
.get<VerifyNicknameDTO>(`${USER_BASE_URL}/nicknames/${nickname}`) | ||
.post<RefreshResponseDTO>(`${AUTH_BASE_URL}/token`, undefined, { | ||
withCredentials: true, | ||
}) | ||
.then((res) => res.data); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,18 @@ | ||
import { SuccessDTO } from '@/shared'; | ||
import { ErrorDTO, SuccessDTO } from '@/shared'; | ||
|
||
export interface CreateUserDTO { | ||
export interface LoginRequestDTO { | ||
email: string; | ||
password: string; | ||
nickname: string; | ||
} | ||
|
||
export type VerifyEmailDTO = SuccessDTO<{ exists: boolean }>; | ||
export type LoginResponseDTO = | ||
| SuccessDTO<{ | ||
accessToken: string; | ||
}> | ||
| ErrorDTO; | ||
|
||
export type VerifyNicknameDTO = SuccessDTO<{ exists: boolean }>; | ||
export type RefreshResponseDTO = | ||
| SuccessDTO<{ | ||
accessToken: string; | ||
}> | ||
| ErrorDTO; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,156 +1,55 @@ | ||
import { debounce } from 'es-toolkit'; | ||
import { useCallback, useEffect, useMemo, useState } from 'react'; | ||
import { isAxiosError } from 'axios'; | ||
import { useState } from 'react'; | ||
|
||
import { verifyEmail, verifyNickname } from '@/features/auth/index'; | ||
import { login } from '@/features/auth/auth.api'; | ||
import { useAuthStore } from '@/features/auth/auth.store'; | ||
import { ValidationStatusWithMessage } from '@/shared'; | ||
|
||
export type ValidationStatus = 'INITIAL' | 'PENDING' | 'VALID' | 'INVALID'; | ||
|
||
export interface ValidationStatusWithMessage { | ||
status: ValidationStatus; | ||
message?: string; | ||
} | ||
|
||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; | ||
|
||
const validateEmail = (email: string): ValidationStatusWithMessage => { | ||
if (!email) return { status: 'INITIAL' }; | ||
if (!emailRegex.test(email)) | ||
return { status: 'INVALID', message: '이메일 형식이 올바르지 않습니다.' }; | ||
if (email.includes(' ')) | ||
return { | ||
status: 'INVALID', | ||
message: '이메일에 공백이 포함될 수 없습니다.', | ||
}; | ||
return { status: 'PENDING', message: '중복 여부를 검사 중입니다.' }; | ||
}; | ||
|
||
const validateNickname = (nickname: string): ValidationStatusWithMessage => { | ||
if (!nickname) return { status: 'INITIAL' }; | ||
if (nickname.length < 3 || nickname.length > 20) | ||
return { | ||
status: 'INVALID', | ||
message: '닉네임은 3-20자 사이로 입력해주세요.', | ||
}; | ||
if (nickname.includes(' ')) | ||
return { | ||
status: 'INVALID', | ||
message: '닉네임에 공백이 포함될 수 없습니다.', | ||
}; | ||
return { status: 'PENDING', message: '중복 여부를 검사 중입니다.' }; | ||
}; | ||
|
||
const validatePassword = (password: string): ValidationStatusWithMessage => { | ||
if (!password) return { status: 'INITIAL' }; | ||
if (password.length < 8 || password.length > 20) | ||
return { | ||
status: 'INVALID', | ||
message: '비밀번호는 8-20자 사이로 입력해주세요.', | ||
}; | ||
if (password.includes(' ')) | ||
return { | ||
status: 'INVALID', | ||
message: '비밀번호에는 공백이 포함될 수 없습니다.', | ||
}; | ||
return { status: 'VALID', message: '사용 가능한 비밀번호입니다.' }; | ||
}; | ||
export function useSignInForm() { | ||
const { setAccessToken } = useAuthStore(); | ||
|
||
export function useSignUpForm() { | ||
const [email, setEmail] = useState(''); | ||
|
||
const [nickname, setNickname] = useState(''); | ||
|
||
const [password, setPassword] = useState(''); | ||
|
||
const [emailValidationStatus, setEmailValidationStatus] = | ||
useState<ValidationStatusWithMessage>({ status: 'INITIAL' }); | ||
|
||
const [nicknameValidationStatus, setNicknameValidationStatus] = | ||
useState<ValidationStatusWithMessage>({ status: 'INITIAL' }); | ||
|
||
const [passwordValidationStatus, setPasswordValidationStatus] = | ||
useState<ValidationStatusWithMessage>({ status: 'INITIAL' }); | ||
|
||
const isSignUpEnabled = useMemo( | ||
() => | ||
emailValidationStatus.status === 'VALID' && | ||
nicknameValidationStatus.status === 'VALID' && | ||
passwordValidationStatus.status === 'VALID', | ||
[emailValidationStatus, nicknameValidationStatus, passwordValidationStatus], | ||
); | ||
|
||
const checkEmailToVerify = useCallback( | ||
debounce(async (emailToVerify: string) => { | ||
const response = await verifyEmail(emailToVerify); | ||
|
||
setEmail(emailToVerify); | ||
setEmailValidationStatus( | ||
response.data.exists | ||
? { status: 'INVALID', message: '이미 사용 중인 이메일입니다.' } | ||
: { status: 'VALID', message: '사용 가능한 이메일입니다.' }, | ||
); | ||
}, 500), | ||
[], | ||
); | ||
|
||
const checkNicknameToVerify = useCallback( | ||
debounce(async (nicknameToVerify: string) => { | ||
const response = await verifyNickname(nicknameToVerify); | ||
|
||
setNickname(nicknameToVerify); | ||
setNicknameValidationStatus( | ||
response.data.exists | ||
? { status: 'INVALID', message: '이미 사용 중인 닉네임입니다.' } | ||
: { status: 'VALID', message: '사용 가능한 닉네임입니다.' }, | ||
); | ||
}, 500), | ||
[], | ||
); | ||
|
||
useEffect(() => { | ||
const validationStatus = validateEmail(email); | ||
setEmailValidationStatus(validationStatus); | ||
if (validationStatus.status === 'PENDING') checkEmailToVerify(email); | ||
else checkEmailToVerify.cancel(); | ||
}, [email, checkEmailToVerify]); | ||
|
||
useEffect(() => { | ||
const validationStatus = validateNickname(nickname); | ||
setNicknameValidationStatus(validationStatus); | ||
if (validationStatus.status === 'PENDING') checkNicknameToVerify(nickname); | ||
else checkNicknameToVerify.cancel(); | ||
}, [nickname, checkNicknameToVerify]); | ||
|
||
useEffect(() => { | ||
setPasswordValidationStatus(validatePassword(password)); | ||
}, [password]); | ||
|
||
return { | ||
email, | ||
setEmail, | ||
nickname, | ||
setNickname, | ||
password, | ||
setPassword, | ||
emailValidationStatus, | ||
nicknameValidationStatus, | ||
passwordValidationStatus, | ||
isSignUpEnabled, | ||
const isLoginEnabled = email.length > 0 && password.length > 7; | ||
|
||
const [loginFailed, setLoginFailed] = useState<ValidationStatusWithMessage>({ | ||
status: 'INITIAL', | ||
}); | ||
|
||
const handleLogin = async () => { | ||
try { | ||
const response = await login({ email, password }); | ||
|
||
if (response.type === 'success') { | ||
setAccessToken(response.data.accessToken); | ||
} | ||
} catch (e) { | ||
if (isAxiosError(e) && e.response && 'error' in e.response.data) { | ||
if (e.response.status === 400) { | ||
setLoginFailed({ | ||
status: 'INVALID', | ||
message: e.response.data.error.messages.shift(), | ||
}); | ||
} else if (e.response.status === 401) { | ||
setLoginFailed({ | ||
status: 'INVALID', | ||
message: e.response.data.error.message, | ||
}); | ||
} | ||
} | ||
throw e; | ||
} | ||
}; | ||
} | ||
|
||
export function useSignInForm() { | ||
const [email, setEmail] = useState(''); | ||
|
||
const [password, setPassword] = useState(''); | ||
|
||
const [isLoginFailed, setIsLoginFailed] = useState(false); | ||
|
||
return { | ||
email, | ||
setEmail, | ||
password, | ||
setPassword, | ||
isLoginFailed, | ||
setIsLoginFailed, | ||
isLoginEnabled, | ||
loginFailed, | ||
handleLogin, | ||
}; | ||
} |
Oops, something went wrong.