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";
|
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;
|
||||||
|
|||||||
Reference in New Issue
Block a user