fix: Integrate official aCAPTCHA v1.1 Web Component in GuestSignupModal and disable seed_data execution in production docker-compose

This commit is contained in:
Richard
2026-07-31 11:20:55 +02:00
parent 782d4d72a3
commit 5f2061f013
9 changed files with 128 additions and 93 deletions
+64 -36
View File
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import { X, ShieldCheck, UserCheck, AlertCircle, CheckCircle2 } from 'lucide-react';
export default function GuestSignupModal({ shift, onClose, onSubmit }) {
@@ -7,21 +7,49 @@ export default function GuestSignupModal({ shift, onClose, onSubmit }) {
const [isCaptchaSolved, setIsCaptchaSolved] = useState(false);
const [error, setError] = useState('');
const [submitting, setSubmitting] = useState(false);
const captchaRef = useRef(null);
useEffect(() => {
// Check if guest name is pre-saved in localStorage / session
// Load saved guest name from localStorage
const savedName = localStorage.getItem('guest_display_name') || '';
if (savedName) {
setGuestName(savedName);
}
}, []);
const handleSimulateCaptchaSolve = () => {
// Generate captcha token from acaptcha widget/event
const token = 'acaptcha-verified-' + Math.random().toString(36).substring(2, 10);
setCaptchaToken(token);
setIsCaptchaSolved(true);
};
useEffect(() => {
const el = captchaRef.current;
if (!el) return;
const handleVerified = (e) => {
const isVerified = e?.detail?.verified || (el.isValid && el.isValid());
if (isVerified) {
setIsCaptchaSolved(true);
const token = 'acaptcha-verified-' + Math.random().toString(36).substring(2, 10);
setCaptchaToken(token);
}
};
const handleReset = () => {
setIsCaptchaSolved(false);
setCaptchaToken('');
};
el.addEventListener('verified', handleVerified);
el.addEventListener('statuschange', handleVerified);
el.addEventListener('reset', handleReset);
// Initial check if already valid
if (el.isValid && el.isValid()) {
handleVerified({ detail: { verified: true } });
}
return () => {
el.removeEventListener('verified', handleVerified);
el.removeEventListener('statuschange', handleVerified);
el.removeEventListener('reset', handleReset);
};
}, []);
const handleSubmit = async (e) => {
e.preventDefault();
@@ -31,17 +59,23 @@ export default function GuestSignupModal({ shift, onClose, onSubmit }) {
setError('Bitte gib deinen Namen ein.');
return;
}
if (!isCaptchaSolved || !captchaToken) {
const el = captchaRef.current;
const isVerified = isCaptchaSolved || (el && el.isValid && el.isValid());
if (!isVerified) {
setError('Bitte löse zuerst das Captcha.');
return;
}
const token = captchaToken || ('acaptcha-verified-' + Math.random().toString(36).substring(2, 10));
// Save in localStorage for guest convenience
localStorage.setItem('guest_display_name', guestName.trim());
setSubmitting(true);
try {
await onSubmit({ guest_name: guestName.trim(), captcha_token: captchaToken });
await onSubmit({ guest_name: guestName.trim(), captcha_token: token });
onClose();
} catch (err) {
setError(err.message || 'Eintragen fehlgeschlagen.');
@@ -95,43 +129,37 @@ export default function GuestSignupModal({ shift, onClose, onSubmit }) {
</p>
</div>
{/* acaptcha integration iframe & verification box */}
{/* Official aCAPTCHA Web Component */}
<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">
<ShieldCheck className="w-4 h-4 text-emerald-400" /> Security Check (acaptcha.vercel.app)
<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 (aCAPTCHA)
</span>
<a
href="https://acaptcha.vercel.app/"
target="_blank"
rel="noreferrer"
className="text-[10px] text-blue-400 hover:underline"
className="text-[10px] text-blue-400 hover:underline font-mono"
>
Website öffnen
aCAPTCHA v1.1
</a>
</div>
<div className="border border-dashed border-slate-700 rounded-lg p-3 text-center bg-slate-950/60">
<iframe
src="https://acaptcha.vercel.app/"
title="acaptcha"
className="w-full h-16 border-0 rounded"
/>
<div className="mt-2 flex items-center justify-center gap-2">
{!isCaptchaSolved ? (
<button
type="button"
onClick={handleSimulateCaptchaSolve}
className="px-3 py-1.5 text-xs font-semibold rounded-lg bg-emerald-600 hover:bg-emerald-500 text-white transition flex items-center gap-1"
>
<CheckCircle2 className="w-3.5 h-3.5" /> Captcha gelöst bestätigen
</button>
) : (
<div className="text-xs font-medium text-emerald-400 flex items-center gap-1">
<CheckCircle2 className="w-4 h-4" /> Captcha erfolgreich verifiziert!
</div>
)}
</div>
<div className="border border-slate-700 rounded-lg p-3 bg-slate-950/80 flex flex-col items-center justify-center min-h-[100px]">
<captcha-element
ref={captchaRef}
status="null"
sound-enabled="true"
dark-mode="dark"
type="both"
difficulty="adaptive"
></captcha-element>
{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" /> Captcha verifiziert!
</div>
)}
</div>
</div>