Skip to content

Commit 1e15b7b

Browse files
committed
Make free plan badge clickable to upgrade
1 parent 23029db commit 1e15b7b

File tree

13 files changed

+150
-28
lines changed

13 files changed

+150
-28
lines changed

apps/dashboard/src/app/(app)/account/overview/AccountTeamsUI.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ function TeamRow(props: {
116116
<div>
117117
<div className="flex items-center gap-3">
118118
<p className="font-semibold text-sm">{props.team.name}</p>
119-
<TeamPlanBadge plan={plan} />
119+
<TeamPlanBadge plan={plan} teamSlug={props.team.slug} />
120120
</div>
121121
<p className="text-muted-foreground text-sm capitalize">
122122
{props.role.toLowerCase()}

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
"use client";
2+
13
import type { Team } from "@/api/team";
24
import { Badge, type BadgeProps } from "@/components/ui/badge";
35
import { cn } from "@/lib/utils";
6+
import Link from "next/link";
7+
import { useTrack } from "../../../hooks/analytics/useTrack";
48

59
const teamPlanToBadgeVariant: Record<
610
Team["billingPlan"],
@@ -31,16 +35,38 @@ export function getTeamPlanBadgeLabel(plan: Team["billingPlan"]) {
3135
}
3236

3337
export function TeamPlanBadge(props: {
38+
teamSlug: string;
3439
plan: Team["billingPlan"];
3540
className?: string;
3641
postfix?: string;
3742
}) {
38-
return (
43+
const badge = (
3944
<Badge
4045
variant={teamPlanToBadgeVariant[props.plan]}
4146
className={cn("px-1.5 capitalize", props.className)}
4247
>
4348
{`${getTeamPlanBadgeLabel(props.plan)}${props.postfix || ""}`}
4449
</Badge>
4550
);
51+
52+
const track = useTrack();
53+
54+
if (props.plan === "free") {
55+
return (
56+
<Link
57+
href={`/team/${props.teamSlug}/~/settings/billing?showPlans=true`}
58+
onClick={() => {
59+
track({
60+
category: "billing",
61+
action: "show_plans",
62+
label: "team_badge",
63+
});
64+
}}
65+
>
66+
{badge}
67+
</Link>
68+
);
69+
}
70+
71+
return badge;
4672
}

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/_components/BillingAlertBannersUI.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
import { Spinner } from "@/components/ui/Spinner/Spinner";
44
import { Button } from "@/components/ui/button";
5+
import { TrackedLinkTW } from "@/components/ui/tracked-link";
56
import { useDashboardRouter } from "@/lib/DashboardRouter";
67
import { cn } from "@/lib/utils";
7-
import Link from "next/link";
88
import { useTransition } from "react";
99
import { useStripeRedirectEvent } from "../../../../(stripe)/stripe-redirect/stripeRedirectChannel";
1010

@@ -54,9 +54,17 @@ function BillingAlertBanner(props: {
5454
"border border-red-600 bg-red-100 text-red-800 hover:bg-red-200 dark:border-red-700 dark:bg-red-900 dark:text-red-100 dark:hover:bg-red-800",
5555
)}
5656
>
57-
<Link href={`/team/${props.teamSlug}/~/settings/invoices`}>
57+
<TrackedLinkTW
58+
href={`/team/${props.teamSlug}/~/settings/invoices`}
59+
category="billingBanner"
60+
label={
61+
props.variant === "warning"
62+
? "pastDue_viewInvoices"
63+
: "serviceCutoff_payNow"
64+
}
65+
>
5866
{props.ctaLabel}
59-
</Link>
67+
</TrackedLinkTW>
6068
</Button>
6169
</div>
6270
);
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"use client";
2+
3+
import type { Team } from "@/api/team";
4+
import { Button } from "@/components/ui/button";
5+
import { TrackedLinkTW } from "@/components/ui/tracked-link";
6+
import { cn } from "@/lib/utils";
7+
import { ArrowRightIcon, RocketIcon } from "lucide-react";
8+
9+
/**
10+
* Banner shown to teams on the free plan encouraging them to upgrade.
11+
* It links to the team's billing settings page and automatically opens
12+
* the pricing modal via the `showPlans=true` query param.
13+
*/
14+
export function FreePlanUpsellBannerUI(props: {
15+
teamSlug: string;
16+
highlightPlan: Team["billingPlan"];
17+
}) {
18+
return (
19+
<div
20+
className={cn(
21+
"relative overflow-hidden rounded-lg border border-green-600 bg-gradient-to-r from-green-50 to-transparent p-5 dark:border-green-700 dark:from-green-900/20",
22+
)}
23+
>
24+
{/* Decorative background blur */}
25+
<div className="-right-10 -top-10 pointer-events-none absolute size-28 rounded-full bg-green-600 opacity-20 blur-2xl" />
26+
27+
<div className="relative flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
28+
<div className="flex items-start gap-3">
29+
<div className="mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-green-600 text-white">
30+
<RocketIcon className="size-5" />
31+
</div>
32+
<div>
33+
<h3 className="font-semibold text-green-900 text-lg tracking-tight dark:text-green-200">
34+
Unlock more with thirdweb
35+
</h3>
36+
<p className="mt-0.5 text-green-800 text-sm dark:text-green-300">
37+
Upgrade to increase limits and access advanced features.
38+
</p>
39+
</div>
40+
</div>
41+
42+
<Button
43+
asChild
44+
variant="upsell"
45+
size="sm"
46+
className="mt-2 gap-2 hover:translate-y-0 hover:shadow-inner sm:mt-0"
47+
>
48+
<TrackedLinkTW
49+
href={`/team/${props.teamSlug}/~/settings/billing?showPlans=true&highlight=${props.highlightPlan || "growth"}`}
50+
category="billingBanner"
51+
label="freePlan_viewPlans"
52+
>
53+
View plans <ArrowRightIcon className="size-4" />
54+
</TrackedLinkTW>
55+
</Button>
56+
</div>
57+
</div>
58+
);
59+
}

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { redirect } from "next/navigation";
88
import { getAuthToken } from "../../../api/lib/getAuthToken";
99
import { loginRedirect } from "../../../login/loginRedirect";
1010
import { Changelog } from "./_components/Changelog";
11+
import { FreePlanUpsellBannerUI } from "./_components/FreePlanUpsellBannerUI";
1112
import { InviteTeamMembersButton } from "./_components/invite-team-members-button";
1213
import {
1314
type ProjectWithAnalytics,
@@ -55,11 +56,18 @@ export default async function Page(props: {
5556
<div className="container flex grow flex-col gap-4 lg:flex-row">
5657
{/* left */}
5758
<div className="flex grow flex-col gap-6 pt-8 lg:pb-20">
58-
<DismissibleAlert
59-
title="Looking for Engines?"
60-
description="Engines, contracts, project settings, and more are now managed within projects. Open or create a project to access them."
61-
localStorageId={`${team.id}-engines-alert`}
62-
/>
59+
{team.billingPlan === "free" ? (
60+
<FreePlanUpsellBannerUI
61+
teamSlug={team.slug}
62+
highlightPlan="growth"
63+
/>
64+
) : (
65+
<DismissibleAlert
66+
title="Looking for Engines?"
67+
description="Engines, contracts, project settings, and more are now managed within projects. Open or create a project to access them."
68+
localStorageId={`${team.id}-engines-alert`}
69+
/>
70+
)}
6371

6472
<TeamProjectsPage
6573
projects={projectsWithTotalWallets}

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/usage/rpc/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export default async function RPCUsage(props: {
8686
<div className="font-bold text-2xl capitalize">
8787
{currentRateLimit.toLocaleString()} RPS
8888
</div>
89-
<TeamPlanBadge plan={currentPlan} />
89+
<TeamPlanBadge plan={currentPlan} teamSlug={team.slug} />
9090
</div>
9191
</CardContent>
9292
</Card>

apps/dashboard/src/app/(app)/team/[team_slug]/layout.tsx

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { getTeamBySlug } from "@/api/team";
22
import { Button } from "@/components/ui/button";
3+
import { TrackedLinkTW } from "@/components/ui/tracked-link";
34
import { PosthogIdentifierServer } from "components/wallets/PosthogIdentifierServer";
45
import { ArrowRightIcon } from "lucide-react";
5-
import Link from "next/link";
66
import { redirect } from "next/navigation";
77
import { Suspense } from "react";
88
import { EnsureValidConnectedWalletLoginServer } from "../../components/EnsureValidConnectedWalletLogin/EnsureValidConnectedWalletLoginServer";
@@ -31,17 +31,25 @@ export default async function RootTeamLayout(props: {
3131
return (
3232
<div className="flex min-h-dvh flex-col">
3333
<div className="flex grow flex-col">
34-
{team.billingPlan === "starter_legacy" && (
35-
<StarterLegacyDiscontinuedBanner teamSlug={team_slug} />
36-
)}
34+
{(() => {
35+
// Show only one banner at a time following priority:
36+
// 1. Service cut off (invalid payment)
37+
// 2. Past due invoices
38+
// 3. Starter legacy plan discontinued notice
39+
if (team.billingStatus === "invalidPayment") {
40+
return <ServiceCutOffBanner teamSlug={team_slug} />;
41+
}
3742

38-
{team.billingStatus === "pastDue" && (
39-
<PastDueBanner teamSlug={team_slug} />
40-
)}
43+
if (team.billingStatus === "pastDue") {
44+
return <PastDueBanner teamSlug={team_slug} />;
45+
}
4146

42-
{team.billingStatus === "invalidPayment" && (
43-
<ServiceCutOffBanner teamSlug={team_slug} />
44-
)}
47+
if (team.billingPlan === "starter_legacy") {
48+
return <StarterLegacyDiscontinuedBanner teamSlug={team_slug} />;
49+
}
50+
51+
return null;
52+
})()}
4553

4654
{props.children}
4755
</div>
@@ -74,12 +82,14 @@ function StarterLegacyDiscontinuedBanner(props: {
7482
size="sm"
7583
className="mt-3 gap-2 border border-red-600 bg-red-100 text-red-800 hover:bg-red-200 dark:border-red-700 dark:bg-red-900 dark:text-red-100 dark:hover:bg-red-800"
7684
>
77-
<Link
85+
<TrackedLinkTW
7886
href={`/team/${props.teamSlug}/~/settings/billing?showPlans=true`}
87+
category="billingBanner"
88+
label="starterLegacy_selectPlan"
7989
>
8090
Select a new plan
8191
<ArrowRightIcon className="size-4" />
82-
</Link>
92+
</TrackedLinkTW>
8393
</Button>
8494
</div>
8595
</div>

apps/dashboard/src/app/(app)/team/components/TeamHeader/TeamHeaderUI.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export function TeamHeaderDesktopUI(props: TeamHeaderCompProps) {
6666
/>
6767
<span> {currentTeam.name} </span>
6868
<TeamVerifiedIcon domain={currentTeam.verifiedDomain} />
69-
<TeamPlanBadge plan={teamPlan} />
69+
<TeamPlanBadge plan={teamPlan} teamSlug={currentTeam.slug} />
7070
</Link>
7171

7272
<TeamAndProjectSelectorPopoverButton

apps/dashboard/src/app/(app)/team/components/TeamHeader/TeamSelectionUI.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,10 @@ export function TeamSelectionUI(props: {
115115
<TeamVerifiedIcon domain={team.verifiedDomain} />
116116
</div>
117117

118-
<TeamPlanBadge plan={team.billingPlan} />
118+
<TeamPlanBadge
119+
plan={team.billingPlan}
120+
teamSlug={team.slug}
121+
/>
119122
</Link>
120123
</Button>
121124
</li>

apps/dashboard/src/app/(app)/team/~/[[...paths]]/page.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,10 @@ export default async function Page(props: {
101101
>
102102
{team.name}
103103
</Link>
104-
<TeamPlanBadge plan={team.billingPlan} />
104+
<TeamPlanBadge
105+
plan={team.billingPlan}
106+
teamSlug={team.slug}
107+
/>
105108
<ChevronRightIcon className="ml-auto size-4 text-muted-foreground group-hover:text-foreground" />
106109
</div>
107110
);

apps/dashboard/src/components/onboarding/ApplyForOpCreditsModal.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ export function ApplyForOpCredits(props: {
113113
<TeamPlanBadge
114114
className="absolute top-5 right-6"
115115
plan={creditsRecord.plan}
116+
teamSlug={team.slug}
116117
/>
117118
</div>
118119

apps/dashboard/src/components/onboarding/PlanCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export function PlanCard({ creditsRecord, teamSlug }: PlanCardProps) {
1515
<h2 className="font-semibold text-foreground text-lg tracking-tight">
1616
{creditsRecord.upTo || "Up To"} {creditsRecord.credits} Gas Credits
1717
</h2>
18-
<TeamPlanBadge plan={creditsRecord.plan} />
18+
<TeamPlanBadge plan={creditsRecord.plan} teamSlug={teamSlug} />
1919
</div>
2020

2121
<div className="flex flex-col gap-2 px-6 py-4">

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,11 @@ export const GatedSwitch: React.FC<GatedSwitchProps> = (
6363
>
6464
<div className="inline-flex items-center gap-2">
6565
{isUpgradeRequired && (
66-
<TeamPlanBadge plan={props.requiredPlan} postfix="+" />
66+
<TeamPlanBadge
67+
plan={props.requiredPlan}
68+
teamSlug={props.teamSlug}
69+
postfix="+"
70+
/>
6771
)}
6872
<Switch
6973
{...props.switchProps}

0 commit comments

Comments
 (0)