-
Notifications
You must be signed in to change notification settings - Fork 8
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
회원가입 / 로그인 페이지 구현, 기능 구현 #284
Merged
Hain-tain
merged 68 commits into
woowacourse-teams:dev/fe
from
Jaymyong66:feat/signup_login
Aug 6, 2024
Merged
Changes from all commits
Commits
Show all changes
68 commits
Select commit
Hold shift + click to select a range
39f4cdc
feat(SignupPage): 회원가입 페이지 생성, 라우트 설정
Jaymyong66 9d77a01
feat(LoginPage): 로그인 페이지 생성, 라우트 설정
Jaymyong66 ce63c28
refactor(style): 오타로 스타일 적용 안된 것 수정
Jaymyong66 e9a704e
refactor(Input): Wrapper -> Container 변수명 변경
Jaymyong66 b737873
feat(Input): Input 컴포넌트에 Label 추가
Jaymyong66 5021580
feat(Images): passwordEye 이미지 생성
Jaymyong66 7c9a36b
feat(SignupPage): 회원가입 페이지 UI
Jaymyong66 f6d7a1e
feat(Login): 로그인 페이지 UI
Jaymyong66 b057b4d
feat(authentication): 비밀번호 보기 버튼 훅 생성
Jaymyong66 a5682e7
feat(LoginPage): 비밀번호 보기 기능 적용
Jaymyong66 3d1b049
feat(SignupPage): 비밀번호와 비밀번호 확인 각각에 보기 기능 적용
Jaymyong66 239c865
feat(authentication): useSignupForm 생성, http 요청 제외
Jaymyong66 166bf64
feat(SignupPage): 회원가입 유효성 검증 hook 적용
Jaymyong66 af1ff3b
feat(api): 회원가입 POST 요청 함수 생성
Jaymyong66 809800c
feat(mocks): 회원가입 POST 요청에 대한 MSW 모킹 추가
Jaymyong66 ff71d12
feat(authentication): 회원가입 hook에 submit 액션에 API 요청 추가
Jaymyong66 31cbd52
feat(SignupPage): 로그인 페이지로 가는 버튼 기능 추가
Jaymyong66 eef1dce
feat(LoginPage): 회원가입 페이지로 가는 버튼 기능 추가
Jaymyong66 ab67ca5
refactor(SignupPage): HelperText의 높이 고정, 유효하지 않을 시 Input의 isValid 변경 처리
Jaymyong66 3d23bce
feat(api): 중복 이메일, 중복 닉네임 확인 API 요청 함수 생성
Jaymyong66 2cd4b9f
refactor(mocks): MSW handler의 회원가입 요청 URL 상수화
Jaymyong66 4caf5ce
feat(mocks): MSW handler 추가 - 이메일 중복 검사, 닉네임 중복 검사 URL
Jaymyong66 d151b66
refactor(mocks): templates 관련 MSW handler 코드 스타일 변경
Jaymyong66 e7cda45
feat(authentication): 이메일 중복 검사 Query 훅 생성
Jaymyong66 a6670bc
feat(mocks): 이메일 중복 검사, 닉네임 중복 검사 MSW handler 생성
Jaymyong66 002db5a
feat(SignupPage): 이메일 입력 후 Blur 이벤트 발생 시, 이메일 중복 검사 요청 기능
Jaymyong66 2eb2d13
refactor(authentication): 회원가입 요청의 데이터 타입에서 confirmPassword 제거,
Jaymyong66 f37863f
refactor(authentication): queries/authentication 폴더에 index 파일 생성
Jaymyong66 dd54fde
feat(api): 로그인 API 요청 함수 생성
Jaymyong66 25e52fe
feat(mocks): 로그인 요청 MSW handler 추가
Jaymyong66 274289b
feat(authentication): 회원가입 후 로그인 페이지로 라우팅
Jaymyong66 b25e29b
feat(authentication): 로그인 기능 훅 생성, LoginPage에 적용
Jaymyong66 c9f151c
refactor(mocks): 멤버별 템플릿 목록 모킹 데이터 분리
Jaymyong66 8cc6a9f
refactor(authentication): 로그인 훅에서 로컬스토리지 로직 분리 -> 임시로 API 레이어로
Jaymyong66 246ab5c
feat(api): customFetch - localStorage에 token이 있다면 요청 header에 추가
Jaymyong66 0fb93ba
feat(mocks): handler - 두 명의 유저를 가정, token에 따라 응답을 다르게 주는 login 모킹
Jaymyong66 7675ce2
refactor(SignupPage): nickname -> username 으로 변경
Jaymyong66 fd9a757
feat(authentication): username 중복 검사 요청 훅 생성
Jaymyong66 2dae680
refactor(authentication): useSignupForm - 입력값의 전후 공백 제거, nickname -> …
Jaymyong66 d9a15b1
feat(SignupPage): 중복 사용자이름 검사 추가
Jaymyong66 c8b046f
refactor(SignupPage): form 태그 추가, gap 변경
Jaymyong66 3390522
refactor(SignupPage): h1태그를 Heading 컴포넌트로 변경
Jaymyong66 7b93a74
refactor(LoginPage): Input 상위에 form 태그 추가
Jaymyong66 880d4c2
refactor(LoginPage): input에 autoComplate 옵션 추가
Jaymyong66 6351a4c
refactor(SignupPage): input에 autoComplate 옵션 추가
Jaymyong66 2ba0dc7
refactor(authentication): handleSubmit에 event의 preventDefault 추가
Jaymyong66 b7ac892
refactor(authentication): 로그인 입력값에 전후 공백 제거
Jaymyong66 2d7b4ac
refactor(pages): LoginPage, SignupPage - Text 컴포넌트 적용
Jaymyong66 48d3d8a
feat(api): customFetch - fetch함수에 httponly 쿠키를 위한 credentials 옵션 추가
Jaymyong66 733a9a3
refactor(authenication): validates - 유효성 검사 로직 분리
Jaymyong66 1b1208f
feat(hooks): useInput - 훅 생성
Jaymyong66 8968ad3
refactor(authentication): useSignupForm - useInput 훅 사용, handle 함수 구현…
Jaymyong66 2c2daa1
refactor(hooks): useInput - handle 함수를 useCallback으로 최적화(useEffect의 의…
Jaymyong66 fc17de2
refactor(authentication): useLoginForm - useInput 훅 사용
Jaymyong66 b67ffae
refactor(template): useTemplateListQuery - mock 데이터 변경에 따른 테스트 스펙 변경
Jaymyong66 b4983d4
refactor(authentication): API 변경에 따른 response 반환값 제거
Jaymyong66 4950c57
refactor(api): customFetch - 내부 throw error 처리 로직 제거
Jaymyong66 0e0cb60
refactor(authentication): 이메일 중복 handling 함수
Jaymyong66 8b68a40
refactor(mocks): 이메일이 중복된 경우의 mocking 추가
Jaymyong66 6f4adbf
refactor(hooks): useInput -> useInputWithValidate 훅 이름 변경
Jaymyong66 735ced5
refactor(pages): 변경된 Button 컴포넌트 적용
Jaymyong66 e3795bf
feat(api): getLoginState 생성
Jaymyong66 c648c7a
feat(mocks): login/check 에 대한 실패 모킹 handler 추가
Jaymyong66 41090fb
feat(authentication): useLoginStateQuery 생성
Jaymyong66 b7a90df
feat(authentication): useCheckLoginState 생성
Jaymyong66 1ab9ab4
feat(Header): Header 마운트시 useCheckLoginState 훅 실행
Jaymyong66 7da0574
refactor(authentication): 사용자 이름 중복 확인 요청 API 변경
Jaymyong66 9024f33
refactor(api): 인증/인가 토큰 변경에 따른 localstorage token 관리 제거
Jaymyong66 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { LoginRequest, SignupRequest } from '@/types/authentication'; | ||
import { customFetch } from './customFetch'; | ||
|
||
const API_URL = process.env.REACT_APP_API_URL; | ||
|
||
export const SIGNUP_API_URL = `${API_URL}/signup`; | ||
export const LOGIN_API_URL = `${API_URL}/login`; | ||
export const LOGIN_STATE_API_URL = `${API_URL}/login/check`; | ||
export const CHECK_USERNAME_API_URL = `${API_URL}/check-username`; | ||
export const CHECK_EMAIL_API_URL = `${API_URL}/check-email`; | ||
|
||
export const postSignup = async (signupInfo: SignupRequest) => | ||
await customFetch({ | ||
method: 'POST', | ||
url: `${SIGNUP_API_URL}`, | ||
body: JSON.stringify(signupInfo), | ||
}); | ||
|
||
export const postLogin = async (loginInfo: LoginRequest) => { | ||
const response = await customFetch({ | ||
method: 'POST', | ||
url: `${LOGIN_API_URL}`, | ||
body: JSON.stringify(loginInfo), | ||
}); | ||
|
||
return response; | ||
}; | ||
|
||
export const getLoginState = async () => { | ||
const url = `${LOGIN_STATE_API_URL}`; | ||
|
||
const response = await customFetch({ url }); | ||
|
||
if (response.status === 401) { | ||
throw new Error('로그인을 해주세요.'); | ||
} | ||
|
||
if (!response.ok) { | ||
throw new Error('서버 에러가 발생했습니다.'); | ||
} | ||
|
||
return {}; | ||
}; | ||
|
||
export const checkEmail = async (email: string) => { | ||
const params = new URLSearchParams({ email }); | ||
const url = `${CHECK_EMAIL_API_URL}?${params}`; | ||
|
||
const response = await customFetch({ url }); | ||
|
||
if (response.status === 409) { | ||
throw new Error('중복된 이메일입니다.'); | ||
} | ||
|
||
if (!response.ok) { | ||
throw new Error('서버 에러가 발생했습니다.'); | ||
} | ||
|
||
return {}; | ||
}; | ||
|
||
export const checkUsername = async (username: string) => { | ||
const params = new URLSearchParams({ username }); | ||
const url = `${CHECK_USERNAME_API_URL}?${params}`; | ||
|
||
const response = await customFetch({ url }); | ||
|
||
if (response.status === 409) { | ||
throw new Error('중복된 닉네임입니다.'); | ||
} | ||
|
||
if (!response.ok) { | ||
throw new Error('서버 에러가 발생했습니다.'); | ||
} | ||
|
||
return {}; | ||
}; |
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,4 +1,7 @@ | ||
export const QUERY_KEY = { | ||
TEMPLATE: 'template', | ||
TEMPLATE_LIST: 'templateList', | ||
LOGIN_STATE: 'loginState', | ||
CHECK_EMAIL: 'checkEmail', | ||
CHECK_USERNAME: 'userName', | ||
}; |
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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 |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export { useShowPassword } from './useShowPassword'; | ||
export { useSignupForm } from './useSignupForm'; | ||
export { useCheckLoginState } from './useCheckLoginState'; |
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 |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { useCallback, useEffect } from 'react'; | ||
import { useNavigate } from 'react-router-dom'; | ||
|
||
import { useLoginStateQuery } from '@/queries/authentication/useLoginStateQuery'; | ||
|
||
export const useCheckLoginState = () => { | ||
const { error, isError } = useLoginStateQuery(); | ||
const navigate = useNavigate(); | ||
|
||
const handleLoginNavigate = useCallback(() => { | ||
navigate('/login'); | ||
}, [navigate]); | ||
|
||
useEffect(() => { | ||
if (isError) { | ||
alert(error.message); | ||
handleLoginNavigate(); | ||
} | ||
}, [error, isError, handleLoginNavigate]); | ||
}; | ||
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 |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { FormEvent } from 'react'; | ||
import { useNavigate } from 'react-router-dom'; | ||
|
||
import { postLogin } from '@/api/authentication'; | ||
import { useInputWithValidate } from '../useInputWithValidate'; | ||
import { validateEmail, validatePassword } from './validates'; | ||
|
||
export const useLoginForm = () => { | ||
const navigate = useNavigate(); | ||
|
||
const { | ||
value: email, | ||
errorMessage: emailError, | ||
handleChange: handleEmailChange, | ||
} = useInputWithValidate('', validateEmail); | ||
|
||
const { | ||
value: password, | ||
errorMessage: passwordError, | ||
handleChange: handlePasswordChange, | ||
} = useInputWithValidate('', validatePassword); | ||
|
||
const isFormValid = () => !emailError && !passwordError && email && password; | ||
|
||
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => { | ||
e.preventDefault(); | ||
|
||
if (isFormValid()) { | ||
const response = await postLogin({ email, password }); | ||
|
||
if (!response.ok) { | ||
console.error(response); | ||
|
||
return; | ||
} | ||
|
||
navigate('/'); | ||
} | ||
}; | ||
|
||
return { | ||
email, | ||
password, | ||
errors: { | ||
email: emailError, | ||
password: passwordError, | ||
}, | ||
handleEmailChange, | ||
handlePasswordChange, | ||
isFormValid, | ||
handleSubmit, | ||
}; | ||
}; |
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 |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { useState } from 'react'; | ||
|
||
export const useShowPassword = () => { | ||
const [showPassword, setShowPassword] = useState(false); | ||
|
||
const handlePasswordToggle = () => { | ||
setShowPassword((prevShowPassword) => !prevShowPassword); | ||
}; | ||
|
||
return { | ||
showPassword, | ||
handlePasswordToggle, | ||
}; | ||
}; |
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 |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import { FormEvent, useEffect } from 'react'; | ||
import { useNavigate } from 'react-router-dom'; | ||
|
||
import { postSignup } from '@/api/authentication'; | ||
import { useCheckEmailQuery } from '@/queries/authentication'; | ||
import { useCheckUsernameQuery } from '@/queries/authentication/useCheckUsernameQuery'; | ||
import { useInputWithValidate } from '../useInputWithValidate'; | ||
import { validateEmail, validateUsername, validatePassword, validateConfirmPassword } from './validates'; | ||
|
||
export const useSignupForm = () => { | ||
const navigate = useNavigate(); | ||
|
||
const { | ||
value: email, | ||
errorMessage: emailError, | ||
handleChange: handleEmailChange, | ||
handleErrorMessage: handleEmailErrorMessage, | ||
} = useInputWithValidate('', validateEmail); | ||
|
||
const { | ||
value: username, | ||
errorMessage: usernameError, | ||
handleChange: handleUsernameChange, | ||
handleErrorMessage: handleUsernameErrorMessage, | ||
} = useInputWithValidate('', validateUsername); | ||
|
||
const { | ||
value: password, | ||
errorMessage: passwordError, | ||
handleChange: handlePasswordChange, | ||
} = useInputWithValidate('', validatePassword); | ||
|
||
const { | ||
value: confirmPassword, | ||
errorMessage: confirmPasswordError, | ||
handleChange: handleConfirmPasswordChange, | ||
handleErrorMessage: handleConfirmPasswordErrorMessage, | ||
} = useInputWithValidate('', (value, compareValue) => validateConfirmPassword(value, compareValue ?? '')); | ||
|
||
const { refetch: checkEmailQuery } = useCheckEmailQuery(email); | ||
const { refetch: checkUsernameQuery } = useCheckUsernameQuery(username); | ||
|
||
const handleEmailCheck = async () => { | ||
const { error } = await checkEmailQuery(); | ||
|
||
// refetch does not exist onError | ||
if (error) { | ||
handleEmailErrorMessage(error.message); | ||
} | ||
}; | ||
|
||
const handleUsernameCheck = async () => { | ||
const { error } = await checkUsernameQuery(); | ||
|
||
// refetch does not exist onError | ||
if (error) { | ||
handleUsernameErrorMessage(error.message); | ||
} | ||
}; | ||
|
||
// only change password not confirmPassword | ||
useEffect(() => { | ||
handleConfirmPasswordErrorMessage(validateConfirmPassword(password, confirmPassword)); | ||
}, [password, confirmPassword, handleConfirmPasswordErrorMessage]); | ||
|
||
const isFormValid = () => | ||
!emailError && | ||
!usernameError && | ||
!passwordError && | ||
!confirmPasswordError && | ||
email && | ||
username && | ||
password && | ||
confirmPassword; | ||
|
||
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => { | ||
e.preventDefault(); | ||
|
||
if (isFormValid()) { | ||
const response = await postSignup({ email, username, password }); | ||
|
||
if (!response.ok) { | ||
console.error(response); | ||
|
||
return; | ||
} | ||
|
||
navigate('/login'); | ||
} | ||
}; | ||
|
||
return { | ||
email, | ||
username, | ||
password, | ||
confirmPassword, | ||
errors: { | ||
email: emailError, | ||
username: usernameError, | ||
password: passwordError, | ||
confirmPassword: confirmPasswordError, | ||
}, | ||
handleEmailChange, | ||
handleUsernameChange, | ||
handlePasswordChange, | ||
handleConfirmPasswordChange, | ||
isFormValid, | ||
handleSubmit, | ||
handleEmailCheck, | ||
handleUsernameCheck, | ||
}; | ||
}; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
접근 권한을 제어하는 다른 방법도 있더라구요! 링크 첨부해요!
이와 같이
router.ts
에서 설정하면 각 페이지에서 인가 여부를 판단하지 않아도 됩니다!https://github.com/noveogroup-amorgunov/nukeapp/blob/main/src/app/appRouter.tsx