1
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?

ログイン画面練習2

1
Last updated at Posted at 2026-05-21

React + TypeScript ログイン練習(AuthContext・React Router v6)

工場システムのログイン画面練習プロジェクトです。
モックJSONとの照合・AuthContext・未ログインの自動リダイレクトがすべて入っています。

📁 ファイル構成

factory-login/
├── src/
│   ├── context/
│   │   └── AuthContext.tsx     ← ログイン状態の管理(メイン)
│   ├── components/
│   │   ├── LoginForm.tsx       ← ログインフォーム
│   │   └── RequireAuth.tsx     ← 未ログインのリダイレクト
│   ├── pages/
│   │   ├── Login.tsx           ← /login ページ
│   │   └── Dashboard.tsx       ← /dashboard ページ(要ログイン)
│   ├── data/
│   │   └── users.json          ← モックユーザーデータ(8項目)
│   ├── types/
│   │   └── user.ts             ← TypeScript型定義
│   ├── App.tsx                 ← ルーティング設定
│   └── main.tsx                ← エントリーポイント
├── package.json
├── tsconfig.json
└── vite.config.ts

🚀 起動手順

npm install
npm run dev
# → http://localhost:5173

🔑 テストアカウント(パスワード共通: password

ユーザー名 role 状態
yamada admin 有効
suzuki supervisor 有効
tanaka operator 有効
sato operator 無効(エラーテスト用)

ソースコード

package.json

{
  "name": "factory-login",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.22.0"
  },
  "devDependencies": {
    "@types/react": "^18.2.0",
    "@types/react-dom": "^18.2.0",
    "@vitejs/plugin-react": "^4.2.0",
    "typescript": "^5.2.0",
    "vite": "^5.1.0"
  }
}

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": ["src"]
}

vite.config.ts

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
})

index.html

<!doctype html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>工場管理システム</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

src/types/user.ts

// User型の定義
// "admin" | "supervisor" | "operator" はユニオン型
// → この3つの文字列のどれか、という意味

export type UserRole = "admin" | "supervisor" | "operator";
export type UserStatus = "active" | "inactive";

export type User = {
  id: number;
  username: string;
  email: string;
  name: string;
  role: UserRole;
  department: string;
  line: string;
  status: UserStatus;
};

src/data/users.json

[
  {
    "id": 1,
    "username": "yamada",
    "email": "yamada@factory.com",
    "name": "山田 太郎",
    "role": "admin",
    "department": "管理部",
    "line": "全ライン",
    "status": "active"
  },
  {
    "id": 2,
    "username": "suzuki",
    "email": "suzuki@factory.com",
    "name": "鈴木 花子",
    "role": "supervisor",
    "department": "製造部",
    "line": "Aライン",
    "status": "active"
  },
  {
    "id": 3,
    "username": "tanaka",
    "email": "tanaka@factory.com",
    "name": "田中 一郎",
    "role": "operator",
    "department": "製造部",
    "line": "Bライン",
    "status": "active"
  },
  {
    "id": 4,
    "username": "sato",
    "email": "sato@factory.com",
    "name": "佐藤 次郎",
    "role": "operator",
    "department": "品質管理部",
    "line": "検査ライン",
    "status": "inactive"
  }
]

src/context/AuthContext.tsx

ここが詰まりやすいポイント。3つのパーツで構成されています。

パーツ 役割
createContext 「共有する箱」を作る
AuthProvider 箱に値を入れて子孫コンポーネントに配る
useAuth() どのコンポーネントからでも箱の中身を取り出す
import {
  createContext,
  useContext,
  useState,
  useCallback,
  ReactNode,
} from "react";
import type { User } from "../types/user";
import usersData from "../data/users.json";

// JSONをUser型の配列にキャスト(tsconfig の resolveJsonModule: true が必要)
const USERS: User[] = usersData as User[];

// 練習用固定パスワード
const FIXED_PASSWORD = "password";

// ログイン結果の型(3パターン)
type LoginResult = "success" | "not_found" | "inactive";

