# 画面設計書 82-プロフィール編集ページ

## 概要

本ドキュメントは、Ghost会員向けポータル（Portal）のプロフィール編集ページ（AccountProfilePage）の画面設計書である。会員が自身のプロフィール情報（名前、メールアドレス）を編集するための画面を定義する。

### 本画面の処理概要

プロフィール編集ページは、Ghost会員サイトのPortalウィジェット内で、会員が自身のアカウント情報を更新するための画面である。

**業務上の目的・背景**：会員制サイトにおいて、ユーザーが自身のプロフィール情報を自由に更新できることは、ユーザー体験の基本要件である。名前やメールアドレスの変更を管理者に依頼する必要がなく、ユーザー自身で完結できるセルフサービス機能を提供することで、運営負荷の軽減とユーザー満足度の向上を実現する。また、正確なメールアドレスの維持はニュースレター配信やアカウント回復において不可欠である。

**画面へのアクセス方法**：Portalウィジェットを開き、アカウントホームページから「プロフィール設定」をクリック、またはURL直接アクセス（`#/portal/accountProfile`）によりアクセスする。サインイン済みの会員のみがアクセス可能であり、未サインイン時はサインインページにリダイレクトされる。

**主要な操作・処理内容**：
1. 現在のプロフィール情報（名前、メールアドレス）の表示
2. 名前の編集と保存
3. メールアドレスの編集と保存
4. 入力バリデーション（必須チェック、メール形式チェック）
5. 保存ボタンによる更新処理の実行

**画面遷移**：
- 遷移元：アカウントホームページ（accountHome）
- 遷移先：アカウントホームページ（保存成功後、戻るボタン押下時）、サインインページ（未認証時）

**権限による表示制御**：
- サインイン済み会員のみアクセス可能
- 未認証状態ではサインインページへリダイレクト
- 会員種別（無料/有料）による表示の違いはなし

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 10 | メンバープロフィール | 主機能 | 会員プロフィール情報の編集 |
| 71 | Portal | 補助機能 | Portalウィジェット内でのUI表示 |

## 画面種別

編集

## URL/ルーティング

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

## 入出力項目

| 項目名 | 項目ID | 型 | 必須 | 入力/出力 | 説明 |
|--------|--------|-----|------|----------|------|
| 名前 | name | string | × | 入力 | 会員の表示名 |
| メールアドレス | email | string | ○ | 入力 | 会員のメールアドレス |

## 表示項目

| 項目名 | データソース | 説明 |
|--------|-------------|------|
| ページタイトル | - | "Account settings" 固定 |
| アバター画像 | member.avatar_image | 会員のGravatar画像 |
| 名前フィールド | member.name | 現在の名前を初期表示 |
| メールフィールド | member.email | 現在のメールを初期表示 |

## イベント仕様

### 1-プロフィール保存

**トリガー**：「Save」ボタンクリック、またはEnterキー押下

**処理フロー**：
1. 入力バリデーション実行（ValidateInputForm）
2. エラーがある場合：エラーメッセージ表示、処理中断
3. エラーがない場合：
   - ポップアップ通知クリア（clearPopupNotification）
   - updateProfileアクション実行
4. 成功時：プロフィール更新完了

**バリデーションルール**：
- 名前：任意（required: false）
- メールアドレス：必須、メール形式チェック

**データ更新**：
- state.name: 入力された名前
- state.email: 入力されたメールアドレス
- state.errors: バリデーションエラー

### 2-入力変更

**トリガー**：入力フィールドの値変更

**処理フロー**：
1. handleInputChange関数で対応するstate項目を更新
2. 画面再描画

### 3-戻るボタン

**トリガー**：BackButtonクリック

**処理フロー**：
1. `doAction('back')` 実行
2. 前のページ（通常はaccountHome）に戻る

### 4-Enterキー送信

**トリガー**：入力フィールド内でEnterキー（keyCode: 13）押下

**処理フロー**：
1. onKeyDownイベントハンドラでkeyCode=13を検知
2. onProfileSave関数を呼び出し
3. プロフィール保存処理を実行

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| プロフィール保存 | members | UPDATE | 会員の名前・メールアドレス更新 |

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

#### members

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | name | 入力された名前 | 空文字も許可 |
| UPDATE | email | 入力されたメールアドレス | 必須、一意制約あり |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|--------------|---------|
| MSG-001 | ラベル | "Account settings" | ページタイトル |
| MSG-002 | ラベル | "Name" | 名前フィールドラベル |
| MSG-003 | ラベル | "Email" | メールフィールドラベル |
| MSG-004 | プレースホルダー | "Jamie Larson" | 名前フィールド |
| MSG-005 | プレースホルダー | "jamie@example.com" | メールフィールド |
| MSG-006 | ボタン | "Save" | 保存ボタン（通常時） |
| MSG-007 | ボタン | "Retry" | 保存ボタン（失敗後） |
| MSG-008 | エラー | バリデーションエラーメッセージ | 入力エラー時 |

