Skip to content

[Feat] WTH-249 : 출석 api 연결#43

Merged
nabbang6 merged 26 commits intodevelopfrom
WTH-249-출석-api-연결
Apr 12, 2026

Hidden character warning

The head ref may contain hidden characters: "WTH-249-\ucd9c\uc11d-api-\uc5f0\uacb0"
Merged

[Feat] WTH-249 : 출석 api 연결#43
nabbang6 merged 26 commits intodevelopfrom
WTH-249-출석-api-연결

Conversation

@nabbang6
Copy link
Copy Markdown
Collaborator

@nabbang6 nabbang6 commented Apr 9, 2026

✅ PR 유형

어떤 변경 사항이 있었나요?

  • 새로운 기능 추가
  • 버그 수정
  • 코드에 영향을 주지 않는 변경사항(오타 수정, 탭 사이즈 변경, 변수명 변경)
  • 코드 리팩토링
  • 주석 추가 및 수정
  • 문서 수정
  • 빌드 부분 혹은 패키지 매니저 수정
  • 파일 혹은 폴더명 수정
  • 파일 혹은 폴더 삭제

📌 관련 이슈번호

  • Closed #249

✅ Key Changes

출석 api

  • 출석 조회 API 연결
  • 출석 체크인 API 연결: attendanceApi.checkIn을 통한 수동 코드 입력 출석 처리 구현
  • QR 코드 출석 기능 구현: qr-code-styling 라이브러리를 사용한 QR 코드 생성 및 useQRCheckIn 훅을 통한 QR 스캔 출석 처리
  • QR 코드 생성 API 연결: 관리자용 QR 코드 생성 API(generateQR) 연결 및 useQRCode 훅 추가
  • useUserRole 기반 권한 판단: isAdmin을 store의 useUserRole로 판단하도록 변경
  • 프록시 리다이렉트 개선: 로그인 리다이렉트 시 query string도 함께 보존

user/club 정보를 (main) 하위 페이지 접근 가능하게 수정

  • UserHydrator 추가: (main) layout에서 서버에서 가져온 user/club 정보를 Zustand store에 hydrate하여, 하위 페이지가 홈을 거치지 않아도 사용자/클럽 정보에 접근 가능
  • 타입 리팩토링: UserInfo, Role 등 범용 타입을 types/user.ts로 분리하고, ClubIdentifier를
    types/club.ts로 이동하여 중복 제거

QR을 통한 출석 플로우

  • QR 코드 카메라로 인식 -> 로그인하지 않은 사용자는 로그인 페이지로 이동 -> 로그인 후 출석 페이지로 리다이렉트 -> 출석 checkIn API 호출 -> 출석 완료 모달 표시

요렇게 되게 구현해두었습니다!
다만 배포 이후에 잘 되는지 테스트가 필요한,,,

📸 스크린샷 or 실행영상

/attendance에서 확인 가능합니다!
qr 코드는 아래처럼 생성됩니당

image

🎸 기타 사항 or 추가 코멘트

  • clubId가 'YUNJcjFKMO'로 하드코딩되어 있으며, 추후 동적으로 변경 필요 (TODO 표시)
  • home.server.ts를 추가하여 서버 컴포넌트에서 홈 대시보드 API를 직접 호출하는 패턴 도입

슬랙으로 말씀드렷던 문제 상황 중 1번만 반영햇습니다!!
2번 clubId 관련도..빠르게 수정해보겟습니다 ㅠ ㅠ

Summary by CodeRabbit

  • 새로운 기능

    • QR 코드 생성·스캔 기반 출석 체크 및 자동 처리(실시간 QR 생성/재생성 포함)
    • 관리자(ADMIN) 권한 지원 및 역할 기반 접근 제어
    • 서버에서 실시간 출석/히스토리 로드
  • 버그 수정

    • 중복 출석 체크 방지 및 QR 만료 시 자동 갱신
    • 출석 관련 실패 시 사용자 친화적 오류 안내(토스트)
  • 개선 사항

    • 출석 확인 흐름과 버튼 상태 개선(비활성화/네비게이션 개선)
    • 레이아웃 초기 사용자·동아리 정보 동기화로 UX 안정성 향상

@nabbang6 nabbang6 requested review from JIN921, dalzzy and woneeeee April 9, 2026 08:30
@nabbang6 nabbang6 self-assigned this Apr 9, 2026
@nabbang6 nabbang6 added 📬 API 서버 API 통신 ✨ Feature 기능 개발 labels Apr 9, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 9, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f88e7bd4-1d56-48c0-b936-8b76eaacae82

📥 Commits

Reviewing files that changed from the base of the PR and between 9735cbc and 4bd7b89.

📒 Files selected for processing (1)
  • src/hooks/useRemainingTime.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/hooks/useRemainingTime.ts

📝 Walkthrough

Walkthrough

패키지에 QR 코드 스타일링 라이브러리를 추가하고, 출석 관련 페이지와 레이아웃을 서버 기반 비동기 컴포넌트로 전환했습니다. 클럽 ID를 쿠키에서 읽어 서버 API로 대시보드·출석·요약을 조회하고, QR 생성·체크인 훅과 에러/토스트 흐름을 통합했습니다.

Changes