// Contextに入れる値の型定義
type AuthContextType = {
  user: User | null;
  login: (loginId: string, password: string) => LoginResult;
  logout: () => void;
  isLoggedIn: boolean;
};

// createContext で「箱」を作る
// 初期値を null にしておき、useAuth() 内でチェックする
// → AuthProvider の外で useAuth() を呼んだらエラーにできる
const AuthContext = createContext<AuthContextType | null>(null);

// AuthProvider:箱に値を入れて子孫に配るコンポーネント
// App.tsx の一番外側でこれを使う
type AuthProviderProps = {
  children: ReactNode;
};

export function AuthProvider({ children }: AuthProviderProps) {
  // null = 未ログイン、User型 = ログイン中
  const [user, setUser] = useState<User | null>(null);

  // ログイン処理:users.json と照合する
  const login = useCallback(
    (loginId: string, password: string): LoginResult => {
      // Array.find() で条件に合う最初のユーザーを探す
      const found = USERS.find((u: User) => {
        const idMatch = u.username === loginId || u.email === loginId;
        const passwordMatch = password === FIXED_PASSWORD;
        return idMatch && passwordMatch;
      });

      if (!found) return "not_found";           // 一致なし
      if (found.status === "inactive") return "inactive"; // 無効アカウント

      setUser(found); // stateにセット → アプリ全体に伝わる
      return "success";
    },
    []
  );

  // ログアウト:user を null に戻すだけ
  const logout = useCallback(() => {
    setUser(null);
  }, []);

  const value: AuthContextType = {
    user,
    login,
    logout,
    isLoggedIn: user !== null,
  };

  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  );
}

// useAuth():どのコンポーネントからでも呼べるカスタムフック
// const { user, login, logout, isLoggedIn } = useAuth();
export function useAuth(): AuthContextType {
  const context = useContext(AuthContext);
  if (context === null) {
    throw new Error(
      "useAuth() は <AuthProvider> の内側で使ってください。\n" +
      "App.tsx で <AuthProvider> が最外側にあるか確認してください。"
    );
  }
  return context;
}

src/components/RequireAuth.tsx

未ログインのユーザーを /login に自動リダイレクトするコンポーネント。
App.tsx の Route設定で保護したいページを囲んで使う。

import { Navigate, Outlet } from "react-router-dom";
// Navigate → 別ページに移動するコンポーネント
// Outlet   → 子Routeを描画する場所

import { useAuth } from "../context/AuthContext";

export default function RequireAuth() {
  const { isLoggedIn } = useAuth();

  if (!isLoggedIn) {
    // 未ログイン → /login にリダイレクト
    // replace={true} で「戻る」ボタンで戻れなくする
    return <Navigate to="/login" replace />;
  }

  // ログイン済み → 子Routeを描画
  return <Outlet />;
}

src/components/LoginForm.tsx

import { useState, KeyboardEvent } from "react";
import { useNavigate } from "react-router-dom";
import { useAuth } from "../context/AuthContext";
import usersData from "../data/users.json";
import type { User } from "../types/user";

const USERS: User[] = usersData as User[];

