Skip to content

전남대 4팀 FE 6차 코드 리뷰#181

Merged
aaaaaattt merged 250 commits intomainfrom
release/2025-10-17
Oct 24, 2025
Merged

전남대 4팀 FE 6차 코드 리뷰#181
aaaaaattt merged 250 commits intomainfrom
release/2025-10-17

Conversation

@aaaaaattt
Copy link
Collaborator

📝작업 내용

[추가 기능 부분]

1. 운영진이 각 지원서에 대해 개별적으로 별점을 부여하는 기능을 추가

  1. 개인 별점 평가 컴포넌트 추가

  2. 수정 버튼을 누르면 이전에 부여한 별점이 표시되며 언제든지 수정 가능

  3. 댓글창 비활성화 로직 구현

  4. 댓글 글자 제한 500자 이내

image

2. (Oauth) 카카오 로그인 추가

  1. kakao login -> 2. 개인정보 동의 -> 3. redirection 페이지의 인가코드를 추출 후 백엔드에 전송 -> 4. 백엔드에서 kakao server에kakaoId 추출 후 DB에서 사용자 조회

위 1~4의 과정을 거친 후 DB에서 사용자를 조회한 후에, 조회 성공 여부에 따라 (1) 로그인 성공 / (2) 관리자 회원 가입 페이지로 분기하는 방식입니다.

5-1.로그인 성공 -> 5-2. 메인 페이지 이동

  • 로그인 성공 이후에는 accessToken과 refreshToken을 발급받는데, accessToken은 LocalStorage에 저장하고, refreshToken은 서버측에서 Set-Cookie 헤더를 통해 브라우저에 전달하며, HttpOnly 속성을 설정해 클라이언트 자바스크립트에서 접근하지 못하도록 저장합니다.

5-2.회원가입 필요 -> 5-3. 관리자 회원가입 페이지 이동

회원가입을 위해서 서버에서 전송해주는 temporaryToken의 응답을 sessionStorage에 저장 후 회원가입 form 제출 때 같이 제출합니다.
가입 성공 이후에는 역시 서버에서 발급해주는 AccessTokenLocalStorage에 저장합니다.

회원가입을 위한 temporaryToken은 일시적으로 필요하기 때문에 sessionStorage에 저장하도록 했고, AccessToken은 이후에 관리자 로그인 이후 서버와 통신할 때 header를 통해 보내야하기 때문에 sessionStorage에 저장했습니다.

refreshToken은 서버에서 응답으로 실어주면 client 요청에 자동으로 header의 cookie에 보내지는 것으로 판단해서 따로 저장하지 않았습니다.

image

3. toast notification 추가(Sonner 라이브러리)

제출 시 공통 toast 알림을 추가했고, 양식은 아래와 같습니다.
image
image

