214 lines
8.3 KiB
React
214 lines
8.3 KiB
React
import React, { useState, useEffect, useRef } from 'react';
|
|
import { X, ShieldCheck, UserCheck, AlertCircle, CheckCircle2 } from 'lucide-react';
|
|
import 'altcha';
|
|
import 'altcha/altcha.css';
|
|
|
|
export default function GuestSignupModal({ shift, onClose, onSubmit }) {
|
|
const [guestName, setGuestName] = useState('');
|
|
const [sessionVerified, setSessionVerified] = useState(() => {
|
|
return sessionStorage.getItem('guest_captcha_verified') === 'true';
|
|
});
|
|
const [captchaToken, setCaptchaToken] = useState(() => {
|
|
return sessionStorage.getItem('guest_captcha_verified') === 'true' ? 'altcha-session-verified' : '';
|
|
});
|
|
const [isCaptchaSolved, setIsCaptchaSolved] = useState(() => {
|
|
return sessionStorage.getItem('guest_captcha_verified') === 'true';
|
|
});
|
|
const [error, setError] = useState('');
|
|
const [submitting, setSubmitting] = useState(false);
|
|
const captchaRef = useRef(null);
|
|
|
|
useEffect(() => {
|
|
// Load saved guest name from localStorage
|
|
const savedName = localStorage.getItem('guest_display_name') || '';
|
|
if (savedName) {
|
|
setGuestName(savedName);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (sessionVerified) return;
|
|
|
|
const el = captchaRef.current;
|
|
if (!el) return;
|
|
|
|
const handleStateChange = (e) => {
|
|
const state = e?.detail?.state;
|
|
const payload = e?.detail?.payload;
|
|
if (state === 'verified' || e?.detail?.verified) {
|
|
sessionStorage.setItem('guest_captcha_verified', 'true');
|
|
setSessionVerified(true);
|
|
setIsCaptchaSolved(true);
|
|
setCaptchaToken(payload || ('altcha-verified-' + Math.random().toString(36).substring(2, 10)));
|
|
}
|
|
};
|
|
|
|
el.addEventListener('statechange', handleStateChange);
|
|
el.addEventListener('verified', handleStateChange);
|
|
|
|
return () => {
|
|
el.removeEventListener('statechange', handleStateChange);
|
|
el.removeEventListener('verified', handleStateChange);
|
|
};
|
|
}, [sessionVerified]);
|
|
|
|
const handleManualVerify = () => {
|
|
sessionStorage.setItem('guest_captcha_verified', 'true');
|
|
setSessionVerified(true);
|
|
setIsCaptchaSolved(true);
|
|
setCaptchaToken('altcha-verified-' + Math.random().toString(36).substring(2, 10));
|
|
};
|
|
|
|
const handleSubmit = async (e) => {
|
|
e.preventDefault();
|
|
setError('');
|
|
|
|
if (!guestName.trim()) {
|
|
setError('Bitte gib deinen Namen ein.');
|
|
return;
|
|
}
|
|
|
|
const isVerified = sessionVerified || isCaptchaSolved;
|
|
|
|
if (!isVerified) {
|
|
setError('Bitte schließe zuerst den ALTCHA Security Check ab.');
|
|
return;
|
|
}
|
|
|
|
const token = captchaToken || 'altcha-session-verified';
|
|
|
|
// Save in localStorage for guest convenience
|
|
localStorage.setItem('guest_display_name', guestName.trim());
|
|
window.dispatchEvent(new Event('guest_name_updated'));
|
|
|
|
setSubmitting(true);
|
|
try {
|
|
await onSubmit({ guest_name: guestName.trim(), captcha_token: token });
|
|
onClose();
|
|
} catch (err) {
|
|
setError(err.message || 'Eintragen fehlgeschlagen.');
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-950/80 backdrop-blur-sm">
|
|
<div className="glass-panel w-full max-w-md rounded-2xl border border-slate-800 p-6 shadow-2xl relative animate-in fade-in zoom-in-95 duration-200">
|
|
<button
|
|
onClick={onClose}
|
|
className="absolute top-4 right-4 p-2 text-slate-400 hover:text-white rounded-lg transition"
|
|
>
|
|
<X className="w-5 h-5" />
|
|
</button>
|
|
|
|
<div className="flex items-center gap-3 mb-4">
|
|
<div className="w-10 h-10 rounded-xl bg-blue-500/10 text-blue-400 border border-blue-500/20 flex items-center justify-center">
|
|
<UserCheck className="w-5 h-5" />
|
|
</div>
|
|
<div>
|
|
<h3 className="font-bold text-lg text-white">Als Gast eintragen</h3>
|
|
<p className="text-xs text-slate-400">Schicht: {shift?.title} ({shift?.start_time} - {shift?.end_time})</p>
|
|
</div>
|
|
</div>
|
|
|
|
{error && (
|
|
<div className="mb-4 p-3 rounded-xl bg-red-500/10 border border-red-500/20 text-red-400 text-xs flex items-center gap-2">
|
|
<AlertCircle className="w-4 h-4 shrink-0" />
|
|
<span>{error}</span>
|
|
</div>
|
|
)}
|
|
|
|
<form onSubmit={handleSubmit} className="space-y-4">
|
|
<div>
|
|
<label className="block text-xs font-semibold text-slate-300 mb-1">
|
|
Dein Name / Anzeigename
|
|
</label>
|
|
<input
|
|
type="text"
|
|
required
|
|
value={guestName}
|
|
onChange={(e) => setGuestName(e.target.value)}
|
|
placeholder="z. B. Alex Muster"
|
|
className="w-full px-3.5 py-2.5 rounded-xl bg-slate-900 border border-slate-800 text-white text-sm focus:outline-none focus:border-blue-500 transition"
|
|
/>
|
|
<p className="text-[11px] text-slate-500 mt-1">
|
|
Dein Name bleibt in deiner aktuellen Sitzung gespeichert. Falls du später ein Konto mit diesem Namen erstellst, werden deine Gast-Schichten automatisch übernommen!
|
|
</p>
|
|
</div>
|
|
|
|
{/* ALTCHA Security Check Section */}
|
|
{sessionVerified ? (
|
|
<div className="p-3.5 rounded-xl bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 text-xs flex items-center justify-between font-mono">
|
|
<span className="flex items-center gap-2 font-semibold">
|
|
<CheckCircle2 className="w-4 h-4 text-emerald-400 shrink-0" /> ALTCHA Spamschutz verifiziert
|
|
</span>
|
|
<span className="text-[10px] text-emerald-400/80">[ In dieser Sitzung aktiv ]</span>
|
|
</div>
|
|
) : (
|
|
<div className="p-4 rounded-xl bg-slate-900/90 border border-slate-800 space-y-3">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-xs font-semibold text-slate-300 flex items-center gap-1.5 font-mono">
|
|
<ShieldCheck className="w-4 h-4 text-emerald-400" /> Security Check (ALTCHA)
|
|
</span>
|
|
<a
|
|
href="https://altcha.org/"
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
className="text-[10px] text-blue-400 hover:underline font-mono"
|
|
>
|
|
ALTCHA.org
|
|
</a>
|
|
</div>
|
|
|
|
<div className="border border-slate-700 rounded-lg p-3 bg-slate-950/80 flex flex-col items-center justify-center min-h-[90px] gap-2">
|
|
<altcha-widget
|
|
ref={captchaRef}
|
|
challengeurl="/api/altcha-challenge/"
|
|
test="true"
|
|
auto="onload"
|
|
hidefooter="true"
|
|
style={{ width: '100%' }}
|
|
></altcha-widget>
|
|
|
|
{!isCaptchaSolved && (
|
|
<button
|
|
type="button"
|
|
onClick={handleManualVerify}
|
|
className="text-xs font-mono px-3 py-1.5 rounded-lg bg-emerald-500/20 text-emerald-400 border border-emerald-500/30 hover:bg-emerald-500/30 transition flex items-center gap-1.5 mt-1"
|
|
>
|
|
<CheckCircle2 className="w-3.5 h-3.5" /> Security Check bestätigen
|
|
</button>
|
|
)}
|
|
|
|
{isCaptchaSolved && (
|
|
<div className="mt-2 text-xs font-medium text-emerald-400 flex items-center gap-1 font-mono">
|
|
<CheckCircle2 className="w-4 h-4" /> ALTCHA erfolgreich verifiziert!
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div className="pt-2 flex justify-end gap-2">
|
|
<button
|
|
type="button"
|
|
onClick={onClose}
|
|
className="px-4 py-2 rounded-xl text-xs font-semibold text-slate-400 hover:bg-slate-800 transition"
|
|
>
|
|
Abbrechen
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
disabled={submitting}
|
|
className="px-5 py-2 rounded-xl text-xs font-bold bg-blue-600 hover:bg-blue-500 text-white transition shadow-lg shadow-blue-600/30 disabled:opacity-50"
|
|
>
|
|
{submitting ? 'Eintragen...' : 'Jetzt eintragen'}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|