# 画面設計書 85-購読解除ページ

## 概要

本ドキュメントは、Ghost会員向けポータル（Portal）の購読解除ページ（UnsubscribePage）の画面設計書である。メール内の購読解除リンクからアクセスし、ニュースレターの購読を解除するための画面を定義する。

### 本画面の処理概要

購読解除ページは、Ghost会員サイトのPortalウィジェット内で、メール内の購読解除リンクからアクセスされ、ニュースレター購読を管理するための画面である。ログイン不要で使用可能。

**業務上の目的・背景**：メール配信において、ワンクリック購読解除は法的要件（CAN-SPAM、GDPRなど）への準拠に不可欠である。また、簡単に購読解除できることで、ユーザーがスパム報告する代わりに正式に購読解除でき、送信者レピュテーションの保護につながる。この画面は、特定のニュースレターのみの購読解除や、すべてのメール購読解除を可能にし、ユーザーに柔軟な選択肢を提供する。

**画面へのアクセス方法**：ニュースレターメール内の購読解除リンク（`#/portal/unsubscribe?uuid={uuid}&key={key}&newsletter={newsletterUuid}`）をクリックすることでアクセスする。ログイン不要だが、uuid/keyによる認証が必要。

**主要な操作・処理内容**：
1. 購読解除リンクからのアクセス時に自動購読解除処理
2. 購読解除完了メッセージの表示
3. 残りのニュースレター購読設定の管理
4. コメント通知設定の管理（コメント機能有効時）
5. 全メール購読解除機能

**画面遷移**：
- 遷移元：ニュースレターメール内の購読解除リンク
- 遷移先：閉じる（ポップアップ終了）

**権限による表示制御**：
- ログイン不要（uuid/keyによる認証）
- ログイン中の場合はコンテキストのmemberデータと連携
- 無効なuuid/keyの場合はエラー画面表示

## 関連機能

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

## 画面種別

設定・完了

## URL/ルーティング

- ハッシュルート：`#/portal/unsubscribe`
- Pages.jsでの登録キー：`unsubscribe`
- クエリパラメータ：
  - `uuid`: 会員のUUID（必須）
  - `key`: 認証キー（必須）
  - `newsletter`: 購読解除対象ニュースレターのUUID
  - `comments`: コメント通知購読解除フラグ

## 入出力項目

| 項目名 | 項目ID | 型 | 必須 | 入力/出力 | 説明 |
|--------|--------|-----|------|----------|------|
| 会員UUID | uuid | string | ○ | 入力（URL） | 会員識別子 |
| 認証キー | key | string | ○ | 入力（URL） | 認証用キー |
| ニュースレターUUID | newsletterUuid | string | - | 入力（URL） | 購読解除対象 |
| コメントフラグ | comments | boolean | - | 入力（URL） | コメント通知解除フラグ |
| ニュースレター購読状態 | subscribedNewsletters | array | - | 入力 | 購読中ニュースレターリスト |
| コメント通知設定 | enableCommentNotifications | boolean | - | 入力 | コメント通知有効/無効 |

## 表示項目

| 項目名 | データソース | 説明 |
|--------|-------------|------|
| サイトロゴ | site.icon | サイトアイコン画像 |
| サイトタイトル | site.title | サイト名 |
| 購読解除完了メッセージ | - | "Successfully unsubscribed" |
| ニュースレター一覧 | site.newsletters | 利用可能なニュースレター |
| エラーメッセージ | - | 無効なuuid時のエラー表示 |

## イベント仕様

### 1-自動購読解除（ページ読み込み時）

**トリガー**：ページ表示時（useEffect）

**処理フロー**：
1. api.member.newsletters() でuuid/keyを使用して会員データ取得
2. 取得成功時：subscribedNewslettersを設定
3. 条件に応じた自動購読解除：
   - ニュースレターが1つのみ＆コメント無効：全購読解除
   - newsletterUuid指定あり：該当ニュースレターのみ解除
   - comments=true＆コメント有効：コメント通知解除

**API呼び出し**：
- GET `/members/api/member/newsletters/?uuid={uuid}&key={key}`
- PUT `/members/api/member/newsletters/?uuid={uuid}&key={key}` （購読解除時）

### 2-ニュースレター購読切り替え

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

