generated from fahricansecer/boilerplate-fe
This commit is contained in:
@@ -4,248 +4,245 @@ import { useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
Check,
|
||||
X,
|
||||
Zap,
|
||||
Crown,
|
||||
Rocket,
|
||||
Loader2,
|
||||
Sparkles,
|
||||
ArrowRight,
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useCreateCheckout, useSubscription } from "@/hooks/use-api";
|
||||
import { useToast } from "@/components/ui/toast";
|
||||
|
||||
const plans = [
|
||||
{
|
||||
id: "free",
|
||||
name: "Free",
|
||||
icon: Sparkles,
|
||||
monthlyPrice: 0,
|
||||
yearlyPrice: 0,
|
||||
credits: 3,
|
||||
description: "AI video üretimini keşfet",
|
||||
color: "emerald",
|
||||
gradient: "from-emerald-500/15 to-emerald-600/5",
|
||||
borderActive: "border-emerald-500/30",
|
||||
buttonClass: "btn-ghost",
|
||||
buttonLabel: "Mevcut Plan",
|
||||
features: [
|
||||
{ label: "3 kredi / ay", included: true },
|
||||
{ label: "720p video kalitesi", included: true },
|
||||
{ label: "Max 30 saniye", included: true },
|
||||
{ label: "5 proje limiti", included: true },
|
||||
{ label: "Temel şablonlar", included: true },
|
||||
{ label: "Öncelikli kuyruk", included: false },
|
||||
{ label: "Marka kaldırma", included: false },
|
||||
{ label: "API erişimi", included: false },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "pro",
|
||||
name: "Pro",
|
||||
price: { monthly: 0, yearly: 0 },
|
||||
description: "Başlangıç için ideal",
|
||||
icon: Zap,
|
||||
monthlyPrice: 19,
|
||||
yearlyPrice: 190,
|
||||
credits: 50,
|
||||
description: "İçerik üreticileri için güçlü araçlar",
|
||||
color: "violet",
|
||||
gradient: "from-violet-500/20 to-violet-600/8",
|
||||
borderActive: "border-violet-500/40",
|
||||
buttonClass: "btn-primary",
|
||||
buttonLabel: "Pro'ya Yükselt",
|
||||
recommended: true,
|
||||
gradient: "from-gray-500/20 to-gray-600/10",
|
||||
iconColor: "text-gray-400",
|
||||
features: [
|
||||
{ label: "50 kredi / ay", included: true },
|
||||
{ label: "1080p video kalitesi", included: true },
|
||||
{ label: "Max 120 saniye", included: true },
|
||||
{ label: "50 proje limiti", included: true },
|
||||
{ label: "Tüm şablonlar", included: true },
|
||||
{ label: "Öncelikli kuyruk", included: true },
|
||||
{ label: "Marka kaldırma", included: true },
|
||||
{ label: "API erişimi", included: false },
|
||||
"Ayda 3 video",
|
||||
"720p kalite",
|
||||
"Temel AI senaryo",
|
||||
"Topluluk desteği",
|
||||
],
|
||||
cta: "Mevcut Plan",
|
||||
popular: false,
|
||||
},
|
||||
{
|
||||
id: "business",
|
||||
name: "Business",
|
||||
name: "Pro",
|
||||
price: { monthly: 29, yearly: 290 },
|
||||
description: "İçerik üreticileri için",
|
||||
icon: Crown,
|
||||
monthlyPrice: 49,
|
||||
yearlyPrice: 490,
|
||||
credits: -1,
|
||||
description: "Ajanslar ve profesyonel ekipler",
|
||||
color: "cyan",
|
||||
gradient: "from-cyan-500/15 to-cyan-600/5",
|
||||
borderActive: "border-cyan-500/30",
|
||||
buttonClass: "btn-primary",
|
||||
buttonLabel: "Business'a Yükselt",
|
||||
gradient: "from-violet-500/20 to-cyan-400/10",
|
||||
iconColor: "text-violet-400",
|
||||
features: [
|
||||
{ label: "Sınırsız kredi", included: true },
|
||||
{ label: "1080p video kalitesi", included: true },
|
||||
{ label: "Max 180 saniye", included: true },
|
||||
{ label: "Sınırsız proje", included: true },
|
||||
{ label: "Tüm şablonlar + Özel", included: true },
|
||||
{ label: "Öncelikli kuyruk", included: true },
|
||||
{ label: "Marka kaldırma", included: true },
|
||||
{ label: "API erişimi", included: true },
|
||||
"Ayda 50 video",
|
||||
"1080p Full HD",
|
||||
"Gelişmiş AI senaryo",
|
||||
"Tweet → Video dönüşümü",
|
||||
"Öncelikli render",
|
||||
"E-posta desteği",
|
||||
],
|
||||
cta: "Pro'ya Geç",
|
||||
popular: true,
|
||||
},
|
||||
{
|
||||
name: "Enterprise",
|
||||
price: { monthly: 99, yearly: 990 },
|
||||
description: "Ajanslar ve büyük ekipler",
|
||||
icon: Rocket,
|
||||
gradient: "from-amber-500/20 to-orange-400/10",
|
||||
iconColor: "text-amber-400",
|
||||
features: [
|
||||
"Sınırsız video",
|
||||
"4K Ultra HD",
|
||||
"Premium AI + özel model",
|
||||
"API erişimi",
|
||||
"Özel şablonlar",
|
||||
"Öncelikli 7/24 destek",
|
||||
"Beyaz etiket seçeneği",
|
||||
],
|
||||
cta: "Enterprise'a Geç",
|
||||
popular: false,
|
||||
},
|
||||
];
|
||||
|
||||
const fadeUp = {
|
||||
hidden: { opacity: 0, y: 20 },
|
||||
show: { opacity: 1, y: 0, transition: { duration: 0.6, ease: [0.16, 1, 0.3, 1] as const } },
|
||||
};
|
||||
|
||||
export default function PricingPage() {
|
||||
const [isYearly, setIsYearly] = useState(false);
|
||||
const [billingCycle, setBillingCycle] = useState<"monthly" | "yearly">(
|
||||
"monthly",
|
||||
);
|
||||
const checkout = useCreateCheckout();
|
||||
const { data: subData } = useSubscription();
|
||||
const toast = useToast();
|
||||
|
||||
const currentPlan = subData?.data?.plan ?? subData?.plan ?? "Free";
|
||||
|
||||
const handleCheckout = async (planName: string) => {
|
||||
if (planName === "Free") {
|
||||
toast.info("Free plan zaten aktif");
|
||||
return;
|
||||
}
|
||||
if (planName === currentPlan) {
|
||||
toast.info("Bu plan zaten aktif");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await checkout.mutateAsync({ planName, billingCycle });
|
||||
} catch {
|
||||
toast.error("Ödeme sayfası açılamadı. Lütfen tekrar deneyin.");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-5xl mx-auto space-y-10 py-4">
|
||||
{/* ── Başlık ── */}
|
||||
<motion.div
|
||||
variants={fadeUp}
|
||||
initial="hidden"
|
||||
animate="show"
|
||||
className="text-center space-y-3"
|
||||
>
|
||||
<h1 className="font-[family-name:var(--font-display)] text-3xl md:text-4xl font-bold tracking-tight">
|
||||
Planını Seç, Üretmeye Başla
|
||||
</h1>
|
||||
<p className="text-[var(--color-text-muted)] text-sm md:text-base max-w-md mx-auto">
|
||||
Her plan ücretsiz deneme ile başlar. İstediğin zaman yükselt veya iptal et.
|
||||
</p>
|
||||
|
||||
{/* Aylık / Yıllık Toggle */}
|
||||
<div className="flex items-center justify-center gap-3 pt-2">
|
||||
<span className={cn("text-sm", !isYearly ? "text-[var(--color-text-primary)]" : "text-[var(--color-text-muted)]")}>
|
||||
Aylık
|
||||
</span>
|
||||
<button
|
||||
onClick={() => setIsYearly(!isYearly)}
|
||||
className={cn(
|
||||
"relative w-14 h-7 rounded-full transition-colors",
|
||||
isYearly ? "bg-violet-500" : "bg-[var(--color-bg-elevated)]"
|
||||
)}
|
||||
>
|
||||
<motion.div
|
||||
className="absolute top-0.5 w-6 h-6 rounded-full bg-white shadow-md"
|
||||
animate={{ left: isYearly ? "calc(100% - 1.625rem)" : "0.125rem" }}
|
||||
transition={{ type: "spring", stiffness: 500, damping: 30 }}
|
||||
/>
|
||||
</button>
|
||||
<span className={cn("text-sm", isYearly ? "text-[var(--color-text-primary)]" : "text-[var(--color-text-muted)]")}>
|
||||
Yıllık
|
||||
</span>
|
||||
{isYearly && (
|
||||
<motion.span
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
className="badge badge-emerald text-[10px]"
|
||||
>
|
||||
%17 tasarruf
|
||||
</motion.span>
|
||||
)}
|
||||
<div className="max-w-5xl mx-auto space-y-8">
|
||||
{/* Header */}
|
||||
<div className="text-center space-y-3">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-violet-500/10 border border-violet-500/20 text-violet-300 text-xs font-medium">
|
||||
<Sparkles size={12} />
|
||||
Fiyatlandırma
|
||||
</div>
|
||||
</motion.div>
|
||||
<h1 className="text-3xl md:text-4xl font-[family-name:var(--font-display)] font-bold text-[var(--color-text-primary)]">
|
||||
İhtiyacınıza Uygun Plan
|
||||
</h1>
|
||||
<p className="text-sm text-[var(--color-text-muted)] max-w-lg mx-auto">
|
||||
İster hobby, ister profesyonel — her seviyeye uygun planlarımızla AI
|
||||
video üretimine başlayın.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* ── Plan Kartları ── */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 md:gap-5">
|
||||
{plans.map((plan, i) => {
|
||||
const Icon = plan.icon;
|
||||
const price = isYearly ? plan.yearlyPrice : plan.monthlyPrice;
|
||||
const period = isYearly ? "/yıl" : "/ay";
|
||||
{/* Billing Toggle */}
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
<span
|
||||
className={`text-sm ${
|
||||
billingCycle === "monthly"
|
||||
? "text-[var(--color-text-primary)]"
|
||||
: "text-[var(--color-text-muted)]"
|
||||
}`}
|
||||
>
|
||||
Aylık
|
||||
</span>
|
||||
<button
|
||||
onClick={() =>
|
||||
setBillingCycle((p) => (p === "monthly" ? "yearly" : "monthly"))
|
||||
}
|
||||
className={`relative w-12 h-6 rounded-full transition-colors ${
|
||||
billingCycle === "yearly"
|
||||
? "bg-violet-500"
|
||||
: "bg-[var(--color-bg-elevated)]"
|
||||
}`}
|
||||
>
|
||||
<motion.div
|
||||
className="absolute top-0.5 left-0.5 w-5 h-5 rounded-full bg-white shadow"
|
||||
animate={{ x: billingCycle === "yearly" ? 24 : 0 }}
|
||||
transition={{ type: "spring", bounce: 0.25, duration: 0.3 }}
|
||||
/>
|
||||
</button>
|
||||
<span
|
||||
className={`text-sm ${
|
||||
billingCycle === "yearly"
|
||||
? "text-[var(--color-text-primary)]"
|
||||
: "text-[var(--color-text-muted)]"
|
||||
}`}
|
||||
>
|
||||
Yıllık
|
||||
<span className="ml-1 text-xs text-emerald-400 font-medium">
|
||||
%17 tasarruf
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Plans Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-5">
|
||||
{plans.map((plan, idx) => {
|
||||
const isCurrentPlan =
|
||||
plan.name.toLowerCase() === currentPlan.toLowerCase();
|
||||
const PlanIcon = plan.icon;
|
||||
const price = plan.price[billingCycle];
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key={plan.id}
|
||||
initial={{ opacity: 0, y: 24 }}
|
||||
key={plan.name}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: i * 0.1, duration: 0.6, ease: [0.16, 1, 0.3, 1] }}
|
||||
className={cn(
|
||||
"relative card-surface p-6 flex flex-col bg-gradient-to-br",
|
||||
plan.gradient,
|
||||
plan.recommended && "glow-violet md:-translate-y-2"
|
||||
)}
|
||||
transition={{ delay: idx * 0.1 }}
|
||||
className={`relative card p-6 flex flex-col ${
|
||||
plan.popular
|
||||
? "border-violet-500/30 shadow-lg shadow-violet-500/5"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{plan.recommended && (
|
||||
<div className="absolute -top-3 left-1/2 -translate-x-1/2">
|
||||
<span className="badge bg-violet-500 text-white text-[10px] px-3 py-1 shadow-lg shadow-violet-500/30">
|
||||
⚡ Önerilen
|
||||
</span>
|
||||
{plan.popular && (
|
||||
<div className="absolute -top-3 left-1/2 -translate-x-1/2 px-3 py-0.5 rounded-full bg-violet-500 text-white text-[10px] font-bold uppercase tracking-wider">
|
||||
En Popüler
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className={cn(
|
||||
"w-10 h-10 rounded-xl flex items-center justify-center",
|
||||
plan.color === "violet" && "bg-violet-500/15 text-violet-400",
|
||||
plan.color === "emerald" && "bg-emerald-500/15 text-emerald-400",
|
||||
plan.color === "cyan" && "bg-cyan-500/15 text-cyan-400"
|
||||
)}>
|
||||
<Icon size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-[family-name:var(--font-display)] text-lg font-bold">{plan.name}</h3>
|
||||
<p className="text-[11px] text-[var(--color-text-muted)]">{plan.description}</p>
|
||||
</div>
|
||||
<div
|
||||
className={`w-10 h-10 rounded-xl bg-gradient-to-br ${plan.gradient} flex items-center justify-center mb-4`}
|
||||
>
|
||||
<PlanIcon size={20} className={plan.iconColor} />
|
||||
</div>
|
||||
|
||||
{/* Fiyat */}
|
||||
<h3 className="text-lg font-bold text-[var(--color-text-primary)]">
|
||||
{plan.name}
|
||||
</h3>
|
||||
<p className="text-xs text-[var(--color-text-muted)] mb-4">
|
||||
{plan.description}
|
||||
</p>
|
||||
|
||||
<div className="mb-5">
|
||||
<div className="flex items-baseline gap-1">
|
||||
<span className="font-[family-name:var(--font-display)] text-4xl font-bold">
|
||||
${price}
|
||||
<span className="text-3xl font-[family-name:var(--font-display)] font-bold text-[var(--color-text-primary)]">
|
||||
${price}
|
||||
</span>
|
||||
{price > 0 && (
|
||||
<span className="text-sm text-[var(--color-text-muted)]">
|
||||
/{billingCycle === "monthly" ? "ay" : "yıl"}
|
||||
</span>
|
||||
{price > 0 && (
|
||||
<span className="text-sm text-[var(--color-text-muted)]">{period}</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-[var(--color-text-ghost)] mt-1">
|
||||
{plan.credits === -1 ? "Sınırsız video üretimi" : `${plan.credits} kredi dahil`}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Features */}
|
||||
<ul className="space-y-2.5 flex-1 mb-6">
|
||||
{plan.features.map((feat) => (
|
||||
<li key={feat.label} className="flex items-center gap-2.5 text-sm">
|
||||
{feat.included ? (
|
||||
<Check size={14} className="text-emerald-400 shrink-0" />
|
||||
) : (
|
||||
<X size={14} className="text-[var(--color-text-ghost)] shrink-0" />
|
||||
)}
|
||||
<span className={cn(
|
||||
feat.included ? "text-[var(--color-text-secondary)]" : "text-[var(--color-text-ghost)]"
|
||||
)}>
|
||||
{feat.label}
|
||||
</span>
|
||||
{plan.features.map((f) => (
|
||||
<li
|
||||
key={f}
|
||||
className="flex items-start gap-2 text-sm text-[var(--color-text-secondary)]"
|
||||
>
|
||||
<Check
|
||||
size={14}
|
||||
className="shrink-0 mt-0.5 text-emerald-400"
|
||||
/>
|
||||
{f}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{/* CTA */}
|
||||
<button className={cn("w-full py-3 rounded-xl font-semibold text-sm flex items-center justify-center gap-2", plan.buttonClass)}>
|
||||
{plan.buttonLabel}
|
||||
{price > 0 && <ArrowRight size={14} />}
|
||||
<button
|
||||
onClick={() => handleCheckout(plan.name)}
|
||||
disabled={
|
||||
isCurrentPlan || (checkout.isPending && !isCurrentPlan)
|
||||
}
|
||||
className={`w-full py-2.5 rounded-xl text-sm font-semibold transition-all ${
|
||||
isCurrentPlan
|
||||
? "bg-[var(--color-bg-elevated)] text-[var(--color-text-ghost)] cursor-default"
|
||||
: plan.popular
|
||||
? "btn-primary"
|
||||
: "bg-[var(--color-bg-elevated)] text-[var(--color-text-secondary)] hover:bg-[var(--color-bg-surface)] border border-[var(--color-border-faint)]"
|
||||
}`}
|
||||
>
|
||||
{checkout.isPending && !isCurrentPlan ? (
|
||||
<Loader2 size={16} className="animate-spin mx-auto" />
|
||||
) : isCurrentPlan ? (
|
||||
"Mevcut Plan"
|
||||
) : (
|
||||
plan.cta
|
||||
)}
|
||||
</button>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* ── Trust ── */}
|
||||
<motion.div
|
||||
variants={fadeUp}
|
||||
initial="hidden"
|
||||
animate="show"
|
||||
className="text-center space-y-2 pt-4"
|
||||
>
|
||||
<p className="text-xs text-[var(--color-text-ghost)]">
|
||||
🔒 Güvenli ödeme • İstediğin zaman iptal • 7 gün para iade garantisi
|
||||
</p>
|
||||
<p className="text-[10px] text-[var(--color-text-ghost)]">
|
||||
Stripe ile güvenli ödeme altyapısı
|
||||
</p>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user