Skip to content

Commit b5114df

Browse files
committed
[TOOL-4858] Update Team Usage Overview page UI for free plan (#7423)
<!-- ## 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 improving the handling of team usage and upsell features in the dashboard. It introduces new components and logic to display upsell content based on the team's billing plan. ### Detailed summary - Updated import paths for `getTeamBySlug` and `UpsellWrapper`. - Added `UpsellContent` component to display upsell information. - Implemented logic to show upsell content if the team is on a "free" plan. - Refactored the `UpsellWrapper` to include `UpsellContent`. - Enhanced rendering of benefits in `UpsellContent`. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Refactor** - Improved code structure by extracting upsell card UI into a dedicated component, resulting in cleaner and more maintainable code. - Updated import paths for consistency and clarity. - **Style** - Adjusted layout classes for better visual alignment of upsell content. - **Bug Fixes** - Unified error handling for usage page, ensuring a consistent message for all error scenarios. Free plan users now see a clear upgrade prompt. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 66270f7 commit b5114df

File tree

3 files changed

+123
-91
lines changed

3 files changed

+123
-91
lines changed

apps/dashboard/src/@/components/blocks/upsell-wrapper.tsx

Lines changed: 99 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export function UpsellWrapper({
4747
}
4848

4949
return (
50-
<div className={cn("relative flex-1", className)}>
50+
<div className={cn("relative flex-1 flex flex-col", className)}>
5151
{/* Background content - blurred and non-interactive */}
5252
<div className="absolute inset-0 overflow-hidden">
5353
<div className="pointer-events-none select-none opacity-60 blur-[1px]">
@@ -59,84 +59,109 @@ export function UpsellWrapper({
5959
<div className="absolute inset-0 bg-gradient-to-b from-muted/20 via-muted/30 to-background" />
6060

6161
{/* Upsell content */}
62-
<div className="relative z-10 flex items-center justify-center p-16">
63-
<Card className="w-full max-w-2xl border-2 shadow-2xl">
64-
<CardHeader className="space-y-4 text-center">
65-
<div className="mx-auto flex h-16 w-16 items-center justify-center rounded-full border-2 bg-muted">
66-
<LockIcon className="h-8 w-8 text-muted-foreground" />
67-
</div>
62+
<div className="relative z-10 flex items-center justify-center grow py-20 px-6">
63+
<UpsellContent
64+
benefits={benefits}
65+
currentPlan={currentPlan}
66+
featureDescription={featureDescription}
67+
featureName={featureName}
68+
requiredPlan={requiredPlan}
69+
teamSlug={teamSlug}
70+
/>
71+
</div>
72+
</div>
73+
);
74+
}
6875

69-
<div className="space-y-2">
70-
<TeamPlanBadge
71-
plan="scale"
72-
postfix=" Feature"
73-
teamSlug={teamSlug}
74-
/>
75-
<CardTitle className="font-bold text-2xl text-foreground md:text-3xl">
76-
Unlock {featureName}
77-
</CardTitle>
78-
<CardDescription className="mx-auto max-w-md text-base text-muted-foreground">
79-
{featureDescription}
80-
</CardDescription>
81-
</div>
82-
</CardHeader>
76+
export function UpsellContent(props: {
77+
teamSlug: string;
78+
featureName: string;
79+
featureDescription: string;
80+
requiredPlan: Team["billingPlan"];
81+
currentPlan: Team["billingPlan"];
82+
benefits?: {
83+
description: string;
84+
status: "available" | "soon";
85+
}[];
86+
}) {
87+
return (
88+
<Card className="w-full max-w-xl border shadow-2xl">
89+
<CardHeader className="space-y-4 text-center">
90+
<div className="mx-auto flex p-4 items-center justify-center rounded-full border bg-card">
91+
<LockIcon className="size-8 text-muted-foreground" />
92+
</div>
8393

84-
<CardContent className="space-y-6">
85-
{benefits.length > 0 && (
86-
<div className="space-y-3">
87-
<h4 className="font-semibold text-muted-foreground text-sm uppercase tracking-wide">
88-
What you'll get:
89-
</h4>
90-
<div className="grid gap-2">
91-
{benefits.map((benefit) => (
92-
<div
93-
className="flex items-center gap-3"
94-
key={benefit.description}
95-
>
96-
<div className="flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-accent">
97-
<SparklesIcon className="h-3 w-3 text-success-text" />
98-
</div>
99-
<span className="text-sm">{benefit.description}</span>
100-
{benefit.status === "soon" && (
101-
<Badge className="text-xs" variant="secondary">
102-
Coming Soon
103-
</Badge>
104-
)}
105-
</div>
106-
))}
107-
</div>
108-
</div>
109-
)}
94+
<div className="space-y-4">
95+
<TeamPlanBadge
96+
plan={props.requiredPlan}
97+
postfix=" Feature"
98+
teamSlug={props.teamSlug}
99+
/>
100+
<div className="space-y-1">
101+
<CardTitle className="font-bold text-2xl text-foreground md:text-3xl">
102+
Unlock {props.featureName}
103+
</CardTitle>
104+
<CardDescription className="mx-auto max-w-md text-base text-muted-foreground">
105+
{props.featureDescription}
106+
</CardDescription>
107+
</div>
108+
</div>
109+
</CardHeader>
110110

111-
<div className="flex flex-col gap-3 pt-4 sm:flex-row">
112-
<Button asChild className="flex-1 py-3 font-semibold" size="lg">
113-
<Link
114-
href={`/team/${teamSlug}/~/settings/billing?showPlans=true&highlight=${requiredPlan}`}
115-
>
116-
<CrownIcon className="mr-2 h-4 w-4" />
117-
Upgrade to{" "}
118-
<span className="ml-1 capitalize">{requiredPlan}</span>
119-
</Link>
120-
</Button>
121-
<Button asChild className="md:flex-1" size="lg" variant="outline">
122-
<Link
123-
href={`/team/${teamSlug}/~/settings/billing?showPlans=true`}
111+
<CardContent className="space-y-6">
112+
{props.benefits && props.benefits.length > 0 && (
113+
<div className="space-y-3">
114+
<h4 className="font-semibold text-muted-foreground text-sm uppercase tracking-wide">
115+
What you'll get:
116+
</h4>
117+
<div className="grid gap-2">
118+
{props.benefits.map((benefit) => (
119+
<div
120+
className="flex items-center gap-3"
121+
key={benefit.description}
124122
>
125-
View All Plans
126-
</Link>
127-
</Button>
123+
<div className="flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-accent">
124+
<SparklesIcon className="h-3 w-3 text-success-text" />
125+
</div>
126+
<span className="text-sm">{benefit.description}</span>
127+
{benefit.status === "soon" && (
128+
<Badge className="text-xs" variant="secondary">
129+
Coming Soon
130+
</Badge>
131+
)}
132+
</div>
133+
))}
128134
</div>
135+
</div>
136+
)}
129137

130-
<div className="pt-2 text-center">
131-
<p className="text-muted-foreground text-xs">
132-
You are currently on the{" "}
133-
<span className="font-medium capitalize">{currentPlan}</span>{" "}
134-
plan.
135-
</p>
136-
</div>
137-
</CardContent>
138-
</Card>
139-
</div>
140-
</div>
138+
<div className="flex flex-col gap-3 pt-4 sm:flex-row">
139+
<Button asChild className="flex-1 py-3 font-semibold" size="lg">
140+
<Link
141+
href={`/team/${props.teamSlug}/~/settings/billing?showPlans=true&highlight=${props.requiredPlan}`}
142+
>
143+
<CrownIcon className="mr-2 h-4 w-4" />
144+
Upgrade to{" "}
145+
<span className="ml-1 capitalize">{props.requiredPlan}</span>
146+
</Link>
147+
</Button>
148+
<Button asChild className="md:flex-1" size="lg" variant="outline">
149+
<Link
150+
href={`/team/${props.teamSlug}/~/settings/billing?showPlans=true`}
151+
>
152+
View All Plans
153+
</Link>
154+
</Button>
155+
</div>
156+
157+
<div className="pt-2 text-center">
158+
<p className="text-muted-foreground text-xs">
159+
You are currently on the{" "}
160+
<span className="font-medium capitalize">{props.currentPlan}</span>{" "}
161+
plan.
162+
</p>
163+
</div>
164+
</CardContent>
165+
</Card>
141166
);
142167
}

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/audit-log/layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { redirect } from "next/navigation";
2-
import { getTeamBySlug } from "../../../../../../../@/api/team";
3-
import { UpsellWrapper } from "../../../../../../../@/components/blocks/upsell-wrapper";
2+
import { getTeamBySlug } from "@/api/team";
3+
import { UpsellWrapper } from "@/components/blocks/upsell-wrapper";
44

55
export default async function Layout(props: {
66
children: React.ReactNode;

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

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { InfoIcon } from "lucide-react";
33
import { redirect } from "next/navigation";
44
import { getTeamBySlug } from "@/api/team";
55
import { getBilledUsage } from "@/api/usage/billing-preview";
6+
import { UpsellContent } from "@/components/blocks/upsell-wrapper";
67
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
78
import {
89
Card,
@@ -12,6 +13,7 @@ import {
1213
CardHeader,
1314
CardTitle,
1415
} from "@/components/ui/card";
16+
import { getValidTeamPlan } from "@/utils/getValidTeamPlan";
1517
import { getValidAccount } from "../../../../../account/settings/getAccount";
1618
import {
1719
formatPrice,
@@ -34,23 +36,28 @@ export default async function Page(props: {
3436
}
3537

3638
const usagePreview = await getBilledUsage(team.slug);
39+
const validPlan = getValidTeamPlan(team);
40+
41+
if (validPlan === "free") {
42+
return (
43+
<div className="grow flex flex-col justify-center items-center">
44+
<UpsellContent
45+
currentPlan={team.billingPlan}
46+
featureDescription="View RPC, Wallet, Storage, Account Abstraction, Engine Cloud, Webhooks usage and more"
47+
featureName="Usage"
48+
requiredPlan="starter"
49+
teamSlug={params.team_slug}
50+
/>
51+
</div>
52+
);
53+
}
3754

3855
if (usagePreview.status === "error") {
39-
switch (usagePreview.reason) {
40-
case "free_plan":
41-
return (
42-
<div className="flex min-h-[350px] items-center justify-center rounded-lg border p-4 text-destructive-text">
43-
You are on a free plan. Please upgrade to a paid plan to view your
44-
usage.
45-
</div>
46-
);
47-
default:
48-
return (
49-
<div className="flex min-h-[350px] items-center justify-center rounded-lg border p-4 text-destructive-text">
50-
Something went wrong. Please try again later.
51-
</div>
52-
);
53-
}
56+
return (
57+
<div className="flex min-h-[350px] items-center justify-center rounded-lg border p-4 text-destructive-text">
58+
Something went wrong. Please try again later.
59+
</div>
60+
);
5461
}
5562

5663
const grandTotalCents = usagePreview.data.result.reduce((total, category) => {

0 commit comments

Comments
 (0)