# 画面設計書 78-サインインページ

## 概要

本ドキュメントは、Portal（公開会員ポータル）におけるサインインページの設計書です。

### 本画面の処理概要

この画面は、Ghostサイトの会員（メンバー）がメールアドレスを使ってサインインするためのページです。マジックリンク認証を使用し、パスワード不要でメール経由でログインできます。

**業務上の目的・背景**：Ghost Portalは、サイト訪問者・購読者向けの会員ポータル機能です。本画面は、既存会員がサイトにログインするためのインターフェースを提供します。マジックリンク方式を採用することで、パスワード管理の負担なく安全な認証を実現しています。購読コンテンツの閲覧や、アカウント設定の変更にはサインインが必要です。

**画面へのアクセス方法**：Portalウィジェットを開き、サインインページを選択するか、サインアップページから「Sign in」リンクをクリックしてアクセスします。すでにログイン済みの場合はアカウントホームページへ自動遷移します。

**主要な操作・処理内容**：
1. メールアドレスの入力
2. サインインリクエストの送信（マジックリンク送信）
3. サインアップページへの遷移

**画面遷移**：
- 遷移元：Portalウィジェット、サインアップページ
- 遷移先：マジックリンクページ、アカウントホームページ（ログイン済み時）
- 関連遷移：サインアップページ

**権限による表示制御**：メンバーシップ機能が無効の場合、サインインフォームは表示されず、代わりにメッセージが表示されます。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 9 | メンバー認証 | 主機能 | 会員のメールアドレスによるサインイン |
| 71 | Portal | 補助機能 | Portalウィジェット内でのUI表示 |

## 画面種別

入力フォーム（認証）

## URL/ルーティング

- ページ識別子: `signin`
- Portal内パス: `#/portal/signin`
- ルート定義: `apps/portal/src/pages.js`

## 入出力項目

| 項目名 | 入出力 | データ型 | 必須 | 説明 |
|--------|--------|----------|------|------|
| email | 入力 | string | Yes | メールアドレス |
| phonenumber | 入力 | string | No | 電話番号（スパム対策用隠しフィールド） |
| token | 入力 | string | No | 認証トークン（特殊ケース用） |
| redirect | 入力 | string | No | サインイン後のリダイレクト先URL |

## 表示項目

### ヘッダー

| 項目名 | データ型 | 説明 |
|--------|----------|------|
| サイトアイコン | image | サイトのアイコン画像 |
| タイトル | string | "Sign in" またはサイト名（無効時） |

### フォーム

| 項目名 | データ型 | 説明 |
|--------|----------|------|
| Emailフィールド | input | メールアドレス入力フィールド |
| Continueボタン | button | サインインリクエスト送信ボタン |
| サインアップリンク | link | "Don't have an account?" + "Sign up" |

### 無効時表示

| 項目名 | データ型 | 説明 |
|--------|----------|------|
| 招待アイコン | svg | InvitationIcon |
| メッセージ | string | "Memberships unavailable, contact the owner for access." |

## イベント仕様

### 1-メール入力

- トリガー: Emailフィールドへの入力
- 処理:
  1. `handleInputChange()` でstateを更新
  2. `email` stateに値を設定

### 2-サインイン送信

- トリガー: 「Continue」ボタンクリック、またはEnterキー押下
- 処理:
  1. `ValidateInputForm()` でバリデーション実行
  2. エラーがある場合はstate.errorsに設定して表示
  3. エラーがない場合、`doAction('signin', {email, phonenumber, redirect, token})` を実行
  4. アクション実行中は「Sending login link...」を表示
  5. 成功時はマジックリンクページへ遷移
  6. 失敗時は「Retry」ボタンを表示

### 3-サインアップへ遷移

- トリガー: 「Sign up」リンククリック
- 処理:
  1. `doAction('switchPage', {page: 'signup'})` を実行
  2. サインアップページへ遷移

### 4-自動リダイレクト（ログイン済み）

- トリガー: `componentDidMount()` 時にmemberが存在
- 処理:
  1. `doAction('switchPage', {page: 'accountHome'})` を実行
  2. アカウントホームページへ遷移

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| サインイン送信 | - | - | マジックリンクメール送信（DBへの直接書き込みなし） |

※ マジックリンクトークンの生成・管理はGhost Core APIで処理

## メッセージ仕様

| 種別 | メッセージ | 表示タイミング |
|------|-----------|---------------|
| ラベル | Email | メールフィールドラベル |
| プレースホルダー | jamie@example.com | メールフィールドプレースホルダー |
| ボタン | Continue | 通常時のサインインボタン |
| ボタン | Sending login link... | サインイン処理中 |
| ボタン | Retry | サインイン失敗時 |
| 情報 | Don't have an account? | サインアップへの案内 |
| リンク | Sign up | サインアップページへのリンク |
| 情報 | Memberships unavailable, contact the owner for access. | メンバーシップ無効時 |

