Skip to content

Commit 0bf55bc

Browse files
authored
improvement(billing): open a Chat plan popup on send instead of a static card above the composer (#9608)
* improvement(billing): open a Chat plan popup on send instead of a static card above the composer * improvement(billing): name the plan upgrade link input
1 parent 133c671 commit 0bf55bc

17 files changed

Lines changed: 207 additions & 81 deletions

File tree

‎apps/sim/app/extension/[workspaceId]/workspace.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { isApiClientError } from '@/lib/api/client/errors'
55
import { ExtensionChat } from '@/app/extension/[workspaceId]/chat'
66
import ExtensionLoading from '@/app/extension/loading'
77
import { ExtensionSignIn } from '@/app/extension/sign-in'
8+
import { ChatPlanModal } from '@/app/workspace/[workspaceId]/home/components/chat-plan-modal'
89
import { BlockVisibilityLoader } from '@/app/workspace/[workspaceId]/providers/block-visibility-loader'
910
import { CustomBlocksLoader } from '@/app/workspace/[workspaceId]/providers/custom-blocks-loader'
1011
import { ProviderModelsLoader } from '@/app/workspace/[workspaceId]/providers/provider-models-loader'
@@ -65,6 +66,7 @@ export function ExtensionWorkspace({ workspaceId }: ExtensionWorkspaceProps) {
6566
>
6667
<ExtensionChat workspaceId={workspaceId} workspaceName={data.workspace.name} />
6768
</PermissionAccessBoundary>
69+
<ChatPlanModal />
6870
</WorkspacePermissionsProvider>
6971
</BrandingProvider>
7072
</WorkspaceHostProvider>

‎apps/sim/app/workspace/[workspaceId]/components/workspace-chrome/workspace-chrome.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { usePathname } from 'next/navigation'
1616
import { getDesktopBridge } from '@/lib/desktop'
1717
import { SidebarChromeProvider } from '@/app/workspace/[workspaceId]/components/workspace-chrome/sidebar-chrome-context'
1818
import { useSidebarPeek } from '@/app/workspace/[workspaceId]/components/workspace-chrome/use-sidebar-peek'
19+
import { ChatPlanModal } from '@/app/workspace/[workspaceId]/home/components/chat-plan-modal'
1920
import { SidebarTooltip } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/sidebar-tooltip'
2021
import { SIDEBAR_NO_MOTION_CLASS } from '@/app/workspace/[workspaceId]/w/components/sidebar/constants'
2122
import { isMobileViewport, useIsMobile } from '@/hooks/use-is-mobile'
@@ -446,6 +447,7 @@ export function WorkspaceChrome({
446447
</div>
447448
)}
448449
{!isFullscreen && <TitleBarHistoryNav />}
450+
<ChatPlanModal />
449451
</div>
450452
)
451453
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
'use client'
2+
3+
import {
4+
ChipModal,
5+
ChipModalBody,
6+
ChipModalDescription,
7+
ChipModalFooter,
8+
ChipModalHeader,
9+
} from '@sim/emcn'
10+
import { useRouter } from 'next/navigation'
11+
import { CHAT_PLAN_REQUIRED_UPGRADE } from '@/lib/billing/chat-plan'
12+
import { rememberSettingsReturnUrl } from '@/lib/navigation/settings-return'
13+
import { usePlanUpgradeLink } from '@/app/workspace/[workspaceId]/home/hooks/use-plan-upgrade-link'
14+
import { useChatPlanModalStore } from '@/stores/modals/chat-plan/store'
15+
16+
/**
17+
* Answers a send the workspace's plan refuses: Chat is on paid plans, with the way to upgrade
18+
* for a viewer who may, and who to ask for one who may not. Render it once per workspace shell.
19+
*/
20+
export function ChatPlanModal() {
21+
const isOpen = useChatPlanModalStore((state) => state.isOpen)
22+
const close = useChatPlanModalStore((state) => state.close)
23+
24+
return (
25+
<ChipModal
26+
open={isOpen}
27+
onOpenChange={(open) => !open && close()}
28+
srTitle='Paid Plan Required'
29+
size='sm'
30+
>
31+
<ChatPlanModalContent onClose={close} />
32+
</ChipModal>
33+
)
34+
}
35+
36+
interface ChatPlanModalContentProps {
37+
onClose: () => void
38+
}
39+
40+
/** Mounted only while the modal shows, so a closed modal reads no session or billing context. */
41+
function ChatPlanModalContent({ onClose }: ChatPlanModalContentProps) {
42+
const router = useRouter()
43+
const { href, opensInNewTab, buttonLabel, canManageBilling, unavailableMessage } =
44+
usePlanUpgradeLink(CHAT_PLAN_REQUIRED_UPGRADE)
45+
46+
const upgrade = () => {
47+
onClose()
48+
if (opensInNewTab) {
49+
window.open(href, '_blank', 'noopener,noreferrer')
50+
return
51+
}
52+
rememberSettingsReturnUrl(href)
53+
router.push(href)
54+
}
55+
56+
return (
57+
<>
58+
<ChipModalHeader onClose={onClose}>Paid Plan Required</ChipModalHeader>
59+
<ChipModalBody>
60+
<ChipModalDescription>
61+
{CHAT_PLAN_REQUIRED_UPGRADE.message}
62+
{!canManageBilling && ` ${unavailableMessage}`}
63+
</ChipModalDescription>
64+
</ChipModalBody>
65+
{canManageBilling ? (
66+
<ChipModalFooter
67+
onCancel={onClose}
68+
primaryAction={{ label: buttonLabel, onClick: upgrade }}
69+
/>
70+
) : (
71+
<ChipModalFooter onCancel={onClose} cancelLabel='Close' defaultAction='dismiss' />
72+
)}
73+
</>
74+
)
75+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { ChatPlanModal } from './chat-plan-modal'

‎apps/sim/app/workspace/[workspaceId]/home/components/chat-plan-notice/chat-plan-notice.tsx‎

Lines changed: 0 additions & 21 deletions
This file was deleted.

‎apps/sim/app/workspace/[workspaceId]/home/components/chat-plan-notice/index.ts‎

Lines changed: 0 additions & 1 deletion
This file was deleted.

‎apps/sim/app/workspace/[workspaceId]/home/components/index.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
export { ChatMessageAttachments } from './chat-message-attachments'
2-
export { ChatPlanNotice } from './chat-plan-notice'
32
export { ChatSurfaceProvider, useChatSurface } from './chat-surface-context'
43
export { ContextMentionIcon } from './context-mention-icon'
54
export { CreditsChip } from './credits-chip'

‎apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/index.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ export {
2727
credentialTagHasVisibleCard,
2828
formatCredentialSubmissionMessage,
2929
isHttpUrl,
30-
PlanUsageLimitDisplay,
3130
parseCredentialSubmissionMessage,
3231
parseCredentialSubmissionProgress,
3332
parseCredentialTagBody,

‎apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/special-tags.tsx‎

Lines changed: 4 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,9 @@ import {
3030
} from '@sim/emcn/icons'
3131
import { isRecordLike, omit } from '@sim/utils/object'
3232
import { useParams } from 'next/navigation'
33-
import { getOrganizationSettingsHref } from '@/components/settings/navigation'
34-
import { useSettingsNavigationTarget } from '@/components/settings/settings-navigation-provider'
3533
import { useSession } from '@/lib/auth/client'
3634
import { CHAT_PLAN_REQUIRED_REASON } from '@/lib/billing/chat-plan'
37-
import {
38-
buildHostedUpgradeUrl,
39-
buildUpgradeHref,
40-
HOSTED_BILLING_SETTINGS_URL,
41-
} from '@/lib/billing/upgrade-reasons'
42-
import { canManageWorkspaceBilling } from '@/lib/billing/workspace-permissions'
4335
import { isBrowserAgentAvailable, sendBrowserPanelAction } from '@/lib/browser-agent/transport'
44-
import { useDeploymentShape } from '@/lib/core/config/deployment-shape'
4536
import { isSafeHttpUrl } from '@/lib/core/utils/urls'
4637
import { readLatestOAuthChatAttempt } from '@/lib/credentials/oauth-chat-attempt'
4738
import { getDesktopBridge } from '@/lib/desktop'
@@ -64,7 +55,6 @@ import { OAUTH_PROVIDERS } from '@/lib/oauth/oauth'
6455
import { getServiceConfigByProviderId } from '@/lib/oauth/utils'
6556
import { organizationSecretNameSchema } from '@/lib/organization-secrets/validation'
6657
import { finishTerminalHandoff, isTerminalAvailable } from '@/lib/terminal/transport'
67-
import { useOptionalOrganizationContext } from '@/app/o/[organizationId]/providers/organization-provider'
6858
import { useChatSurface } from '@/app/workspace/[workspaceId]/home/components/chat-surface-context'
6959
import { ContextMentionIcon } from '@/app/workspace/[workspaceId]/home/components/context-mention-icon'
7060
import {
@@ -93,6 +83,7 @@ import {
9383
} from '@/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/use-oauth-chip-connection'
9484
import { usePersonalCredentialConnection } from '@/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/use-personal-credential-connection'
9585
import { ResourceWorkspaceHost } from '@/app/workspace/[workspaceId]/home/components/resource-workspace-host'
86+
import { usePlanUpgradeLink } from '@/app/workspace/[workspaceId]/home/hooks/use-plan-upgrade-link'
9687
import type {
9788
ChatMessageContext,
9889
MothershipResource,
@@ -121,7 +112,6 @@ import { useTablesList } from '@/hooks/queries/tables'
121112
import { useWorkflows } from '@/hooks/queries/workflows'
122113
import { useWorkspaceFileAtPath, useWorkspaceFileLookup } from '@/hooks/queries/workspace-files'
123114
import { useWorkspaceUsageGate } from '@/hooks/queries/workspace-usage'
124-
import { useSettingsNavigation } from '@/hooks/use-settings-navigation'
125115

126116
export interface OptionsItemData {
127117
title: string
@@ -3500,45 +3490,14 @@ interface PlanUsageLimitDisplayProps {
35003490
* The payer's plan limit, or a free plan that does not include Chat: the card links to where
35013491
* the plan or the limit can change, and a workspace's plan to its upgrade page.
35023492
*/
3503-
export function PlanUsageLimitDisplay({ data }: PlanUsageLimitDisplayProps) {
3504-
const { data: session } = useSession()
3493+
function PlanUsageLimitDisplay({ data }: PlanUsageLimitDisplayProps) {
35053494
const hostContext = useOptionalWorkspaceHostContext()
3506-
const organizationContext = useOptionalOrganizationContext()
3507-
const { getSettingsHref } = useSettingsNavigation()
3508-
const { hosted } = useDeploymentShape()
3509-
const navigationTarget = useSettingsNavigationTarget()
3510-
const opensInNewTab = !hosted || navigationTarget === '_blank'
3495+
const { href, opensInNewTab, buttonLabel, canManageBilling, unavailableMessage } =
3496+
usePlanUpgradeLink(data)
35113497
const planRequired = data.reason === CHAT_PLAN_REQUIRED_REASON
3512-
const buttonLabel = data.action === 'upgrade_plan' ? 'Upgrade Plan' : 'Increase Limit'
3513-
3514-
// Self-hosted plan and limit both live on the hosted account, so local
3515-
// workspace billing roles say nothing about who may change them.
3516-
const href = hosted
3517-
? organizationContext
3518-
? getOrganizationSettingsHref(organizationContext.organization.id, 'billing')
3519-
: planRequired && hostContext
3520-
? buildUpgradeHref(hostContext.workspace.id)
3521-
: getSettingsHref({ section: 'billing' })
3522-
: data.action === 'upgrade_plan'
3523-
? buildHostedUpgradeUrl()
3524-
: HOSTED_BILLING_SETTINGS_URL
3525-
const canManageBilling =
3526-
!hosted ||
3527-
(organizationContext
3528-
? organizationContext.viewer.isAdmin
3529-
: Boolean(hostContext && canManageWorkspaceBilling(hostContext, session?.user?.id)))
35303498
const usageGate = useWorkspaceUsageGate(
35313499
data.action === 'increase_limit' && !canManageBilling ? hostContext?.workspace.id : undefined
35323500
)
3533-
const action = planRequired
3534-
? 'upgrade this workspace’s plan'
3535-
: 'manage this workspace’s usage limits'
3536-
const unavailableMessage =
3537-
organizationContext && !planRequired
3538-
? 'Contact an organization admin to manage usage limits.'
3539-
: organizationContext || hostContext?.hostOrganizationId
3540-
? `Contact an organization admin to ${action}.`
3541-
: `Only the workspace owner can ${action}.`
35423501

35433502
return (
35443503
<InteractionCard

‎apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/mothership-chat.tsx‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ import { inter } from '@/app/_styles/fonts/inter/inter'
3333
import { FindBar } from '@/app/workspace/[workspaceId]/components/find-bar'
3434
import { MessageActions } from '@/app/workspace/[workspaceId]/components/message-actions'
3535
import { ChatMessageAttachments } from '@/app/workspace/[workspaceId]/home/components/chat-message-attachments'
36-
import { ChatPlanNotice } from '@/app/workspace/[workspaceId]/home/components/chat-plan-notice'
3736
import { ChatSurfaceProvider } from '@/app/workspace/[workspaceId]/home/components/chat-surface-context'
3837
import { ComposerScrollArea } from '@/app/workspace/[workspaceId]/home/components/composer-scroll-area'
3938
import {
@@ -1037,7 +1036,6 @@ export function MothershipChat({
10371036
>
10381037
<div className={styles.footerInner}>
10391038
<ComputerUseActivity />
1040-
{!isLoading && <ChatPlanNotice />}
10411039
<QueuedMessages
10421040
messageQueue={messageQueue}
10431041
editingQueuedId={editingQueuedId}

0 commit comments

Comments
 (0)