feat(FN-263): normalize typography on subscription page (+2 more)
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled

Commits merged:
- feat(FN-263): normalize typography on subscription page
- chore(FN-263): apply Biome formatting fix for long className lines
- feat(FN-263): normalize typography on subscription page

Files changed:
.../src/routes/dashboard/subscription/index.tsx    | 116 +++++++++++----------
 1 file changed, 62 insertions(+), 54 deletions(-)

Fusion-Task-Id: FN-263
This commit is contained in:
Fusion
2026-05-13 04:51:49 +00:00
parent f0bfe6ec8b
commit fa49c20d09

View File

@@ -414,7 +414,7 @@ export function SubscriptionPage() {
<CardContent className="relative flex flex-col items-center gap-6 py-10">
<div className="flex items-center gap-2">
<Sparkles className="h-6 w-6 animate-pulse text-brand" />
<h2 className="text-xl font-bold text-foreground">
<h2 className="text-xl font-bold leading-8 text-foreground">
{t("subscription.onboarding.provisioning")}
</h2>
</div>
@@ -439,7 +439,7 @@ export function SubscriptionPage() {
{/* If animation finished but mutation still pending */}
{animationEnded && trialMutation.isPending && (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<div className="flex items-center gap-2 text-sm leading-5 text-muted-foreground">
<Loader2 className="h-4 w-4 animate-spin" />
{t("subscription.onboarding.step4")}...
</div>
@@ -448,7 +448,7 @@ export function SubscriptionPage() {
{/* Mutation error */}
{trialMutation.isError && (
<div className="flex flex-col items-center gap-3">
<p className="text-sm text-red-600 dark:text-red-400">
<p className="text-sm leading-5 text-red-600 dark:text-red-400">
{t("subscription.onboarding.error")}
</p>
<Button variant="outline" onClick={() => trialMutation.mutate()}>
@@ -472,14 +472,14 @@ export function SubscriptionPage() {
<CardContent className="relative flex flex-col items-center gap-6 py-10">
<CheckCircle2 className="h-16 w-16 text-brand" />
<h2 className="text-center text-2xl font-bold text-foreground">
<h2 className="text-center text-2xl font-bold leading-9 text-foreground">
{t("subscription.onboarding.completed")}
</h2>
{/* Subscription info box */}
<div className="w-full max-w-md space-y-4 rounded-xl border border-brand/20 bg-background/60 p-5 backdrop-blur-sm dark:bg-background/30">
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">
<span className="text-sm leading-5 text-muted-foreground">
{t("subscription.currentPlan")}
</span>
<Badge className="bg-brand text-brand-foreground">
@@ -487,23 +487,23 @@ export function SubscriptionPage() {
</Badge>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">
<span className="text-sm leading-5 text-muted-foreground">
{t("subscription.billingPeriod")}
</span>
<span className="text-sm font-medium">
<span className="text-sm font-medium leading-5">
{t("subscription.onboarding.trialDuration")}
</span>
</div>
{freshSub?.endDate && (
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">{t("subscription.endDate")}</span>
<span className="text-sm font-medium">
<span className="text-sm leading-5 text-muted-foreground">{t("subscription.endDate")}</span>
<span className="text-sm font-medium leading-5">
{new Date(freshSub.endDate).toLocaleDateString("tr-TR")}
</span>
</div>
)}
<Separator />
<ul className="space-y-2 text-sm">
<ul className="space-y-2 text-sm leading-relaxed">
{["allBrands", "vinSearch", "partsCatalog", "schemaViewer"].map((f) => (
<li key={f} className="flex items-center gap-2 text-foreground/85">
<Check className="h-4 w-4 text-brand" />
@@ -530,7 +530,7 @@ export function SubscriptionPage() {
// ─── Normal subscription page ──────────────────────────────────────────────
return (
<div className="mx-auto max-w-5xl space-y-8 pb-16">
<h2 className="text-2xl font-bold">{t("subscription.title")}</h2>
<h2 className="text-2xl font-bold leading-9">{t("subscription.title")}</h2>
{/* Active Subscription Status */}
{subscription && !(eligibleForTrial && subscription.status === "trial") && (
@@ -539,13 +539,13 @@ export function SubscriptionPage() {
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Crown className="h-5 w-5 text-primary" />
<CardTitle>{t("subscription.currentPlan")}</CardTitle>
<CardTitle className="leading-7">{t("subscription.currentPlan")}</CardTitle>
</div>
<Badge variant={statusVariants[subscription.status] || "secondary"}>
{t(`subscription.statusLabels.${subscription.status}`)}
</Badge>
</div>
<CardDescription>
<CardDescription className="font-normal leading-5">
{subscription.plan?.name} &mdash;{" "}
{subscription.status === "trial"
? t("subscription.onboarding.trialDuration")
@@ -557,7 +557,9 @@ export function SubscriptionPage() {
<CardContent className="space-y-4">
{subscription.brands && subscription.brands.length > 0 && (
<div>
<p className="mb-2 text-sm font-medium">{t("subscription.accessibleBrands")}:</p>
<p className="mb-2 text-sm font-medium leading-5">
{t("subscription.accessibleBrands")}:
</p>
<div className="flex flex-wrap gap-2">
{subscription.brands.map((b) => (
<Badge key={b.brandId} variant="outline" className="flex items-center gap-1.5">
@@ -569,7 +571,7 @@ export function SubscriptionPage() {
</div>
)}
<div className="flex flex-wrap gap-6 text-sm">
<div className="flex flex-wrap gap-6 text-sm leading-5">
{subscription.startDate && (
<div>
<span className="text-muted-foreground">{t("subscription.startDate")}: </span>
@@ -589,7 +591,7 @@ export function SubscriptionPage() {
</div>
{subscription.status === "trial" && subscription.endDate && (
<div className="flex items-center gap-2 rounded-lg bg-brand/10 px-3 py-2 text-sm text-brand">
<div className="flex items-center gap-2 rounded-lg bg-brand/10 px-3 py-2 text-sm leading-5 text-brand">
<Sparkles className="h-4 w-4" />
{(() => {
const days = Math.max(
@@ -644,18 +646,18 @@ export function SubscriptionPage() {
<span className="font-semibold text-foreground">
{t(`subscription.plans.${downgradePlan.key}.name`)}
</span>
<span className="text-lg font-bold">
<span className="text-lg font-bold leading-7">
{formatTRY(
billingPeriod === "yearly"
? downgradePlan.priceYearly
: downgradePlan.priceMonthly,
)}
<span className="text-xs font-normal text-muted-foreground">
<span className="text-xs font-normal leading-4 text-muted-foreground">
{billingPeriod === "yearly" ? t("common.perYear") : t("common.perMonth")}
</span>
</span>
</div>
<p className="text-sm text-muted-foreground">
<p className="text-sm leading-5 text-muted-foreground">
{t(`subscription.plans.${downgradePlan.key}.description`)}
</p>
{(() => {
@@ -670,7 +672,7 @@ export function SubscriptionPage() {
const savings = currentPrice - newPrice;
if (savings <= 0) return null;
return (
<p className="text-sm font-medium text-primary">
<p className="text-sm font-medium leading-5 text-primary">
{t("subscription.downgradeOffer.savingsLabel", {
savings: formatTRY(savings),
})}
@@ -682,7 +684,7 @@ export function SubscriptionPage() {
{/* Brand selector — only for non-full plans */}
{downgradePlan.brandLimit < FULL_PLAN_BRAND_LIMIT && (
<div className="space-y-2">
<p className="text-sm text-muted-foreground">
<p className="text-sm leading-5 text-muted-foreground">
{t("subscription.downgradeOffer.brandsHint")}
</p>
<Suspense fallback={<BrandSelectorFallback />}>
@@ -791,14 +793,16 @@ export function SubscriptionPage() {
<CardHeader className="relative">
<div className="flex items-center gap-2">
<Sparkles className="h-5 w-5 text-brand" />
<CardTitle className="text-foreground">{t("subscription.trialTitle")}</CardTitle>
<CardTitle className="text-foreground leading-7">
{t("subscription.trialTitle")}
</CardTitle>
</div>
<CardDescription className="text-muted-foreground">
<CardDescription className="text-muted-foreground font-normal leading-5">
{t("subscription.trialDescription")}
</CardDescription>
</CardHeader>
<CardContent className="relative space-y-4">
<ul className="space-y-2 text-sm">
<ul className="space-y-2 text-sm leading-relaxed">
{["allBrands", "vinSearch", "partsCatalog", "schemaViewer"].map((f) => (
<li key={f} className="flex items-center gap-2 text-foreground/85">
<Check className="h-4 w-4 text-brand" />
@@ -818,7 +822,7 @@ export function SubscriptionPage() {
<ShieldCheck className="mr-2 h-4 w-4" />
{trialMutation.isPending ? t("common.loading") : t("subscription.startTrial")}
</Button>
<div className="flex flex-wrap items-center justify-center gap-x-4 gap-y-1 text-xs text-foreground/70">
<div className="flex flex-wrap items-center justify-center gap-x-4 gap-y-1 text-xs leading-5 text-foreground/70">
<span className="flex items-center gap-1">
<CreditCard className="h-3 w-3 shrink-0" />
{t("subscription.trustNoCard")}
@@ -840,10 +844,10 @@ export function SubscriptionPage() {
{!eligibleForTrial && !subscription && (
<Card className="border-dashed">
<CardContent className="py-8 text-center">
<p className="mb-2 text-lg font-medium text-muted-foreground">
<p className="mb-2 text-lg font-medium leading-7 text-muted-foreground">
{t("subscription.noSubscription")}
</p>
<p className="text-sm text-muted-foreground">
<p className="text-sm leading-5 text-muted-foreground">
{t("subscription.selectBrandsDescription")}
</p>
</CardContent>
@@ -874,7 +878,7 @@ export function SubscriptionPage() {
{/* Plan Comparison Cards */}
<div>
<h3 className="mb-4 text-lg font-semibold">{t("subscription.planComparison")}</h3>
<h3 className="mb-4 text-xl font-semibold leading-8">{t("subscription.planComparison")}</h3>
<div ref={planCardsRef} className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
{plans.map((plan) => {
const isSelected = selectedPlanKey === plan.key;
@@ -909,16 +913,18 @@ export function SubscriptionPage() {
</Badge>
)}
<CardHeader>
<CardTitle className="text-lg">
<CardTitle className="text-lg leading-7">
{t(`subscription.plans.${plan.key}.name`)}
</CardTitle>
<CardDescription>
<CardDescription className="font-normal leading-5">
{t(`subscription.plans.${plan.key}.description`)}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div>
<span className="text-3xl font-bold">{formatTRY(price)}</span>
<span className="text-xl sm:text-2xl lg:text-3xl font-bold leading-8 sm:leading-9 lg:leading-10">
{formatTRY(price)}
</span>
<span className="text-muted-foreground">
{billingPeriod === "monthly" ? t("common.perMonth") : t("common.perYear")}
</span>
@@ -928,7 +934,7 @@ export function SubscriptionPage() {
</Badge>
)}
</div>
<ul className="space-y-2 text-sm">
<ul className="text-sm leading-relaxed space-y-2">
{plan.features.map((f) => (
<li key={f} className="flex items-center gap-2">
<Check className="h-4 w-4 text-primary" />
@@ -939,7 +945,7 @@ export function SubscriptionPage() {
</CardContent>
<CardFooter>
{isCurrentPlan ? (
<div className="flex w-full items-center justify-center rounded-md bg-muted py-2 text-sm font-medium text-muted-foreground">
<div className="flex w-full items-center justify-center rounded-md bg-muted py-2 text-sm font-medium leading-5 text-muted-foreground">
<Check className="mr-1.5 h-4 w-4" />
{t("subscription.currentPlan")}
</div>
@@ -977,10 +983,12 @@ export function SubscriptionPage() {
<Separator />
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-lg">{t("subscription.orderSummary")}</CardTitle>
<CardTitle className="text-lg leading-7">
{t("subscription.orderSummary")}
</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<div className="flex items-center justify-between text-sm">
<div className="flex items-center justify-between text-sm leading-5">
<span className="text-muted-foreground">
{t("subscription.orderSummaryPlan")}
</span>
@@ -988,7 +996,7 @@ export function SubscriptionPage() {
{t(`subscription.plans.${selectedPlanKey}.name`)}
</span>
</div>
<div className="flex items-center justify-between text-sm">
<div className="flex items-center justify-between text-sm leading-5">
<span className="text-muted-foreground">
{t("subscription.orderSummaryPeriod")}
</span>
@@ -997,7 +1005,7 @@ export function SubscriptionPage() {
</span>
</div>
{selectedBrandIds.length > 0 && (
<div className="flex items-center justify-between text-sm">
<div className="flex items-center justify-between text-sm leading-5">
<span className="text-muted-foreground">
{t("subscription.orderSummaryBrands")}
</span>
@@ -1007,11 +1015,11 @@ export function SubscriptionPage() {
</div>
)}
<Separator />
<div className="flex items-center justify-between font-semibold">
<div className="flex items-center justify-between font-bold">
<span>{t("subscription.orderSummaryPrice")}</span>
<span>
{formatTRY(summaryPrice)}
<span className="text-xs font-normal text-muted-foreground">
<span className="text-xs font-normal leading-4 text-muted-foreground">
{periodLabel}
</span>
</span>
@@ -1027,8 +1035,8 @@ export function SubscriptionPage() {
<>
<Separator />
<div className="space-y-4">
<h3 className="text-lg font-semibold">{t("subscription.selectBrands")}</h3>
<p className="text-sm text-muted-foreground">
<h3 className="text-xl font-semibold leading-8">{t("subscription.selectBrands")}</h3>
<p className="text-sm leading-5 text-muted-foreground">
{t("subscription.selectBrandsDescription")}
</p>
<Suspense fallback={<BrandSelectorFallback />}>
@@ -1056,7 +1064,7 @@ export function SubscriptionPage() {
</div>
<section
aria-label="Ödeme güvencesi"
className="flex flex-wrap items-center justify-end gap-x-3 gap-y-1 text-xs text-foreground/70"
className="flex flex-wrap items-center justify-end gap-x-3 gap-y-1 text-xs leading-5 text-foreground/70"
>
<span aria-hidden="true">🔒</span>
<span>{t("subscription.paymentTrustSSL")}</span>
@@ -1073,28 +1081,28 @@ export function SubscriptionPage() {
>
<Badge
variant="outline"
className="px-1.5 py-0 text-[10px] font-normal"
className="px-1.5 py-0 text-[0.625rem] leading-3 font-normal"
aria-hidden="true"
>
Visa
</Badge>
<Badge
variant="outline"
className="px-1.5 py-0 text-[10px] font-normal"
className="px-1.5 py-0 text-[0.625rem] leading-3 font-normal"
aria-hidden="true"
>
Mastercard
</Badge>
<Badge
variant="outline"
className="px-1.5 py-0 text-[10px] font-normal"
className="px-1.5 py-0 text-[0.625rem] leading-3 font-normal"
aria-hidden="true"
>
Troy
</Badge>
<Badge
variant="outline"
className="px-1.5 py-0 text-[10px] font-normal"
className="px-1.5 py-0 text-[0.625rem] leading-3 font-normal"
aria-hidden="true"
>
AmEx
@@ -1121,12 +1129,12 @@ export function SubscriptionPage() {
<div className="fixed inset-x-0 bottom-0 z-50 animate-fade-in-up border-t border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/80">
<div className="mx-auto flex max-w-5xl items-center justify-between px-6 py-3">
<div className="flex items-baseline gap-3">
<span className="text-sm font-semibold">
<span className="text-sm font-semibold leading-5">
{t(`subscription.plans.${selectedPlanKey}.name`)}
</span>
<span className="text-sm text-muted-foreground">
<span className="text-sm leading-5 text-muted-foreground">
{formatTRY(barPrice)}
<span className="text-xs">{barPeriodLabel}</span>
<span className="text-xs leading-4">{barPeriodLabel}</span>
</span>
</div>
<Button size="sm" className="px-6" onClick={handleProceedToPayment}>
@@ -1172,16 +1180,16 @@ function FeatureMatrix() {
return (
<div>
<h3 className="mb-4 text-lg font-semibold">{t("subscription.featureMatrix")}</h3>
<h3 className="mb-4 text-xl font-semibold leading-8">{t("subscription.featureMatrix")}</h3>
<div className="overflow-x-auto rounded-lg border">
<table className="w-full text-sm">
<thead>
<tr className="border-b bg-muted/50">
<th className="w-48 px-4 py-3 text-left font-medium text-muted-foreground" />
<th className="w-48 px-4 py-3 text-left font-semibold leading-5 text-muted-foreground" />
{plans.map((plan) => (
<th
key={plan.key}
className={`relative px-4 py-3 text-center font-semibold ${
className={`relative px-4 py-3 text-center font-semibold leading-5 ${
plan.popular ? "text-primary" : "text-foreground"
}`}
aria-label={t(`subscription.plans.${plan.key}.name`)}
@@ -1192,13 +1200,13 @@ function FeatureMatrix() {
<tbody>
{ALL_FEATURES.map((feature, i) => (
<tr key={feature} className={i % 2 === 0 ? "bg-background" : "bg-muted/25"}>
<td className="px-4 py-3 font-medium text-foreground">
<td className="px-4 py-3 font-medium leading-5 text-foreground">
{t(`subscription.features.${feature}`)}
</td>
{plans.map((plan) => {
const has = (plan.features as readonly string[]).includes(feature);
return (
<td key={plan.key} className="px-4 py-3 text-center">
<td key={plan.key} className="px-4 py-3 text-center leading-5">
{has ? (
<Check
className="mx-auto h-4 w-4 text-primary"