import HCaptcha from '@hcaptcha/react-hcaptcha' import { Elements } from '@stripe/react-stripe-js' import { loadStripe } from '@stripe/stripe-js' import { useTheme } from 'next-themes' import { useCallback, useEffect, useState } from 'react' import { toast } from 'sonner' import { Modal } from 'ui' import AddPaymentMethodForm from './AddPaymentMethodForm' import { getStripeElementsAppearanceOptions } from './Payment.utils' import { useOrganizationPaymentMethodSetupIntent } from '@/data/organizations/organization-payment-method-setup-intent-mutation' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { STRIPE_PUBLIC_KEY } from '@/lib/constants' interface AddNewPaymentMethodModalProps { visible: boolean returnUrl: string onCancel: () => void onConfirm: () => void } const stripePromise = loadStripe(STRIPE_PUBLIC_KEY) const AddNewPaymentMethodModal = ({ visible, returnUrl, onCancel, onConfirm, }: AddNewPaymentMethodModalProps) => { const { resolvedTheme } = useTheme() const [intent, setIntent] = useState() const { data: selectedOrganization } = useSelectedOrganizationQuery() const [captchaToken, setCaptchaToken] = useState(null) const [captchaRef, setCaptchaRef] = useState(null) const { mutate: setupIntent } = useOrganizationPaymentMethodSetupIntent({ onSuccess: (intent) => { setIntent(intent) }, onError: (error) => { toast.error(`Failed to setup intent: ${error.message}`) }, }) const captchaRefCallback = useCallback((node: any) => { setCaptchaRef(node) }, []) useEffect(() => { const initSetupIntent = async (hcaptchaToken: string | undefined) => { const slug = selectedOrganization?.slug if (!slug) return console.error('Slug is required') if (!hcaptchaToken) return console.error('HCaptcha token required') setIntent(undefined) setupIntent({ slug, hcaptchaToken }) } const loadPaymentForm = async () => { if (visible && captchaRef) { let token = captchaToken try { if (!token) { const captchaResponse = await captchaRef.execute({ async: true }) token = captchaResponse?.response ?? null } } catch (error) { return } await initSetupIntent(token ?? undefined) resetCaptcha() } } loadPaymentForm() }, [visible, captchaRef]) const resetCaptcha = () => { setCaptchaToken(null) captchaRef?.resetCaptcha() } const options = { clientSecret: intent ? intent.client_secret : '', appearance: getStripeElementsAppearanceOptions(resolvedTheme), } as any const onLocalCancel = () => { setIntent(undefined) return onCancel() } const onLocalConfirm = () => { setIntent(undefined) return onConfirm() } return ( // We cant display the hCaptcha in the modal, as the modal auto-closes when clicking the captcha // So we only show the modal if the captcha has been executed successfully (intent loaded) <> { // [Joshen] This is to ensure that hCaptcha popup remains clickable if (document !== undefined) document.body.classList.add('pointer-events-auto!') }} onClose={() => { onLocalCancel() if (document !== undefined) document.body.classList.remove('pointer-events-auto!') }} onVerify={(token) => { setCaptchaToken(token) if (document !== undefined) document.body.classList.remove('pointer-events-auto!') }} onExpire={() => { setCaptchaToken(null) }} /> ) } export default AddNewPaymentMethodModal