export default function LoginForm() {
  const [loginId, setLoginId]     = useState<string>("");
  const [password, setPassword]   = useState<string>("");
  const [error, setError]         = useState<string>("");
  const [isLoading, setIsLoading] = useState<boolean>(false);

  const { login } = useAuth();      // AuthContextからlogin関数を取得
  const navigate = useNavigate();   // ページ遷移フック

  const handleSubmit = async () => {
    if (!loginId.trim() || !password.trim()) {
      setError("ユーザー名とパスワードを入力してください");
      return;
    }

    setIsLoading(true);
    setError("");

    // 本来はAPI呼び出し。モックなので少し待つ
    await new Promise((resolve) => setTimeout(resolve, 500));

    // AuthContext の login() → users.json と照合
    const result = login(loginId, password);

    setIsLoading(false);

    if (result === "success") {
      navigate("/dashboard"); // ← 成功したらここで遷移!
    } else if (result === "inactive") {
      setError("このアカウントは無効化されています。管理者にお問い合わせください。");
    } else {
      setError("ユーザー名またはパスワードが違います");
    }
  };

  // Enterキーでも送信できるように
  const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
    if (e.key === "Enter") handleSubmit();
  };

  return (
    <div style={styles.wrapper}>
      <div style={styles.card}>
        <div style={styles.header}>
          <div style={styles.icon}>🏭</div>
          <h1 style={styles.title}>工場管理システム</h1>
          <p style={styles.subtitle}>FACTORY MANAGEMENT SYSTEM</p>
        </div>

        <div style={styles.form}>
          <div style={styles.field}>
            <label style={styles.label}>ユーザー名またはメールアドレス</label>
            <input
              type="text"
              value={loginId}
              onChange={(e) => setLoginId(e.target.value)}
              onKeyDown={handleKeyDown}
              placeholder="例: yamada"
              style={styles.input}
              disabled={isLoading}
            />
          </div>

          <div style={styles.field}>
            <label style={styles.label}>パスワード</label>
            <input
              type="password"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
              onKeyDown={handleKeyDown}
              placeholder="パスワードを入力"
              style={styles.input}
              disabled={isLoading}
            />
          </div>

          {error && <p style={styles.error}>⚠️ {error}</p>}

          <button
            onClick={handleSubmit}
            disabled={isLoading}
            style={{ ...styles.button, opacity: isLoading ? 0.6 : 1 }}
          >
            {isLoading ? "認証中..." : "ログイン"}
          </button>
        </div>

        {/* テスト用ユーザー一覧(クリックで自動入力) */}
        <div style={styles.testBox}>
          <p style={styles.testTitle}>🧪 テストアカウント(パスワード共通: password)</p>
          <table style={styles.table}>
            <thead>
              <tr>
                {["ユーザー名", "role", "部署", "ライン", "状態"].map((h) => (
                  <th key={h} style={styles.th}>{h}</th>
                ))}
              </tr>
            </thead>
            <tbody>
              {USERS.map((u) => (
                <tr
                  key={u.id}
                  style={{ cursor: u.status === "active" ? "pointer" : "default" }}
                  onClick={() => { if (u.status === "active") setLoginId(u.username); }}
                >
                  <td style={styles.td}>{u.username}</td>
                  <td style={styles.td}>{u.role}</td>
                  <td style={styles.td}>{u.department}</td>
                  <td style={styles.td}>{u.line}</td>
                  <td style={{ ...styles.td, color: u.status === "active" ? "#4ade80" : "#f87171", fontWeight: 600 }}>
                    {u.status === "active" ? "✓ 有効" : "✕ 無効"}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
          <p style={styles.hint}>※ 有効なアカウントをクリックすると自動入力されます</p>
        </div>
      </div>
    </div>
  );
}

const styles: Record<string, React.CSSProperties> = {
  wrapper: { minHeight: "100vh", background: "#0f172a", display: "flex", alignItems: "center", justifyContent: "center", padding: "24px", fontFamily: "'Segoe UI', 'Hiragino Sans', sans-serif" },
  card: { background: "#1e293b", border: "1px solid #334155", borderRadius: "10px", padding: "36px", width: "100%", maxWidth: "620px", boxShadow: "0 8px 32px rgba(0,0,0,0.5)" },
  header: { textAlign: "center", marginBottom: "28px" },
  icon: { fontSize: "52px" },
  title: { color: "#f1f5f9", fontSize: "22px", fontWeight: 700, margin: "8px 0 4px" },
  subtitle: { color: "#64748b", fontSize: "11px", letterSpacing: "0.12em", margin: 0 },
  form: { display: "flex", flexDirection: "column", gap: "16px" },
  field: { display: "flex", flexDirection: "column", gap: "6px" },
  label: { color: "#94a3b8", fontSize: "13px", fontWeight: 600 },
  input: { background: "#0f172a", border: "1px solid #334155", borderRadius: "6px", padding: "10px 14px", color: "#f1f5f9", fontSize: "14px", outline: "none" },
  error: { background: "rgba(220,38,38,0.15)", border: "1px solid #dc2626", borderRadius: "6px", padding: "10px 14px", color: "#fca5a5", fontSize: "13px", margin: 0 },
  button: { background: "#2563eb", color: "#fff", border: "none", borderRadius: "6px", padding: "12px", fontSize: "15px", fontWeight: 700, cursor: "pointer", letterSpacing: "0.04em", marginTop: "4px" },
  testBox: { marginTop: "24px", background: "#0f172a", borderRadius: "6px", padding: "16px", border: "1px solid #334155" },
  testTitle: { color: "#94a3b8", fontSize: "12px", fontWeight: 700, margin: "0 0 10px" },
  table: { width: "100%", borderCollapse: "collapse", fontSize: "12px" },
  th: { color: "#64748b", textAlign: "left" as const, padding: "6px 8px", borderBottom: "1px solid #334155", fontWeight: 600 },
  td: { color: "#94a3b8", padding: "6px 8px", borderBottom: "1px solid #1e293b" },
  hint: { color: "#475569", fontSize: "11px", margin: "8px 0 0" },
};

src/pages/Login.tsx

// pages/ → URLに対応する画面全体
// components/ → 再利用できる部品
// このファイルは LoginForm を表示するだけ

import LoginForm from "../components/LoginForm";

export default function Login() {
  return <LoginForm />;
}

src/pages/Dashboard.tsx

import { useNavigate } from "react-router-dom";
import { useAuth } from "../context/AuthContext";
import type { UserRole } from "../types/user";

const ROLE_CONFIG: Record<UserRole, { label: string; color: string; bg: string; border: string }> = {
  admin:      { label: "👑 管理者",      color: "#92400e", bg: "#fef3c7", border: "#f59e0b" },
  supervisor: { label: "🔧 監督者",      color: "#1e3a8a", bg: "#dbeafe", border: "#3b82f6" },
  operator:   { label: "⚙️ オペレーター", color: "#14532d", bg: "#dcfce7", border: "#22c55e" },
};

const ROLE_PERMISSIONS: Record<UserRole, string[]> = {
  admin:      ["全データ閲覧", "全データ編集", "ユーザー管理", "システム設定"],
  supervisor: ["担当ラインのデータ閲覧", "担当ラインのデータ編集", "レポート出力"],
  operator:   ["担当ラインのデータ閲覧のみ"],
};

export default function Dashboard() {
  // RequireAuth で守られているので user は必ず User型(nullにならない)
  const { user, logout } = useAuth();
  const navigate = useNavigate();

  const handleLogout = () => {
    logout();           // AuthContext の user を null にリセット
    navigate("/login"); // ログインページへ遷移
  };

  if (!user) return null; // TypeScript用(理論上ここには来ない)

  const roleConfig = ROLE_CONFIG[user.role];
  const permissions = ROLE_PERMISSIONS[user.role];

  return (
    <div style={styles.wrapper}>
      <div style={styles.card}>
        <div style={styles.header}>
          <div>
            <h1 style={styles.title}>ようこそ、{user.name} さん</h1>
            <p style={styles.subtitle}>{roleConfig.label} · {user.department}</p>
          </div>
          <button onClick={handleLogout} style={styles.logoutBtn}>ログアウト</button>
        </div>

        {/* ユーザー情報(8項目) */}
        <section style={styles.section}>
          <h2 style={styles.sectionTitle}>ユーザー情報</h2>
          <div style={styles.grid}>
            <InfoCard label="社員番号"       value={`#${user.id}`} />
            <InfoCard label="ユーザー名"     value={user.username} />
            <InfoCard label="氏名"           value={user.name} />
            <InfoCard label="メールアドレス" value={user.email} />
            <InfoCard label="役割 (role)"    value={user.role} highlight />
            <InfoCard label="部署"           value={user.department} />
            <InfoCard label="担当ライン"     value={user.line} />
            <InfoCard label="ステータス"     value={user.status} />
          </div>
        </section>

        {/* 役割別権限 */}
        <section style={styles.section}>
          <h2 style={styles.sectionTitle}>あなたの権限</h2>
          <div style={{ ...styles.roleBox, background: roleConfig.bg, borderColor: roleConfig.border, color: roleConfig.color }}>
            <p style={{ fontWeight: 700, marginBottom: "10px" }}>{roleConfig.label}</p>
            <ul style={{ margin: 0, paddingLeft: "20px" }}>
              {permissions.map((p) => <li key={p} style={{ marginBottom: "4px", fontSize: "14px" }}>{p}</li>)}
            </ul>
          </div>
        </section>
      </div>
    </div>
  );
}

function InfoCard({ label, value, highlight }: { label: string; value: string | number; highlight?: boolean }) {
  return (
    <div style={styles.infoCard}>
      <span style={styles.infoLabel}>{label}</span>
      <span style={{ ...styles.infoValue, ...(highlight ? { color: "#818cf8", fontWeight: 700 } : {}) }}>
        {value}
      </span>
    </div>
  );
}

const styles: Record<string, React.CSSProperties> = {
  wrapper: { minHeight: "100vh", background: "#0f172a", display: "flex", alignItems: "flex-start", justifyContent: "center", padding: "40px 24px", fontFamily: "'Segoe UI', 'Hiragino Sans', sans-serif" },
  card: { background: "#1e293b", border: "1px solid #334155", borderRadius: "10px", padding: "36px", width: "100%", maxWidth: "680px", boxShadow: "0 8px 32px rgba(0,0,0,0.5)" },
  header: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: "28px" },
  title: { color: "#f1f5f9", fontSize: "22px", fontWeight: 700, margin: "0 0 4px" },
  subtitle: { color: "#64748b", fontSize: "13px", margin: 0 },
  logoutBtn: { background: "transparent", border: "1px solid #475569", color: "#94a3b8", padding: "8px 18px", borderRadius: "6px", cursor: "pointer", fontSize: "13px" },
  section: { marginBottom: "24px" },
  sectionTitle: { color: "#64748b", fontSize: "11px", fontWeight: 700, letterSpacing: "0.1em", textTransform: "uppercase" as const, margin: "0 0 12px" },
  grid: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "10px" },
  infoCard: { background: "#0f172a", border: "1px solid #334155", borderRadius: "6px", padding: "10px 14px", display: "flex", flexDirection: "column" as const, gap: "3px" },
  infoLabel: { color: "#64748b", fontSize: "11px", fontWeight: 600, letterSpacing: "0.05em", textTransform: "uppercase" as const },
  infoValue: { color: "#e2e8f0", fontSize: "14px" },
  roleBox: { borderRadius: "8px", padding: "16px 18px", border: "1px solid" },
};

