# 画面設計書 84-ニュースレター選択ページ

## 概要

本ドキュメントは、Ghost会員向けポータル（Portal）のニュースレター選択ページ（NewsletterSelectionPage）の画面設計書である。新規会員登録時に購読するニュースレターを選択するための画面を定義する。

### 本画面の処理概要

ニュースレター選択ページは、Ghost会員サイトのPortalウィジェット内で、サインアップフロー中に会員が購読するニュースレターを選択するための画面である。

**業務上の目的・背景**：複数のニュースレターを運営するサイトにおいて、会員登録時にユーザーが自身の興味に合ったニュースレターのみを選択できることは、メール疲れによる購読解除を防ぎ、開封率・クリック率を向上させる上で重要である。初期設定で全ニュースレターを購読させる代わりに、ユーザーに選択権を与えることで、よりエンゲージメントの高いメーリングリストを構築できる。

**画面へのアクセス方法**：サインアップページでメールアドレス入力後、複数のニュースレターが存在する場合にこの画面に遷移する。または、オファーページからのサインアップ時にも表示される場合がある。直接URL（`#/portal/signupNewsletter`）でのアクセスも可能。

**主要な操作・処理内容**：
1. 利用可能なニュースレター一覧の表示
2. 各ニュースレターの購読ON/OFF切り替え
3. 「Continue」ボタンで選択を確定しサインアップ完了
4. 「Choose a different plan」で前のプラン選択画面に戻る

**画面遷移**：
- 遷移元：サインアップページ（signup）、オファーページ（offer）
- 遷移先：マジックリンクページ（magiclink）、プラン選択画面（戻る時）

**権限による表示制御**：
- 未認証状態でアクセス可能（サインアップフロー中）
- 有料プランのみのニュースレターはロックアイコン付きで表示（選択不可）

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 20 | ニュースレター管理 | 主機能 | 購読ニュースレターの選択 |
| 71 | Portal | 補助機能 | Portalウィジェット内でのUI表示 |

## 画面種別

選択

## URL/ルーティング

- ハッシュルート：`#/portal/signupNewsletter`
- Pages.jsでの登録キー：`signupNewsletter`

## 入出力項目

| 項目名 | 項目ID | 型 | 必須 | 入力/出力 | 説明 |
|--------|--------|-----|------|----------|------|
| 選択ニュースレター | subscribedNewsletters | array | - | 入力 | 購読するニュースレターのリスト |
| 名前 | name | string | - | 入力（pageData経由） | サインアップ時の名前 |
| メール | email | string | ○ | 入力（pageData経由） | サインアップ時のメール |
| プラン | plan | string | - | 入力（pageData経由） | 選択したプランID |
| 電話番号 | phonenumber | string | - | 入力（pageData経由） | ハニーポットフィールド |
| オファーID | offerId | string | - | 入力（pageData経由） | 適用するオファーID |

## 表示項目

| 項目名 | データソース | 説明 |
|--------|-------------|------|
| ページタイトル | - | "Choose your newsletters" 固定 |
| ニュースレター一覧 | site.newsletters | 利用可能なニュースレター |
| ニュースレター名 | newsletter.name | 各ニュースレターの名前 |
| ニュースレター説明 | newsletter.description | 各ニュースレターの説明文 |
| ロックアイコン | newsletter.paid | 有料会員限定ニュースレター表示 |

## イベント仕様

### 1-ニュースレター選択切り替え

**トリガー**：ニュースレター行のSwitchコンポーネントトグル

**処理フロー**：
1. トグル状態に応じてsubscribedNewsletters配列を更新
2. setSubscribedNewsletters()でローカルstate更新

**データ更新**：
- state.subscribedNewsletters: 更新されたニュースレターリスト

### 2-サインアップ続行

**トリガー**：「Continue」ボタンクリック

**処理フロー**：
1. subscribedNewslettersをフォーマット（idとnameのみ抽出）
2. pageDataから name, email, plan, phonenumber, offerId を取得
3. `doAction('signup', {name, email, plan, phonenumber, newsletters, offerId})` 実行
4. 成功時：マジックリンクページへ遷移

**データ更新**：
- APIで新規会員作成
- マジックリンクメール送信

### 3-プラン選択に戻る

**トリガー**：「Choose a different plan」ボタンクリック

**処理フロー**：
1. onBack() コールバック実行
2. 前の画面（通常はサインアップページまたはオファーページ）に戻る

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| サインアップ続行 | members | INSERT | 新規会員レコード作成 |
| サインアップ続行 | members_newsletters | INSERT | 会員とニュースレターの関連作成 |

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

#### members

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | name | 入力された名前 | nullable |
| INSERT | email | 入力されたメール | 必須、一意 |
| INSERT | status | 'free' または 'paid' | プランに依存 |

#### members_newsletters

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | member_id | 作成された会員ID | - |
| INSERT | newsletter_id | 選択されたニュースレターID | 複数レコード |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|--------------|---------|
| MSG-001 | ラベル | "Choose your newsletters" | ページタイトル |
| MSG-002 | ボタン | "Continue" | 通常時 |
| MSG-003 | ボタン | "Retry" | サインアップ失敗後 |
| MSG-004 | リンク | "Choose a different plan" | 無料プランのみでない場合 |
| MSG-005 | ツールチップ | "Unlock access to all newsletters by becoming a paid subscriber." | 有料限定ニュースレターのロックアイコン |

## 例外処理

