feat: full studio build -- light theme, canvas thumbnails, i18n (fa/en)
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
"use client";
|
||||
|
||||
import { Fragment } from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
import { PricingAnimatedPrice } from "@/components/sections/PricingAnimatedPrice";
|
||||
import { PricingBillingToggle } from "@/components/sections/PricingBillingToggle";
|
||||
import { PricingCheckoutButton } from "@/components/sections/PricingCheckoutButton";
|
||||
import {
|
||||
PricingCompareFeatureLabel,
|
||||
PricingCompareValueCell,
|
||||
} from "@/components/sections/PricingCompareValue";
|
||||
import type { BillingPeriod, PricingTier } from "@/components/sections/pricing-data";
|
||||
import {
|
||||
COMPARE_ANNUAL_SAVINGS_BADGE,
|
||||
COMPARE_SECTIONS,
|
||||
getCompareAtPrice,
|
||||
getDisplayPrice,
|
||||
PRICING_TIERS,
|
||||
} from "@/components/sections/pricing-data";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import type { PaidPlanId } from "@/lib/plans";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface PricingCompareTableProps {
|
||||
billing: BillingPeriod;
|
||||
onBillingChange: (billing: BillingPeriod) => void;
|
||||
}
|
||||
|
||||
function SavingsArrowIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="28"
|
||||
height="20"
|
||||
viewBox="0 0 28 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="text-blue-600"
|
||||
aria-hidden
|
||||
>
|
||||
<path
|
||||
d="M2 14C8 6 14 4 22 6"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M18 4L23 6L21 11"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function PlanHeaderCell({
|
||||
tier,
|
||||
billing,
|
||||
}: {
|
||||
tier: PricingTier;
|
||||
billing: BillingPeriod;
|
||||
}) {
|
||||
const highlighted = tier.highlighted ?? false;
|
||||
const isStripePlan = tier.id === "pro" || tier.id === "business";
|
||||
|
||||
return (
|
||||
<th
|
||||
className={cn(
|
||||
"px-4 pb-4 pt-6 align-top",
|
||||
highlighted && "bg-blue-50/30"
|
||||
)}
|
||||
>
|
||||
{highlighted ? (
|
||||
<span className="mb-2 inline-block rounded-full bg-gradient-to-r from-violet-500 to-blue-600 px-2.5 py-0.5 text-[10px] font-bold uppercase tracking-wide text-white">
|
||||
Most Popular
|
||||
</span>
|
||||
) : (
|
||||
<span className="mb-2 block h-5" aria-hidden />
|
||||
)}
|
||||
<p className="font-heading text-base font-bold text-neutral-900">
|
||||
{tier.name}
|
||||
</p>
|
||||
<PricingAnimatedPrice
|
||||
price={getDisplayPrice(tier, billing)}
|
||||
compareAt={getCompareAtPrice(tier, billing)}
|
||||
billing={billing}
|
||||
size="compact"
|
||||
/>
|
||||
{isStripePlan ? (
|
||||
<PricingCheckoutButton
|
||||
plan={tier.id as PaidPlanId}
|
||||
billing={billing}
|
||||
label={tier.cta}
|
||||
className={cn(
|
||||
"mt-3 h-9 w-full rounded-lg text-sm font-semibold",
|
||||
highlighted
|
||||
? "bg-rf-blue hover:bg-rf-blue/90"
|
||||
: "border border-gray-300 bg-white text-neutral-800 hover:bg-gray-50"
|
||||
)}
|
||||
variant={highlighted ? "default" : "secondary"}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="mt-3 h-9 w-full rounded-lg border-gray-300 text-sm font-semibold"
|
||||
asChild
|
||||
>
|
||||
<Link href="/auth?tab=sign-up">{tier.cta}</Link>
|
||||
</Button>
|
||||
)}
|
||||
</th>
|
||||
);
|
||||
}
|
||||
|
||||
export function PricingCompareTable({
|
||||
billing,
|
||||
onBillingChange,
|
||||
}: PricingCompareTableProps) {
|
||||
const lite = PRICING_TIERS.find((t) => t.id === "lite");
|
||||
const pro = PRICING_TIERS.find((t) => t.id === "pro");
|
||||
const business = PRICING_TIERS.find((t) => t.id === "business");
|
||||
|
||||
if (!lite || !pro || !business) return null;
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-5xl overflow-x-auto rounded-2xl border border-gray-100 bg-white shadow-sm">
|
||||
<table className="w-full min-w-[760px] border-collapse">
|
||||
<thead className="sticky top-0 z-10 bg-white">
|
||||
<tr className="border-b border-gray-100">
|
||||
<th className="w-[38%] px-6 pb-4 pt-6 text-left align-top">
|
||||
<h3 className="bg-gradient-to-r from-blue-600 to-violet-600 bg-clip-text font-heading text-lg font-bold text-transparent sm:text-xl">
|
||||
Compare Plans & Features
|
||||
</h3>
|
||||
<div className="mt-4 items-start">
|
||||
<PricingBillingToggle
|
||||
billing={billing}
|
||||
onChange={onBillingChange}
|
||||
layoutId="pricing-compare-billing-pill"
|
||||
/>
|
||||
</div>
|
||||
<p className="mt-3 flex items-center gap-1 text-xs font-bold uppercase tracking-wide text-blue-600">
|
||||
Save up to {COMPARE_ANNUAL_SAVINGS_BADGE}%
|
||||
<SavingsArrowIcon />
|
||||
</p>
|
||||
</th>
|
||||
<PlanHeaderCell tier={lite} billing={billing} />
|
||||
<PlanHeaderCell tier={pro} billing={billing} />
|
||||
<PlanHeaderCell tier={business} billing={billing} />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{COMPARE_SECTIONS.map((section) => (
|
||||
<Fragment key={section.title}>
|
||||
<tr className="bg-gray-50">
|
||||
<td
|
||||
colSpan={4}
|
||||
className="px-6 py-3 text-xs font-bold uppercase tracking-widest text-gray-500"
|
||||
>
|
||||
{section.title}
|
||||
</td>
|
||||
</tr>
|
||||
{section.rows.map((row) => (
|
||||
<tr
|
||||
key={`${section.title}-${row.feature}`}
|
||||
className="border-b border-gray-100 transition-colors hover:bg-gray-50/60"
|
||||
>
|
||||
<td className="px-6 py-3">
|
||||
<PricingCompareFeatureLabel
|
||||
feature={row.feature}
|
||||
tooltip={row.tooltip}
|
||||
/>
|
||||
</td>
|
||||
<PricingCompareValueCell value={row.lite} />
|
||||
<PricingCompareValueCell value={row.pro} highlighted />
|
||||
<PricingCompareValueCell value={row.business} />
|
||||
</tr>
|
||||
))}
|
||||
</Fragment>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user