feat: add login page with split layout matching Stirling-PDF style

This commit is contained in:
Siddharth Kumar Sah
2026-03-22 03:05:42 +08:00
parent e8dfd2e628
commit b7900ed4ef
2 changed files with 95 additions and 0 deletions
+4
View File
@@ -1,10 +1,14 @@
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { HomePage } from "./pages/home-page";
import { LoginPage } from "./pages/login-page";
import { ToolPage } from "./pages/tool-page";
export function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/:toolId" element={<ToolPage />} />
<Route path="/" element={<HomePage />} />
</Routes>
</BrowserRouter>
+91
View File
@@ -0,0 +1,91 @@
import { useState, type FormEvent } from "react";
import { useNavigate } from "react-router-dom";
import { setToken } from "@/lib/api";
export function LoginPage() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
const navigate = useNavigate();
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
setLoading(true);
setError("");
try {
const res = await fetch("/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password }),
});
if (!res.ok) {
const data = await res.json().catch(() => ({}));
setError(data.error || "Invalid username or password");
return;
}
const data = await res.json();
setToken(data.token);
navigate("/");
} catch {
setError("Connection error");
} finally {
setLoading(false);
}
};
return (
<div className="flex h-screen bg-background">
<div className="flex-1 flex items-center justify-center p-8">
<div className="w-full max-w-md space-y-8">
<div>
<h1 className="text-3xl font-bold text-foreground">
Stirling <span className="text-primary">Image</span>
</h1>
<h2 className="text-2xl font-bold mt-4 text-foreground">Login</h2>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium mb-1 text-foreground">Username</label>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="Enter username"
className="w-full px-4 py-3 rounded-lg border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20"
required
/>
</div>
<div>
<label className="block text-sm font-medium mb-1 text-foreground">Password</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Enter your password"
className="w-full px-4 py-3 rounded-lg border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20"
required
/>
</div>
{error && <p className="text-sm text-destructive">{error}</p>}
<button
type="submit"
disabled={loading || !username || !password}
className="w-full py-3 rounded-lg bg-primary/80 text-primary-foreground font-medium hover:bg-primary transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{loading ? "Logging in..." : "Login"}
</button>
</form>
</div>
</div>
<div className="hidden lg:flex flex-1 bg-primary/90 items-center justify-center p-12 text-white rounded-l-3xl">
<div className="max-w-lg space-y-6 text-center">
<h2 className="text-3xl font-bold">Your one-stop-shop for all your image needs.</h2>
<p className="text-lg text-white/80">
A privacy-first image suite that lets you resize, compress, convert, and process images with 30+ powerful tools.
</p>
</div>
</div>
</div>
);
}