4. 상세 수정 페이지 저장 로직추가

  • [FEATURE] 상세 수정 페이지 저장 등 로직추가 (#136) #144
    저장 버튼 클릭 시 API 요청 연동 및 동작 구현
    VITE_API_BASE_URL 기준으로 백엔드와 연결
    저장 성공/실패 시 toast 알림 적용
    취소 버튼 클릭 시 대시보드 라우팅 처리 (이전 이슈 반영)
    회장 이름, 모집 일정 관련 요소 제거 및 수정 방법 안내 고안
    '동아리 짧은 소개' 데이터 수정 UI 추가 및 수정 로직 구현
    react-hook-form 기반 폼 로직과 useClubDetailEdit 훅 연동

5. husky 적용 및 기본설정

image

6. 활동후기 UI 구현

[개선 부분]

1. 인터뷰시간 선택 component 관심사 분리 적용 & useReducer를 통한 응집도 향상

  1. Props를 통해 받은 time 변환 util 함수
  2. formData 업데이트 hook
  3. 2번의 hook을 매개변수로 받는 마우스 드래그 customHook

위와 같이 3개로 분리를 해보았습니다.

const timeIntervalArray: [string, string][] = generateTimeIntervalArray(availableTime);
  const { updateScheduleData } = useUpdateFormValue();

  const { handleMouseDown, handleMouseMove, handleMouseUp, selectedTime } = useDragSelection(
    updateScheduleData,
    date,
    timeIntervalArray,
  );
  • [REFACTOR] 지원폼 제출 관심사 분리 적용 #164
    2차 시도 : 멘토님의 피드백 이후
    각 로직을 최대한 관계없이 모듈화처럼 독립적으로 나눠보려고 시도했으나 계획했던 것처럼 것처럼 아예 독립적으로 구현하기 힘들다고 판단이 되었습니다.(각 부분이 생각보다 결합을 끊을 수 없는 부분이 존재했습니다.)
    이후 useReducer를 통해 각 상태의 응집도를 높이고자 3개의 상태로 구분했습니다. MouseMove, MosueDown, MouseUp 3가지 action을 기반으로 여러 상태 변경을 이전보다 예측 가능하도록 수정해보았습니다.

2. 동아리 상세 페이지들 폼 구조 & 입력 검증 리팩토링

  1. msw로 목데이터 변경

  2. 폼 라이브러리 통일

  3. 입력값 검증 및 제한 추가

location=20자 이하 /필수 
presidentPhoneNumber=형식제한(000-0000-0000) /선택필드 
applicationNotice = 100자 이하 / 선택필드 
introductionOverview, introductionActivity, introductionIdeal = 1200자 이하 /필수
  • 연락처 입력란 → 전화번호 형식 검증
  • 잘못된 형식일 경우 에러 메시지 표시
스크린샷 2025-10-07 오후 10 14 56 스크린샷 2025-10-07 오후 10 15 09 스크린샷 2025-10-07 오후 10 20 11

4. 입력폼 UI 개선

개선 전 개선 후
개선 전 개선 후
  • 필수데이터 표식 추가
스크린샷 2025-10-07 오후 10 11 05

5. Vite 프록시 도입을 통한 개발 환경 개선 및 API 호출 리팩터링

로컬 개발 환경에서 백엔드 API 호출 시 발생하는 CORS 오류 해결

1. Vite 프록시 설정 추가 (vite.config.ts)
server.proxy 옵션을 사용하여 /api 경로로 들어오는 모든 요청을 백엔드 서버로 전달

  server: {
      proxy: {
        '/api': {
          target: env.VITE_API_BASE_URL,
          changeOrigin: true,
          secure: false,
        },
      },
    },

2. API 호출 방식 전체 리팩터링
기존에 import.meta.env.VITE_API_BASE_URL을 직접 사용하던 모든 fetch 함수를 프록시를 통하는 상대 경로(/api/...)를 사용하도록 수정

변경 전

const response = await fetch(

import.meta.env.VITE_API_BASE_URL + `/clubs/${clubId}/applicants/${applicantId}/application`,
);

상대경로 url에서 절대경로로 수정

// 수정 후
  const url = ${import.meta.env.VITE_API_BASE_URL}/clubs/${clubId}/dashboard/applicants;

6. 대시보드 지원자 목록 실시간 동기화 및 필터링 개선

react-query의 useQuery를 사용하여 전체 지원자 목록 API(api/clubs/{clubId}/dashboard)를 호출
refetchInterval 옵션을 30초로 설정하여, 사용자가 페이지에 머무는 동안 백그라운드에서 주기적으로 최신 데이터를 가져오도록 구현
클라이언트 사이드 필터링으로 전환

기존의 필터링 방식을 변경하여, 폴링으로 가져온 전체 데이터를 기반으로 프론트엔드에서 필터링
useState로 현재 필터 상태('전체', '합격' 등)를 관리하고, useMemo를 사용하여 필터링 연산이 필요할 때만 실행

7. github action을 통한 자동 배포 branch 변경 main -> dev

백엔드와 배포 간격의 통일성을 최대한 맞춰보기 위해 변경하게 되었습니다.

Ajin and others added 30 commits October 3, 2025 20:42
- 각 별의 왼쪽/오른쪽 클릭으로 0.5 단위 평가 가능
- 마우스 호버 시 반개 단위 미리보기 제공
- component 이름변경
- Drag 상태와 마우스 클릭 상태 useRef -> useState 변경
- type name 중복
- mouseUp event 발생 -> field Value 갱신
- 비즈니스/상태변환 & UI 부분으로 분리
: 드래그 종료 동작 handler 제거
aaaaaattt and others added 19 commits October 15, 2025 15:55
…ogin

[FEAT] 관리자 Oauth 로그인 관련 Redirect 추가
- 로그인 성공 -> accessToken SessionStorage 관리
- 회원 가입 필요 -> temporary Token SessionStorage 관리
- Login , Register 부분 우선 추가
…orkflow-trigger

[FIX] - workflow trigger branch 변경
- accessToken : localStorage
- temporaryToken : sessionStorage
…ogin

[FEAT] OAuth 로그인 인가 코드 전송 후 회원가입/로그인 성공 분기 작업
@aaaaaattt aaaaaattt self-assigned this Oct 17, 2025
@aaaaaattt aaaaaattt added 🛠️ Refactor 리팩터링 이슈 ✨ Feature 기능 구현 이슈 ⚙️ chore 설정 및 기타 이슈 labels Oct 17, 2025
@gemini-code-assist
Copy link
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

Comment on lines +26 to +40
export const ToastProvider = () => (
<Toaster
position='top-center'
richColors
toastOptions={{
style: {
borderRadius: theme.radius.md,
padding: '12px 20px',
fontWeight: theme.font.weight.bold,
fontSize: theme.font.size.base,
boxShadow: theme.shadow.md,
},
}}
/>
);
Copy link

Choose a reason for hiding this comment

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

[확인] App.tsx와 main.tsx의 각 역할은 무엇일까요? App.tsx가 앱 구성을 위해 사용된다면 ToastProvider는 여기에 속하지 않는지 고민해 볼만한 것 같습니다.

}
} catch (e) {
const error = e as AxiosError<ErrorResponse>;
return new Error(error.response?.data.message);
Copy link

Choose a reason for hiding this comment

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

[확인] 에러 처리하는 부분에서 return의 용도와 에러 알림이 필요한 부분이 아닌지 확인이 필요해 보입니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

새로운 Error 객체를 반환하면 호출한 쪽에서 받는다고 잘못 생각했던 것 같습니다.
에러 처리에서 return과 throw 처리를 구분해서 사용해야할 것 같습니다.

[질문] 멘토님 추가로 질문이 있는데, 에러 처리는 반드시 명시적으로 사용자가 원인을 알 수 있도록 UI를 통해 보여줘야하는 건가요?

Copy link

Choose a reason for hiding this comment

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

무엇을 피드백할지는 구성원들의 합의하에 결정되므로 예, 아니오로 대답할 수 없는 부분입니다. 여러분들이 사용자 액션 실패에 대해서만 피드백할 수도 있고 아닐수도 있는 사항이기 때문에 실제 본인들이 앱을 사용할 때의 사용성을 생각해서 결정하시면 될 것 같습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

그렇군요 감사합니다!

}
};
fetchToken();
}, [navigate]);
Copy link

