118 lines
4.7 KiB
TypeScript
118 lines
4.7 KiB
TypeScript
import { useState, useEffect } from "react";
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Button } from "@/components/ui/button";
|
|
import { CheckCircle2, XCircle, Loader2, Mail } from "lucide-react";
|
|
|
|
export default function VerifyPage() {
|
|
const [status, setStatus] = useState<"loading" | "success" | "error">("loading");
|
|
const [message, setMessage] = useState("");
|
|
|
|
useEffect(() => {
|
|
const params = new URLSearchParams(window.location.search);
|
|
const token = params.get("token");
|
|
|
|
if (!token) {
|
|
setStatus("error");
|
|
setMessage("No verification token provided.");
|
|
return;
|
|
}
|
|
|
|
fetch(`/api/auth/verify/${token}`)
|
|
.then(async (res) => {
|
|
const data = await res.json();
|
|
if (!res.ok) {
|
|
throw new Error(data.error || "Verification failed");
|
|
}
|
|
return data;
|
|
})
|
|
.then(() => {
|
|
setStatus("success");
|
|
setMessage("Email verified successfully!");
|
|
// Auto-login set by server via session cookie.
|
|
// Redirect to dashboard using full page navigation (not wouter hash routing)
|
|
// so the auth context picks up the new session cookie.
|
|
setTimeout(() => {
|
|
window.location.href = "/#/";
|
|
}, 2000);
|
|
})
|
|
.catch((err) => {
|
|
setStatus("error");
|
|
setMessage(err.message || "Verification failed.");
|
|
});
|
|
}, []);
|
|
|
|
return (
|
|
<div className="flex min-h-screen items-center justify-center bg-background px-4">
|
|
<div className="absolute inset-0 bg-gradient-to-b from-primary/5 via-transparent to-transparent pointer-events-none" />
|
|
<div className="relative w-full max-w-md">
|
|
<div className="mb-8 text-center">
|
|
<div className="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary/10 border border-primary/20 mb-4">
|
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none">
|
|
<path d="M16 4L4 16L16 28L28 16L16 4Z" fill="hsl(130 70% 50%)" fillOpacity="0.2" />
|
|
<path d="M16 8L8 16L16 24L24 16L16 8Z" fill="hsl(130 70% 50%)" />
|
|
<text x="16" y="19" textAnchor="middle" fill="white" fontSize="10" fontWeight="bold">G</text>
|
|
</svg>
|
|
</div>
|
|
<h1 className="text-2xl font-bold tracking-tight text-foreground">GammaDesk</h1>
|
|
</div>
|
|
|
|
<Card className="border-border bg-card/50 backdrop-blur-sm shadow-xl">
|
|
<CardHeader className="pb-4 text-center">
|
|
{status === "loading" && (
|
|
<>
|
|
<div className="mx-auto w-14 h-14 rounded-full bg-primary/10 flex items-center justify-center mb-2">
|
|
<Loader2 className="h-7 w-7 text-primary animate-spin" />
|
|
</div>
|
|
<CardTitle className="text-xl font-semibold">Verifying...</CardTitle>
|
|
<CardDescription className="text-sm text-muted-foreground">
|
|
Please wait while we verify your email
|
|
</CardDescription>
|
|
</>
|
|
)}
|
|
{status === "success" && (
|
|
<>
|
|
<div className="mx-auto w-14 h-14 rounded-full bg-primary/10 flex items-center justify-center mb-2">
|
|
<CheckCircle2 className="h-7 w-7 text-primary" />
|
|
</div>
|
|
<CardTitle className="text-xl font-semibold">Account Verified!</CardTitle>
|
|
<CardDescription className="text-sm text-muted-foreground">
|
|
{message}
|
|
</CardDescription>
|
|
</>
|
|
)}
|
|
{status === "error" && (
|
|
<>
|
|
<div className="mx-auto w-14 h-14 rounded-full bg-destructive/10 flex items-center justify-center mb-2">
|
|
<XCircle className="h-7 w-7 text-destructive" />
|
|
</div>
|
|
<CardTitle className="text-xl font-semibold">Verification Failed</CardTitle>
|
|
<CardDescription className="text-sm text-muted-foreground">
|
|
{message}
|
|
</CardDescription>
|
|
</>
|
|
)}
|
|
</CardHeader>
|
|
<CardContent className="flex flex-col gap-4">
|
|
{status === "success" && (
|
|
<div className="text-center text-sm text-muted-foreground">
|
|
Redirecting to dashboard...
|
|
</div>
|
|
)}
|
|
{status === "error" && (
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => {
|
|
window.location.href = "/#/login";
|
|
}}
|
|
className="w-full h-12"
|
|
>
|
|
← Back to Sign In
|
|
</Button>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|