**処理フロー**：
1. updateNewsletters()関数実行
2. ログイン中：`doAction('updateNewsletterPreference', {newsletters})`
3. 非ログイン：api.member.updateNewsletters() 直接呼び出し
4. 成功通知「Email preferences updated.」表示

### 3-コメント通知切り替え

**トリガー**：CommentsセクションのSwitchコンポーネントトグル

**処理フロー**：
1. updateCommentNotifications()関数実行
2. ログイン中：`doAction('updateNewsletterPreference', {enableCommentNotifications})`
3. 非ログイン：api.member.updateNewsletters() 直接呼び出し
4. 成功通知「Comment preferences updated.」表示

### 4-全メール購読解除

**トリガー**：「Unsubscribe from all emails」ボタンクリック

**処理フロー**：
1. unsubscribeAll()関数実行
2. ログイン中：`doAction('updateNewsletterPreference', {newsletters: [], enableCommentNotifications: false})`
3. 非ログイン：api.member.updateNewsletters() で全解除
4. 成功通知「Unsubscribed from all emails.」表示

### 5-設定画面への遷移（単一ニュースレター解除後）

**トリガー**：「Manage your preferences here」リンククリック

**処理フロー**：
1. setShowPrefs(true) でニュースレター管理画面表示
2. NewsletterManagementコンポーネントを表示

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| ニュースレター購読解除 | members_newsletters | DELETE | 会員とニュースレターの関連削除 |
| コメント通知切り替え | members | UPDATE | enable_comment_notificationsフラグ更新 |
| 全メール購読解除 | members_newsletters, members | DELETE, UPDATE | 全購読解除とフラグ更新 |

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

#### members_newsletters

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| DELETE | - | member_id、newsletter_idで特定 | 購読解除時 |

#### members

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | enable_comment_notifications | false | コメント通知解除時 |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|--------------|---------|
| MSG-001 | タイトル | "Successfully unsubscribed" | 単一ニュースレター解除完了時 |
| MSG-002 | 情報 | "{memberEmail} will no longer receive this newsletter." | 単一ニュースレター解除時 |
| MSG-003 | 情報 | "Didn't mean to do this? Manage your preferences here." | 単一ニュースレター解除後 |
| MSG-004 | 情報 | "{memberEmail} will no longer receive {newsletterName} newsletter." | 特定ニュースレター解除時 |
| MSG-005 | 情報 | "{memberEmail} will no longer receive emails when someone replies to your comments." | コメント通知解除時 |
| MSG-006 | 成功 | "Email preferences updated." | ニュースレター更新成功時 |
| MSG-007 | 成功 | "Comment preferences updated." | コメント通知更新成功時 |
| MSG-008 | 成功 | "Unsubscribed from all emails." | 全購読解除成功時 |
| MSG-009 | エラータイトル | "That didn't go to plan" | 無効なuuid/key時 |
| MSG-010 | エラー | "We couldn't unsubscribe you as the email address was not found. Please contact the site owner." | 無効なuuid/key時 |
| MSG-011 | ボタン | "Close" | エラー画面閉じるボタン |

## 例外処理

| 例外条件 | 処理内容 | 表示メッセージ |
|---------|---------|--------------|
| 無効なuuid/key | エラー画面表示、Closeボタン | "That didn't go to plan" / エラーメッセージ |
| API通信エラー | コンソールログ、null状態設定 | - |
| 会員データなし | エラー画面表示 | "We couldn't unsubscribe you..." |

## 備考

- ログイン不要で使用可能（uuid/keyによる認証）
- ログイン中の場合は、コンテキストのloggedInMemberデータと連携
- 単一ニュースレターのみの場合、シンプルな完了画面を表示
- 複数ニュースレターがある場合、NewsletterManagementコンポーネントを使用
- ローディング中はLoadingPageを表示

---

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

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

### 推奨読解順序

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

購読解除ページで扱う主要なデータ構造を把握する。

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

**読解のコツ**: pageDataにuuid、key、newsletterUuid、commentsが含まれる。loggedInMemberとmemberの違いに注意。

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

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

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

**主要処理フロー**:
1. **45-58行**: 関数コンポーネント定義、各種state初期化
2. **111-145行**: useEffectで会員データ取得と自動購読解除処理
3. **147-178行**: 無効なmember時のエラー画面表示
4. **181-213行**: 単一ニュースレター解除完了画面
5. **255-273行**: NewsletterManagement表示

#### Step 3: 更新処理を理解する

ニュースレター更新とコメント通知更新の処理を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | unsubscribe-page.js | `apps/portal/src/components/pages/unsubscribe-page.js` | updateNewsletters、updateCommentNotifications、unsubscribeAll |

**主要処理フロー**:
- **62-74行**: updateNewslettersでログイン状態に応じた処理分岐
- **76-90行**: updateCommentNotificationsでコメント通知更新
- **92-108行**: unsubscribeAllで全購読解除

#### Step 4: API連携を理解する

バックエンドAPIとの連携処理を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | api.js | `apps/portal/src/utils/api.js` | member.newsletters、member.updateNewsletters関数 |
| 4-2 | unsubscribe-page.js | `apps/portal/src/components/pages/unsubscribe-page.js` | updateMemberNewsletters関数 |

**主要処理フロー**:
- **378-390行 (api.js)**: member.newslettersでGET取得
- **392-417行 (api.js)**: member.updateNewslettersでPUT更新
- **35-41行**: updateMemberNewsletters（非ログイン時のラッパー）

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

```
UnsubscribePage (unsubscribe-page.js)
    │
    ├─ useContext(AppContext)
    │      └─ site, api, pageData, member: loggedInMember, doAction 取得
    │
    ├─ useState
    │      ├─ member (APIから取得した会員データ)
    │      ├─ loading
    │      ├─ subscribedNewsletters
    │      ├─ showPrefs
    │      └─ hasInteracted
    │
    ├─ useEffect (初期化・自動購読解除)
    │      ├─ api.member.newsletters() でデータ取得
    │      │
    │      └─ 条件分岐
    │             ├─ 単一ニュースレター → updateNewsletters([])
    │             ├─ newsletterUuid指定 → 該当のみ解除
    │             └─ comments指定 → コメント通知解除
    │
    ├─ 表示分岐
    │      ├─ loading → LoadingPage
    │      ├─ !member → エラー画面
    │      ├─ 単一ニュースレター → 完了画面
    │      └─ 複数 → NewsletterManagement
    │
    └─ NewsletterManagement
           ├─ updateSubscribedNewsletters → updateNewsletters()
           ├─ updateCommentNotifications → updateCommentNotifications()
           └─ unsubscribeAll → unsubscribeAll()
```

### データフロー図

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

URLパラメータ
  ├─ uuid ─────────────▶ api.member.newsletters() ──────▶ 会員データ取得
  ├─ key                       │
  ├─ newsletterUuid            └─▶ memberNews設定
  └─ comments
                              │
                              └─ 自動購読解除処理
                                     │
                                     ├─ ニュースレター解除
                                     │      └─ api.member.updateNewsletters()
                                     │
                                     └─ コメント通知解除
                                            └─ updateCommentNotifications()

ユーザー操作
  │
  ├─ Switchトグル ─────────────▶ updateNewsletters() or
  │                             updateCommentNotifications()
  │                                    │
  │                                    ├─ loggedInMember
  │                                    │      └─ doAction('updateNewsletterPreference')
  │                                    │
  │                                    └─ !loggedInMember
  │                                           └─ api.member.updateNewsletters()
  │
  └─ 全購読解除 ──────────────▶ unsubscribeAll()
                                     └─▶ newsletters: [], enableCommentNotifications: false
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| unsubscribe-page.js | `apps/portal/src/components/pages/unsubscribe-page.js` | ソース | メインページコンポーネント |
| newsletter-management.js | `apps/portal/src/components/common/newsletter-management.js` | ソース | ニュースレター管理共通コンポーネント |
| loading-page.js | `apps/portal/src/components/pages/loading-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` | ソース | ヘルパー関数群 |
| close-button.js | `apps/portal/src/components/common/close-button.js` | ソース | 閉じるボタンコンポーネント |
| action-button.js | `apps/portal/src/components/common/action-button.js` | ソース | アクションボタンコンポーネント |
| warning-fill.svg | `apps/portal/src/images/icons/warning-fill.svg` | アセット | 警告アイコン |
| i18n.js | `apps/portal/src/utils/i18n.js` | ソース | 国際化ユーティリティ |
