mirror of
https://github.com/supabase/supabase.git
synced 2026-10-15 22:29:11 +03:00
- Mandatory address input when adding a new payment method - Removed the global HCaptcha store that wasn't used consistently and would sometimes block payment method changes - Remove the custom billing address data & tax id form on org creation and plan upgrades in favour of Stripe's `AddressElement` - Unify usage of the Stripe payment elements into a single component - Customers can mark "Purchasing as a business" and will then be able to put down a tax id - Adjusted billing address form to have better labels + tax id is filtered down to selected country - Adjusted Stripe Elements styling to use floating labels (otherwise very hard to use with address element) + additional styling changes - New flag to filter out payment methods that do not have an address for org upgrades and credit top ups, this will be enforced a few days after rolling this out - Added Google Maps Places API integration for address auto-completion via Stripe AddressElement - Upgraded Stripe dependencies - Slight adjustments to styling of plan upgrade modal
133 lines
4.5 KiB
TypeScript
133 lines
4.5 KiB
TypeScript
import { zodResolver } from '@hookform/resolvers/zod'
|
|
import { useEffect } from 'react'
|
|
import { useForm } from 'react-hook-form'
|
|
|
|
import { BillingCustomerDataFormValues, BillingCustomerDataSchema } from './BillingCustomerDataForm'
|
|
import { TAX_IDS } from './TaxID.constants'
|
|
import { sanitizeTaxIdValue } from './TaxID.utils'
|
|
import { components } from 'api-types'
|
|
import type { CustomerAddress } from 'data/organizations/types'
|
|
|
|
export type FormCustomerData = {
|
|
address: CustomerAddress | undefined
|
|
billing_name: string
|
|
tax_id: components['schemas']['TaxIdResponse']['tax_id'] | null
|
|
}
|
|
|
|
interface UseBillingAddressFormProps {
|
|
initialCustomerData?: Partial<BillingCustomerDataFormValues> | null
|
|
onCustomerDataChange: (data: FormCustomerData) => void
|
|
}
|
|
|
|
export function useBillingCustomerDataForm({
|
|
initialCustomerData,
|
|
onCustomerDataChange,
|
|
}: UseBillingAddressFormProps) {
|
|
const form = useForm<BillingCustomerDataFormValues>({
|
|
resolver: zodResolver(BillingCustomerDataSchema),
|
|
defaultValues: {
|
|
billing_name: initialCustomerData?.billing_name || '',
|
|
line1: initialCustomerData?.line1 || '',
|
|
line2: initialCustomerData?.line2 || '',
|
|
city: initialCustomerData?.city || '',
|
|
state: initialCustomerData?.state || '',
|
|
postal_code: initialCustomerData?.postal_code || '',
|
|
country: initialCustomerData?.country || '',
|
|
tax_id_type: initialCustomerData?.tax_id_type || '',
|
|
tax_id_value: initialCustomerData?.tax_id_value || '',
|
|
tax_id_name: initialCustomerData?.tax_id_name || '',
|
|
},
|
|
})
|
|
|
|
// Update form when initialAddress changes
|
|
useEffect(() => {
|
|
if (initialCustomerData) {
|
|
form.reset({
|
|
billing_name: initialCustomerData.billing_name || '',
|
|
line1: initialCustomerData.line1 || '',
|
|
line2: initialCustomerData.line2 || '',
|
|
city: initialCustomerData.city || '',
|
|
state: initialCustomerData.state || '',
|
|
postal_code: initialCustomerData.postal_code || '',
|
|
country: initialCustomerData.country || '',
|
|
tax_id_type: initialCustomerData?.tax_id_type || '',
|
|
tax_id_value: initialCustomerData?.tax_id_value || '',
|
|
tax_id_name: initialCustomerData?.tax_id_name || '',
|
|
})
|
|
}
|
|
}, [initialCustomerData])
|
|
|
|
const handleSubmit = async (values: BillingCustomerDataFormValues) => {
|
|
const trimmedValues = Object.entries(values).reduce((acc, [key, value]) => {
|
|
acc[key as keyof BillingCustomerDataFormValues] =
|
|
typeof value === 'string' ? value.trim() : value
|
|
return acc
|
|
}, {} as BillingCustomerDataFormValues)
|
|
|
|
const addressPayload = !trimmedValues.line1 ? null : trimmedValues
|
|
|
|
const selectedTaxId = TAX_IDS.find((option) => option.name === values.tax_id_name)
|
|
|
|
onCustomerDataChange({
|
|
address: addressPayload
|
|
? {
|
|
line1: trimmedValues.line1!,
|
|
line2: trimmedValues.line2,
|
|
city: trimmedValues.city,
|
|
state: trimmedValues.state,
|
|
postal_code: trimmedValues.postal_code,
|
|
country: trimmedValues.country!,
|
|
}
|
|
: undefined,
|
|
billing_name: trimmedValues.billing_name,
|
|
tax_id:
|
|
selectedTaxId && values.tax_id_type?.length && values.tax_id_value?.length
|
|
? {
|
|
type: values.tax_id_type,
|
|
value: sanitizeTaxIdValue({
|
|
value: values.tax_id_value,
|
|
name: form.getValues().tax_id_name,
|
|
}),
|
|
country: selectedTaxId?.countryIso2,
|
|
}
|
|
: null,
|
|
})
|
|
}
|
|
|
|
const handleReset = () => {
|
|
if (initialCustomerData) {
|
|
form.reset({
|
|
billing_name: initialCustomerData.billing_name || '',
|
|
line1: initialCustomerData.line1 || '',
|
|
line2: initialCustomerData.line2 || '',
|
|
city: initialCustomerData.city || '',
|
|
state: initialCustomerData.state || '',
|
|
postal_code: initialCustomerData.postal_code || '',
|
|
country: initialCustomerData.country || '',
|
|
tax_id_type: initialCustomerData?.tax_id_type || '',
|
|
tax_id_value: initialCustomerData?.tax_id_value || '',
|
|
tax_id_name: initialCustomerData?.tax_id_name || '',
|
|
})
|
|
} else {
|
|
form.reset({
|
|
line1: '',
|
|
line2: '',
|
|
city: '',
|
|
state: '',
|
|
postal_code: '',
|
|
country: '',
|
|
tax_id_type: '',
|
|
tax_id_value: '',
|
|
tax_id_name: '',
|
|
})
|
|
}
|
|
}
|
|
|
|
return {
|
|
form,
|
|
handleSubmit,
|
|
handleReset,
|
|
isDirty: form.formState.isDirty,
|
|
}
|
|
}
|