Laravel10 + ReactでSPA作成 [Part 2]

以下のサイトを日本語にしてみました。（ページ全てを訳してはいません）
誤りがあったらご指摘いただけると嬉しいです。

https://dcodemania.com/post/full-stack-laravel-10-react-js-spa-authentication-part-2

## イントロダクション
フルスタックの`Laravel 10`と`React JS`は、モダンなWebアプリケーションの構築に使用される堅牢な技術スタックです。
これらを組み合わせることで、レスポンシブでセキュアな、シームレスで直感的なユーザー体験を提供します。
このようなアプリケーションの構築で重要な側面の1つは、認証の実装で、許可されたユーザーだけが機密データにアクセスし、特定のアクションを実行できるようにします。

フルスタックの`Laravel 10`と`React JS`でSPA認証を実装するために、`Laravel Sanctum`はAPIエンドポイントを保護するために使用できる軽量でトークン・ベースの認証パッケージです。
`Laravel Sanctum`を使用すると、開発者はAPIトークンを生成して管理することができ、ユーザーのリクエストを認証し、保護されたリソースへのアクセスを提供するために使用できます。

SPA認証に`Laravel Sanctum`を使用する場合、開発者はステートレスリクエストかステートフルリクエストのどちらかを選択して実装することができます。
ステートフルリクエストでは、ユーザーの認証状態がサーバー側に永続化されますが、ステートレスリクエストでは永続化されません。
ステートフルリクエストでは、ユーザーの認証状態がサーバーに保存され、セッションクッキーを使ってユーザーが識別されます。
対照的に、ステートレスリクエストでは、リクエストごとにAPIトークンを送信し、サーバーは応答する前にトークンの有効性を検証する。

`Laravel Sanctum`でステートフルリクエストを実装するには、セッションドライバーとステートフルドメインを設定します。
一方、ステートレスリクエストを実装するには、リクエストヘッダーでトークンをチェックするミドルウェアを設定します。

全体として、`Laravel Sanctum`を使用したフルスタック`Laravel 10` & `React JS`は、開発者にSPA認証を実装する堅牢で安全な方法を提供します。
ステートフルまたはステートレスリクエストのどちらを使用しても、`Laravel Sanctum`はユーザー認証と認可を簡単に管理し、開発者に安全で柔軟な最新のWebアプリケーションを構築する方法を提供します。

フルスタック`Laravel 10` & `React.js 18`シリーズのPart 2です。
このパートでは、Reactアプリを作成し、Part 1で既に見たAPIを利用する方法を見ていきます。

## Step 1: Viteを使ってReactをインストールする
お使いのコンピューターに`Node.js`と`npm`がインストールされていることを確認してください。
ターミナルで `node -v` と `npm -v` を実行することで確認できます。

```sh:command
# npm 6.x の場合
npm create vite@latest frontend --template react

# npm 7+ の場合は--が必要です
npm create vite@latest frontend -- --template react
```
上記のコマンドを実行すると、frontendというディレクトリが作成されるので、このディレクトリをVS Codeで開きます。

## Step 2: その他の依存関係のインストール
このReactアプリでは、他にもいくつかの依存関係を使うので、すべての依存関係をインストールするには、以下のコマンドに従ってください。
```sh:command
npm install react-router-dom axios flowbite flowbite-react
```
<br>

いくつかの開発依存ファイルも使用するので、すべての開発依存ファイルをインストールするには、以下のコマンドに従ってください。
```sh:command
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```
npm経由で`tailwindcss`とその類の依存関係をインストールし、`init`コマンドを実行して`tailwind.config.cjs`と`postcss.config.cjs`の両方を生成します。

ここでは、ルーティングに`react-router-dom`、HTTPリクエストの送信に`axios`、バックエンドの消費に`flowbite`を使っています。
開発者やデザイナーが、レスポンシブで美しいWebインターフェースを素早く簡単に作成できるように設計されています。
Flowbiteは、シームレスに連携するように設計されたUIコンポーネントとテンプレートの包括的なセットを提供し、一貫性のある視覚的に魅力的なWebアプリケーションを簡単に作成できます。

