"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { Badge, Button, Container } from "@/components/ui"; import { useAuth } from "@/hooks/useAuth"; import { useTheme } from "@/hooks/useTheme"; import { loadAssessmentSchema, loadCommercialBundles, loadCommercialPricing, loadCommercialProducts, requestRecommendations, } from "../adapters"; import type { AdapterResult, AssessmentQuestion, AssessmentSchema, CommercialBundle, CommercialPricingItem, CommercialProduct, RecommendationResult, } from "../types"; import { BundleCard, CommercialEmptyState, ProductCard, } from "../components"; import { loadAssessmentSession, saveAssessmentSession, type AssessmentSession, } from "../session"; type WizardStep = | "landing" | "assessment" | "recommendations" | "trial_summary" | "create_workspace"; export type { AssessmentSession }; export { loadAssessmentSession, saveAssessmentSession }; function QuestionField({ question, value, onChange, }: { question: AssessmentQuestion; value: unknown; onChange: (v: unknown) => void; }) { const inputClass = "mt-1 w-full rounded-xl border border-gray-200 px-4 py-2.5 text-sm focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary"; if (question.input_type === "boolean") { return ( ); } if (question.input_type === "number") { return ( ); } if (question.input_type === "multi" && question.options?.length) { const selected = Array.isArray(value) ? (value as string[]) : []; return (
{question.prompt}
{question.options.map((opt) => { const on = selected.includes(opt.value); return ( ); })}
); } if (question.input_type === "single" && question.options?.length) { return (
{question.prompt}
{question.options.map((opt) => ( ))}
); } return ( ); } export function CommercialLandingExperience() { const { theme } = useTheme(); const { isAuthenticated, login, loading: authLoading } = useAuth(); const router = useRouter(); const siteName = theme?.site_name ?? "TorbatYar"; const [step, setStep] = useState("landing"); const [schemaRes, setSchemaRes] = useState | null>(null); const [productsRes, setProductsRes] = useState | null>(null); const [bundlesRes, setBundlesRes] = useState | null>(null); const [pricingRes, setPricingRes] = useState | null>(null); const [recoRes, setRecoRes] = useState | null>(null); const [answers, setAnswers] = useState>({}); const [selectedBundle, setSelectedBundle] = useState(null); const [qIndex, setQIndex] = useState(0); const [busy, setBusy] = useState(false); useEffect(() => { const session = loadAssessmentSession(); setAnswers(session.answers || {}); if (session.selected_bundle_code) setSelectedBundle(session.selected_bundle_code); }, []); const persist = useCallback((next: AssessmentSession) => { saveAssessmentSession(next); }, []); const startAssessment = async () => { setBusy(true); const [schema, products, bundles, pricing] = await Promise.all([ loadAssessmentSchema(), loadCommercialProducts(), loadCommercialBundles(), loadCommercialPricing(), ]); setSchemaRes(schema); setProductsRes(products); setBundlesRes(bundles); setPricingRes(pricing); setQIndex(0); setStep("assessment"); setBusy(false); }; const questions = schemaRes?.data.questions ?? []; const currentQ = questions[qIndex]; const runRecommendations = async () => { setBusy(true); persist({ answers, selected_bundle_code: selectedBundle }); const reco = await requestRecommendations(answers); setRecoRes(reco); setStep("recommendations"); setBusy(false); }; const productByCode = useMemo(() => { const map = new Map(); (productsRes?.data ?? []).forEach((p) => map.set(p.product_code, p)); return map; }, [productsRes]); const bundleByCode = useMemo(() => { const map = new Map(); (bundlesRes?.data ?? []).forEach((b) => map.set(b.bundle_code, b)); return map; }, [bundlesRes]); const recommendedProducts = (recoRes?.data.recommended_products ?? []) .map((r) => ({ ...r, product: productByCode.get(r.code) })) .filter((r) => r.product); const recommendedBundles = (recoRes?.data.recommended_bundles ?? []) .map((r) => ({ ...r, bundle: bundleByCode.get(r.code) })) .filter((r) => r.bundle); const goWorkspace = () => { persist({ answers, selected_bundle_code: selectedBundle, selected_product_codes: recoRes?.data.recommended_products.map((p) => p.code), }); if (isAuthenticated) { const intent = typeof answers.business_type_code === "string" ? answers.business_type_code : ""; router.push(intent ? `/onboarding?intent=${encodeURIComponent(intent)}` : "/onboarding"); } else { setStep("create_workspace"); } }; return (
{step === "landing" ? (
Commercial SaaS Runtime

{siteName}

نوع کسب‌وکار و نیازها را مشخص کنید؛ محصولات، باندل و trial از رجیستری پلتفرم پیشنهاد می‌شود — نه از لیست ثابت اپلیکیشن‌ها.

{isAuthenticated ? ( ) : ( )}
    {["ارزیابی", "پیشنهاد", "Trial", "Workspace"].map((s, i) => (
  1. {i + 1}. {s}
  2. ))}
) : null} {step === "assessment" ? (

ارزیابی نیازها

سؤالات از metadata بک‌اند بارگذاری می‌شوند و برای صنایع آینده بدون تغییر frontend گسترش می‌یابند.

{schemaRes?.availability === "unavailable" || schemaRes?.availability === "empty" ? (

می‌توانید بدون پیشنهاد موتور، ساخت workspace را ادامه دهید. پس از ثبت schema در Admin، همین صفحه سؤالات را نشان می‌دهد.

) : currentQ ? (

سؤال {qIndex + 1} از {questions.length}

{ const next = { ...answers, [currentQ.question_id]: v }; setAnswers(next); persist({ answers: next, selected_bundle_code: selectedBundle }); }} />
{qIndex < questions.length - 1 ? ( ) : ( )}
) : null}
) : null} {step === "recommendations" ? (

پیشنهاد محصولات و باندل

نتایج از رجیستری/موتور پیشنهاد؛ در صورت نبود سرویس، داده جعلی نمایش داده نمی‌شود.

{recoRes?.message ? (

{recoRes.message}

) : null}

محصولات پیشنهادی

{recommendedProducts.length ? (
{recommendedProducts.map((r) => ( ))}
) : productsRes?.availability === "ready" && productsRes.data.length ? (
{productsRes.data.slice(0, 8).map((p) => ( ))}
) : ( )}

باندل پیشنهادی

{recommendedBundles.length ? (
{recommendedBundles.map((r) => ( { setSelectedBundle(r.code); persist({ answers, selected_bundle_code: r.code }); }} /> ))}
) : bundlesRes?.availability === "ready" && bundlesRes.data.length ? (
{bundlesRes.data.map((b) => ( { setSelectedBundle(b.bundle_code); persist({ answers, selected_bundle_code: b.bundle_code }); }} /> ))}
) : ( )}
) : null} {step === "trial_summary" ? (

خلاصه دوره آزمایشی

جزئیات trial (روز، سهمیه، قوانین) از سیاست‌ها و قیمت‌گذاری بک‌اند خوانده می‌شود. تا زمان در دسترس بودن رجیستری، فقط وضعیت آمادگی نمایش داده می‌شود — بدون اعداد ساختگی.

{pricingRes?.availability === "ready" && pricingRes.data.length ? (
    {pricingRes.data .filter((p) => p.pricing_model === "trial" || p.trial_days) .map((p) => (
  • {p.display_name} {p.trial_days ? ` — ${p.trial_days} روز` : ""} {p.pricing_item_code}
  • ))}
) : ( )} {selectedBundle ? (

باندل انتخابی: {selectedBundle}

) : (

باندلی انتخاب نشده است.

)}
) : null} {step === "create_workspace" ? (

ورود برای ساخت Workspace

برای ایجاد tenant و داشبورد تجاری وارد شوید. انتخاب‌های ارزیابی در session نگه داشته می‌شود.

اگر وارد شده‌اید، ادامه onboarding
) : null}
); }