# CLAUDE.md ## Project Overview DPOT - Next.js 15 App Router 기반 Creators Support and donate platform frontend (https://dpot.web.or.kr) - **Framework**: Next.js 15.3+ (App Router, Server Components) - **React**: 19.1+ - **Language**: TypeScript 5 - **Backend API**: https://localhost:4000 (ASP.NET Core Minimal API) ## Project Structure ``` app/ ├── (auth)/ → 인증 (login, register, forgot-password, reset-password, welcome, approval) ├── (main)/ → 메인 레이아웃 그룹 │ ├── (account)/ → 계정 관리 (profile, change-password, change-email 등) │ ├── (forum)/ → 게시판 (board, post, comment, latest) │ ├── channel/ → 채널 상세 ([channelSID]) │ ├── note/ → 쪽지 (inbox, send) │ ├── notification/→ 알림 목록 │ ├── support/ → 고객지원 (faq, guide, contact) │ └── docs/ → 문서 페이지 ├── widget/ → OBS 위젯 (alert, goal, rank, crew — 별도 레이아웃) ├── remote/ → 후원 리모콘 ([channelSID] — 별도 레이아웃) ├── api/ → Route Handler (auth, channel, donation, note, notification, payment, forum 등) ├── auth/ → 인증 관련 ├── component/ → 공통 레이아웃 컴포넌트 │ ├── channel/ → ChannelSidebar (좌측), ChannelSidebarItem │ ├── NotificationBell, ProfileDropdown, ChargeModal │ └── Layout, PopupModal, Editor └── styles/ → 글로벌 + 컴포넌트 SCSS components/ui/ → shadcn/ui 컴포넌트 (accordion, button, checkbox, dialog, dropdown-menu, input, label, select, textarea, HotIndicator) contexts/ → React Context Provider ├── authProvider.tsx → 로그인 상태 관리, 토큰 갱신 ├── memberProvider.tsx → 현재 사용자 정보 ├── configProvider.tsx → 시스템 설정 (initialConfig prop, React.cache) ├── signalrProvider.tsx → SignalR WebSocket (채팅) └── themeProvider.tsx → 테마 관리 hooks/ → 커스텀 훅 (useAuth, useChat, useDragScroll, useTheme, useErrorAlert, useDonationAlert, useDonationHub, useNotification) lib/ ├── api/ → 서버 사이드 API 호출 함수 (Server Actions) │ ├── auth.ts → 인증 API │ ├── account.ts → 계정 관련 API │ ├── system.ts → 시스템 설정 API │ ├── forum/ → board.ts, post.ts │ └── page/ → document.ts, faq.ts └── utils/ ├── client.ts → 클라이언트 유틸 (fetchApi, cn, formatDate, throwError) ├── server.ts → 서버 유틸 (fetchJson, getAccessToken, checkPermission, getSignalRChatUrl) └── permission.ts → 권한 체크 types/ → TypeScript 타입 정의 ├── response/ → API 응답 타입 (common, account, forum, page) ├── request/ → 요청 페이로드 타입 ├── forum/ → 게시판 도메인 타입 (post, comment, board, boardGroup 등) ├── account/ → 계정 타입 (member, loginLog) ├── chat.ts, broadcast.ts, config.ts └── editor.min.d.ts → CKEditor 타입 constants/ → 상수 정의 (common.ts, forum.ts) middleware.ts → 인증 미들웨어 (토큰 검증, 리다이렉트) ``` ## Code Style Rules - 인덴트: 탭 사용 - PK/ID 변수명: `memberID`, `postID` (camelCase + 대문자 ID) - 클라이언트 컴포넌트: 파일 최상단에 `'use client'` 선언 - 서버 전용 함수: `'use server'` 선언 (Server Actions, lib/utils/server.ts) - 컴포넌트 파일명: PascalCase (`PopupModal.tsx`, `Layout.tsx`) - 페이지/라우트: 소문자 kebab-case 디렉토리 (`support/faq/`) - 스타일: `app/styles/` 에 컴포넌트별 SCSS 파일 (inline CSS 사용 금지 → SCSS 또는 Tailwind) - SCSS 클래스명: BEM 네이밍 (`block__element--modifier`) - 파일 내용의 마지막 줄 제거 (빈 줄 없이 끝남) - type 을 지정할 때 `|` 문자 양쪽에 공백 제거해(예시로 `string|null|undefined`) - button 태그에 submit type이 아니면 `type="button"` 필수 - C# 람다 `) => {` 한 줄에 붙여 작성 (개행 금지) — Frontend에서도 동일 스타일 적용 - `if (loading) return

준비 중...

