feat(invite-system): implement user invite functionality with registration mode control
All checks were successful
Build & Push Docker Image / build (push) Successful in 6m24s
Build & Push Docker Image / build (release) Successful in 6m25s

This commit is contained in:
2026-03-01 12:53:45 +01:00
parent 8c39275615
commit df4666bb63
15 changed files with 516 additions and 38 deletions

View File

@@ -1,12 +1,15 @@
import { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
import { useLanguage } from '../contexts/LanguageContext';
import { Mail, Lock, User, ArrowRight, Loader2, CheckCircle } from 'lucide-react';
import { useBranding } from '../contexts/BrandingContext';
import { Mail, Lock, User, ArrowRight, Loader2, CheckCircle, ShieldAlert } from 'lucide-react';
import BrandLogo from '../components/BrandLogo';
import toast from 'react-hot-toast';
export default function Register() {
const [searchParams] = useSearchParams();
const inviteToken = searchParams.get('invite') || '';
const [username, setUsername] = useState('');
const [displayName, setDisplayName] = useState('');
const [email, setEmail] = useState('');
@@ -16,8 +19,12 @@ export default function Register() {
const [needsVerification, setNeedsVerification] = useState(false);
const { register } = useAuth();
const { t } = useLanguage();
const { registrationMode } = useBranding();
const navigate = useNavigate();
// Invite-only mode without a token → show blocked message
const isBlocked = registrationMode === 'invite' && !inviteToken;
const handleSubmit = async (e) => {
e.preventDefault();
@@ -33,7 +40,7 @@ export default function Register() {
setLoading(true);
try {
const result = await register(username, displayName, email, password);
const result = await register(username, displayName, email, password, inviteToken);
if (result?.needsVerification) {
setNeedsVerification(true);
toast.success(t('auth.verificationSent'));
@@ -77,6 +84,15 @@ export default function Register() {
{t('auth.login')}
</Link>
</div>
) : isBlocked ? (
<div className="text-center space-y-4">
<ShieldAlert size={48} className="mx-auto text-amber-400" />
<h2 className="text-2xl font-bold text-th-text">{t('auth.inviteOnly')}</h2>
<p className="text-th-text-s">{t('auth.inviteOnlyDesc')}</p>
<Link to="/login" className="btn-primary inline-flex items-center gap-2 mt-4">
{t('auth.login')}
</Link>
</div>
) : (
<>
<div className="mb-8">