Skip to content

Commit 3c4b792

Browse files
change CTA text for upsell dialog to be dependent on role
1 parent 501b1f1 commit 3c4b792

5 files changed

Lines changed: 81 additions & 29 deletions

File tree

packages/web/src/app/(app)/@sidebar/components/sidebarBase.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export function SidebarBase({ session, collapsible = "icon", headerContent, chil
8989
{children}
9090
</SidebarContent>
9191
<SidebarFooter className="border-t border-sidebar-border">
92-
{!isValidLicenseActive && isOwner && <UpgradeButton />}
92+
{!isValidLicenseActive && <UpgradeButton />}
9393
{collapsible !== "none" && <CollapseSidebarButton />}
9494
<WhatsNewSidebarButton />
9595
{session ? (

packages/web/src/app/(app)/layout.tsx

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { ServiceErrorException } from "@/lib/serviceError";
3333
import { ConnectAccountsCard } from "@/ee/features/sso/components/connectAccountsCard";
3434
import { SidebarProvider } from "@/components/ui/sidebar";
3535
import { CheckoutReturnHandler } from "@/ee/features/lighthouse/checkoutReturnHandler";
36+
import { RoleProvider } from "@/features/auth/roleProvider";
3637

3738
interface LayoutProps {
3839
children: React.ReactNode;
@@ -169,31 +170,32 @@ export default async function Layout(props: LayoutProps) {
169170
: await __unsafePrisma.license.findUnique({ where: { orgId: org.id } });
170171

171172
return (
172-
<SyntaxGuideProvider>
173-
174-
<div className="fixed inset-0 flex bg-shell">
175-
<SidebarProvider defaultOpen={cookieStore.get("sidebar_state")?.value !== "false"}>
176-
{sidebar}
177-
<div className="flex-1 min-h-0 flex flex-col pt-2 pb-2 pr-2">
178-
<div className="flex-1 min-h-0 bg-background flex flex-col border border-[#e6e6e6] dark:border-[#1d1d1f] rounded-xl overflow-hidden">
179-
<BannerSlot
180-
role={role}
181-
license={license}
182-
offlineLicense={offlineLicense}
183-
hasPermissionSyncEntitlement={hasPermissionSyncEntitlement}
184-
hasPendingFirstSync={hasPendingFirstSync}
185-
/>
186-
<div className="flex-1 min-h-0 overflow-y-auto">
187-
{children}
173+
<RoleProvider role={role}>
174+
<SyntaxGuideProvider>
175+
<div className="fixed inset-0 flex bg-shell">
176+
<SidebarProvider defaultOpen={cookieStore.get("sidebar_state")?.value !== "false"}>
177+
{sidebar}
178+
<div className="flex-1 min-h-0 flex flex-col pt-2 pb-2 pr-2">
179+
<div className="flex-1 min-h-0 bg-background flex flex-col border border-[#e6e6e6] dark:border-[#1d1d1f] rounded-xl overflow-hidden">
180+
<BannerSlot
181+
role={role}
182+
license={license}
183+
offlineLicense={offlineLicense}
184+
hasPermissionSyncEntitlement={hasPermissionSyncEntitlement}
185+
hasPendingFirstSync={hasPendingFirstSync}
186+
/>
187+
<div className="flex-1 min-h-0 overflow-y-auto">
188+
{children}
189+
</div>
188190
</div>
189191
</div>
190-
</div>
191-
</SidebarProvider>
192-
</div>
193-
<SyntaxReferenceGuide />
194-
<GitHubStarToast />
195-
{env.EXPERIMENT_ASK_GH_ENABLED !== 'true' && <UpgradeToast />}
196-
<CheckoutReturnHandler userEmail={session?.user.email} />
197-
</SyntaxGuideProvider>
192+
</SidebarProvider>
193+
</div>
194+
<SyntaxReferenceGuide />
195+
<GitHubStarToast />
196+
{env.EXPERIMENT_ASK_GH_ENABLED !== 'true' && <UpgradeToast />}
197+
<CheckoutReturnHandler userEmail={session?.user.email} />
198+
</SyntaxGuideProvider>
199+
</RoleProvider>
198200
)
199201
}

packages/web/src/ee/features/lighthouse/upsellDialog.tsx

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { createCheckoutSession } from "@/ee/features/lighthouse/actions";
1010
import { useToast } from "@/components/hooks/use-toast";
1111
import {
1212
Dialog,
13+
DialogClose,
1314
DialogContent,
1415
DialogDescription,
1516
DialogFooter,
@@ -26,6 +27,8 @@ import {
2627
} from "@/components/ui/table";
2728
import { OffersResponse } from "@/ee/features/lighthouse/types";
2829
import { useOffers } from "@/ee/features/lighthouse/useOffers";
30+
import { useRole } from "@/features/auth/useRole";
31+
import { OrgRole } from "@sourcebot/db";
2932

3033
interface FeatureLinkProps {
3134
text: string;
@@ -128,6 +131,8 @@ function UpsellDialogContent({ offers, returnPath }: UpsellDialogContentProps) {
128131
const [billingInterval, setBillingInterval] = useState<BillingInterval>("year");
129132
const [isCheckoutSessionCreating, setIsCheckoutSessionCreating] = useState(false);
130133
const { toast } = useToast();
134+
const role = useRole();
135+
const isOwner = role === OrgRole.OWNER;
131136

132137
const enterprisePrice = formatPrice(
133138
billingInterval === "year" ? offers.pricing.annual.unitAmount : offers.pricing.monthly.unitAmount,
@@ -163,6 +168,15 @@ function UpsellDialogContent({ offers, returnPath }: UpsellDialogContentProps) {
163168
}, [billingInterval, offers.trial.eligible, returnPath, toast]);
164169

165170
const { title, description, buttonText } = useMemo(() => {
171+
// Members can't upgrade the workspace themselves — show them the feature
172+
// table so they understand what's gated, but route them to the owner.
173+
if (!isOwner) {
174+
return {
175+
title: "Enterprise feature",
176+
description: "Ask your workspace owner to upgrade to Enterprise.",
177+
buttonText: "Got it",
178+
}
179+
}
166180
// trial, no cc
167181
if (offers.trial.eligible && !offers.trial.creditCardRequired) {
168182
return {
@@ -187,7 +201,7 @@ function UpsellDialogContent({ offers, returnPath }: UpsellDialogContentProps) {
187201
buttonText: "Upgrade"
188202
}
189203
}
190-
}, [offers.trial.creditCardRequired, offers.trial.durationDays, offers.trial.eligible]);
204+
}, [isOwner, offers.trial.creditCardRequired, offers.trial.durationDays, offers.trial.eligible]);
191205

192206
return (
193207
<>
@@ -278,9 +292,15 @@ function UpsellDialogContent({ offers, returnPath }: UpsellDialogContentProps) {
278292
<ExternalLink className="h-3.5 w-3.5 ml-2" />
279293
</a>
280294
</Button>
281-
<LoadingButton onClick={handlePrimaryAction} loading={isCheckoutSessionCreating}>
282-
{buttonText}
283-
</LoadingButton>
295+
{isOwner ? (
296+
<LoadingButton onClick={handlePrimaryAction} loading={isCheckoutSessionCreating}>
297+
{buttonText}
298+
</LoadingButton>
299+
) : (
300+
<DialogClose asChild>
301+
<Button>{buttonText}</Button>
302+
</DialogClose>
303+
)}
284304
</DialogFooter>
285305
</>
286306
);
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use client';
2+
3+
import { createContext } from "react";
4+
import { OrgRole } from "@sourcebot/db";
5+
6+
export const RoleContext = createContext<{ role: OrgRole | null }>({ role: null });
7+
8+
interface RoleProviderProps {
9+
children: React.ReactNode;
10+
role: OrgRole | null;
11+
}
12+
13+
export const RoleProvider = ({ children, role }: RoleProviderProps) => {
14+
return (
15+
<RoleContext.Provider
16+
value={{ role }}
17+
>
18+
{children}
19+
</RoleContext.Provider>
20+
)
21+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'use client';
2+
3+
import { useContext } from "react";
4+
import { RoleContext } from "./roleProvider";
5+
6+
export const useRole = () => {
7+
const { role } = useContext(RoleContext);
8+
return role;
9+
}

0 commit comments

Comments
 (0)