Choose a reason for hiding this comment

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

[확인] 좀 더 명확한 의존성 배열 선언이 필요해 보입니다. fetchToken은 한번만 실행되어야 하는 함수처럼 보이는데요.
빈 배열을 사용하던지, 훅 내부에서 의존하는 setAccessToken, setTemporaryToken 모두 명시하던지 고민이 필요해 보여요.

Comment on lines +10 to +13
const { handleMouseDown, handleMouseMove, handleMouseUp, states } = useDragSelection(
date,
timeSlotsArray,
);
Copy link

Choose a reason for hiding this comment

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

👍👍👍👍

newPreviousIndexDiffSign: number | null;
};

export const updateDragState = (
Copy link

Choose a reason for hiding this comment

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

[확인] 도메인이라는 개념이 헷갈리실 것 같아요. drag는 동아리 도메인 어디에도 속하지 않는 기술적인 내용이므로 domain 폴더 하위에 두는 건 적절해 보이지 않습니다😂

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

위 로직은 동아리 지원 페이지 내부에서만 사용되고, useDragSelection 훅 내부에서 actions 객체를 통해 전달된 값을 매개변수로 받으면서 mouse의 이동방향 변화를 감지하는 로직인데, 분명히 독립적으로 존재할 수는 없지만 이 페이지 내부에서만 적용되는 규칙이 적용되는 로직이라고 생각해서 domain 폴더로 생각했었습니다.
[질문] 위와 같이 추출된 함수는 일반적으로 어떻게 관리할 수 있을까요? 따로 어떤 폴더구조를 통해 관리할 수 있을까요? 아니면 추출한 위치의 하단에 그대로 정의하는 방식이 맞는지 생가긍ㄹ 하게 만드는 부분인 것 같습니다.

Copy link

Choose a reason for hiding this comment

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

도메인은 흔히 예로 드는 쇼핑몰로 보면 상품, 댓글, 결제, 정상 등을 도메인이라고 할 수 있습니다.
이 영역 안에서의 지식을 알고 있는 것들이 도메인 폴더 하위에 위치하는 게 일반적입니다.
drag와 같은 모듈을 어떻게 관리할지는 단순 결정의 문제이고 중요한 것은 도메인 하위로 취급하면 안된다는 것만 이해하시면 될 것 같습니다.

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 36 to 40
updateComment({
commentId,
content: editedContent,
rating,
rating: editedRating,
});
Copy link

Choose a reason for hiding this comment

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

[의견] 댓글 글자 제한이 적용된 것에 맞춰서 수정시에도 이를 확인하면 좋을 것 같습니다.

Comment on lines +29 to +35
const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
if (disabled) {
e.preventDefault();
return;
}
e.preventDefault();
navigate(to);
Copy link

Choose a reason for hiding this comment

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

[의견] 위치를 조정하면 preventDefault의 중복 작성을 개선할 수 있을 것 같아요.

Copy link
Contributor

Choose a reason for hiding this comment

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

피드백 주신 부분을 반영하여, preventDefault 호출 중복을 제거해야 될 것 같습니다 감사합니다!

@@ -0,0 +1,5 @@
export const setAccessToken = (token: string) => localStorage.setItem('accessToken', token);
export const getAccessToken = () => localStorage.getItem('accessToken');
export const getRefreshToken = () => localStorage.getItem('refreshToken');
Copy link

Choose a reason for hiding this comment

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

[확인] getRefreshToken는 설계 내용가 맞지 않는 함수 같습니다?

Comment on lines +15 to +17
<Button to='/signup' width='100%' type='button'>
{'회원가입'}
</Button>
Copy link

Choose a reason for hiding this comment

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

[확인] 설계 흐름상 회원가입을 위해서는 토큰이 필요한 것으로 보이는데, 현재 구현을 보면 해당 버튼을 통해서 사용자가 직접 회원가입을 할 수 없을 것 같아요.

Comment on lines +9 to +13
interface LoginSuccessResponse {
status: 'LOGIN_SUCCESS';
accessToken: string;
refreshToken: string;
}
Copy link

Choose a reason for hiding this comment

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

[확인] 설계와 맞지 않는 부분이 있는 것 같습니다. refreshToken는 쿠키에 설정되는 것으로 타입에서는 제거되어야 하지 않을까 합니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

얘기해주신 것처럼 refreshToken을 불러오는 부분과 일부 type에 반영된 부분의 삭제가 필요해 보입니다.

@aaaaaattt aaaaaattt merged commit 3bd06d2 into main Oct 24, 2025
5 checks passed
@aaaaaattt aaaaaattt deleted the release/2025-10-17 branch October 24, 2025 16:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚙️ chore 설정 및 기타 이슈 ✨ Feature 기능 구현 이슈 🛠️ Refactor 리팩터링 이슈

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants