# 機能設計書 73-Signup Form

## 概要

本ドキュメントは、Ghost CMSのメール購読フォームを提供するフロントエンドウィジェット「Signup Form」の機能設計書である。Signup FormはReact + TypeScriptで実装された軽量なコンポーネントで、サイトの任意の場所にメール購読フォームを埋め込むことができる。

### 本機能の処理概要

Signup Formは、Ghostサイトや外部ページに埋め込み可能なメール購読フォームウィジェットである。訪問者がメールアドレスを入力してサブスクライブボタンをクリックすると、マジックリンクメールが送信され、無料メンバーとして登録される。シンプルなUIで、カスタマイズ可能な外観と最小限の設定で導入できる点が特徴である。

**業務上の目的・背景**：ニュースレター購読者の獲得はコンテンツサイトの成長に不可欠である。Signup Formは、記事内やサイドバー、ポップアップなど、任意の場所に埋め込み可能なフォームを提供し、訪問者をメンバーに変換する機会を最大化する。Portalとは異なり、シンプルで軽量な実装により、埋め込み場所の自由度が高い。

**機能の利用シーン**：
- 記事の末尾にニュースレター購読フォームを設置する際
- サイドバーやフッターに常設フォームを配置する際
- ランディングページでメール収集を行う際
- 外部サイト（WordPress等）にGhostニュースレター購読フォームを埋め込む際

**主要な処理内容**：
1. フォームの初期表示（カスタマイズ可能なスタイル）
2. メールアドレスのバリデーション
3. Integrity Tokenの取得（不正リクエスト防止）
4. マジックリンクの送信リクエスト
5. 成功/エラー状態の表示
6. ミニマルモードでのインライン成功表示

**関連システム・外部連携**：
- Ghost Members API（`/members/api/`）：メンバー登録処理
- マジックリンクメール送信システム

**権限による制御**：
- 本機能は認証不要で利用可能（パブリックフォーム）
- Integrity Tokenによる不正リクエスト防止

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 101 | サインアップフォーム | 主機能 | メール購読の登録 |
| 102 | サインアップ成功ページ | 主機能 | 登録完了メッセージの表示 |

## 機能種別

データ入力 / 外部API連携 / UI表示

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| email | string | Yes | 購読者のメールアドレス | メール形式チェック |
| data-title | string | No | フォームタイトル | 文字列 |
| data-description | string | No | フォーム説明文 | 文字列 |
| data-icon | string | No | アイコンURL | 有効なURL |
| data-site | string | Yes | GhostサイトURL | 有効なURL |
| data-labels | string | No | メンバーに付与するラベル（カンマ区切り） | 文字列 |
| data-background-color | string | No | 背景色 | 有効なカラーコード |
| data-text-color | string | No | テキスト色 | 有効なカラーコード |
| data-button-color | string | No | ボタン背景色 | 有効なカラーコード |
| data-button-text-color | string | No | ボタンテキスト色 | 有効なカラーコード |
| data-locale | string | No | 表示言語 | 有効なロケールコード |

### 入力データソース

- HTMLスクリプトタグ属性：`data-*`属性でフォーム設定を取得
- フォーム入力：メールアドレス

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| page | Page | 現在表示中のページ（FormPage/SuccessPage） |
| loading | boolean | ローディング状態 |
| success | boolean | 成功状態 |
| error | string | エラーメッセージ |

### 出力先

- DOM（iframeベースのフォームコンポーネント）
- Ghost Members API（マジックリンク送信リクエスト）

## 処理フロー

### 処理シーケンス

```
1. 初期化
   └─ スクリプトタグからオプションを解析
   └─ Ghost API初期化
   └─ FormPageコンポーネントをレンダリング

2. フォーム入力
   └─ メールアドレス入力をstateで管理
   └─ エラー状態のクリア

3. サブミット処理
   └─ メールアドレスバリデーション
   └─ バリデーションエラー時はエラー表示
   └─ Integrity Token取得
   └─ sendMagicLink API呼び出し

4. 結果表示
   └─ ミニマルモード：インラインで成功表示
   └─ 通常モード：SuccessPageに遷移
   └─ エラー時：エラーメッセージ表示
```

### フローチャート