| 例外条件 | 処理内容 | 表示メッセージ |
|---------|---------|--------------|
| サインアップ失敗 | 「Retry」ボタン表示、再試行可能 | - |
| API通信エラー | ボタン無効化、エラー状態 | - |

## 備考

- デフォルトでsubscribe_on_signup=trueのニュースレターが選択状態となる
- 有料会員限定（newsletter.paid=true）のニュースレターは、無料会員サインアップ時にはロック状態で表示される
- 無料プランのみの場合（hasOnlyFreePlan）、「Choose a different plan」リンクは表示されない
- この画面はサインアップフローの一部であり、既存会員は通常accountEmailページを使用する

---

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

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

### 推奨読解順序

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

ニュースレター選択ページで扱う主要なデータ構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | app-context.js | `apps/portal/src/app-context.js` | AppContextの構造（site, brandColor, doAction, action） |
| 1-2 | helpers.js | `apps/portal/src/utils/helpers.js` | getSiteNewsletters、hasOnlyFreePlan関数 |

**読解のコツ**: site.newslettersにニュースレター一覧が、各ニュースレターにはsubscribe_on_signupフラグがある。

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

ページコンポーネントの構造と初期化処理を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | newsletter-selection-page.js | `apps/portal/src/components/pages/newsletter-selection-page.js` | NewsletterSelectionPage関数コンポーネント |
| 2-2 | pages.js | `apps/portal/src/pages.js` | ページルーティング定義 |

**主要処理フロー**:
1. **66行目**: 関数コンポーネント定義、pageDataとonBackをpropsで受け取る
2. **68-71行目**: デフォルト購読ニュースレター（subscribe_on_signup=true）をフィルタリング
3. **87行目**: subscribedNewslettersをuseStateで管理
4. **88-136行目**: UIレンダリング

#### Step 3: ニュースレター表示コンポーネントを理解する

個別ニュースレター行の表示ロジックを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | newsletter-selection-page.js | `apps/portal/src/components/pages/newsletter-selection-page.js` | NewsletterPrefSection、NewsletterPrefs |

**主要処理フロー**:
- **9-49行目**: NewsletterPrefSectionで個別ニュースレター行UI
  - 13-24行目: 有料限定ニュースレターはロックアイコン表示
  - 26-48行目: 通常ニュースレターはSwitch表示
- **51-64行目**: NewsletterPrefsで一覧生成

#### Step 4: サインアップ処理を理解する

サインアップアクションの実行フローを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | newsletter-selection-page.js | `apps/portal/src/components/pages/newsletter-selection-page.js` | Continueボタンのonclick |
| 4-2 | api.js | `apps/portal/src/utils/api.js` | member.sendMagicLink関数 |

**主要処理フロー**:
- **106-114行目**: Continueボタン押下時のsignupアクション実行
- **277-326行 (api.js)**: sendMagicLinkでマジックリンクメール送信

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

```
NewsletterSelectionPage (newsletter-selection-page.js)
    │
    ├─ useContext(AppContext)
    │      └─ brandColor, site, doAction, action 取得
    │
    ├─ getSiteNewsletters()
    │      └─ site.newsletters から一覧取得
    │
    ├─ useState(defaultNewsletters)
    │      └─ subscribe_on_signup=true のものをデフォルト選択
    │
    ├─ NewsletterPrefs
    │      └─ NewsletterPrefSection (各ニュースレター)
    │             ├─ 有料限定: LockIcon表示
    │             └─ 通常: Switch コンポーネント
    │                    └─ onToggle → setSubscribedNewsletters()
    │
    ├─ ActionButton (Continue)
    │      └─ onClick
    │             ├─ newsletters フォーマット
    │             └─ doAction('signup', {...})
    │
    └─ button (Choose a different plan)
           └─ onClick → onBack()
```

### データフロー図

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

site.newsletters ──────▶ getSiteNewsletters() ─────────▶ ニュースレター一覧
                              │
                              └─ filter(subscribe_on_signup)
                                     │
                                     └─▶ defaultNewsletters

pageData ─────────────▶ {name, email, plan, ...} ──────▶ サインアップデータ

ユーザー操作
  │
  ├─ Switchトグル ─────────────▶ setSubscribedNewsletters()
  │                                    └─▶ ローカルstate更新
  │
  └─ Continueボタン ──────────▶ doAction('signup', {...})
                                     │
                                     ├─ newsletters: 選択リスト
                                     ├─ name, email, plan...
                                     │
                                     └─▶ api.member.sendMagicLink()
                                            │
                                            ├─ members INSERT
                                            ├─ members_newsletters INSERT
                                            │
                                            └─▶ マジックリンクページへ遷移
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| newsletter-selection-page.js | `apps/portal/src/components/pages/newsletter-selection-page.js` | ソース | メインページコンポーネント |
| pages.js | `apps/portal/src/pages.js` | ソース | ページルーティング定義 |
| app-context.js | `apps/portal/src/app-context.js` | ソース | アプリケーションコンテキスト定義 |
| api.js | `apps/portal/src/utils/api.js` | ソース | API通信処理 |
| helpers.js | `apps/portal/src/utils/helpers.js` | ソース | ヘルパー関数群 |
| switch.js | `apps/portal/src/components/common/switch.js` | ソース | Switchトグルコンポーネント |
| action-button.js | `apps/portal/src/components/common/action-button.js` | ソース | アクションボタンコンポーネント |
| lock.svg | `apps/portal/src/images/icons/lock.svg` | アセット | ロックアイコン |
| i18n.js | `apps/portal/src/utils/i18n.js` | ソース | 国際化ユーティリティ |
