Files
supabase/apps/studio/components/interfaces/Organization/restriction.constants.tsx
Kanishk DudejaandIgnacio Dobronich 83592b1932 feat(billing): show billing address modal for paid orgs with missing address (#43480)
### Summary

This PR adds a dismissable modal that prompts paid org users to fill in
their billing address. We need billing addresses for tax compliance and
the existing banner hasn't been as effective (~10k orgs still missing).

The modal shows on page load when all of these are true:

- ConfigCat flag `enableBillingAddressModal` is on
- Org is on a paid plan (not free)
- Org does not have billing address set
- Org is not partner-managed
- User has BILLING_WRITE permission on stripe.customer

The modal can be dismissed via the close button, but re-opens on every
page navigation.

Once the user saves a valid address, the organizations query is
invalidated and organization_missing_address flips to false - the modal
stops appearing permanently.

<img width="1256" height="703" alt="Screenshot 2026-03-05 at 4 22 11 PM"
src="https://github.com/user-attachments/assets/57e7d1b8-b3b6-4366-ad23-60910a0defe8"
/>

### Testing

#### Automated Tests

I've added unit tests in this PR

#### Manual testing

Manual test cases:

- Free org - modal does not appear
- Partner-managed org - modal does not appear
- User with "developer role" modal does not appear
- Configcat Flag disabled - modal does not appear
- Dismiss modal, navigate to another page - modal re-opens
- Fill form and save - modal closes and does not reappear
- API failure loading billing data - shows error state with Retry button

---------

Co-authored-by: Ignacio Dobronich <ignacio@dobronich.com>
2026-03-13 09:33:09 -03:00

52 lines
1.6 KiB
TypeScript

import { type ReactNode } from 'react'
import { InlineLink } from 'components/ui/InlineLink'
export const RESTRICTION_MESSAGES = {
GRACE_PERIOD: {
title: 'Organization plan has exceeded its quota',
description: (date: string, slug: string): ReactNode => (
<>
You have been given a grace period until {date}.{' '}
<InlineLink href={`/org/${slug}/usage`}>Review usage</InlineLink>
</>
),
},
GRACE_PERIOD_OVER: {
title: 'Grace period is over',
description: (slug: string): ReactNode => (
<>
Your projects will not be able to serve requests when you use up your quota.{' '}
<InlineLink href={`/org/${slug}/billing`}>Review billing</InlineLink>
</>
),
},
RESTRICTED: {
title: 'Services restricted',
description: (slug: string): ReactNode => (
<>
Your projects are unable to serve requests as your organization has used up its quota.{' '}
<InlineLink href={`/org/${slug}/billing`}>Resolve billing issues</InlineLink>
</>
),
},
OVERDUE_INVOICES: {
title: 'Outstanding invoices',
description: (slug: string): ReactNode => (
<>
Please <InlineLink href={`/org/${slug}/billing#invoices`}>pay your invoices</InlineLink> to
avoid service disruption
</>
),
},
OVERDUE_INVOICES_FROM_OTHER_ORGS: {
title: 'Outstanding invoices in other organization',
description: (slug: string): ReactNode => (
<>
Please <InlineLink href={`/org/${slug}/billing#invoices`}>pay invoices</InlineLink> for
other organizations to avoid service disruption
</>
),
},
}