Skip to content

Conversation

ToKyun02
Copy link
Collaborator

요구사항

회원가입

  • 유효한 정보를 입력하고 스웨거 명세된 “/auth/signUp”으로 POST 요청해서 성공 응답을 받으면 회원가입이 완료됩니다.
  • 회원가입이 완료되면 “/login”로 이동합니다.
  • 회원가입 페이지에 접근시 로컬 스토리지에 accessToken이 있는 경우 ‘/’ 페이지로 이동합니다.

로그인

  • 회원가입을 성공한 정보를 입력하고 스웨거 명세된 “/auth/signIp”으로 POST 요청을 하면 로그인이 완료됩니다.
  • 로그인이 완료되면 로컬 스토리지에 accessToken을 저장하고 “/” 로 이동합니다.
  • 로그인/회원가입 페이지에 접근시 로컬 스토리지에 accessToken이 있는 경우 ‘/’ 페이지로 이동합니다.

메인

  • 로컬 스토리지에 accessToken이 있는 경우 상단바 ‘로그인’ 버튼이 판다 이미지로 바뀝니다.

주요 변경사항

  • 홈페이지 레이아웃 구현
  • 회원가입 기능 구현
  • 로그인 기능 개선
  • 토큰 재요청 로직 간소화

스크린샷

배포 웹사이트

멘토에게

  • 회원가입 완료 후 로그인 페이지로 이동시키는 것 보다 홈페이지로 이동시키는게 좋다고 생각하여 요구사항 내용에서 수정했습니다.
  • accessToken 존재 시 로그인/회원가입 페이지 접속할 때 리다이렉트 하는 로직은 깜빡하고 구현안했습니다.
  • 로그인 상태는 전역 상태 관리 라이브러리(zustand)의 store에서 관리했습니다.
  • 지난 번 멘토링 때 얘기해 주신 axios의 intercepts를 활용하여 기존 fetch로 구현했던 것보다 재사용성을 조금 더 높혔습니다.

@ToKyun02 ToKyun02 requested a review from Lanace January 25, 2025 03:04
@ToKyun02 ToKyun02 self-assigned this Jan 25, 2025
@ToKyun02 ToKyun02 added the 매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다. label Jan 25, 2025
@ToKyun02 ToKyun02 changed the title Next 김도균 sprint11 [김도균] Sprint11 Jan 25, 2025
Copy link
Collaborator

@Lanace Lanace left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

전반적으로 코드를 깔끔하게 유지하고 관리하실 수 있도록 일괄되게 짜는게 좋네요ㅎㅎ

이런 습관은 계속 들이시면 좋아요!ㅋㅋ

설 연휴인데 고생많으셨구요~!!

import { AxiosError } from 'axios';

export async function submitArticle(formData: FormData, accessToken: string | null, refreshToken: string | null) {
interface submitArticleResponse {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

가급적이면 interface 나 type 의 이름의 시작은 대문자가 좀더 좋을것같아요~

SubmitArticleResponse 정도로요ㅎㅎ

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

급하게 하느라고 소문자로 작성했네요... 이 부분 고려해서 수정하겠습니다.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

동작하는데엔 큰 문제는 없긴 하지만 가능하면 챙겨주면 좋아요ㅎㅎ!

id?: number;
}

export async function submitArticle(formData: FormData, accessToken: string | null, refreshToken: string | null): Promise<submitArticleResponse> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formData로 데이터를 받으면 처음 이 함수를 보는 사람은 어떤 값을 포함해서 보내야 하는지 알기가 조금 어려워요ㅠ

차라리 email / password를 각각 받거나 SubmitArticleRequest 타입을 새로 정의해서 인자를 받고,
request body로 넘겨주는건 어떨까요?ㅎㅎ

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

멘토님 의견이 더 좋다고 생각합니다! 다음에 적용해보겠습니다.

try {
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/articles`, {
method: 'POST',
const response = await apiHelper.post<Article | ResponseWithAccessToken<Article>>('/articles', formDataObject, {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게 명시적으로 타입을 지정해주는거 너무 좋아요...!

근데 response로 Article | ResponseWithAccessToken

가 오나요...?
둘중 하나로만 올것같긴 해서... ResponseWithAccessToken로 오는경우는 어떤경우인지 싶어요

Comment on lines +28 to +30
if ('accessToken' in response.data) {
const newAccessToken = response.data.accessToken as string;
result.accessToken = newAccessToken;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아~ 실제로 보내주는 경우가 진짜 있나보네요...!
스웨거 문서에는 일단 안보여서 그런경우를 몰랐는데, 이렇게 온다고 하면 처리 잘 하셨네요ㅎㅎ!

return { success: true, message: '게시글 생성이 완료되어 3초 후 페이지를 이동합니다.', id };
const result: submitArticleResponse = { success: true, message: '게시글 생성이 완료되어 3초 후 페이지를 이동합니다.', id: response.data.id };
if ('accessToken' in response.data) {
const newAccessToken = response.data.accessToken as string;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

근데 이경우엔 강제로 타입을 캐스팅 하지 않아도 값이 추론되지 않을까 싶긴 한데,
만약 추론이 된다면 따로 as를 사용해서 강제 캐스팅은 안해도 될것같아요...!

강제캐스팅은 좋은 패턴은 아니거든요ㅠ

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

타입을 axios 관련 함수를 다 작성한 후에 적용해서 잠시 as로 놓았던 것 같습니다. 이 부분 참고하겠습니다!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

app router에 (...) 도 잘 활용하셨네요

헤더가 있는 부분이나 인증쪽을 묶은것도 그렇구요ㅎㅎ

Comment on lines +10 to +16
interface Valid {
email: string;
password: string;
isEmailValid: boolean;
isPasswordValid: boolean;
isValid: boolean;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네이밍이 조금더 구체적이면 좋을것같아요...!

Valid 보단 LoginDataWithValid나 AuthData 같이요...ㅎㅎ

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네이밍에 조금 더 신경쓰겠습니다!

Comment on lines +34 to +40
const validateInputs = (email: string, password: string) => {
const isEmailValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
const isPasswordValid = password.length >= 8;
const isValid = isEmailValid && isPasswordValid;
return { isEmailValid, isPasswordValid, isValid };
};

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요건 아에 따로 컴포넌트 밖에 있어도 괜찮을것같은데요...?ㅎㅎ

의존성도 따로 없어서 utils로 빼도 되구요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

적용하겠습니다!

return { isEmailValid, isPasswordValid, isValid };
};

const handleInputChange = (field: 'email' | 'password', value: string) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

field를 email과 password로 추린거 좋네요!

이런식으로 원치않는 값이 넘어오는걸 빌드타임때 알 수 있도록 하는게 좋아요~

Comment on lines +16 to +22
export const useAuthStore = create<Store>((set) => ({
accessToken: getLocalStorageItem('accessToken', ''),
setAccessToken: (accessToken: string) => {
localStorage.setItem('accessToken', accessToken);
set((state) => ({ ...state, accessToken }));
},
}));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zustand에서 자체적으로 localstorage에 저장해주는 기능을 지원해요ㅎㅎ!

https://zustand.docs.pmnd.rs/middlewares/persist#persist

persist인데, localstorage나 sessionstorage 등 저장소랑 동기화 해주니 이걸 사용하시는게 좀더 좋을것같아요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이러한 기능이 있는지 처음 알았네요. 말씀해주신 부분 참고하여 적용해보겠습니다!

@Lanace Lanace merged commit 38932e9 into codeit-bootcamp-frontend:Next-김도균 Jan 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants