Skip to content

develop-> main#135

Merged
ff1451 merged 17 commits intomainfrom
develop
Feb 25, 2026
Merged

develop-> main#135
ff1451 merged 17 commits intomainfrom
develop

Conversation

@ff1451
Copy link
Copy Markdown
Collaborator

@ff1451 ff1451 commented Feb 25, 2026

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 메시지 및 모집 공고에서 URL과 인스타그램 핸들 자동 링크화
    • 모집 공고 편집 시 시간 선택 기능 추가
    • 지원자 목록 페이지네이션 지원
    • 알림 토큰 조회 기능 추가
  • 개선

    • 지원자 조회 시 시간을 포함한 날짜 정보 표시
    • 날짜 파싱 방식 개선

@ff1451 ff1451 self-assigned this Feb 25, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 25, 2026

📝 Walkthrough

Walkthrough

React 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)

Check name Status Explanation Resolution
Title check ⚠️ Warning PR 제목 'develop-> main'은 브랜치명만 명시하고 실제 변경사항의 핵심을 전혀 설명하지 않습니다. 링크 기능 추가, 모집 공고 시간 선택 기능, 알림 토큰 관리 등 주요 기능 변경이 있지만 반영되지 않았습니다. 브랜치 이름이 아닌 주요 기능을 설명하는 제목으로 변경하세요. 예: 'Add LinkifiedText component and recruitment time scheduling' 또는 구체적인 기능명을 포함하세요.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch develop

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Comment @coderabbitai help to get the list of available commands and usage tips.

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: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 1e442a6 and 4f0b367.

📒 Files selected for processing (21)
  • src/apis/client.ts
  • src/apis/club/entity.ts
  • src/apis/club/index.ts
  • src/apis/notification/index.ts
  • src/components/common/LinkifiedText.tsx
  • src/global.d.ts
  • src/pages/Chat/ChatRoom.tsx
  • src/pages/Club/ClubDetail/components/ClubRecruitment.tsx
  • src/pages/Club/ClubList/components/ClubCard.tsx
  • src/pages/Guide/guideData.ts
  • src/pages/Home/hooks/useGetNotificationToken.ts
  • src/pages/Manager/ManagedApplicationDetail/index.tsx
  • src/pages/Manager/ManagedApplicationList/index.tsx
  • src/pages/Manager/ManagedRecruitment/index.tsx
  • src/pages/Manager/ManagedRecruitmentWrite/index.tsx
  • src/pages/Manager/ManagedRecruitmentWrite/utils.ts
  • src/pages/Manager/components/TimePicker/index.tsx
  • src/pages/Manager/hooks/useManagedApplications.ts
  • src/pages/User/MyPage/hooks/useLogout.ts
  • src/stores/authStore.ts
  • src/utils/ts/date.ts

Comment on lines +34 to +45
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,
};
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

괄호를 포함한 정상 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.

Comment on lines +8 to +12
{ 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 },
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# 1. 프로젝트 구조 확인
find . -maxdepth 2 -type f \( -name "*.ts" -o -name "*.tsx" -o -name ".env*" -o -name "*.config.*" \) | head -20

Repository: BCSDLab/KONECT_FRONT_END

Length of output: 186


🏁 Script executed:

# 2. guideData.ts 전체 파일 확인
cat -n src/pages/Guide/guideData.ts

Repository: BCSDLab/KONECT_FRONT_END

Length of output: 785


🏁 Script executed:

# 3. 프로젝트 내 stage-static 하드코딩 위치 검색
rg -n "stage-static|koreatech\.in" --type ts --type tsx

Repository: 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 -15

Repository: 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 -30

Repository: BCSDLab/KONECT_FRONT_END

Length of output: 95


🏁 Script executed:

# vite.config.ts 확인
cat -n vite.config.ts

Repository: BCSDLab/KONECT_FRONT_END

Length of output: 3118


🏁 Script executed:

# src/apis/client.ts 확인 (환경별 base URL 처리 방식)
cat -n src/apis/client.ts

Repository: 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 ts

Repository: 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 f

Repository: 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.

Comment on lines +4 to +9
export const useGetNotificationToken = () => {
return useQuery({
queryKey: ['notification', 'token'],
queryFn: () => getNotificationToken(),
retry: false,
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n --type=ts --type=tsx "useGetNotificationToken" -A 5 -B 2

Repository: BCSDLab/KONECT_FRONT_END

Length of output: 95


🏁 Script executed:

find . -name "*.ts" -o -name "*.tsx" | head -20

Repository: BCSDLab/KONECT_FRONT_END

Length of output: 617


🏁 Script executed:

rg -n "getNotificationToken" --type=ts --type=tsx -B 2 -A 5

Repository: BCSDLab/KONECT_FRONT_END

Length of output: 95


🏁 Script executed:

rg "useGetNotificationToken" -A 5 -B 2

Repository: BCSDLab/KONECT_FRONT_END

Length of output: 687


🏁 Script executed:

rg "getNotificationToken" -A 5 -B 2

Repository: BCSDLab/KONECT_FRONT_END

Length of output: 1305


🏁 Script executed:

cat src/pages/Home/hooks/useGetNotificationToken.ts

Repository: BCSDLab/KONECT_FRONT_END

Length of output: 356


🏁 Script executed:

cat src/apis/notification/index.ts

Repository: BCSDLab/KONECT_FRONT_END

Length of output: 674


🏁 Script executed:

rg "useGetNotificationToken\(" -A 10

Repository: 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.tsx

Repository: 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-heading

Repository: BCSDLab/KONECT_FRONT_END

Length of output: 162


🏁 Script executed:

rg "notification.*token" -i --type-list | head -5

Repository: BCSDLab/KONECT_FRONT_END

Length of output: 457


🏁 Script executed:

rg "notification.*token" -i

Repository: BCSDLab/KONECT_FRONT_END

Length of output: 771


현재 사용되지 않는 훅입니다.

useGetNotificationToken은 코드베이스 어디에서도 임포트되지 않고 있습니다. 불필요한 경우 제거하거나, 향후 사용하려면 아래를 개선하세요:

  1. queryFn 불필요한 래퍼 제거: () => getNotificationToken()getNotificationToken
  2. 훅을 사용할 시 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';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

총 페이지 수가 줄어들 때 현재 페이지가 범위를 벗어날 수 있습니다.

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를 유효한 범위로
강제합니다.

Comment on lines +84 to +100
<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>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

승인/거절 버튼 텍스트(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.

Suggested change
<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.

Comment on lines +9 to +13
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')}`;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Comment thread src/utils/ts/date.ts
Comment on lines +12 to +13
const [hour, minute] = timePart.split(':');
return `${date} ${hour.padStart(2, '0')}:${minute.padStart(2, '0')}`;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

시간 파트 파싱 실패 시 런타임 에러가 발생할 수 있습니다.

Line [12]-Line [13]에서 timePartHH:MM 형태가 아니면 minuteundefined가 되어 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.

Suggested change
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.

@ff1451 ff1451 merged commit b3bd548 into main Feb 25, 2026
4 checks passed
ff1451 added a commit that referenced this pull request Mar 6, 2026
* 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>
@coderabbitai coderabbitai Bot mentioned this pull request Mar 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants