332 lines
15 KiB
TypeScript
332 lines
15 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useState } from "react";
|
||
import Link from "next/link";
|
||
import { Badge, Button, Container } from "@/components/ui";
|
||
import { useAuth } from "@/hooks/useAuth";
|
||
import { useTheme } from "@/hooks/useTheme";
|
||
import {
|
||
loadCommercialBundles,
|
||
loadCommercialPricing,
|
||
loadCommercialProducts,
|
||
} from "../adapters";
|
||
import { commercialGet, extractItems } from "../adapters/http";
|
||
import type { CommercialBundle, CommercialPricingItem, CommercialProduct } from "../types";
|
||
|
||
const JOURNEY = [
|
||
"کشف نیاز",
|
||
"پیشنهاد هوشمند",
|
||
"باندل و قیمت",
|
||
"Trial رایگان",
|
||
"Workspace",
|
||
"تولید",
|
||
];
|
||
|
||
const FAQ = [
|
||
{
|
||
q: "آیا میتوانم بدون کارت بانکی شروع کنم؟",
|
||
a: "بله. پس از ساخت workspace میتوانید دوره آزمایشی را از Billing فعال کنید — جزئیات trial از رجیستری قیمتگذاری میآید.",
|
||
},
|
||
{
|
||
q: "محصولات جدید چطور اضافه میشوند؟",
|
||
a: "با ثبت در رجیستری تجاری Admin. بدون تغییر frontend در کاتالوگ و لانچر ظاهر میشوند.",
|
||
},
|
||
{
|
||
q: "دامنه اختصاصی کی باز میشود؟",
|
||
a: "بر اساس سیاست دامنه پس از entitlement / اشتراک فعال — تا آن زمان فقط زیردامنه پلتفرم.",
|
||
},
|
||
{
|
||
q: "آیا باندل قیمت ثابت دارد؟",
|
||
a: "خیر. باندل فقط به pricing_refs ارجاع میدهد؛ مبالغ از کاتالوگ قیمت runtime خوانده میشود.",
|
||
},
|
||
];
|
||
|
||
export function CommercialMarketingLanding() {
|
||
const { theme } = useTheme();
|
||
const { isAuthenticated, login, loading: authLoading } = useAuth();
|
||
const siteName = theme?.site_name ?? "TorbatYar";
|
||
|
||
const [products, setProducts] = useState<CommercialProduct[]>([]);
|
||
const [bundles, setBundles] = useState<CommercialBundle[]>([]);
|
||
const [pricing, setPricing] = useState<CommercialPricingItem[]>([]);
|
||
const [businessTypes, setBusinessTypes] = useState<
|
||
Array<{ business_type_code: string; display_name: string }>
|
||
>([]);
|
||
|
||
useEffect(() => {
|
||
void Promise.all([
|
||
loadCommercialProducts(),
|
||
loadCommercialBundles(),
|
||
loadCommercialPricing(),
|
||
commercialGet<unknown>("/api/v1/commercial/business-types"),
|
||
]).then(([p, b, pr, bt]) => {
|
||
if (p.availability === "ready") setProducts(p.data.filter((x) => x.visibility !== "hidden"));
|
||
if (b.availability === "ready") setBundles(b.data);
|
||
if (pr.availability === "ready") setPricing(pr.data.slice(0, 4));
|
||
if (bt.ok) {
|
||
setBusinessTypes(
|
||
extractItems<{ business_type_code: string; display_name: string }>(bt.data, [
|
||
"items",
|
||
"business_types",
|
||
])
|
||
);
|
||
}
|
||
});
|
||
}, []);
|
||
|
||
return (
|
||
<div className="pb-20">
|
||
{/* Hero — one composition, brand-first, full-bleed atmosphere */}
|
||
<section className="relative min-h-[min(92vh,820px)] overflow-hidden">
|
||
<div
|
||
className="absolute inset-0"
|
||
style={{
|
||
background:
|
||
"radial-gradient(ellipse 80% 60% at 70% 20%, rgba(2,132,199,0.22), transparent 55%), radial-gradient(ellipse 50% 40% at 10% 80%, rgba(15,23,42,0.08), transparent 50%), linear-gradient(165deg, #0b1220 0%, #0f2744 42%, #0c4a6e 100%)",
|
||
}}
|
||
/>
|
||
<div
|
||
className="pointer-events-none absolute inset-0 opacity-[0.12]"
|
||
style={{
|
||
backgroundImage:
|
||
"url(\"data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='1'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")",
|
||
}}
|
||
/>
|
||
<Container className="relative flex min-h-[min(92vh,820px)] flex-col justify-center py-16">
|
||
<p className="text-sm font-semibold tracking-[0.2em] text-sky-200/90">{siteName}</p>
|
||
<h1 className="mt-4 max-w-3xl text-4xl font-extrabold leading-[1.15] text-white sm:text-5xl lg:text-6xl">
|
||
سیستمعامل کسبوکار شما روی ابر
|
||
</h1>
|
||
<p className="mt-5 max-w-xl text-base leading-8 text-sky-100/85 sm:text-lg">
|
||
از کشف نیاز تا داشبورد تولید — محصولات، باندل و قیمت از رجیستری زنده؛ بدون کاتالوگ ثابت.
|
||
</p>
|
||
<div className="mt-9 flex flex-wrap gap-3">
|
||
<Link href="/discover">
|
||
<Button size="lg" className="bg-white text-secondary hover:bg-sky-50">
|
||
شروع Trial رایگان
|
||
</Button>
|
||
</Link>
|
||
<Link href="/pricing">
|
||
<Button
|
||
size="lg"
|
||
variant="outline"
|
||
className="border-white/40 bg-transparent text-white hover:bg-white/10"
|
||
>
|
||
مقایسه قیمت
|
||
</Button>
|
||
</Link>
|
||
{isAuthenticated ? (
|
||
<Link href="/dashboard">
|
||
<Button size="lg" variant="ghost" className="text-sky-100 hover:bg-white/10">
|
||
داشبورد
|
||
</Button>
|
||
</Link>
|
||
) : (
|
||
<Button
|
||
size="lg"
|
||
variant="ghost"
|
||
className="text-sky-100 hover:bg-white/10"
|
||
loading={authLoading}
|
||
onClick={() => login()}
|
||
>
|
||
ورود
|
||
</Button>
|
||
)}
|
||
</div>
|
||
</Container>
|
||
</section>
|
||
|
||
{/* Business selector teaser */}
|
||
<section className="border-b border-gray-100 bg-white py-16 dark:border-gray-800 dark:bg-gray-950">
|
||
<Container>
|
||
<h2 className="text-2xl font-bold text-secondary dark:text-gray-100">کشف کسبوکار</h2>
|
||
<p className="mt-2 max-w-2xl text-sm text-gray-600 dark:text-gray-400">
|
||
نوع کسبوکار از رجیستری میآید؛ ارزیابی چندمرحلهای نیازها را به پیشنهاد محصول و باندل وصل
|
||
میکند.
|
||
</p>
|
||
<div className="mt-8 flex flex-wrap gap-2">
|
||
{businessTypes.length ? (
|
||
businessTypes.slice(0, 12).map((t) => (
|
||
<Link
|
||
key={t.business_type_code}
|
||
href={`/discover?type=${encodeURIComponent(t.business_type_code)}`}
|
||
className="rounded-full border border-gray-200 px-4 py-2 text-sm text-secondary transition hover:border-primary hover:bg-[var(--color-primary-soft)] dark:border-gray-700 dark:text-gray-200"
|
||
>
|
||
{t.display_name}
|
||
</Link>
|
||
))
|
||
) : (
|
||
<Link href="/discover">
|
||
<Button>شروع ارزیابی کسبوکار</Button>
|
||
</Link>
|
||
)}
|
||
</div>
|
||
</Container>
|
||
</section>
|
||
|
||
{/* Product ecosystem */}
|
||
<section className="py-16">
|
||
<Container>
|
||
<h2 className="text-2xl font-bold text-secondary dark:text-gray-100">اکوسیستم محصولات</h2>
|
||
<p className="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
||
کشف پویا از رجیستری — افزودن محصول در Admin بدون تغییر این صفحه.
|
||
</p>
|
||
{products.length ? (
|
||
<ul className="mt-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||
{products.slice(0, 9).map((p) => (
|
||
<li
|
||
key={p.product_code}
|
||
className="border-b border-gray-100 pb-4 dark:border-gray-800"
|
||
>
|
||
<p className="font-semibold text-secondary dark:text-gray-100">{p.display_name}</p>
|
||
<p className="mt-1 text-xs text-gray-500">{p.description || p.category || "—"}</p>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
) : (
|
||
<p className="mt-6 text-sm text-gray-500">
|
||
کاتالوگ محصول هنوز از API پر نشده — داده جعلی نمایش داده نمیشود.
|
||
</p>
|
||
)}
|
||
</Container>
|
||
</section>
|
||
|
||
{/* Feature / industry / bundle / pricing teasers */}
|
||
<section className="bg-[var(--surface-muted)] py-16 dark:bg-gray-900/40">
|
||
<Container className="space-y-14">
|
||
<div>
|
||
<h2 className="text-2xl font-bold text-secondary dark:text-gray-100">قابلیتهای پلتفرم</h2>
|
||
<p className="mt-2 max-w-2xl text-sm text-gray-600 dark:text-gray-400">
|
||
ارزیابی metadata-driven، پیشنهاد runtime، قفل قابلیت، دامنه بر اساس سیاست، Billing و
|
||
لانچر اپها — همه از Core Commercial.
|
||
</p>
|
||
</div>
|
||
|
||
<div>
|
||
<h2 className="text-2xl font-bold text-secondary dark:text-gray-100">صنایع</h2>
|
||
<p className="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
||
صنایع نامحدود از business-types registry — نه لیست ثابت frontend.
|
||
</p>
|
||
{!businessTypes.length ? (
|
||
<p className="mt-4 text-sm text-gray-500">انواع کسبوکار از API در دسترس نیست.</p>
|
||
) : null}
|
||
</div>
|
||
|
||
<div>
|
||
<h2 className="text-2xl font-bold text-secondary dark:text-gray-100">باندلها</h2>
|
||
{bundles.length ? (
|
||
<ul className="mt-6 grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||
{bundles.slice(0, 6).map((b) => (
|
||
<li key={b.bundle_code}>
|
||
<p className="text-lg font-bold text-secondary dark:text-gray-100">
|
||
{b.display_name}
|
||
</p>
|
||
<p className="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
||
{b.description || "باندل تجاری از رجیستری"}
|
||
</p>
|
||
{b.recommendation_badge ? (
|
||
<Badge variant="primary" className="mt-2">
|
||
{b.recommendation_badge}
|
||
</Badge>
|
||
) : null}
|
||
</li>
|
||
))}
|
||
</ul>
|
||
) : (
|
||
<p className="mt-4 text-sm text-gray-500">باندلی در رجیستری نیست.</p>
|
||
)}
|
||
<Link href="/discover" className="mt-6 inline-block text-sm font-semibold text-primary">
|
||
مقایسه و انتخاب باندل →
|
||
</Link>
|
||
</div>
|
||
|
||
<div>
|
||
<h2 className="text-2xl font-bold text-secondary dark:text-gray-100">نمای قیمت</h2>
|
||
{pricing.length ? (
|
||
<ul className="mt-6 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||
{pricing.map((item) => (
|
||
<li key={item.pricing_item_code}>
|
||
<p className="font-semibold text-secondary dark:text-gray-100">
|
||
{item.display_name}
|
||
</p>
|
||
<p className="mt-1 text-xs text-gray-500">
|
||
{item.pricing_model || "—"} ·{" "}
|
||
{item.amount_minor != null
|
||
? `${item.amount_minor} ${item.currency || ""}`
|
||
: "مبلغ از بکاند"}
|
||
</p>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
) : (
|
||
<p className="mt-4 text-sm text-gray-500">آیتم قیمت از API نیست — بدون مبلغ ساختگی.</p>
|
||
)}
|
||
<Link href="/pricing" className="mt-6 inline-block text-sm font-semibold text-primary">
|
||
مشاهده کامل قیمتگذاری →
|
||
</Link>
|
||
</div>
|
||
</Container>
|
||
</section>
|
||
|
||
{/* Customer journey */}
|
||
<section className="py-16">
|
||
<Container>
|
||
<h2 className="text-2xl font-bold text-secondary dark:text-gray-100">مسیر مشتری</h2>
|
||
<ol className="mt-8 grid gap-4 sm:grid-cols-3 lg:grid-cols-6">
|
||
{JOURNEY.map((step, i) => (
|
||
<li key={step} className="text-sm">
|
||
<span className="font-mono text-xs text-primary">{String(i + 1).padStart(2, "0")}</span>
|
||
<p className="mt-1 font-semibold text-secondary dark:text-gray-100">{step}</p>
|
||
</li>
|
||
))}
|
||
</ol>
|
||
</Container>
|
||
</section>
|
||
|
||
{/* Trust + FAQ + CTA */}
|
||
<section className="border-t border-gray-100 bg-white py-16 dark:border-gray-800 dark:bg-gray-950">
|
||
<Container className="grid gap-12 lg:grid-cols-2">
|
||
<div>
|
||
<h2 className="text-2xl font-bold text-secondary dark:text-gray-100">اعتماد سازمانی</h2>
|
||
<ul className="mt-4 space-y-2 text-sm text-gray-600 dark:text-gray-400">
|
||
<li>چندمستأجری و SSO یکپارچه</li>
|
||
<li>سیاست دامنه و entitlement قابل ممیزی</li>
|
||
<li>رجیستری نسخهدار محصولات و قیمت</li>
|
||
<li>بدون کاتالوگ جعلی در frontend</li>
|
||
</ul>
|
||
</div>
|
||
<div>
|
||
<h2 className="text-2xl font-bold text-secondary dark:text-gray-100">سوالات متداول</h2>
|
||
<dl className="mt-4 space-y-4">
|
||
{FAQ.map((item) => (
|
||
<div key={item.q}>
|
||
<dt className="text-sm font-semibold text-secondary dark:text-gray-100">{item.q}</dt>
|
||
<dd className="mt-1 text-sm text-gray-600 dark:text-gray-400">{item.a}</dd>
|
||
</div>
|
||
))}
|
||
</dl>
|
||
</div>
|
||
</Container>
|
||
<Container className="mt-14 text-center">
|
||
<h2 className="text-3xl font-extrabold text-secondary dark:text-gray-100">
|
||
آماده ساخت workspace هستید؟
|
||
</h2>
|
||
<p className="mx-auto mt-3 max-w-lg text-sm text-gray-600 dark:text-gray-400">
|
||
ارزیابی را شروع کنید، باندل را انتخاب کنید، trial بگیرید و وارد داشبورد تجاری شوید.
|
||
</p>
|
||
<div className="mt-8 flex flex-wrap justify-center gap-3">
|
||
<Link href="/discover">
|
||
<Button size="lg">شروع رایگان</Button>
|
||
</Link>
|
||
<Link href="/register?redirect=/onboarding">
|
||
<Button size="lg" variant="outline">
|
||
ثبتنام
|
||
</Button>
|
||
</Link>
|
||
</div>
|
||
</Container>
|
||
</section>
|
||
</div>
|
||
);
|
||
}
|