## 例外処理

| 例外条件 | 処理内容 | 表示メッセージ |
|---------|---------|--------------|
| 未認証状態 | サインインページへリダイレクト | - |
| 必須項目未入力 | エラーメッセージ表示 | フィールド固有のエラー |
| メール形式不正 | エラーメッセージ表示 | メール形式エラー |
| API通信エラー | 「Retry」ボタン表示 | - |
| メールアドレス重複 | エラーメッセージ表示 | サーバーからのエラーメッセージ |

## 備考

- アバター画像はGravatarから取得され、この画面では変更不可
- メールアドレス変更時は確認メールが送信される場合がある（サイト設定依存）
- 名前は空文字での保存も可能

---

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

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

### 推奨読解順序

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

プロフィール編集ページで扱う主要なデータ構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | app-context.js | `apps/portal/src/app-context.js` | AppContextの構造（member オブジェクト） |
| 1-2 | form.js | `apps/portal/src/utils/form.js` | ValidateInputForm関数のバリデーションロジック |

**読解のコツ**: memberオブジェクトにはname、email、avatar_image等のプロパティが含まれる。

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

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

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

**主要処理フロー**:
1. **14-21行**: コンストラクタでmemberの name, email を初期stateに設定
2. **23-30行**: componentDidMountで未認証チェック、必要に応じてサインインページへリダイレクト
3. **180-197行**: render()でフォームとボタンを描画

#### Step 3: 入力フォームを理解する

入力フィールドの定義とバリデーションを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | account-profile-page.js | `apps/portal/src/components/pages/account-profile-page.js` | getInputFields関数 |

**主要処理フロー**:
- **131-159行**: getInputFieldsでフィールド定義を返す
  - name: type='text', required=false
  - email: type='email', required=true
- **168-177行**: renderProfileDataでInputFormコンポーネントを使用

#### Step 4: 保存処理を理解する

プロフィール保存のフローとAPI連携を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | account-profile-page.js | `apps/portal/src/components/pages/account-profile-page.js` | onProfileSave関数 |
| 4-2 | api.js | `apps/portal/src/utils/api.js` | member.update関数 |

**主要処理フロー**:
- **41-55行**: onProfileSaveでバリデーション後にupdateProfileアクション実行
- **209-234行 (api.js)**: member.update APIでPUTリクエスト送信

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

```
AccountProfilePage (account-profile-page.js)
    │
    ├─ constructor()
    │      └─ member.name, member.email を初期state設定
    │
    ├─ componentDidMount()
    │      └─ 未認証チェック → doAction('switchPage', {page: 'signin'})
    │
    ├─ renderHeader()
    │      └─ BackButton, タイトル表示
    │
    ├─ renderProfileData()
    │      └─ InputForm
    │             └─ getInputFields() でフィールド定義
    │
    ├─ renderSaveButton()
    │      └─ ActionButton
    │             └─ onClick → onProfileSave()
    │
    └─ onProfileSave()
           ├─ ValidateInputForm()
           ├─ doAction('clearPopupNotification')
           └─ doAction('updateProfile', {email, name})
                  └─ api.member.update()
```

### データフロー図

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

member.name ───────────▶ state.name ────────────────────▶ 入力フィールド
member.email ──────────▶ state.email ───────────────────▶ 入力フィールド
member.avatar_image ───▶ MemberAvatar ─────────────────▶ アバター表示

ユーザー入力
  │
  ├─ 名前変更 ──────────▶ handleInputChange() ──────────▶ state.name更新
  │
  ├─ メール変更 ────────▶ handleInputChange() ──────────▶ state.email更新
  │
  └─ 保存ボタン ────────▶ onProfileSave()
                              │
                              ├─ ValidateInputForm() ──▶ エラー表示 or
                              │
                              └─ doAction('updateProfile')
                                      │
                                      └─ api.member.update() ──▶ members更新
                                                                  └─▶ 成功通知
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| account-profile-page.js | `apps/portal/src/components/pages/account-profile-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通信処理 |
| form.js | `apps/portal/src/utils/form.js` | ソース | フォームバリデーション |
| input-form.js | `apps/portal/src/components/common/input-form.js` | ソース | 入力フォームコンポーネント |
| member-gravatar.js | `apps/portal/src/components/common/member-gravatar.js` | ソース | アバター表示コンポーネント |
| action-button.js | `apps/portal/src/components/common/action-button.js` | ソース | アクションボタンコンポーネント |
| back-button.js | `apps/portal/src/components/common/back-button.js` | ソース | 戻るボタンコンポーネント |
| i18n.js | `apps/portal/src/utils/i18n.js` | ソース | 国際化ユーティリティ |
