Skip to content

Commit 04c10f8

Browse files
committed
[TOOL-4471] GatedSwitch updates, Make In-app wallet branding gated by starter+ (#7021)
<!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on enhancing the billing and plan management features in the dashboard. It introduces new props for handling plan highlights and open plan sheets, modifies tooltip styles, and adjusts billing card components to improve user experience. ### Detailed summary - Updated `tooltip.tsx` to change padding in tooltip content. - Changed `requiredPlan` from `authRequiredPlan` to `brandingRequiredPlan` in `Configure/index.tsx`. - Added `openPlanSheetButtonByDefault` and `highlightPlan` props in various components. - Modified `PlanInfoCardClient` and `Billing` components to utilize new props. - Updated `TeamPlanBadge` to include an optional postfix. - Enhanced `BillingPricing` logic to handle plan highlights. - Adjusted `GatedSwitch` tooltip content for better user guidance. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent ab17388 commit 04c10f8

File tree

10 files changed

+107
-32
lines changed

10 files changed

+107
-32
lines changed

apps/dashboard/src/@/components/ui/tooltip.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,11 @@ export function ToolTipLabel(props: {
5454
align={props.align}
5555
sideOffset={10}
5656
className={cn(
57-
"max-w-[400px] whitespace-normal leading-relaxed",
57+
"max-w-[400px] whitespace-normal p-0 leading-relaxed",
5858
props.contentClassName,
5959
)}
6060
>
61-
<div className="flex items-center gap-1.5 p-2 text-sm">
61+
<div className="flex items-center gap-1.5 p-4 text-sm">
6262
{props.leftIcon}
6363
{props.label}
6464
{props.rightIcon}

apps/dashboard/src/app/(app)/components/TeamPlanBadge.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ const teamPlanToBadgeVariant: Record<
88
> = {
99
// gray
1010
free: "secondary",
11-
starter: "secondary",
1211
// yellow
12+
starter: "warning",
1313
starter_legacy: "warning",
1414
growth_legacy: "warning",
1515
// green
@@ -20,7 +20,7 @@ const teamPlanToBadgeVariant: Record<
2020
pro: "default",
2121
};
2222

23-
function getTeamPlanBadgeLabel(plan: Team["billingPlan"]) {
23+
export function getTeamPlanBadgeLabel(plan: Team["billingPlan"]) {
2424
if (plan === "growth_legacy") {
2525
return "Growth - Legacy";
2626
}
@@ -33,13 +33,14 @@ function getTeamPlanBadgeLabel(plan: Team["billingPlan"]) {
3333
export function TeamPlanBadge(props: {
3434
plan: Team["billingPlan"];
3535
className?: string;
36+
postfix?: string;
3637
}) {
3738
return (
3839
<Badge
3940
variant={teamPlanToBadgeVariant[props.plan]}
4041
className={cn("px-1.5 capitalize", props.className)}
4142
>
42-
{getTeamPlanBadgeLabel(props.plan)}
43+
{`${getTeamPlanBadgeLabel(props.plan)}${props.postfix || ""}`}
4344
</Badge>
4445
);
4546
}

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/settings/billing/components/PlanInfoCard.client.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@ import { PlanInfoCardUI } from "./PlanInfoCard";
77
export function PlanInfoCardClient(props: {
88
subscriptions: TeamSubscription[];
99
team: Team;
10+
openPlanSheetButtonByDefault: boolean;
11+
highlightPlan: Team["billingPlan"] | undefined;
1012
}) {
1113
return (
1214
<PlanInfoCardUI
15+
openPlanSheetButtonByDefault={props.openPlanSheetButtonByDefault}
1316
team={props.team}
1417
subscriptions={props.subscriptions}
1518
getTeam={async () => {
@@ -26,6 +29,7 @@ export function PlanInfoCardClient(props: {
2629

2730
return res.data.result;
2831
}}
32+
highlightPlan={props.highlightPlan}
2933
/>
3034
);
3135
}

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/settings/billing/components/PlanInfoCard.stories.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ function Story(props: {
118118
team={team}
119119
subscriptions={zeroUsageOnDemandSubs}
120120
getTeam={teamTeamStub}
121+
highlightPlan={undefined}
122+
openPlanSheetButtonByDefault={false}
121123
/>
122124
</BadgeContainer>
123125

@@ -129,6 +131,8 @@ function Story(props: {
129131
}}
130132
subscriptions={zeroUsageOnDemandSubs}
131133
getTeam={teamTeamStub}
134+
highlightPlan={undefined}
135+
openPlanSheetButtonByDefault={false}
132136
/>
133137
</BadgeContainer>
134138

@@ -137,6 +141,8 @@ function Story(props: {
137141
team={team}
138142
subscriptions={trialPlanZeroUsageOnDemandSubs}
139143
getTeam={teamTeamStub}
144+
highlightPlan={undefined}
145+
openPlanSheetButtonByDefault={false}
140146
/>
141147
</BadgeContainer>
142148

@@ -145,6 +151,8 @@ function Story(props: {
145151
team={team}
146152
subscriptions={subsWith1Usage}
147153
getTeam={teamTeamStub}
154+
highlightPlan={undefined}
155+
openPlanSheetButtonByDefault={false}
148156
/>
149157
</BadgeContainer>
150158

@@ -153,6 +161,8 @@ function Story(props: {
153161
team={team}
154162
subscriptions={subsWith4Usage}
155163
getTeam={teamTeamStub}
164+
highlightPlan={undefined}
165+
openPlanSheetButtonByDefault={false}
156166
/>
157167
</BadgeContainer>
158168
</div>

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/settings/billing/components/PlanInfoCard.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,15 @@ export function PlanInfoCardUI(props: {
2828
subscriptions: TeamSubscription[];
2929
team: Team;
3030
getTeam: () => Promise<Team>;
31+
openPlanSheetButtonByDefault: boolean;
32+
highlightPlan: Team["billingPlan"] | undefined;
3133
}) {
32-
const { subscriptions, team } = props;
34+
const { subscriptions, team, openPlanSheetButtonByDefault } = props;
3335
const validPlan = getValidTeamPlan(team);
3436
const isActualFreePlan = team.billingPlan === "free";
35-
const [isPlanSheetOpen, setIsPlanSheetOpen] = useState(false);
37+
const [isPlanSheetOpen, setIsPlanSheetOpen] = useState(
38+
openPlanSheetButtonByDefault,
39+
);
3640

3741
const planSub = subscriptions.find(
3842
(subscription) => subscription.type === "PLAN",
@@ -54,6 +58,7 @@ export function PlanInfoCardUI(props: {
5458
isOpen={isPlanSheetOpen}
5559
onOpenChange={setIsPlanSheetOpen}
5660
getTeam={props.getTeam}
61+
highlightPlan={props.highlightPlan}
5762
/>
5863

5964
<div className="flex flex-col gap-4 p-4 lg:flex-row lg:items-center lg:justify-between lg:p-6">
@@ -298,6 +303,7 @@ function ViewPlansSheet(props: {
298303
isOpen: boolean;
299304
onOpenChange: (open: boolean) => void;
300305
getTeam: () => Promise<Team>;
306+
highlightPlan: Team["billingPlan"] | undefined;
301307
}) {
302308
return (
303309
<Sheet open={props.isOpen} onOpenChange={props.onOpenChange}>
@@ -309,6 +315,7 @@ function ViewPlansSheet(props: {
309315
team={props.team}
310316
trialPeriodEndedAt={props.trialPeriodEndedAt}
311317
getTeam={props.getTeam}
318+
highlightPlan={props.highlightPlan}
312319
/>
313320
</SheetContent>
314321
</Sheet>

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/settings/billing/page.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getTeamBySlug } from "@/api/team";
1+
import { type Team, getTeamBySlug } from "@/api/team";
22
import { getTeamSubscriptions } from "@/api/team-subscription";
33
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
44
import { Billing } from "components/settings/Account/Billing";
@@ -10,8 +10,13 @@ export default async function Page(props: {
1010
params: Promise<{
1111
team_slug: string;
1212
}>;
13+
searchParams: Promise<{
14+
showPlans?: string | string[];
15+
highlight?: string | string[];
16+
}>;
1317
}) {
1418
const params = await props.params;
19+
const searchParams = await props.searchParams;
1520
const pagePath = `/team/${params.team_slug}/settings/billing`;
1621

1722
const [account, team, authToken] = await Promise.all([
@@ -41,6 +46,12 @@ export default async function Page(props: {
4146

4247
return (
4348
<Billing
49+
highlightPlan={
50+
typeof searchParams.highlight === "string"
51+
? (searchParams.highlight as Team["billingPlan"])
52+
: undefined
53+
}
54+
openPlanSheetButtonByDefault={searchParams.showPlans === "true"}
4455
team={team}
4556
subscriptions={subscriptions}
4657
twAccount={account}

apps/dashboard/src/components/embedded-wallets/Configure/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ export const InAppWalletSettingsUI: React.FC<
179179
!!config.applicationImageUrl?.length || !!config.applicationName?.length;
180180

181181
const authRequiredPlan = "accelerate";
182+
const brandingRequiredPlan = "starter";
182183

183184
// accelerate or higher plan required
184185
const canEditSmsCountries =
@@ -275,7 +276,7 @@ export const InAppWalletSettingsUI: React.FC<
275276
form={form}
276277
teamPlan={props.teamPlan}
277278
teamSlug={props.teamSlug}
278-
requiredPlan={authRequiredPlan}
279+
requiredPlan={brandingRequiredPlan}
279280
/>
280281

281282
<NativeAppsFieldset form={form} />

apps/dashboard/src/components/settings/Account/Billing/GatedSwitch.tsx

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import type { Team } from "@/api/team";
2+
import { Button } from "@/components/ui/button";
23
import { Switch } from "@/components/ui/switch";
34
import { ToolTipLabel } from "@/components/ui/tooltip";
45
import { TrackedLinkTW } from "@/components/ui/tracked-link";
56
import { cn } from "@/lib/utils";
6-
import { TeamPlanBadge } from "../../../../app/(app)/components/TeamPlanBadge";
7+
import { ExternalLinkIcon } from "lucide-react";
8+
import {
9+
TeamPlanBadge,
10+
getTeamPlanBadgeLabel,
11+
} from "../../../../app/(app)/components/TeamPlanBadge";
712
import { planToTierRecordForGating } from "./planToTierRecord";
813

914
type SwitchProps = React.ComponentProps<typeof Switch>;
@@ -26,26 +31,40 @@ export const GatedSwitch: React.FC<GatedSwitchProps> = (
2631
return (
2732
<ToolTipLabel
2833
hoverable
29-
contentClassName="max-w-[300px]"
3034
label={
3135
isUpgradeRequired ? (
32-
<span>
33-
To access this feature, <br /> Upgrade to the{" "}
34-
<TrackedLinkTW
35-
target="_blank"
36-
href={`/team/${props.teamSlug}/~/settings/billing`}
37-
category="advancedFeature"
38-
label={props.trackingLabel}
39-
className="text-link-foreground capitalize hover:text-foreground"
40-
>
41-
{props.requiredPlan} plan
42-
</TrackedLinkTW>
43-
</span>
36+
<div className="w-full min-w-[280px]">
37+
<h3 className="font-medium text-base">
38+
<span className="capitalize">
39+
{getTeamPlanBadgeLabel(props.requiredPlan)}+
40+
</span>{" "}
41+
plan required
42+
</h3>
43+
<p className="mb-3.5 text-muted-foreground">
44+
Upgrade your plan to use this feature
45+
</p>
46+
47+
<div className="flex w-full flex-col gap-2">
48+
<Button asChild size="sm" className="justify-start gap-2">
49+
<TrackedLinkTW
50+
target="_blank"
51+
href={`/team/${props.teamSlug}/~/settings/billing?showPlans=true&highlight=${props.requiredPlan}`}
52+
category="advancedFeature"
53+
label="checkout"
54+
>
55+
{`Upgrade to ${getTeamPlanBadgeLabel(props.requiredPlan)} plan`}
56+
<ExternalLinkIcon className="size-4" />
57+
</TrackedLinkTW>
58+
</Button>
59+
</div>
60+
</div>
4461
) : undefined
4562
}
4663
>
4764
<div className="inline-flex items-center gap-2">
48-
{isUpgradeRequired && <TeamPlanBadge plan={props.requiredPlan} />}
65+
{isUpgradeRequired && (
66+
<TeamPlanBadge plan={props.requiredPlan} postfix="+" />
67+
)}
4968
<Switch
5069
{...props.switchProps}
5170
checked={props.switchProps?.checked && !isUpgradeRequired}

apps/dashboard/src/components/settings/Account/Billing/Pricing.tsx

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ interface BillingPricingProps {
2929
team: Team;
3030
trialPeriodEndedAt: string | undefined;
3131
getTeam: () => Promise<Team>;
32+
highlightPlan: Team["billingPlan"] | undefined;
3233
}
3334

3435
type CtaLink =
@@ -49,6 +50,7 @@ export const BillingPricing: React.FC<BillingPricingProps> = ({
4950
team,
5051
trialPeriodEndedAt,
5152
getTeam,
53+
highlightPlan,
5254
}) => {
5355
const validTeamPlan = getValidTeamPlan(team);
5456
const [isPending, startTransition] = useTransition();
@@ -65,17 +67,28 @@ export const BillingPricing: React.FC<BillingPricingProps> = ({
6567
const isCurrentPlanScheduledToCancel = team.planCancellationDate !== null;
6668

6769
const highlightGrowthPlan =
68-
!isCurrentPlanScheduledToCancel &&
69-
(validTeamPlan === "free" ||
70-
validTeamPlan === "starter" ||
71-
validTeamPlan === "growth_legacy");
70+
highlightPlan === "growth" ||
71+
(!highlightPlan &&
72+
!isCurrentPlanScheduledToCancel &&
73+
(validTeamPlan === "free" ||
74+
validTeamPlan === "starter" ||
75+
validTeamPlan === "growth_legacy"));
7276

7377
const highlightStarterPlan =
74-
!isCurrentPlanScheduledToCancel && validTeamPlan === "starter_legacy";
78+
highlightPlan === "starter" ||
79+
(!highlightPlan &&
80+
!isCurrentPlanScheduledToCancel &&
81+
validTeamPlan === "starter_legacy");
7582
const highlightAcceleratePlan =
76-
!isCurrentPlanScheduledToCancel && validTeamPlan === "growth";
83+
highlightPlan === "accelerate" ||
84+
(!highlightPlan &&
85+
!isCurrentPlanScheduledToCancel &&
86+
validTeamPlan === "growth");
7787
const highlightScalePlan =
78-
!isCurrentPlanScheduledToCancel && validTeamPlan === "accelerate";
88+
highlightPlan === "scale" ||
89+
(!highlightPlan &&
90+
!isCurrentPlanScheduledToCancel &&
91+
validTeamPlan === "accelerate");
7992

8093
return (
8194
<div>

apps/dashboard/src/components/settings/Account/Billing/index.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,30 @@ interface BillingProps {
1313
subscriptions: TeamSubscription[];
1414
twAccount: Account;
1515
client: ThirdwebClient;
16+
openPlanSheetButtonByDefault: boolean;
17+
highlightPlan: Team["billingPlan"] | undefined;
1618
}
1719

1820
export const Billing: React.FC<BillingProps> = ({
1921
team,
2022
subscriptions,
2123
twAccount,
2224
client,
25+
openPlanSheetButtonByDefault,
26+
highlightPlan,
2327
}) => {
2428
const validPayment =
2529
team.billingStatus === "validPayment" || team.billingStatus === "pastDue";
2630

2731
return (
2832
<div className="flex flex-col gap-12">
2933
<div>
30-
<PlanInfoCardClient team={team} subscriptions={subscriptions} />
34+
<PlanInfoCardClient
35+
team={team}
36+
subscriptions={subscriptions}
37+
openPlanSheetButtonByDefault={openPlanSheetButtonByDefault}
38+
highlightPlan={highlightPlan}
39+
/>
3140
</div>
3241

3342
<CreditsInfoCard

0 commit comments

Comments
 (0)