feat:admin
This commit is contained in:
272
apps/web/src/routes/login.tsx
Normal file
272
apps/web/src/routes/login.tsx
Normal file
@@ -0,0 +1,272 @@
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { orpc } from "@/utils/orpc";
|
||||
|
||||
export const Route = createFileRoute("/login")({
|
||||
component: LoginPage,
|
||||
});
|
||||
|
||||
function LoginPage() {
|
||||
const navigate = useNavigate();
|
||||
const [isSignup, setIsSignup] = useState(false);
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [name, setName] = useState("");
|
||||
|
||||
const sessionQuery = useQuery({
|
||||
queryKey: ["session"],
|
||||
queryFn: () => authClient.getSession(),
|
||||
});
|
||||
|
||||
const loginMutation = useMutation({
|
||||
mutationFn: async ({
|
||||
email,
|
||||
password,
|
||||
}: {
|
||||
email: string;
|
||||
password: string;
|
||||
}) => {
|
||||
const result = await authClient.signIn.email({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
if (result.error) {
|
||||
throw new Error(result.error.message);
|
||||
}
|
||||
return result.data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.success("Succesvol ingelogd!");
|
||||
// Check role and redirect
|
||||
authClient.getSession().then((session) => {
|
||||
const user = session.data?.user as { role?: string } | undefined;
|
||||
if (user?.role === "admin") {
|
||||
navigate({ to: "/admin" });
|
||||
} else {
|
||||
navigate({ to: "/" });
|
||||
}
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(`Login mislukt: ${error.message}`);
|
||||
},
|
||||
});
|
||||
|
||||
const signupMutation = useMutation({
|
||||
mutationFn: async ({
|
||||
email,
|
||||
password,
|
||||
name,
|
||||
}: {
|
||||
email: string;
|
||||
password: string;
|
||||
name: string;
|
||||
}) => {
|
||||
const result = await authClient.signUp.email({
|
||||
email,
|
||||
password,
|
||||
name,
|
||||
});
|
||||
if (result.error) {
|
||||
throw new Error(result.error.message);
|
||||
}
|
||||
return result.data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.success("Account aangemaakt! Wacht op goedkeuring van een admin.");
|
||||
setIsSignup(false);
|
||||
setName("");
|
||||
setEmail("");
|
||||
setPassword("");
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(`Registratie mislukt: ${error.message}`);
|
||||
},
|
||||
});
|
||||
|
||||
const requestAdminMutation = useMutation({
|
||||
...orpc.requestAdminAccess.mutationOptions(),
|
||||
onSuccess: () => {
|
||||
toast.success("Admin toegang aangevraagd!");
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(`Aanvraag mislukt: ${error.message}`);
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (isSignup) {
|
||||
signupMutation.mutate({ email, password, name });
|
||||
} else {
|
||||
loginMutation.mutate({ email, password });
|
||||
}
|
||||
};
|
||||
|
||||
const handleRequestAdmin = () => {
|
||||
requestAdminMutation.mutate();
|
||||
};
|
||||
|
||||
// If already logged in as admin, redirect
|
||||
if (sessionQuery.data?.data?.user) {
|
||||
const user = sessionQuery.data.data.user as { role?: string };
|
||||
if (user.role === "admin") {
|
||||
navigate({ to: "/admin" });
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const isLoggedIn = !!sessionQuery.data?.data?.user;
|
||||
const user = sessionQuery.data?.data?.user as
|
||||
| { role?: string; name?: string }
|
||||
| undefined;
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-[#214e51] px-4">
|
||||
<Card className="w-full max-w-md border-white/10 bg-white/5">
|
||||
<CardHeader className="text-center">
|
||||
<CardTitle className="font-['Intro',sans-serif] text-3xl text-white">
|
||||
{isLoggedIn
|
||||
? "Admin Toegang"
|
||||
: isSignup
|
||||
? "Account Aanmaken"
|
||||
: "Inloggen"}
|
||||
</CardTitle>
|
||||
<CardDescription className="text-white/60">
|
||||
{isLoggedIn
|
||||
? `Welkom, ${user?.name}`
|
||||
: isSignup
|
||||
? "Maak een account aan om toegang te krijgen"
|
||||
: "Log in om toegang te krijgen tot het admin dashboard"}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{isLoggedIn ? (
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-lg border border-yellow-500/30 bg-yellow-500/10 p-4">
|
||||
<p className="text-center text-yellow-200">
|
||||
Je bent ingelogd maar hebt geen admin toegang.
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleRequestAdmin}
|
||||
disabled={requestAdminMutation.isPending}
|
||||
className="w-full bg-white text-[#214e51] hover:bg-white/90"
|
||||
>
|
||||
{requestAdminMutation.isPending
|
||||
? "Bezig..."
|
||||
: "Vraag Admin Toegang Aan"}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => authClient.signOut()}
|
||||
variant="outline"
|
||||
className="w-full border-white/20 bg-transparent text-white hover:bg-white/10"
|
||||
>
|
||||
Uitloggen
|
||||
</Button>
|
||||
<Link
|
||||
to="/"
|
||||
className="block text-center text-sm text-white/60 hover:text-white"
|
||||
>
|
||||
← Terug naar website
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{isSignup && (
|
||||
<div>
|
||||
<label
|
||||
htmlFor="name"
|
||||
className="mb-2 block text-sm text-white/60"
|
||||
>
|
||||
Naam
|
||||
</label>
|
||||
<Input
|
||||
id="name"
|
||||
type="text"
|
||||
placeholder="Jouw naam"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
required
|
||||
className="border-white/20 bg-white/10 text-white placeholder:text-white/40"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="mb-2 block text-sm text-white/60"
|
||||
>
|
||||
Email
|
||||
</label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
placeholder="jouw@email.be"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
className="border-white/20 bg-white/10 text-white placeholder:text-white/40"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="password"
|
||||
className="mb-2 block text-sm text-white/60"
|
||||
>
|
||||
Wachtwoord
|
||||
</label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
placeholder="••••••••"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
className="border-white/20 bg-white/10 text-white placeholder:text-white/40"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={loginMutation.isPending || signupMutation.isPending}
|
||||
className="w-full bg-white text-[#214e51] hover:bg-white/90"
|
||||
>
|
||||
{loginMutation.isPending || signupMutation.isPending
|
||||
? "Bezig..."
|
||||
: isSignup
|
||||
? "Account Aanmaken"
|
||||
: "Inloggen"}
|
||||
</Button>
|
||||
<div className="flex flex-col gap-2 text-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsSignup(!isSignup)}
|
||||
className="text-sm text-white/60 hover:text-white"
|
||||
>
|
||||
{isSignup
|
||||
? "Al een account? Log in"
|
||||
: "Nog geen account? Registreer"}
|
||||
</button>
|
||||
<Link to="/" className="text-sm text-white/60 hover:text-white">
|
||||
← Terug naar website
|
||||
</Link>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user