yves.gugger 6b6ec01484 feat: Add missing features - Stripe payments, password reset, rate limiting, contact form, newsletter
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
2025-12-08 14:37:42 +01:00

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>
)
}