/* * Copyright (C) 2026 Fluxer Contributors * * This file is part of Fluxer. * * Fluxer is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Fluxer is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Fluxer. If not, see . */ import * as ModalActionCreators from '@app/actions/ModalActionCreators'; import {TurnstileWidget} from '@app/components/captcha/TurnstileWidget'; import styles from '@app/components/modals/CaptchaModal.module.css'; import * as Modal from '@app/components/modals/Modal'; import {Button} from '@app/components/uikit/button/Button'; import {Logger} from '@app/lib/Logger'; import RuntimeConfigStore from '@app/stores/RuntimeConfigStore'; import HCaptcha from '@hcaptcha/react-hcaptcha'; import {Trans, useLingui} from '@lingui/react/macro'; import {observer} from 'mobx-react-lite'; import {useCallback, useEffect, useRef, useState} from 'react'; const logger = new Logger('CaptchaModal'); export type CaptchaType = 'turnstile' | 'hcaptcha'; interface HCaptchaComponentProps { sitekey: string; onVerify?: (token: string) => void; onExpire?: () => void; onError?: (error: string) => void; theme?: 'light' | 'dark'; ref?: React.Ref; } const HCaptchaComponent = HCaptcha as React.ComponentType; interface CaptchaModalProps { onVerify: (token: string, captchaType: CaptchaType) => void; onCancel?: () => void; preferredType?: CaptchaType; error?: string | null; isVerifying?: boolean; closeOnVerify?: boolean; } export const CaptchaModal = observer( ({onVerify, onCancel, preferredType, error, isVerifying, closeOnVerify = true}: CaptchaModalProps) => { const {t} = useLingui(); const hcaptchaRef = useRef(null); const [captchaType, setCaptchaType] = useState(() => { if (preferredType) return preferredType; if (RuntimeConfigStore.captchaProvider === 'turnstile' && RuntimeConfigStore.turnstileSiteKey) { return 'turnstile'; } if (RuntimeConfigStore.captchaProvider === 'hcaptcha' && RuntimeConfigStore.hcaptchaSiteKey) { return 'hcaptcha'; } return RuntimeConfigStore.turnstileSiteKey ? 'turnstile' : 'hcaptcha'; }); useEffect(() => { if (captchaType === 'hcaptcha') { const timer = setTimeout(() => { hcaptchaRef.current?.resetCaptcha(); }, 100); return () => clearTimeout(timer); } return; }, [captchaType]); useEffect(() => { if (error) { if (captchaType === 'hcaptcha') { hcaptchaRef.current?.resetCaptcha(); } } }, [error, captchaType]); const handleVerify = useCallback( (token: string) => { onVerify(token, captchaType); if (closeOnVerify) { ModalActionCreators.pop(); } }, [onVerify, captchaType, closeOnVerify], ); const handleCancel = useCallback(() => { onCancel?.(); ModalActionCreators.pop(); }, [onCancel]); const handleExpire = useCallback(() => { if (captchaType === 'hcaptcha') { hcaptchaRef.current?.resetCaptcha(); } }, [captchaType]); const handleError = useCallback( (error: string) => { logger.error(`${captchaType} error:`, error); }, [captchaType], ); const handleSwitchToHCaptcha = useCallback(() => { setCaptchaType('hcaptcha'); }, []); const handleSwitchToTurnstile = useCallback(() => { setCaptchaType('turnstile'); }, []); const showSwitchButton = (captchaType === 'turnstile' && RuntimeConfigStore.hcaptchaSiteKey) || (captchaType === 'hcaptcha' && RuntimeConfigStore.turnstileSiteKey); return (

We need to make sure you're not a bot. Please complete the verification below.

{error && (

{error}

)}
{captchaType === 'turnstile' ? ( ) : ( )}
{showSwitchButton && (
)}
); }, );