Cohort / File(s) Summary
의존성 추가
package.json
qr-code-styling v^1.9.2 추가
출석 페이지 서버 구성요소 전환
src/app/(private)/(main)/attendance/page.tsx, src/app/(private)/(main)/attendance/history/page.tsx, src/app/(private)/(main)/attendance/qr/page.tsx
동기 → async 서버 컴포넌트. 쿠키의 clubId 사용, 서버 API 호출로 실데이터 로드, 에러 메시지/리다이렉트 처리 추가
레이아웃 비동기 처리 및 사용자 하이드레이션
src/app/(private)/(main)/layout.tsx, src/providers/user-hydrator.tsx
MainLayout을 서버 컴포넌트로 변경해 dashboard 조회 후 UserHydrator로 클라이언트 스토어 초기화
출석 API(서버/클라이언트)
src/lib/apis/attendance.server.ts, src/lib/apis/attendance.ts, src/lib/apis/home.server.ts
서버용 getAttendance/getDetail, 클라이언트용 checkIn/generateQR 추가, 홈 대시보드 서버 API(재검증/태그) 추가
QR 관련 훅 및 로직
src/hooks/useQRCode.ts, src/hooks/useAttendanceQR.ts, src/hooks/useQRCheckIn.ts
QR 데이터 페칭(useQuery), QR 코드 생성관리(qr-code-styling) 및 만료/리패치, QR 자동 체크인 훅 추가(에러 매핑·리다이렉트 포함)
출석 컴포넌트 통합 및 변경
src/components/attendance/AttendanceContent.tsx, src/components/attendance/AttendanceHistoryContent.tsx, src/components/attendance/AttendanceQRContent.tsx, src/components/attendance/AttendanceTodayCard.tsx, src/components/attendance/AttendanceCompleteModal.tsx
props 타입 변경(attendance/summary optional, errorMessage, qr props 등), QR 흐름 통합, 수동 체크인 API 호출 로직 추가, 모달/버튼 동작·disabled 처리 수정
UI 유틸/카드 변경
src/components/ui/card.tsx
CardProps에 primary/secondary 버튼 disabled 지원 추가
타입 정리 및 확장
src/types/user.ts, src/types/home.ts, src/types/club.ts, src/types/attendance.ts, src/types/index.ts
사용자 타입 중앙화(UserInfo 등), ClubIdentifier 추가, QRCodeData/Response 타입 추가, re-exports 업데이트
상태관리·기타 변경
src/stores/useUserStore.ts, src/components/home/HomePageSections.tsx, src/hooks/useRemainingTime.ts, src/proxy.ts
USER STORE에 ADMIN 역할 추가 및 setUser 시그니처 정렬, Home 섹션의 클라이언트 초기화 제거, useSyncExternalStore로 카운트다운 전환, 로그인 리다이렉트에 원래 쿼리 포함
아이콘 리소스 정리
src/assets/icons/index.ts
AttendanceQRIcon 내보내기 제거
새 상수 파일
src/constants/attendance/error.ts, src/constants/attendance/index.ts
출석 에러 코드 및 메시지 매핑 추가, 배럴 재수출

Sequence Diagram(s)

sequenceDiagram
    participant User as 사용자
    participant Browser as 브라우저
    participant MainLayout as MainLayout(server)
    participant HomeAPI as Home API
    participant UserHydrator as UserHydrator(client)
    participant AttendancePage as AttendancePage(server)
    participant AttendanceServer as Attendance Server
    participant AttendanceContent as AttendanceContent(client)

    User->>Browser: /attendance 접근
    Browser->>MainLayout: 요청 (쿠키 포함)
    MainLayout->>HomeAPI: getDashboard(clubId)
    HomeAPI-->>MainLayout: 대시보드 데이터
    MainLayout->>UserHydrator: 사용자/클럽 정보 전달
    MainLayout-->>Browser: 페이지 응답

    Browser->>AttendancePage: 서버컴포넌트 렌더
    AttendancePage->>AttendanceServer: getAttendance(clubId)
    AttendanceServer-->>AttendancePage: attendance 데이터
    AttendancePage-->>AttendanceContent: { attendance, errorMessage, qrSessionId?, qrCode? }
    AttendanceContent->>Browser: 클라이언트 UI 렌더
Loading
sequenceDiagram
    participant User as 사용자
    participant AttendanceContent as AttendanceContent(client)
    participant useQRCheckIn as useQRCheckIn(hook)
    participant AttendanceAPI as Attendance API
    participant Toast as toastError
    participant Router as router

    User->>AttendanceContent: QR 파라미터 포함 방문
    AttendanceContent->>useQRCheckIn: 초기화(qrSessionId, qrCode)
    useQRCheckIn->>AttendanceAPI: checkIn(clubId, sessionId, code)
    alt 성공
        AttendanceAPI-->>useQRCheckIn: 성공
        useQRCheckIn-->>AttendanceContent: isChecked=true
    else 실패
        AttendanceAPI-->>useQRCheckIn: 에러(code?)
        useQRCheckIn->>Toast: ATTENDANCE_ERROR_MESSAGE[code]
        useQRCheckIn->>Router: replace('/attendance')
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • JIN921
  • dalzzy
  • woneeeee

Poem

🐰 QR 빛나네, 토끼가 방긋
쿠키로 불러온 클럽의 숨결
서버가 건네는 진짜 출석표
모의 데이터는 이쯤에서 굿바이
뽀송한 코드로 점프! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경사항을 명확하게 반영하고 있습니다. 출석 API 연결이 이 PR의 핵심이며, 제목은 간결하고 구체적입니다.
Description check ✅ Passed PR 설명이 템플릿의 주요 섹션을 충실히 따르고 있습니다. PR 유형, 관련 이슈번호, Key Changes, 스크린샷을 모두 포함하고 있으며, 추가 코멘트도 제공되었습니다.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch WTH-249-출석-api-연결

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
Copy link
Copy Markdown

github-actions bot commented Apr 9, 2026

🤖 Claude 테스트 제안

모델: claude-sonnet-4-6 | 토큰: 0 입력 / 0 출력

변경된 컴포넌트에 대해 Claude가 생성한 테스트 코드입니다. 검토 후 적합한 부분만 사용하세요.

src/app/(private)/(main)/attendance/history/page.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/app/(private)/(main)/attendance/page.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/app/(private)/(main)/attendance/qr/page.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/app/(private)/(main)/layout.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/attendance/AttendanceCompleteModal.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


이 코멘트는 Claude API를 통해 자동 생성되었습니다. 반드시 검토 후 사용하세요.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 9, 2026

PR 테스트 결과

Jest: 통과

🎉 모든 테스트를 통과했습니다!

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 9, 2026

PR 검증 결과

TypeScript: 통과
ESLint: 통과
Prettier: 통과
Build: 통과

🎉 모든 검증을 통과했습니다!

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 9, 2026

구현한 기능 Preview: https://weeth-4pk762nx9-weethsite-4975s-projects.vercel.app

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

🧹 Nitpick comments (1)
src/stores/useUserStore.ts (1)

5-11: role도 공용 타입을 재사용하는 편이 안전합니다.

UserInfo는 공용 타입으로 옮겼는데 Line 11만 문자열 유니온을 다시 적어 두면 다음 역할 추가 때 store와 타입 정의가 또 어긋납니다. Role | null이나 UserInfo['role'] | null로 맞추는 쪽이 이번 리팩토링 방향과도 일관됩니다.

♻️ 제안된 정리
-import type { UserInfo } from '@/types/user';
+import type { Role, UserInfo } from '@/types/user';
@@
-  role: null as 'LEAD' | 'ADMIN' | 'USER' | null,
+  role: null as Role | null,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/stores/useUserStore.ts` around lines 5 - 11, The initialState object
declares role as a string union; change it to reuse the shared type by replacing
the explicit union on role with UserInfo['role'] | null so the store stays in
sync with the central UserInfo type; update the role declaration in initialState
(in useUserStore.ts) to use UserInfo['role'] | null and ensure any imports for
UserInfo remain in place.
🤖 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/app/`(private)/(main)/attendance/history/page.tsx:
- Around line 9-20: Don't swallow exceptions from
attendanceServerApi.getDetail('YUNJcjFKMO'): capture the error instead of
replacing it with a fake empty summary and surface an explicit error state to
the UI (either rethrow to let the page's error boundary handle it or pass an
error prop to AttendanceHistoryContent). Update the try/catch around
attendanceServerApi.getDetail to set an error variable (e.g., fetchError) or
rethrow the caught error, and change the return to conditionally render an error
UI or pass fetchError alongside summary so real failures are distinguishable
from a genuine empty attendance result.

In `@src/app/`(private)/(main)/attendance/page.tsx:
- Around line 12-18: The attendance fetch currently uses a hardcoded clubId
string in attendanceServerApi.getAttendance('YUNJcjFKMO'), locking all
attendance logic to one club; replace that literal with the real club identifier
the page should trust (e.g., use the route parameter or server-derived value) —
for example, read params.clubId (or a server-validated clubId from
cookies/session or from a prior server lookup) and pass that variable into
attendanceServerApi.getAttendance(clubId). Update any related QR/history flows
in this file that call attendanceServerApi.getAttendance or generate club-scoped
data to use the same dynamic clubId variable (ensure you validate/authorize the
clubId on the server side before use).

In `@src/app/`(private)/(main)/attendance/qr/page.tsx:
- Around line 13-25: Validate that sessionId is a numeric integer before using
it: where sessionId is read and before calling redirect or rendering
AttendanceQRContent, parse and check it (e.g., const id = Number(sessionId);
ensure !isNaN(id) && Number.isInteger(id) and id > 0) and if the check fails
call redirect('/attendance') instead of proceeding to render with
Number(sessionId); update usages (Number(sessionId)) to the validated id so
downstream code does not receive NaN or invalid paths.

In `@src/app/`(private)/(main)/layout.tsx:
- Around line 7-18: MainLayout currently hardcodes clubId by calling
homeServerApi.getDashboard('YUNJcjFKMO') and hydrates UserHydrator with that
static clubId/clubName, which breaks multi-club and routing context; change
MainLayout to derive clubId dynamically (e.g., from route params, server
session/context, or request headers) before calling homeServerApi.getDashboard
or replace getDashboard argument with the dynamic value, then pass the resulting
{ id: clubId, name: clubName } into UserHydrator so each route/user gets the
correct club context; update the code paths that reference MainLayout,
homeServerApi.getDashboard, UserHydrator, clubId and clubName to use the dynamic
source.

