| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102 |
- import { PermissionAction } from '@supabase/shared-types/out/constants'
- import { useParams } from 'common'
- import { useInView } from 'react-intersection-observer'
- import { CreditCodeRedemption } from './CreditCodeRedemption'
- import { CreditTopUp } from './CreditTopUp'
- 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 { useOrgBalanceQuery } from '@/data/subscriptions/org-balance-query'
- import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
- const CreditBalance = () => {
- const { slug } = useParams()
- const { isSuccess: isPermissionsLoaded, can: canReadSubscriptions } = useAsyncCheckPermissions(
- PermissionAction.BILLING_READ,
- 'stripe.subscriptions'
- )
- const { ref, inView } = useInView({ triggerOnce: true })
- // Endpoint is expensive, so we only load it if it's in view
- const {
- data: balanceData,
- error: balanceError,
- isPending: isBalanceLoading,
- isError: isBalanceError,
- isSuccess: isBalanceSuccess,
- } = useOrgBalanceQuery({ orgSlug: slug }, { enabled: canReadSubscriptions && inView })
- const combinedCreditBalanceCents = balanceData?.total_balance_cents ?? 0
- const combinedCreditBalance = combinedCreditBalanceCents / 100
- const hasCredits = combinedCreditBalanceCents > 0
- const hasDebt = combinedCreditBalanceCents < 0
- const balance = Math.abs(combinedCreditBalance).toFixed(2)
- return (
- <ScaffoldSection ref={ref}>
- <ScaffoldSectionDetail>
- <div className="sticky space-y-2 top-12 pr-3">
- <div className="flex items-center space-x-2">
- <p className="text-foreground text-base m-0">Credit Balance</p>
- </div>
- <p className="text-sm text-foreground-light m-0">
- Credits will be applied to future invoices, before charging your payment method. This
- balance includes purchased credits and any prorated credits from plan changes.
- </p>
- <p className="text-sm text-foreground-light m-0">
- If your credits run out, your default payment method will be charged.
- </p>
- </div>
- </ScaffoldSectionDetail>
- <ScaffoldSectionContent>
- {isPermissionsLoaded && !canReadSubscriptions ? (
- <NoPermission resourceText="view this organization's credits" />
- ) : (
- <FormPanel
- footer={
- balanceData?.billing_via_partner ? undefined : (
- <div className="flex justify-end items-center py-4 px-8 gap-x-2">
- <CreditCodeRedemption slug={slug} />
- <CreditTopUp slug={slug} />
- </div>
- )
- }
- >
- <FormSection>
- <FormSectionContent fullWidth loading={isBalanceLoading}>
- {isBalanceError && (
- <AlertError
- subject="Failed to retrieve organization customer profile"
- error={balanceError}
- />
- )}
- {isBalanceSuccess && (
- <div className="flex w-full justify-between items-center">
- <span>Balance</span>
- <div className="flex items-center space-x-1">
- {hasDebt && <h4 className="opacity-50">-</h4>}
- <h4 className="opacity-50">$</h4>
- <h1 className="relative">{balance}</h1>
- {hasCredits && <h4 className="opacity-50">/credits</h4>}
- </div>
- </div>
- )}
- </FormSectionContent>
- </FormSection>
- </FormPanel>
- )}
- </ScaffoldSectionContent>
- </ScaffoldSection>
- )
- }
- export default CreditBalance
|