## Step 3: いくつかのファイルの設定
まず、`package.json`ファイルを開き、`dev`スクリプトにポートを追加します。
```json:package.json
"scripts": {
	"dev": "vite --port=3000",
	"build": "vite build",
	"preview": "vite preview"
},
```
<br>

次に、`tailwind.config.js`ファイルを開き、以下のコードで更新します。
```js:tailwind.config.js
/* eslint-disable no-undef */
/** @type {import('tailwindcss').Config} */
export default {
  content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
  darkMode: "media",
  theme: {
    container: {
      padding: {
        DEFAULT: "1rem",
        sm: "2rem",
        lg: "4rem",
        xl: "5rem",
        "2xl": "6rem",
      },
    },
    extend: {
      colors: {
        primary: {
          50: "#eff6ff",
          100: "#dbeafe",
          200: "#bfdbfe",
          300: "#93c5fd",
          400: "#60a5fa",
          500: "#3b82f6",
          600: "#2563eb",
          700: "#1d4ed8",
          800: "#1e40af",
          900: "#1e3a8a",
        },
      },
    },
    fontFamily: {
      body: [
        "Inter",
        "ui-sans-serif",
        "system-ui",
        "-apple-system",
        "system-ui",
        "Segoe UI",
        "Roboto",
        "Helvetica Neue",
        "Arial",
        "Noto Sans",
        "sans-serif",
        "Apple Color Emoji",
        "Segoe UI Emoji",
        "Segoe UI Symbol",
        "Noto Color Emoji",
      ],
      sans: [
        "Inter",
        "ui-sans-serif",
        "system-ui",
        "-apple-system",
        "system-ui",
        "Segoe UI",
        "Roboto",
        "Helvetica Neue",
        "Arial",
        "Noto Sans",
        "sans-serif",
        "Apple Color Emoji",
        "Segoe UI Emoji",
        "Segoe UI Symbol",
        "Noto Color Emoji",
      ],
    },
  },
  plugins: [
    require('flowbite/plugin')
  ],
};
```
<br>

次に、`src/index.css`ファイルを開き、以下のコードを使用します。
```css:src/index.css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

## Step 4: プロジェクトの構成に取り組む
このステップでは、`src`ディレクトリの中にいくつかのディレクトリとファイルを作成します。

それではまず、`src`ディレクトリの中に`components`ディレクトリを作りましょう。
次に、`components`ディレクトリの中に以下の2つのコンポーネントを作ります。

- `GuestLayout.jsx` 
- `ProtectedLayout.jsx`

この2つのコンポーネントがレイアウトコンポーネントです。

次に、`src`ディレクトリの中に`pages`ディレクトリを作り、その中に次のようなページを作ります。

- `Login.jsx`
- `Register.jsx` 
- `Profile.jsx`
- `About.jsx`

次に、`src`ディレクトリの中に`contexts`ディレクトリを作り、その中に1つのファイルを置きます。

- `AuthContext.jsx`

## Step 5: ルーターファイルでの作業
このステップでは、`src`ディレクトリ内に`router.js`という名前のファイルを作成します。
このファイルの中では、以下のコードを使ってください。
```js:router.js
import { createBrowserRouter } from "react-router-dom";
import Login from './pages/Login';
import About from './pages/About';
import Profile from './pages/Profile';
import Register from './pages/Register';
import ProtectedLayout from './components/ProtectedLayout';
import GuestLayout from './components/GuestLayout';

const router = createBrowserRouter([
  {
    path: "/",
    element: <GuestLayout />,
    children: [
      {
        path: "/",
        element: <Login />,
      },
      {
        path: "/register",
        element: <Register />,
      },
    ],
  },
  {
    path: "/",
    element: <ProtectedLayout />,
    children: [
      {
        path: "/about",
        element: <About />,
      },
      {
        path: "/profile",
        element: <Profile />,
      },
    ],
  },
]);

export default router;
```
上のコードは、`react-router-dom`ライブラリの`createBrowserRouter`を使ってルーターを設定しています。
このルーターは、ゲストユーザーと認証ユーザー用に異なるレイアウトを持つウェブアプリケーションのルートを定義しています。

ルーターは、ルートオブジェクトの配列として定義されます。
各ルートオブジェクトには、そのルートの URL パスを表す `path` プロパティと、パスが一致したときにレンダリングされるコンポーネントを指定する `element` プロパティが含まれます。

最初のルートオブジェクトは、ゲストユーザー用のルートを定義しています。
`element` プロパティには `GuestLayout` コンポーネントが設定され、これらのルートのレイアウトとして使用されます。
`children`プロパティはネストしたルートオブジェクトの配列で、このレイアウト内で表示する特定のページを定義しています。
この場合、2つのルートが定義されています。
1つはログインページ（`path: '/'`）、もう1つは登録ページ（`path: '/register'`）です。

2つ目のルートオブジェクトは、認証されたユーザーのためのルートを定義しています。
`element`プロパティには`ProtectedLayout`コンポーネントが設定され、これらのルートのレイアウトとして使用されます。
前のルートオブジェクトと同様に、`children`プロパティはネストされたルートオブジェクトの配列で、このレイアウト内で表示されるべき特定のページを定義します。
この場合、2つのルートが定義されています。
1つはaboutページ（`path: '/about'`）、もう1つはユーザープロファイルページ（`path: '/profile'`）です。

## Step 6: AuthContext.jsxファイルを動作させる
このファイルはすでに作成されているので、`src/contexts/AuthContext.jsx`ファイルを開き、以下のコードを記述します。
```jsx:src/contexts/AuthContext.jsx
/* eslint-disable react/prop-types */
import { createContext, useContext, useState } from "react";
import axios from "axios";

const AuthContent = createContext({
  user: null,
  setUser: () => { },
  csrfToken: () => { },
});

export const AuthProvider = ({ children }) => {
  const [user, _setUser] = useState(
    JSON.parse(localStorage.getItem('user')) || null
  );

  // ユーザーをローカルストレージへセットする
  const setUser = (user) => {
    if (user) {
      localStorage.setItem('user', JSON.stringify(user));
    } else {
      localStorage.removeItem('user');
    }
    _setUser(user);
  };

  // guestメソッドのcsrfトークン設定
  const csrfToken = async () => {
    await axios.get('http://localhost:8000/sunctum/csrf-cookie');
    return true;
  };

  return (
    <AuthContent.Provider value={{ user, setUser, csrfToken }}>
      {children}
    </AuthContent.Provider>
  );
};

export const useAuth = () => {
  return useContext(AuthContent);
}
```
上のコードは、Webアプリケーションの認証状態を管理するReactコンテキスト・プロバイダとフックです。

`AuthProvider`コンポーネントは、Reactの[`createContext`](https://qiita.com/ryokkkke/items/dc25111fcf52ea579d58)関数を使用して`AuthContent`という新しいコンテキストを作成します。
このコンテキストは、ユーザーオブジェクトを`null`に設定したデフォルト値と、ユーザーオブジェクトを新しい値で更新する`setUser`とゲストメソッド用のCSRFトークンを生成する`csrfToken`の2つの関数を提供します。

`AuthProvider`の内部では、ユーザーの状態は`useState`を使用して初期化され、値は`JSON.parse(localStorage.getItem('user'))`を使用してローカルストレージから取得されます。
ローカル・ストレージにユーザ値がない場合は、デフォルト値の`null`が使用されます。

`setUser`関数は、`JSON.stringify`と`localStorage.setItem`を使用して、ユーザー状態を更新し、ユーザーオブジェクトをローカルストレージに設定します。

`csrfToken` 関数は、`axios` を使用して `sanctum/csrf-cookie` エンドポイントに `GET` リクエストを行い、CSRF トークンを生成します。

`AuthProvider`コンポーネントは、`user`、`setUser`、`csrfToken`の値と、認証状態にアクセスする必要がある子コンポーネントとなる`children` propを含むオブジェクトに設定された値propを持つ`AuthContent.Provider`コンポーネントを返します。

`useAuth` フックは `useContext` フックを使用して `AuthContent` コンテキストを取得し、 `user`、`setUser`、`csrfToken` の値を含む `value` オブジェクトを返します。子コンポーネントでこのフックを使うと、認証状態にアクセスしたり、 ユーザの認証状態に応じたアクションを実行したりすることができます。

## Step 7: axios.jsファイルの作成
`src`ディレクトリに`axios.js`というファイルを作り、以下のコードを使ってください。
```js:axios.js
import { Axios } from "axios";