;` 이런 식으로 한 줄로 처리하지 말고 중괄호 사용해줘 그리고 다음 줄로 개행을 하도록 해 ## Architecture Rules - **서버 컴포넌트가 기본** — `'use client'` 없으면 RSC - **클라이언트 상태**: React Context (AuthProvider, MemberProvider, ConfigProvider) - **API 프록시 패턴**: 클라이언트 → `app/api/[도메인]/[...path]/route.ts` → 백엔드 - 클라이언트에서 직접 백엔드 호출 안 함 (CORS, 쿠키 처리) - **서버 데이터 흐름**: Server Component → `fetchJson()` → 백엔드 직접 호출 - **인증**: JWT Bearer 토큰 (accessToken/refreshToken, httpOnly 쿠키) - **실시간**: SignalR WebSocket - AppHub (`/hubs/app`) — 채팅, 알림, 쪽지, 접속자, 크루 초대 - DonationHub (`/hubs/donation`) — 후원 위젯/리모콘 - **결제**: 다날 PG (`@danalpay/javascript-sdk`) ## Code Conventions ### 사용하는 패턴 - `fetchApi()` — 클라이언트에서 Route Handler 호출 (`lib/utils/client.ts`), 실패 시 자동 throw (에러 무시 시 `{ silent: true }`) - `fetchJson()` — 서버에서 백엔드 직접 호출 (`lib/utils/server.ts`) - `ResultDto` — 모든 API 응답 래퍼 (`{ success, status, message, data, errors }`) - `cn()` — Tailwind 클래스 병합 (clsx + tailwind-merge) - `dangerouslySetInnerHTML` — 서버에서 받은 HTML 콘텐츠 렌더링 - Route Group — `(auth)`, `(main)/(account)`, `(main)/(forum)` 으로 관련 페이지 그룹화 - `notFound()` — 데이터 없을 때 404 처리 ### 사용하지 않는 패턴 (제안 금지) - Redux, Zustand (Context + 로컬 상태 사용) - GraphQL (REST API만 사용) - 클라이언트에서 백엔드 직접 호출 (반드시 Route Handler 프록시 경유) - CSS-in-JS (Tailwind + SCSS 사용) - React Query / SWR (직접 fetch 사용) ## Styling - **Tailwind CSS** — 기본 스타일링 - **SCSS** — 컴포넌트별 `style.scss`, 글로벌 `globals.scss` - **CSS Modules** — `common.module.scss` (레이아웃 그리드) - **shadcn/ui** — new-york 스타일, Radix UI 기반 (`components/ui/`) - **CSS Variables** — HSL 형식 테마 변수 (`--background`, `--foreground` 등) - **Font Awesome** — 아이콘 (@fortawesome 패키지) - **Lucide React** — shadcn/ui 아이콘 ## Provider 구조 (Root Layout) ``` ThemeProvider → SignalRProvider → AuthProvider → MemberProvider → ConfigProvider → {children} ``` - `ThemeProvider`: 테마 관리 (라이트/다크 모드) - `SignalRProvider`: WebSocket 연결 (AppHub) — accessToken, signalRUrl prop - `AuthProvider`: 로그인 상태 관리, 토큰 갱신 - `MemberProvider`: 현재 사용자 정보 - `ConfigProvider`: `initialConfig` prop으로 서버에서 설정 전달 (React.cache) ## 용어 규칙 - **POINT (포인트)**: 후원 가능한 금액 (PG 충전 → PgCharged 잔액) - **머니 (Money)**: 후원 받은 금액 (Donation 잔액 → 출금 가능) ## 레이아웃 구조 - **좌측 사이드바** (`ChannelSidebar`): 채널 목록, 온/오프라인 LED, 시청자 수, 다크모드/다국어/즐겨찾기 - **우측**: 방송 상세 페이지에서 채팅 표시 (고정 아님, 페이지별 조건부) - **모바일**: 하단 네비게이션 제거 → 상단 가로 스크롤 탭 (YouTube 모바일 스타일) - **위젯/리모콘**: 별도 Route Group, root layout 미사용 ## API Route Handler 패턴 ```typescript // app/api/{도메인}/[...path]/route.ts export async function POST(request: NextRequest, { params }: { params: Promise<{ path: string[] }> }) { const { path } = await params; const endpoint = `/api/${path.join('/')}`; const res: ResultDto = await fetchJson(endpoint, { method: 'POST', body: await request.arrayBuffer(), headers: { 'Content-Type': request.headers.get('content-type') || '' } }); return NextResponse.json(res); } ``` ## Build & Run ```bash # 개발 서버 (HTTPS, 포트 3000) npm run dev # 프로덕션 빌드 npm run build # 프로덕션 실행 npm run start # 린트 npm run lint ``` ## 사용 Domain - https://dpot.web.or.kr -> 사용자 단 사용 URL - https://admin.dpot.web.or.kr -> 관리자 단 사용 URL - https://api.dpot.web.or.kr -> 사용자 단 통신 API URL