# 画面設計書 4-メール確認画面

## 概要

本ドキュメントは、Giselle Studio のメール確認画面の設計仕様を記載したものである。

### 本画面の処理概要

メール確認画面は、メールサインアップ後にユーザーのメールアドレスを検証するための確認コード入力画面である。サインアップ時に登録したメールアドレスに送信された6桁の確認コードを入力し、アカウントのメール認証を完了する。

**業務上の目的・背景**：本画面は、ユーザーが入力したメールアドレスが実際にそのユーザーのものであることを確認するための認証ステップである。メール認証により、なりすましアカウントの作成を防止し、ユーザーへの重要な通知（パスワードリセット、チーム招待等）が正しい宛先に届くことを保証する。また、スパムアカウントの大量作成を抑制する効果もある。

**画面へのアクセス方法**：以下の方法でメール確認画面にアクセスできる。
- メールサインアップ画面でサインアップ成功後、自動的にリダイレクト
- ブラウザで直接 `/signup/verify-email` URLにアクセス（メールアドレスがコンテキストに保持されている場合のみ有効）

**主要な操作・処理内容**：
1. 確認コード入力：6桁のOTPコード（ワンタイムパスワード）を入力
2. コード検証：入力完了後、自動的に検証を実行（または「Verify」ボタン押下）
3. コード再送信：確認コードが届かない場合の再送信機能
4. アカウント初期化：検証成功時にアカウント初期化処理を実行

**画面遷移**：
- 遷移元：メールサインアップ画面
- 遷移先：ワークスペース一覧画面（検証成功時）

**権限による表示制御**：メール確認画面はサインアップ直後の未認証ユーザーのみがアクセスすることを想定している。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 96 | ユーザー認証 | 主機能 | サインアップ後のメール確認コード入力と検証処理 |

## 画面種別

認証（確認コード入力）

## URL/ルーティング

| 項目 | 値 |
|------|-----|
| URL | `/signup/verify-email` |
| ルートファイル | `apps/studio.giselles.ai/app/(auth)/signup/verify-email/page.tsx` |

## 入出力項目

### 入力項目

| 項目名 | 項目ID | 型 | 必須 | 最大長 | 説明 |
|--------|--------|-----|------|--------|------|
| 確認コード | token | string | Yes | 6 | 6桁の確認コード（OTP） |
| メールアドレス | verificationEmail | string | Yes | - | 検証対象のメールアドレス（hidden、コンテキストから取得） |

### 出力項目

本画面での直接的な出力項目はない。検証成功時はワークスペース一覧画面へリダイレクト、失敗時はエラーメッセージを表示する。

## 表示項目

| 項目名 | 表示内容 | 説明 |
|--------|----------|------|
| タイトル | "Verify your email" | 画面タイトル |
| 説明文 | "We've sent a confirmation code to your email. Please enter it below to complete your registration." | コード入力の案内 |
| OTP入力フィールド | 6桁のコード入力欄 | 3桁ずつ区切られた入力UI |
| 再送信誘導 | "Didn't receive a code?" + "Click to resend" | コード再送信への誘導 |
| 検証ボタン | "Verify" | 検証実行ボタン |

## イベント仕様

### 1-確認コード入力完了

**トリガー**: 6桁の確認コードを全て入力

**処理フロー**:
1. `InputOTP`コンポーネントの`onComplete`イベントが発火
2. フォームが自動的にサブミットされる（`requestSubmit`）
3. Server Action `verifyEmail` が呼び出される
4. コンテキストから`verificationEmail`を取得
5. `supabase.auth.verifyOtp`で確認コードを検証
6. 検証成功時、`initializeAccount`でアカウント初期化を実行
7. `/`（ルート）にリダイレクト

**例外処理**:
- トークン未入力：`invalid_verification_payload`エラーを返却
- トークン無効/期限切れ：Supabaseエラーメッセージを表示（オーバーライドあり）
- ユーザー取得失敗：`missing_user`エラーを返却

### 2-検証ボタン押下

**トリガー**: "Verify" ボタンをクリック

**処理フロー**:
1. イベント1（確認コード入力完了）と同じ処理フロー

### 3-コード再送信リンク押下

**トリガー**: "Click to resend" リンクをクリック

**処理フロー**:
1. Server Action `resendOtp` が呼び出される
2. コンテキストから`verificationEmail`を取得
3. `supabase.auth.resend`で確認コードを再送信
4. 成功時、成功メッセージを表示
5. 失敗時、エラーメッセージを表示

**例外処理**:
- メールアドレス未取得：`invalid_email`エラーを返却
- 再送信失敗：Supabaseエラーメッセージを表示

## データベース更新仕様

### 操作別データベース影響一覧

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 確認コード検証成功 | users | INSERT | 新規ユーザーレコード作成 |
| 確認コード検証成功 | supabase_user_mappings | INSERT | Supabaseユーザーとの紐付け |
| 確認コード検証成功 | teams | INSERT | デフォルトチーム作成 |
| 確認コード検証成功 | team_memberships | INSERT | チームメンバーシップ作成 |
| 確認コード検証成功 | agents | INSERT | サンプルエージェント作成 |
| 確認コード検証成功 | workspaces | INSERT | サンプルワークスペース作成 |
| 確認コード検証成功 | apps | INSERT | サンプルアプリ作成 |

### テーブル別更新項目詳細

#### users

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | `usr_${createId()}` | CUID形式のユーザーID |
| INSERT | email | supabaseUserEmail | Supabaseから取得 |
| INSERT | avatarUrl | null | メール登録時はアバターなし |

#### teams

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | `createTeamId()` | チームID生成 |
| INSERT | name | "My Project" | デフォルトチーム名 |
| INSERT | plan | "internal" / "free" | 内部ユーザーはinternal、それ以外はfree |

## メッセージ仕様

| メッセージ種別 | メッセージ内容 | 表示条件 |
|--------------|---------------|----------|
| エラー | "Invalid verification payload. Please request a new code." | トークンまたはメールアドレスが取得できない場合 |
| エラー | "The confirmation code you've entered has expired or is invalid." | 確認コードが期限切れまたは無効な場合 |
| エラー | "Please enter a confirmation code." | 確認コードが未入力の場合 |
| エラー | "No user returned" | 検証後にユーザー情報が取得できない場合 |
| エラー | "Please enter a valid email address." | 再送信時にメールアドレスが取得できない場合 |
| 成功 | "A new confirmation code has been sent to your email address." | コード再送信成功時 |

## 例外処理

| 例外種別 | 条件 | 処理内容 |
|---------|------|----------|
| バリデーションエラー | トークンまたはメールアドレスが未取得 | エラーメッセージを表示 |
| 認証エラー | 確認コードが無効/期限切れ | ユーザーフレンドリーなエラーメッセージを表示 |
| システムエラー | ユーザー情報取得失敗 | エラーメッセージを表示 |
| 再送信エラー | 確認コード再送信失敗 | エラーメッセージを表示 |

## 備考

- OTP入力には`InputOTP`コンポーネントを使用（3桁ずつ区切られたUI）
- 6桁全て入力すると自動的にフォームがサブミットされる
- `SignupContext`からメールアドレスを取得（前画面で保存）
- 確認コードの期限切れメッセージはオーバーライドされ、ユーザーフレンドリーなメッセージに変換
- 検証成功時に`initializeAccount`が呼ばれ、サンプルワークスペースとアプリが自動作成される
- `notranslate`クラスでGoogle Chromeの自動翻訳によるDOMエラーを防止

---

## コードリーディングガイド

本画面を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

まず、OTP検証で使用されるデータ構造とコンテキストを理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | context.tsx | `apps/studio.giselles.ai/app/(auth)/signup/context.tsx` | `SignupState`と`SignupContext`の定義（行14-37） |
| 1-2 | error.ts | `apps/studio.giselles.ai/lib/supabase/error.ts` | `AUTH_ERROR_MESSAGE_OVERRIDES`（行21-26）でのエラーメッセージ変換 |

**読解のコツ**: `SignupContext`のstate.emailに前画面で入力したメールアドレスが保持されている。このメールアドレスがOTP検証に使用される。

#### Step 2: エントリーポイントを理解する

処理の起点となるページコンポーネントを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | page.tsx | `apps/studio.giselles.ai/app/(auth)/signup/verify-email/page.tsx` | シンプルなページ構造（行1-19）、`VerifyEmailForm`の配置 |

**主要処理フロー**:
1. **行4-18**: 最小限のレイアウト構成
2. **行7-9**: タイトル表示
3. **行10-13**: 説明文表示
4. **行15**: `VerifyEmailForm`でOTP入力フォームを表示

#### Step 3: フォームコンポーネントを理解する

OTP入力と検証処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | verify-email-form.tsx | `apps/studio.giselles.ai/app/(auth)/signup/verify-email/verify-email-form.tsx` | 複数の`useActionState`（行19-26）、自動サブミット（行28-30）、再送信処理（行31-35） |

**主要処理フロー**:
- **行18**: `useSignupContext`でメールアドレスを取得
- **行19-22**: `useActionState(verifyEmail)`で検証状態を管理
- **行23-26**: `useActionState(resendOtp)`で再送信状態を管理
- **行28-30**: `handleComplete`で6桁入力時に自動サブミット
- **行31-35**: `handleResend`で再送信処理
- **行74-92**: `InputOTP`コンポーネントで3+3桁のOTP入力UI
- **行93**: hiddenフィールドでメールアドレスを送信

