Skip to content

[TOOL-4858] Update Team Usage Overview page UI for free plan #7423

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 99 additions & 74 deletions apps/dashboard/src/@/components/blocks/upsell-wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export function UpsellWrapper({
}

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

{/* Upsell content */}
<div className="relative z-10 flex items-center justify-center p-16">
<Card className="w-full max-w-2xl border-2 shadow-2xl">
<CardHeader className="space-y-4 text-center">
<div className="mx-auto flex h-16 w-16 items-center justify-center rounded-full border-2 bg-muted">
<LockIcon className="h-8 w-8 text-muted-foreground" />
</div>
<div className="relative z-10 flex items-center justify-center grow py-20 px-6">
<UpsellContent
benefits={benefits}
currentPlan={currentPlan}
featureDescription={featureDescription}
featureName={featureName}
requiredPlan={requiredPlan}
teamSlug={teamSlug}
/>
</div>
</div>
);
}

<div className="space-y-2">
<TeamPlanBadge
plan="scale"
postfix=" Feature"
teamSlug={teamSlug}
/>
<CardTitle className="font-bold text-2xl text-foreground md:text-3xl">
Unlock {featureName}
</CardTitle>
<CardDescription className="mx-auto max-w-md text-base text-muted-foreground">
{featureDescription}
</CardDescription>
</div>
</CardHeader>
export function UpsellContent(props: {
teamSlug: string;
featureName: string;
featureDescription: string;
requiredPlan: Team["billingPlan"];
currentPlan: Team["billingPlan"];
benefits?: {
description: string;
status: "available" | "soon";
}[];
}) {
return (
<Card className="w-full max-w-xl border shadow-2xl">
<CardHeader className="space-y-4 text-center">
<div className="mx-auto flex p-4 items-center justify-center rounded-full border bg-card">
<LockIcon className="size-8 text-muted-foreground" />
</div>

<CardContent className="space-y-6">
{benefits.length > 0 && (
<div className="space-y-3">
<h4 className="font-semibold text-muted-foreground text-sm uppercase tracking-wide">
What you'll get:
</h4>
<div className="grid gap-2">
{benefits.map((benefit) => (
<div
className="flex items-center gap-3"
key={benefit.description}
>
<div className="flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-accent">
<SparklesIcon className="h-3 w-3 text-success-text" />
</div>
<span className="text-sm">{benefit.description}</span>
{benefit.status === "soon" && (
<Badge className="text-xs" variant="secondary">
Coming Soon
</Badge>
)}
</div>
))}
</div>
</div>
)}
<div className="space-y-4">
<TeamPlanBadge
plan={props.requiredPlan}
postfix=" Feature"
teamSlug={props.teamSlug}
/>
<div className="space-y-1">
<CardTitle className="font-bold text-2xl text-foreground md:text-3xl">
Unlock {props.featureName}
</CardTitle>
<CardDescription className="mx-auto max-w-md text-base text-muted-foreground">
{props.featureDescription}
</CardDescription>
</div>
</div>
</CardHeader>

<div className="flex flex-col gap-3 pt-4 sm:flex-row">
<Button asChild className="flex-1 py-3 font-semibold" size="lg">
<Link
href={`/team/${teamSlug}/~/settings/billing?showPlans=true&highlight=${requiredPlan}`}
>
<CrownIcon className="mr-2 h-4 w-4" />
Upgrade to{" "}
<span className="ml-1 capitalize">{requiredPlan}</span>
</Link>
</Button>
<Button asChild className="md:flex-1" size="lg" variant="outline">
<Link
href={`/team/${teamSlug}/~/settings/billing?showPlans=true`}
<CardContent className="space-y-6">
{props.benefits && props.benefits.length > 0 && (
<div className="space-y-3">
<h4 className="font-semibold text-muted-foreground text-sm uppercase tracking-wide">
What you'll get:
</h4>
<div className="grid gap-2">
{props.benefits.map((benefit) => (
<div
className="flex items-center gap-3"
key={benefit.description}
>
View All Plans
</Link>
</Button>
<div className="flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-accent">
<SparklesIcon className="h-3 w-3 text-success-text" />
</div>
<span className="text-sm">{benefit.description}</span>
{benefit.status === "soon" && (
<Badge className="text-xs" variant="secondary">
Coming Soon
</Badge>
)}
</div>
))}
</div>
</div>
)}

<div className="pt-2 text-center">
<p className="text-muted-foreground text-xs">
You are currently on the{" "}
<span className="font-medium capitalize">{currentPlan}</span>{" "}
plan.
</p>
</div>
</CardContent>
</Card>
</div>
</div>
<div className="flex flex-col gap-3 pt-4 sm:flex-row">
<Button asChild className="flex-1 py-3 font-semibold" size="lg">
<Link
href={`/team/${props.teamSlug}/~/settings/billing?showPlans=true&highlight=${props.requiredPlan}`}
>
<CrownIcon className="mr-2 h-4 w-4" />
Upgrade to{" "}
<span className="ml-1 capitalize">{props.requiredPlan}</span>
</Link>
</Button>
<Button asChild className="md:flex-1" size="lg" variant="outline">
<Link
href={`/team/${props.teamSlug}/~/settings/billing?showPlans=true`}
>
View All Plans
</Link>
</Button>
</div>

<div className="pt-2 text-center">
<p className="text-muted-foreground text-xs">
You are currently on the{" "}
<span className="font-medium capitalize">{props.currentPlan}</span>{" "}
plan.
</p>
</div>
</CardContent>
</Card>
);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { redirect } from "next/navigation";
import { getTeamBySlug } from "../../../../../../../@/api/team";
import { UpsellWrapper } from "../../../../../../../@/components/blocks/upsell-wrapper";
import { getTeamBySlug } from "@/api/team";
import { UpsellWrapper } from "@/components/blocks/upsell-wrapper";

export default async function Layout(props: {
children: React.ReactNode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { InfoIcon } from "lucide-react";
import { redirect } from "next/navigation";
import { getTeamBySlug } from "@/api/team";
import { getBilledUsage } from "@/api/usage/billing-preview";
import { UpsellContent } from "@/components/blocks/upsell-wrapper";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import {
Card,
Expand All @@ -12,6 +13,7 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { getValidTeamPlan } from "@/utils/getValidTeamPlan";
import { getValidAccount } from "../../../../../account/settings/getAccount";
import {
formatPrice,
Expand All @@ -34,23 +36,28 @@ export default async function Page(props: {
}

const usagePreview = await getBilledUsage(team.slug);
const validPlan = getValidTeamPlan(team);

if (validPlan === "free") {
return (
<div className="grow flex flex-col justify-center items-center">
<UpsellContent
currentPlan={team.billingPlan}
featureDescription="View RPC, Wallet, Storage, Account Abstraction, Engine Cloud, Webhooks usage and more"
featureName="Usage"
requiredPlan="starter"
teamSlug={params.team_slug}
/>
</div>
);
}

if (usagePreview.status === "error") {
switch (usagePreview.reason) {
case "free_plan":
return (
<div className="flex min-h-[350px] items-center justify-center rounded-lg border p-4 text-destructive-text">
You are on a free plan. Please upgrade to a paid plan to view your
usage.
</div>
);
default:
return (
<div className="flex min-h-[350px] items-center justify-center rounded-lg border p-4 text-destructive-text">
Something went wrong. Please try again later.
</div>
);
}
return (
<div className="flex min-h-[350px] items-center justify-center rounded-lg border p-4 text-destructive-text">
Something went wrong. Please try again later.
</div>
);
}

const grandTotalCents = usagePreview.data.result.reduce((total, category) => {
Expand Down
Loading