fix: resolve invitation link expiration by adding verification landing page
All checks were successful
Staging Build / build (push) Successful in 3m35s
All checks were successful
Staging Build / build (push) Successful in 3m35s
This commit is contained in:
76
shop/app/auth/verify-link/page.tsx
Normal file
76
shop/app/auth/verify-link/page.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
'use client';
|
||||
|
||||
import { use, useState } from "react";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ShieldCheck, Loader2 } from "lucide-react";
|
||||
|
||||
interface PageProps {
|
||||
searchParams: Promise<{
|
||||
token_hash?: string;
|
||||
type?: string;
|
||||
next?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export default function VerifyLinkPage({ searchParams }: PageProps) {
|
||||
const params = use(searchParams);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const tokenHash = params.token_hash || "";
|
||||
const type = params.type || "";
|
||||
const next = params.next || "/";
|
||||
|
||||
const handleSubmit = () => {
|
||||
setIsSubmitting(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative flex min-h-svh w-full items-center justify-center p-6 md:p-10 overflow-hidden bg-slate-950">
|
||||
{/* Background gradients for rich aesthetics */}
|
||||
<div className="absolute inset-0 bg-[linear-gradient(to_right,#0f172a_1px,transparent_1px),linear-gradient(to_bottom,#0f172a_1px,transparent_1px)] bg-[size:4rem_4rem] [mask-image:radial-gradient(ellipse_60%_50%_at_50%_0%,#000_70%,transparent_100%)] opacity-60" />
|
||||
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-[500px] h-[500px] bg-blue-500/10 rounded-full blur-[120px] pointer-events-none" />
|
||||
<div className="absolute bottom-0 right-1/4 w-[400px] h-[400px] bg-indigo-500/10 rounded-full blur-[100px] pointer-events-none" />
|
||||
|
||||
<div className="relative w-full max-w-md z-10">
|
||||
<Card className="border-slate-800/80 bg-slate-900/60 backdrop-blur-xl shadow-2xl transition-all duration-300 hover:border-slate-700/80">
|
||||
<CardHeader className="flex flex-col items-center justify-center text-center space-y-4 pt-8">
|
||||
<div className="p-3.5 bg-blue-500/10 rounded-full text-blue-400 border border-blue-500/20 shadow-[0_0_20px_rgba(59,130,246,0.15)] animate-pulse">
|
||||
<ShieldCheck className="w-8 h-8" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<CardTitle className="text-2xl font-bold tracking-tight text-slate-100">
|
||||
Sicherheit prüfen
|
||||
</CardTitle>
|
||||
<p className="text-sm text-slate-400 max-w-xs mx-auto">
|
||||
Bitte bestätigen Sie den Vorgang mit einem Klick auf den Button, um fortzufahren.
|
||||
</p>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pb-8">
|
||||
<form action="/auth/confirm" method="GET" onSubmit={handleSubmit} className="w-full">
|
||||
<input type="hidden" name="token_hash" value={tokenHash} />
|
||||
<input type="hidden" name="type" value={type} />
|
||||
<input type="hidden" name="next" value={next} />
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isSubmitting || !tokenHash}
|
||||
className="w-full bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 text-white font-medium shadow-[0_0_20px_rgba(59,130,246,0.25)] transition-all duration-200 h-11 active:scale-[0.98] disabled:opacity-50"
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Wird weitergeleitet...
|
||||
</>
|
||||
) : (
|
||||
"Bestätigen & Fortfahren"
|
||||
)}
|
||||
</Button>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -84,7 +84,7 @@ export async function createUser(data: { email: string; role?: 'admin' | 'partne
|
||||
const tokenHash = (linkData as any)?.properties?.hashed_token;
|
||||
if (tokenHash) {
|
||||
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://staging.hephex.de';
|
||||
const setupLink = `${siteUrl}/auth/confirm?token_hash=${tokenHash}&type=recovery&next=/auth/update-password`;
|
||||
const setupLink = `${siteUrl}/auth/verify-link?token_hash=${tokenHash}&type=recovery&next=/auth/update-password`;
|
||||
await sendMail({
|
||||
to: data.email,
|
||||
subject: 'Willkommen bei CASPOS Shop – Ihr Benutzerkonto wurde erstellt',
|
||||
@@ -183,7 +183,7 @@ export async function resetPassword(email: string) {
|
||||
if (!tokenHash) throw new Error('Recovery token not generated');
|
||||
// Build link pointing to our own /auth/confirm route
|
||||
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://staging.hephex.de';
|
||||
const resetLink = `${siteUrl}/auth/confirm?token_hash=${tokenHash}&type=recovery&next=/auth/update-password`;
|
||||
const resetLink = `${siteUrl}/auth/verify-link?token_hash=${tokenHash}&type=recovery&next=/auth/update-password`;
|
||||
await sendMail({
|
||||
to: email,
|
||||
subject: 'Passwort zurücksetzen',
|
||||
|
||||
Reference in New Issue
Block a user