# 画面設計書 101-サインアップフォーム

## 概要

本ドキュメントは、Ghost CMSの埋め込み用サインアップフォーム（Signup Form）画面の設計を定義するものである。この画面は外部サイトに埋め込み可能なメール購読登録フォームを提供する。

### 本画面の処理概要

サインアップフォームは、Ghostサイトへのメール購読者（メンバー）を獲得するための埋め込みウィジェットである。外部サイトや任意のWebページに設置でき、訪問者がメールアドレスを入力することで簡単にニュースレター購読を開始できる。

**業務上の目的・背景**：Ghostサイト運営者がサイト外でもメール購読者を獲得できるようにするため、埋め込み可能なサインアップフォームが必要である。これにより、ランディングページ、外部ブログ、パートナーサイトなど様々な場所からメンバー獲得が可能となり、サイトの成長を促進する。フォームはカスタマイズ可能で、ブランドカラーやタイトル、説明文を設定でき、埋め込み先サイトのデザインに合わせることができる。

**画面へのアクセス方法**：このウィジェットは管理画面の「設定 > 成長 > 埋め込みフォーム設定」からHTMLコードを取得し、任意のWebページに埋め込むことで表示される。scriptタグにdata属性を設定することでカスタマイズが可能である。

**主要な操作・処理内容**：
1. メールアドレスの入力 - ユーザーがメールアドレスを入力フィールドに入力する
2. バリデーション - 入力されたメールアドレスの形式を検証する
3. Integrity Token取得 - スパム防止のためのトークンをAPIから取得する
4. マジックリンク送信 - メンバーAPI経由でマジックリンクメールを送信する
5. 成功/エラー表示 - 処理結果に応じたフィードバックを表示する

**画面遷移**：
- 成功時（通常モード）: サインアップ成功ページ（SuccessPage）へ遷移
- 成功時（ミニマルモード）: フォーム内で「Email sent」と表示し、遷移なし
- エラー時: 同一画面でエラーメッセージを表示

**権限による表示制御**：この画面は公開ウィジェットであり、認証不要で誰でも利用可能。ただし、埋め込み元サイトのセキュリティ設定やGhostサイト側のスパムフィルター設定により、登録が制限される場合がある。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 8 | メンバー登録 | 主機能 | メール購読の登録処理 |
| 73 | Signup Form | 補助機能 | 埋め込みフォームUIの表示 |
| 9 | メンバー認証 | API連携 | マジックリンクによる認証処理 |

## 画面種別

登録（埋め込みウィジェット）

## URL/ルーティング

- 埋め込みスクリプト: 外部サイトの任意のページに`<script>`タグで設置
- API エンドポイント: `{siteUrl}/members/api/integrity-token/`, `{siteUrl}/members/api/send-magic-link/`

## 入出力項目

### 入力項目

| 項目名 | 項目ID | 型 | 必須 | 最大桁数 | 入力形式 | 説明 |
|--------|--------|-----|------|----------|----------|------|
| メールアドレス | email | string | 必須 | - | email | 購読登録用メールアドレス |

### 設定項目（data属性）

| 項目名 | data属性 | 型 | 必須 | 説明 |
|--------|----------|-----|------|------|
| サイトURL | data-site | string | 任意 | GhostサイトのURL（省略時はwindow.location.origin） |
| タイトル | data-title | string | 任意 | フォーム上部に表示するタイトル |
| 説明文 | data-description | string | 任意 | タイトル下に表示する説明文 |
| アイコン | data-icon | string | 任意 | タイトル上に表示するアイコン画像URL |
| 背景色 | data-background-color | string | 任意 | フォームの背景色 |
| テキスト色 | data-text-color | string | 任意 | テキストの色 |
| ボタン色 | data-button-color | string | 任意 | 送信ボタンの背景色 |
| ボタンテキスト色 | data-button-text-color | string | 任意 | 送信ボタンのテキスト色 |
| ラベル | data-label-1, data-label-2, ... | string | 任意 | 登録時に付与するラベル（複数指定可） |
| ロケール | data-locale | string | 任意 | 表示言語（デフォルト: 'en'） |

## 表示項目

| 項目名 | 表示条件 | 説明 |
|--------|----------|------|
| アイコン | options.icon が設定されている場合 | 64px高さのアイコン画像 |
| タイトル | options.title が設定されている場合 | フォームのタイトル（h1） |
| 説明文 | options.description が設定されている場合 | フォームの説明文 |
| メールアドレス入力欄 | 常時 | type="email"の入力フィールド |
| Subscribeボタン | loading/success以外 | 送信ボタン |
| Email sentテキスト | ミニマルモードかつsuccess時 | 送信完了テキスト |
| ローディングアイコン | loading時 | スピナーアイコン |
| エラーメッセージ | error時 | 赤色のエラーテキスト |

