Skip to content

Commit 36bc57f

Browse files
authored
fix(ui): polish subscription billing settings (#3781)
* fix(ui): polish subscription billing settings Made-with: Cursor * fix(ui): trigger purchase refresh on success Made-with: Cursor
1 parent 2771b67 commit 36bc57f

File tree

2 files changed

+63
-32
lines changed

2 files changed

+63
-32
lines changed

apps/sim/app/workspace/[workspaceId]/settings/components/subscription/components/credit-balance/credit-balance.tsx

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { useState } from 'react'
3+
import { useEffect, useRef, useState } from 'react'
44
import {
55
Button,
66
Input,
@@ -39,10 +39,55 @@ export function CreditBalance({
3939
const [validationError, setValidationError] = useState<string | null>(null)
4040
const [requestId, setRequestId] = useState<string | null>(null)
4141
const purchaseCredits = usePurchaseCredits()
42+
const closeTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
4243

4344
const dollarAmount = Number.parseInt(amount, 10) || 0
4445
const creditPreview = dollarsToCredits(dollarAmount)
4546

47+
const clearCloseTimeout = () => {
48+
if (closeTimeoutRef.current) {
49+
clearTimeout(closeTimeoutRef.current)
50+
closeTimeoutRef.current = null
51+
}
52+
}
53+
54+
const resetModalState = () => {
55+
setAmount('')
56+
setValidationError(null)
57+
purchaseCredits.reset()
58+
}
59+
60+
const openModal = () => {
61+
clearCloseTimeout()
62+
resetModalState()
63+
setRequestId(crypto.randomUUID())
64+
setIsOpen(true)
65+
}
66+
67+
const closeModal = () => {
68+
clearCloseTimeout()
69+
setIsOpen(false)
70+
setRequestId(null)
71+
resetModalState()
72+
}
73+
74+
useEffect(() => {
75+
return () => {
76+
clearCloseTimeout()
77+
}
78+
}, [])
79+
80+
const handleOpenChange = (open: boolean) => {
81+
if (open) {
82+
openModal()
83+
return
84+
}
85+
86+
if (!purchaseCredits.isPending) {
87+
closeModal()
88+
}
89+
}
90+
4691
const handleAmountChange = (value: string) => {
4792
const numericValue = value.replace(/[^0-9]/g, '')
4893
setAmount(numericValue)
@@ -68,27 +113,16 @@ export function CreditBalance({
68113
{ amount: numAmount, requestId },
69114
{
70115
onSuccess: () => {
71-
setTimeout(() => {
72-
setIsOpen(false)
73-
onPurchaseComplete?.()
116+
onPurchaseComplete?.()
117+
clearCloseTimeout()
118+
closeTimeoutRef.current = setTimeout(() => {
119+
closeModal()
74120
}, 1500)
75121
},
76122
}
77123
)
78124
}
79125

80-
const handleOpenChange = (open: boolean) => {
81-
setIsOpen(open)
82-
if (open) {
83-
setRequestId(crypto.randomUUID())
84-
} else {
85-
setAmount('')
86-
setValidationError(null)
87-
purchaseCredits.reset()
88-
setRequestId(null)
89-
}
90-
}
91-
92126
const displayError = validationError || purchaseCredits.error?.message
93127

94128
return (
@@ -103,9 +137,7 @@ export function CreditBalance({
103137
{canPurchase && (
104138
<Modal open={isOpen} onOpenChange={handleOpenChange}>
105139
<ModalTrigger asChild>
106-
<Button variant='active' className='h-[32px] text-[13px]'>
107-
Add Credits
108-
</Button>
140+
<Button variant='active'>Add Credits</Button>
109141
</ModalTrigger>
110142
<ModalContent size='sm'>
111143
<ModalHeader>Add Credits</ModalHeader>

apps/sim/app/workspace/[workspaceId]/settings/components/subscription/subscription.tsx

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,9 @@ function CreditPlanCard({
192192

193193
return (
194194
<article className='flex flex-1 flex-col overflow-hidden rounded-[6px] border border-[var(--border-1)] bg-[var(--surface-5)]'>
195-
<div className='flex items-center justify-between gap-[8px] px-[14px] py-[10px]'>
195+
<div className='flex min-h-[44px] items-center justify-between gap-[8px] px-[14px] py-[10px]'>
196196
<span className='font-medium text-[14px] text-[var(--text-primary)]'>{name}</span>
197-
<div className='flex items-baseline gap-[4px]'>
197+
<div className='flex shrink-0 items-baseline gap-[4px] whitespace-nowrap'>
198198
<span className='font-medium text-[14px] text-[var(--text-primary)]'>
199199
${isAnnual ? discountedMonthly : dollars}
200200
</span>
@@ -207,7 +207,7 @@ function CreditPlanCard({
207207
</div>
208208
</div>
209209

210-
<div className='flex items-center gap-[12px] border-[var(--border-1)] border-t bg-[var(--surface-4)] px-[14px] py-[10px]'>
210+
<div className='flex items-center gap-[12px] rounded-t-[8px] border-[var(--border-1)] border-t bg-[var(--surface-4)] px-[14px] py-[10px]'>
211211
<div className='flex flex-col'>
212212
<span className='font-semibold text-[18px] text-[var(--text-primary)]'>
213213
{credits.toLocaleString()}
@@ -242,13 +242,13 @@ function CreditPlanCard({
242242
</div>
243243
)}
244244

245-
<div className='border-[var(--border-1)] border-t bg-[var(--surface-4)] px-[14px] py-[14px]'>
245+
<div className='flex min-h-[60px] items-center border-[var(--border-1)] border-t bg-[var(--surface-4)] px-[14px] py-[14px]'>
246246
{isCurrentPlan ? (
247-
<Button onClick={onManagePlan} className='w-full' variant='default'>
247+
<Button onClick={onManagePlan} className='h-[32px] w-full' variant='default'>
248248
{isCancelledAtPeriodEnd ? 'Restore Subscription' : 'Manage plan'}
249249
</Button>
250250
) : (
251-
<Button onClick={onButtonClick} className='w-full' variant='primary'>
251+
<Button onClick={onButtonClick} className='h-[32px] w-full' variant='primary'>
252252
{buttonText}
253253
</Button>
254254
)}
@@ -933,9 +933,9 @@ export function Subscription() {
933933

934934
{/* Billing details section */}
935935
{(subscription.isPaid || (!isLoading && isTeamAdmin)) && (
936-
<div className='flex flex-col'>
936+
<div className='flex flex-col gap-[16px]'>
937937
{subscription.isPaid && permissions.canViewUsageInfo && (
938-
<div className='py-[2px]'>
938+
<div>
939939
<CreditBalance
940940
balance={subscriptionData?.data?.creditBalance ?? 0}
941941
canPurchase={hasUsablePaidAccess && permissions.canEditUsageLimit}
@@ -952,7 +952,7 @@ export function Subscription() {
952952
subscriptionData?.data?.periodEnd &&
953953
!permissions.showTeamMemberView &&
954954
!permissions.isEnterpriseMember && (
955-
<div className='flex items-center justify-between border-[var(--border-1)] border-t pt-[16px]'>
955+
<div className='flex items-center justify-between gap-[16px]'>
956956
<Label>{isCancelledAtPeriodEnd ? 'Access Until' : 'Next Billing Date'}</Label>
957957
<span className='text-[13px] text-[var(--text-secondary)]'>
958958
{new Date(subscriptionData.data.periodEnd).toLocaleDateString()}
@@ -961,19 +961,18 @@ export function Subscription() {
961961
)}
962962

963963
{subscription.isPaid && permissions.canViewUsageInfo && (
964-
<div className='border-[var(--border-1)] border-t pt-[16px]'>
964+
<div>
965965
<BillingUsageNotificationsToggle />
966966
</div>
967967
)}
968968

969969
{subscription.isPaid &&
970970
!permissions.showTeamMemberView &&
971971
!permissions.isEnterpriseMember && (
972-
<div className='flex items-center justify-between border-[var(--border-1)] border-t pt-[16px]'>
972+
<div className='flex items-center justify-between gap-[16px]'>
973973
<Label>Invoices</Label>
974974
<Button
975975
variant='active'
976-
size='sm'
977976
disabled={openBillingPortal.isPending}
978977
onClick={() => {
979978
const portalWindow = window.open('', '_blank')
@@ -1008,7 +1007,7 @@ export function Subscription() {
10081007
)}
10091008

10101009
{!isLoading && isTeamAdmin && (
1011-
<div className='flex items-center justify-between border-[var(--border-1)] border-t pt-[16px]'>
1010+
<div className='flex items-center justify-between gap-[16px]'>
10121011
<div className='flex items-center gap-[6px]'>
10131012
<Label htmlFor='billed-account'>Billed Account</Label>
10141013
<Tooltip.Root>

0 commit comments

Comments
 (0)