Skip to content

fix(chat): wire spot actions to backend#8

Merged
seoJing merged 2 commits into
mainfrom
feature/spot-api-contract-sync
May 23, 2026
Merged

fix(chat): wire spot actions to backend#8
seoJing merged 2 commits into
mainfrom
feature/spot-api-contract-sync

Conversation

@seoJing
Copy link
Copy Markdown
Member

@seoJing seoJing commented May 23, 2026

Summary

  • Enrich SPOT chat rooms with backend participants, schedule, votes, and files on room list/detail load
  • Wire chat vote creation/casting to spot APIs and sync room/message vote state
  • Enforce owner-only vote/schedule/file actions and supporter-only reverse-offer actions from the room current user identity
  • Keep reverse offer as local/mock flow, and stop file creation from saving placeholder URLs until the real upload/file URL contract exists
  • Include map-selected feed creation location regression coverage from the current branch diff

Checks

  • pnpm lint (passes with existing warnings)
  • pnpm test (24 files / 98 tests)
  • pnpm build
  • pnpm tsc --noEmit
  • git diff --check
  • notjing final gate

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 팀 채팅에서 위치 선택 시 위도/경도를 직접 입력할 수 있는 기능 추가
    • 투표 참여 기능 추가로 실시간 투표 결과 반영
  • 버그 수정

    • 팀 채팅의 일정/투표/파일 추가 기능이 방 소유자에게만 표시되도록 권한 제어 개선
    • 역제안 생성 권한 검증 로직 추가
  • 개선

    • 파일 업로드 및 일정 저장 시 오류 상태 표시 개선
    • 스팟 방 로딩 시 백엔드 데이터 동기화 강화

Review Change Stack

- enrich chat rooms with spot participants, schedule, votes, and files
- call spot vote APIs and sync room/message state
- enforce owner/supporter action gates from backend user identity
- block placeholder file registration until a real file URL contract exists
- keep map-selected feed creation location changes with regression coverage
@vercel
Copy link
Copy Markdown

vercel Bot commented May 23, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
frontend Ready Ready Preview, Comment May 23, 2026 4:05pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 23, 2026

Warning

Review limit reached

@seoJing, we couldn't start this review because you've used your available PR reviews for now.

Your plan currently allows 1 review/hour. Refill in 24 minutes and 29 seconds.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more review capacity refills, 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 trial, open-source, and free plans. In all cases, review capacity refills continuously over time.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 61454fb9-5e26-4852-b3db-f91a3003d8ca

📥 Commits

Reviewing files that changed from the base of the PR and between 48d8a6e and 1db8718.

📒 Files selected for processing (6)
  • src/features/chat/client/MainChatPageClient.tsx
  • src/features/chat/model/mock.ts
  • src/features/chat/ui/ChatCreationPanel.tsx
  • src/features/chat/ui/ChatDetail.tsx
  • src/features/post/ui/post-form/MapLocationPicker.test.tsx
  • src/features/post/ui/post-form/MapLocationPicker.tsx
📝 Walkthrough

워크스루

이 PR은 스팟 팀 채팅의 투표, 파일, 일정 관리를 백엔드 API 중심으로 전환하면서 소유자 권한 검증을 강화하고, 포스트 위치 선택에 수동 좌표 입력 기능을 추가합니다. 주요 변경은 비동기 상태 관리, 권한 기반 UI 제어, 그리고 타입 구조 정리입니다.

변경사항

스팟 채팅 권한 및 백엔드 동기화