## イベント仕様

### 1-フォーム送信

**トリガー**: Subscribeボタンクリックまたはフォームsubmit

**処理フロー**:
1. メールアドレスのバリデーション実行（正規表現チェック）
2. 無効な場合、エラーメッセージ「Please enter a valid email address」を表示して処理終了
3. loadingステートをtrueに設定
4. `/members/api/integrity-token/`からIntegrity Tokenを取得
5. `/members/api/send-magic-link/`にPOSTリクエストを送信
   - リクエストボディ: email, emailType('signup'), labels, urlHistory, integrityToken
6. 成功時:
   - ミニマルモード: successステートをtrueに設定、フォーム内で完了表示
   - 通常モード: SuccessPageへ遷移
7. エラー時: エラーメッセージ「Something went wrong, please try again.」を表示

### 2-入力値変更

**トリガー**: メールアドレス入力欄への入力

**処理フロー**:
1. 入力値をemailステートに保存
2. UIの入力欄に反映

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| マジックリンク送信 | members | INSERT | 新規メンバーレコードの作成（バックエンド処理） |
| マジックリンク送信 | members_labels | INSERT | メンバーへのラベル紐付け（バックエンド処理） |
| マジックリンク送信 | members_subscribe_events | INSERT | 購読イベントの記録（バックエンド処理） |

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

#### members（バックエンドで処理）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | email | 入力されたメールアドレス | マジックリンク確認後に作成 |
| INSERT | status | 'free' | 無料メンバーとして登録 |

## メッセージ仕様

| メッセージID | 種別 | メッセージ | 表示条件 |
|-------------|------|-----------|----------|
| MSG-001 | エラー | Please enter a valid email address | メールアドレス形式が不正な場合 |
| MSG-002 | エラー | Something went wrong, please try again. | API通信エラー時 |
| MSG-003 | 成功 | Email sent | ミニマルモードでの送信成功時（ボタン内表示） |

## 例外処理

| 例外状況 | 処理内容 | 表示メッセージ |
|---------|---------|---------------|
| メールアドレス形式不正 | 送信処理を中止し、エラー表示 | Please enter a valid email address |
| Integrity Token取得失敗 | 送信処理を中止し、エラー表示 | Something went wrong, please try again. |
| マジックリンク送信失敗 | エラー表示 | Something went wrong, please try again. |
| ネットワークエラー | エラー表示 | Something went wrong, please try again. |

## 備考

- ミニマルモードは`data-title`が設定されていない場合に自動的に有効となる
- URLヒストリー（アトリビューション情報）は、埋め込み先がGhostサイトと同じドメインの場合はsessionStorageから取得し、異なるドメインの場合は現在のURLとEmbedミディアムとして記録される
- i18n対応により、`data-locale`で指定した言語でUIテキストが表示される
- フォームは1ページに複数設置可能（各scriptタグに対応したルートDIVが生成される）

---

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

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

### 推奨読解順序

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

まず、フォームの設定オプションとAPIの型定義を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | app-context.ts | `apps/signup-form/src/app-context.ts` | SignupFormOptionsの型定義（6-17行目）を確認し、フォームで使用可能な設定項目を把握 |
| 1-2 | api.tsx | `apps/signup-form/src/utils/api.tsx` | GhostApiの型定義とAPIメソッド（getIntegrityToken, sendMagicLink）を理解 |

**読解のコツ**: TypeScriptの型定義から、アプリケーションで扱うデータの構造を把握する。SignupFormOptionsがdata属性から読み取られる設定値、GhostApiがバックエンドとの通信インターフェースを表す。

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

処理の起点となるファイル・関数を特定する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.tsx | `apps/signup-form/src/index.tsx` | アプリケーション初期化処理。scriptタグの取得とReactアプリのマウント |
| 2-2 | App.tsx | `apps/signup-form/src/App.tsx` | アプリケーションルートコンポーネント。コンテキスト設定とページルーティング |

**主要処理フロー**:
1. **6-23行目 (index.tsx)**: getScriptTag()でscriptタグを取得
2. **28-41行目 (index.tsx)**: getRootDiv()でマウント先DIVを生成
3. **43-52行目 (index.tsx)**: init()でReactアプリをレンダリング
4. **14-56行目 (App.tsx)**: Appコンポーネントでオプション読み込み、API初期化、コンテキスト提供

#### Step 3: オプション処理を理解する

scriptタグのdata属性からオプションを読み取る処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | options.tsx | `apps/signup-form/src/utils/options.tsx` | useOptionsフックによるdata属性の読み取りとMutationObserverによる動的更新 |

**主要処理フロー**:
- **5-24行目**: buildOptions()でscriptTagのdatasetから各オプションを抽出
- **28-42行目**: MutationObserverで属性変更を監視し、オプションを動的に更新

#### Step 4: フォームページを理解する

フォームのロジックとUI表示を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | form-page.tsx | `apps/signup-form/src/components/pages/form-page.tsx` | フォームのビジネスロジック。submit処理、エラーハンドリング |
| 4-2 | form-view.tsx | `apps/signup-form/src/components/pages/form-view.tsx` | フォームのUI表示。通常モードとミニマルモードの分岐 |

**主要処理フロー**:
- **14-39行目 (form-page.tsx)**: submit関数でバリデーション、API呼び出し、ページ遷移を制御
- **15-21行目 (form-view.tsx)**: ミニマルモードの場合のシンプルなフォーム表示
- **24-39行目 (form-view.tsx)**: 通常モードのフルUI表示
- **53-91行目 (form-view.tsx)**: Formコンポーネントでinput、button、loading状態を表示

#### Step 5: API通信を理解する

バックエンドとの通信処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | api.tsx | `apps/signup-form/src/utils/api.tsx` | APIクライアントの実装。endpointFor関数とHTTPリクエスト |

**主要処理フロー**:
- **15-30行目**: getIntegrityToken()でスパム防止トークンを取得
- **31-55行目**: sendMagicLink()でメールアドレス、ラベル、URL履歴を送信

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

```
index.tsx (init)
    |
    +-- getScriptTag()
    +-- getRootDiv()
    +-- ReactDOM.createRoot().render()
            |
            +-- App.tsx
                    |
                    +-- useOptions() ... options.tsx
                    +-- setupGhostApi() ... api.tsx
                    +-- AppContextProvider
                            |
                            +-- Frame
                            +-- ContentBox
                                    |
                                    +-- FormPage ... form-page.tsx
                                            |
                                            +-- FormView ... form-view.tsx
                                                    |
                                                    +-- Form (内部コンポーネント)
                                            |
                                            +-- api.getIntegrityToken()
                                            +-- api.sendMagicLink()
                                            |
                                            +-- setPage('SuccessPage') --> SuccessPage
```

### データフロー図

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

scriptタグ data属性 ───────▶ useOptions() ─────────────────────▶ SignupFormOptions
                                    |
                                    v
                              AppContextProvider
                                    |
メールアドレス入力 ─────────▶ Form.submitHandler() ─────────────▶ ローディング表示
                                    |
                                    +── isValidEmail() ──────────▶ エラーメッセージ（無効時）
                                    |
                                    +── api.getIntegrityToken() ──▶ integrityToken
                                    |
                                    +── api.sendMagicLink() ──────▶ 成功: SuccessPage遷移
                                    |                                  または Email sent表示
                                    +── エラーキャッチ ───────────▶ エラーメッセージ
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.tsx | `apps/signup-form/src/index.tsx` | エントリーポイント | アプリケーション初期化 |
| App.tsx | `apps/signup-form/src/App.tsx` | ソース | ルートコンポーネント、コンテキスト設定 |
| app-context.ts | `apps/signup-form/src/app-context.ts` | ソース | コンテキスト定義、型定義 |
| pages.tsx | `apps/signup-form/src/pages.tsx` | ソース | ページコンポーネントのエクスポート |
| form-page.tsx | `apps/signup-form/src/components/pages/form-page.tsx` | ソース | フォームページのロジック |
| form-view.tsx | `apps/signup-form/src/components/pages/form-view.tsx` | ソース | フォームページのUI |
| success-page.tsx | `apps/signup-form/src/components/pages/success-page.tsx` | ソース | 成功ページのロジック |
| success-view.tsx | `apps/signup-form/src/components/pages/success-view.tsx` | ソース | 成功ページのUI |
| api.tsx | `apps/signup-form/src/utils/api.tsx` | ソース | Ghost API クライアント |
| options.tsx | `apps/signup-form/src/utils/options.tsx` | ソース | オプション読み取りフック |
| validator.tsx | `apps/signup-form/src/utils/validator.tsx` | ソース | メールバリデーション |
| helpers.tsx | `apps/signup-form/src/utils/helpers.tsx` | ソース | ヘルパー関数（URL履歴取得等） |
| constants.tsx | `apps/signup-form/src/utils/constants.tsx` | ソース | 定数定義 |
| frame.tsx | `apps/signup-form/src/components/frame.tsx` | ソース | iframeレイアウトコンポーネント |
| content-box.tsx | `apps/signup-form/src/components/content-box.tsx` | ソース | コンテンツコンテナ |
