Features: - Hybrid semantic + keyword quiz search (pgvector HNSW + PostgreSQL ILIKE) - AWS Bedrock Titan Embed V2 embeddings via LiteLLM proxy (0.71 cosine sim) - Multi-provider TTS: OpenAI, AWS Polly (neural), ElevenLabs, Google Cloud TTS - Unified Settings page (profile, theme, Nextcloud integration, admin shortcuts) - Good morning/afternoon greeting on dashboard - manage.py CLI: reset-password, list-users, reembed - Email verification enforced: register no longer returns JWT for unverified users - Quiz search with debounced input, semantic/keyword/title modes, highlighted snippets - TTS button: loading/playing states, voice selector locked during playback - TTS auto-stops when navigating between questions - Footer added; mobile quiz nav overflow fixed; markdown theme body selector fixed - OpenAI Alloy as default TTS voice; favicon added - SMTP configured via smtp2go; password reset rate limiting (3/hour) - PostgreSQL upgraded to pgvector/pgvector:pg16 Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
43 lines
1.9 KiB
JavaScript
43 lines
1.9 KiB
JavaScript
import { useState, useEffect } from 'react'
|
|
import { useSearchParams, Link } from 'react-router-dom'
|
|
import api from '../api/client'
|
|
|
|
export default function VerifyEmailPage() {
|
|
const [searchParams] = useSearchParams()
|
|
const token = searchParams.get('token')
|
|
const [status, setStatus] = useState('verifying') // verifying | success | error
|
|
const [message, setMessage] = useState('')
|
|
|
|
useEffect(() => {
|
|
if (!token) { setStatus('error'); setMessage('No verification token provided.'); return }
|
|
api.get(`/auth/verify-email?token=${token}`)
|
|
.then(res => { setStatus('success'); setMessage(res.data.message) })
|
|
.catch(err => { setStatus('error'); setMessage(err.response?.data?.detail || 'Verification failed.') })
|
|
}, [token])
|
|
|
|
return (
|
|
<div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', background: '#f1f5f9' }}>
|
|
<div className="card" style={{ maxWidth: 420, width: '100%', textAlign: 'center' }}>
|
|
{status === 'verifying' && (
|
|
<><div className="spinner" style={{ margin: '0 auto 16px' }}></div><p>Verifying your email...</p></>
|
|
)}
|
|
{status === 'success' && (
|
|
<>
|
|
<div style={{ fontSize: '3rem', marginBottom: 12 }}>✅</div>
|
|
<h2 style={{ color: '#166534', marginBottom: 8 }}>Email Verified!</h2>
|
|
<p style={{ color: '#64748b', marginBottom: 24 }}>{message}</p>
|
|
<Link to="/login" className="btn btn-primary">Log In</Link>
|
|
</>
|
|
)}
|
|
{status === 'error' && (
|
|
<>
|
|
<div style={{ fontSize: '3rem', marginBottom: 12 }}>❌</div>
|
|
<h2 style={{ color: '#dc2626', marginBottom: 8 }}>Verification Failed</h2>
|
|
<p style={{ color: '#64748b', marginBottom: 24 }}>{message}</p>
|
|
<Link to="/login" className="btn btn-secondary">Back to Login</Link>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|