Add mail verification and use .env insteads of environment in compose
Some checks failed
Build & Push Docker Image / build (push) Has been cancelled
Some checks failed
Build & Push Docker Image / build (push) Has been cancelled
This commit is contained in:
@@ -8,6 +8,7 @@ import ProtectedRoute from './components/ProtectedRoute';
|
||||
import Home from './pages/Home';
|
||||
import Login from './pages/Login';
|
||||
import Register from './pages/Register';
|
||||
import VerifyEmail from './pages/VerifyEmail';
|
||||
import Dashboard from './pages/Dashboard';
|
||||
import RoomDetail from './pages/RoomDetail';
|
||||
import Settings from './pages/Settings';
|
||||
@@ -45,6 +46,7 @@ export default function App() {
|
||||
<Route path="/" element={user ? <Navigate to="/dashboard" /> : <Home />} />
|
||||
<Route path="/login" element={user ? <Navigate to="/dashboard" /> : <Login />} />
|
||||
<Route path="/register" element={user ? <Navigate to="/dashboard" /> : <Register />} />
|
||||
<Route path="/verify-email" element={<VerifyEmail />} />
|
||||
<Route path="/join/:uid" element={<GuestJoin />} />
|
||||
|
||||
{/* Protected routes */}
|
||||
|
||||
@@ -30,6 +30,9 @@ export function AuthProvider({ children }) {
|
||||
|
||||
const register = useCallback(async (name, email, password) => {
|
||||
const res = await api.post('/auth/register', { name, email, password });
|
||||
if (res.data.needsVerification) {
|
||||
return { needsVerification: true };
|
||||
}
|
||||
localStorage.setItem('token', res.data.token);
|
||||
setUser(res.data.user);
|
||||
return res.data.user;
|
||||
|
||||
@@ -61,7 +61,17 @@
|
||||
"registerSuccess": "Registrierung erfolgreich!",
|
||||
"loginFailed": "Anmeldung fehlgeschlagen",
|
||||
"registerFailed": "Registrierung fehlgeschlagen",
|
||||
"allFieldsRequired": "Alle Felder sind erforderlich"
|
||||
"allFieldsRequired": "Alle Felder sind erforderlich",
|
||||
"verificationSent": "Verifizierungs-E-Mail wurde gesendet!",
|
||||
"verificationSentDesc": "Wir haben dir eine E-Mail mit einem Bestätigungslink geschickt. Bitte klicke auf den Link, um dein Konto zu aktivieren.",
|
||||
"checkYourEmail": "Prüfe dein Postfach",
|
||||
"verifying": "E-Mail wird verifiziert...",
|
||||
"verifySuccess": "Deine E-Mail-Adresse wurde erfolgreich bestätigt. Du kannst dich jetzt anmelden.",
|
||||
"verifySuccessTitle": "E-Mail bestätigt!",
|
||||
"verifyFailed": "Verifizierung fehlgeschlagen",
|
||||
"verifyFailedTitle": "Verifizierung fehlgeschlagen",
|
||||
"verifyTokenMissing": "Kein Verifizierungstoken vorhanden.",
|
||||
"emailNotVerified": "E-Mail-Adresse noch nicht verifiziert. Bitte prüfe dein Postfach."
|
||||
},
|
||||
"home": {
|
||||
"poweredBy": "Powered by BigBlueButton",
|
||||
|
||||
@@ -61,7 +61,17 @@
|
||||
"registerSuccess": "Registration successful!",
|
||||
"loginFailed": "Login failed",
|
||||
"registerFailed": "Registration failed",
|
||||
"allFieldsRequired": "All fields are required"
|
||||
"allFieldsRequired": "All fields are required",
|
||||
"verificationSent": "Verification email sent!",
|
||||
"verificationSentDesc": "We've sent you an email with a verification link. Please click the link to activate your account.",
|
||||
"checkYourEmail": "Check your inbox",
|
||||
"verifying": "Verifying your email...",
|
||||
"verifySuccess": "Your email has been verified successfully. You can now sign in.",
|
||||
"verifySuccessTitle": "Email verified!",
|
||||
"verifyFailed": "Verification failed",
|
||||
"verifyFailedTitle": "Verification failed",
|
||||
"verifyTokenMissing": "No verification token provided.",
|
||||
"emailNotVerified": "Email not yet verified. Please check your inbox."
|
||||
},
|
||||
"home": {
|
||||
"poweredBy": "Powered by BigBlueButton",
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useState } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { useLanguage } from '../contexts/LanguageContext';
|
||||
import { Mail, Lock, User, ArrowRight, Loader2 } from 'lucide-react';
|
||||
import { Mail, Lock, User, ArrowRight, Loader2, CheckCircle } from 'lucide-react';
|
||||
import BrandLogo from '../components/BrandLogo';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
@@ -12,6 +12,7 @@ export default function Register() {
|
||||
const [password, setPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [needsVerification, setNeedsVerification] = useState(false);
|
||||
const { register } = useAuth();
|
||||
const { t } = useLanguage();
|
||||
const navigate = useNavigate();
|
||||
@@ -31,9 +32,14 @@ export default function Register() {
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
await register(name, email, password);
|
||||
toast.success(t('auth.registerSuccess'));
|
||||
navigate('/dashboard');
|
||||
const result = await register(name, email, password);
|
||||
if (result?.needsVerification) {
|
||||
setNeedsVerification(true);
|
||||
toast.success(t('auth.verificationSent'));
|
||||
} else {
|
||||
toast.success(t('auth.registerSuccess'));
|
||||
navigate('/dashboard');
|
||||
}
|
||||
} catch (err) {
|
||||
toast.error(err.response?.data?.error || t('auth.registerFailed'));
|
||||
} finally {
|
||||
@@ -60,6 +66,18 @@ export default function Register() {
|
||||
<BrandLogo size="lg" />
|
||||
</div>
|
||||
|
||||
{needsVerification ? (
|
||||
<div className="text-center space-y-4">
|
||||
<CheckCircle size={48} className="mx-auto text-green-400" />
|
||||
<h2 className="text-2xl font-bold text-th-text">{t('auth.checkYourEmail')}</h2>
|
||||
<p className="text-th-text-s">{t('auth.verificationSentDesc')}</p>
|
||||
<p className="text-sm text-th-text-s font-medium">{email}</p>
|
||||
<Link to="/login" className="btn-primary inline-flex items-center gap-2 mt-4">
|
||||
{t('auth.login')}
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="mb-8">
|
||||
<h2 className="text-2xl font-bold text-th-text mb-2">{t('auth.createAccount')}</h2>
|
||||
<p className="text-th-text-s">
|
||||
@@ -156,6 +174,8 @@ export default function Register() {
|
||||
<Link to="/" className="block mt-4 text-center text-sm text-th-text-s hover:text-th-text transition-colors">
|
||||
{t('auth.backToHome')}
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
87
src/pages/VerifyEmail.jsx
Normal file
87
src/pages/VerifyEmail.jsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Link, useSearchParams } from 'react-router-dom';
|
||||
import { useLanguage } from '../contexts/LanguageContext';
|
||||
import { CheckCircle, XCircle, Loader2, Mail } from 'lucide-react';
|
||||
import BrandLogo from '../components/BrandLogo';
|
||||
import api from '../services/api';
|
||||
|
||||
export default function VerifyEmail() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const token = searchParams.get('token');
|
||||
const { t } = useLanguage();
|
||||
|
||||
const [status, setStatus] = useState('loading'); // loading | success | error
|
||||
const [message, setMessage] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) {
|
||||
setStatus('error');
|
||||
setMessage(t('auth.verifyTokenMissing'));
|
||||
return;
|
||||
}
|
||||
|
||||
api.get(`/auth/verify-email?token=${token}`)
|
||||
.then(() => {
|
||||
setStatus('success');
|
||||
setMessage(t('auth.verifySuccess'));
|
||||
})
|
||||
.catch(err => {
|
||||
setStatus('error');
|
||||
setMessage(err.response?.data?.error || t('auth.verifyFailed'));
|
||||
});
|
||||
}, [token]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center p-6 relative overflow-hidden">
|
||||
{/* Animated background */}
|
||||
<div className="absolute inset-0 bg-th-bg">
|
||||
<div className="absolute inset-0 opacity-30">
|
||||
<div className="absolute top-1/4 left-1/4 w-96 h-96 bg-th-accent rounded-full blur-[128px] animate-pulse" />
|
||||
<div className="absolute bottom-1/4 right-1/4 w-80 h-80 bg-purple-500 rounded-full blur-[128px] animate-pulse" style={{ animationDelay: '2s' }} />
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-64 h-64 bg-pink-500 rounded-full blur-[128px] animate-pulse" style={{ animationDelay: '4s' }} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative w-full max-w-md">
|
||||
<div className="card p-8 backdrop-blur-xl bg-th-card/80 border border-th-border shadow-2xl rounded-2xl text-center">
|
||||
<div className="flex justify-center mb-8">
|
||||
<BrandLogo size="lg" />
|
||||
</div>
|
||||
|
||||
{status === 'loading' && (
|
||||
<div className="space-y-4">
|
||||
<Loader2 size={48} className="mx-auto animate-spin text-th-accent" />
|
||||
<p className="text-th-text">{t('auth.verifying')}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{status === 'success' && (
|
||||
<div className="space-y-4">
|
||||
<CheckCircle size={48} className="mx-auto text-green-400" />
|
||||
<h2 className="text-2xl font-bold text-th-text">{t('auth.verifySuccessTitle')}</h2>
|
||||
<p className="text-th-text-s">{message}</p>
|
||||
<Link to="/login" className="btn-primary inline-flex items-center gap-2 mt-4">
|
||||
{t('auth.login')}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{status === 'error' && (
|
||||
<div className="space-y-4">
|
||||
<XCircle size={48} className="mx-auto text-red-400" />
|
||||
<h2 className="text-2xl font-bold text-th-text">{t('auth.verifyFailedTitle')}</h2>
|
||||
<p className="text-th-text-s">{message}</p>
|
||||
<Link to="/register" className="btn-primary inline-flex items-center gap-2 mt-4">
|
||||
{t('auth.register')}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Link to="/" className="block mt-6 text-center text-sm text-th-text-s hover:text-th-text transition-colors">
|
||||
{t('auth.backToHome')}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user