In `@src/components/attendance/AttendanceContent.tsx`:
- Around line 63-64: The manual check-in call is using a hardcoded clubId
string; change attendanceApi.checkIn('YUNJcjFKMO', sessionId, Number(code)) to
use the same current clubId source as the QR flow. Locate AttendanceContent
component and retrieve the clubId from the same prop/store selector used by
AttendanceQRContent (e.g., the prop named clubId or the Redux
selector/useClubStore used in AttendanceQRContent) and pass that clubId into
attendanceApi.checkIn(sessionId, Number(code)) (preserve sessionId and code),
ensuring both flows target the identical clubId.
- Around line 59-65: The manual check-in flow (handleAttendanceComplete) only
calls setIsManualChecked(true) but the AttendanceCompleteModal is controlled by
qrCompleteModalOpen, so the manual path never shows the modal; update the logic
so both flows share the same completion state: either set
qrCompleteModalOpen(true) when setting setIsManualChecked(true) inside
handleAttendanceComplete (and the analogous manual-success branch around lines
115-117), or change AttendanceCompleteModal's open prop to use a combined
condition (qrCompleteModalOpen || isManualChecked) so the modal appears for both
QR and manual check-ins; adjust only the state updates or the modal open
expression accordingly.

In `@src/components/attendance/AttendanceTodayCard.tsx`:
- Around line 20-24: In AttendanceTodayCard, guard against null/undefined
sessionId in the QR admin flow: update the QR button click handler and the
button's disabled logic (where it currently only checks disabled) to also check
sessionId == null, and prevent building/navigating to
`/attendance/qr?sessionId=${sessionId}` when sessionId is null (return early or
show noop); reference the sessionId prop and the QR button handler in
AttendanceTodayCard to locate and fix the code.

In `@src/hooks/useQRCheckIn.ts`:
- Around line 25-26: The checkIn call in useQRCheckIn currently uses a hardcoded
clubId ('YUNJcjFKMO'); update useQRCheckIn to accept a clubId parameter (or
internally call the same source hook used by AttendanceQRContent.tsx, e.g.,
useClubId()) and pass that dynamic clubId into attendanceApi.checkIn along with
Number(qrSessionId) and Number(qrCode) so the issued QR and the verification use
the same club context; ensure the function signature and all call sites are
updated to provide the real clubId.
- Around line 17-21: The current effect flips hasCheckedIn.current to true
immediately, preventing retries for new qrSessionId/qrCode combos or after
failures; update the logic in useQRCheckIn's useEffect so you either (a) track
processed combos with a Set/Map keyed by `${qrSessionId}:${qrCode}` (check
membership before sending and add the key only after a successful check-in) or
(b) keep a boolean but only set hasCheckedIn.current = true after the request
completes successfully; reference hasCheckedIn, qrSessionId, qrCode and the
effect that initiates the request to locate and modify the code.

---

Nitpick comments:
In `@src/stores/useUserStore.ts`:
- Around line 5-11: The initialState object declares role as a string union;
change it to reuse the shared type by replacing the explicit union on role with
UserInfo['role'] | null so the store stays in sync with the central UserInfo
type; update the role declaration in initialState (in useUserStore.ts) to use
UserInfo['role'] | null and ensure any imports for UserInfo remain in place.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 94086f27-989b-40b3-98c6-e26eb78a4a49

📥 Commits

Reviewing files that changed from the base of the PR and between f994d11 and f6e19b6.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (30)
  • package.json
  • src/app/(private)/(main)/attendance/history/page.tsx
  • src/app/(private)/(main)/attendance/page.tsx
  • src/app/(private)/(main)/attendance/qr/page.tsx
  • src/app/(private)/(main)/layout.tsx
  • src/components/attendance/AttendanceCompleteModal.tsx
  • src/components/attendance/AttendanceContent.tsx
  • src/components/attendance/AttendanceHistoryContent.tsx
  • src/components/attendance/AttendanceQRContent.tsx
  • src/components/attendance/AttendanceTodayCard.tsx
  • src/components/home/HomePageSections.tsx
  • src/components/ui/card.tsx
  • src/constants/attendance/attendance.ts
  • src/constants/attendance/error.ts
  • src/constants/attendance/index.ts
  • src/hooks/useQRCheckIn.ts
  • src/hooks/useQRCode.ts
  • src/lib/apis/attendance.server.ts
  • src/lib/apis/attendance.ts
  • src/lib/apis/home.server.ts
  • src/providers/user-hydrator.tsx
  • src/proxy.ts
  • src/stores/index.ts
  • src/stores/useClubStore.ts
  • src/stores/useUserStore.ts
  • src/types/attendance.ts
  • src/types/club.ts
  • src/types/home.ts
  • src/types/index.ts
  • src/types/user.ts

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 9, 2026

PR 테스트 결과

Jest: 통과

🎉 모든 테스트를 통과했습니다!

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 9, 2026

🤖 Claude 테스트 제안

모델: claude-sonnet-4-6 | 토큰: 0 입력 / 0 출력

