Skip to content

[배포] 2026-05-08 프로덕션 배포 (모바일 식단 페이지 디자인 수정, 콜밴 제한 사용자 모달 추가, 시설물 정보 API 응답 변경)#1248

Merged
ff1451 merged 9 commits intomainfrom
develop
May 8, 2026
Merged

[배포] 2026-05-08 프로덕션 배포 (모바일 식단 페이지 디자인 수정, 콜밴 제한 사용자 모달 추가, 시설물 정보 API 응답 변경)#1248
ff1451 merged 9 commits intomainfrom
develop

Conversation

@ff1451
Copy link
Copy Markdown
Contributor

@ff1451 ff1451 commented May 8, 2026

What is this PR? 🔍

  • 배포

Changes 📝

  • 콜밴 제한된 사용자 알림 모달 추가
  • 모바일 식단 페이지의 디자인 수정
  • 시설물 정보 API 응답값 변경 반영
  • SonarCloud 연동 및 하네스 구성

✔️ Please check if the PR fulfills these requirements

  • It's submitted to the correct branch, not the develop branch unconditionally?
  • If on a hotfix branch, ensure it targets main?
  • There are no warning message when you run yarn lint

Summary by CodeRabbit

릴리스 노트

  • 새 기능

    • 콜밴 이용 제한 상태 확인 및 제한 사유 안내 모달 추가
    • 캠퍼스 정보에서 동적 숍 아이콘 로드 기능 추가
  • 개선사항

    • 캠퍼스 정보 숍 아이콘 표시 개선
    • 콜밴 필터 로직 최적화
  • 기타

    • 의존성 업데이트 (axios, postcss)
    • 코드 품질 자동 분석 시스템 통합

dependabot Bot and others added 8 commits April 17, 2026 20:42
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…s 1 directory (#1233)

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
- sonarcloud issue 반영
…irectory (#1247)

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
@ff1451 ff1451 self-assigned this May 8, 2026
@ff1451 ff1451 added the 🌏 Deploy 배포 관련 사항 label May 8, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

Review Change Stack

Warning

Rate limit exceeded

@ff1451 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 56 minutes and 50 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 22785db9-2b81-4413-aa7b-ef93d11d7607

📥 Commits

Reviewing files that changed from the base of the PR and between 43b6a39 and 465eb1d.

⛔ Files ignored due to path filters (9)
  • .pnp.cjs is excluded by !.pnp.cjs and included by **
  • .yarn/cache/@next-env-npm-15.5.16-451e0aa651-4ba5975b7b.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/@next-swc-darwin-arm64-npm-15.5.16-3ce66e459a-10.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/@types-react-npm-19.2.10-92289ba21f-0e34a0e42d.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/@types-react-npm-19.2.14-072ed0943f-fbff239089.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/next-npm-15.5.16-6a72a2b582-6575ffe4b6.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/react-dom-npm-19.2.6-6f68db79da-695122e37d.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/react-npm-19.2.6-f326db5d50-205f0db93b.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock, !yarn.lock and included by **
📒 Files selected for processing (1)
  • package.json

살펴보기

KOIN 웹 프로젝트에 SonarCloud 기반 자동화 파이프라인, 콜반 사용 제한 모달 기능, 캠퍼스 정보 동적 아이콘 시스템을 추가하고 여러 코드 구조를 개선했습니다.

변경사항

KOIN 자동화 파이프라인

계층 / 파일(s) 요약
파이프라인 핵심 설정
sonar-project.properties, .claude/skills/koin-pipeline/SKILL.md
SonarCloud 프로젝트 설정과 두 가지 자동화 파이프라인(품질 개선, 기능 구현)의 오케스트레이션을 정의합니다.
에이전트 명세
.claude/agents/sonarcloud-analyzer.md, .claude/agents/code-quality-fixer.md, .claude/agents/feature-planner.md, .claude/agents/feature-implementer.md, .claude/agents/qa-reviewer.md, .claude/agents/pr-creator.md
SonarCloud 분석, 이슈 수정, 기능 계획/구현, QA 검증, PR 생성을 담당하는 여섯 에이전트의 입출력 계약과 작업 원칙을 정의합니다.
스킬 가이드
.claude/skills/sonarcloud-analysis/*, .claude/skills/code-quality-fix/SKILL.md, .claude/skills/feature-planning/SKILL.md, .claude/skills/feature-implementation/SKILL.md, .claude/skills/qa-review/SKILL.md, .claude/skills/github-pr-creation/SKILL.md
각 에이전트의 구체적인 실행 절차와 기술 패턴을 제공하는 스킬 문서를 추가합니다.
리포지토리 규칙
.claude/CLAUDE.md, .github/workflows/sonarcloud.yml, .gitignore, package.json
개발 규칙을 한국어로 정리하고, SonarCloud GitHub Actions 워크플로우를 추가하고, 워크스페이스 디렉터리를 무시하고, 의존성을 최소 업데이트합니다.

콜반 사용 제한 모달 기능

계층 / 파일(s) 요약
API 타입 및 엔드포인트
src/api/callvan/entity.ts, src/api/callvan/APIDetail.ts, src/api/callvan/index.ts, src/api/callvan/queries.ts
제한 상태 타입(CallvanRestrictionType, CallvanRestrictionResponse)과 GET /callvan/restriction 엔드포인트를 추가하고, 쿼리 키를 토큰 기반으로 변경합니다.
모달 컴포넌트
src/components/Callvan/components/CallvanRestrictionModal/index.tsx, src/components/Callvan/components/CallvanRestrictionModal/CallvanRestrictionModal.module.scss
제한 상태 정보를 표시하는 React 모달 컴포넌트와 해당 스타일을 추가합니다.
훅 및 유틸리티
src/components/Callvan/hooks/useCallvanRestrictionModal.tsx, src/components/Callvan/utils/callvanRestriction.ts
모달을 열고 닫기 위한 훅, 에러 타입 검사, 지역화된 모달 텍스트 생성 유틸을 추가합니다.
뮤테이션 통합
src/components/Callvan/hooks/useCreateCallvan.ts, src/components/Callvan/hooks/useJoinCallvan.ts, src/api/callvan/mutations.ts, src/pages/callvan/index.tsx, src/pages/callvan/notifications/index.tsx
콜반 생성/참여 뮤테이션의 에러 핸들러를 제한 모달 훅으로 통합하고, SSR 캐시 키를 정렬합니다.
기타
src/components/Callvan/components/AddPostForm/index.tsx, src/components/Callvan/components/CallvanPageLayout/index.tsx
시간 계산 로직을 명확화하고, 저자 필터링 로직을 수정합니다.

캠퍼스 정보 동적 아이콘 시스템

계층 / 파일(s) 요약
API 타입
src/api/coopshop/entity.ts
CoopShopDetailResponseicon_url: string | null 필드를 추가합니다.
ShopIcon 컴포넌트
src/components/CampusInfo/index.tsx, src/components/CampusInfo/CampusInfo.module.scss
백엔드 제공 이미지 URL을 렌더링하거나, 없으면 가게 이름의 첫 글자를 텍스트 폴백으로 표시하는 ShopIcon 컴포넌트를 추가합니다.
페이지 마이그레이션
src/pages/campusinfo/index.tsx
기존 페이지 구현을 components/CampusInfo로의 재내보내기로 간소화합니다.

마이너 리팩토링

계층 / 파일(s) 요약
시간표 헬퍼 추출
src/components/TimetablePage/components/MainTimetable/index.tsx
학기 및 시간표 존재 여부 검증 로직을 checkSemesterAndTimetable 도우미 함수로 추출합니다.
SSR 프리페치 추출
src/pages/timetable/index.tsx
시간표 SSR 프리페치 로직을 prefetchTimetableData 헬퍼로 분리합니다.
타입 정의 정제
src/components/TimetablePage/components/TimetableGridPlaceholder/index.tsx, src/pages/room/[id]/index.tsx, src/utils/ts/withCacheControl.ts
인터페이스를 함수 타입 별칭으로 변환하고, readonly 프로퍼티를 추가합니다.
스타일 개선
src/components/cafeteria/MobileCafeteriaPage/MobileCafeteriaPage.module.scss, src/components/cafeteria/MobileCafeteriaPage/components/MobileDiningBlocks/MobileDiningBlocks.module.scss
마진, 보더 반경, 오버플로우 스타일을 개선합니다.

예상 코드 리뷰 노력

🎯 4 (복잡함) | ⏱️ ~75분

관련 가능 PR

  • BCSDLab/KOIN_WEB_RECODE#1238: 콜반 제한 기능의 직접적인 구현 PR으로, 동일한 API 및 컴포넌트 변경 사항을 공유합니다.
  • BCSDLab/KOIN_WEB_RECODE#1240: 캠퍼스 정보의 아이콘 URL API 추가 및 CampusInfo 컴포넌트 업데이트와 관련이 있습니다.
  • BCSDLab/KOIN_WEB_RECODE#1236: SonarCloud 워크플로우 및 자동화 파이프라인 인프라 설정과 관련이 있습니다.

제안 라벨

✨ Feature, 📬 API, 🔨 Refactor, 🎓 Campus

제안 리뷰어

  • JeongWon-CHO
  • ParkSungju01
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 변경사항의 주요 내용을 포괄적으로 반영하고 있으며, 모바일 식단 페이지 디자인 수정, 콜밴 제한 사용자 모달 추가, 시설물 정보 API 응답 변경 등 핵심 변화를 명확히 요약하고 있습니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions github-actions Bot requested review from dooohun and hyejun0228 May 8, 2026 00:51
Copy link
Copy Markdown

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

🧹 Nitpick comments (3)
src/components/CampusInfo/index.tsx (3)

36-39: 💤 Low value

Props 타입을 type 대신 interface로 선언해주세요.

코딩 가이드라인에 따라 컴포넌트 Props 타입 정의에는 interface 사용이 권장됩니다.

♻️ 제안 수정
-type ShopIconProps = {
+interface ShopIconProps {
   readonly iconUrl: string | null | undefined;
   readonly name: string;
-};
+}

As per coding guidelines, src/components/**에서는 "Props 타입이 명확하게 정의되어 있는지(interface 사용 권장)"를 확인하도록 되어 있습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/CampusInfo/index.tsx` around lines 36 - 39, Replace the
ShopIconProps union type declaration with an interface declaration (keep the
same members and readonly modifiers) so the component uses an interface for its
props; update the declaration named ShopIconProps in this file to use "interface
ShopIconProps { readonly iconUrl: string | null | undefined; readonly name:
string; }" and ensure any imports/uses of ShopIconProps remain unchanged.

44-52: ⚡ Quick win

<img> 로드 실패 시 텍스트 폴백이 동작하지 않습니다.

iconUrl이 유효한 문자열이지만 실제 URL이 깨져있거나 404를 반환하는 경우, onError 핸들러가 없어 부서진 이미지 아이콘이 그대로 노출됩니다. useState로 오류 상태를 관리하거나 onError에서 src를 제거하는 방식으로 텍스트 폴백과 동일하게 동작하도록 처리해주세요.

🛡️ 제안 수정
+import { useState } from 'react';
+
 type ShopIconProps = {
   readonly iconUrl: string | null | undefined;
   readonly name: string;
 };

 function ShopIcon({ iconUrl, name }: ShopIconProps) {
+  const [imgError, setImgError] = useState(false);
+
   return (
     <div className={styles['icon-wrapper']}>
-      {iconUrl ? (
+      {iconUrl && !imgError ? (
         // eslint-disable-next-line `@next/next/no-img-element`
-        <img className={styles['icon-image']} src={iconUrl} alt={name} decoding="async" />
+        <img
+          className={styles['icon-image']}
+          src={iconUrl}
+          alt={name}
+          decoding="async"
+          onError={() => setImgError(true)}
+        />
       ) : (
         <span className={styles['icon-fallback']} aria-hidden="true">
           {name.slice(0, 1)}
         </span>
       )}
     </div>
   );
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/CampusInfo/index.tsx` around lines 44 - 52, The img element
using iconUrl doesn't handle broken image loads, so add an error fallback:
inside the CampusInfo component track a local boolean state (e.g., imageError)
via useState and set it true in an onError handler on the <img> (or
alternatively clear src in onError), then change the conditional that currently
checks iconUrl to also require !imageError (render the same <span
className={styles['icon-fallback']}> with {name.slice(0,1)} when imageError is
true); reference the existing iconUrl, name, styles['icon-image'] and
styles['icon-fallback'] to locate where to add the state and onError.

124-156: ⚖️ Poor tradeoff

API 응답의 시설물 수가 달라지면 레이아웃이 깨질 수 있습니다.

.slice(0, 2), .slice(2, 6), .slice(6) 방식으로 3개 컬럼에 고정 분배하고 있어, 백엔드에서 시설물 수가 변경되면 특정 컬럼이 비거나 마지막 컬럼에 아이템이 몰리는 현상이 발생할 수 있습니다. 현재 데이터가 고정적이라면 문제없으나, 향후 유연성을 위해 동적 분배 로직 도입을 검토해주세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/CampusInfo/index.tsx` around lines 124 - 156, The current
three-column rendering uses hardcoded slices (filteredCampusInfo.slice(0,2),
slice(2,6), slice(6)) which breaks layout when the number of items changes;
replace that with a small helper (e.g., a function named distributeIntoColumns
or chunkIntoColumns) that takes filteredCampusInfo and numberOfColumns = 3 and
returns an array of 3 arrays distributing items evenly (use round-robin or
compute per-column counts with Math.ceil/Math.floor) and then map over those
three arrays in the JSX instead of the fixed slices; update the JSX that maps
using ShopIcon and getFormattedShopTime to consume columns[columnIndex] rather
than the old slice calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/CLAUDE.md:
- Around line 71-74: Add a blank line immediately before and after the Markdown
table block in .claude/CLAUDE.md to satisfy MD058 (blanks-around-tables); locate
the table starting with the header line "**변경 이력:**" and the pipe header row "|
날짜 | 변경 내용 | 대상 | 사유 |" and insert one empty line above "**변경 이력:**" (or between
preceding paragraph and the table) and one empty line after the table closing
row to remove the lint warning.

In @.claude/skills/feature-planning/SKILL.md:
- Around line 56-66: Update the markdown code block that shows the component
tree (the block starting with "src/components/[FeatureName]/") to include a
language specifier by changing the opening fence from ``` to ```text (and leave
the closing fence unchanged) so the tree block is recognized as text and
resolves the MD040 warning.

In @.claude/skills/github-pr-creation/SKILL.md:
- Around line 37-43: Update the fenced code block that contains the commit
message examples (the block starting with ``` and the lines "feat: [기능명] 추가"
etc.) to include a language tag (e.g., ```text or ```bash) so markdownlint MD040
is satisfied; edit the code block delimiter in
.claude/skills/github-pr-creation/SKILL.md to add the chosen language without
changing the block content.

In @.claude/skills/koin-pipeline/SKILL.md:
- Line 39: Multiple fenced code blocks in SKILL.md are declared with bare ```
which triggers markdownlint MD040; update each backtick fence to include an
appropriate language hint (e.g., ```bash, ```json, ```text) to remove the
warning. Search for the bare triple-backtick blocks in SKILL.md (the occurrences
around the examples referenced) and change them to the correct language for
their contents so each fenced block (those repeated occurrences) has a language
specifier.
- Line 20: 현재 문서의 백업 명령 `mv _workspace/ _workspace_prev/`는 `_workspace_prev/`가
존재하면 실패하거나 덮어쓸 위험이 있으니 `_workspace_prev/` 대신 고유한 이름을 사용하도록 수정하세요: 문서 내 해당 문장(참조
`_workspace/`, `_workspace_prev/`, `mv _workspace/ _workspace_prev/`)을 타임스탬프 또는
유니크 ID를 포함한 폴더명(예: `_workspace_prev_<timestamp>` 또는
`_workspace_YYYYMMDD_HHMMSS`)으로 변경하도록 안내하고, 기존 이름 충돌을 피하기 위해 덮어쓰기 대신 새 이름으로 mv
하도록 설명을 덧붙이세요.

In @.claude/skills/qa-review/SKILL.md:
- Around line 25-29: Replace the non-deterministic "yarn dlx tsc --noEmit" and
the non-working "./node_modules/.bin/tsc" approach with commands that use the
pinned TypeScript in this repo: invoke the package script or the installed tsc
via Yarn PnP (e.g. run "yarn tsc --noEmit" or "yarn run tsc --noEmit"), or reuse
the package.json build script that contains "tsc && next build"; update the
documentation in SKILL.md to show one of these deterministic commands instead of
"yarn dlx tsc" and remove the ./node_modules path suggestion so QA uses the
project's locked TypeScript (v5.9.3).

In @.claude/skills/sonarcloud-analysis/SKILL.md:
- Around line 14-16: Update the placeholder SONAR_PROJECT_KEY example so it
matches the actual SonarCloud project key used elsewhere (replace the current
"koin_web_recode" example with the real project key string used in your other
docs/configs); ensure the SONAR_PROJECT_KEY variable value is identical to the
project key referenced by your SonarCloud organization and any related scripts
that call the issues API so the API calls succeed.

In @.github/workflows/sonarcloud.yml:
- Line 26: The workflow uses the mutable ref
"SonarSource/sonarcloud-github-action@master" which is a supply-chain risk;
replace the mutable tag with an immutable release tag or commit SHA (e.g.,
change uses: SonarSource/sonarcloud-github-action@master to uses:
SonarSource/sonarcloud-github-action@vX.Y.Z or uses:
SonarSource/sonarcloud-github-action@<commit-sha>) so the action is pinned to a
fixed, auditable version.
- Around line 20-24: The workflow lacks a Corepack activation step, so the
runner may use an unpredictable Yarn version; add a step after the
actions/setup-node@v4 step to enable Corepack and prepare the pinned Yarn
version from package.json (e.g., run corepack enable && corepack prepare
yarn@4.10.3 --activate) before the "Install package" / yarn step so the CI uses
Yarn 4 consistently.

In `@src/api/callvan/mutations.ts`:
- Around line 21-22: The literal queryKey in invalidateCallvanNotifications
should use the central factory constant instead of a hardcoded array: add
notificationsRoot: ['callvan', 'notifications'] as const to callvanQueryKeys in
queries.ts, then update invalidateCallvanNotifications (the function taking
QueryClient) to call queryClient.invalidateQueries({ queryKey:
callvanQueryKeys.notificationsRoot }) so it matches the other invalidations and
centralizes the key definition.

In `@src/api/callvan/queries.ts`:
- Around line 15-16: The query keys notifications and restriction currently
embed the raw token; change the functions notifications and restriction to omit
the token from the returned queryKey (e.g., return ['callvan','notifications']
as const and ['callvan','restriction'] as const) and keep the token only as a
queryFn parameter so the token is consumed inside the request function (preserve
existing queryFn signatures to accept token). Also update any query option
helpers that call these keys (the option functions referenced near the other key
usages) so they call notifications()/restriction() without passing the token
into the key generation, and ensure they pass the token only into the queryFn
when invoking or constructing fetch callbacks.

In `@src/api/coopshop/entity.ts`:
- Line 27: The icon_url field is currently declared as required (icon_url:
string | null) but can be undefined in older backend responses or fixtures;
update the declaration to make icon_url optional (add ? to the icon_url
property) so the TypeScript type matches possible runtime values (string | null
| undefined) and adjust any consuming code if it relies on icon_url always being
present; locate the icon_url property in the entity/interface in
src/api/coopshop/entity.ts and change its signature to be optional.

In
`@src/components/Callvan/components/CallvanRestrictionModal/CallvanRestrictionModal.module.scss`:
- Around line 81-96: The button class &__button in
CallvanRestrictionModal.module.scss lacks a :focus-visible rule, so add a
:focus-visible selector for .&__button that displays a clear, high-contrast
focus indicator (e.g., a visible outline or ring using outline or box-shadow
that respects the existing border-radius and uses accessible color contrast),
ensure the focus style does not change layout (use outline-offset or box-shadow)
and include ::focus-visible alongside :focus fallback for broad browser support;
update the SCSS near the &__button block so keyboard users can perceive focus in
the CallvanRestrictionModal.

In `@src/components/Callvan/utils/callvanRestriction.ts`:
- Around line 18-22: The current isCallvanRestrictedError only checks
isKoinError and status === CALLVAN_RESTRICTED_ERROR_STATUS which can misclassify
generic 403s; update isCallvanRestrictedError to also verify the KoinError.code
against a Callvan-specific identifier (e.g., CALLVAN_RESTRICTION_CODE) so it
returns true only when both status and code match; reference the existing
isKoinError, CALLVAN_RESTRICTED_ERROR_STATUS, and (add/use)
CALLVAN_RESTRICTION_CODE constants, and keep
useCallvanRestrictionModal.openFromError’s existing double-checks but make the
utility function defensively validate both fields.

In `@src/pages/timetable/index.tsx`:
- Around line 49-57: The prefetch uses validatedFrameId without checking if it
exists in the timetableFrameList, which can cause
timetableQueries.lectureInfo(token, currentFrameId) to fail during SSR; update
the logic that sets currentFrameId so it only uses validatedFrameId when that ID
is present in timetableFrameList, otherwise fall back to mainFrame?.id (or
null), then build prefetchPromises accordingly before pushing
timetableQueries.lectureInfo(token, currentFrameId); ensure you reference
validatedFrameId, timetableFrameList, mainFrame, currentFrameId and
prefetchPromises when applying this guard.

---

Nitpick comments:
In `@src/components/CampusInfo/index.tsx`:
- Around line 36-39: Replace the ShopIconProps union type declaration with an
interface declaration (keep the same members and readonly modifiers) so the
component uses an interface for its props; update the declaration named
ShopIconProps in this file to use "interface ShopIconProps { readonly iconUrl:
string | null | undefined; readonly name: string; }" and ensure any imports/uses
of ShopIconProps remain unchanged.
- Around line 44-52: The img element using iconUrl doesn't handle broken image
loads, so add an error fallback: inside the CampusInfo component track a local
boolean state (e.g., imageError) via useState and set it true in an onError
handler on the <img> (or alternatively clear src in onError), then change the
conditional that currently checks iconUrl to also require !imageError (render
the same <span className={styles['icon-fallback']}> with {name.slice(0,1)} when
imageError is true); reference the existing iconUrl, name, styles['icon-image']
and styles['icon-fallback'] to locate where to add the state and onError.
- Around line 124-156: The current three-column rendering uses hardcoded slices
(filteredCampusInfo.slice(0,2), slice(2,6), slice(6)) which breaks layout when
the number of items changes; replace that with a small helper (e.g., a function
named distributeIntoColumns or chunkIntoColumns) that takes filteredCampusInfo
and numberOfColumns = 3 and returns an array of 3 arrays distributing items
evenly (use round-robin or compute per-column counts with Math.ceil/Math.floor)
and then map over those three arrays in the JSX instead of the fixed slices;
update the JSX that maps using ShopIcon and getFormattedShopTime to consume
columns[columnIndex] rather than the old slice calls.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e54a9543-ff25-4465-a55c-1f5dffffb7eb

📥 Commits

Reviewing files that changed from the base of the PR and between cfad22b and 43b6a39.

⛔ Files ignored due to path filters (48)
  • .pnp.cjs is excluded by !.pnp.cjs and included by **
  • .yarn/cache/@babel-runtime-npm-7.23.9-3b96e23cc2-9a520fe1bf.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/@babel-runtime-npm-7.29.2-b49cad1c67-f55ba4052a.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/@esbuild-darwin-arm64-npm-0.27.2-d675c4a521-10.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/@esbuild-linux-x64-npm-0.27.2-11f1a3d9db-10.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/@img-sharp-darwin-arm64-npm-0.34.3-8944698b4c-10.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/@img-sharp-libvips-darwin-arm64-npm-1.2.0-2d65006be7-10.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/@img-sharp-libvips-linux-x64-npm-1.2.0-91cf635ac8-10.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/@img-sharp-linux-x64-npm-0.34.3-aa297ca1ca-10.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/@next-swc-linux-x64-gnu-npm-15.5.15-39e2669d0f-10.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/@rollup-rollup-darwin-arm64-npm-4.59.0-db3495ba42-10.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/@rollup-rollup-linux-x64-gnu-npm-4.59.0-da6c703f69-10.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/@sentry-cli-darwin-npm-2.45.0-76059cfa9f-10.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/@sentry-cli-darwin-npm-2.58.5-1f667e3b9d-10.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/@sentry-cli-linux-x64-npm-2.45.0-6e5f26280b-10.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/@sentry-cli-linux-x64-npm-2.58.5-7cea7778bc-10.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/@trysound-sax-npm-0.2.0-9f763d0295-7379713eca.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/@unrs-resolver-binding-darwin-arm64-npm-1.11.1-c78d4bd2cb-10.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/@unrs-resolver-binding-linux-x64-gnu-npm-1.11.1-93a00570de-10.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/axios-npm-0.31.0-c3cc4d3e42-80536a915c.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/axios-npm-0.31.1-0574a0de7d-097dffdc0a.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/brace-expansion-npm-1.1.11-fb95eb05ad-faf34a7bb0.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/brace-expansion-npm-1.1.14-a997f4f4e7-2de747a589.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/braces-npm-3.0.2-782240b28a-966b1fb48d.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/braces-npm-3.0.3-582c14023c-fad11a0d46.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/fill-range-npm-7.0.1-b8b1817caa-e260f7592f.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/fill-range-npm-7.1.1-bf491486db-a7095cb39e.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/flatted-npm-3.2.9-0462256d3c-dc2b89e46a.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/flatted-npm-3.4.2-e32280259b-a9e78fe5c2.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/immutable-npm-4.3.5-5958499808-dbc1b8c808.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/immutable-npm-4.3.8-9db2d34fb7-27a134cec0.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/js-yaml-npm-3.14.1-b968c6095e-9e22d80b4d.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/js-yaml-npm-3.14.2-debd9d20c3-172e0b6007.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/lodash-es-npm-4.18.1-02cf41b912-8bfad225ef.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/lodash-npm-4.18.1-a64c3070ac-306fea53df.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/minimatch-npm-3.1.2-9405269906-e0b25b04cd.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/minimatch-npm-3.1.5-86958baf50-b11a7ee577.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/minimatch-npm-9.0.3-69d7d6fad5-c81b47d281.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/picomatch-npm-2.3.1-c782cfd986-60c2595003.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/picomatch-npm-2.3.2-4d85543a37-b788ef8148.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/postcss-npm-8.4.47-2f4d4be1fa-f2b50ba9b6.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/postcss-npm-8.5.14-1cf8d01c78-2e3f4dea69.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/regenerator-runtime-npm-0.14.1-a6c97c609a-5db3161abb.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/sax-npm-1.6.0-39dc3ef158-0909cedcd9.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/svgo-npm-3.3.2-69e1d32944-82fdea9b93.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/svgo-npm-3.3.3-f4851edd74-f3c1b4d05d.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • .yarn/cache/tar-npm-6.2.1-237800bb20-bfbfbb2861.zip is excluded by !**/.yarn/**, !**/*.zip, !.yarn/** and included by **
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock, !yarn.lock and included by **
📒 Files selected for processing (45)
  • .claude/CLAUDE.md
  • .claude/agents/code-quality-fixer.md
  • .claude/agents/feature-implementer.md
  • .claude/agents/feature-planner.md
  • .claude/agents/pr-creator.md
  • .claude/agents/qa-reviewer.md
  • .claude/agents/sonarcloud-analyzer.md
  • .claude/skills/code-quality-fix/SKILL.md
  • .claude/skills/feature-implementation/SKILL.md
  • .claude/skills/feature-planning/SKILL.md
  • .claude/skills/github-pr-creation/SKILL.md
  • .claude/skills/koin-pipeline/SKILL.md
  • .claude/skills/qa-review/SKILL.md
  • .claude/skills/sonarcloud-analysis/SKILL.md
  • .claude/skills/sonarcloud-analysis/references/sonarcloud-setup.md
  • .github/workflows/sonarcloud.yml
  • .gitignore
  • package.json
  • sonar-project.properties
  • src/api/callvan/APIDetail.ts
  • src/api/callvan/entity.ts
  • src/api/callvan/index.ts
  • src/api/callvan/mutations.ts
  • src/api/callvan/queries.ts
  • src/api/coopshop/entity.ts
  • src/components/Callvan/components/AddPostForm/index.tsx
  • src/components/Callvan/components/CallvanPageLayout/index.tsx
  • src/components/Callvan/components/CallvanRestrictionModal/CallvanRestrictionModal.module.scss
  • src/components/Callvan/components/CallvanRestrictionModal/index.tsx
  • src/components/Callvan/hooks/useCallvanRestrictionModal.tsx
  • src/components/Callvan/hooks/useCreateCallvan.ts
  • src/components/Callvan/hooks/useJoinCallvan.ts
  • src/components/Callvan/utils/callvanRestriction.ts
  • src/components/CampusInfo/CampusInfo.module.scss
  • src/components/CampusInfo/index.tsx
  • src/components/TimetablePage/components/MainTimetable/index.tsx
  • src/components/TimetablePage/components/TimetableGridPlaceholder/index.tsx
  • src/components/cafeteria/MobileCafeteriaPage/MobileCafeteriaPage.module.scss
  • src/components/cafeteria/MobileCafeteriaPage/components/MobileDiningBlocks/MobileDiningBlocks.module.scss
  • src/pages/callvan/index.tsx
  • src/pages/callvan/notifications/index.tsx
  • src/pages/campusinfo/index.tsx
  • src/pages/room/[id]/index.tsx
  • src/pages/timetable/index.tsx
  • src/utils/ts/withCacheControl.ts

Comment thread .claude/CLAUDE.md
Comment on lines +71 to +74
**변경 이력:**
| 날짜 | 변경 내용 | 대상 | 사유 |
|------|----------|------|------|
| 2026-04-21 | 초기 구성 | 전체 | SonarCloud 품질 관리 + 기능 구현 자동화 파이프라인 |
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

표 앞뒤 공백 줄을 추가해 Markdown lint 경고를 제거하세요.

현재 MD058(blanks-around-tables) 경고가 발생합니다.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 72-72: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/CLAUDE.md around lines 71 - 74, Add a blank line immediately before
and after the Markdown table block in .claude/CLAUDE.md to satisfy MD058
(blanks-around-tables); locate the table starting with the header line "**변경
이력:**" and the pipe header row "| 날짜 | 변경 내용 | 대상 | 사유 |" and insert one empty
line above "**변경 이력:**" (or between preceding paragraph and the table) and one
empty line after the table closing row to remove the lint warning.

Comment on lines +56 to +66
```
src/components/[FeatureName]/
├── index.tsx # 메인 컴포넌트
├── [FeatureName].module.scss # 스타일
├── hooks/
│ └── use[FeatureName].ts # 데이터 페칭 훅
├── PCView/ # PC 전용 뷰 (레이아웃이 크게 다를 때)
│ └── PCView.tsx
└── MobileView/ # 모바일 전용 뷰
└── MobileView.tsx
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

디렉터리 트리 코드블록에 language를 지정해주세요.

Line 56 코드블록이 language 없이 선언되어 markdownlint(MD040) 경고가 발생합니다. 트리 구조는 text 지정이 가장 무난합니다.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 56-56: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/skills/feature-planning/SKILL.md around lines 56 - 66, Update the
markdown code block that shows the component tree (the block starting with
"src/components/[FeatureName]/") to include a language specifier by changing the
opening fence from ``` to ```text (and leave the closing fence unchanged) so the
tree block is recognized as text and resolves the MD040 warning.

Comment on lines +37 to +43
```
feat: [기능명] 추가
fix: [이슈] 수정
refactor: [대상] 리팩토링
style: 코드 스타일 수정 (SonarCloud 이슈 해결)
chore: 기타 변경사항
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

컨벤셔널 커밋 예시 코드블록에 language를 지정해주세요.

Line 37 코드블록이 language 없이 선언되어 markdownlint(MD040) 경고가 발생합니다. text 또는 bash로 지정해주세요.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 37-37: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/skills/github-pr-creation/SKILL.md around lines 37 - 43, Update the
fenced code block that contains the commit message examples (the block starting
with ``` and the lines "feat: [기능명] 추가" etc.) to include a language tag (e.g.,
```text or ```bash) so markdownlint MD040 is satisfied; edit the code block
delimiter in .claude/skills/github-pr-creation/SKILL.md to add the chosen
language without changing the block content.

|------|------|
| `_workspace/` 없음 | 신규 실행 → Phase 1로 |
| `_workspace/` 있고 부분 수정 요청 | 부분 재실행 → 해당 Phase부터 |
| `_workspace/` 있고 새 입력 | `mv _workspace/ _workspace_prev/` 후 신규 실행 |
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

_workspace 백업 시 기존 백업 덮어쓰기/충돌 위험을 제거해주세요.

Line 20의 mv _workspace/ _workspace_prev/_workspace_prev/가 이미 있을 때 실패하거나 이전 백업을 잃을 수 있습니다. 타임스탬프 백업으로 바꾸는 게 안전합니다.

제안 패치
-| `_workspace/` 있고 새 입력 | `mv _workspace/ _workspace_prev/` 후 신규 실행 |
+| `_workspace/` 있고 새 입력 | `mv _workspace/ "_workspace_prev_$(date +%Y%m%d_%H%M%S)/"` 후 신규 실행 |
📝 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
| `_workspace/` 있고 새 입력 | `mv _workspace/ _workspace_prev/` 후 신규 실행 |
| `_workspace/` 있고 새 입력 | `mv _workspace/ "_workspace_prev_$(date +%Y%m%d_%H%M%S)/"` 후 신규 실행 |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/skills/koin-pipeline/SKILL.md at line 20, 현재 문서의 백업 명령 `mv
_workspace/ _workspace_prev/`는 `_workspace_prev/`가 존재하면 실패하거나 덮어쓸 위험이 있으니
`_workspace_prev/` 대신 고유한 이름을 사용하도록 수정하세요: 문서 내 해당 문장(참조 `_workspace/`,
`_workspace_prev/`, `mv _workspace/ _workspace_prev/`)을 타임스탬프 또는 유니크 ID를 포함한
폴더명(예: `_workspace_prev_<timestamp>` 또는 `_workspace_YYYYMMDD_HHMMSS`)으로 변경하도록
안내하고, 기존 이름 충돌을 피하기 위해 덮어쓰기 대신 새 이름으로 mv 하도록 설명을 덧붙이세요.


### A-1. SonarCloud Analyzer 실행

```
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fenced code block에 language를 지정해 markdownlint(MD040) 경고를 없애주세요.

현재 문서의 코드 블록들이 language 없이 선언되어 lint 경고가 반복됩니다. bash, json, text 등으로 명시하면 됩니다.

Also applies to: 54-54, 69-69, 86-86, 105-105, 123-123, 138-138, 155-155, 179-179

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 39-39: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/skills/koin-pipeline/SKILL.md at line 39, Multiple fenced code
blocks in SKILL.md are declared with bare ``` which triggers markdownlint MD040;
update each backtick fence to include an appropriate language hint (e.g.,
```bash, ```json, ```text) to remove the warning. Search for the bare
triple-backtick blocks in SKILL.md (the occurrences around the examples
referenced) and change them to the correct language for their contents so each
fenced block (those repeated occurrences) has a language specifier.

Comment on lines +15 to +16
notifications: (token: string) => ['callvan', 'notifications', token] as const,
restriction: (token: string) => ['callvan', 'restriction', token] as const,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# queryKey에 토큰(민감값)이 직접 포함되는 지점 점검 (read-only)
rg -nP "callvanQueryKeys\.(notifications|restriction)\(token\)|\['callvan',\s*'(notifications|restriction)',\s*token\]" src -C2

Repository: BCSDLab/KOIN_WEB_RECODE

Length of output: 1393


🏁 Script executed:

# Check for other token-based queryKey patterns in the api folder
rg -nP "queryKey.*token\)|token.*as const" src/api --type ts

Repository: BCSDLab/KOIN_WEB_RECODE

Length of output: 1849


🏁 Script executed:

# Check invalidation patterns in callvan queries file
rg -n "invalidate|queryKey" src/api/callvan/queries.ts -A2 -B2

Repository: BCSDLab/KOIN_WEB_RECODE

Length of output: 1314


queryKey에 raw 토큰을 포함하지 마세요.

Line 15–16에서 인증 토큰을 queryKey 세그먼트에 직접 포함하면 React Query Devtools, 로그, 메모리 덤프 등에서 민감값이 노출될 수 있습니다. 같은 파일의 list, infiniteList, postDetail, chat 등 다른 쿼리는 이미 토큰을 queryKey에서 제외하고 queryFn에서만 사용하는 패턴을 따르고 있으므로, notificationsrestriction도 동일하게 수정해야 합니다.

수정 예시
-  notifications: (token: string) => ['callvan', 'notifications', token] as const,
-  restriction: (token: string) => ['callvan', 'restriction', token] as const,
+  notifications: () => ['callvan', 'notifications'] as const,
+  restriction: () => ['callvan', 'restriction'] as const,

그리고 쿼리 옵션 함수들에서도 해당 queryKey 호출 시 토큰 인자를 제거하세요 (Line 49, 56).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/callvan/queries.ts` around lines 15 - 16, The query keys
notifications and restriction currently embed the raw token; change the
functions notifications and restriction to omit the token from the returned
queryKey (e.g., return ['callvan','notifications'] as const and
['callvan','restriction'] as const) and keep the token only as a queryFn
parameter so the token is consumed inside the request function (preserve
existing queryFn signatures to accept token). Also update any query option
helpers that call these keys (the option functions referenced near the other key
usages) so they call notifications()/restriction() without passing the token
into the key generation, and ensure they pass the token only into the queryFn
when invoking or constructing fetch callbacks.

phone: string | null;
location: string;
remarks: string | null;
icon_url: string | null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

icon_url 필드를 선택적(?)으로 표시하는 것을 고려하세요.

백엔드가 아직 이 필드를 내려주지 않는 이전 응답이나 테스트 픽스처에서는 undefined가 될 수 있습니다. 타입이 string | null(필수)로 선언되어 있으면 그런 경우 타입 불일치가 생기지만, 런타임에서는 ShopIconPropsstring | null | undefined를 허용하기 때문에 조용히 처리됩니다. 백엔드가 항상 이 필드를 반환한다고 보장된다면 현행 유지가 맞지만, 그렇지 않다면 ?를 붙여 타입과 실제 데이터를 일치시켜 주세요.

💡 제안 수정
-  icon_url: string | null;
+  icon_url?: string | null;
📝 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
icon_url: string | null;
icon_url?: string | null;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/coopshop/entity.ts` at line 27, The icon_url field is currently
declared as required (icon_url: string | null) but can be undefined in older
backend responses or fixtures; update the declaration to make icon_url optional
(add ? to the icon_url property) so the TypeScript type matches possible runtime
values (string | null | undefined) and adjust any consuming code if it relies on
icon_url always being present; locate the icon_url property in the
entity/interface in src/api/coopshop/entity.ts and change its signature to be
optional.

Comment on lines +81 to +96
&__button {
display: flex;
width: 100%;
align-items: center;
justify-content: center;
border: none;
border-radius: 8px;
background: $callvan-restriction-accent-color;
padding: 12px;
color: #fff;
font-family: $callvan-restriction-font-family;
font-size: 15px;
font-weight: 500;
line-height: 1.6;
cursor: pointer;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

키보드 포커스 가시성이 없습니다

Line 81-96의 버튼에 :focus-visible 스타일이 없어 키보드 사용자가 현재 포커스를 인지하기 어렵습니다. 모달은 접근성 민감 구간이라 포커스 링을 명시해 주세요.

제안 diff
   &__button {
@@
     cursor: pointer;
+
+    &:focus-visible {
+      outline: 2px solid `#111`;
+      outline-offset: 2px;
+    }
   }

As per coding guidelines src/components/**: 4. 접근성(a11y) 관련 속성이 적절히 사용되고 있는지 확인해주세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/components/Callvan/components/CallvanRestrictionModal/CallvanRestrictionModal.module.scss`
around lines 81 - 96, The button class &__button in
CallvanRestrictionModal.module.scss lacks a :focus-visible rule, so add a
:focus-visible selector for .&__button that displays a clear, high-contrast
focus indicator (e.g., a visible outline or ring using outline or box-shadow
that respects the existing border-radius and uses accessible color contrast),
ensure the focus style does not change layout (use outline-offset or box-shadow)
and include ::focus-visible alongside :focus fallback for broad browser support;
update the SCSS near the &__button block so keyboard users can perceive focus in
the CallvanRestrictionModal.

Comment on lines +18 to +22
export function isCallvanRestrictedError(error: unknown): boolean {
if (!isKoinError(error)) return false;

return error.status === CALLVAN_RESTRICTED_ERROR_STATUS;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 목적: 제한 모달 트리거 조건이 403 단독인지, 제한 전용 코드가 이미 존재하는지 확인
# 기대: restriction 관련 식별자(code/type/message) 사용 여부를 확인할 수 있어야 함

rg -n -C3 "isCallvanRestrictedError|CALLVAN_RESTRICTED_ERROR_STATUS|status === 403" src/components src/api
rg -n -C3 "restriction|RESTRICT|FORBIDDEN|isKoinError|error\\.code|error\\.status" src/components src/api

Repository: BCSDLab/KOIN_WEB_RECODE

Length of output: 50380


🏁 Script executed:

# Check isKoinError type definition and available fields
rg -n "interface.*KoinError|type.*KoinError" --type ts -A5

# Check how the error is used in useCallvanRestrictionModal to see if there's additional context
cat -n src/components/Callvan/hooks/useCallvanRestrictionModal.tsx | head -50

# Look at the CallvanRestrictionResponse API call and how it's structured
cat -n src/api/callvan/APIDetail.ts | grep -A10 "GetCallvanRestriction"

# Check if there's error message or code in the Koin error that could distinguish
rg -n "error\\.message|error\\.code" src/components/Callvan -A2 -B2

Repository: BCSDLab/KOIN_WEB_RECODE

Length of output: 2560


🏁 Script executed:

# Check if there's a specific error code constant for Callvan restrictions
rg -n "CALLVAN|restriction|code.*=" src/components/Callvan src/api/callvan -i

# Check backend error codes or constants that might indicate restriction
rg -n "const.*403|const.*code|error.*code" src/api src/constants -B1 -A1 | head -40

# Look for any error code usage in similar functions
rg -n "error\\.code" src/components -A2 -B2 | head -50

Repository: BCSDLab/KOIN_WEB_RECODE

Length of output: 50451


error.code를 함께 확인하거나 에러 식별자를 추가로 검증하세요

현재 status === 403 단독 판별은 Callvan 특화 제한과 다른 Forbidden 에러(비밀번호 오류, 권한 부족 등)를 구분하지 못합니다. KoinErrorcode 필드를 지원하므로, 제한 전용 코드가 있다면 다음과 같이 조건을 강화할 수 있습니다:

export function isCallvanRestrictedError(error: unknown): boolean {
  if (!isKoinError(error)) return false;

  // 제한 전용 코드가 정의되어 있으면 추가 조건으로 검증
  return error.status === CALLVAN_RESTRICTED_ERROR_STATUS && error.code === CALLVAN_RESTRICTION_CODE;
}

useCallvanRestrictionModalopenFromError에서 제한 상태를 재확인하는 로직이 이미 있어 이중 방어가 되고 있으나, 유틸리티 함수 자체는 다운스트림 검증에 의존하지 않도록 설계하는 것이 안정성 개선에 도움이 됩니다. 백엔드와 확인하여 제한 전용 코드 값이 있으면 반영해주세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/Callvan/utils/callvanRestriction.ts` around lines 18 - 22, The
current isCallvanRestrictedError only checks isKoinError and status ===
CALLVAN_RESTRICTED_ERROR_STATUS which can misclassify generic 403s; update
isCallvanRestrictedError to also verify the KoinError.code against a
Callvan-specific identifier (e.g., CALLVAN_RESTRICTION_CODE) so it returns true
only when both status and code match; reference the existing isKoinError,
CALLVAN_RESTRICTED_ERROR_STATUS, and (add/use) CALLVAN_RESTRICTION_CODE
constants, and keep useCallvanRestrictionModal.openFromError’s existing
double-checks but make the utility function defensively validate both fields.

Comment on lines +49 to +57
const currentFrameId = validatedFrameId ?? mainFrame?.id ?? null;

const prefetchPromises = [
queryClient.prefetchQuery(timetableQueries.semesterInfo()),
queryClient.prefetchQuery(deptQueries.list()),
];

if (currentFrameId !== null) {
prefetchPromises.push(queryClient.prefetchQuery(timetableQueries.lectureInfo(token, currentFrameId)));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

존재하지 않는 frameId로 SSR prefetch가 실패할 수 있습니다.

Line 49-57에서 validatedFrameId를 목록 존재 여부 확인 없이 우선 사용하고 있어, 실제 timetableFrameList에 없는 ID면 lectureInfo prefetch에서 실패하고 SSR이 깨질 수 있습니다.
validatedFrameId가 리스트에 존재할 때만 사용하고, 아니면 mainFrame?.id로 fallback 하도록 보완하는 게 안전합니다.

수정 예시
-    const mainFrame = timetableFrameList.find((frame) => frame.is_main);
-    const currentFrameId = validatedFrameId ?? mainFrame?.id ?? null;
+    const mainFrame = timetableFrameList.find((frame) => frame.is_main);
+    const hasValidatedFrame = validatedFrameId !== null && timetableFrameList.some((frame) => frame.id === validatedFrameId);
+    const currentFrameId = hasValidatedFrame ? validatedFrameId : (mainFrame?.id ?? null);
📝 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 currentFrameId = validatedFrameId ?? mainFrame?.id ?? null;
const prefetchPromises = [
queryClient.prefetchQuery(timetableQueries.semesterInfo()),
queryClient.prefetchQuery(deptQueries.list()),
];
if (currentFrameId !== null) {
prefetchPromises.push(queryClient.prefetchQuery(timetableQueries.lectureInfo(token, currentFrameId)));
const mainFrame = timetableFrameList.find((frame) => frame.is_main);
const hasValidatedFrame = validatedFrameId !== null && timetableFrameList.some((frame) => frame.id === validatedFrameId);
const currentFrameId = hasValidatedFrame ? validatedFrameId : (mainFrame?.id ?? null);
const prefetchPromises = [
queryClient.prefetchQuery(timetableQueries.semesterInfo()),
queryClient.prefetchQuery(deptQueries.list()),
];
if (currentFrameId !== null) {
prefetchPromises.push(queryClient.prefetchQuery(timetableQueries.lectureInfo(token, currentFrameId)));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/timetable/index.tsx` around lines 49 - 57, The prefetch uses
validatedFrameId without checking if it exists in the timetableFrameList, which
can cause timetableQueries.lectureInfo(token, currentFrameId) to fail during
SSR; update the logic that sets currentFrameId so it only uses validatedFrameId
when that ID is present in timetableFrameList, otherwise fall back to
mainFrame?.id (or null), then build prefetchPromises accordingly before pushing
timetableQueries.lectureInfo(token, currentFrameId); ensure you reference
validatedFrameId, timetableFrameList, mainFrame, currentFrameId and
prefetchPromises when applying this guard.

Copy link
Copy Markdown
Member

@kongwoojin kongwoojin left a comment

Choose a reason for hiding this comment

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

고생하셨습니다

@ff1451 ff1451 merged commit 0c4c41d into main May 8, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🌏 Deploy 배포 관련 사항

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants