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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3 -2
View File
@@ -10,9 +10,10 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=JetBrains+Mono:wght@400;500;600;700&family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/gh/arooshbd2024/aCAPTCHA@main/api/captcha-v1.1.js" defer></script>
<title>Schichtplaner — Veranstaltungsschichtpläne</title>
<script type="module" crossorigin src="/assets/index-BnAMvh5e.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-5idyA2ig.css">
<script type="module" crossorigin src="/assets/index-BVGqcHIp.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-jfrwfPo3.css">
</head>
<body class="bg-paper text-main font-sans antialiased min-h-screen">
<div id="root"></div>
+1
View File
@@ -10,6 +10,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=JetBrains+Mono:wght@400;500;600;700&family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/gh/arooshbd2024/aCAPTCHA@main/api/captcha-v1.1.js" defer></script>
<title>Schichtplaner — Veranstaltungsschichtpläne</title>
</head>
<body class="bg-paper text-main font-sans antialiased min-h-screen">
+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>