diff --git a/backend/api/views.py b/backend/api/views.py index 990b095..78e4af5 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -307,10 +307,12 @@ class UserViewSet(viewsets.ModelViewSet): email_confirmation_code=code ) + verification_link = f"{settings.FRONTEND_URL}/verify-email?username={username}&code={code}" + try: send_mail( 'Verify your Holy Flavors account', - f'Hi {username},\n\nYour verification code is: {code}', + f'Hi {username},\n\nYour verification code is: {code}\n\nAlternatively, you can complete your registration by clicking the link below:\n{verification_link}\n\nWelcome to the archive!', settings.DEFAULT_FROM_EMAIL, [email], fail_silently=False, @@ -342,10 +344,12 @@ class UserViewSet(viewsets.ModelViewSet): user.email_confirmation_code = code user.save() + verification_link = f"{settings.FRONTEND_URL}/verify-email?username={user.username}&code={code}" + try: send_mail( 'Verify your Holy Flavors account', - f'Hi {user.username},\n\nYour new verification code is: {code}', + f'Hi {user.username},\n\nYour new verification code is: {code}\n\nAlternatively, you can complete your registration by clicking the link below:\n{verification_link}', settings.DEFAULT_FROM_EMAIL, [user.email], fail_silently=False, diff --git a/backend/holy_backend/settings.py b/backend/holy_backend/settings.py index f5168d5..7ec54cf 100644 --- a/backend/holy_backend/settings.py +++ b/backend/holy_backend/settings.py @@ -191,6 +191,8 @@ REST_FRAMEWORK = { CORS_ALLOWED_ORIGINS_STRING = os.environ.get('CORS_ALLOWED_ORIGINS', 'https://holy.narl.io,http://localhost:5173,http://127.0.0.1:5173') CORS_ALLOWED_ORIGINS = [origin.strip() for origin in CORS_ALLOWED_ORIGINS_STRING.split(',')] +FRONTEND_URL = os.environ.get('FRONTEND_URL', 'https://holy.narl.io').rstrip('/') + import os # ... (near top of file) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 72842b4..1f511a9 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -55,6 +55,7 @@ const Privacy = lazy(() => import('./pages/Privacy')); const Login = lazy(() => import('./pages/Login')); const Settings = lazy(() => import('./pages/Settings')); const Support = lazy(() => import('./pages/Support')); +const VerifyEmail = lazy(() => import('./pages/VerifyEmail')); const AdminDashboard = lazy(() => import('./pages/AdminDashboard')); const AdminUserDetail = lazy(() => import('./pages/AdminUserDetail')); const AdminRatingDetail = lazy(() => import('./pages/AdminRatingDetail')); @@ -696,6 +697,7 @@ const App: React.FC = () => { } /> } /> } /> + } /> } /> } /> } /> diff --git a/frontend/src/pages/VerifyEmail.tsx b/frontend/src/pages/VerifyEmail.tsx new file mode 100644 index 0000000..d61b90d --- /dev/null +++ b/frontend/src/pages/VerifyEmail.tsx @@ -0,0 +1,99 @@ +import React, { useEffect, useState } from 'react'; +import { useLocation, useNavigate, Link } from 'react-router-dom'; +import { Container, Paper, Typography, Box, CircularProgress, Button, Alert } from '@mui/material'; +import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; +import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; +import { useTranslation } from 'react-i18next'; +import api from '../api'; +import { useTitle } from '../hooks/useTitle'; + +const VerifyEmail: React.FC = () => { + const { t } = useTranslation(); + const location = useLocation(); + const navigate = useNavigate(); + const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading'); + const [errorMsg, setErrorMsg] = useState(''); + + useTitle(t('auth.verifyTitle')); + + useEffect(() => { + const query = new URLSearchParams(location.search); + const username = query.get('username'); + const code = query.get('code'); + + if (!username || !code) { + setStatus('error'); + setErrorMsg('Invalid verification link.'); + return; + } + + const verify = async () => { + try { + await api.post('users/verify_signup/', { username, code }); + setStatus('success'); + // Optional: Auto redirect after 3 seconds + setTimeout(() => { + navigate('/login'); + }, 3000); + } catch (err: any) { + setStatus('error'); + setErrorMsg(err.response?.data?.error || 'Verification failed.'); + } + }; + + verify(); + }, [location, navigate]); + + return ( + + + {status === 'loading' && ( + + + + Verifying your account... + + + Please wait while we confirm your email. + + + )} + + {status === 'success' && ( + + + + {t('auth.verifySuccess')} + + + You will be redirected to the login page shortly. + + + + )} + + {status === 'error' && ( + + + + Verification Failed + + + {errorMsg} + + + The link might be expired or already used. + + + + )} + + + ); +}; + +export default VerifyEmail;