Skip to content

Commit 860610b

Browse files
improvement(billing): team upgrade + session management (#2751)
* improvement(billng): team upgrade + session management * remove comments * session updates should be atomic * make consistent for onSubscritionUpdate * plan upgrade to refresh session * fix var name * remove dead code * preserve params
1 parent 05bbf34 commit 860610b

File tree

6 files changed

+162
-73
lines changed

6 files changed

+162
-73
lines changed

apps/sim/app/_shell/providers/session-provider.tsx

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import type React from 'react'
44
import { createContext, useCallback, useEffect, useMemo, useState } from 'react'
5+
import { useQueryClient } from '@tanstack/react-query'
56
import posthog from 'posthog-js'
67
import { client } from '@/lib/auth/auth-client'
78

@@ -35,12 +36,15 @@ export function SessionProvider({ children }: { children: React.ReactNode }) {
3536
const [data, setData] = useState<AppSession>(null)
3637
const [isPending, setIsPending] = useState(true)
3738
const [error, setError] = useState<Error | null>(null)
39+
const queryClient = useQueryClient()
3840

39-
const loadSession = useCallback(async () => {
41+
const loadSession = useCallback(async (bypassCache = false) => {
4042
try {
4143
setIsPending(true)
4244
setError(null)
43-
const res = await client.getSession()
45+
const res = bypassCache
46+
? await client.getSession({ query: { disableCookieCache: true } })
47+
: await client.getSession()
4448
setData(res?.data ?? null)
4549
} catch (e) {
4650
setError(e instanceof Error ? e : new Error('Failed to fetch session'))
@@ -50,8 +54,25 @@ export function SessionProvider({ children }: { children: React.ReactNode }) {
5054
}, [])
5155

5256
useEffect(() => {
53-
loadSession()
54-
}, [loadSession])
57+
// Check if user was redirected after plan upgrade
58+
const params = new URLSearchParams(window.location.search)
59+
const wasUpgraded = params.get('upgraded') === 'true'
60+
61+
if (wasUpgraded) {
62+
params.delete('upgraded')
63+
const newUrl = params.toString()
64+
? `${window.location.pathname}?${params.toString()}`
65+
: window.location.pathname
66+
window.history.replaceState({}, '', newUrl)
67+
}
68+
69+
loadSession(wasUpgraded).then(() => {
70+
if (wasUpgraded) {
71+
queryClient.invalidateQueries({ queryKey: ['organizations'] })
72+
queryClient.invalidateQueries({ queryKey: ['subscription'] })
73+
}
74+
})
75+
}, [loadSession, queryClient])
5576

5677
useEffect(() => {
5778
if (isPending || typeof posthog.identify !== 'function') {

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/subscription.tsx

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Skeleton } from '@/components/ui'
88
import { useSession } from '@/lib/auth/auth-client'
99
import { useSubscriptionUpgrade } from '@/lib/billing/client/upgrade'
1010
import { USAGE_THRESHOLDS } from '@/lib/billing/client/usage-visualization'
11+
import { getEffectiveSeats } from '@/lib/billing/subscriptions/utils'
1112
import { cn } from '@/lib/core/utils/cn'
1213
import { getBaseUrl } from '@/lib/core/utils/urls'
1314
import { getUserRole } from '@/lib/workspaces/organization/utils'
@@ -191,7 +192,13 @@ export function Subscription() {
191192
const [upgradeError, setUpgradeError] = useState<'pro' | 'team' | null>(null)
192193
const usageLimitRef = useRef<UsageLimitRef | null>(null)
193194

194-
const isLoading = isSubscriptionLoading || isUsageLimitLoading || isWorkspaceLoading
195+
const isOrgPlan =
196+
subscriptionData?.data?.plan === 'team' || subscriptionData?.data?.plan === 'enterprise'
197+
const isLoading =
198+
isSubscriptionLoading ||
199+
isUsageLimitLoading ||
200+
isWorkspaceLoading ||
201+
(isOrgPlan && isOrgBillingLoading)
195202

196203
const subscription = {
197204
isFree: subscriptionData?.data?.plan === 'free' || !subscriptionData?.data?.plan,
@@ -204,7 +211,7 @@ export function Subscription() {
204211
subscriptionData?.data?.status === 'active',
205212
plan: subscriptionData?.data?.plan || 'free',
206213
status: subscriptionData?.data?.status || 'inactive',
207-
seats: organizationBillingData?.totalSeats ?? 0,
214+
seats: getEffectiveSeats(subscriptionData?.data),
208215
}
209216

210217
const usage = {
@@ -445,16 +452,10 @@ export function Subscription() {
445452
? `${subscription.seats} seats`
446453
: undefined
447454
}
448-
current={
449-
subscription.isEnterprise || subscription.isTeam
450-
? (organizationBillingData?.totalCurrentUsage ?? usage.current)
451-
: usage.current
452-
}
455+
current={usage.current}
453456
limit={
454457
subscription.isEnterprise || subscription.isTeam
455-
? organizationBillingData?.totalUsageLimit ||
456-
organizationBillingData?.minimumBillingAmount ||
457-
usage.limit
458+
? organizationBillingData?.data?.totalUsageLimit
458459
: !subscription.isFree &&
459460
(permissions.canEditUsageLimit || permissions.showTeamMemberView)
460461
? usage.current // placeholder; rightContent will render UsageLimit
@@ -468,19 +469,31 @@ export function Subscription() {
468469
<UsageLimit
469470
ref={usageLimitRef}
470471
currentLimit={
471-
subscription.isTeam && isTeamAdmin
472-
? organizationBillingData?.totalUsageLimit || usage.limit
472+
(subscription.isTeam || subscription.isEnterprise) &&
473+
isTeamAdmin &&
474+
organizationBillingData?.data
475+
? organizationBillingData.data.totalUsageLimit
473476
: usageLimitData.currentLimit || usage.limit
474477
}
475478
currentUsage={usage.current}
476479
canEdit={permissions.canEditUsageLimit}
477480
minimumLimit={
478-
subscription.isTeam && isTeamAdmin
479-
? organizationBillingData?.minimumBillingAmount || (subscription.isPro ? 20 : 40)
481+
(subscription.isTeam || subscription.isEnterprise) &&
482+
isTeamAdmin &&
483+
organizationBillingData?.data
484+
? organizationBillingData.data.minimumBillingAmount
480485
: usageLimitData.minimumLimit || (subscription.isPro ? 20 : 40)
481486
}
482-
context={subscription.isTeam && isTeamAdmin ? 'organization' : 'user'}
483-
organizationId={subscription.isTeam && isTeamAdmin ? activeOrgId : undefined}
487+
context={
488+
(subscription.isTeam || subscription.isEnterprise) && isTeamAdmin
489+
? 'organization'
490+
: 'user'
491+
}
492+
organizationId={
493+
(subscription.isTeam || subscription.isEnterprise) && isTeamAdmin
494+
? activeOrgId
495+
: undefined
496+
}
484497
onLimitUpdated={() => {
485498
logger.info('Usage limit updated')
486499
}}

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/settings-modal.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ export function SettingsModal({ open, onOpenChange }: SettingsModalProps) {
174174

175175
const userEmail = session?.user?.email
176176
const userId = session?.user?.id
177+
177178
const userRole = getUserRole(activeOrganization, userEmail)
178179
const isOwner = userRole === 'owner'
179180
const isAdmin = userRole === 'admin'

apps/sim/lib/auth/auth.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2184,8 +2184,22 @@ export const auth = betterAuth({
21842184
status: subscription.status,
21852185
})
21862186

2187-
const resolvedSubscription =
2188-
await ensureOrganizationForTeamSubscription(subscription)
2187+
let resolvedSubscription = subscription
2188+
try {
2189+
resolvedSubscription = await ensureOrganizationForTeamSubscription(subscription)
2190+
} catch (orgError) {
2191+
logger.error(
2192+
'[onSubscriptionComplete] Failed to ensure organization for team subscription',
2193+
{
2194+
subscriptionId: subscription.id,
2195+
referenceId: subscription.referenceId,
2196+
plan: subscription.plan,
2197+
error: orgError instanceof Error ? orgError.message : String(orgError),
2198+
stack: orgError instanceof Error ? orgError.stack : undefined,
2199+
}
2200+
)
2201+
throw orgError
2202+
}
21892203

21902204
await handleSubscriptionCreated(resolvedSubscription)
21912205

@@ -2206,8 +2220,22 @@ export const auth = betterAuth({
22062220
plan: subscription.plan,
22072221
})
22082222

2209-
const resolvedSubscription =
2210-
await ensureOrganizationForTeamSubscription(subscription)
2223+
let resolvedSubscription = subscription
2224+
try {
2225+
resolvedSubscription = await ensureOrganizationForTeamSubscription(subscription)
2226+
} catch (orgError) {
2227+
logger.error(
2228+
'[onSubscriptionUpdate] Failed to ensure organization for team subscription',
2229+
{
2230+
subscriptionId: subscription.id,
2231+
referenceId: subscription.referenceId,
2232+
plan: subscription.plan,
2233+
error: orgError instanceof Error ? orgError.message : String(orgError),
2234+
stack: orgError instanceof Error ? orgError.stack : undefined,
2235+
}
2236+
)
2237+
throw orgError
2238+
}
22112239

22122240
try {
22132241
await syncSubscriptionUsageLimits(resolvedSubscription)

apps/sim/lib/billing/client/upgrade.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,15 @@ export function useSubscriptionUpgrade() {
8181
}
8282

8383
const currentUrl = `${window.location.origin}${window.location.pathname}`
84+
const successUrlObj = new URL(window.location.href)
85+
successUrlObj.searchParams.set('upgraded', 'true')
86+
const successUrl = successUrlObj.toString()
8487

8588
try {
8689
const upgradeParams = {
8790
plan: targetPlan,
8891
referenceId,
89-
successUrl: currentUrl,
92+
successUrl,
9093
cancelUrl: currentUrl,
9194
...(targetPlan === 'team' && { seats: CONSTANTS.INITIAL_TEAM_SEATS }),
9295
} as const

0 commit comments

Comments
 (0)