```mermaid
flowchart TD
    A[Signup Form表示] --> B[メールアドレス入力]
    B --> C[Subscribeボタンクリック]
    C --> D{メールアドレス有効?}
    D -->|No| E[エラーメッセージ表示]
    E --> B
    D -->|Yes| F[Integrity Token取得]
    F --> G{Token取得成功?}
    G -->|No| H[エラー表示]
    G -->|Yes| I[sendMagicLink API呼び出し]
    I --> J{API成功?}
    J -->|No| H
    H --> B
    J -->|Yes| K{ミニマルモード?}
    K -->|Yes| L[インライン成功表示]
    K -->|No| M[SuccessPage表示]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-73-01 | メールバリデーション | 有効なメール形式のみ受け付け | フォームサブミット時 |
| BR-73-02 | ラベル付与 | data-labels指定時にメンバーにラベルを付与 | labels属性が設定されている場合 |
| BR-73-03 | ミニマルモード | タイトル/説明/アイコンが未設定時はミニマル表示 | isMinimal(options) === true |
| BR-73-04 | URL履歴追跡 | サインアップ元のURL履歴を記録 | 常に |
| BR-73-05 | 多言語対応 | data-locale指定の言語でUIを表示 | locale属性が設定されている場合 |

### 計算ロジック

- ミニマルモード判定：`!title && !description && !icon`

## データベース操作仕様

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| サインアップ | members | INSERT | マジックリンク認証後に新規メンバーレコード作成 |
| ラベル付与 | members_labels | INSERT | 指定ラベルをメンバーに関連付け |

### テーブル別操作詳細

#### members

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | email, status | 入力値, 'free' | マジックリンク認証後 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| Invalid email | バリデーションエラー | メール形式が不正 | 「Please enter a valid email address」表示 |
| API Error | 通信エラー | Integrity Token取得失敗 | 「Something went wrong, please try again.」表示 |
| API Error | 通信エラー | sendMagicLink失敗 | 「Something went wrong, please try again.」表示 |

### リトライ仕様

- 自動リトライなし
- ユーザーが再度サブミットすることで再試行可能

## トランザクション仕様

- Signup Formはクライアントサイドアプリケーションのため、直接的なDBトランザクション制御は行わない
- サーバーサイド（Ghost Core）のMembers APIがトランザクション管理を担当
- マジックリンク認証時にメンバーレコードが作成される

## パフォーマンス要件

- 軽量バンドル：React + 最小限の依存関係
- 即座のフィードバック：ローディング状態とエラー状態を即座に表示
- iframeベースのサンドボックス：ホストページとの分離による安全性

## セキュリティ考慮事項

- Integrity Token：不正リクエスト防止のためのトークン検証
- メール形式バリデーション：クライアント・サーバー両方でチェック
- URL履歴追跡：サインアップ元の追跡によるスパム対策

## 備考

- Signup Formは`apps/signup-form/`ディレクトリに配置されたReact + TypeScriptアプリケーション
- ビルド成果物はUMD形式で`umd/`ディレクトリに出力
- Storybookによるコンポーネントドキュメント・開発環境を提供
- Tailwind CSSでスタイリング
- 多言語対応は`@tryghost/i18n`パッケージの'signup-form'名前空間を使用
- React 18を使用（Comments UI等より新しいバージョン）

---

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

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

### 推奨読解順序

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

まず、Signup Formで使用される主要なデータ構造を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | app-context.ts | `apps/signup-form/src/app-context.ts` | AppContextType型の定義 |
| 1-2 | options.tsx | `apps/signup-form/src/utils/options.tsx` | Options型とuseOptionsフックの定義 |
| 1-3 | pages.tsx | `apps/signup-form/src/pages.tsx` | Page型とPageName型の定義 |

**読解のコツ**: app-context.tsにはAppContextTypeが定義されており、page、api、options、setPage、t（翻訳関数）、scriptTag等のプロパティを持つ。これがアプリ全体で共有されるコンテキストである。

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.tsx | `apps/signup-form/src/index.tsx` | アプリケーションのエントリーポイント |
| 2-2 | app.tsx | `apps/signup-form/src/app.tsx` | メインアプリケーションコンポーネント |

**主要処理フロー（app.tsx）**:
1. **15行目**: useOptionsでスクリプトタグからオプションを解析
2. **17-20行目**: pageステートの初期化（FormPage）
3. **22-24行目**: Ghost API初期化
4. **33行目**: i18n初期化
5. **34-41行目**: AppContextの構築
6. **43-56行目**: PageComponentの動的レンダリング

#### Step 3: API通信層を理解する

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | api.tsx | `apps/signup-form/src/utils/api.tsx` | Ghost Members API通信ラッパー |
| 3-2 | helpers.tsx | `apps/signup-form/src/utils/helpers.tsx` | ヘルパー関数（getUrlHistory等） |

**主要処理フロー（api.tsx）**:
- **15-30行目**: getIntegrityToken - Integrity Token取得
- **31-55行目**: sendMagicLink - マジックリンク送信

#### Step 4: ページコンポーネントを理解する

各画面のUI実装を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | form-page.tsx | `apps/signup-form/src/components/pages/form-page.tsx` | フォームページロジック |
| 4-2 | form-view.tsx | `apps/signup-form/src/components/pages/form-view.tsx` | フォームUI |
| 4-3 | success-page.tsx | `apps/signup-form/src/components/pages/success-page.tsx` | 成功ページロジック |
| 4-4 | success-view.tsx | `apps/signup-form/src/components/pages/success-view.tsx` | 成功ページUI |

**主要処理フロー（form-page.tsx）**:
- **7-12行目**: ステート初期化（error, loading, success）
- **14-40行目**: submit関数 - バリデーション・API呼び出し・ページ遷移
- **42-55行目**: FormViewのレンダリング（propsでスタイル設定渡し）

**読解のコツ（form-view.tsx）**:
- **5-40行目**: FormViewコンポーネント - ミニマルモードと通常モードの分岐
- **53-92行目**: Formコンポーネント - 入力フィールドとサブミットボタン
- ローディング/成功状態に応じたアニメーション遷移の実装

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

```
index.tsx (init)
    │
    └─ App (app.tsx)
           │
           ├─ useOptions(scriptTag)
           │      └─ options.tsx
           │             └─ data-*属性の解析
           │
           ├─ setupGhostApi({siteUrl})
           │      └─ api.tsx
           │
           ├─ i18nLib(locale, 'signup-form')
           │
           └─ render()
                  └─ AppContextProvider
                         └─ Frame
                                └─ ContentBox
                                       └─ PageComponent (動的)
                                              │
                                              ├─ FormPage
                                              │      ├─ submit()
                                              │      │      ├─ isValidEmail()
                                              │      │      ├─ api.getIntegrityToken()
                                              │      │      └─ api.sendMagicLink()
                                              │      └─ FormView
                                              │             └─ Form
                                              │
                                              └─ SuccessPage
                                                     └─ SuccessView
```

### データフロー図

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

data-*属性 ─────────────┐
  (site, title, etc.)   │
                        │
メールアドレス入力 ─────┼─▶ App (app.tsx)
                        │   ├─ useOptions()           ─▶ options解析
                        │   └─ context構築            ─▶ provider提供
                        │
                        └─▶ FormPage
                               ├─ submit()
                               │      │
                               │      ▼
                               │   validator.tsx
                               │   isValidEmail()
                               │      │
                               │      ▼
                               │   api.tsx
                               │   ├─ getIntegrityToken()
                               │   └─ sendMagicLink()
                               │      │
                               │      ▼
                               │   Ghost Members API ───▶ マジックリンク送信
                               │      │
                               │      ▼
                               └─ setPage() / setSuccess()
                                      │
                                      ▼
                               SuccessPage / インライン成功表示
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| 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` | ソース | ページルーティング定義 |
| api.tsx | `apps/signup-form/src/utils/api.tsx` | ソース | API通信ラッパー |
| options.tsx | `apps/signup-form/src/utils/options.tsx` | ソース | オプション解析 |
| helpers.tsx | `apps/signup-form/src/utils/helpers.tsx` | ソース | ヘルパー関数 |
| validator.tsx | `apps/signup-form/src/utils/validator.tsx` | ソース | バリデーション |
| constants.tsx | `apps/signup-form/src/utils/constants.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 |
| content-box.tsx | `apps/signup-form/src/components/content-box.tsx` | ソース | コンテンツコンテナ |
| frame.tsx | `apps/signup-form/src/components/frame.tsx` | ソース | iframeラッパー |
| iframe.tsx | `apps/signup-form/src/components/iframe.tsx` | ソース | iframe実装 |
| package.json | `apps/signup-form/package.json` | 設定 | パッケージ定義 |
| vite.config.ts | `apps/signup-form/vite.config.ts` | 設定 | ビルド設定 |
| tailwind.config.cjs | `apps/signup-form/tailwind.config.cjs` | 設定 | Tailwind CSS設定 |
| preview.stories.tsx | `apps/signup-form/src/preview.stories.tsx` | ソース | Storybookプレビュー |