### バリデーションエラー

| フィールド | エラー条件 | メッセージ |
|-----------|-----------|-----------|
| email | 空または不正な形式 | （バリデーションライブラリによるメッセージ） |

## 例外処理

| 例外状況 | 対応内容 |
|---------|---------|
| ログイン済み | アカウントホームページへ自動遷移 |
| メンバーシップ無効 | サインインフォームを非表示、メッセージを表示 |
| サインイン失敗 | 「Retry」ボタンを表示 |
| サインアップ不可 | サインアップリンクを非表示 |

## 備考

- マジックリンク方式：パスワードなしでメールリンクによる認証
- phonenumberフィールドはスパム対策用のハニーポット（非表示）
- i18n対応：`t()` 関数で国際化
- サイトのbrandColorを使用したスタイリング
- CloseButtonコンポーネントでポータルを閉じる機能

---

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

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

### 推奨読解順序

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

まず、プログラム間で受け渡されるデータ構造を理解することが重要です。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | app-context.js | `apps/portal/src/app-context.js` | AppContextの構造（site, member, action等） |

**読解のコツ**: Portalは React Context を使用して状態を共有しています。`doAction` がアクション実行の中心です。

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | signin-page.js | `apps/portal/src/components/pages/signin-page.js` | サインインページの実装 |
| 2-2 | pages.js | `apps/portal/src/pages.js` | ページマッピング |

**主要処理フロー**:
1. **L12-21**: constructor - 初期state設定
2. **L23-29**: componentDidMount - ログイン済みチェックとリダイレクト
3. **L37-49**: doSignin - サインイン処理実行
4. **L66-93**: getInputFields - フォームフィールド定義
5. **L95-117**: renderSubmitButton - ボタンのレンダリング
6. **L119-134**: renderSignupMessage - サインアップリンク
7. **L136-169**: renderForm - フォーム全体のレンダリング

#### Step 3: ヘルパー・ユーティリティを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | form.js | `apps/portal/src/utils/form.js` | ValidateInputFormの実装 |
| 3-2 | helpers.js | `apps/portal/src/utils/helpers.js` | isSigninAllowed, isSignupAllowed等 |
| 3-3 | i18n.js | `apps/portal/src/utils/i18n.js` | 国際化関数 |

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

```
SigninPage (React Component)
    |
    +-- componentDidMount()
    |       +-- (if member) doAction('switchPage', {page: 'accountHome'})
    |
    +-- handleSignin(e)
    |       +-- doSignin()
    |               +-- ValidateInputForm()
    |               +-- (if valid) doAction('signin', {email, phonenumber, redirect, token})
    |
    +-- renderForm()
    |       +-- isSigninAllowed({site})
    |       +-- isSignupAllowed({site})
    |       +-- hasAvailablePrices({site})
    |       +-- InputForm (component)
    |       +-- renderSubmitButton()
    |       +-- renderSignupMessage()
    |
    +-- renderFormHeader()
            +-- renderSiteIcon()
            +-- renderSiteTitle()
```

### データフロー図

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

ページ読み込み
    |
    +-- componentDidMount() --> member存在? --> accountHome遷移
    |
    +-- renderForm() --> isSigninAllowed? --> (false) --> メッセージ表示
                              |
                              +-- (true) --> フォーム表示

メールアドレス入力
    |
    +-- handleInputChange() --> state.email更新

Continueクリック
    |
    +-- handleSignin() --> ValidateInputForm()
                              |
                              +-- (errors) --> state.errors更新 --> エラー表示
                              |
                              +-- (valid) --> doAction('signin') --> action='signin:running'
                                                    |
                                                    +-- (success) --> magiclink page
                                                    |
                                                    +-- (failure) --> action='signin:failed'
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| signin-page.js | `apps/portal/src/components/pages/signin-page.js` | ソース | サインインページコンポーネント |
| pages.js | `apps/portal/src/pages.js` | ソース | ページマッピング定義 |
| app-context.js | `apps/portal/src/app-context.js` | ソース | Reactコンテキスト定義 |
| input-form.js | `apps/portal/src/components/common/input-form.js` | ソース | 入力フォームコンポーネント |
| action-button.js | `apps/portal/src/components/common/action-button.js` | ソース | アクションボタンコンポーネント |
| close-button.js | `apps/portal/src/components/common/close-button.js` | ソース | 閉じるボタンコンポーネント |
| form.js | `apps/portal/src/utils/form.js` | ソース | フォームバリデーション |
| helpers.js | `apps/portal/src/utils/helpers.js` | ソース | ヘルパー関数 |
| i18n.js | `apps/portal/src/utils/i18n.js` | ソース | 国際化 |