변경된 컴포넌트에 대해 Claude가 생성한 테스트 코드입니다. 검토 후 적합한 부분만 사용하세요.

src/app/(private)/(main)/attendance/history/page.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/app/(private)/(main)/attendance/page.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/app/(private)/(main)/attendance/qr/page.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/app/(private)/(main)/layout.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/attendance/AttendanceCompleteModal.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


이 코멘트는 Claude API를 통해 자동 생성되었습니다. 반드시 검토 후 사용하세요.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 9, 2026

PR 검증 결과

TypeScript: 통과
ESLint: 통과
Prettier: 통과
Build: 통과

🎉 모든 검증을 통과했습니다!

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 9, 2026

구현한 기능 Preview: https://weeth-g7h6gszpl-weethsite-4975s-projects.vercel.app

Copy link
Copy Markdown
Collaborator

@JIN921 JIN921 left a comment

Choose a reason for hiding this comment

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

수고하셧습니다,, 워낙 꼼꼼하게 해주셔서 큰 수정사항은 없는 거 같아요!!

export const homeServerApi = {
getDashboard: (clubId: string) =>
apiServer.get<HomeDashboardResponse>(`/clubs/${clubId}/dashboard/home`, {
cache: 'no-store',
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

이거 캐시 설정을 no-store로 해 놓으면 메인 폴더 하위 페이지에서 매 네비게이션마다,, 호출 될 거 같습니당 revalidate 설정하고 클럽 아이디를 태그로 달면 좋을 거 같애요...

function useQRCheckIn({ qrSessionId, qrCode }: UseQRCheckInParams) {
const router = useRouter();
const [isChecked, setIsChecked] = useState(false);
const [completeModalOpen, setCompleteModalOpen] = useState(false);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

현재는 useQRCheckIn에서 성공 모달 UI 상태까지 책임지는 구조가 되어서,,,, 이거 onSuccess 콜백으로 제어하면 좋을 거 같습니다! MutationCallbacks 타입 잇으니까 onSuccess 콜백 받고 { isChecked }만 반환하도록 하면 어떨까요? 모달 열기 같은 UI 처리는 호출 측에서 onSuccess 콜백으로 넣어주면 훅이 체크인 로직에만 집중할 수 잇을 거 같습니당

const { setUser } = useUserActions();
const { setClub } = useClubActions();

useEffect(() => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

이거 첫 렌더 시점에서 null이 반환되면 깜빡거려 보일 수도 잇는데,, useRef 가드로 렌더 중에 동기적으로 store를 세팅하믄 어떨까요,,,

@nabbang6 nabbang6 force-pushed the WTH-249-출석-api-연결 branch from b3c6660 to e6c4577 Compare April 12, 2026 15:03
@github-actions
Copy link
Copy Markdown

🤖 Claude 테스트 제안

모델: claude-sonnet-4-6 | 토큰: 0 입력 / 0 출력

변경된 컴포넌트에 대해 Claude가 생성한 테스트 코드입니다. 검토 후 적합한 부분만 사용하세요.

src/app/(private)/(main)/attendance/history/page.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/app/(private)/(main)/attendance/page.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/app/(private)/(main)/attendance/qr/page.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/app/(private)/(main)/layout.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/attendance/AttendanceCompleteModal.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


이 코멘트는 Claude API를 통해 자동 생성되었습니다. 반드시 검토 후 사용하세요.

@github-actions
Copy link
Copy Markdown

PR 테스트 결과

Jest: 통과

🎉 모든 테스트를 통과했습니다!

@github-actions
Copy link
Copy Markdown

PR 검증 결과

TypeScript: 통과
ESLint: 통과
Prettier: 통과
Build: 통과

🎉 모든 검증을 통과했습니다!

@github-actions
Copy link
Copy Markdown

구현한 기능 Preview: https://weeth-6alqlmso4-weethsite-4975s-projects.vercel.app

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

🧹 Nitpick comments (4)
src/components/attendance/AttendanceContent.tsx (1)

69-71: 에러 타입 단언 개선 고려

error as { response?: ... } 타입 단언이 안전하지 않을 수 있습니다. Axios 에러 타입을 사용하면 더 타입 안전합니다.

💡 타입 개선 제안
+ import { isAxiosError } from 'axios';

  } catch (error) {
-   const errorCode = (error as { response?: { data?: { code?: number } } }).response?.data?.code;
+   const errorCode = isAxiosError(error) ? error.response?.data?.code : undefined;
    toastError(errorCode ? ATTENDANCE_ERROR_MESSAGE[errorCode] : undefined);
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/attendance/AttendanceContent.tsx` around lines 69 - 71, The
catch block in AttendanceContent.tsx uses an unsafe assertion "error as {
response?: { data?: { code?: number } } }"; replace it with the Axios error type
to be type-safe: import AxiosError from 'axios' (or use AxiosError generic) and
cast the caught error to AxiosError<{ code?: number }>, then read
error.response?.data?.code and pass ATTENDANCE_ERROR_MESSAGE[errorCode] (or
undefined) to toastError; update the catch signature and ensure the import for
AxiosError is added and toastError/ATTENDANCE_ERROR_MESSAGE usage remains
unchanged.
src/app/(private)/(main)/layout.tsx (1)

14-15: clubId 쿠키 누락 시 빈 화면 대신 리다이렉트 고려

clubId가 없을 때 null을 반환하면 사용자에게 빈 화면이 표시됩니다. 로그인 페이지나 클럽 선택 페이지로 리다이렉트하는 것이 UX에 더 적합할 수 있습니다.

💡 리다이렉트 제안
+ import { redirect } from 'next/navigation';

  const clubId = (await cookies()).get(CLUB_ID_KEY)?.value;
- if (!clubId) return null;
+ if (!clubId) redirect('/login');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(private)/(main)/layout.tsx around lines 14 - 15, The current
layout.tsx returns null when clubId (from cookies().get(CLUB_ID_KEY)?.value) is
missing, causing a blank page; replace this behavior by performing a redirect to
the appropriate route (e.g., login or club selection) instead of returning null.
In layout.tsx, locate the clubId extraction and the early return (the lines
using cookies(), CLUB_ID_KEY and clubId) and call the Next.js redirect helper
(or your app's routing util) to send the user to the desired page (e.g.,
'/login' or '/select-club') when clubId is falsy; ensure you add the needed
import for redirect at the top of the file.
src/components/attendance/AttendanceQRContent.tsx (1)

59-79: 만료 상태에서 사용자에게 추가 안내 고려

isExpired일 때 "마감"만 표시되고 새 QR 코드가 자동 생성되는 동안 사용자가 혼란스러울 수 있습니다. 새로고침 중임을 나타내는 로딩 인디케이터나 안내 문구를 추가하면 UX가 개선될 수 있습니다.

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

In `@src/components/attendance/AttendanceQRContent.tsx` around lines 59 - 79, When
isExpired is true the UI only shows "마감", which can confuse users while a new QR
is being generated; update the JSX inside AttendanceQRContent (references:
isExpired, isLoading, qrRef, qrData) to display a clear regeneration state: when
isExpired && isLoading show a loading indicator/message (e.g., "새 QR 코드 생성 중..."
and/or spinner) in place of the QR and the timer, and when isExpired &&
!isLoading show a brief instruction like "새 코드를 곧 표시합니다" or the regenerated QR
once qrData exists; ensure you update the conditional that renders the timer
({isExpired ? ...}) and the QR container (div ref={qrRef}) to reflect these
states so users see a clear loading/refresh message while a new QR is created.
src/hooks/useAttendanceQR.ts (1)

17-37: QR 코드 인스턴스 정리 로직 누락

컴포넌트 언마운트 시 qrCodeRef.current가 정리되지 않아 메모리 누수 가능성이 있습니다. 또한 qrRef.current가 변경되면 기존 QR 코드가 다시 마운트되지 않습니다.

♻️ 정리 로직 추가 제안
  useEffect(() => {
    if (!qrData || !qrRef.current) return;

    const checkInUrl = `${window.location.origin}/attendance?sessionId=${qrData.sessionId}&code=${qrData.code}`;

    if (!qrCodeRef.current) {
      qrCodeRef.current = new QRCodeStyling({
        width: 256,
        height: 256,
        data: checkInUrl,
        qrOptions: { errorCorrectionLevel: 'L' },
        type: 'svg',
        dotsOptions: { type: 'dots' },
        cornersSquareOptions: { type: 'extra-rounded' },
        cornersDotOptions: { type: 'extra-rounded' },
      });
      qrCodeRef.current.append(qrRef.current);
    } else {
      qrCodeRef.current.update({ data: checkInUrl });
    }
+
+   return () => {
+     if (qrRef.current) {
+       qrRef.current.innerHTML = '';
+     }
+   };
  }, [qrData]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useAttendanceQR.ts` around lines 17 - 37, The effect creating the
QRCodeStyling instance (in useEffect) lacks cleanup and doesn't handle changes
to qrRef, risking memory leaks and stale mounts; update the effect tied to
qrData so it also reacts when qrRef.current changes, and add a cleanup that, if
qrCodeRef.current exists, removes/unappends it from qrRef.current and calls the
QRCodeStyling teardown/clear method (then nulls qrCodeRef.current) to fully
release resources; ensure you re-append the existing qrCodeRef.current to a new
qrRef.current when qrRef changes instead of creating a duplicate instance.
🤖 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/attendance/AttendanceHistoryContent.tsx`:
- Around line 77-79: The StatBox fields (total, attendanceCount, absenceCount)
are rendering "0회" even when the summary is missing or there was a
fetch/permission failure; update AttendanceHistoryContent to detect when summary
(or the fetch error/state) is absent and avoid treating that as a real
zero—render a clear placeholder (e.g., '-' or '조회불가') or a dedicated "failed to
load" UI for the statistics instead, and ensure the statistics block is rendered
separately from the attendance list when in error/unknown state; adjust the
logic that currently uses `${total ?? 0}회`, `${attendanceCount ?? 0}회`,
`${absenceCount ?? 0}회` to check summary/fetch state first and only format
numeric values when summary is present.

In `@src/hooks/useAttendanceQR.ts`:
- Around line 7-15: The hook useAttendanceQR currently calls useRemainingTime
with an empty string which causes immediate expiry when clubId is null; update
the call in useAttendanceQR to pass qrData?.expiredAt ?? undefined (or null)
instead of '' and, if needed, adjust useRemainingTime to accept undefined/null
as “no expiry” so it short-circuits (returning not-expired/zero timers) — this
keeps hooks stable (useQRCode and useRemainingTime still called) and prevents
unnecessary expiry logic and refetch checks involving qrData, referring to
useAttendanceQR, useQRCode, useRemainingTime, qrData, isExpired, refetch and
getRemainingSeconds.

In `@src/stores/useClubStore.ts`:
- Line 5: Remove the unused import ClubIdentifier from the top of
useClubStore.ts; locate the import statement that reads "import type {
ClubIdentifier }" and delete it so the file no longer imports an unused type and
the lint warning is resolved. If you plan to use ClubIdentifier later, instead
keep it commented with a TODO or add a usage; otherwise simply remove the
import.

---

Nitpick comments:
In `@src/app/`(private)/(main)/layout.tsx:
- Around line 14-15: The current layout.tsx returns null when clubId (from
cookies().get(CLUB_ID_KEY)?.value) is missing, causing a blank page; replace
this behavior by performing a redirect to the appropriate route (e.g., login or
club selection) instead of returning null. In layout.tsx, locate the clubId
extraction and the early return (the lines using cookies(), CLUB_ID_KEY and
clubId) and call the Next.js redirect helper (or your app's routing util) to
send the user to the desired page (e.g., '/login' or '/select-club') when clubId
is falsy; ensure you add the needed import for redirect at the top of the file.

In `@src/components/attendance/AttendanceContent.tsx`:
- Around line 69-71: The catch block in AttendanceContent.tsx uses an unsafe
assertion "error as { response?: { data?: { code?: number } } }"; replace it
with the Axios error type to be type-safe: import AxiosError from 'axios' (or
use AxiosError generic) and cast the caught error to AxiosError<{ code?: number
}>, then read error.response?.data?.code and pass
ATTENDANCE_ERROR_MESSAGE[errorCode] (or undefined) to toastError; update the
catch signature and ensure the import for AxiosError is added and
toastError/ATTENDANCE_ERROR_MESSAGE usage remains unchanged.

In `@src/components/attendance/AttendanceQRContent.tsx`:
- Around line 59-79: When isExpired is true the UI only shows "마감", which can
confuse users while a new QR is being generated; update the JSX inside
AttendanceQRContent (references: isExpired, isLoading, qrRef, qrData) to display
a clear regeneration state: when isExpired && isLoading show a loading
indicator/message (e.g., "새 QR 코드 생성 중..." and/or spinner) in place of the QR
and the timer, and when isExpired && !isLoading show a brief instruction like "새
코드를 곧 표시합니다" or the regenerated QR once qrData exists; ensure you update the
conditional that renders the timer ({isExpired ? ...}) and the QR container (div
ref={qrRef}) to reflect these states so users see a clear loading/refresh
message while a new QR is created.

In `@src/hooks/useAttendanceQR.ts`:
- Around line 17-37: The effect creating the QRCodeStyling instance (in
useEffect) lacks cleanup and doesn't handle changes to qrRef, risking memory
leaks and stale mounts; update the effect tied to qrData so it also reacts when
qrRef.current changes, and add a cleanup that, if qrCodeRef.current exists,
removes/unappends it from qrRef.current and calls the QRCodeStyling
teardown/clear method (then nulls qrCodeRef.current) to fully release resources;
ensure you re-append the existing qrCodeRef.current to a new qrRef.current when
qrRef changes instead of creating a duplicate instance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cadf95d3-b785-430e-95a3-a54186225ce1

📥 Commits

Reviewing files that changed from the base of the PR and between b3c6660 and e6c4577.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (30)
  • package.json
  • src/app/(private)/(main)/attendance/history/page.tsx
  • src/app/(private)/(main)/attendance/page.tsx
  • src/app/(private)/(main)/attendance/qr/page.tsx
  • src/app/(private)/(main)/layout.tsx
  • src/components/attendance/AttendanceCompleteModal.tsx
  • src/components/attendance/AttendanceContent.tsx
  • src/components/attendance/AttendanceHistoryContent.tsx
  • src/components/attendance/AttendanceQRContent.tsx
  • src/components/attendance/AttendanceTodayCard.tsx
  • src/components/home/HomePageSections.tsx
  • src/components/ui/card.tsx
  • src/constants/attendance/attendance.ts
  • src/constants/attendance/error.ts
  • src/constants/attendance/index.ts
  • src/hooks/useAttendanceQR.ts
  • src/hooks/useQRCheckIn.ts
  • src/hooks/useQRCode.ts
  • src/lib/apis/attendance.server.ts
  • src/lib/apis/attendance.ts
  • src/lib/apis/home.server.ts
  • src/providers/user-hydrator.tsx
  • src/proxy.ts
  • src/stores/useClubStore.ts
  • src/stores/useUserStore.ts
  • src/types/attendance.ts
  • src/types/club.ts
  • src/types/home.ts
  • src/types/index.ts
  • src/types/user.ts
✅ Files skipped from review due to trivial changes (7)
  • package.json
  • src/constants/attendance/index.ts
  • src/types/club.ts
  • src/types/attendance.ts
  • src/constants/attendance/error.ts
  • src/lib/apis/attendance.server.ts
  • src/types/user.ts
🚧 Files skipped from review as they are similar to previous changes (12)
  • src/proxy.ts
  • src/components/home/HomePageSections.tsx
  • src/types/index.ts
  • src/components/attendance/AttendanceTodayCard.tsx
  • src/stores/useUserStore.ts
  • src/providers/user-hydrator.tsx
  • src/lib/apis/attendance.ts
  • src/hooks/useQRCheckIn.ts
  • src/hooks/useQRCode.ts
  • src/app/(private)/(main)/attendance/history/page.tsx
  • src/types/home.ts
  • src/components/ui/card.tsx

@github-actions
Copy link
Copy Markdown

🤖 Claude 테스트 제안

모델: claude-sonnet-4-6 | 토큰: 0 입력 / 0 출력

변경된 컴포넌트에 대해 Claude가 생성한 테스트 코드입니다. 검토 후 적합한 부분만 사용하세요.

src/app/(private)/(main)/attendance/history/page.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/app/(private)/(main)/attendance/page.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/app/(private)/(main)/attendance/qr/page.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/app/(private)/(main)/layout.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/attendance/AttendanceCompleteModal.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


이 코멘트는 Claude API를 통해 자동 생성되었습니다. 반드시 검토 후 사용하세요.

@github-actions
Copy link
Copy Markdown

PR 테스트 결과

Jest: 통과

🎉 모든 테스트를 통과했습니다!

@github-actions
Copy link
Copy Markdown

PR 검증 결과

TypeScript: 통과
ESLint: 통과
Prettier: 통과
Build: 실패

⚠️ 일부 검증에 실패했습니다. 확인 후 수정해주세요.

@github-actions
Copy link
Copy Markdown

🤖 Claude 테스트 제안

모델: claude-sonnet-4-6 | 토큰: 0 입력 / 0 출력

변경된 컴포넌트에 대해 Claude가 생성한 테스트 코드입니다. 검토 후 적합한 부분만 사용하세요.

src/app/(private)/(main)/attendance/history/page.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/app/(private)/(main)/attendance/page.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/app/(private)/(main)/attendance/qr/page.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/app/(private)/(main)/layout.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/attendance/AttendanceCompleteModal.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


이 코멘트는 Claude API를 통해 자동 생성되었습니다. 반드시 검토 후 사용하세요.

@github-actions
Copy link
Copy Markdown

PR 테스트 결과

Jest: 통과

🎉 모든 테스트를 통과했습니다!

@github-actions
Copy link
Copy Markdown

PR 검증 결과

TypeScript: 통과
ESLint: 통과
Prettier: 통과
Build: 통과

🎉 모든 검증을 통과했습니다!

@github-actions
Copy link
Copy Markdown

구현한 기능 Preview: https://weeth-d63rawlmi-weethsite-4975s-projects.vercel.app

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

🤖 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/hooks/useRemainingTime.ts`:
- Around line 23-24: In useRemainingTime, when endTime is falsy the hook
currently returns early but leaves the remaining state unchanged; update the
early-return branch (the if (!endTime) block) to reset remaining to 0 by calling
setRemaining(0) and also ensure any active interval/timeout is cleared
(clearInterval/clearTimeout on the stored timer ID) before returning so the UI
shows expired state consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d44a78fa-e68a-48fa-9fc5-bbb7d7a6a456

📥 Commits

Reviewing files that changed from the base of the PR and between e6c4577 and 9735cbc.

⛔ Files ignored due to path filters (1)
  • src/assets/icons/attendance/ic_attendance_qr.svg is excluded by !**/*.svg
📒 Files selected for processing (3)
  • src/assets/icons/index.ts
  • src/components/attendance/AttendanceHistoryContent.tsx
  • src/hooks/useRemainingTime.ts
💤 Files with no reviewable changes (1)
  • src/assets/icons/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/attendance/AttendanceHistoryContent.tsx

@github-actions
Copy link
Copy Markdown

🤖 Claude 테스트 제안

모델: claude-sonnet-4-6 | 토큰: 0 입력 / 0 출력

변경된 컴포넌트에 대해 Claude가 생성한 테스트 코드입니다. 검토 후 적합한 부분만 사용하세요.

src/app/(private)/(main)/attendance/history/page.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/app/(private)/(main)/attendance/page.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/app/(private)/(main)/attendance/qr/page.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/app/(private)/(main)/layout.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/attendance/AttendanceCompleteModal.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


이 코멘트는 Claude API를 통해 자동 생성되었습니다. 반드시 검토 후 사용하세요.

@github-actions
Copy link
Copy Markdown

PR 테스트 결과

Jest: 통과

🎉 모든 테스트를 통과했습니다!

@github-actions
Copy link
Copy Markdown

PR 검증 결과

TypeScript: 통과
ESLint: 통과
Prettier: 통과
Build: 통과

🎉 모든 검증을 통과했습니다!

@github-actions
Copy link
Copy Markdown

구현한 기능 Preview: https://weeth-bpfjw2l40-weethsite-4975s-projects.vercel.app

@nabbang6 nabbang6 merged commit 86ed7e0 into develop Apr 12, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📬 API 서버 API 통신 ✨ Feature 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants