Files
Danny White 476d4a5851 refactor(ui): drop redundant Button variant="default" props (#50161)
## What kind of change does this PR introduce?

Mechanical cleanup on top of the Button default-variant change (#50160).

## What is the current behavior?

Many callsites still pass `variant="default"` even though that is now
the component default.

## What is the new behavior?

Removes redundant static `variant="default"` from legacy `Button` and
`ButtonTooltip` callsites. Keeps explicit defaults where they document
the API:

- `button-default.tsx` and `button-sizes.tsx` demos
- `DocsButton`, which pins neutral styling at the wrapper boundary

## To test

Studio:

- [Auth → Rate
Limits](https://studio-staging-2s957kwc4-supabase.vercel.app/dashboard/project/_/auth/rate-limits):
dirty the form so Cancel appears; Cancel stays neutral, Save stays green
- [Project Settings → API
Keys](https://studio-staging-2s957kwc4-supabase.vercel.app/dashboard/project/_/settings/api-keys):
`DocsButton` in the header actions stays neutral

Design system:

- [Design system →
Button](https://design-system-git-dnywh-dc924ac1-supabase.vercel.app/design-system/docs/components/button):
`button-default` / `button-sizes` still show explicit default styling;
Primary (green) is restricted to the Primary section (and `asChild`)

WWW:

- [www → Brand
assets](https://zone-www-dot-com-git-dnywh-dc924ac1-supabase.vercel.app/brand-assets):
Download logo kit / Download button kit stay neutral
2026-09-11 17:05:26 +10:00

223 lines
9.6 KiB
TypeScript

import { PermissionAction, SupportCategories } from '@supabase/shared-types/out/constants'
import { useParams } from 'common'
import { CreditCardIcon, ExternalLink, Plus } from 'lucide-react'
import { useState } from 'react'
import { useInView } from 'react-intersection-observer'
import { toast } from 'sonner'
import { Button } from 'ui'
import { Admonition } from 'ui-patterns/Admonition'
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
import ChangePaymentMethodModal from './ChangePaymentMethodModal'
import CreditCard from './CreditCard'
import DeletePaymentMethodModal from './DeletePaymentMethodModal'
import AddNewPaymentMethodModal from '@/components/interfaces/Billing/Payment/AddNewPaymentMethodModal'
import { SupportLink } from '@/components/interfaces/Support/SupportLink'
import {
ScaffoldSection,
ScaffoldSectionContent,
ScaffoldSectionDetail,
} from '@/components/layouts/Scaffold'
import { AlertError } from '@/components/ui/AlertError'
import { FormPanel } from '@/components/ui/Forms/FormPanel'
import { FormSection, FormSectionContent } from '@/components/ui/Forms/FormSection'
import { NoPermission } from '@/components/ui/NoPermission'
import PartnerManagedResource from '@/components/ui/PartnerManagedResource'
import { isPartnerBillingOrganization } from '@/data/organizations/managed-by-utils'
import { useOrganizationPaymentMethodsQuery } from '@/data/organizations/organization-payment-methods-query'
import { useOrgSubscriptionQuery } from '@/data/subscriptions/org-subscription-query'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
import { MANAGED_BY } from '@/lib/constants/infrastructure'
import { getURL } from '@/lib/helpers'
const PaymentMethods = () => {
const { slug } = useParams()
const { data: selectedOrganization } = useSelectedOrganizationQuery()
const [selectedMethodForUse, setSelectedMethodForUse] = useState<any>()
const [selectedMethodToDelete, setSelectedMethodToDelete] = useState<any>()
const [showAddPaymentMethodModal, setShowAddPaymentMethodModal] = useState(false)
const { ref, inView } = useInView({ triggerOnce: true })
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: slug }, { enabled: inView })
const {
data: paymentMethods,
error,
isPending: isLoading,
isError,
isSuccess,
} = useOrganizationPaymentMethodsQuery({ slug }, { enabled: inView })
const { can: canReadPaymentMethods, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions(
PermissionAction.BILLING_READ,
'stripe.payment_methods'
)
const { can: canUpdatePaymentMethods } = useAsyncCheckPermissions(
PermissionAction.BILLING_WRITE,
'stripe.payment_methods'
)
const isPartnerBilledOrganization = isPartnerBillingOrganization(
selectedOrganization?.billing_partner
)
const isStripeManagedOrganization =
selectedOrganization?.managed_by === MANAGED_BY.STRIPE_PROJECTS
return (
<>
<ScaffoldSection ref={ref}>
<ScaffoldSectionDetail>
<div className="sticky space-y-2 top-12">
<p className="text-foreground text-base m-0">Payment Methods</p>
<p className="text-sm text-foreground-light mb-2 pr-4 m-0">
{isStripeManagedOrganization
? 'Billing for this organisation is handled through Stripe Projects.'
: 'Payments for your subscription are made using the default card.'}
</p>
</div>
</ScaffoldSectionDetail>
<ScaffoldSectionContent>
{selectedOrganization && isPartnerBilledOrganization ? (
<PartnerManagedResource
managedBy={selectedOrganization?.managed_by}
resource="Payment Methods"
cta={{
installationId: selectedOrganization?.partner_id,
}}
/>
) : isPermissionsLoaded && !canReadPaymentMethods ? (
<NoPermission resourceText="view this organization's payment methods" />
) : (
<>
{isLoading && (
<div className="space-y-2">
<ShimmeringLoader />
<ShimmeringLoader className="w-3/4" />
<ShimmeringLoader className="w-1/2" />
</div>
)}
{isError && (
<AlertError subject="Failed to retrieve payment methods" error={error as any} />
)}
{isSuccess && (
<>
{subscription?.payment_method_type === 'invoice' && (
<Admonition
type="note"
layout="horizontal"
title="Payment is currently by invoice"
description={
isStripeManagedOrganization
? 'You get a monthly invoice and payment link via email. Manage payment methods through Stripe Projects.'
: 'You get a monthly invoice and payment link via email. To change your payment method, please contact us via our support form.'
}
actions={
isStripeManagedOrganization ? (
<Button
asChild
key="stripe-projects-billing-docs"
iconRight={<ExternalLink size={14} />}
>
<a
href="https://docs.stripe.com/projects#manage-billing"
target="_blank"
rel="noopener noreferrer"
>
View Stripe Projects docs
</a>
</Button>
) : (
<Button asChild key="payment-method-support">
<SupportLink
queryParams={{
category: SupportCategories.BILLING,
subject: 'Request to change payment method',
}}
>
Contact support
</SupportLink>
</Button>
)
}
/>
)}
<FormPanel
footer={
!isStripeManagedOrganization ? (
<div className="flex items-center justify-between py-4 px-8">
{!canUpdatePaymentMethods ? (
<p className="text-sm text-foreground-light">
You need additional permissions to manage payment methods
</p>
) : (
<div />
)}
<Button
icon={<Plus />}
disabled={!canUpdatePaymentMethods}
onClick={() => setShowAddPaymentMethodModal(true)}
>
Add new card
</Button>
</div>
) : undefined
}
>
<FormSection>
<FormSectionContent fullWidth loading={false}>
{(paymentMethods?.data?.length ?? 0) === 0 ? (
<div className="flex items-center gap-2 opacity-50">
<CreditCardIcon size={16} strokeWidth={1.5} />
<p className="text-sm">No payment methods</p>
</div>
) : (
<div className="space-y-3">
{paymentMethods?.data?.map((paymentMethod) => (
<CreditCard
key={paymentMethod.id}
paymentMethod={paymentMethod}
canUpdatePaymentMethods={canUpdatePaymentMethods}
paymentMethodType={subscription?.payment_method_type}
setSelectedMethodForUse={setSelectedMethodForUse}
setSelectedMethodToDelete={setSelectedMethodToDelete}
paymentMethodCount={paymentMethods?.data.length ?? 0}
subscriptionPlan={subscription?.plan.id}
/>
))}
</div>
)}
</FormSectionContent>
</FormSection>
</FormPanel>
</>
)}
</>
)}
</ScaffoldSectionContent>
</ScaffoldSection>
<AddNewPaymentMethodModal
visible={showAddPaymentMethodModal}
returnUrl={`${getURL()}/org/${slug}/billing`}
onCancel={() => setShowAddPaymentMethodModal(false)}
onConfirm={() => {
setShowAddPaymentMethodModal(false)
toast.success('Successfully added new payment method')
}}
/>
<ChangePaymentMethodModal
selectedPaymentMethod={selectedMethodForUse}
onClose={() => setSelectedMethodForUse(undefined)}
/>
<DeletePaymentMethodModal
selectedPaymentMethod={selectedMethodToDelete}
onClose={() => setSelectedMethodToDelete(undefined)}
/>
</>
)
}
export default PaymentMethods