레이어 / 파일(들) 요약
권한 판정 기본값 및 스토어 인터페이스 확장
src/features/chat/model/mock.ts, src/features/chat/model/use-main-chat-store.ts
isSupporterForSpot의 userId 기본값을 room.currentUserId로 변경하고, useMainChatStore의 팀 액션 메서드들을 Promise 기반 비동기로 확장하여 castTeamVote를 추가합니다.
권한 판정 헬퍼 및 백엔드 보강 유틸
src/features/chat/model/use-main-chat-store.ts
isOwnedSpotRoom, canManageOwnerActions, canCreateReverseOffer 권한 판정 헬퍼와 replaceVoteInRoom, applyLocalVoteSelection 투표 반영 유틸, enrichSpotRoomWithBackend 백엔드 보강 함수를 추가합니다.
투표 생성 및 캐스팅 백엔드 동기화
src/features/chat/model/use-main-chat-store.ts
createTeamVote를 spotsApi.createVote 호출 기반으로 변경하고, castTeamVote를 신규로 구현하여 로컬 선택 상태를 반영한 후 API로 전송합니다.
파일 공유 및 일정 백엔드 동기화
src/features/chat/model/use-main-chat-store.ts
createTeamFileShare를 spotsApi.uploadFile 호출 기반으로 변경하고, updateSpotSchedule을 spotsApi.upsertSchedule 응답 반영 방식으로 전환하며, createTeamReverseOffer에 권한 검증을 추가합니다.
방 로딩 시 백엔드 데이터 보강
src/features/chat/model/use-main-chat-store.ts
loadRooms에서 스팟 방을 enrichSpotRoomsWithBackend로 보강하고, loadRoom은 스팟 방을 enrichSpotRoomWithBackend로 보강한 후 반환합니다.
바텀 네비게이션 소유자 액션 제어
src/features/chat/ui/ChatBottomNav.tsx, src/features/chat/client/MainChatPageClient.tsx
ChatBottomNavTeamProps에 showOwnerActions 필드를 추가하고, 투표/일정/파일 버튼을 이 플래그로 조건부 렌더링합니다. MainChatPageClient는 selectedSpotRoom 소유자 여부로 플래그를 결정합니다.
투표 생성 및 액션 패널 비동기 처리
src/features/chat/ui/ChatCreationPanel.tsx
투표 생성 패널의 handleSubmit을 async로 변경하여 createTeamVote 결과 확인 후 패널을 닫고, 투표 액션 패널에서 castTeamVote를 비동기로 호출하며 현재 사용자 ID 기반 투표 상태를 표시합니다.
일정 생성 및 액션 패널 비동기/에러 처리
src/features/chat/ui/ChatCreationPanel.tsx
일정 패널들에 isSaving/saveError 상태를 추가하고, handleSave를 비동기 try/catch/finally로 변경하여 저장 중 중복 실행을 방지하고 실패 시 에러 메시지를 표시합니다.
파일 생성 패널 업로드 오류 처리
src/features/chat/ui/ChatCreationPanel.tsx
파일 패널에 uploadError 상태를 추가하여 실제 등록 미지원 메시지를 표시하고, 닫기 버튼을 추가합니다.
팀 생성 패널 단계별 권한 제약
src/features/chat/ui/ChatCreationPanel.tsx
vote/schedule/file은 소유자만 접근 가능하도록 하고, reverse-offer는 권한 조건 만족 시만 패널을 렌더링합니다.
ChatDetail에서 권한 기반 생성 항목 제어
src/features/chat/ui/ChatDetail.tsx
isOwnedSpotRoom, canManageOwnerActions, canCreateReverseOffer를 계산하여 생성 항목 배열을 조건부 스프레드로 구성합니다.
투표 캐스팅 및 권한 테스트
src/features/chat/model/use-main-chat-store.test.ts
castTeamVote가 spotsApi.castVote를 올바른 인자로 호출하고 투표 상태를 동기화하는지, owner-only 액션들이 권한 없이 null을 반환하는지를 검증합니다.

포스트 위치 선택 기능 개선

레이어 / 파일(들) 요약
위치 데이터 타입 모델 정리
src/features/post/model/types.ts, src/features/post/model/use-post-base-form.ts, src/features/post/ui/post-form/MapLocationPicker.tsx, src/features/post/ui/post-form/PostBaseInfoSection.tsx
SelectedPostLocation 타입을 model/types로 이동하고, MapLocationPicker에서 로컬 export를 제거하여 import 경로를 통일합니다.
MapLocationPicker 수동 좌표 입력 기능
src/features/post/ui/post-form/MapLocationPicker.tsx
manualLat/manualLng 상태와 manualError를 추가하고, handleManualSelect 함수를 구현하여 좌표 범위(-9090 위도, -180180 경도) 검증 후 선택을 처리합니다. 수동 입력 UI(텍스트 필드, 버튼, 에러 배지)를 추가합니다.
MapLocationPicker 수동 입력 테스트
src/features/post/ui/post-form/MapLocationPicker.test.tsx
현재 중심점 선택 버튼 및 수동 좌표 입력 후 선택 동작을 테스트하여 onChange가 올바른 lat/lng/label을 전달함을 검증합니다.

예상 코드 리뷰 노력

🎯 4 (Complex) | ⏱️ ~50 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.90% 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 PR 제목은 'fix(chat): wire spot actions to backend'로, SPOT 채팅 액션을 백엔드에 연결하는 변경사항의 주요 내용을 명확하게 요약하고 있습니다.
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 feature/spot-api-contract-sync

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.

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

🧹 Nitpick comments (4)
src/features/post/ui/post-form/MapLocationPicker.test.tsx (1)

78-96: ⚡ Quick win

입력 검증 실패 경로 테스트도 추가해 주세요.

빈 문자열/범위 초과 입력에서 onChange가 호출되지 않고 오류 메시지가 노출되는 케이스를 함께 검증하면 회귀 방지에 효과적입니다.

테스트 추가 예시
+it('shows an error and does not call onChange for invalid manual coordinates', () => {
+    const handleChange = vi.fn();
+    render(<MapLocationPicker value={null} onChange={handleChange} />);
+
+    fireEvent.change(screen.getByLabelText('선택할 위치의 위도'), {
+        target: { value: '' },
+    });
+    fireEvent.change(screen.getByLabelText('선택할 위치의 경도'), {
+        target: { value: '200' },
+    });
+    fireEvent.click(screen.getByRole('button', { name: '좌표로 선택' }));
+
+    expect(handleChange).not.toHaveBeenCalled();
+    expect(
+        screen.getByText('위도는 -90~90, 경도는 -180~180 사이 숫자로 입력해주세요.'),
+    ).toBeTruthy();
+});
🤖 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/features/post/ui/post-form/MapLocationPicker.test.tsx` around lines 78 -
96, Add tests for the MapLocationPicker component to cover input validation
failure paths: using the existing test pattern with MapLocationPicker and the
handleChange mock, add cases where latitude or longitude inputs are empty
strings and where values are out of range (e.g., lat beyond ±90 or lng beyond
±180), trigger the selection button (the '좌표로 선택' button via getByRole) and
assert that onChange (handleChange) is NOT called and that the component shows
the appropriate validation error message (use screen.getByText / getByLabelText
to verify the displayed error). Reference the existing MapLocationPicker test
file and mimic the fireEvent.change + fireEvent.click flow used in the
successful case to validate these failure scenarios.
src/features/chat/client/MainChatPageClient.tsx (1)

292-294: 💤 Low value

선택적 제안: 권한 판정 헬퍼를 모델 레이어로 이동

isOwnedSpotRoom 헬퍼가 이 파일 내에서 여러 곳(Line 306, 826, 830)에 사용되고 있고, isSupporterForSpot는 모델 레이어(model/mock)에서 임포트되고 있습니다. 권한 판정 로직의 일관성과 재사용성을 위해 isOwnedSpotRoom도 모델 레이어로 옮기는 것을 고려해 보세요.

현재 동작에는 문제가 없으나, 향후 다른 컴포넌트에서도 소유자 권한 체크가 필요할 경우 중복 구현을 방지할 수 있습니다.

🤖 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/features/chat/client/MainChatPageClient.tsx` around lines 292 - 294,
Extract the isOwnedSpotRoom helper from MainChatPageClient into the model layer
(same place as isSupporterForSpot, e.g., model/mock): move the function
implementation into that module, export it, then replace the local definition in
MainChatPageClient with an import of the new exported isOwnedSpotRoom and update
all call sites (the usages inside MainChatPageClient and any other modules) to
use the imported function; ensure TypeScript types (SpotChatRoom) are imported
or exported as needed and run type checks to confirm no breakages.
src/features/chat/ui/ChatDetail.tsx (1)

