以下のサイトを日本語にしてみました。(ページ全てを訳してはいません)
誤りがあったらご指摘いただけると嬉しいです。
イントロダクション
フルスタックの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.jsx
ProtectedLayout.jsx
この2つのコンポーネントがレイアウトコンポーネントです。
次に、src
ディレクトリの中にpages
ディレクトリを作り、その中に次のようなページを作ります。
Login.jsx
Register.jsx
Profile.jsx
About.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 ブラウザでこれを開くだけです。