src/App.tsx

ルーティングの設定ファイル。構造が最重要。

import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import { AuthProvider } from "./context/AuthContext";
import RequireAuth from "./components/RequireAuth";
import Login from "./pages/Login";
import Dashboard from "./pages/Dashboard";

export default function App() {
  return (
    // ① AuthProvider:一番外側で囲む。これがないと useAuth() がエラーになる
    <AuthProvider>

      {/* ② BrowserRouter:URLの変化をReact Routerに伝える */}
      <BrowserRouter>

        {/* ③ Routes:URLとコンポーネントを対応させる */}
        <Routes>
          {/* / → /login に自動リダイレクト */}
          <Route path="/" element={<Navigate to="/login" replace />} />

          {/* /login:誰でもアクセスできる */}
          <Route path="/login" element={<Login />} />

          {/* ログイン必須ゾーン:RequireAuth が未ログインを弾く */}
          <Route element={<RequireAuth />}>
            <Route path="/dashboard" element={<Dashboard />} />
            {/* ページが増えたらここに追加 */}
          </Route>
        </Routes>

      </BrowserRouter>
    </AuthProvider>
  );
}

src/main.tsx

// アプリのエントリーポイント(最初に実行されるファイル)
// index.html の <div id="root"> に App を描画する
// ここは定型文なので触らなくていい

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

