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 (회귀 없음)
201 lines
8.4 KiB
Markdown
201 lines
8.4 KiB
Markdown
# 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)
|