Amplify UI を使ったログイン画面（Amplify UIその3） カスタム

## 概要説明

その２のつづきで、Amplify UIのサインイン・サインアップで、ヘッダー、フッター等のカスタマイズ、日本語化をしてみます
その１：[Amplify UIの説明](https://qiita.com/ssugimoto/items/73e119d73296819afe01 )
その２：[Amplify UI Authenticatorの日本語化](https://qiita.com/ssugimoto/items/d0bc7540493499b6b154)
その３：この記事

### 最終ゴールは、以下のようなログイン・アカウント作成画面

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1482752/43329057-7720-c879-3b8d-725a14a019a2.png" width="400" /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1482752/39388eb9-cb79-403f-cb6b-fcaa38646260.png" width="300" />

その２で作成した `UIVocabularies.tsx` は少し修正

---

## 手順
- 以下のサンプルを修正しながらやっていくと楽にいける
- https://aws.amazon.com/jp/blogs/mobile/amplify-uis-new-authenticator-component-makes-it-easy-to-add-customizable-login-pages-to-your-react-angular-or-vue-app/ 
    - 動かしながら修正できる https://codesandbox.io/s/match-your-brand-vdwk0?file=/src/App.js
- https://ui.docs.amplify.aws/components/authenticator#customization も似たサンプルあり

---

### ソース
- App.tsx
    - Higher-Order Component（HOC） の withAuthenticator を使う
    - withAuthenticatorの引数 `components` に カスタマイズ用のヘッダーやフッターの関数を渡す
    - UIVocabularies.tsxで、Amplify UI の言語をjaにする

```src/App.tsx
import { SignInHeader } from 'Auth/SignInHeader';
import { LoginFooter } from 'Auth/LoginFooter';
import { LoginHeader } from 'Auth/LoginHeader';
import { SetUIVocabularies } from 'Auth/UIVocabularies';
import { SignInFooter } from 'Auth/SignInFooter';
import { SignUpHeader } from 'Auth/SignUpHeader';
import { withAuthenticator } from '@aws-amplify/ui-react';
import { FC } from 'react';

//UIデフォルト日本語
SetUIVocabularies(('ja'));

export function Appsub({ signOut, user }: any ) {
  
  return (
    <>
      <h1>Hello {user.username}</h1>
      <button onClick={signOut}>Sign out</button>
    </>
  );
}

export const App: FC = withAuthenticator(Appsub, {
  components: {
    Header: LoginHeader,
    SignIn: {
      Header: SignInHeader,
      Footer: SignInFooter
    },
    SignUp:{
      Header: SignUpHeader
    },
    Footer: LoginFooter
  }
});
```



```src/Auth/LoginFooter.tsx
import { Flex, Text, useTheme } from "@aws-amplify/ui-react";

export function LoginFooter() {
  const { tokens } = useTheme();

  return (
    <Flex justifyContent="center" padding={tokens.space.medium}>
      <Text>&copy; All Rights Reserved</Text>
    </Flex>
  );
}
```


```src/Auth/LoginHeader.tsx
import { Heading, useTheme } from '@aws-amplify/ui-react';
import { ReactComponent as LogoShip } from './icon2_64px.svg';

export function LoginHeader() {
  const { tokens } = useTheme();
  return (
    <>
      {/* <ThemeProvider theme={theme}>
      <MyThemeComponent><LogoShip/>ABCシステム</MyThemeComponent>
      </ThemeProvider> */}
      
      <Heading level={2} padding={tokens.space.small} textAlign="center" backgroundColor="white">
      <LogoShip/>ABCシステム
      </Heading>
    </>
  );
}
```


```src/Auth/SignInFooter.tsx
import { Flex, Link, useAuthenticator, useTheme } from "@aws-amplify/ui-react";
import { Translations } from "@aws-amplify/ui-components";
import { I18n } from "aws-amplify";

export function SignInFooter() {
  const { toResetPassword } = useAuthenticator();
  const { tokens } = useTheme();
  const footerText = I18n.get([Translations.RESET_YOUR_PASSWORD]);

  return (
    <Flex justifyContent="center" padding={`0 0 ${tokens.space.medium}`}>
      <Link onClick={toResetPassword}>{footerText}</Link>
    </Flex>
  );
}
```

```src/Auth/SignInHeader.tsx
import { Heading, useTheme } from "@aws-amplify/ui-react";
import { Translations } from "@aws-amplify/ui-components";
import { I18n } from 'aws-amplify';
export function SignInHeader() {
    const { tokens } = useTheme();
    const headerText = I18n.get([Translations.SIGN_IN_HEADER_TEXT]);
    return (
        <Heading level={4} padding={`${tokens.space.xl} ${tokens.space.xl} 0`} >
            {headerText}
        </Heading>
    );
}
```

```src/Auth/SignUpHeader.tsx
import { Heading, useTheme } from "@aws-amplify/ui-react";
import { Translations } from "@aws-amplify/ui-components";
import { I18n } from 'aws-amplify';
export function SignUpHeader() {
    const { tokens } = useTheme();
    const headetText = I18n.get([Translations.SIGN_UP_HEADER_TEXT]);
    return (
        <Heading level={4} padding={`${tokens.space.xl} ${tokens.space.xl} 0`} >
            {headetText}
        </Heading>
    );
}
```

```src/Auth/UIVocabularies.tsx

import { I18n } from 'aws-amplify';
import { Translations } from "@aws-amplify/ui-components";

export const SetUIVocabularies = (lang: string): void => {
    I18n.setLanguage(lang);

    //see ,https://ui.docs.amplify.aws/components/authenticator
    // refs) node_modules/@aws-amplify/ui-components/dist/types/common/Translations.d.ts
    // refs) https://github.com/aws-amplify/amplify-ui/blob/main/packages/ui/src/i18n/dictionaries/authenticator/ja.ts
    I18n.putVocabulariesForLanguage('ja', {
        // ---
        // サインイン
        // ---
        [Translations.SIGN_IN_ACTION]:  'ログイン',  // 'Login', // Tab header
        [Translations.SIGN_IN_TEXT]: 'ログイン', // Button label
        [Translations.SIGN_IN_HEADER_TEXT]: 'アカウントにログイン', //'Sign in to your account', 
        Username: 'ユーザーID', // Username label
        Password: 'パスワード', // Password label
        [Translations.FORGOT_PASSWORD_TEXT]: 'パスワードリセット', 

        // ---
        // サインアップ、一部他と兼用
        // ---
        [Translations.CREATE_ACCOUNT_TEXT]: 'アカウント作成', // Tab header
        'Create Account':  'アカウント作成', // 'Register', // Tab header
        [Translations.SIGN_UP_HEADER_TEXT]: '新しいユーザー', // Header text
        'Confirm Password': '確認用パスワード', // Confirm Password label
        Email: 'メールアドレス',
        // 'Phone Number': 'Enter your phone number',

        // ---
        // パスワードリセット
        // ---
        [Translations.RESET_YOUR_PASSWORD]: 'パスワードをリセット', // 画面説明 lable
        [Translations.USERNAME_PLACEHOLDER]: 'ユーザーIDまたはメールアドレス', // リセットするユーザー名 lable
        // SEND_CODE型だとなぜか認識されない → [Translations.SEND_CODE]: 'コード送信', // コード送信ボタン
        'Send code': 'コード送信', // パスワードリセットコードの送信時の説明タイトル
        [Translations.BACK_TO_SIGN_IN]: 'ログインに戻る', // 前画面に戻るリンク表示文字列

        // パスワードリセット後
        [Translations.CONFIRM_SIGN_UP_RESEND_CODE]: '再送信',   // Resend Code 再送信
        'Code':'確認コード', //パスワードリセット時のコード（Your verification codeを入れる）、TOTPでのコード入力のplaceholder
        [Translations.NEW_PASSWORD_LABEL]: "新しいパスワード", //新しいパスワード
        // パスワード確認用のplaceholerは、サインアップと同じ

        // Submit はどの型定義か不明
        'Submit': '送信',

        // ---
        // パスワード変更
        // ---
        [Translations.CHANGE_PASSWORD]: 'パスワード変更',
        
        //--- アカウント復元用のメールアドレス確認
        [Translations.VERIFY_CONTACT_VERIFY_LABEL]: '確認',   // Verify
        'Skip': 'スキップ', //今は確認せずにスキップ

        // --- TOTPログイン認証のパスワード認証後
        [Translations.CONFIRM_TOTP_CODE]: '2段階認証 認証コード', // TOTPのワンタイムトークンの入力画面説明ラベル
        [Translations.CONFIRM]: '確認', //TOTPの送信ボタン
        

        // ---
        // 
        // ---
        'Signing in': 'サインイン...', // サインイン（ログイン）ボタン後の処理中
        //定義にsubmitting がない


        //--- cognitoのサーバーからのメッセージ
        // Cognito Server Side Error Messages
        // ref.) https://github.com/aws-amplify/amplify-js/issues/867
        //---
        'User does not exist.': 'ユーザーが存在しません',
        'Incorrect username or password.': 'ユーザー名またはパスワードが違います',
        'User is not confirmed.': 'ユーザーは検証されていません',
        'User already exists': 'ユーザーは既に存在します',
        'Invalid verification code provided, please try again.': '指定された確認コードが無効です。もう一度お試しください',
        'Invalid password format': 'パスワードのフォーマットが不正です',
        'Account recovery requires verified contact information': 'アカウントの復旧には確認済みの連絡先情報が必要です',
        'Invalid phone number format': '不正な電話番号フォーマットです。 電話番号は次のフォーマットで入力してください: +12345678900',
        'An account with the given email already exists.': 'そのメールアドレスは既に存在します',
        'Username cannot be empty': 'ユーザー名は必須です',
        'Password attempts exceeded': 'ログイン試行回数が上限に達しました',
        'Attempt limit exceeded, please try after some time.': '試行制限を超過しました。しばらくしてからもう一度お試しください',
        'Username/client id combination not found.': 'ユーザーが存在しません',
        'CUSTOM_AUTH is not enabled for the client.': 'パスワードは必須です', // 本来の意味とは異なるが、パスワード未入力時に発生するのでこの訳としている
        'Password does not conform to policy: Password not long enough': 'パスワードは8文字以上を入力してください (8文字以上の大文字小文字を含む英数字)', // 適宜修正
        'Password does not conform to policy: Password must have uppercase characters': 'パスワードには大文字を含めてください (8文字以上の大文字小文字を含む英数字)', // 適宜修正
        'Password does not conform to policy: Password must have lowercase characters': 'パスワードには小文字を含めてください (8文字以上の大文字小文字を含む英数字)', // 適宜修正
        'Password does not conform to policy: Password must have numeric characters': 'パスワードには数字を含めてください (8文字以上の大文字小文字を含む英数字)', // 適宜修正
        "1 validation error detected: Value at 'password' failed to satisfy constraint: Member must have length greater than or equal to 6": 'パスワードは8文字以上、大文字小文字を含む英数字を指定してください', // 適宜修正。本来の意味とは異なるがこれで明示している。
        "2 validation errors detected: Value at 'password' failed to satisfy constraint: Member must have length greater than or equal to 6; Value at 'password' failed to satisfy constraint: Member must satisfy regular expression pattern: ^[\S]+.*[\S]+$": 'パスワードは8文字以上、大文字小文字を含む英数字を指定してください', // 適宜修正。本来の意味とは異なるがこれで明示している。
        'Temporary password has expired and must be reset by an administrator.': '一時パスワードは無効です。管理者によるリセットが必要です',
        "1 validation error detected: Value null at 'attributeName' failed to satisfy constraint: Member must not be null": '入力チェックエラー、必須項目がNULLです', //アカウント復旧でのメールアドレスのラジオをチェックONにせず、送信した場合
        'Invalid code received for user': '無効なコードです', // TOTPでのトークンに誤りがある
        'Invalid session for the user, session is expired.' : '無効なセッション、セッションは有効期限切れです。ログインからやり直してください' // ログインセッション無効です、ログインからやり直し
    });
};
```

- 組み込んだら
npm start で画面を表示すると、カスタマイズしたログイン画面が表示される