feat: SMTP integration, dynamic app_settings for email, and email verification enforcement for tier upgrades

This commit is contained in:
Mohan Ki
2026-07-27 18:54:19 +05:30
parent 41e333b30e
commit b328b0429a
9 changed files with 273 additions and 2 deletions
+101
View File
@@ -0,0 +1,101 @@
'use client';
import { useEffect, useState, Suspense } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import api from '@/lib/api';
import Link from 'next/link';
function VerifyEmailContent() {
const router = useRouter();
const searchParams = useSearchParams();
const token = searchParams.get('token');
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
const [message, setMessage] = useState('Verifying your email address...');
useEffect(() => {
if (!token) {
setStatus('error');
setMessage('No verification token found. Please check your email link.');
return;
}
const verifyToken = async () => {
try {
await api.get(`/auth/verify-email/${token}`);
setStatus('success');
setMessage('Your email has been verified successfully!');
// Auto redirect to dashboard after 3 seconds
setTimeout(() => {
router.push('/dashboard');
}, 3000);
} catch (err: any) {
setStatus('error');
setMessage(err.response?.data?.error || 'Failed to verify email. The link may have expired.');
}
};
verifyToken();
}, [token, router]);
return (
<div className="min-h-screen bg-gray-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8 font-sans">
<div className="sm:mx-auto sm:w-full sm:max-w-md">
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
Email Verification
</h2>
</div>
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10 text-center">
{status === 'loading' && (
<div className="text-gray-500">
<svg className="animate-spin h-8 w-8 mx-auto mb-4 text-indigo-600" xmlns="http://www.w3.org/2000/svg" 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>
<p className="font-semibold text-lg">{message}</p>
</div>
)}
{status === 'success' && (
<div>
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100 mb-4">
<svg className="h-6 w-6 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
</svg>
</div>
<p className="font-bold text-gray-900 text-lg mb-2">{message}</p>
<p className="text-gray-500 text-sm mb-6">Redirecting to your dashboard...</p>
<Link href="/dashboard" className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Go to Dashboard
</Link>
</div>
)}
{status === 'error' && (
<div>
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-red-100 mb-4">
<svg className="h-6 w-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</div>
<p className="font-bold text-gray-900 text-lg mb-4">{message}</p>
<Link href="/dashboard" className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Back to Dashboard
</Link>
</div>
)}
</div>
</div>
</div>
);
}
export default function VerifyEmailPage() {
return (
<Suspense fallback={<div className="min-h-screen flex items-center justify-center">Loading...</div>}>
<VerifyEmailContent />
</Suspense>
)
}