Skip to content

Commit 45af563

Browse files
authored
fix(billing): bump better-auth version & fix existing subscription issue when adding seats (#484)
* bump better-auth version & fix existing subscription issue Bwhen adding seats * ack PR comments
1 parent bf579d8 commit 45af563

File tree

10 files changed

+254
-136
lines changed

10 files changed

+254
-136
lines changed
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { useEffect, useState } from 'react'
2+
import { Button } from '@/components/ui/button'
3+
import {
4+
Dialog,
5+
DialogContent,
6+
DialogDescription,
7+
DialogFooter,
8+
DialogHeader,
9+
DialogTitle,
10+
} from '@/components/ui/dialog'
11+
import { Label } from '@/components/ui/label'
12+
import {
13+
Select,
14+
SelectContent,
15+
SelectItem,
16+
SelectTrigger,
17+
SelectValue,
18+
} from '@/components/ui/select'
19+
import { env } from '@/lib/env'
20+
21+
interface TeamSeatsDialogProps {
22+
open: boolean
23+
onOpenChange: (open: boolean) => void
24+
title: string
25+
description: string
26+
currentSeats?: number
27+
initialSeats?: number
28+
isLoading: boolean
29+
onConfirm: (seats: number) => Promise<void>
30+
confirmButtonText: string
31+
showCostBreakdown?: boolean
32+
}
33+
34+
export function TeamSeatsDialog({
35+
open,
36+
onOpenChange,
37+
title,
38+
description,
39+
currentSeats,
40+
initialSeats = 1,
41+
isLoading,
42+
onConfirm,
43+
confirmButtonText,
44+
showCostBreakdown = false,
45+
}: TeamSeatsDialogProps) {
46+
const [selectedSeats, setSelectedSeats] = useState(initialSeats)
47+
48+
useEffect(() => {
49+
if (open) {
50+
setSelectedSeats(initialSeats)
51+
}
52+
}, [open, initialSeats])
53+
54+
const costPerSeat = env.TEAM_TIER_COST_LIMIT ?? 40
55+
const totalMonthlyCost = selectedSeats * costPerSeat
56+
const costChange = currentSeats ? (selectedSeats - currentSeats) * costPerSeat : 0
57+
58+
const handleConfirm = async () => {
59+
await onConfirm(selectedSeats)
60+
}
61+
62+
return (
63+
<Dialog open={open} onOpenChange={onOpenChange}>
64+
<DialogContent>
65+
<DialogHeader>
66+
<DialogTitle>{title}</DialogTitle>
67+
<DialogDescription>{description}</DialogDescription>
68+
</DialogHeader>
69+
70+
<div className='py-4'>
71+
<Label htmlFor='seats'>Number of seats</Label>
72+
<Select
73+
value={selectedSeats.toString()}
74+
onValueChange={(value) => setSelectedSeats(Number.parseInt(value))}
75+
>
76+
<SelectTrigger id='seats'>
77+
<SelectValue placeholder='Select number of seats' />
78+
</SelectTrigger>
79+
<SelectContent>
80+
{[1, 2, 3, 4, 5, 10, 15, 20, 25, 30, 40, 50].map((num) => (
81+
<SelectItem key={num} value={num.toString()}>
82+
{num} {num === 1 ? 'seat' : 'seats'} (${num * costPerSeat}/month)
83+
</SelectItem>
84+
))}
85+
</SelectContent>
86+
</Select>
87+
88+
<p className='mt-2 text-muted-foreground text-sm'>
89+
Your team will have {selectedSeats} {selectedSeats === 1 ? 'seat' : 'seats'} with a
90+
total of ${totalMonthlyCost} inference credits per month.
91+
</p>
92+
93+
{showCostBreakdown && currentSeats !== undefined && (
94+
<div className='mt-3 rounded-md bg-muted/50 p-3'>
95+
<div className='flex justify-between text-sm'>
96+
<span>Current seats:</span>
97+
<span>{currentSeats}</span>
98+
</div>
99+
<div className='flex justify-between text-sm'>
100+
<span>New seats:</span>
101+
<span>{selectedSeats}</span>
102+
</div>
103+
<div className='mt-2 flex justify-between border-t pt-2 font-medium text-sm'>
104+
<span>Monthly cost change:</span>
105+
<span>
106+
{costChange > 0 ? '+' : ''}${costChange}
107+
</span>
108+
</div>
109+
</div>
110+
)}
111+
</div>
112+
113+
<DialogFooter>
114+
<Button variant='outline' onClick={() => onOpenChange(false)} disabled={isLoading}>
115+
Cancel
116+
</Button>
117+
<Button
118+
onClick={handleConfirm}
119+
disabled={isLoading || (showCostBreakdown && selectedSeats === currentSeats)}
120+
>
121+
{isLoading ? (
122+
<div className='flex items-center space-x-2'>
123+
<div className='h-4 w-4 animate-spin rounded-full border-2 border-current border-b-transparent' />
124+
<span>Loading...</span>
125+
</div>
126+
) : (
127+
<span>{confirmButtonText}</span>
128+
)}
129+
</Button>
130+
</DialogFooter>
131+
</DialogContent>
132+
</Dialog>
133+
)
134+
}

apps/sim/app/w/components/sidebar/components/settings-modal/components/subscription/subscription.tsx

Lines changed: 19 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,12 @@ import { useEffect, useState } from 'react'
22
import { AlertCircle } from 'lucide-react'
33
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
44
import { Button } from '@/components/ui/button'
5-
import {
6-
Dialog,
7-
DialogContent,
8-
DialogDescription,
9-
DialogFooter,
10-
DialogHeader,
11-
DialogTitle,
12-
} from '@/components/ui/dialog'
13-
import { Label } from '@/components/ui/label'
145
import { Progress } from '@/components/ui/progress'
15-
import {
16-
Select,
17-
SelectContent,
18-
SelectItem,
19-
SelectTrigger,
20-
SelectValue,
21-
} from '@/components/ui/select'
226
import { Skeleton } from '@/components/ui/skeleton'
237
import { useActiveOrganization, useSession, useSubscription } from '@/lib/auth-client'
8+
import { env } from '@/lib/env'
249
import { createLogger } from '@/lib/logs/console-logger'
10+
import { TeamSeatsDialog } from './components/team-seats-dialog'
2511

2612
const logger = createLogger('Subscription')
2713

@@ -332,7 +318,7 @@ export function Subscription({
332318
setIsTeamDialogOpen(true)
333319
}
334320

335-
const confirmTeamUpgrade = async () => {
321+
const confirmTeamUpgrade = async (selectedSeats?: number) => {
336322
if (!session?.user) {
337323
setError('You need to be logged in to upgrade your team subscription')
338324
return
@@ -341,10 +327,12 @@ export function Subscription({
341327
setIsUpgradingTeam(true)
342328
setError(null)
343329

330+
const seatsToUse = selectedSeats || seats
331+
344332
try {
345333
const result = await subscription.upgrade({
346334
plan: 'team',
347-
seats,
335+
seats: seatsToUse,
348336
successUrl: window.location.href,
349337
cancelUrl: window.location.href,
350338
})
@@ -816,54 +804,19 @@ export function Subscription({
816804
</div>
817805
)}
818806

819-
<Dialog open={isTeamDialogOpen} onOpenChange={setIsTeamDialogOpen}>
820-
<DialogContent>
821-
<DialogHeader>
822-
<DialogTitle>Team Subscription</DialogTitle>
823-
<DialogDescription>
824-
Set up a team workspace with collaborative features. Each seat costs $40/month and
825-
gets $40 of inference credits.
826-
</DialogDescription>
827-
</DialogHeader>
828-
829-
<div className='py-4'>
830-
<Label htmlFor='seats'>Number of seats</Label>
831-
<Select
832-
value={seats.toString()}
833-
onValueChange={(value) => setSeats(Number.parseInt(value))}
834-
>
835-
<SelectTrigger id='seats'>
836-
<SelectValue placeholder='Select number of seats' />
837-
</SelectTrigger>
838-
<SelectContent>
839-
{[1, 2, 3, 4, 5, 10, 15, 20, 25, 30, 40, 50].map((num) => (
840-
<SelectItem key={num} value={num.toString()}>
841-
{num} {num === 1 ? 'seat' : 'seats'} (${num * 40}/month)
842-
</SelectItem>
843-
))}
844-
</SelectContent>
845-
</Select>
846-
847-
<p className='mt-2 text-muted-foreground text-sm'>
848-
Your team will have {seats} {seats === 1 ? 'seat' : 'seats'} with a total of $
849-
{seats * 40} inference credits per month.
850-
</p>
851-
</div>
852-
853-
<DialogFooter>
854-
<Button
855-
variant='outline'
856-
onClick={() => setIsTeamDialogOpen(false)}
857-
disabled={isUpgradingTeam}
858-
>
859-
Cancel
860-
</Button>
861-
<Button onClick={confirmTeamUpgrade} disabled={isUpgradingTeam}>
862-
{isUpgradingTeam ? <ButtonSkeleton /> : <span>Upgrade to Team Plan</span>}
863-
</Button>
864-
</DialogFooter>
865-
</DialogContent>
866-
</Dialog>
807+
<TeamSeatsDialog
808+
open={isTeamDialogOpen}
809+
onOpenChange={setIsTeamDialogOpen}
810+
title='Team Subscription'
811+
description={`Set up a team workspace with collaborative features. Each seat costs $${env.TEAM_TIER_COST_LIMIT}/month and gets $${env.TEAM_TIER_COST_LIMIT} of inference credits.`}
812+
initialSeats={seats}
813+
isLoading={isUpgradingTeam}
814+
onConfirm={async (selectedSeats: number) => {
815+
setSeats(selectedSeats)
816+
await confirmTeamUpgrade(selectedSeats)
817+
}}
818+
confirmButtonText='Upgrade to Team Plan'
819+
/>
867820
</>
868821
)}
869822
</div>

0 commit comments

Comments
 (0)