Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions apps/sim/app/_shell/providers/session-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import type React from 'react'
import { createContext, useCallback, useEffect, useMemo, useState } from 'react'
import { useQueryClient } from '@tanstack/react-query'
import posthog from 'posthog-js'
import { client } from '@/lib/auth/auth-client'

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

const loadSession = useCallback(async () => {
const loadSession = useCallback(async (bypassCache = false) => {
try {
setIsPending(true)
setError(null)
const res = await client.getSession()
const res = bypassCache
? await client.getSession({ query: { disableCookieCache: true } })
: await client.getSession()
setData(res?.data ?? null)
} catch (e) {
setError(e instanceof Error ? e : new Error('Failed to fetch session'))
Expand All @@ -50,8 +54,25 @@ export function SessionProvider({ children }: { children: React.ReactNode }) {
}, [])

useEffect(() => {
loadSession()
}, [loadSession])
// Check if user was redirected after plan upgrade
const params = new URLSearchParams(window.location.search)
const wasUpgraded = params.get('upgraded') === 'true'

if (wasUpgraded) {
params.delete('upgraded')
const newUrl = params.toString()
? `${window.location.pathname}?${params.toString()}`
: window.location.pathname
window.history.replaceState({}, '', newUrl)
}

loadSession(wasUpgraded).then(() => {
if (wasUpgraded) {
queryClient.invalidateQueries({ queryKey: ['organizations'] })
queryClient.invalidateQueries({ queryKey: ['subscription'] })
}
})
}, [loadSession, queryClient])

useEffect(() => {
if (isPending || typeof posthog.identify !== 'function') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Skeleton } from '@/components/ui'
import { useSession } from '@/lib/auth/auth-client'
import { useSubscriptionUpgrade } from '@/lib/billing/client/upgrade'
import { USAGE_THRESHOLDS } from '@/lib/billing/client/usage-visualization'
import { getEffectiveSeats } from '@/lib/billing/subscriptions/utils'
import { cn } from '@/lib/core/utils/cn'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { getUserRole } from '@/lib/workspaces/organization/utils'
Expand Down Expand Up @@ -191,7 +192,13 @@ export function Subscription() {
const [upgradeError, setUpgradeError] = useState<'pro' | 'team' | null>(null)
const usageLimitRef = useRef<UsageLimitRef | null>(null)

const isLoading = isSubscriptionLoading || isUsageLimitLoading || isWorkspaceLoading
const isOrgPlan =
subscriptionData?.data?.plan === 'team' || subscriptionData?.data?.plan === 'enterprise'
const isLoading =
isSubscriptionLoading ||
isUsageLimitLoading ||
isWorkspaceLoading ||
(isOrgPlan && isOrgBillingLoading)

const subscription = {
isFree: subscriptionData?.data?.plan === 'free' || !subscriptionData?.data?.plan,
Expand All @@ -204,7 +211,7 @@ export function Subscription() {
subscriptionData?.data?.status === 'active',
plan: subscriptionData?.data?.plan || 'free',
status: subscriptionData?.data?.status || 'inactive',
seats: organizationBillingData?.totalSeats ?? 0,
seats: getEffectiveSeats(subscriptionData?.data),
}

const usage = {
Expand Down Expand Up @@ -445,16 +452,10 @@ export function Subscription() {
? `${subscription.seats} seats`
: undefined
}
current={
subscription.isEnterprise || subscription.isTeam
? (organizationBillingData?.totalCurrentUsage ?? usage.current)
: usage.current
}
current={usage.current}
limit={
subscription.isEnterprise || subscription.isTeam
? organizationBillingData?.totalUsageLimit ||
organizationBillingData?.minimumBillingAmount ||
usage.limit
? organizationBillingData?.data?.totalUsageLimit
: !subscription.isFree &&
(permissions.canEditUsageLimit || permissions.showTeamMemberView)
? usage.current // placeholder; rightContent will render UsageLimit
Expand All @@ -468,19 +469,31 @@ export function Subscription() {
<UsageLimit
ref={usageLimitRef}
currentLimit={
subscription.isTeam && isTeamAdmin
? organizationBillingData?.totalUsageLimit || usage.limit
(subscription.isTeam || subscription.isEnterprise) &&
isTeamAdmin &&
organizationBillingData?.data
? organizationBillingData.data.totalUsageLimit
: usageLimitData.currentLimit || usage.limit
}
currentUsage={usage.current}
canEdit={permissions.canEditUsageLimit}
minimumLimit={
subscription.isTeam && isTeamAdmin
? organizationBillingData?.minimumBillingAmount || (subscription.isPro ? 20 : 40)
(subscription.isTeam || subscription.isEnterprise) &&
isTeamAdmin &&
organizationBillingData?.data
? organizationBillingData.data.minimumBillingAmount
: usageLimitData.minimumLimit || (subscription.isPro ? 20 : 40)
}
context={subscription.isTeam && isTeamAdmin ? 'organization' : 'user'}
organizationId={subscription.isTeam && isTeamAdmin ? activeOrgId : undefined}
context={
(subscription.isTeam || subscription.isEnterprise) && isTeamAdmin
? 'organization'
: 'user'
}
organizationId={
(subscription.isTeam || subscription.isEnterprise) && isTeamAdmin
? activeOrgId
: undefined
}
onLimitUpdated={() => {
logger.info('Usage limit updated')
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ export function SettingsModal({ open, onOpenChange }: SettingsModalProps) {

const userEmail = session?.user?.email
const userId = session?.user?.id

const userRole = getUserRole(activeOrganization, userEmail)
const isOwner = userRole === 'owner'
const isAdmin = userRole === 'admin'
Expand Down
36 changes: 32 additions & 4 deletions apps/sim/lib/auth/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2184,8 +2184,22 @@ export const auth = betterAuth({
status: subscription.status,
})

const resolvedSubscription =
await ensureOrganizationForTeamSubscription(subscription)
let resolvedSubscription = subscription
try {
resolvedSubscription = await ensureOrganizationForTeamSubscription(subscription)
} catch (orgError) {
logger.error(
'[onSubscriptionComplete] Failed to ensure organization for team subscription',
{
subscriptionId: subscription.id,
referenceId: subscription.referenceId,
plan: subscription.plan,
error: orgError instanceof Error ? orgError.message : String(orgError),
stack: orgError instanceof Error ? orgError.stack : undefined,
}
)
throw orgError
}

await handleSubscriptionCreated(resolvedSubscription)

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

const resolvedSubscription =
await ensureOrganizationForTeamSubscription(subscription)
let resolvedSubscription = subscription
try {
resolvedSubscription = await ensureOrganizationForTeamSubscription(subscription)
} catch (orgError) {
logger.error(
'[onSubscriptionUpdate] Failed to ensure organization for team subscription',
{
subscriptionId: subscription.id,
referenceId: subscription.referenceId,
plan: subscription.plan,
error: orgError instanceof Error ? orgError.message : String(orgError),
stack: orgError instanceof Error ? orgError.stack : undefined,
}
)
throw orgError
}

try {
await syncSubscriptionUsageLimits(resolvedSubscription)
Expand Down
5 changes: 4 additions & 1 deletion apps/sim/lib/billing/client/upgrade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,15 @@ export function useSubscriptionUpgrade() {
}

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

try {
const upgradeParams = {
plan: targetPlan,
referenceId,
successUrl: currentUrl,
successUrl,
cancelUrl: currentUrl,
...(targetPlan === 'team' && { seats: CONSTANTS.INITIAL_TEAM_SEATS }),
} as const
Expand Down
Loading