const axios = Axios.create({
  baseURL: "http://localhost:8000/api",
  withCredentials: true,
  headers: {
    "Content-Type": "application/json",
    "Accept": "application/json",
  },
});

export default axios;
```
これは、いくつかのデフォルト設定で`Axios`インスタンスを作成し、モジュールとしてエクスポートするJavaScriptコードです。
`Axios` は、JavaScript アプリケーションから AJAX リクエストを行うための一般的な HTTP クライアントライブラリです。

以下は、コードの各パートが何を行うかの内訳です。

1. 最初の行は、`Axios`ライブラリをインポートします。
2. 次の行では、`create`メソッドを使用して新しいAxiosインスタンスを作成します。
これは、すべてのAPIエンドポイントのベースURLである`http://localhost:8000/api`を設定しています。
また、`withCredentials`を`true`に設定することで、リクエストとともにクッキーが送信され、認証されたリクエストが可能になることを意味します。
最後に、デフォルトの`Content-Type`ヘッダーを`application/json`に、`Accept`ヘッダーをa`pplication/json`に設定します。
3. 最後の行は、新しく作成された `Axios` インスタンスをモジュールとしてエクスポートし、他のモジュールがインポートして使用できるようにしています。

## レイアウトファイルでの作業
すでに`components`ディレクトリ内に2つのレイアウトファイルを作成しています。
`GuestLayout.jsx`を開き、以下のコードを使用してください。
```jsx:GuestLayout.jsx
import React from "react";
import { Navigate, Outlet } from "react-router-dom";
import { useAuth } from '../contexts/AuthContext';

export default function GuestLayout() {
  const { user } = useAuth();

  // もしユーザーがログインしていたら、プロフィールページにリダイレクト
  if (user) {
    return <Navigate to="/profile" />
  }
  return (
    <>
      <Outlet />
    </>
  );
}
```
<br>

次に、`ProtectedLayout.jsx`ファイルを開き、以下のコードを使用してください。
```jsx:ProtectedLayout.jsx
import React, { useEffect } from "react";
import { Navigate, Outlet } from "react-router-dom";
import { NavLink } from "react-router-dom";
import axios from "../axios";
import { useAuth } from "../contexts/AuthContext";

export default function DefaultLayout() {
  const { user, setUser } = useAuth();

  // サーバーからユーザーがログインしているかどうかをチェック
  useEffect(() => {
    (async () => {
      try {
        const resp = await axios.get('/user');
        if (resp.status === 200) {
          setUser(resp.data.data);
        }
      } catch (error) {
        if (error.response.status === 401) {
          localStorage.removeItem('user');
          window.location.href = '/';
        }
      }
    })();
  }, []);

  // ユーザーがログインしていない場合はログイン画面にリダイレクト
  if (!user) {
    return <Navigate to="/" />;
  }

  // ログアウトユーザー
  const handleLogout = async () => {
    try {
      const resp = await axios.post('/logout');
      if (resp.status === 200) {
        localStorage.removeItem('user');
        window.location.href = '/';
      }
    } catch (error) {
      console.log(error);
    }
  };

  return (
    <>
      <nav className="bg-white border-gray-200 px-2 sm:px-4 py-2.5 dark:bg-gray-900">
        <div className="container flex flex-wrap items-center justify-between mx-auto">
          <a href="https://dcodemania.com/" className="flex items-center">
            <img
              src="https://dcodemania.com/img/logo.svg"
              className="h-6 mr-3 sm:h-9"
              alt="DCodeMania Logo"
            />
            <span className="self-center text-xl font-semibold whitespace-nowrap dark:text-white">
              DCodeMania
            </span>
          </a>
          <button
            data-collapse-toggle="navbar-default"
            type="button"
            className="inline-flex items-center p-2 ml-3 text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600"
            aria-controls="navbar-default"
            aria-expanded="false"
          >
            <span className="sr-only">Open main menu</span>
            <svg
              className="w-6 h-6"
              aria-hidden="true"
              fill="currentColor"
              viewBox="0 0 20 20"
              xmlns="http://www.w3.org/2000/svg"
            >
              <path
                fillRule="evenodd"
                d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z"
                clipRule="evenodd"
              ></path>
            </svg>
          </button>
          <div className="hidden w-full md:block md:w-auto" id="navbar-default">
            <ul className="flex flex-col p-4 mt-4 border border-gray-100 rounded-lg bg-gray-50 md:flex-row md:space-x-8 md:mt-0 md:text-sm md:font-medium md:border-0 md:bg-white dark:bg-gray-800 md:dark:bg-gray-900 dark:border-gray-700">
              <li>
                <NavLink
                  to="/profile"
                  className={({ isActive }) =>
                    isActive
                      ? "block py-2 pl-3 pr-4 text-white bg-blue-700 rounded md:bg-transparent md:text-blue-700 md:p-0 dark:text-white"
                      : "block py-2 pl-3 pr-4 rounded md:bg-transparent md:p-0 dark:text-gray-400 md:dark:hover:text-white"
                  }
                >
                  Profile
                </NavLink>
              </li>
              <li>
                <NavLink
                  to="/about"
                  className={({ isActive }) =>
                    isActive
                      ? "block py-2 pl-3 pr-4 text-white bg-blue-700 rounded md:bg-transparent md:text-blue-700 md:p-0 dark:text-white"
                      : "block py-2 pl-3 pr-4 rounded md:bg-transparent md:p-0 dark:text-gray-400 md:dark:hover:text-white"
                  }
                >
                  About
                </NavLink>
              </li>

              <li>
                <a
                  href="#"
                  onClick={handleLogout}
                  className="block py-2 pl-3 pr-4 text-gray-700 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-gray-400 md:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent"
                >
                  Logout
                </a>
              </li>
            </ul>
          </div>
        </div>
      </nav>
      <main className="container flex justify-center flex-col items-center mt-10">
        <Outlet />
      </main>
    </>
  );
}
```
このファイルにはヘッダー・ナビゲーション部分も含まれています。

