import { Elements } from '@stripe/react-stripe-js' import { loadStripe, StripeAddressElement, StripeElementsOptions } from '@stripe/stripe-js' import { PermissionAction } from '@supabase/shared-types/out/constants' import { useQueryClient } from '@tanstack/react-query' import { useFlag } from 'common' import { useTheme } from 'next-themes' import { useEffect, useMemo, useRef, useState } from 'react' import { toast } from 'sonner' import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogSection, DialogTitle, Form, } from 'ui' import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' import { getAddressElementAppearanceOptions, STRIPE_ELEMENT_FONTS, } from '@/components/interfaces/Billing/Payment/Payment.utils' import { BillingCustomerDataForm } from '@/components/interfaces/Organization/BillingSettings/BillingCustomerData/BillingCustomerDataForm' import { useBillingCustomerDataForm } from '@/components/interfaces/Organization/BillingSettings/BillingCustomerData/useBillingCustomerDataForm' import { useOrganizationCustomerProfileQuery } from '@/data/organizations/organization-customer-profile-query' import { useOrganizationCustomerProfileUpdateMutation } from '@/data/organizations/organization-customer-profile-update-mutation' import { useOrganizationTaxIdQuery } from '@/data/organizations/organization-tax-id-query' import { invalidateOrganizationsQuery } from '@/data/organizations/organizations-query' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { IS_PLATFORM, STRIPE_PUBLIC_KEY } from '@/lib/constants' const stripePromise = loadStripe(STRIPE_PUBLIC_KEY) export function UpdateBillingAddressModal() { const queryClient = useQueryClient() const { resolvedTheme } = useTheme() const [dismissed, setDismissed] = useState(false) const [isSubmitting, setIsSubmitting] = useState(false) const addressElementRef = useRef(null) const showMissingAddressModal = useFlag('enableBillingAddressModal') const { data: org } = useSelectedOrganizationQuery() const slug = org?.slug const { can: canBillingRead, isSuccess: billingReadLoaded } = useAsyncCheckPermissions( PermissionAction.BILLING_READ, 'stripe.customer' ) const { can: canBillingWrite, isSuccess: billingWriteLoaded } = useAsyncCheckPermissions( PermissionAction.BILLING_WRITE, 'stripe.customer' ) const permissionsLoaded = billingReadLoaded && billingWriteLoaded const canViewModal = canBillingRead || canBillingWrite const canManageBillingAddress = canBillingWrite const shouldShow = Boolean( IS_PLATFORM && showMissingAddressModal && org && org.plan.id !== 'free' && org.organization_missing_address && !org.billing_partner && permissionsLoaded && canViewModal ) const { data: customerProfile, isSuccess: profileLoaded, isError: profileError, } = useOrganizationCustomerProfileQuery( { slug }, { enabled: shouldShow && !dismissed && !!slug && canManageBillingAddress } ) const { data: taxId, isSuccess: taxIdLoaded, isError: taxIdError, } = useOrganizationTaxIdQuery( { slug }, { enabled: shouldShow && !dismissed && !!slug && canManageBillingAddress } ) const open = shouldShow && !dismissed && (!canManageBillingAddress || (!profileError && !taxIdError)) const { mutateAsync: updateCustomerProfile } = useOrganizationCustomerProfileUpdateMutation({ onError: () => {}, }) const { form, handleSubmit, isDirty, resetKey, onAddressChange, applyAddressElementValue, markCurrentValuesAsSaved, addressCountry, addressOptions, } = useBillingCustomerDataForm({ customerProfile, taxId, onCustomerDataChange: async (data) => { if (!slug) return setIsSubmitting(true) try { await updateCustomerProfile({ slug, address: data.address, billing_name: data.billing_name, tax_id: data.tax_id, }) setDismissed(true) await invalidateOrganizationsQuery(queryClient) toast.success('Successfully updated billing address') } catch (error: any) { toast.error(`Failed to update billing address: ${error.message}`) throw error } finally { setIsSubmitting(false) } }, }) useEffect(() => { addressElementRef.current = null }, [resetKey]) const onFormSubmit = async (e: React.FormEvent) => { e.preventDefault() try { if (addressElementRef.current) { const addressResult = await addressElementRef.current.getValue() applyAddressElementValue(addressResult) } const result = await handleSubmit() if (result.status === 'error') { toast.error(result.message) return } markCurrentValuesAsSaved( result.submittedState.addressValue, result.submittedState.taxIdValues ) } catch { // Save failure toasts are handled inside onCustomerDataChange. } } const stripeElementsOptions: StripeElementsOptions = useMemo( () => ({ mode: 'setup', currency: 'usd', appearance: getAddressElementAppearanceOptions(resolvedTheme), fonts: STRIPE_ELEMENT_FONTS, }) as any, [resolvedTheme] ) return ( { if (!value) setDismissed(true) }} > e.preventDefault()} onEscapeKeyDown={(e) => e.preventDefault()} > Billing address required {canManageBillingAddress ? ( 'Please provide a billing address for your organization. If you are a registered business and have a Tax ID, please add your Tax ID too.' ) : ( <> Your organization requires a billing address. If you are a registered business, a Tax ID is also required.

Please ask an organization administrator or owner to update it as soon as possible to avoid service restrictions. )}
{canManageBillingAddress && (!profileLoaded || !taxIdLoaded ? (
) : (
{ addressElementRef.current = element }} addressCountry={addressCountry} />
))}
) }