From cecb3281201d6990437ba01bf4518c0ba3598cc2 Mon Sep 17 00:00:00 2001 From: "kemal.earth" <606977+kemaldotearth@users.noreply.github.com> Date: Wed, 2 Sep 2026 09:32:36 +0100 Subject: [PATCH] feat(studio): clean up free tier upgrade box (#49851) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Small update of the Projects overview UI to resolve the competing CTA's as well as alignment. Open to tweak a little more here. | Before | After | |--------|--------| | Screenshot 2026-09-01 at 15 08 33 | Screenshot 2026-09-01 at 15 05
40 | ## Summary by CodeRabbit * **Style** * Refined the plan usage card layout with larger metric values and cleaner labels. * Added dividers between usage metrics for improved readability. * Simplified card and loading-state styling by removing unnecessary borders, backgrounds, and padding. * Updated the upgrade button’s visual treatment. --- .../interfaces/ProjectHome/PlanUsageCard.tsx | 68 ++++++------------- 1 file changed, 20 insertions(+), 48 deletions(-) diff --git a/apps/studio/components/interfaces/ProjectHome/PlanUsageCard.tsx b/apps/studio/components/interfaces/ProjectHome/PlanUsageCard.tsx index 740ff099c64..2b9b91e3ce3 100644 --- a/apps/studio/components/interfaces/ProjectHome/PlanUsageCard.tsx +++ b/apps/studio/components/interfaces/ProjectHome/PlanUsageCard.tsx @@ -14,7 +14,6 @@ type MetricConfig = { key: PricingMetric label: string unit: MetricUnit - /** Anchor id of the matching section on the org usage page. */ anchor: string } @@ -40,26 +39,15 @@ const METRICS: MetricConfig[] = [ }, ] -const formatGigabytes = (value: number) => { - if (value === 0) return '0 GB' - if (value < 1) return `${(value * 1000).toFixed(0)} MB` - return `${value.toFixed(value < 10 ? 2 : 1)} GB` -} - -const formatGigabyteLimit = (limit: number) => { - if (limit < 1) return `${(limit * 1000).toFixed(0)} MB` - return `${limit} GB` -} - -// Show counts in full with thousands separators (e.g. `50,000`) rather than abbreviated -// (`50k`), to match the pricing page and avoid ambiguity around plan limits. const formatCount = (value: number) => value.toLocaleString() -const formatValue = (value: number, unit: MetricUnit) => - unit === 'gigabytes' ? formatGigabytes(value) : formatCount(value) - -const formatLimit = (limit: number, unit: MetricUnit) => - unit === 'gigabytes' ? formatGigabyteLimit(limit) : formatCount(limit) +const formatUsagePair = (value: number, limit: number, unit: MetricUnit) => { + if (unit === 'count') return { value: formatCount(value), limit: formatCount(limit) } + if (limit < 1) { + return { value: (value * 1000).toFixed(0), limit: `${(limit * 1000).toFixed(0)} MB` } + } + return { value: value === 0 ? '0' : value.toFixed(value < 10 ? 2 : 1), limit: `${limit} GB` } +} const RING_RADIUS = 7 const RING_CIRCUMFERENCE = 2 * Math.PI * RING_RADIUS @@ -113,8 +101,6 @@ const ProgressRing = ({ ) } -// The upgrade CTA surface this card represents. Used as the telemetry `source` + -// `placement` value. Kept as a constant so the tracking stays explicit. const PLACEMENT = 'org_projects_list' const CompactMetricRow = ({ @@ -131,26 +117,25 @@ const CompactMetricRow = ({ const ratio = limit > 0 ? current / limit : 0 const isOver = limit > 0 && current >= limit const isApproaching = limit > 0 && ratio >= 0.8 && !isOver + const formatted = formatUsagePair(current, limit, config.unit) return ( -
+
- - {config.label} - + {config.label}
- + - {formatValue(current, config.unit)} + {formatted.value} / - {formatLimit(limit, config.unit)} + {formatted.limit} ( -
-
+
+
(
) -/** - * Renders the upgrade CTA's plan-usage card in the org project list (the - * `org_projects_list` CTA placement). The parent is responsible for gating on free plan — - * this component only renders the visual sections once usage data is available. Shaped - * like a `ProjectCard` so it reads as another tile. - */ export const PlanUsageCard = () => { const track = useTrack() const { data: organization } = useSelectedOrganizationQuery() @@ -199,21 +178,13 @@ export const PlanUsageCard = () => { }).filter((row): row is { config: MetricConfig; usageItem: OrgMetricsUsage } => row !== null) : [] - // Hide entirely on hard error or when the org has zero applicable metrics — both - // are extreme edge cases. Otherwise always render the card shell so the layout is - // reserved from first paint and the usage rows fade in once the query resolves. if (isError) return null if (isSuccess && visibleRows.length === 0) return null return (
  • -
    -
    +
    +
    Free plan usage

    Current billing cycle

    @@ -222,11 +193,12 @@ export const PlanUsageCard = () => { track('upgrade_cta_clicked', { placement: PLACEMENT })} />
    -
    +
    {isSuccess ? visibleRows.map(({ config, usageItem }) => (