feat(V2-3): Web App MVP — Next.js 15 + @d3ro/api-client

packages/api-client (@d3ro/api-client) 신규:
- types.ts: 12개 테이블 Row/Insert/Update 타입 + Database 형식
- client.ts: createD3roSupabaseClient 팩토리 (placeholder fallback)
- auth.ts: signInWithOAuth/signOut/getSession/onAuthStateChange
- meetings.ts: 회의 + 메모 + 문서 + 전사 CRUD
- history.ts: 음성 입력 이력 CRUD
- usage.ts: 일일 쿼터 + 구독 조회
- 루트 barrel은 types만 re-export, 함수는 subpath import 전용

apps/web (@d3ro/web) 신규 — Next.js 15 App Router:
- transpilePackages: @d3ro/core, ui, i18n, api-client
- ThemeProvider (MUI + AppRouterCacheProvider)
- I18nProvider (localStorage 어댑터)
- AuthProvider (Supabase session Context)
- 라우트 9개:
  - / (auth 상태 기반 리다이렉트)
  - /login (Google/GitHub OAuth, 미설정 경고)
  - /auth/callback (code -> session 교환)
  - /dashboard (요약 카드 + 최근 회의)
  - /meetings (카드 그리드 리스트)
  - /meetings/[id] (transcripts/memos/documents)
  - /record (getUserMedia + MediaRecorder + stt-proxy)
- Sidebar, 인증 가드, 9바 웨이브폼, 레벨 미터

packages/ui 확장:
- MetalCard가 BoxProps 상속 (sx/onClick 등 전달)
- theme.ts에 typoSx(key) 헬퍼 추가 (d3roTypo -> MUI sx 변환)
- d3roPalette.tag.blue 추가 (M5 정리 포함)
- DS 컴포넌트 9개에 'use client' directive
- MetalDial 미사용 import 제거

packages/i18n 확장:
- ko.json에 7개 새 키 (nav.meetings/record/logout, login.*)

설계 결정:
- Database 제네릭 현재는 기본 타입 (V2-4에서 supabase gen types로 자동화)
- api-client barrel은 타입만 노출해서 컴파일 전파 차단
- DS 컴포넌트 client 경계 명시
- env 없어도 Next.js 빌드 성공 (placeholder URL/key)

검증:
- web typecheck OK
- desktop typecheck OK (회귀 없음)
- web next build OK (9 라우트 정적/동적 생성)
- desktop build OK (회귀 없음)
This commit is contained in:
yunchan8804 2026-04-09 02:52:31 +09:00
parent 9742b2109a
commit d0c33ca259
190 changed files with 6167 additions and 18 deletions

201
docs/v2/phase-V2-3.md Normal file
View file

@ -0,0 +1,201 @@
# 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은 서버 컴포넌트 유지, `<ThemeProvider>`를 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)