## Step 9-1: Registerページでの作業
Registerページファイルはすでに`src/pages/Register.jsx`の中に作成されています。
`Register.jsx`を開き、以下のコードを使用してください。
```jsx:src/pages/Register.jsx
/* eslint-disable no-unused-vars */
import React from "react";
import { Link, Navigate } from "react-router-dom";
import axios from "../axios";
import { useAuth } from '../contexts/AuthContext';

export default function Register() {
  const { setUser } = useAuth();
  const [nameError, setNameError] = React.useState();
  const [emailError, setEmailError] = React.useState();
  const [passwordError, setPasswordError] = React.useState();

  // ユーザーの登録
  const handleSubmit = async (e) => {
    e.preventDefault();
    const { name, email, password, cpassword } = e.target.elements;
    const body = {
      name: name.value,
      email: email.value,
      password: password.value,
      password_confirmation: cpassword.value,
    };
    try {
      const resp = await axios.post('/register', body);
      if (resp.status === 200) {
        setUser(resp.data.user);
        return <Navigate to="/profile" />;
      }
    } catch (error) {
      if (error.response.status === 422) {
        console.log(error.response.data.errors);
        if (error.response.data.errors.name) {
          setNameError(error.response.data.errors.name[0]);
        } else {
          setNameError('');
        }
        if (error.response.data.errors.email) {
          setEmailError(error.response.data.errors.email[0]);
        } else {
          setEmailError("");
        }
        if (error.response.data.errors.password) {
          setPasswordError(error.response.data.errors.password[0]);
        } else {
          setPasswordError("");
        }
      }
    }
  };

  return (
    <section className="bg-gray-50 dark:bg-gray-900">
      <div className="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
        <a
          href="#"
          className="flex items-center mb-6 text-2xl font-semibold text-gray-900 dark:text-white"
        >
          <img
            className="w-8 h-8 mr-2"
            src="https://dcodemania.com/img/logo.svg"
            alt="logo"
          />
          DCodemania
        </a>
        <div className="w-full bg-white rounded-lg shadow dark:border md:mt-0 sm:max-w-md xl:p-0 dark:bg-gray-800 dark:border-gray-700">
          <div className="p-6 space-y-4 md:space-y-6 sm:p-8">
            <h1 className="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
              Create and account
            </h1>
            <form
              className="space-y-4 md:space-y-6"
              action="#"
              method="post"
              onSubmit={handleSubmit}
            >
              <div>
                <label
                  htmlFor="name"
                  className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
                >
                  Full Name
                </label>
                <input
                  type="text"
                  name="name"
                  id="name"
                  className="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
                  placeholder="Jhone Doe"
                  required
                />
                {nameError && (
                  <p className="text-sm text-red-600">{nameError}</p>
                )}
              </div>
              <div>
                <label
                  htmlFor="email"
                  className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
                >
                  Your email
                </label>
                <input
                  type="email"
                  name="email"
                  id="email"
                  className="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
                  placeholder="name@company.com"
                  required
                />
                {emailError && (
                  <p className="text-sm text-red-600">{emailError}</p>
                )}
              </div>
              <div>
                <label
                  htmlFor="password"
                  className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
                >
                  Password
                </label>
                <input
                  type="password"
                  name="password"
                  id="password"
                  placeholder="••••••••"
                  className="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
                  required
                />
                {passwordError && (
                  <p className="text-sm text-red-600">{passwordError}</p>
                )}
              </div>
              <div>
                <label
                  htmlFor="cpassword"
                  className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
                >
                  Confirm password
                </label>
                <input
                  type="password"
                  name="cpassword"
                  id="cpassword"
                  placeholder="••••••••"
                  className="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
                  required
                />
              </div>

              <button
                type="submit"
                className="w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800"
              >
                Create an account
              </button>
              <p className="text-sm font-light text-gray-500 dark:text-gray-400">
                Already have an account?{" "}
                <Link
                  to="/"
                  className="font-medium text-primary-600 hover:underline dark:text-primary-500"
                >
                  Login here
                </Link>
              </p>
            </form>
          </div>
        </div>
      </div>
    </section>
  );
}

```

