Skip to content

[release] FE v1.5.0#1386

Merged
seongwon030 merged 182 commits intomainfrom
develop-fe
Apr 5, 2026
Merged

[release] FE v1.5.0#1386
seongwon030 merged 182 commits intomainfrom
develop-fe

Conversation

@seongwon030
Copy link
Copy Markdown
Member

@seongwon030 seongwon030 commented Apr 5, 2026

🚀 릴리즈 PR

📦 버전 정보

항목 내용
서비스 💾 BE / 💻 FE
Bump 타입 🚨 MAJOR / ➕ MINOR / 🔧 PATCH
예상 버전 vX.Y.Z

⚠️ 반드시 라벨을 지정해주세요: 서비스 라벨(💾 BE, 💻 FE)과 버전 라벨(🚨 MAJOR, ➕ MINOR, 🔧 PATCH)이 없으면 태그가 생성되지 않습니다.

📖 버전 라벨 선택 가이드 (Semantic Versioning)
라벨 버전 변화 선택 기준 예시
🚨 MAJOR v1.0.0v2.0.0 기존 API/기능이 호환되지 않는 변경 API 엔드포인트 삭제/변경, 요청/응답 스펙 변경, DB 스키마 대규모 변경
➕ MINOR v1.0.0v1.1.0 기존 기능은 유지하면서 새 기능 추가 새 API 엔드포인트 추가, 새 기능 도입, 기존 API에 선택적 필드 추가
🔧 PATCH v1.0.0v1.0.1 기능 변경 없이 버그 수정/내부 개선 버그 수정, 성능 개선, 리팩토링, 문서 수정

📋 포함된 변경사항

이번 릴리즈에 포함된 주요 변경사항을 요약합니다.

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 홍보·이벤트 페이지 및 상세 뷰 추가
    • Google Calendar 및 Notion 캘린더 동기화 기능 (관리자 페이지)
    • 동아리 일정 캘린더 표시 기능
    • 홍보 게시판 알림 기능 추가
  • UI 개선

    • 헤더 네비게이션 리뉴얼 (관리자 페이지 → 홍보·이벤트)
    • 푸터에 운영 페이지 버튼 추가
    • 홍보 카드 및 상세 페이지 UI 개선
  • 문서

    • 개발 프로세스 및 테스트 작성 가이드 추가

suhyun113 added 30 commits March 3, 2026 23:12
- 응답에 유효한 데이터베이스 ID가 있는 경우 선택한 Notion 데이터베이스 ID를 설정
- Google Calendar 관련 쿼리 키 및 훅 추가
- Google Calendar 캘린더 및 이벤트 조회 기능 구현
- 선택된 캘린더 관리 및 연결 해제 기능 추가
- 기존 코드 리팩토링 및 상태 관리 개선
- 요청 ID를 사용하여 이전 요청의 응답을 무효화하는 로직 추가
- 페이지 로드 및 데이터베이스 페이지 적용 시 요청 ID 확인 추가
- useRef를 사용하여 초기화 상태를 추적하는 로직 추가
- notionCalendarEvents가 비어있거나 초기화가 완료된 경우 useEffect에서 조기 반환하도록 수정
- 오류 발생 시 사용자에게 명확한 메시지 제공
- 세션 스토리지에서 상태 키 제거 및 URL에서 OAuth 매개변수 정리 로직 추가
- useRef를 사용하여 초기화 상태를 추적하는 로직 추가
- allUnifiedEvents가 비어있거나 초기화가 완료된 경우 useEffect에서 조기 반환하도록 수정
- activeTab 상태를 useMemo를 사용하여 계산하도록 변경
- SCHEDULE 탭에서 캘린더 이벤트가 없을 경우 기본 탭으로 돌아가는 useEffect 추가
- 코드 가독성을 높이기 위해 불필요한 초기화 로직 제거
- useRef를 사용하여 초기화 상태를 추적하는 로직 추가
- parsedEvents가 비어있거나 초기화가 완료된 경우 useEffect에서 조기 반환하도록 수정
- 캘린더의 첫 번째 이벤트에 따라 기본 선택 날짜 및 표시 월 설정 로직 추가
- 클럽 이름을 URL에 안전하게 포함하기 위해 encodeURIComponent 사용
- articles 배열에서 유효한 timestamp를 필터링하여 최대값을 반환하도록 수정
- 빈 timestamps 배열에 대한 처리를 추가하여 0을 반환하도록 변경
- globalThis.crypto.randomUUID가 지원되지 않는 경우를 대비하여 대체 로직 추가
- Uint8Array를 사용하여 안전한 난수 문자열 생성 방식으로 변경
- 날짜 배열 생성을 위한 로직을 addDays 함수를 사용하여 간결하게 변경
- 코드 가독성을 높이기 위해 불필요한 변수 제거
- 유효하지 않은 날짜 문자열에 대한 처리를 추가하여 NaN 반환 시 빈 문자열을 반환하도록 수정
- 기본 정렬 로직을 수정하여 aStart와 bStart의 차이를 반환하도록 변경하여 정렬 정확도 향상
- hasCalendarEvents를 hasCalendarConnection으로 변경하여 클럽의 캘린더 연결 상태를 정확하게 반영
- useRef를 사용하여 캘린더 데이터 로딩 상태를 추적하는 hasLoadedOnce 추가
- isInitialChecking 상태를 개선하여 초기 로딩 후에만 true로 설정되도록 수정
- clubId 또는 clubName을 key prop으로 사용하여 컴포넌트의 재렌더링 최적화
@seongwon030 seongwon030 mentioned this pull request Apr 5, 2026
Copy link
Copy Markdown
Collaborator

@suhyun113 suhyun113 left a comment

Choose a reason for hiding this comment

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

릴리즈 고고고

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (1)

74-77: ⚠️ Potential issue | 🔴 Critical

이전 지적이 아직 남아 있습니다: 일정 조회에는 실제 clubId만 전달해야 합니다.

Line 75의 clubName fallback 때문에 이름 기반 URL에서는 /api/club/{clubName}/calendar-events 요청이 나갑니다. getClubCalendarEvents는 ID 기반 엔드포인트라 이 경우 일정 탭이 그대로 깨집니다. 최소한 여기서는 clubName fallback를 제거하고 ID만 넘겨야 하고, 이름만 있는 경로도 지원해야 한다면 먼저 실제 ID를 해석한 뒤 그 값을 사용해야 합니다.

최소 수정 예시
 const { data: calendarEvents = [] } = useGetClubCalendarEvents(
-  (clubName ?? clubId) || '',
+  clubId || '',
   { enabled: hasCalendarConnection && activeTab === TAB_TYPE.SCHEDULE },
 );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx` around lines 74 - 77,
The calendar fetch is incorrectly falling back to clubName; update the
useGetClubCalendarEvents call to pass only the numeric clubId (remove the
clubName ?? clubId fallback) so the ID-based endpoint is used: change the first
argument to clubId || '' and keep the options check (hasCalendarConnection &&
activeTab === TAB_TYPE.SCHEDULE); if routes can provide name-only, implement a
preceding resolution step that maps clubName to clubId before calling
useGetClubCalendarEvents (resolve the ID first, then call
useGetClubCalendarEvents with the resolved ID).
🧹 Nitpick comments (3)
frontend/src/pages/AdminPage/tabs/CalendarSyncTab/hooks/useNotionCalendarData.ts (1)

124-126: loadNotionPages 의존성으로 인해 불필요한 재실행 가능성이 있습니다.

loadNotionPagesonError 콜백에 의존하므로, 부모 컴포넌트에서 onError가 매 렌더마다 새로 생성되면 이 효과가 반복 실행될 수 있습니다. 초기 로드가 한 번만 실행되도록 보장하려면 ref 플래그를 사용하는 것을 고려해 보세요.

초기 로드 한 번만 실행하도록 수정
+  const didInitialLoadRef = useRef(false);
+
   useEffect(() => {
+    if (didInitialLoadRef.current) return;
+    didInitialLoadRef.current = true;
     loadNotionPages();
   }, [loadNotionPages]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/src/pages/AdminPage/tabs/CalendarSyncTab/hooks/useNotionCalendarData.ts`
around lines 124 - 126, The effect currently depends on loadNotionPages which
can change when its onError callback changes, causing unnecessary re-runs;
modify useNotionPages usage so the initial load runs only once by adding a ref
guard: inside useEffect check a useRef flag (e.g., hasLoadedRef) and call
loadNotionPages() only if not set, then set the flag, and keep the effect deps
minimal (empty array) or ensure loadNotionPages is stable via useCallback;
reference the loadNotionPages function and the onError dependency when making
the change.
frontend/src/utils/calendarSyncUtils.ts (1)

168-205: Notion 속성 파싱에서 타입 안전성 개선을 권장합니다.

Object.values(properties)Array<Record<string, unknown>>으로 캐스팅하고 있습니다. NotionSearchItem.propertiesRecord<string, unknown>으로 정의되어 있어 현재 코드가 동작하지만, 속성 값의 구조가 예상과 다를 경우 런타임에 조용히 실패합니다.

타입 가드 함수 추출 제안
+interface NotionDateProperty {
+  type: 'date';
+  date: { start: string; end?: string | null };
+}
+
+interface NotionTitleProperty {
+  type: 'title';
+  title: Array<{ plain_text?: string }>;
+}
+
+const isDateProperty = (prop: unknown): prop is NotionDateProperty =>
+  typeof prop === 'object' &&
+  prop !== null &&
+  (prop as Record<string, unknown>).type === 'date' &&
+  typeof ((prop as Record<string, unknown>).date as Record<string, unknown>)?.start === 'string';
+
+const isTitleProperty = (prop: unknown): prop is NotionTitleProperty =>
+  typeof prop === 'object' &&
+  prop !== null &&
+  (prop as Record<string, unknown>).type === 'title' &&
+  Array.isArray((prop as Record<string, unknown>).title);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/utils/calendarSyncUtils.ts` around lines 168 - 205, The parsing
in parseNotionCalendarEvent uses a blanket cast of Object.values(properties) to
Array<Record<string, unknown>> which is unsafe; replace the cast by adding
lightweight type-guard checks when locating the date and title properties (e.g.,
validate property.type === 'date' && typeof property.date === 'object' && typeof
property.date.start === 'string' for dateProperty, and property.type === 'title'
&& Array.isArray(property.title) && every segment has typeof segment.plain_text
=== 'string' for titleProperty), ensure parseDateKey(date) is only called after
these guards and return null when shapes don't match; update references to
dateProperty, titleProperty, parseNotionCalendarEvent and
NotionSearchItem.properties accordingly so the function fails fast and
type-safely instead of relying on a cast.
frontend/src/pages/AdminPage/tabs/CalendarSyncTab/hooks/useGoogleCalendarData.ts (1)

34-40: eventTimeRange가 마운트 시점에 고정됩니다.

useMemo의 의존성 배열이 비어 있어 컴포넌트가 오래 마운트되어 있으면 시간 범위가 업데이트되지 않습니다. 일반적인 사용에서는 문제가 없지만, 장기 세션에서 이벤트 조회 범위가 stale해질 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/src/pages/AdminPage/tabs/CalendarSyncTab/hooks/useGoogleCalendarData.ts`
around lines 34 - 40, eventTimeRange is being memoized with an empty dependency
array so it never updates after mount; replace the useMemo block with a plain
computed value (i.e., remove useMemo and just compute const eventTimeRange = {
timeMin: ..., timeMax: ... } using a fresh new Date() on each render) so the
date range reflects the current time, or alternatively add a relevant dependency
(e.g., a currentTime state/prop) if you want controlled updates; update the code
around the eventTimeRange constant in useGoogleCalendarData accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/.eslintignore`:
- Line 1: The repository currently uses a deprecated .eslintignore file
(frontend/.eslintignore) which ESLint 9.x ignores; replace this by adding an
eslint.config.js that exports a flat config array and include an entry with the
ignores property to match 'public/mockServiceWorker.js' (e.g. export default [{
ignores: ['public/mockServiceWorker.js'] }, ...]); remove the legacy
.eslintignore or keep it cleared so the ignore is enforced via the new
eslint.config.js and ensure your CI/linter picks up eslint.config.js.

---

Duplicate comments:
In `@frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx`:
- Around line 74-77: The calendar fetch is incorrectly falling back to clubName;
update the useGetClubCalendarEvents call to pass only the numeric clubId (remove
the clubName ?? clubId fallback) so the ID-based endpoint is used: change the
first argument to clubId || '' and keep the options check (hasCalendarConnection
&& activeTab === TAB_TYPE.SCHEDULE); if routes can provide name-only, implement
a preceding resolution step that maps clubName to clubId before calling
useGetClubCalendarEvents (resolve the ID first, then call
useGetClubCalendarEvents with the resolved ID).

---

Nitpick comments:
In
`@frontend/src/pages/AdminPage/tabs/CalendarSyncTab/hooks/useGoogleCalendarData.ts`:
- Around line 34-40: eventTimeRange is being memoized with an empty dependency
array so it never updates after mount; replace the useMemo block with a plain
computed value (i.e., remove useMemo and just compute const eventTimeRange = {
timeMin: ..., timeMax: ... } using a fresh new Date() on each render) so the
date range reflects the current time, or alternatively add a relevant dependency
(e.g., a currentTime state/prop) if you want controlled updates; update the code
around the eventTimeRange constant in useGoogleCalendarData accordingly.

In
`@frontend/src/pages/AdminPage/tabs/CalendarSyncTab/hooks/useNotionCalendarData.ts`:
- Around line 124-126: The effect currently depends on loadNotionPages which can
change when its onError callback changes, causing unnecessary re-runs; modify
useNotionPages usage so the initial load runs only once by adding a ref guard:
inside useEffect check a useRef flag (e.g., hasLoadedRef) and call
loadNotionPages() only if not set, then set the flag, and keep the effect deps
minimal (empty array) or ensure loadNotionPages is stable via useCallback;
reference the loadNotionPages function and the onError dependency when making
the change.

In `@frontend/src/utils/calendarSyncUtils.ts`:
- Around line 168-205: The parsing in parseNotionCalendarEvent uses a blanket
cast of Object.values(properties) to Array<Record<string, unknown>> which is
unsafe; replace the cast by adding lightweight type-guard checks when locating
the date and title properties (e.g., validate property.type === 'date' && typeof
property.date === 'object' && typeof property.date.start === 'string' for
dateProperty, and property.type === 'title' && Array.isArray(property.title) &&
every segment has typeof segment.plain_text === 'string' for titleProperty),
ensure parseDateKey(date) is only called after these guards and return null when
shapes don't match; update references to dateProperty, titleProperty,
parseNotionCalendarEvent and NotionSearchItem.properties accordingly so the
function fails fast and type-safely instead of relying on a cast.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c287ee46-6781-4092-a0c3-c81625867db2

📥 Commits

Reviewing files that changed from the base of the PR and between 5527141 and 3adb2fc.

📒 Files selected for processing (18)
  • frontend/.eslintignore
  • frontend/CLAUDE.md
  • frontend/src/constants/queryKeys.ts
  • frontend/src/hooks/Queries/useGoogleCalendar.ts
  • frontend/src/pages/AdminPage/tabs/CalendarSyncTab/CalendarSyncTab.styles.ts
  • frontend/src/pages/AdminPage/tabs/CalendarSyncTab/hooks/useGoogleCalendarData.ts
  • frontend/src/pages/AdminPage/tabs/CalendarSyncTab/hooks/useNotionCalendarData.ts
  • frontend/src/pages/AdminPage/tabs/CalendarSyncTab/hooks/useNotionCalendarUiState.ts
  • frontend/src/pages/AdminPage/tabs/CalendarSyncTab/hooks/useNotionOAuth.ts
  • frontend/src/pages/AdminPage/tabs/CalendarSyncTab/hooks/useUnifiedCalendarUiState.ts
  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx
  • frontend/src/pages/ClubDetailPage/components/ClubScheduleCalendar/ClubScheduleCalendar.tsx
  • frontend/src/pages/PromotionPage/components/detail/PromotionClubCTA/PromotionClubCTA.tsx
  • frontend/src/pages/PromotionPage/utils/promotionNotification.ts
  • frontend/src/pages/PromotionPage/utils/sortPromotions.ts
  • frontend/src/types/club.ts
  • frontend/src/utils/calendarSyncUtils.ts
  • frontend/src/utils/formatKSTDateTime.ts
💤 Files with no reviewable changes (1)
  • frontend/CLAUDE.md
✅ Files skipped from review due to trivial changes (3)
  • frontend/src/pages/AdminPage/tabs/CalendarSyncTab/CalendarSyncTab.styles.ts
  • frontend/src/pages/PromotionPage/utils/promotionNotification.ts
  • frontend/src/utils/formatKSTDateTime.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • frontend/src/pages/PromotionPage/components/detail/PromotionClubCTA/PromotionClubCTA.tsx
  • frontend/src/types/club.ts
  • frontend/src/constants/queryKeys.ts
  • frontend/src/pages/PromotionPage/utils/sortPromotions.ts
  • frontend/src/pages/AdminPage/tabs/CalendarSyncTab/hooks/useNotionCalendarUiState.ts
  • frontend/src/pages/ClubDetailPage/components/ClubScheduleCalendar/ClubScheduleCalendar.tsx

@seongwon030 seongwon030 merged commit 83cb37b into main Apr 5, 2026
4 of 5 checks passed
@seongwon030 seongwon030 changed the title Develop fe [release] FE v1.5.0 Apr 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💻 FE Frontend ➕ MINOR Minor 릴리즈 📈 release 릴리즈 배포

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants