From 6d8ad6c2f2cd815187156d38e4da6a716dfc7f02 Mon Sep 17 00:00:00 2001 From: wjdalss21 Date: Fri, 19 Jun 2026 18:26:39 +0900 Subject: [PATCH 1/5] =?UTF-8?q?fix:=20DisputeParticipantDto=EC=97=90=20nic?= =?UTF-8?q?kname=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/types/dispute.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types/dispute.ts b/src/types/dispute.ts index bf7aeea..bc9e019 100644 --- a/src/types/dispute.ts +++ b/src/types/dispute.ts @@ -24,6 +24,7 @@ export interface DisputeParticipantDto { disputeId: string userId: string role: ParticipantRole + nickname: string | null profileImageUrl: string | null joinedAt: string createdAt: string From d6348b58aa1d8358aa11e9765ba605a7f00a956b Mon Sep 17 00:00:00 2001 From: wjdalss21 Date: Sat, 20 Jun 2026 10:51:32 +0900 Subject: [PATCH 2/5] =?UTF-8?q?fix:=20=ED=99=88=20=EC=9D=BC=EA=B8=B0=20?= =?UTF-8?q?=EB=B0=95=EC=8A=A4=20max-width=20=EC=A0=9C=EA=B1=B0=EB=A1=9C=20?= =?UTF-8?q?=EB=84=88=EB=B9=84=20100%=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- src/app/page.module.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/page.module.scss b/src/app/page.module.scss index f13889b..20d5882 100644 --- a/src/app/page.module.scss +++ b/src/app/page.module.scss @@ -57,7 +57,6 @@ align-items: center; align-self: center; width: 100%; - max-width: 365px; height: 88px; padding: 20px 16px; border: 2px solid v.$color-black-700; From 3fa4766db7e7c470cc5994cb2e45d01f7b75aadd Mon Sep 17 00:00:00 2001 From: wjdalss21 Date: Sat, 20 Jun 2026 11:17:20 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=ED=99=88=20=EC=A7=84=ED=96=89?= =?UTF-8?q?=EC=A4=91=EC=9D=B8=20=EC=82=AC=EA=B1=B4=20API=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GET /api/disputes에 active=true 파라미터 추가 → 4개 진행중 상태 필터링 - toParticipantDto에 nickname: null 추가로 타입 불일치 해소 - useActiveCases 실제 fetch 함수 활성화 및 DTO → ActiveCase 변환 구현 Co-Authored-By: Claude Sonnet 4.6 --- src/app/api/disputes/route.ts | 19 ++++++++++++++- src/hooks/useActiveCases.ts | 46 +++++++++++++++++++++++++---------- 2 files changed, 51 insertions(+), 14 deletions(-) diff --git a/src/app/api/disputes/route.ts b/src/app/api/disputes/route.ts index d913269..7ab949a 100644 --- a/src/app/api/disputes/route.ts +++ b/src/app/api/disputes/route.ts @@ -1,7 +1,7 @@ import { NextRequest, NextResponse } from 'next/server' import { getServerSession } from 'next-auth' import { z } from 'zod' -import { type Prisma, type CategoryGroup as PrismaCategoryGroup } from '@prisma/client' +import { type Prisma, type CategoryGroup as PrismaCategoryGroup, DisputeStatus } from '@prisma/client' import { authOptions } from '@/lib/auth' import { prisma } from '@/lib/db' import { getSessionUserId } from '@/lib/auth/session' @@ -32,6 +32,9 @@ function toParticipantDto(p: DisputeForList['participants'][number]): DisputePar disputeId: p.disputeId, userId: p.userId, role: p.role.toLowerCase() as DisputeParticipantDto['role'], + // DisputeParticipantDto 타입에 nickname이 선언되어 있어 null로 채워 타입 불일치 방지 + // 닉네임을 DB에서 조회하지 않으므로 null 반환 (타입이 string | null을 허용함) + nickname: null, profileImageUrl: p.user.profileImageUrl ?? null, joinedAt: p.joinedAt.toISOString(), createdAt: p.createdAt.toISOString(), @@ -67,6 +70,8 @@ export async function GET(request: NextRequest) { const { searchParams } = new URL(request.url) const rawCategory = searchParams.get('categoryGroup') + // URL 쿼리 파라미터는 문자열로 전달되므로 "true" 문자열과 비교 + const active = searchParams.get('active') === 'true' const page = Math.max(1, parseInt(searchParams.get('page') ?? '1', 10)) const limit = Math.min(50, Math.max(1, parseInt(searchParams.get('limit') ?? '20', 10))) @@ -94,6 +99,18 @@ export async function GET(request: NextRequest) { deletedAt: null, participants: { some: { userId } }, ...(rawCategory ? { categoryGroup: rawCategory.toUpperCase() as PrismaCategoryGroup } : {}), + // active=true 일 때 진행중 상태만 필터링 + // draft(진술 전), judged(판결 완료), closed/expired/deleted 제외 + ...(active ? { + status: { + in: [ + DisputeStatus.WAITING_OPPONENT, + DisputeStatus.OPPONENT_JOINED, + DisputeStatus.BOTH_SUBMITTED, + DisputeStatus.JUDGING, + ], + }, + } : {}), } try { diff --git a/src/hooks/useActiveCases.ts b/src/hooks/useActiveCases.ts index 01df52c..fb0a4b3 100644 --- a/src/hooks/useActiveCases.ts +++ b/src/hooks/useActiveCases.ts @@ -2,7 +2,11 @@ import { useQuery } from '@tanstack/react-query' import type { CategoryGroup } from '@/types/common' +import type { DisputeListResponse, DisputeDto } from '@/types/dispute' +// ActiveCasesSection 컴포넌트가 기대하는 participant 형태 +// API 응답(DisputeParticipantDto)은 profileImageUrl/nickname이 flat하게 있지만 +// 컴포넌트는 user.profileImageUrl처럼 nested 구조를 사용하므로 변환이 필요함 type Participant = { role: 'role_a' | 'role_b' user: { @@ -20,25 +24,41 @@ export type ActiveCase = { participants: Participant[] } -// 진행중인 사건 = 진술 시작 후 ~ 판결 전 -// draft(진술 전), judging(판결 처리 중), judged(판결 완료) 제외 -const ACTIVE_DISPUTE_STATUSES = ['waiting_opponent', 'opponent_joined', 'both_submitted'] as const -type ActiveDisputeStatus = (typeof ACTIVE_DISPUTE_STATUSES)[number] +// DisputeDto(API 응답) → ActiveCase(컴포넌트 타입) 변환 +// API의 flat 구조를 컴포넌트가 기대하는 nested user 구조로 맞춰줌 +function toActiveCase(dto: DisputeDto): ActiveCase { + return { + id: dto.id, + categoryGroup: dto.categoryGroup, + title: dto.title, + description: dto.description, + createdAt: dto.createdAt, + participants: dto.participants.map((p) => ({ + role: p.role, + user: { + nickname: p.nickname, + profileImageUrl: p.profileImageUrl, + }, + })), + } +} -type RawCase = ActiveCase & { status: ActiveDisputeStatus } +async function fetchActiveCases(): Promise { + // active=true: 서버에서 진행중 상태(waiting_opponent, opponent_joined, both_submitted, judging)만 필터링 + const res = await fetch('/api/disputes?active=true') + if (!res.ok) throw new Error('진행중인 사건을 불러오지 못했습니다.') -// TODO: 진행중인 사건 API 연동 시 활성화 -// async function fetchActiveCases(): Promise { -// const res = await fetch('/api/disputes?active=true') -// if (!res.ok) throw new Error('진행중인 사건을 불러오지 못했습니다.') -// const json = await res.json() -// return json.data.disputes -// } + const json: { data: DisputeListResponse } = await res.json() + + // API가 이미 createdAt 기준 최신순 정렬로 내려주므로 클라이언트 재정렬 불필요 + return json.data.disputes.map(toActiveCase) +} export function useActiveCases() { return useQuery({ queryKey: ['disputes', 'active'], - queryFn: async (): Promise => [], + queryFn: fetchActiveCases, + // 1분간 캐시 유지 — 홈 재진입 시 불필요한 재요청 방지 staleTime: 1000 * 60, }) } From 23a516b9ad554d3da82f152814a4ea12c546971d Mon Sep 17 00:00:00 2001 From: wjdalss21 Date: Sat, 20 Jun 2026 11:20:26 +0900 Subject: [PATCH 4/5] =?UTF-8?q?fix:=20=EC=82=AC=EA=B1=B4=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20AbortController=20=ED=83=80=EC=9E=84=EC=95=84?= =?UTF-8?q?=EC=9B=83=205=EC=B4=88=20=E2=86=92=2030=EC=B4=88=EB=A1=9C=20?= =?UTF-8?q?=EC=A6=9D=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- src/components/home/NewCaseButton.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/home/NewCaseButton.tsx b/src/components/home/NewCaseButton.tsx index 4c2bd23..3730271 100644 --- a/src/components/home/NewCaseButton.tsx +++ b/src/components/home/NewCaseButton.tsx @@ -31,7 +31,8 @@ export default function NewCaseButton() { setIsCreating(true) setErrorMessage(null) const controller = new AbortController() - const timeout = setTimeout(() => controller.abort(), 5000) + // 방 생성 + 사건 생성 두 번의 순차 API 호출을 커버할 수 있도록 30초로 설정 + const timeout = setTimeout(() => controller.abort(), 30000) try { const roomRes = await fetch('/api/rooms', { method: 'POST', From d4383fd5e3d944914a632e3ed28fa921eb6951da Mon Sep 17 00:00:00 2001 From: wjdalss21 Date: Sat, 20 Jun 2026 11:31:42 +0900 Subject: [PATCH 5/5] =?UTF-8?q?fix:=20disputes/[id]/route.ts=20toParticipa?= =?UTF-8?q?ntDto=EC=97=90=20nickname=20=ED=95=84=EB=93=9C=20=EB=88=84?= =?UTF-8?q?=EB=9D=BD=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- src/app/api/disputes/[id]/route.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/api/disputes/[id]/route.ts b/src/app/api/disputes/[id]/route.ts index 78fa89c..90106fe 100644 --- a/src/app/api/disputes/[id]/route.ts +++ b/src/app/api/disputes/[id]/route.ts @@ -41,6 +41,7 @@ function toParticipantDto(p: DisputeForDetail['participants'][number]): DisputeP disputeId: p.disputeId, userId: p.userId, role: p.role.toLowerCase() as DisputeParticipantDto['role'], + nickname: null, profileImageUrl: p.user.profileImageUrl ?? null, joinedAt: p.joinedAt.toISOString(), createdAt: p.createdAt.toISOString(),