- Create admin-session lib with sign/verify helpers using HMAC-SHA256 - Add admin login API that checks hardcoded super/admin credentials and DB-stored admin users with bcrypt password comparison - Add admin logout API to clear session cookie - Add change-password API for admin self-service password changes - Create admin login page with Macedonian UI - Update middleware to protect /admin/* and /api/admin/* routes with admin session check, bypassing Clerk auth
79 lines
3.0 KiB
TypeScript
79 lines
3.0 KiB
TypeScript
"use client";
|
||
|
||
import { useState } from "react";
|
||
import { useRouter } from "next/navigation";
|
||
|
||
export default function AdminLoginPage() {
|
||
const router = useRouter();
|
||
const [username, setUsername] = useState("");
|
||
const [password, setPassword] = useState("");
|
||
const [error, setError] = useState("");
|
||
const [loading, setLoading] = useState(false);
|
||
|
||
const handleSubmit = async (e: React.FormEvent) => {
|
||
e.preventDefault();
|
||
setLoading(true);
|
||
setError("");
|
||
try {
|
||
const res = await fetch("/api/admin/login", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ username, password }),
|
||
});
|
||
const data = await res.json();
|
||
if (!res.ok) {
|
||
throw new Error(data.error || "Најавата не успеа");
|
||
}
|
||
router.push("/admin/dashboard");
|
||
} catch (err) {
|
||
setError(err instanceof Error ? err.message : "Најавата не успеа");
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="flex min-h-screen items-center justify-center bg-stone-100">
|
||
<div className="w-full max-w-sm rounded-lg bg-white p-8 shadow-sm">
|
||
<h1 className="mb-6 text-center text-xl font-semibold text-stone-900">Администрација</h1>
|
||
<form onSubmit={handleSubmit} className="space-y-4">
|
||
<div>
|
||
<label htmlFor="username" className="block text-sm font-medium text-stone-700">
|
||
Корисничко име
|
||
</label>
|
||
<input
|
||
id="username"
|
||
type="text"
|
||
value={username}
|
||
onChange={(e) => setUsername(e.target.value)}
|
||
className="mt-1 block w-full rounded-lg border border-stone-200 px-3 py-2.5 text-stone-900 placeholder:text-stone-400 focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary"
|
||
autoComplete="username"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label htmlFor="password" className="block text-sm font-medium text-stone-700">
|
||
Лозинка
|
||
</label>
|
||
<input
|
||
id="password"
|
||
type="password"
|
||
value={password}
|
||
onChange={(e) => setPassword(e.target.value)}
|
||
className="mt-1 block w-full rounded-lg border border-stone-200 px-3 py-2.5 text-stone-900 placeholder:text-stone-400 focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary"
|
||
autoComplete="current-password"
|
||
/>
|
||
</div>
|
||
{error && <p className="text-sm text-red-600">{error}</p>}
|
||
<button
|
||
type="submit"
|
||
disabled={loading || !username || !password}
|
||
className="w-full rounded-lg bg-primary px-6 py-2.5 text-sm font-medium text-white transition-colors hover:bg-primary-light disabled:cursor-not-allowed disabled:opacity-50"
|
||
>
|
||
{loading ? "Најавување..." : "Најави се"}
|
||
</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|