以下のサイトを日本語にしてみました。(ページ全てを訳してはいません)
誤りがあったらご指摘いただけると嬉しいです。
イントロダクション
フルスタックの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 を実行することで確認できます。
# 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アプリでは、他にもいくつかの依存関係を使うので、すべての依存関係をインストールするには、以下のコマンドに従ってください。
npm install react-router-dom axios flowbite flowbite-react
いくつかの開発依存ファイルも使用するので、すべての開発依存ファイルをインストールするには、以下のコマンドに従ってください。
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スクリプトにポートを追加します。
"scripts": {
"dev": "vite --port=3000",
"build": "vite build",
"preview": "vite preview"
},
次に、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ファイルを開き、以下のコードを使用します。
@tailwind base;
@tailwind components;
@tailwind utilities;
Step 4: プロジェクトの構成に取り組む
このステップでは、srcディレクトリの中にいくつかのディレクトリとファイルを作成します。
それではまず、srcディレクトリの中にcomponentsディレクトリを作りましょう。
次に、componentsディレクトリの中に以下の2つのコンポーネントを作ります。
GuestLayout.jsxProtectedLayout.jsx
この2つのコンポーネントがレイアウトコンポーネントです。
次に、srcディレクトリの中にpagesディレクトリを作り、その中に次のようなページを作ります。
Login.jsxRegister.jsxProfile.jsxAbout.jsx
次に、srcディレクトリの中にcontextsディレクトリを作り、その中に1つのファイルを置きます。
AuthContext.jsx
Step 5: ルーターファイルでの作業
このステップでは、srcディレクトリ内に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ファイルを開き、以下のコードを記述します。
/* 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.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というファイルを作り、以下のコードを使ってください。
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 クライアントライブラリです。
以下は、コードの各パートが何を行うかの内訳です。
- 最初の行は、
Axiosライブラリをインポートします。 - 次の行では、
createメソッドを使用して新しいAxiosインスタンスを作成します。
これは、すべてのAPIエンドポイントのベースURLであるhttp://localhost:8000/apiを設定しています。
また、withCredentialsをtrueに設定することで、リクエストとともにクッキーが送信され、認証されたリクエストが可能になることを意味します。
最後に、デフォルトのContent-Typeヘッダーをapplication/jsonに、Acceptヘッダーをapplication/jsonに設定します。 - 最後の行は、新しく作成された
Axiosインスタンスをモジュールとしてエクスポートし、他のモジュールがインポートして使用できるようにしています。
レイアウトファイルでの作業
すでにcomponentsディレクトリ内に2つのレイアウトファイルを作成しています。
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ファイルを開き、以下のコードを使用してください。
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を開き、以下のコードを使用してください。
/* 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を開き、以下のコードを使用してください。
/* 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を開き、以下のコードを使用してください。
/* 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を開き、以下のコードを使用してください。
/* 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アプリケーションの実行
コーディングはすべて完了したので、あとは以下のコマンドでこのアプリケーションを実行するだけです。
npm run dev
このコマンドは、次のURLのアプリケーションを実行します。http://localhost:3000 ブラウザでこれを開くだけです。