Backend: - Add Stripe API endpoints (checkout, portal, webhook) in subscription.py - Add password reset (forgot-password, reset-password) in auth.py - Add email verification endpoints - Add rate limiting with slowapi - Add contact form and newsletter API (contact.py) - Add webhook endpoint for Stripe (webhooks.py) - Add NewsletterSubscriber model - Extend User model with password reset and email verification tokens - Extend email_service with new templates (password reset, verification, contact, newsletter) - Update env.example with all new environment variables Frontend: - Add /forgot-password page - Add /reset-password page with token handling - Add /verify-email page with auto-verification - Add forgot password link to login page - Connect contact form to API - Add API methods for all new endpoints Documentation: - Update README with new API endpoints - Update environment variables documentation - Update pages overview
129 lines
4.6 KiB
TypeScript
129 lines
4.6 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useEffect, Suspense } from 'react'
|
|
import { useSearchParams, useRouter } from 'next/navigation'
|
|
import { Header } from '@/components/Header'
|
|
import { Footer } from '@/components/Footer'
|
|
import { api } from '@/lib/api'
|
|
import { Mail, CheckCircle, AlertCircle, Loader2 } from 'lucide-react'
|
|
import Link from 'next/link'
|
|
|
|
function VerifyEmailContent() {
|
|
const searchParams = useSearchParams()
|
|
const router = useRouter()
|
|
const token = searchParams.get('token')
|
|
|
|
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading')
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
useEffect(() => {
|
|
if (!token) {
|
|
setStatus('error')
|
|
setError('Invalid or missing verification token.')
|
|
return
|
|
}
|
|
|
|
const verifyEmail = async () => {
|
|
try {
|
|
await api.verifyEmail(token)
|
|
setStatus('success')
|
|
|
|
// Redirect to login after 3 seconds
|
|
setTimeout(() => {
|
|
router.push('/login?verified=true')
|
|
}, 3000)
|
|
} catch (err: any) {
|
|
setStatus('error')
|
|
setError(err.message || 'Failed to verify email. The link may have expired.')
|
|
}
|
|
}
|
|
|
|
verifyEmail()
|
|
}, [token, router])
|
|
|
|
return (
|
|
<>
|
|
<Header />
|
|
<main className="min-h-screen flex items-center justify-center p-4 pt-24">
|
|
<div className="w-full max-w-md">
|
|
{status === 'loading' && (
|
|
<div className="bg-background-secondary/50 border border-border rounded-2xl p-8 text-center">
|
|
<div className="w-16 h-16 bg-accent/10 rounded-full flex items-center justify-center mx-auto mb-6">
|
|
<Loader2 className="w-8 h-8 text-accent animate-spin" />
|
|
</div>
|
|
<h1 className="text-display-sm font-bold text-foreground mb-4">
|
|
Verifying your email...
|
|
</h1>
|
|
<p className="text-foreground-muted">
|
|
Please wait while we verify your email address.
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
{status === 'success' && (
|
|
<div className="bg-background-secondary/50 border border-border rounded-2xl p-8 text-center">
|
|
<div className="w-16 h-16 bg-accent/10 rounded-full flex items-center justify-center mx-auto mb-6">
|
|
<CheckCircle className="w-8 h-8 text-accent" />
|
|
</div>
|
|
<h1 className="text-display-sm font-bold text-foreground mb-4">
|
|
Email verified!
|
|
</h1>
|
|
<p className="text-foreground-muted mb-6">
|
|
Your email has been verified successfully. Redirecting you to login...
|
|
</p>
|
|
<Link
|
|
href="/login"
|
|
className="inline-flex items-center justify-center px-6 py-3 bg-accent text-background font-medium rounded-xl hover:bg-accent-hover transition-all"
|
|
>
|
|
Go to login
|
|
</Link>
|
|
</div>
|
|
)}
|
|
|
|
{status === 'error' && (
|
|
<div className="bg-background-secondary/50 border border-border rounded-2xl p-8 text-center">
|
|
<div className="w-16 h-16 bg-danger/10 rounded-full flex items-center justify-center mx-auto mb-6">
|
|
<AlertCircle className="w-8 h-8 text-danger" />
|
|
</div>
|
|
<h1 className="text-display-sm font-bold text-foreground mb-4">
|
|
Verification failed
|
|
</h1>
|
|
<p className="text-foreground-muted mb-6">
|
|
{error}
|
|
</p>
|
|
<div className="space-y-3">
|
|
<Link
|
|
href="/login"
|
|
className="inline-flex items-center justify-center w-full px-6 py-3 bg-accent text-background font-medium rounded-xl hover:bg-accent-hover transition-all"
|
|
>
|
|
Go to login
|
|
</Link>
|
|
<button
|
|
onClick={() => window.location.reload()}
|
|
className="inline-flex items-center justify-center w-full px-6 py-3 bg-background-tertiary text-foreground font-medium rounded-xl hover:bg-background-secondary transition-all border border-border"
|
|
>
|
|
Try again
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</main>
|
|
<Footer />
|
|
</>
|
|
)
|
|
}
|
|
|
|
export default function VerifyEmailPage() {
|
|
return (
|
|
<Suspense fallback={
|
|
<main className="min-h-screen flex items-center justify-center">
|
|
<div className="animate-pulse text-foreground-muted">Loading...</div>
|
|
</main>
|
|
}>
|
|
<VerifyEmailContent />
|
|
</Suspense>
|
|
)
|
|
}
|
|
|