diff --git a/src/features/dashboard/billing/addons.tsx b/src/features/dashboard/billing/addons.tsx index 70e2e123c..8dab0af40 100644 --- a/src/features/dashboard/billing/addons.tsx +++ b/src/features/dashboard/billing/addons.tsx @@ -20,7 +20,7 @@ import { useDashboard } from '../context' import { ConcurrentSandboxAddOnPurchaseDialog } from './concurrent-sandboxes-addon-dialog' import { ADDON_500_SANDBOXES_ID, TIER_PRO_ID } from './constants' import { useBillingItems } from './hooks' -import { formatAddonQuantity } from './utils' +import { formatAddonQuantity, isEnterpriseTier } from './utils' interface AddonItemProps { name: string @@ -222,6 +222,12 @@ export default function Addons() { ) } + const isEnterprise = selectedTierId ? isEnterpriseTier(selectedTierId) : false + + if (isEnterprise) { + return null + } + if (!isOnProTier) { return (
diff --git a/src/features/dashboard/billing/credits.tsx b/src/features/dashboard/billing/credits.tsx index cd162f8b5..58f878811 100644 --- a/src/features/dashboard/billing/credits.tsx +++ b/src/features/dashboard/billing/credits.tsx @@ -4,10 +4,14 @@ import { formatCurrency } from '@/lib/utils/formatting' import { Label } from '@/ui/primitives/label' import { Separator } from '@/ui/primitives/separator' import { Skeleton } from '@/ui/primitives/skeleton' +import { useDashboard } from '../context' import { useUsage } from './hooks' +import { isEnterpriseTier } from './utils' export default function Credits() { const { credits, isLoading } = useUsage() + const { team } = useDashboard() + const isEnterprise = isEnterpriseTier(team.tier) return (
@@ -17,14 +21,21 @@ export default function Credits() {
{isLoading ? ( - ) : ( + ) : isEnterprise ? null : ( {formatCurrency(credits ?? 0)} )}

- Automatically applied to invoices. Subscription costs excluded. + {isEnterprise ? ( + <> + Automatically applied to invoices{' '} + as per contract + + ) : ( + 'Automatically applied to invoices. Subscription costs excluded.' + )}

diff --git a/src/features/dashboard/billing/invoices.tsx b/src/features/dashboard/billing/invoices.tsx index 947a52de8..e88be89eb 100644 --- a/src/features/dashboard/billing/invoices.tsx +++ b/src/features/dashboard/billing/invoices.tsx @@ -21,7 +21,9 @@ import { TableLoadingState, TableRow, } from '@/ui/primitives/table' +import { useDashboard } from '../context' import { useInvoices } from './hooks' +import { isEnterpriseTier } from './utils' const COLUMN_WIDTHS = { date: 120, @@ -43,9 +45,14 @@ function formatDate(dateString: string) { interface InvoicesEmptyProps { error?: string + isEnterprise?: boolean } -function InvoicesEmpty({ error }: InvoicesEmptyProps) { +function InvoicesEmpty({ error, isEnterprise }: InvoicesEmptyProps) { + const emptyMessage = isEnterprise + ? 'Invoices are sent directly to your company via email.' + : 'No invoices yet' + return ( - {error ? error : 'No invoices yet'} + {error ? error : emptyMessage} ) @@ -63,6 +70,8 @@ function InvoicesEmpty({ error }: InvoicesEmptyProps) { export default function BillingInvoicesTable() { const { invoices, isLoading, error } = useInvoices() + const { team } = useDashboard() + const isEnterprise = isEnterpriseTier(team.tier) const hasData = invoices && invoices.length > 0 const showLoader = isLoading && !hasData @@ -99,7 +108,9 @@ export default function BillingInvoicesTable() { {showLoader && ( )} - {showEmpty && } + {showEmpty && ( + + )} {hasData && invoices.map((invoice) => ( diff --git a/src/features/dashboard/billing/select-plan.tsx b/src/features/dashboard/billing/select-plan.tsx index 5fd02aafa..c4d266ad2 100644 --- a/src/features/dashboard/billing/select-plan.tsx +++ b/src/features/dashboard/billing/select-plan.tsx @@ -24,7 +24,12 @@ import { useDashboard } from '../context' import { TIER_BASE_ID, TIER_PRO_ID } from './constants' import { useBillingItems } from './hooks' import { TierAvatarBorder } from './tier-avatar-border' -import { formatHours, formatMibToGb, formatTierDisplayName } from './utils' +import { + formatHours, + formatMibToGb, + formatTierDisplayName, + isEnterpriseTier, +} from './utils' interface PlanFeature { icon: React.ReactNode @@ -121,6 +126,7 @@ interface PlanCardProps { isLoading?: boolean onSelectPlan: () => void isSelectingPlan: boolean + disabled?: boolean } function PlanCardSkeleton() { @@ -154,6 +160,7 @@ function PlanCard({ isLoading, onSelectPlan, isSelectingPlan, + disabled, }: PlanCardProps) { const { team } = useDashboard() @@ -197,8 +204,10 @@ function PlanCard({ {priceDisplay} - {isCurrentPlan ? ( - + {isCurrentPlan || disabled ? ( + ) : (
@@ -285,6 +294,7 @@ export default function SelectPlan() { const isOnBaseTier = selectedTierId === TIER_BASE_ID const isOnProTier = selectedTierId === TIER_PRO_ID + const isEnterprise = selectedTierId ? isEnterpriseTier(selectedTierId) : false const hobbyFeatures = getHobbyFeatures(baseTier) const proFeatures = getProFeatures(proTier) @@ -299,6 +309,7 @@ export default function SelectPlan() { isLoading={isLoading} onSelectPlan={handleDowngrade} isSelectingPlan={isPortalLoading} + disabled={isEnterprise} /> proTier?.id && handleUpgrade(proTier.id)} isSelectingPlan={isCheckoutLoading} + disabled={isEnterprise} />
) diff --git a/src/features/dashboard/billing/selected-plan.tsx b/src/features/dashboard/billing/selected-plan.tsx index fad5fc171..086eb2e5e 100644 --- a/src/features/dashboard/billing/selected-plan.tsx +++ b/src/features/dashboard/billing/selected-plan.tsx @@ -27,7 +27,12 @@ import { useDashboard } from '../context' import { useBillingItems } from './hooks' import { TierAvatarBorder } from './tier-avatar-border' import type { BillingTierData } from './types' -import { formatHours, formatMibToGb, formatTierDisplayName } from './utils' +import { + formatHours, + formatMibToGb, + formatTierDisplayName, + isEnterpriseTier, +} from './utils' function formatCpu(vcpu: number): string { return `${vcpu} vCPU` @@ -88,6 +93,7 @@ function PlanDetails({ isLoading, }: PlanDetailsProps) { const isBaseTier = !selectedTier || selectedTier.id.includes('base') + const isEnterprise = selectedTier ? isEnterpriseTier(selectedTier.id) : false const { teamSlug } = useRouteParams<'/dashboard/[teamSlug]/billing'>() const pathname = usePathname() const router = useRouter() @@ -116,44 +122,50 @@ function PlanDetails({ return (
- - -
- {isOnPlanPage ? ( - - ) : isLoading ? ( - - ) : ( - <> - {isBaseTier ? ( - - ) : ( - - )} - - - )} -
+ ) : isLoading ? ( + + ) : ( + <> + {isBaseTier ? ( + + ) : ( + + )} + + + )} +
+ )}
@@ -166,9 +178,10 @@ function PlanDetails({ interface PlanTitleProps { selectedTier: BillingTierData['selected'] isLoading: boolean + isEnterprise?: boolean } -function PlanTitle({ selectedTier, isLoading }: PlanTitleProps) { +function PlanTitle({ selectedTier, isLoading, isEnterprise }: PlanTitleProps) { return (
@@ -179,11 +192,13 @@ function PlanTitle({ selectedTier, isLoading }: PlanTitleProps) { {selectedTier ? formatTierDisplayName(selectedTier.name) : null} - - {selectedTier?.price_cents - ? `${formatCurrency(selectedTier.price_cents / 100)}/mo` - : 'FREE'} - + {!isEnterprise && ( + + {selectedTier?.price_cents + ? `${formatCurrency(selectedTier.price_cents / 100)}/mo` + : 'FREE'} + + )}
)} diff --git a/src/features/dashboard/billing/utils.ts b/src/features/dashboard/billing/utils.ts index a0aaa4bed..9a5157e3d 100644 --- a/src/features/dashboard/billing/utils.ts +++ b/src/features/dashboard/billing/utils.ts @@ -3,9 +3,14 @@ import { l } from '@/core/shared/clients/logger/logger' import { ADDON_500_SANDBOXES_ID, TIER_BASE_ID, TIER_PRO_ID } from './constants' import type { BillingAddonData, BillingTierData } from './types' +export function isEnterpriseTier(tierIdOrName: string): boolean { + return tierIdOrName.toLowerCase().includes('enterprise') +} + export function formatTierDisplayName(name: string): string { if (name.toLowerCase().includes('base')) return 'Hobby' if (name.toLowerCase().includes('pro')) return 'Professional' + if (isEnterpriseTier(name)) return 'Enterprise' return name }