feat: add email verification page and allow public access via middleware.
This commit is contained in:
136
src/app/verify-email/page.tsx
Normal file
136
src/app/verify-email/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { auth } from "@/auth";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
// 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) => {
|
||||
const { nextUrl } = req;
|
||||
|
||||
Reference in New Issue
Block a user