Files
supabase/apps/studio/data/organizations/organization-creation-preview.ts
Ignacio Dobronich 927d399264 feat: show tax in new org creation (#44553)
- Call the new `POST /platform/organizations/preview-creation` endpoint
when creating an organization on a paid plan to show a tax breakdown
before submission
- Preview is triggered reactively when the user completes the billing
address in the Stripe AddressElement (debounced, same pattern as
subscription upgrades)
- Displays plan price, tax line (with percentage), and total due today -
hides the plan price row when there's no tax adjustment

## Test plan

- [ ]  Create a new org on the Free plan - no preview should appear
- [ ]  Create a new org on Pro/Team - fill in billing address with all
fields including name
- [ ]  Verify tax preview appears after address is complete (1s
debounce)
- [ ]  Verify tax line shows for taxable jurisdictions (NY Zip Code
10001), hidden for non-taxable
- [ ]  Verify plan price row is hidden when total equals plan price (no
tax)
- [ ]  Change address country - verify preview updates
- [ ]  Add a tax ID - verify preview updates

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Pricing preview during organization signup that shows plan price
differences, tax details (rate/amount or estimation failure), and “Total
due today.”
* Preview updates reactively based on selected plan and spend cap (PRO
without spend cap treated as PAYG) and appears only for paid plans after
preview initialization.
* Debounced billing address and tax ID input collection for accurate
previews; panel opacity reduced while fetching.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-14 11:48:48 -03:00

69 lines
2.0 KiB
TypeScript

import { keepPreviousData, useQuery } from '@tanstack/react-query'
import { organizationKeys } from './keys'
import type { CustomerAddress, CustomerTaxId } from './types'
import { handleError, post } from '@/data/fetchers'
import type { ResponseError, UseCustomQueryOptions } from '@/types'
export type OrganizationCreationPreviewVariables = {
tier?: string
address?: CustomerAddress
taxId?: CustomerTaxId
}
export type OrganizationCreationPreviewResponse = {
currency: string
plan_price: number
tax: {
currency: string
tax_amount: number
tax_rate_percentage: number
total_amount_excluding_tax: number
total_amount_including_tax: number
} | null
tax_status: 'calculated' | 'not_applicable' | 'failed'
total: number
}
export async function previewOrganizationCreation({
tier,
address,
taxId,
}: OrganizationCreationPreviewVariables) {
if (!tier) throw new Error('tier is required')
const { data, error } = await post(`/platform/organizations/preview-creation`, {
body: {
tier: tier as 'tier_pro' | 'tier_payg' | 'tier_team',
...(address && { address }),
...(taxId && { tax_id: taxId }),
},
})
if (error) handleError(error)
return data as OrganizationCreationPreviewResponse
}
export type OrganizationCreationPreviewData = Awaited<
ReturnType<typeof previewOrganizationCreation>
>
export const useOrganizationCreationPreview = <TData = OrganizationCreationPreviewData>(
{ tier, address, taxId }: OrganizationCreationPreviewVariables,
{
enabled = true,
...options
}: UseCustomQueryOptions<OrganizationCreationPreviewData, ResponseError, TData> = {}
) =>
useQuery<OrganizationCreationPreviewData, ResponseError, TData>({
queryKey: organizationKeys.creationPreview(tier, {
address: address as Record<string, unknown> | undefined,
taxId: taxId as Record<string, unknown> | undefined,
}),
queryFn: () => previewOrganizationCreation({ tier, address, taxId }),
enabled: enabled && typeof tier !== 'undefined',
placeholderData: keepPreviousData,
...options,
})