1000-1093: 💤 Low value

"새로 만들기" 섹션 빈 상태 가능성

OFFER 타입 스팟에서 비소유자(non-author)인 경우, canManageOwnerActionscanCreateReverseOffer 모두 false가 되어 "새로 만들기" 제목만 표시되고 항목이 없는 상태가 됩니다. 해당 케이스에서 섹션 자체를 숨기거나 안내 메시지를 추가하는 것이 UX에 도움될 수 있습니다.

💡 빈 상태 처리 예시
+const hasCreationItems = canManageOwnerActions || canCreateReverseOffer;

 <section className="flex flex-col gap-2">
+    {hasCreationItems ? (
+        <>
             <p className="text-[11px] font-semibold tracking-[0.14em] text-muted-foreground uppercase">
                 새로 만들기
             </p>
             <div className="grid grid-cols-2 gap-2">
                 {/* ... existing items ... */}
             </div>
+        </>
+    ) : null}
 </section>
🤖 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/features/chat/ui/ChatDetail.tsx` around lines 1000 - 1093, The "새로 만들기"
section can render empty when both canManageOwnerActions and
canCreateReverseOffer are false; update the component to check the combined
items array (derived from canManageOwnerActions and canCreateReverseOffer)
before rendering: if the array is empty, either do not render the entire
<section> block or render a small empty-state hint (e.g., "사용 가능한 항목이 없습니다") in
place of the grid; keep existing handlers (openCreation, setShortcutPickerOpen)
untouched and reference the same keys (step) and props when rendering items.
src/features/chat/ui/ChatCreationPanel.tsx (1)

234-243: ⚡ Quick win

createTeamVote 실패 시 에러 피드백 누락

ScheduleCreatePanelisSaving/saveError 상태로 에러를 처리하지만, VoteCreatePanelcreateTeamVote 실패 시 에러를 catch하지 않아 사용자에게 피드백이 없습니다. 일관성 있는 UX를 위해 동일한 패턴 적용을 권장합니다.

🔧 에러 처리 추가 제안
+    const [isCreating, setIsCreating] = useState(false);
+    const [createError, setCreateError] = useState<string | null>(null);

     async function handleSubmit() {
-        if (!canSubmit) return;
+        if (!canSubmit || isCreating) return;
+
+        setIsCreating(true);
+        setCreateError(null);
+
         setSelectedContextId(room.id);
-        const createdRoom = await createTeamVote(
-            question.trim(),
-            options.filter((o) => o.trim()),
-            multiSelect,
-        );
-        if (createdRoom) onClose();
+        try {
+            const createdRoom = await createTeamVote(
+                question.trim(),
+                options.filter((o) => o.trim()),
+                multiSelect,
+            );
+            if (createdRoom) onClose();
+        } catch {
+            setCreateError('투표 생성에 실패했어요. 잠시 후 다시 시도해주세요.');
+        } finally {
+            setIsCreating(false);
+        }
     }
🤖 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/features/chat/ui/ChatCreationPanel.tsx` around lines 234 - 243, The
handleSubmit function should mirror ScheduleCreatePanel's save pattern: set an
"isSaving" flag before calling createTeamVote and clear it afterward, and catch
errors to set a "saveError" state so the UI can show feedback. Update
handleSubmit to call setIsSaving(true) at start, await
createTeamVote(question.trim(), options.filter(o => o.trim()), multiSelect)
inside try, call onClose() only on success, and in catch setSaveError(err) (and
ensure setIsSaving(false) in finally); reference the existing handleSubmit,
createTeamVote, setSelectedContextId, onClose, question, options and multiSelect
symbols when making the change.
🤖 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 `@src/features/post/ui/post-form/MapLocationPicker.tsx`:
- Around line 59-75: handleManualSelect currently converts manualLat/manualLng
to numbers which treats empty strings as 0; first check that manualLat and
manualLng are non-empty (e.g., trim and verify not '') before Number conversion,
setManualError with the same message and return if either is empty, then proceed
to parse and validate numeric ranges; update the logic inside handleManualSelect
to validate emptiness first, then Number.isFinite and range checks, referencing
manualLat, manualLng, handleManualSelect, and setManualError.

---

Nitpick comments:
In `@src/features/chat/client/MainChatPageClient.tsx`:
- Around line 292-294: Extract the isOwnedSpotRoom helper from
MainChatPageClient into the model layer (same place as isSupporterForSpot, e.g.,
model/mock): move the function implementation into that module, export it, then
replace the local definition in MainChatPageClient with an import of the new
exported isOwnedSpotRoom and update all call sites (the usages inside
MainChatPageClient and any other modules) to use the imported function; ensure
TypeScript types (SpotChatRoom) are imported or exported as needed and run type
checks to confirm no breakages.

In `@src/features/chat/ui/ChatCreationPanel.tsx`:
- Around line 234-243: The handleSubmit function should mirror
ScheduleCreatePanel's save pattern: set an "isSaving" flag before calling
createTeamVote and clear it afterward, and catch errors to set a "saveError"
state so the UI can show feedback. Update handleSubmit to call setIsSaving(true)
at start, await createTeamVote(question.trim(), options.filter(o => o.trim()),
multiSelect) inside try, call onClose() only on success, and in catch
setSaveError(err) (and ensure setIsSaving(false) in finally); reference the
existing handleSubmit, createTeamVote, setSelectedContextId, onClose, question,
options and multiSelect symbols when making the change.

In `@src/features/chat/ui/ChatDetail.tsx`:
- Around line 1000-1093: The "새로 만들기" section can render empty when both
canManageOwnerActions and canCreateReverseOffer are false; update the component
to check the combined items array (derived from canManageOwnerActions and
canCreateReverseOffer) before rendering: if the array is empty, either do not
render the entire <section> block or render a small empty-state hint (e.g., "사용
가능한 항목이 없습니다") in place of the grid; keep existing handlers (openCreation,
setShortcutPickerOpen) untouched and reference the same keys (step) and props
when rendering items.

In `@src/features/post/ui/post-form/MapLocationPicker.test.tsx`:
- Around line 78-96: Add tests for the MapLocationPicker component to cover
input validation failure paths: using the existing test pattern with
MapLocationPicker and the handleChange mock, add cases where latitude or
longitude inputs are empty strings and where values are out of range (e.g., lat
beyond ±90 or lng beyond ±180), trigger the selection button (the '좌표로 선택'
button via getByRole) and assert that onChange (handleChange) is NOT called and
that the component shows the appropriate validation error message (use
screen.getByText / getByLabelText to verify the displayed error). Reference the
existing MapLocationPicker test file and mimic the fireEvent.change +
fireEvent.click flow used in the successful case to validate these failure
scenarios.
🪄 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: e14841a5-b417-48c8-b336-2376b4e299ef

📥 Commits

Reviewing files that changed from the base of the PR and between 491d70b and 48d8a6e.

📒 Files selected for processing (12)
  • src/features/chat/client/MainChatPageClient.tsx
  • src/features/chat/model/mock.ts
  • src/features/chat/model/use-main-chat-store.test.ts
  • src/features/chat/model/use-main-chat-store.ts
  • src/features/chat/ui/ChatBottomNav.tsx
  • src/features/chat/ui/ChatCreationPanel.tsx
  • src/features/chat/ui/ChatDetail.tsx
  • src/features/post/model/types.ts
  • src/features/post/model/use-post-base-form.ts
  • src/features/post/ui/post-form/MapLocationPicker.test.tsx
  • src/features/post/ui/post-form/MapLocationPicker.tsx
  • src/features/post/ui/post-form/PostBaseInfoSection.tsx

Comment thread src/features/post/ui/post-form/MapLocationPicker.tsx
@vercel
Copy link
Copy Markdown

vercel Bot commented May 23, 2026

Deployment failed with the following error:

The provided GitHub repository does not contain the requested branch or commit reference. Please ensure the repository is not empty.

@seoJing seoJing merged commit daecca4 into main May 23, 2026
4 of 6 checks passed
@seoJing seoJing deleted the feature/spot-api-contract-sync branch May 23, 2026 17:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant