Conversation
* feat: 모집 공고에 시작, 종료 시간 추가 * feat: 지원자 목록 정렬 추가 및 시간 표시 * feat: 채팅, 모집 공고 링크 파싱 기능 추가 * feat: 인스타그램 파싱 추가 * fix: prettier error * refactor: 유틸 분리 및 타입 변경 * chore: 타입 변경 반영 * chore: 동일 스타일 통합 및 entity 확장 사용
This reverts commit 98570fc.
This reverts commit 3a56f4a.
* refactor: 푸시 알림 토큰 로직 브릿지 방식으로 수정 * fix: try catch 적용
📝 WalkthroughWalkthroughReact Native WebView 브릿지를 통해 토큰 갱신 및 인증 상태 메시지를 전달하도록 클라이언트 API를 수정했습니다. 클럽 API 엔티티를 리팩토링하여 날짜 필드명을 표준화(startDate/endDate → startAt/endAt)하고, 지원자 목록에 페이지네이션을 추가했습니다. 새로운 LinkifiedText 컴포넌트를 도입하여 메시지와 모집공고에서 URL 및 Instagram 핸들을 자동으로 링크 처리합니다. 지원자 관리 페이지에 페이지네이션 UI를 구현하고, 모집 기간 설정에 시간 선택 기능(TimePicker)을 추가했습니다. 관련 날짜/시간 유틸리티 함수와 훅들도 함께 추가되었습니다. Possibly related PRs
🚥 Pre-merge checks | ✅ 1 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/pages/Manager/ManagedRecruitmentWrite/index.tsx (1)
166-168:⚠️ Potential issue | 🟡 Minor기존 이미지에
URL.revokeObjectURL호출
isExisting이미지의previewUrl은 서버 URL이므로revokeObjectURL을 호출해도 실제로는 no-op이지만, 의미론적으로 blob URL이 아닌 URL에는 호출하지 않는 것이 올바릅니다.🛡️ 수정 제안
const handleDeleteImage = () => { - URL.revokeObjectURL(images[currentImageIndex].previewUrl); + const img = images[currentImageIndex]; + if (!img.isExisting) URL.revokeObjectURL(img.previewUrl); const newImages = images.filter((_, index) => index !== currentImageIndex);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Manager/ManagedRecruitmentWrite/index.tsx` around lines 166 - 168, handleDeleteImage에서 images[currentImageIndex].previewUrl에 무조건 URL.revokeObjectURL을 호출하고 있는데, 서버 URL(기존 이미지)의 경우 blob이 아니므로 revoke를 호출하지 않도록 변경하세요: handleDeleteImage 내부에서 현재 이미지 객체(images[currentImageIndex])의 isExisting 플래그(또는 previewUrl.startsWith('blob:') 검사)를 사용해 blob URL일 때만 URL.revokeObjectURL를 호출하고, isExisting인 경우에는 revoke를 건너뛰고 기존 로직(newImages 생성/상태 업데이트 등)은 그대로 실행되게 수정하세요.
🧹 Nitpick comments (4)
src/pages/Manager/components/TimePicker/index.tsx (2)
13-33: 시간 파싱/포맷 로직은 공용 유틸로 통합을 권장합니다.현재 파일의
TIME_REGEX,parseTime,formatTime가 다른 시간 유틸과 중복되어 규칙 드리프트 가능성이 있습니다. 공용 모듈로 모으면 유지보수가 쉬워집니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Manager/components/TimePicker/index.tsx` around lines 13 - 33, The TIME_REGEX, parseTime, formatTime and pad2 in this file are duplicated time utilities; extract them into a shared utility module (e.g., create/export TIME_REGEX, pad2, parseTime, formatTime from a common time utils file) and replace the local definitions by importing those exports into this component; update the references in this file to use the imported symbols (TIME_REGEX, parseTime, formatTime, pad2) and remove the local implementations so all components use the single source of truth.
138-141: 기본 트리거 버튼에 dialog 접근성 속성을 추가해 주세요.모달 오픈 버튼에
aria-haspopup,aria-expanded를 주면 보조기기 탐색성이 좋아집니다.♿ 제안 수정안
<button type="button" onClick={handleOpenModal} + aria-haspopup="dialog" + aria-expanded={isOpen} className="bg-indigo-0 text-h3 w-full rounded-xl border border-indigo-50 px-4 py-3.5 text-indigo-700 shadow-[0_2px_6px_rgba(2,23,48,0.08)]" >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Manager/components/TimePicker/index.tsx` around lines 138 - 141, The trigger button for opening the modal (the JSX button using onClick={handleOpenModal}) is missing accessibility attributes; add aria-haspopup="dialog" and aria-expanded={isOpen} (where isOpen is the boolean state that tracks the modal) to the button, and if the dialog element has an id, also add aria-controls="yourDialogId" to the same button so assistive tech can relate the control to the dialog; update the JSX for the button in TimePicker's component (the element that calls handleOpenModal) to include these attributes and ensure isOpen references the component's modal state.src/pages/Manager/hooks/useManagedApplications.ts (1)
70-70: invalidate queryKey 조합이 중복되어 유지보수 포인트가 늘어납니다.Line [70], Line [86]의 키 조합을 공통 함수로 빼두면 키 구조 변경 시 누락 위험을 줄일 수 있습니다.
♻️ 제안 리팩터
const applicationQueryKeys = { all: ['manager'], managedClubApplications: (clubId: number, page?: number, limit?: number) => [ ...applicationQueryKeys.all, 'managedClubApplications', clubId, page, limit, ], @@ }; + +const managedClubApplicationsBaseKey = (clubId: number) => + [...applicationQueryKeys.all, 'managedClubApplications', clubId] as const; @@ - queryClient.invalidateQueries({ queryKey: [...applicationQueryKeys.all, 'managedClubApplications', clubId] }); + queryClient.invalidateQueries({ queryKey: managedClubApplicationsBaseKey(clubId) }); @@ - queryClient.invalidateQueries({ queryKey: [...applicationQueryKeys.all, 'managedClubApplications', clubId] }); + queryClient.invalidateQueries({ queryKey: managedClubApplicationsBaseKey(clubId) });Also applies to: 86-86
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Manager/hooks/useManagedApplications.ts` at line 70, Extract the repeated queryKey combination [...applicationQueryKeys.all, 'managedClubApplications', clubId] into a single helper (e.g., a function named getManagedClubApplicationsKey or a new property on applicationQueryKeys like managedClubApplicationsKey) and replace both usages of queryClient.invalidateQueries({ queryKey: [...applicationQueryKeys.all, 'managedClubApplications', clubId] }) with queryClient.invalidateQueries({ queryKey: getManagedClubApplicationsKey(clubId) }) (or the equivalent applicationQueryKeys.managedClubApplicationsKey(clubId)), updating any other places that build the same key so future key-structure changes are made in one place.src/pages/Manager/ManagedRecruitmentWrite/index.tsx (1)
68-73: textarea 높이 조정 로직 중복 제거 권장
useEffect(lines 68-73)와handleContentChange(lines 136-139)에 동일한 textarea 리사이즈 로직이 반복됩니다. 헬퍼로 추출하면 DRY하게 유지됩니다.♻️ 리팩토링 제안
+ const adjustTextareaHeight = () => { + if (textareaRef.current) { + textareaRef.current.style.height = 'auto'; + textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`; + } + }; useEffect(() => { - if (textareaRef.current) { - textareaRef.current.style.height = 'auto'; - textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`; - } + adjustTextareaHeight(); }, [content]); const handleContentChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { setContent(e.target.value); - if (textareaRef.current) { - textareaRef.current.style.height = 'auto'; - textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`; - } + adjustTextareaHeight(); };Also applies to: 134-140
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Manager/ManagedRecruitmentWrite/index.tsx` around lines 68 - 73, Extract the duplicated textarea resize logic into a helper function (e.g., adjustTextareaHeight) that reads textareaRef.current, sets style.height to 'auto', then to `${scrollHeight}px`; replace the inline logic in useEffect and in handleContentChange with a call to adjustTextareaHeight to remove duplication and keep behavior identical, ensuring the helper is accessible within the component where textareaRef, useEffect, and handleContentChange are defined.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/common/LinkifiedText.tsx`:
- Around line 34-45: The current splitTrailingPunctuation always strips trailing
closers (e.g., ) or ]) which breaks valid URLs like
".../wiki/Function_(mathematics)"; modify splitTrailingPunctuation (and keep
TRAILING_PUNCTUATION_REGEX) so after computing trailing you check whether any
removed closing characters are actually matched by corresponding opening
characters inside the candidate link: for each trailing char that is a closing
bracket/paren, count occurrences of that closing char and its opening
counterpart in the original value (or in value.slice(0, -trailing.length)) and
only treat a closing char as trailing if it would be unmatched (i.e., more
closers than openers); if closers are balanced, keep them as part of the link
and return trailing without those characters. Ensure the function still returns
{ link, trailing } and handles mixed trailing punctuation correctly.
In `@src/pages/Guide/guideData.ts`:
- Around line 8-12: Update guide image URLs to use an environment variable
instead of hardcoded staging host: read VITE_STATIC_BASE_URL and prepend it to
each image path in guideData.ts (or set a default in the GuideItem interface),
replacing occurrences like 'https://stage-static.koreatech.in/konect/guide/...'.
Ensure guideData.ts references import.meta.env.VITE_STATIC_BASE_URL (or a small
helper that falls back to a sensible default) so production builds use the
correct CDN, and keep the existing item shape ({ id, image, duration }) intact.
In `@src/pages/Home/hooks/useGetNotificationToken.ts`:
- Around line 4-9: useGetNotificationToken is unused and its queryFn wraps
getNotificationToken unnecessarily; either remove the hook if unused, or update
it by passing getNotificationToken directly as the queryFn (replace "() =>
getNotificationToken()" with getNotificationToken) and add an enabled condition
(e.g., enabled: requiresAuth or similar) to prevent running when not
authenticated; refer to useGetNotificationToken, getNotificationToken, and the
useQuery options (queryKey, queryFn, enabled) when making the change.
In `@src/pages/Manager/ManagedApplicationList/index.tsx`:
- Line 1: 현재 totalPages가 줄어들면 page가 범위를 벗어날 수 있으니, totalPages 변경을 감지해 page를 상한으로
보정하세요: ManagedApplicationList 컴포넌트에서 useEffect를 추가하여 totalPages가 바뀔 때 page,
setPage를 사용해 새페이지를 clamp(예: const clamped = Math.min(page, Math.max(1,
totalPages)); if (page !== clamped) setPage(clamped)); 이렇게 하면 totalPages 감소 시
page를 유효한 범위로 강제합니다.
- Around line 84-100: The approve/reject buttons currently use plain "O" and "X"
which lack accessible context; update the button elements that call
handleApprove(application.id) and handleReject(application.id) to include
descriptive aria-label attributes (e.g., aria-label="Approve application" and
aria-label="Reject application" or include the application id/name for more
context) while keeping the existing disabled={isPending} behavior and handlers
unchanged so screen readers convey the action intent.
In `@src/pages/Manager/ManagedRecruitmentWrite/utils.ts`:
- Around line 9-13: normalizeTime currently trusts fallback and can produce
"NaN:NaN" if fallback is invalid; validate and sanitize fallback before using it
by calling isValidTimeFormat on fallback and falling back to a safe default
(e.g., "00:00") when invalid, then parse target and clamp minute using
TIME_MINUTE_STEP as before; update normalizeTime to use
isValidTimeFormat(fallback) ? fallback : "00:00" (or another agreed safe
constant) and ensure hour/minute parsing handles NaN (e.g., default to 0) to
avoid producing malformed strings.
In `@src/utils/ts/date.ts`:
- Around line 12-13: The current code assumes timePart splits into [hour,
minute], causing padStart to crash when timePart is malformed; update the logic
in the date formatting function to validate timePart before using hour/minute
(e.g., ensure timePart.split(':') yields two non-empty segments and they match
/^\d{1,2}$/), and if validation fails return only the date string (use the
existing date variable) instead of attempting padStart; keep references to
timePart, hour, minute and the existing return template so you modify that block
only.
---
Outside diff comments:
In `@src/pages/Manager/ManagedRecruitmentWrite/index.tsx`:
- Around line 166-168: handleDeleteImage에서 images[currentImageIndex].previewUrl에
무조건 URL.revokeObjectURL을 호출하고 있는데, 서버 URL(기존 이미지)의 경우 blob이 아니므로 revoke를 호출하지
않도록 변경하세요: handleDeleteImage 내부에서 현재 이미지 객체(images[currentImageIndex])의
isExisting 플래그(또는 previewUrl.startsWith('blob:') 검사)를 사용해 blob URL일 때만
URL.revokeObjectURL를 호출하고, isExisting인 경우에는 revoke를 건너뛰고 기존 로직(newImages 생성/상태
업데이트 등)은 그대로 실행되게 수정하세요.
---
Nitpick comments:
In `@src/pages/Manager/components/TimePicker/index.tsx`:
- Around line 13-33: The TIME_REGEX, parseTime, formatTime and pad2 in this file
are duplicated time utilities; extract them into a shared utility module (e.g.,
create/export TIME_REGEX, pad2, parseTime, formatTime from a common time utils
file) and replace the local definitions by importing those exports into this
component; update the references in this file to use the imported symbols
(TIME_REGEX, parseTime, formatTime, pad2) and remove the local implementations
so all components use the single source of truth.
- Around line 138-141: The trigger button for opening the modal (the JSX button
using onClick={handleOpenModal}) is missing accessibility attributes; add
aria-haspopup="dialog" and aria-expanded={isOpen} (where isOpen is the boolean
state that tracks the modal) to the button, and if the dialog element has an id,
also add aria-controls="yourDialogId" to the same button so assistive tech can
relate the control to the dialog; update the JSX for the button in TimePicker's
component (the element that calls handleOpenModal) to include these attributes
and ensure isOpen references the component's modal state.
In `@src/pages/Manager/hooks/useManagedApplications.ts`:
- Line 70: Extract the repeated queryKey combination
[...applicationQueryKeys.all, 'managedClubApplications', clubId] into a single
helper (e.g., a function named getManagedClubApplicationsKey or a new property
on applicationQueryKeys like managedClubApplicationsKey) and replace both usages
of queryClient.invalidateQueries({ queryKey: [...applicationQueryKeys.all,
'managedClubApplications', clubId] }) with queryClient.invalidateQueries({
queryKey: getManagedClubApplicationsKey(clubId) }) (or the equivalent
applicationQueryKeys.managedClubApplicationsKey(clubId)), updating any other
places that build the same key so future key-structure changes are made in one
place.
In `@src/pages/Manager/ManagedRecruitmentWrite/index.tsx`:
- Around line 68-73: Extract the duplicated textarea resize logic into a helper
function (e.g., adjustTextareaHeight) that reads textareaRef.current, sets
style.height to 'auto', then to `${scrollHeight}px`; replace the inline logic in
useEffect and in handleContentChange with a call to adjustTextareaHeight to
remove duplication and keep behavior identical, ensuring the helper is
accessible within the component where textareaRef, useEffect, and
handleContentChange are defined.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (21)
src/apis/client.tssrc/apis/club/entity.tssrc/apis/club/index.tssrc/apis/notification/index.tssrc/components/common/LinkifiedText.tsxsrc/global.d.tssrc/pages/Chat/ChatRoom.tsxsrc/pages/Club/ClubDetail/components/ClubRecruitment.tsxsrc/pages/Club/ClubList/components/ClubCard.tsxsrc/pages/Guide/guideData.tssrc/pages/Home/hooks/useGetNotificationToken.tssrc/pages/Manager/ManagedApplicationDetail/index.tsxsrc/pages/Manager/ManagedApplicationList/index.tsxsrc/pages/Manager/ManagedRecruitment/index.tsxsrc/pages/Manager/ManagedRecruitmentWrite/index.tsxsrc/pages/Manager/ManagedRecruitmentWrite/utils.tssrc/pages/Manager/components/TimePicker/index.tsxsrc/pages/Manager/hooks/useManagedApplications.tssrc/pages/User/MyPage/hooks/useLogout.tssrc/stores/authStore.tssrc/utils/ts/date.ts
| const splitTrailingPunctuation = (value: string) => { | ||
| const trailing = value.match(TRAILING_PUNCTUATION_REGEX)?.[0] ?? ''; | ||
|
|
||
| if (!trailing) { | ||
| return { link: value, trailing: '' }; | ||
| } | ||
|
|
||
| return { | ||
| link: value.slice(0, -trailing.length), | ||
| trailing, | ||
| }; | ||
| }; |
There was a problem hiding this comment.
괄호를 포함한 정상 URL이 잘릴 수 있습니다.
현재 trailing punctuation 제거 로직은 URL 끝의 )/] 등을 항상 제거해서, .../wiki/Function_(mathematics) 같은 정상 링크가 깨질 수 있습니다. 괄호 짝이 맞는 경우는 유지하도록 분기해 주세요.
수정 예시
-const splitTrailingPunctuation = (value: string) => {
- const trailing = value.match(TRAILING_PUNCTUATION_REGEX)?.[0] ?? '';
-
- if (!trailing) {
- return { link: value, trailing: '' };
- }
-
- return {
- link: value.slice(0, -trailing.length),
- trailing,
- };
-};
+const BRACKET_PAIR: Record<string, string> = {
+ ')': '(',
+ ']': '[',
+ '}': '{',
+ '>': '<',
+};
+
+const splitTrailingPunctuation = (value: string) => {
+ let link = value;
+ let trailing = '';
+
+ while (link.length > 0) {
+ const lastChar = link[link.length - 1];
+ if (!/[)\]}>.,!?;:'"`]/.test(lastChar)) break;
+
+ // URL 내부의 균형 잡힌 닫는 괄호는 유지
+ if (lastChar in BRACKET_PAIR) {
+ const open = BRACKET_PAIR[lastChar];
+ const openCount = [...link].filter((ch) => ch === open).length;
+ const closeCount = [...link].filter((ch) => ch === lastChar).length;
+ if (closeCount <= openCount) break;
+ }
+
+ trailing = lastChar + trailing;
+ link = link.slice(0, -1);
+ }
+
+ return { link, trailing };
+};🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/common/LinkifiedText.tsx` around lines 34 - 45, The current
splitTrailingPunctuation always strips trailing closers (e.g., ) or ]) which
breaks valid URLs like ".../wiki/Function_(mathematics)"; modify
splitTrailingPunctuation (and keep TRAILING_PUNCTUATION_REGEX) so after
computing trailing you check whether any removed closing characters are actually
matched by corresponding opening characters inside the candidate link: for each
trailing char that is a closing bracket/paren, count occurrences of that closing
char and its opening counterpart in the original value (or in value.slice(0,
-trailing.length)) and only treat a closing char as trailing if it would be
unmatched (i.e., more closers than openers); if closers are balanced, keep them
as part of the link and return trailing without those characters. Ensure the
function still returns { link, trailing } and handles mixed trailing punctuation
correctly.
| { id: 1, image: 'https://stage-static.koreatech.in/konect/guide/1-1%EB%B2%88.png', duration: 3000 }, | ||
| { id: 2, image: 'https://stage-static.koreatech.in/konect/guide/2-1%EB%B2%88.png', duration: 3000 }, | ||
| { id: 3, image: 'https://stage-static.koreatech.in/konect/guide/3-1%EB%B2%88.png', duration: 3000 }, | ||
| { id: 4, image: 'https://stage-static.koreatech.in/konect/guide/4%EB%B2%88.png', duration: 3000 }, | ||
| { id: 5, image: 'https://stage-static.koreatech.in/konect/guide/5%EB%B2%88.png', duration: 3000 }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# 1. 프로젝트 구조 확인
find . -maxdepth 2 -type f \( -name "*.ts" -o -name "*.tsx" -o -name ".env*" -o -name "*.config.*" \) | head -20Repository: BCSDLab/KONECT_FRONT_END
Length of output: 186
🏁 Script executed:
# 2. guideData.ts 전체 파일 확인
cat -n src/pages/Guide/guideData.tsRepository: BCSDLab/KONECT_FRONT_END
Length of output: 785
🏁 Script executed:
# 3. 프로젝트 내 stage-static 하드코딩 위치 검색
rg -n "stage-static|koreatech\.in" --type ts --type tsxRepository: BCSDLab/KONECT_FRONT_END
Length of output: 95
🏁 Script executed:
# 4. 환경 설정 관련 파일 검색 (env, config, constants, etc.)
fd -HI -e ts -e tsx -e js -e json | xargs rg -l "process\.env|VITE_|import.*config|import.*constant" | head -15Repository: BCSDLab/KONECT_FRONT_END
Length of output: 413
🏁 Script executed:
# 5. 다른 API 호출이나 URL 사용 패턴 확인
rg -n "http|url|URL|BASE_URL|API" -t ts -t tsx | head -30Repository: BCSDLab/KONECT_FRONT_END
Length of output: 95
🏁 Script executed:
# vite.config.ts 확인
cat -n vite.config.tsRepository: BCSDLab/KONECT_FRONT_END
Length of output: 3118
🏁 Script executed:
# src/apis/client.ts 확인 (환경별 base URL 처리 방식)
cat -n src/apis/client.tsRepository: BCSDLab/KONECT_FRONT_END
Length of output: 10558
🏁 Script executed:
# .env 파일 존재 여부 및 내용
ls -la | grep -E "\.env|env\." && cat .env* 2>/dev/null || echo "No .env files found"Repository: BCSDLab/KONECT_FRONT_END
Length of output: 87
🏁 Script executed:
# 프로젝트 root 파일 목록 (config 관련)
ls -la | grep -E "config|env|\.ts$|\.js$"Repository: BCSDLab/KONECT_FRONT_END
Length of output: 481
🏁 Script executed:
# 다른 컴포넌트에서 URL이나 자산 처리 방식 확인
rg -n "https://|http://" --max-count=20 2>/dev/null || echo "Search completed"Repository: BCSDLab/KONECT_FRONT_END
Length of output: 11494
🏁 Script executed:
# guideData.ts를 import하는 컴포넌트 확인
rg -n "GUIDE_ITEMS|from.*guideData" --type tsRepository: BCSDLab/KONECT_FRONT_END
Length of output: 427
🏁 Script executed:
# Guide 페이지 구조 확인
find src/pages/Guide -type f -name "*.tsx" -o -name "*.ts" | head -10 && \
cat -n src/pages/Guide/index.tsx 2>/dev/null || find src -name "*uide*" -type fRepository: BCSDLab/KONECT_FRONT_END
Length of output: 3056
가이드 이미지 URL을 환경변수로 분리하세요.
stage-static.koreatech.in은 스테이징 환경 CDN이므로 운영 배포 시 이미지 로딩이 실패합니다. 이미 src/apis/client.ts에서 API 베이스 URL을 VITE_API_PATH 환경변수로 관리 중이므로, 정적 자산도 동일하게 VITE_STATIC_BASE_URL (또는 유사한 명명)로 통일해 주세요. GuideItem 인터페이스에 기본값을 설정하거나, guideData.ts에서 환경변수를 참조하도록 수정하면 됩니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/Guide/guideData.ts` around lines 8 - 12, Update guide image URLs to
use an environment variable instead of hardcoded staging host: read
VITE_STATIC_BASE_URL and prepend it to each image path in guideData.ts (or set a
default in the GuideItem interface), replacing occurrences like
'https://stage-static.koreatech.in/konect/guide/...'. Ensure guideData.ts
references import.meta.env.VITE_STATIC_BASE_URL (or a small helper that falls
back to a sensible default) so production builds use the correct CDN, and keep
the existing item shape ({ id, image, duration }) intact.
| export const useGetNotificationToken = () => { | ||
| return useQuery({ | ||
| queryKey: ['notification', 'token'], | ||
| queryFn: () => getNotificationToken(), | ||
| retry: false, | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg -n --type=ts --type=tsx "useGetNotificationToken" -A 5 -B 2Repository: BCSDLab/KONECT_FRONT_END
Length of output: 95
🏁 Script executed:
find . -name "*.ts" -o -name "*.tsx" | head -20Repository: BCSDLab/KONECT_FRONT_END
Length of output: 617
🏁 Script executed:
rg -n "getNotificationToken" --type=ts --type=tsx -B 2 -A 5Repository: BCSDLab/KONECT_FRONT_END
Length of output: 95
🏁 Script executed:
rg "useGetNotificationToken" -A 5 -B 2Repository: BCSDLab/KONECT_FRONT_END
Length of output: 687
🏁 Script executed:
rg "getNotificationToken" -A 5 -B 2Repository: BCSDLab/KONECT_FRONT_END
Length of output: 1305
🏁 Script executed:
cat src/pages/Home/hooks/useGetNotificationToken.tsRepository: BCSDLab/KONECT_FRONT_END
Length of output: 356
🏁 Script executed:
cat src/apis/notification/index.tsRepository: BCSDLab/KONECT_FRONT_END
Length of output: 674
🏁 Script executed:
rg "useGetNotificationToken\(" -A 10Repository: BCSDLab/KONECT_FRONT_END
Length of output: 50
🏁 Script executed:
find . -path "*/pages/Home*" -name "*.ts" -o -path "*/pages/Home*" -name "*.tsx"Repository: BCSDLab/KONECT_FRONT_END
Length of output: 478
🏁 Script executed:
cat src/pages/Home/index.tsxRepository: BCSDLab/KONECT_FRONT_END
Length of output: 8115
🏁 Script executed:
rg "import.*useGetNotificationToken"Repository: BCSDLab/KONECT_FRONT_END
Length of output: 50
🏁 Script executed:
rg "useGetNotificationToken" --no-headingRepository: BCSDLab/KONECT_FRONT_END
Length of output: 162
🏁 Script executed:
rg "notification.*token" -i --type-list | head -5Repository: BCSDLab/KONECT_FRONT_END
Length of output: 457
🏁 Script executed:
rg "notification.*token" -iRepository: BCSDLab/KONECT_FRONT_END
Length of output: 771
현재 사용되지 않는 훅입니다.
useGetNotificationToken은 코드베이스 어디에서도 임포트되지 않고 있습니다. 불필요한 경우 제거하거나, 향후 사용하려면 아래를 개선하세요:
queryFn불필요한 래퍼 제거:() => getNotificationToken()→getNotificationToken- 훅을 사용할 시
enabled조건 추가 필요 (requiresAuth API이므로)
export const useGetNotificationToken = () => {
return useQuery({
queryKey: ['notification', 'token'],
- queryFn: () => getNotificationToken(),
+ queryFn: getNotificationToken,
retry: false,
});
};🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/Home/hooks/useGetNotificationToken.ts` around lines 4 - 9,
useGetNotificationToken is unused and its queryFn wraps getNotificationToken
unnecessarily; either remove the hook if unused, or update it by passing
getNotificationToken directly as the queryFn (replace "() =>
getNotificationToken()" with getNotificationToken) and add an enabled condition
(e.g., enabled: requiresAuth or similar) to prevent running when not
authenticated; refer to useGetNotificationToken, getNotificationToken, and the
useQuery options (queryKey, queryFn, enabled) when making the change.
| @@ -1,7 +1,8 @@ | |||
| import { useState } from 'react'; | |||
There was a problem hiding this comment.
총 페이지 수가 줄어들 때 현재 페이지가 범위를 벗어날 수 있습니다.
Line [27]-Line [29] 기준으로 totalPages가 감소해도 page를 보정하지 않아 3 / 2 같은 상태가 발생할 수 있습니다. 페이지 상한 보정이 필요합니다.
🔧 제안 수정안
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
@@
const total = managedClubApplicationList?.totalCount ?? 0;
const totalPages = managedClubApplicationList?.totalPage ?? 1;
const currentPage = page;
+
+ useEffect(() => {
+ setPage((prev) => Math.min(Math.max(prev, 1), Math.max(totalPages, 1)));
+ }, [totalPages]);Also applies to: 27-29
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/Manager/ManagedApplicationList/index.tsx` at line 1, 현재 totalPages가
줄어들면 page가 범위를 벗어날 수 있으니, totalPages 변경을 감지해 page를 상한으로 보정하세요:
ManagedApplicationList 컴포넌트에서 useEffect를 추가하여 totalPages가 바뀔 때 page, setPage를
사용해 새페이지를 clamp(예: const clamped = Math.min(page, Math.max(1, totalPages)); if
(page !== clamped) setPage(clamped)); 이렇게 하면 totalPages 감소 시 page를 유효한 범위로
강제합니다.
| <button | ||
| type="button" | ||
| onClick={(e) => handleApprove(e, application.id)} | ||
| disabled={isPending} | ||
| className="flex h-8 w-8 items-center justify-center rounded-full text-green-600 hover:bg-green-200 disabled:opacity-50" | ||
| > | ||
| O | ||
| </button> | ||
|
|
||
| <button | ||
| type="button" | ||
| onClick={(e) => handleReject(e, application.id)} | ||
| disabled={isPending} | ||
| className="flex h-8 w-8 items-center justify-center rounded-full text-red-600 hover:bg-red-200 disabled:opacity-50" | ||
| > | ||
| X | ||
| </button> |
There was a problem hiding this comment.
승인/거절 버튼 텍스트(O/X)는 접근성 문맥이 부족합니다.
Line [90], Line [99]은 스크린리더에서 의미 전달이 약합니다. aria-label을 추가해 액션 의도를 명확히 해주세요.
♿ 제안 수정안
<button
type="button"
onClick={(e) => handleApprove(e, application.id)}
disabled={isPending}
+ aria-label={`${application.name} 지원 승인`}
+ title="승인"
className="flex h-8 w-8 items-center justify-center rounded-full text-green-600 hover:bg-green-200 disabled:opacity-50"
>
- O
+ <span aria-hidden>O</span>
</button>
@@
<button
type="button"
onClick={(e) => handleReject(e, application.id)}
disabled={isPending}
+ aria-label={`${application.name} 지원 거절`}
+ title="거절"
className="flex h-8 w-8 items-center justify-center rounded-full text-red-600 hover:bg-red-200 disabled:opacity-50"
>
- X
+ <span aria-hidden>X</span>
</button>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <button | |
| type="button" | |
| onClick={(e) => handleApprove(e, application.id)} | |
| disabled={isPending} | |
| className="flex h-8 w-8 items-center justify-center rounded-full text-green-600 hover:bg-green-200 disabled:opacity-50" | |
| > | |
| O | |
| </button> | |
| <button | |
| type="button" | |
| onClick={(e) => handleReject(e, application.id)} | |
| disabled={isPending} | |
| className="flex h-8 w-8 items-center justify-center rounded-full text-red-600 hover:bg-red-200 disabled:opacity-50" | |
| > | |
| X | |
| </button> | |
| <button | |
| type="button" | |
| onClick={(e) => handleApprove(e, application.id)} | |
| disabled={isPending} | |
| aria-label={`${application.name} 지원 승인`} | |
| title="승인" | |
| className="flex h-8 w-8 items-center justify-center rounded-full text-green-600 hover:bg-green-200 disabled:opacity-50" | |
| > | |
| <span aria-hidden>O</span> | |
| </button> | |
| <button | |
| type="button" | |
| onClick={(e) => handleReject(e, application.id)} | |
| disabled={isPending} | |
| aria-label={`${application.name} 지원 거절`} | |
| title="거절" | |
| className="flex h-8 w-8 items-center justify-center rounded-full text-red-600 hover:bg-red-200 disabled:opacity-50" | |
| > | |
| <span aria-hidden>X</span> | |
| </button> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/Manager/ManagedApplicationList/index.tsx` around lines 84 - 100,
The approve/reject buttons currently use plain "O" and "X" which lack accessible
context; update the button elements that call handleApprove(application.id) and
handleReject(application.id) to include descriptive aria-label attributes (e.g.,
aria-label="Approve application" and aria-label="Reject application" or include
the application id/name for more context) while keeping the existing
disabled={isPending} behavior and handlers unchanged so screen readers convey
the action intent.
| export function normalizeTime(value: string, fallback: string): string { | ||
| const target = isValidTimeFormat(value) ? value : fallback; | ||
| const [hour, minute] = target.split(':').map(Number); | ||
| const normalizedMinute = Math.floor(minute / TIME_MINUTE_STEP) * TIME_MINUTE_STEP; | ||
| return `${String(hour).padStart(2, '0')}:${String(normalizedMinute).padStart(2, '0')}`; |
There was a problem hiding this comment.
fallback 시간이 유효하지 않으면 NaN:NaN이 반환될 수 있습니다.
Line [10]-Line [13]에서 fallback 형식 검증이 없어 잘못된 값이 들어오면 깨진 문자열이 만들어집니다. fallback도 안전값으로 보정해 주세요.
🔧 제안 수정안
export function normalizeTime(value: string, fallback: string): string {
- const target = isValidTimeFormat(value) ? value : fallback;
+ const safeFallback = isValidTimeFormat(fallback) ? fallback : DEFAULT_START_TIME;
+ const target = isValidTimeFormat(value) ? value : safeFallback;
const [hour, minute] = target.split(':').map(Number);
+ if (!Number.isFinite(hour) || !Number.isFinite(minute)) return DEFAULT_START_TIME;
const normalizedMinute = Math.floor(minute / TIME_MINUTE_STEP) * TIME_MINUTE_STEP;
return `${String(hour).padStart(2, '0')}:${String(normalizedMinute).padStart(2, '0')}`;
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/Manager/ManagedRecruitmentWrite/utils.ts` around lines 9 - 13,
normalizeTime currently trusts fallback and can produce "NaN:NaN" if fallback is
invalid; validate and sanitize fallback before using it by calling
isValidTimeFormat on fallback and falling back to a safe default (e.g., "00:00")
when invalid, then parse target and clamp minute using TIME_MINUTE_STEP as
before; update normalizeTime to use isValidTimeFormat(fallback) ? fallback :
"00:00" (or another agreed safe constant) and ensure hour/minute parsing handles
NaN (e.g., default to 0) to avoid producing malformed strings.
| const [hour, minute] = timePart.split(':'); | ||
| return `${date} ${hour.padStart(2, '0')}:${minute.padStart(2, '0')}`; |
There was a problem hiding this comment.
시간 파트 파싱 실패 시 런타임 에러가 발생할 수 있습니다.
Line [12]-Line [13]에서 timePart가 HH:MM 형태가 아니면 minute가 undefined가 되어 padStart 호출 시 크래시가 납니다. 시간 파싱 실패 시 날짜만 반환하도록 가드가 필요합니다.
🔧 제안 수정안
- const [hour, minute] = timePart.split(':');
- return `${date} ${hour.padStart(2, '0')}:${minute.padStart(2, '0')}`;
+ const [hour = '', minute = ''] = timePart.split(':');
+ if (!hour || !minute) return date;
+ return `${date} ${hour.padStart(2, '0')}:${minute.padStart(2, '0')}`;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const [hour, minute] = timePart.split(':'); | |
| return `${date} ${hour.padStart(2, '0')}:${minute.padStart(2, '0')}`; | |
| const [hour = '', minute = ''] = timePart.split(':'); | |
| if (!hour || !minute) return date; | |
| return `${date} ${hour.padStart(2, '0')}:${minute.padStart(2, '0')}`; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/utils/ts/date.ts` around lines 12 - 13, The current code assumes timePart
splits into [hour, minute], causing padStart to crash when timePart is
malformed; update the logic in the date formatting function to validate timePart
before using hour/minute (e.g., ensure timePart.split(':') yields two non-empty
segments and they match /^\d{1,2}$/), and if validation fails return only the
date string (use the existing date variable) instead of attempting padStart;
keep references to timePart, hour, minute and the existing return template so
you modify that block only.
* develop → main 배포 (#125) * feat: 알림설정 구현 (#108) * chore: min-w 추가 * [chore] 코드래빗 설정 파일 추가 (#110) * chore: 코드래빗 설정 파일 추가 * chore: 불필요한 tools 설정 제거 * [feat] preMember 목록 분리 및 삭제 기능 추가 (#115) * feat: preMember 목록 분리 및 삭제 기능 추가 * chore: Pascal case * chore: 변경된 변수명 반영 * feat: 학번 숫자 필터링 추가 * [feat] 채팅 목차 알림 상태 추가 (#113) * chore: svg 추가 * feat: 채팅목록 알림 끄기 설정시 UI 추가 * feat: 목차 열릴시 애니메이션 추가 * [feat] 달력 카테고리 분류 추가 (#118) * feat: 카테고리 추가 * chore: 불필요한 코드 삭제 * [refactor] 관리자 페이지 및 마이페이지 UI 수정 및 리팩토링 (#117) * feat: 정보 카드에서 정보 페이지로 이동하도록 기능 추가 * feat: 토스트 전역상태 추가 * refactor: query 훅 분리 및 onSuccess 콜백 옵션 제거 * refactor: query 훅 사용처 수정 * refactor: 사용처 수정 2 * fix: 채팅창 스크롤 초기화 문제 수정 및 줄바꿈 기준 변경 * feat: 토글 스위치 구현 * refactor: 모집 공고 관련 목록 페이지 디자인 수정 * feat: 컴포넌트 구현 및 icon 추가 * refactor: z-index 값 수정 * refactor: API 필드 변경 사항 반영 * refactor: 모집 공고 페이지 디자인 수정 및 라우트 백 수정 * refactor: 학교 목록에 없을 시 문구 디자인 수정 * fix: lint error * fix: 타입 변경 * feat: 모집 관련 페이지 API 추가사항 반영 * refactor: 토스트 타이머 클린업 추라 * refactor: 전역토스트 사용 변경 * refactor: 관리자 클럽 조회 훅 호출 범위 줄이기 * feat: onError handler add * chore: add button type and remove fragment * [feat] 문의하기 버튼 로직 추가 (#120) * feat: API 추가 및 연결 * chore: placeholder 제거 * [fix] 채팅 스크롤 미갱신 버그 및 멤버 직위 렌더링 수정 (#122) * fix: 채팅 스크롤 미작동 수정 * fix: 한글 출력으로 수정 * feat: 첫 글자 보여주도록 수정 (#124) * hotfix: 동아리 소개 줄바꿈 적용 및 한 줄 소개 글자수 제한 상향 * chore: 리뷰 반영 * fix: disabled 조건 변경 * 126 fix 배포 전 qa 사항 반영 (#127) * fix: 가입 버튼 문구 수정 * fix: 검색창 placeholder 수정 * fix: 불필요해진 UI 비활성화 * fix: 모집공고 없을 때 빈 화면 처리 * fix: 무의미한 회비페이지 수정 * feat: 토글 시 자동 페이지 이동 추가 * fix: 페이지 이동 조건 수정 --------- Co-authored-by: 김혜준 <114041848+hyejun0228@users.noreply.github.com> Co-authored-by: hyejun <hyejunkkim228@gmail.com> * develop -> main 배포 (#132) * chore: 타입 변경 반영 * [feat] 모집 공고에 시간 추가 및 지원서 정렬 추가 (#131) * feat: 모집 공고에 시작, 종료 시간 추가 * feat: 지원자 목록 정렬 추가 및 시간 표시 * feat: 채팅, 모집 공고 링크 파싱 기능 추가 * feat: 인스타그램 파싱 추가 * fix: prettier error * refactor: 유틸 분리 및 타입 변경 * chore: 타입 변경 반영 * chore: 동일 스타일 통합 및 entity 확장 사용 * chore:ios 토큰 테스트를 위한 로직 추가 * Revert "chore:ios 토큰 테스트를 위한 로직 추가" This reverts commit 98570fc. * hotfix: 모집 공고 textarea 길이 자동 재계산 추가 * feat: 페이지네이션 추가 * feat: 수정 * feat: 페이지네이션 수정 * feat: 디자인 수정 * feat: 로직 수정 * fix: key 수정 * develop-> main (#135) * chore: 타입 변경 반영 * [feat] 모집 공고에 시간 추가 및 지원서 정렬 추가 (#131) * feat: 모집 공고에 시작, 종료 시간 추가 * feat: 지원자 목록 정렬 추가 및 시간 표시 * feat: 채팅, 모집 공고 링크 파싱 기능 추가 * feat: 인스타그램 파싱 추가 * fix: prettier error * refactor: 유틸 분리 및 타입 변경 * chore: 타입 변경 반영 * chore: 동일 스타일 통합 및 entity 확장 사용 * chore:ios 토큰 테스트를 위한 로직 추가 * Revert "chore:ios 토큰 테스트를 위한 로직 추가" This reverts commit 98570fc. * hotfix: 모집 공고 textarea 길이 자동 재계산 추가 * Reapply "chore:ios 토큰 테스트를 위한 로직 추가" This reverts commit 3a56f4a. * chore: ios토큰 * feat: 가이드 페이지 이미지 변경 * chore: 푸시알림 로직 임시 변경 * refactor: 푸시 알림 토큰 로직 브릿지 방식으로 수정 (#134) * refactor: 푸시 알림 토큰 로직 브릿지 방식으로 수정 * fix: try catch 적용 * feat: 페이지네이션 추가 * feat: 수정 * feat: 페이지네이션 수정 * feat: 디자인 수정 * feat: 로직 수정 * fix: key 수정 * chore: 스테이지 용 임시 UI 제거 --------- Co-authored-by: hyejun <hyejunkkim228@gmail.com> * [feat] sentry 모니터링 추가 (#141) (#142) * chore: sentry 의존성 추가 * feat: sentry 세팅 * chore: 워크플로우 수정 및 vite 세팅 * chore: 임시 추가 * refactor: 색상 토큰 파일 분리 * refactor: 타이포그래피 스타일 파일 분리 * refactor: 피그마 타이포그래피 네이밍으로 클래스 정렬 * fix: 리뷰 코멘트 기반 접근성 및 토큰 정리 --------- Co-authored-by: 김혜준 <114041848+hyejun0228@users.noreply.github.com> Co-authored-by: hyejun <hyejunkkim228@gmail.com>
Summary by CodeRabbit
릴리스 노트
새로운 기능
개선