# Phase V2-3: Web App MVP — 상세 설계 > 브라우저에서 로그인 → 녹음 → 전사 → 회의록 생성 → 편집을 수행하는 Next.js 앱. > V2-1의 `packages/{core,ui,i18n}`과 V2-2의 Supabase 인프라를 소비하는 첫 consumer. --- ## 1. 목표 1. **로그인 가능한 웹앱**: Supabase Auth OAuth (Google/GitHub) 2. **녹음 → 전사 파이프라인**: 브라우저 마이크 → Edge Function(stt-proxy) → DB 저장 3. **회의록 CRUD**: 리스트, 상세, 편집, 삭제 4. **AI 문서 생성**: 회의록 → llm-proxy 호출 → meeting_documents 저장 5. **UI 재사용**: `@d3ro/ui` DS 컴포넌트를 Next.js에서 사용 6. **Supabase 미설정 상태에서도 빌드 성공**: env 없어도 `next build` 통과 (런타임 placeholder) ## 2. 디렉토리 구조 ``` apps/web/ ├── package.json # @d3ro/web, Next.js 15, MUI 7, @d3ro/core/ui/i18n/api-client ├── next.config.mjs # transpilePackages: ['@d3ro/ui', '@d3ro/core', '@d3ro/i18n', '@d3ro/api-client'] ├── tsconfig.json # extends ../../tsconfig.base.json ├── next-env.d.ts ├── .env.local.example # NEXT_PUBLIC_SUPABASE_URL / ANON_KEY 템플릿 ├── public/ └── src/ ├── app/ │ ├── layout.tsx # 루트 layout + ThemeProvider + I18nProvider │ ├── page.tsx # / — 랜딩/로그인 리다이렉트 │ ├── login/ │ │ └── page.tsx # /login — OAuth 버튼 │ ├── auth/ │ │ └── callback/ │ │ └── route.ts # OAuth 콜백 처리 │ ├── dashboard/ │ │ ├── layout.tsx # 인증 가드 + 사이드바 │ │ └── page.tsx # /dashboard — 요약 카드 │ ├── meetings/ │ │ ├── page.tsx # /meetings — 리스트 │ │ └── [id]/ │ │ └── page.tsx # /meetings/[id] — 상세 │ └── record/ │ └── page.tsx # /record — 녹음 페이지 ├── components/ │ ├── providers/ │ │ ├── theme-provider.tsx # MUI + @d3ro/ui theme │ │ ├── i18n-provider.tsx # @d3ro/i18n + localStorage 어댑터 │ │ └── auth-provider.tsx # Supabase session context │ ├── layout/ │ │ ├── sidebar.tsx │ │ └── top-bar.tsx │ └── record/ │ ├── mic-recorder.tsx # getUserMedia + MediaRecorder │ └── waveform-bars.tsx # 웨이브 애니메이션 (DS 스타일) └── lib/ ├── supabase-server.ts # RSC/route handler용 서버 클라이언트 ├── supabase-browser.ts # 클라이언트 컴포넌트용 └── route-guards.ts # requireUser() 헬퍼 ``` ## 3. packages/api-client 설계 별도 패키지로 만들어 web과 desktop이 공유. ``` packages/api-client/ ├── package.json # @d3ro/api-client ├── tsconfig.json └── src/ ├── index.ts # barrel ├── client.ts # createSupabaseClient (url + key 주입형 팩토리) ├── types.ts # Database row 타입 (supabase gen types 대체) ├── auth.ts # signInWithOAuth, signOut, getSession ├── meetings.ts # CRUD 함수 (list, get, create, update, delete) ├── history.ts # history 테이블 CRUD └── usage.ts # daily_usage 조회 (쿼터 표시용) ``` ### 주요 설계 포인트 - **환경변수 주입형**: `createClient(url, anonKey)`를 factory로 제공. 각 앱(web/desktop)이 자체 env를 읽어 주입. - **타입**: V2-2의 SQL 스키마와 동기화. Supabase CLI로 `supabase gen types typescript` 실행 가능하지만 V2-3에서는 수동 타입 정의로 시작 (추후 자동화). - **SSR/CSR 양립**: Next.js RSC에서도 쓸 수 있도록 createClient는 `SupabaseClient`를 반환만 하고 상태를 들고 있지 않음. ## 4. Next.js 환경 설정 고려사항 ### 4.1 `transpilePackages` Monorepo의 `.ts` 소스를 Next.js가 직접 해석하도록: ```js // next.config.mjs export default { transpilePackages: ['@d3ro/core', '@d3ro/ui', '@d3ro/i18n', '@d3ro/api-client'] } ``` ### 4.2 MUI + Emotion RSC 호환 Next.js 15 App Router는 기본 RSC. MUI 컴포넌트는 `'use client'` 필요. → 루트 layout은 서버 컴포넌트 유지, ``를 client component로 래핑. → `@mui/material-nextjs/v15-appRouter`의 `AppRouterCacheProvider` 사용. ### 4.3 @d3ro/ui 컴포넌트 소비 DS 컴포넌트는 MUI 기반이라 그대로 사용 가능. 단, 컴포넌트마다 `'use client'`가 필요한지 확인. → `packages/ui/src/components/ds/*.tsx`는 MUI `Box/Typography` 사용 → client 필요 → 해결: 각 컴포넌트 상단에 `'use client'` 추가하거나, Next.js 측에서 client wrapper 도입. → MVP는 **page.tsx에 `'use client'` 지정**하는 방식으로 단순 처리. ### 4.4 i18n `@d3ro/i18n`은 React Context 기반이라 client 전용. `I18nProvider`는 localStorage를 `storage` 어댑터로 주입. ## 5. 인증 흐름 (OAuth) ``` /login (버튼 클릭) → supabase.auth.signInWithOAuth({ provider: 'google', options: { redirectTo: '/auth/callback' } }) → Supabase가 Google OAuth로 리다이렉트 → 사용자 동의 후 → /auth/callback?code=... → route handler가 code를 session으로 교환 → Supabase가 쿠키에 세션 저장 → /dashboard로 리다이렉트 ``` ### 세션 관리 - **브라우저**: `@supabase/ssr` 패키지의 `createBrowserClient` (쿠키 기반) - **RSC/route handler**: `createServerClient` + Next.js `cookies()` ## 6. 페이지별 요구사항 ### `/login` - 중앙 카드: "D3RO Voice" 로고 + OAuth 버튼 2개 (Google / GitHub) - 이메일 로그인은 V2-3에서는 제외 (이메일 확인 플로우 복잡, OAuth만) - 이미 로그인이면 `/dashboard`로 리다이렉트 ### `/dashboard` - 사이드바: Dashboard / Meetings / Record / Settings / Logout - 상단 카드 4개: 이번 주 회의 수, 총 녹음 시간, 사용 쿼터, 구독 티어 - 하단: 최근 회의 5건 ### `/meetings` - 카드 그리드 (반응형) - 각 카드: 제목, 날짜, 상태, duration, 첫 메모 1개 - 우상단: "새 회의" 버튼 → `/record` - 검색/필터 (MVP에서는 생략) ### `/meetings/[id]` - 탭: Transcript / Memos / Documents - Transcript: 세그먼트 리스트, 시간 스탬프, 화자 라벨 - Memos: 메모 리스트 - Documents: 생성된 문서 목록 + "새 문서 생성" 버튼 (llm-proxy 호출) ### `/record` - MicRecorder 컴포넌트 - 상태: idle | recording | processing | done - 버튼: Start / Stop / Cancel - 실시간 waveform (9개 바, Speakly 스타일) - 녹음 완료 시: - Blob → Edge Function `stt-proxy`로 POST - 응답의 transcript 표시 - "회의로 저장" 버튼 → `meetings` INSERT ## 7. Supabase 미설정 시 동작 빌드/dev가 항상 성공해야 함. env 없으면: - `createSupabaseClient`가 dummy client 반환 (모든 쿼리 에러 throw 대신 빈 배열) - `/login` 페이지에 "Supabase가 설정되지 않았습니다" 경고 배너 - 사용자가 `.env.local`에 env 추가 후 `pnpm dev` 재시작 ## 8. 빌드/실행 ```bash # 루트에서 npm install # apps/web 새 workspace 인식 # web dev 실행 npm run dev --workspace=@d3ro/web # http://localhost:3000 # web 빌드 npm run build --workspace=@d3ro/web ``` ## 9. 검증 전략 V2-3는 **빌드 통과 + RSC/client 경계 정리**가 MVP 목표. 실제 Supabase 연결 + 녹음 동작 검증은 사용자가 V2-2 배포 후 수행. - `typecheck` — workspace 전체 - `build` — apps/desktop (회귀 없음) + apps/web (Next.js 빌드) - apps/web은 Supabase env 없이도 빌드 성공해야 함 ## 10. 결제/Stripe 마스터 플랜에는 포함되지만 V2-3 범위 **외**로 미룸. → Stripe 연동은 V2-8 (결제 + 출시)에서 일괄 구현. ## 11. 다음 페이즈와의 관계 - **V2-4 (데스크톱 동기화)**: 이번에 만든 `@d3ro/api-client`를 apps/desktop에서도 재사용 - **V2-6 (Mobile)**: Expo 앱도 같은 `@d3ro/api-client` 사용 - **V2-7 (팀 기능)**: web의 /teams 페이지 추가 - **V2-8 (결제)**: /billing 페이지 + Stripe webhook 핸들러 (Supabase Edge Function)