## Step 9-2: Loginページでの作業
Loginページファイルはすでに`src/pages/Login.jsx`の中に作成されています。
`Login.jsx`を開き、以下のコードを使用してください。
```jsx:src/pages/Login.jsx
/* eslint-disable react/no-unescaped-entities */
/* eslint-disable no-unused-vars */
import React from "react";
import { Link, Navigate } from "react-router-dom";
import axios from "../axios";
import { useAuth } from '../contexts/AuthContext';

export default function Login() {
  const { setUser, csrfToken } = useAuth();
  const [error, setError] = React.useState(null);

  // ログインユーザー
  const handleSubmit = async (e) => {
    e.preventDefault();
    const { email, password } = e.target.elements;
    const body = {
      email: email.value,
      password: password.value
    };
    await csrfToken();
    try {
      const resp = await axios.post('/login', body);
      if (resp.status === 200) {
        setUser(resp.data.user);
        return <Navigate to="/profile/" />
      }
    } catch (error) {
      if (error.response.status === 401) {
        setError(error.response.data.message);
      }
    }
  };

  return (
    <section className="bg-gray-50 dark:bg-gray-900">
      <div className="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
        <a
          href="#"
          className="flex items-center mb-6 text-2xl font-semibold text-gray-900 dark:text-white"
        >
          <img
            src="https://dcodemania.com/img/logo.svg"
            className="w-8 h-8 mr-2"
            alt="logo"
          />
          DCodeMania
        </a>
        <div className="w-full bg-white rounded-lg shadow dark:border md:mt-0 sm:max-w-md xl:p-0 dark:bg-gray-800 dark:border-gray-700">
          <div className="p-6 space-y-4 md:space-y-6 sm:p-8">
            <h1 className="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
              Sign in to your account
            </h1>
            {error && (
              <div
                className="flex p-4 mb-4 text-sm text-red-800 border border-red-300 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400 dark:border-red-800"
                role="alert"
              >
                <svg
                  aria-hidden="true"
                  className="flex-shrink-0 inline w-5 h-5 mr-3"
                  fill="currentColor"
                  viewBox="0 0 20 20"
                  xmlns="http://www.w3.org/2000/svg"
                >
                  <path
                    fillRule="evenodd"
                    d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
                    clipRule="evenodd"
                  ></path>
                </svg>
                <span className="sr-only">Info</span>
                <div>{error}</div>
              </div>
            )}

            <form
              action="#"
              className="space-y-4 md:space-y-6"
              method="post"
              onSubmit={handleSubmit}
            >
              <div>
                <label
                  htmlFor="email"
                  className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
                >
                  Your email
                </label>
                <input
                  type="email"
                  name="email"
                  id="email"
                  className="g-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
                  placeholder="name@company.com"
                  required
                />
              </div>
              <div>
                <label
                  htmlFor="password"
                  className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
                >
                  Password
                </label>
                <input
                  type="password"
                  name="password"
                  id="password"
                  className="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
                  placeholder="********"
                  required
                />
              </div>

              <button
                type="submit"
                className="w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800"
              >
                Sign in
              </button>
              <p className="text-sm font-light text-gray-500 dark:text-gray-400">
                Don't have an account yet?{" "}
                <Link
                  to="/register"
                  className="font-medium text-primary-600 hover:underline dark:text-primary-500"
                >
                  Sign up
                </Link>
              </p>
            </form>
          </div>
        </div>
      </div>
    </section>
  );
}
```

