0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Laravel10 + ReactでSPA作成 [Part 2]

Posted at

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

イントロダクション

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

フルスタックのLaravel 10React 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.jsnpmがインストールされていることを確認してください。
ターミナルで node -vnpm -v を実行することで確認できます。

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アプリでは、他にもいくつかの依存関係を使うので、すべての依存関係をインストールするには、以下のコマンドに従ってください。

command
npm install react-router-dom axios flowbite flowbite-react

いくつかの開発依存ファイルも使用するので、すべての開発依存ファイルをインストールするには、以下のコマンドに従ってください。

command
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

npm経由でtailwindcssとその類の依存関係をインストールし、initコマンドを実行してtailwind.config.cjspostcss.config.cjsの両方を生成します。

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

Step 3: いくつかのファイルの設定

まず、package.jsonファイルを開き、devスクリプトにポートを追加します。

package.json
"scripts": {
	"dev": "vite --port=3000",
	"build": "vite build",
	"preview": "vite preview"
},

次に、tailwind.config.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')
  ],
};

次に、src/index.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という名前のファイルを作成します。
このファイルの中では、以下のコードを使ってください。

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ファイルを開き、以下のコードを記述します。

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関数を使用してAuthContentという新しいコンテキストを作成します。
このコンテキストは、ユーザーオブジェクトをnullに設定したデフォルト値と、ユーザーオブジェクトを新しい値で更新するsetUserとゲストメソッド用のCSRFトークンを生成するcsrfTokenの2つの関数を提供します。

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

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

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

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

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

Step 7: axios.jsファイルの作成

srcディレクトリにaxios.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を設定しています。
    また、withCredentialstrueに設定することで、リクエストとともにクッキーが送信され、認証されたリクエストが可能になることを意味します。
    最後に、デフォルトのContent-Typeヘッダーをapplication/jsonに、Acceptヘッダーをapplication/jsonに設定します。
  3. 最後の行は、新しく作成された Axios インスタンスをモジュールとしてエクスポートし、他のモジュールがインポートして使用できるようにしています。

レイアウトファイルでの作業

すでにcomponentsディレクトリ内に2つのレイアウトファイルを作成しています。
GuestLayout.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 />
    </>
  );
}

次に、ProtectedLayout.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を開き、以下のコードを使用してください。

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を開き、以下のコードを使用してください。

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を開き、以下のコードを使用してください。

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を開き、以下のコードを使用してください。

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アプリケーションの実行

コーディングはすべて完了したので、あとは以下のコマンドでこのアプリケーションを実行するだけです。

command
npm run dev

このコマンドは、次のURLのアプリケーションを実行します。http://localhost:3000 ブラウザでこれを開くだけです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?