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;