## Step 10: Profileページでの作業
Profileページファイルはすでに`src/pages/Profile.jsx`の中に作成されています。
`Profile.jsx`を開き、以下のコードを使用してください。
```jsx:Profile.jsx
/* eslint-disable no-unused-vars */
import React from "react";
import { useAuth } from "../contexts/AuthContext";

export default function Profile() {
  const { user } = useAuth();
  return (
    <>
      <div className="text-6xl font-bold text-slate-600">User Profile</div>
      <hr className="bg-slate-400 h-1 w-full my-4" />
      <div className="block p-10 bg-white border border-gray-200 shadow-xl rounded-lg shadowdark:border-gray-700">
        <h5 className="my-2 text-2xl font-bold tracking-tight">
          Name: {user.name}
        </h5>
        <p className="font-normal text-gray-700">Email:{user.email}</p>
        <p className="font-normal text-gray-700">
          Created At: {user.created_at}
        </p>
      </div>
    </>
  );
}
```

## Step 11: Aboutページでの作業
Aboutページファイルはすでに`src/pages/About.jsx`の中に作成されています。
`About.jsx`を開き、以下のコードを使用してください。
```jsx:src/pages/About.jsx
/* eslint-disable no-unused-vars */
import React from "react";

export default function About() {
  return (
    <>
      <div className="text-6xl font-bold text-slate-600">About Us</div>
      <hr className="bg-slate-400 h-1 w-full my-4" />
      <p>
        Lorem ipsum dolor sit amet consectetur, adipisicing elit. Laboriosam
        soluta veniam sequi modi nihil eius explicabo quasi totam quidem
        voluptatibus ex, obcaecati architecto perspiciatis dolorem magni rem vel
        cupiditate repudiandae?
      </p>
      <p>
        Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ratione eaque
        distinctio sunt deleniti voluptatum nostrum expedita voluptatibus
        aliquid mollitia, nam vero! Sed suscipit saepe quo cupiditate!
        Voluptatibus illum amet nulla? Eveniet reiciendis voluptas provident
        aliquid, voluptatum, tempora reprehenderit neque, ad ipsa similique quae
        dignissimos amet odio distinctio atque! Deserunt animi dicta quisquam
        voluptates iste dolorum architecto, sapiente numquam ipsa! Odit.
        Adipisci dignissimos tempora, praesentium excepturi, iste aliquid,
        debitis rem id aperiam itaque asperiores soluta similique eligendi sint
        ut necessitatibus architecto quos ab fugiat harum rerum magnam nulla
        distinctio? Aut, nesciunt. Voluptates doloribus quibusdam voluptatem
        vero in. Itaque dicta quae error nemo sapiente quos id, magnam numquam
        maiores vero sed perferendis quia nihil impedit deleniti doloremque
        repellat! Ullam rem libero ut?
      </p>
    </>
  );
}
```

## Step 12: Reactアプリケーションの実行
コーディングはすべて完了したので、あとは以下のコマンドでこのアプリケーションを実行するだけです。
```sh:command
npm run dev
```
このコマンドは、次のURLのアプリケーションを実行します。http://localhost:3000 ブラウザでこれを開くだけです。