カンニングシート

AuthContext の構造まとめ

// 1. createContext で「箱」を作る
const AuthContext = createContext<AuthContextType | null>(null);

// 2. AuthProvider で「箱に値を入れて子孫に配る」
export function AuthProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<User | null>(null);
  const value = { user, login, logout, isLoggedIn: user !== null };
  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

// 3. useAuth() で「箱から値を取り出す」
export function useAuth() {
  const context = useContext(AuthContext);
  if (context === null) throw new Error("AuthProvider の外で使われました");
  return context;
}

users.json との照合

const found = USERS.find((u: User) => {
  const idMatch = u.username === loginId || u.email === loginId;
  const passwordMatch = password === FIXED_PASSWORD;
  return idMatch && passwordMatch;
});

未ログインのリダイレクト

// RequireAuth.tsx
if (!isLoggedIn) return <Navigate to="/login" replace />;
return <Outlet />;

// App.tsx での使い方
<Route element={<RequireAuth />}>           // RequireAuth で囲む
  <Route path="/dashboard" element={<Dashboard />} />
</Route>

ログイン成功後の遷移

const navigate = useNavigate();
const result = login(loginId, password);
if (result === "success") navigate("/dashboard"); // ← ここで遷移

よくある詰まりポイント

症状 原因 解決
ログインしても遷移しない login() の戻り値を使っていない result === "success"navigate() する
useAuth() は AuthProvider の中で使ってください エラー AuthProvider で囲んでいない App.tsx の一番外側を <AuthProvider> で囲む
/dashboard に未ログインでアクセスできる RequireAuth で囲んでいない App.tsx のRoute設定を確認
JSONのimportでエラー tsconfig の設定 "resolveJsonModule": true を追加
// ============================================================
// src/App.tsx
//
// 【ルーティング構造】
//
//   /                   → /login にリダイレクト
//   /login              → ログインページ(誰でも見れる)
//
//   ▼ ログイン必須ゾーン(RequireAuth で囲む)
//   /dashboard          → ダッシュボード
//   /production         → 生産管理
//   /inspection         → 品質検査
//   /reports            → レポート
//
// 【ポイント】
// RequireAuth の <Route element={<RequireAuth />}> を1つ書いて
// その中に保護したいページをまとめて入れるだけでOK。
// ページが増えても RequireAuth を何度も書かなくていい。
// ============================================================

