Files
supabase/apps/studio/components/interfaces/Billing/ChargeBreakdown.tsx
Ignacio Dobronich 7dcf677a95 feat: add loading indicator and ChargeBreakdown in NewOrgForm (#45236)
## Summary

- Show a `ShimmeringLoader` skeleton in the new-org form while the
creation preview is fetching for the first time, so users see feedback
after entering their billing address.
- Disable the **Create organization** button while the preview is
refetching, to prevent submitting on stale totals.
- Replace the inline charge summary with the
shared `ChargeBreakdown` component, extending it with an
optional `subtotalLabel` prop (defaults to `"Subtotal"`, `"Plan
price"` here) to preserve the existing copy.

## Test plan

- [ ]  Navigate to `/new` → pick Pro/Team → enter a billing address →
confirm shimmer shows until the first preview resolves.
- [ ]  Change address/tax ID → confirm existing totals fade to 50% while
refetching and the submit button is disabled during the fetch.
- [ ]  Confirm the breakdown still renders "Plan price", "Tax (x%)", and
"Total due today" correctly
- [ ]  FREE plan path unaffected — no preview request, submit button
behaves as before.

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

## Summary by CodeRabbit

* **Improvements**
* Organization creation form now displays loading placeholders while
billing preview data is being fetched, providing clearer visual feedback
during the calculation process
* Submit button is now properly disabled during billing information
retrieval
  * Billing breakdown display has been refined for improved consistency

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-27 14:29:10 -03:00

61 lines
1.8 KiB
TypeScript

import { cn } from 'ui'
import { formatCurrency } from '@/lib/helpers'
export interface ChargeBreakdownProps {
subtotal: number
subtotalLabel?: string
total: number
tax?: { amount: number; percentage: number }
taxStatus?: 'calculated' | 'failed' | 'not_applicable'
isFetching: boolean
}
export const ChargeBreakdown = ({
subtotal,
subtotalLabel = 'Subtotal',
total,
tax,
taxStatus,
isFetching,
}: ChargeBreakdownProps) => {
return (
<div
className={cn('text-foreground-light text-sm transition-opacity', isFetching && 'opacity-50')}
>
{total !== subtotal && (
<div className="flex items-center justify-between gap-2 border-b border-muted text-sm">
<div className="py-2">{subtotalLabel}</div>
<div className="py-2 text-right tabular-nums" translate="no">
{formatCurrency(subtotal)}
</div>
</div>
)}
{taxStatus === 'calculated' && tax && tax.amount > 0 && (
<div className="flex items-center justify-between gap-2 border-b border-muted text-sm">
<div className="py-2">Tax ({tax.percentage}%)</div>
<div className="py-2 text-right tabular-nums" translate="no">
{formatCurrency(tax.amount)}
</div>
</div>
)}
{taxStatus === 'failed' && (
<div className="flex items-center justify-between gap-2 border-b border-muted text-sm">
<div className="py-2 text-foreground-lighter">
Tax could not be estimated and may be applied separately
</div>
</div>
)}
<div className="flex items-center justify-between gap-2 text-foreground text-base">
<div className="py-2">Total due today</div>
<div className="py-2 text-right tabular-nums" translate="no">
{formatCurrency(total)}
</div>
</div>
</div>
)
}