feat: add email verification page and allow public access via middleware.

This commit is contained in:
pradeepkumar
2025-12-22 12:50:28 +05:30
parent 5c6ab92056
commit 060643fed8
2 changed files with 137 additions and 1 deletions

View File

@@ -0,0 +1,136 @@
'use client';
import { Suspense, useEffect, useState } from 'react';
import { useSearchParams } from 'next/navigation';
import Link from 'next/link';
import { authService, AuthService } from '@/services/auth.service';
function VerifyEmailContent() {
const searchParams = useSearchParams();
const token = searchParams.get('token');
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
const [message, setMessage] = useState('');
useEffect(() => {
const verifyEmail = async () => {
if (!token) {
setStatus('error');
setMessage('Invalid verification link. No token provided.');
return;
}
try {
const response = await authService.verifyEmail({ token });
setStatus('success');
setMessage(response.data?.message || 'Your email has been verified successfully!');
} catch (error) {
setStatus('error');
const errorMsg = AuthService.getErrorMessage(error);
setMessage(errorMsg || 'Email verification failed. The link may have expired or is invalid.');
}
};
verifyEmail();
}, [token]);
return (
<div className="bg-white rounded-[20px] p-8 shadow-sm">
{status === 'loading' && (
<div className="text-center">
<div className="mb-6">
<svg className="w-16 h-16 mx-auto text-[#5ba4a4] animate-spin" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</div>
<h2 className="text-[24px] font-normal text-[#00293d] mb-3">Verifying Your Email</h2>
<p className="text-sm text-[#00293d]/70">Please wait while we verify your email address...</p>
</div>
)}
{status === 'success' && (
<div className="text-center">
<div className="mb-6">
<svg className="w-16 h-16 mx-auto text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<h2 className="text-[24px] font-normal text-[#00293d] mb-3">Email Verified!</h2>
<p className="text-sm text-[#00293d]/70 mb-6">{message}</p>
<Link
href="/login"
className="inline-block w-full py-4 bg-[#e58625] hover:bg-[#d47a1f] text-[#00293d] font-bold text-sm rounded-[20px] transition-colors text-center"
>
Continue to Login
</Link>
</div>
)}
{status === 'error' && (
<div className="text-center">
<div className="mb-6">
<svg className="w-16 h-16 mx-auto text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<h2 className="text-[24px] font-normal text-[#00293d] mb-3">Verification Failed</h2>
<p className="text-sm text-[#00293d]/70 mb-6">{message}</p>
<div className="space-y-3">
<Link
href="/login"
className="inline-block w-full py-4 bg-[#e58625] hover:bg-[#d47a1f] text-[#00293d] font-bold text-sm rounded-[20px] transition-colors text-center"
>
Go to Login
</Link>
<Link
href="/signup"
className="inline-block w-full py-4 bg-[#f0f5fc] hover:bg-[#e0e8f0] text-[#00293d] font-bold text-sm rounded-[20px] transition-colors text-center"
>
Sign Up Again
</Link>
</div>
</div>
)}
</div>
);
}
function LoadingFallback() {
return (
<div className="bg-white rounded-[20px] p-8 shadow-sm">
<div className="text-center">
<div className="mb-6">
<svg className="w-16 h-16 mx-auto text-[#5ba4a4] animate-spin" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</div>
<h2 className="text-[24px] font-normal text-[#00293d] mb-3">Loading...</h2>
<p className="text-sm text-[#00293d]/70">Please wait...</p>
</div>
</div>
);
}
export default function VerifyEmailPage() {
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-b from-[#c4d9d4] to-[#f0f5fc] py-12 px-4">
<div className="w-full max-w-[510px]">
{/* Logo */}
<div className="text-center mb-4">
<img
src="/assets/logo.svg"
alt="RE-QuestN"
className="h-8 mx-auto"
/>
</div>
{/* Content Card */}
<Suspense fallback={<LoadingFallback />}>
<VerifyEmailContent />
</Suspense>
</div>
</div>
);
}

View File

@@ -2,7 +2,7 @@ import { auth } from "@/auth";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
// Public routes that don't require authentication // Public routes that don't require authentication
const publicRoutes = ["/login", "/signup", "/forgot-password", "/reset-password"]; const publicRoutes = ["/login", "/signup", "/forgot-password", "/reset-password", "/verify-email"];
export default auth((req) => { export default auth((req) => {
const { nextUrl } = req; const { nextUrl } = req;