import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";

import { AuthProvider } from "./context/AuthContext";
import RequireAuth from "./components/RequireAuth";

// ログイン不要ページ
import Login from "./pages/Login";

// ログイン必須ページ
import Dashboard   from "./pages/Dashboard";
import Production  from "./pages/Production";
import Inspection  from "./pages/Inspection";
import Reports     from "./pages/Reports";

export default function App() {
  return (
    // ① AuthProvider:一番外側で囲む。これがないと useAuth() がエラーになる
    <AuthProvider>

      {/* ② BrowserRouter:URLの変化をReact Routerに伝える */}
      <BrowserRouter>

        <Routes>

          {/* / にアクセスしたら /login に自動リダイレクト */}
          <Route path="/" element={<Navigate to="/login" replace />} />

          {/* ログイン不要ページ */}
          <Route path="/login" element={<Login />} />

          {/* ======================================================
              ログイン必須ゾーン
              RequireAuth を element に指定した Route で囲むだけ。
              未ログインなら RequireAuth が /login に飛ばしてくれる。
              ページが増えたらこの中に追加するだけでOK。
          ====================================================== */}
          <Route element={<RequireAuth />}>
            <Route path="/dashboard"  element={<Dashboard />} />
            <Route path="/production" element={<Production />} />
            <Route path="/inspection" element={<Inspection />} />
            <Route path="/reports"    element={<Reports />} />
          </Route>

        </Routes>

      </BrowserRouter>
    </AuthProvider>
  );
}
1
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
1
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?