#### Step 4: Server Action（検証・再送信処理）を理解する

実際の検証と再送信ロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | verify-email.ts | `apps/studio.giselles.ai/app/(auth)/signup/verify-email/verify-email.ts` | `verifyEmail`（行7-50）と`resendOtp`（行52-80）の実装 |

**主要処理フロー**:
- **行11-23**: バリデーション（メールとトークンの存在チェック）
- **行27-31**: `supabase.auth.verifyOtp`でOTP検証
- **行32-34**: 検証エラーハンドリング
- **行35-42**: ユーザー取得エラーハンドリング
- **行44-48**: `initializeAccount`でアカウント初期化
- **行49**: `/`にリダイレクト
- **行66-70**: `supabase.auth.resend`でOTP再送信
- **行74-79**: 成功時にsuccessメッセージを返却

#### Step 5: アカウント初期化を理解する

検証成功後のアカウント初期化処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | initialize-account.ts | `apps/studio.giselles.ai/services/accounts/actions/initialize-account.ts` | トランザクション内でのユーザー・チーム・サンプル作成（行20-176） |

**主要処理フロー**:
- **行25-40**: usersテーブルとsupabase_user_mappingsへのINSERT
- **行41-57**: teamsテーブルとteam_membershipsへのINSERT
- **行60-171**: サンプルワークスペースとアプリの作成

### プログラム呼び出し階層図

```
layout.tsx
    │
    └─ SignupContextProvider
           │
           └─ page.tsx
                  │
                  └─ VerifyEmailForm (Client Component)
                         │
                         ├─ useSignupContext() ─▶ state.email
                         │
                         ├─ useActionState(verifyEmail)
                         │      │
                         │      ├─ supabase.auth.verifyOtp
                         │      │      └─ 検証成功
                         │      │            │
                         │      │            └─ initializeAccount
                         │      │                   ├─ users INSERT
                         │      │                   ├─ teams INSERT
                         │      │                   ├─ workspaces INSERT
                         │      │                   └─ apps INSERT
                         │      │
                         │      └─ redirect("/")
                         │
                         ├─ useActionState(resendOtp)
                         │      └─ supabase.auth.resend
                         │
                         └─ InputOTP
                                ├─ onComplete ─▶ handleComplete ─▶ requestSubmit
                                └─ 6桁入力UI (3+3)
```

### データフロー図

```
[入力]                      [処理]                                    [出力]

6桁OTPコード ───▶ InputOTP
                      │
                      ├─▶ onComplete (6桁入力完了)
                      │         │
                      │         └─▶ requestSubmit (自動サブミット)
                      │
                      └─▶ verifyEmail (Server Action)
                                │
                                ├─▶ supabase.auth.verifyOtp
                                │         │
                                │         ├─▶ 成功 ───▶ initializeAccount
                                │         │                    │
                                │         │                    └─▶ DB INSERT (users, teams, etc.)
                                │         │                              │
                                │         │                              └─▶ redirect("/")
                                │         │
                                │         └─▶ 失敗 ───▶ AuthError
                                │                          │
                                │                          └─▶ エラーメッセージ表示


"Click to resend" ───▶ resendOtp (Server Action)
                            │
                            └─▶ supabase.auth.resend
                                      │
                                      ├─▶ 成功 ───▶ 成功メッセージ表示
                                      │
                                      └─▶ 失敗 ───▶ エラーメッセージ表示
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| page.tsx | `apps/studio.giselles.ai/app/(auth)/signup/verify-email/page.tsx` | ソース | メール確認画面のメインページコンポーネント |
| verify-email-form.tsx | `apps/studio.giselles.ai/app/(auth)/signup/verify-email/verify-email-form.tsx` | ソース | OTP入力フォームのClient Component |
| verify-email.ts | `apps/studio.giselles.ai/app/(auth)/signup/verify-email/verify-email.ts` | ソース | OTP検証と再送信のServer Actions |
| context.tsx | `apps/studio.giselles.ai/app/(auth)/signup/context.tsx` | ソース | サインアップコンテキストプロバイダー |
| input-otp.tsx | `apps/studio.giselles.ai/components/ui/input-otp.tsx` | ソース | OTP入力UIコンポーネント |
| action-prompt.tsx | `apps/studio.giselles.ai/app/(auth)/components/action-prompt.tsx` | ソース | アクション誘導コンポーネント |
| auth-button.tsx | `apps/studio.giselles.ai/app/(auth)/components/auth-button.tsx` | ソース | 認証画面共通のボタンコンポーネント |
| initialize-account.ts | `apps/studio.giselles.ai/services/accounts/actions/initialize-account.ts` | ソース | アカウント初期化処理 |
| error.ts | `apps/studio.giselles.ai/lib/supabase/error.ts` | ソース | 認証エラーの型定義と正規化 |
