Feat: Add Login Page & Auth Method
增加登录界面和授权验证逻辑
This commit is contained in:
@@ -1,18 +1,39 @@
|
||||
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
|
||||
import { BrowserRouter, Routes, Route, Navigate, Outlet, useNavigate } from "react-router-dom";
|
||||
import { useState, useEffect } from "react";
|
||||
import Projects from "./pages/Projects";
|
||||
import ProjectDetail from "./pages/ProjectDetail";
|
||||
import BuildDetail from "./pages/BuildDetail";
|
||||
import Login from "./pages/Login";
|
||||
import Layout from "./components/Layout";
|
||||
|
||||
function AuthGuard() {
|
||||
const navigate = useNavigate();
|
||||
const [state, setState] = useState<"loading" | "ok" | "unauth">("loading");
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/api/auth/me").then((r) => {
|
||||
if (r.ok) setState("ok");
|
||||
else setState("unauth");
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (state === "loading") return null;
|
||||
if (state === "unauth") return <Navigate to="/login" replace />;
|
||||
return <Outlet />;
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route element={<Layout />}>
|
||||
<Route path="/" element={<Navigate to="/projects" replace />} />
|
||||
<Route path="/projects" element={<Projects />} />
|
||||
<Route path="/projects/:id" element={<ProjectDetail />} />
|
||||
<Route path="/builds/:id" element={<BuildDetail />} />
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route element={<AuthGuard />}>
|
||||
<Route element={<Layout />}>
|
||||
<Route path="/" element={<Navigate to="/projects" replace />} />
|
||||
<Route path="/projects" element={<Projects />} />
|
||||
<Route path="/projects/:id" element={<ProjectDetail />} />
|
||||
<Route path="/builds/:id" element={<BuildDetail />} />
|
||||
</Route>
|
||||
</Route>
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
|
||||
@@ -1,17 +1,30 @@
|
||||
import { Outlet, NavLink } from "react-router-dom";
|
||||
import { GitBranch } from "lucide-react";
|
||||
import { Outlet, NavLink, useNavigate } from "react-router-dom";
|
||||
import { GitBranch, LogOut } from "lucide-react";
|
||||
|
||||
export default function Layout() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const logout = async () => {
|
||||
await fetch("/api/auth/logout", { method: "POST" });
|
||||
navigate("/login", { replace: true });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-950 text-gray-100">
|
||||
<header className="border-b border-gray-800 px-6 py-4 flex items-center gap-3">
|
||||
<GitBranch className="w-5 h-5 text-blue-400" />
|
||||
<NavLink
|
||||
to="/projects"
|
||||
className="text-lg font-semibold tracking-tight"
|
||||
<header className="border-b border-gray-800 px-6 py-4 flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<GitBranch className="w-5 h-5 text-blue-400" />
|
||||
<NavLink to="/projects" className="text-lg font-semibold tracking-tight">
|
||||
Paul CI/CD
|
||||
</NavLink>
|
||||
</div>
|
||||
<button
|
||||
onClick={logout}
|
||||
className="flex items-center gap-1.5 text-xs text-gray-400 hover:text-gray-200 transition-colors"
|
||||
>
|
||||
Paul CI/CD
|
||||
</NavLink>
|
||||
<LogOut className="w-3.5 h-3.5" />
|
||||
Logout
|
||||
</button>
|
||||
</header>
|
||||
<main className="max-w-5xl mx-auto px-6 py-8">
|
||||
<Outlet />
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { GitBranch } from "lucide-react";
|
||||
|
||||
export default function Login() {
|
||||
const navigate = useNavigate();
|
||||
const [form, setForm] = useState({ username: "", password: "" });
|
||||
const [error, setError] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const submit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError("");
|
||||
setLoading(true);
|
||||
const res = await fetch("/api/auth/login", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(form),
|
||||
});
|
||||
setLoading(false);
|
||||
if (res.ok) {
|
||||
navigate("/projects", { replace: true });
|
||||
} else {
|
||||
const data = await res.json();
|
||||
setError(data.error || "Login failed");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-950 flex items-center justify-center px-4">
|
||||
<div className="w-full max-w-sm">
|
||||
<div className="flex items-center justify-center gap-2 mb-8">
|
||||
<GitBranch className="w-6 h-6 text-blue-400" />
|
||||
<span className="text-xl font-semibold text-gray-100">Paul CI/CD</span>
|
||||
</div>
|
||||
|
||||
<form
|
||||
onSubmit={submit}
|
||||
className="bg-gray-900 text-white border border-gray-800 rounded-lg p-6 space-y-4"
|
||||
>
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs text-gray-400">Username</label>
|
||||
<input
|
||||
autoFocus
|
||||
value={form.username}
|
||||
onChange={(e) => setForm((f) => ({ ...f, username: e.target.value }))}
|
||||
className="w-full bg-gray-800 border border-gray-700 rounded-md px-3 py-2 text-sm outline-none focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs text-gray-400">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
value={form.password}
|
||||
onChange={(e) => setForm((f) => ({ ...f, password: e.target.value }))}
|
||||
className="w-full bg-gray-800 border border-gray-700 rounded-md px-3 py-2 text-sm outline-none focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-400">{error}</p>}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full bg-blue-600 hover:bg-blue-500 disabled:opacity-50 text-white text-sm py-2 rounded-md transition-colors"
|
||||
>
|
||||
{loading ? "Signing in..." : "Sign in"}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
{"root":["./src/app.tsx","./src/main.tsx","./src/components/layout.tsx","./src/pages/builddetail.tsx","./src/pages/projectdetail.tsx","./src/pages/projects.tsx"],"version":"5.9.3"}
|
||||
{"root":["./src/app.tsx","./src/main.tsx","./src/components/layout.tsx","./src/pages/builddetail.tsx","./src/pages/login.tsx","./src/pages/projectdetail.tsx","./src/pages/projects.tsx"],"version":"5.9.3"}
|
||||
Reference in New Issue
Block a user