diff --git a/assets/js/mine-seeker/components/CaptchaOverlay.jsx b/assets/js/mine-seeker/components/CaptchaOverlay.jsx new file mode 100644 index 0000000..ab63203 --- /dev/null +++ b/assets/js/mine-seeker/components/CaptchaOverlay.jsx @@ -0,0 +1,169 @@ +/** + * This file is part of the SplendidBear Websites' projects. + * + * Copyright (c) 2026 @ www.splendidbear.org + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +import React, { useEffect, useState } from 'react'; + +const CAPTCHA_STORAGE_KEY = 'mineseeker_captcha_verified'; +const CAPTCHA_TOKEN_KEY = 'mineseeker_captcha_token'; +const RECAPTCHA_ACTION = 'mineseeker_play'; + +const CaptchaOverlay = ({ siteKey, onVerified, children }) => { + const [verified, setVerified] = useState(false); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(false); + + useEffect(() => { + const storedToken = sessionStorage.getItem(CAPTCHA_TOKEN_KEY); + const storedTime = sessionStorage.getItem(CAPTCHA_STORAGE_KEY); + + if (storedToken && storedTime) { + const elapsed = (Date.now() - parseInt(storedTime)) / 1000; + if (110 > elapsed) { + const wrapper = document.getElementById('mine-wrapper'); + if (wrapper) { + wrapper.dataset.captchaToken = storedToken; + } + setVerified(true); + onVerified?.(); + return; + } + } + + if (window.grecaptcha) { + window.grecaptcha.ready(() => { + window.grecaptcha + .execute(siteKey, { action: RECAPTCHA_ACTION }) + .then(token => { + handleToken(token); + }) + .catch(() => { + setError(true); + }); + }); + } + }, [siteKey, onVerified]); + + const handleToken = token => { + const wrapper = document.getElementById('mine-wrapper'); + if (wrapper) { + wrapper.dataset.captchaToken = token; + } + sessionStorage.setItem(CAPTCHA_TOKEN_KEY, token); + sessionStorage.setItem(CAPTCHA_STORAGE_KEY, Date.now().toString()); + setVerified(true); + onVerified?.(); + }; + + const handleClick = () => { + setLoading(true); + setError(false); + + window.grecaptcha.ready(() => { + window.grecaptcha + .execute(siteKey, { action: RECAPTCHA_ACTION }) + .then(token => { + handleToken(token); + setLoading(false); + }) + .catch(() => { + setLoading(false); + setError(true); + setTimeout(() => setError(false), 2000); + }); + }); + }; + + if (verified) { + return <>{children}; + } + + const overlayStyles = { + position: 'fixed', + top: 0, + left: 0, + width: '100%', + height: '100%', + background: 'rgba(7, 9, 13, 0.95)', + backdropFilter: 'blur(8px)', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + zIndex: 1000, + }; + + const contentStyles = { + textAlign: 'center', + color: '#fff', + maxWidth: '400px', + padding: '40px', + }; + + const iconStyles = { + fontSize: '64px', + color: '#236f87', + marginBottom: '24px', + }; + + const h1Styles = { + font: '800 32px Rajdhani, sans-serif', + margin: '0 0 16px', + letterSpacing: '1px', + }; + + const pStyles = { + color: 'rgba(149, 207, 245, 0.7)', + font: '400 16px Rajdhani, sans-serif', + margin: '0 0 32px', + letterSpacing: '0.5px', + }; + + const buttonStyles = { + background: error + ? 'linear-gradient(#8a2323 0%, #681a1a 100%)' + : loading + ? 'linear-gradient(#236f87 0%, #1a5068 100%)' + : 'linear-gradient(#236f87 0%, #1a5068 100%)', + border: `2px solid ${error ? '#9a2e2e' : loading ? '#2e7a9a' : '#2e7a9a'}`, + borderRadius: '8px', + color: '#e0f4ff', + cursor: loading ? 'wait' : 'pointer', + font: '800 18px Rajdhani, sans-serif', + letterSpacing: '2px', + padding: '16px 40px', + textTransform: 'uppercase', + transition: 'all 0.3s ease', + display: 'inline-flex', + alignItems: 'center', + gap: '12px', + opacity: loading ? 0.7 : 1, + }; + + return ( +
+
+
+ +
+

Ready to Play?

+

+ Click below to verify you're human and start playing. +

+ +
+
+ ); +}; + +export default CaptchaOverlay; diff --git a/templates/Game/play.html.twig b/templates/Game/play.html.twig index fcae2ff..aa18c77 100644 --- a/templates/Game/play.html.twig +++ b/templates/Game/play.html.twig @@ -11,7 +11,8 @@ data-env="{{ env }}" data-game-id="{{ app.request.get('gameAssoc') }}" data-mercure-hub-url="{{ mercure_hub_url }}" - data-mercure-subscriber-jwt="{{ mercure_subscriber_jwt }}"> + data-mercure-subscriber-jwt="{{ mercure_subscriber_jwt }}" + data-recaptcha-site-key="{{ recaptcha_site_key }}"> {% endblock %} @@ -27,7 +28,6 @@ {% block stylesheets %} {{ vite_entry_link_tags('mineseekerStyle') }} -