"use client";
import { useCallback, useEffect, useMemo, useState } from "react";
import Link from "next/link";
import { useRouter, useSearchParams } from "next/navigation";
import { Badge, Button, Container } from "@/components/ui";
import { useAuth } from "@/hooks/useAuth";
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 =
| "assessment"
| "recommendations"
| "bundles"
| "pricing"
| "trial_summary"
| "create_workspace";
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 bg-white px-4 py-2.5 text-sm focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary dark:border-gray-700 dark:bg-gray-900";
if (question.input_type === "boolean") {
return (
);
}
if (question.input_type === "number") {
return (
);
}
if ((question.input_type === "multi" || question.input_type === "single") && question.options?.length) {
const multi = question.input_type === "multi";
const selected = multi ? (Array.isArray(value) ? (value as string[]) : []) : null;
return (
);
}
return (
);
}
const STEPS: { id: WizardStep; label: string }[] = [
{ id: "assessment", label: "کشف" },
{ id: "recommendations", label: "پیشنهاد" },
{ id: "bundles", label: "باندل" },
{ id: "pricing", label: "قیمت" },
{ id: "trial_summary", label: "Trial" },
];
export function CommercialDiscoverExperience() {
const { isAuthenticated, login, loading: authLoading } = useAuth();
const router = useRouter();
const searchParams = useSearchParams();
const typeParam = searchParams.get("type");
const [step, setStep] = useState("assessment");
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);
const [booted, setBooted] = useState(false);
useEffect(() => {
const session = loadAssessmentSession();
const nextAnswers = { ...session.answers };
if (typeParam && !nextAnswers.business_type_code) {
nextAnswers.business_type_code = typeParam;
}
setAnswers(nextAnswers);
if (session.selected_bundle_code) setSelectedBundle(session.selected_bundle_code);
void (async () => {
setBusy(true);
const [schema, products, bundles, pricing] = await Promise.all([
loadAssessmentSchema(),
loadCommercialProducts(),
loadCommercialBundles(),
loadCommercialPricing(),
]);
setSchemaRes(schema);
setProductsRes(products);
setBundlesRes(bundles);
setPricingRes(pricing);
setBusy(false);
setBooted(true);
})();
}, [typeParam]);
const persist = useCallback((next: AssessmentSession) => {
saveAssessmentSession(next);
}, []);
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);
const top = reco.data.recommended_bundles[0]?.code;
if (top && !selectedBundle) {
setSelectedBundle(top);
persist({ answers, selected_bundle_code: top });
}
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 optionalProducts = (productsRes?.data ?? []).filter(
(p) => !recommendedProducts.some((r) => r.code === p.product_code) && p.visibility !== "hidden"
);
const recommendedBundles = (recoRes?.data.recommended_bundles ?? [])
.map((r) => ({ ...r, bundle: bundleByCode.get(r.code) }))
.filter((r) => r.bundle);
const displayBundles =
recommendedBundles.length > 0
? recommendedBundles.map((r) => r.bundle!)
: bundlesRes?.data ?? [];
const selectedBundleObj = selectedBundle ? bundleByCode.get(selectedBundle) : null;
const pricingForBundle = (pricingRes?.data ?? []).filter((p) =>
selectedBundleObj?.pricing_refs?.includes(p.pricing_item_code)
);
const goWorkspace = () => {
persist({
answers,
selected_bundle_code: selectedBundle,
selected_product_codes: recoRes?.data.recommended_products.map((p) => p.code),
activate_trial: true,
});
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");
}
};
const stepIndex = STEPS.findIndex((s) => s.id === step);
if (!booted) {
return (
آمادهسازی مسیر کشف…
);
}
return (
← بازگشت به خانه
کشف و راهاندازی
{STEPS.map((s, i) => (
-
{s.label}
))}
{step === "assessment" ? (
ارزیابی نیازها
سؤالات از assessment schema — صنایع و نیازها بدون لیست ثابت frontend.
{schemaRes?.availability === "unavailable" ||
schemaRes?.availability === "empty" ||
!questions.length ? (
) : 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" ? (
پیشنهاد شخصیسازیشده
محصولات الزامی، اختیاری و مسیر رشد از recommendation + registry.
{recoRes?.message ? (
{recoRes.message}
) : null}
{recoRes?.data.rule_trace?.length ? (
چرا این پیشنهاد؟
{recoRes.data.rule_trace.map((step, i) => (
- {step}
))}
) : null}
محصولات پیشنهادی / الزامی
{recommendedProducts.length ? (
{recommendedProducts.map((r) => (
{r.reason ? (
{r.reason}
) : null}
))}
) : (
)}
{optionalProducts.length ? (
محصولات اختیاری / قابل گسترش
{optionalProducts.slice(0, 6).map((p) => (
))}
) : null}
{selectedBundleObj ? (
باندل پیشنهادی فعلی
{selectedBundleObj.display_name}
{selectedBundleObj.upgrade_path_code ? (
مسیر رشد: {selectedBundleObj.upgrade_path_code}
) : null}
) : null}
) : null}
{step === "bundles" ? (
مقایسه باندل
Starter / Professional / Enterprise از metadata باندل — بدون پلن ثابت.
{displayBundles.length ? (
{displayBundles.map((b) => (
r.code === b.bundle_code)?.score
}
selected={selectedBundle === b.bundle_code}
onSelect={() => {
setSelectedBundle(b.bundle_code);
persist({ answers, selected_bundle_code: b.bundle_code });
}}
/>
))}
) : (
)}
) : null}
{step === "pricing" ? (
مقایسه قیمت
آیتمهای قیمت از رجیستری؛ تخفیف و پروموشن از discount_refs / metadata.
{(pricingForBundle.length ? pricingForBundle : pricingRes?.data ?? []).length ? (
) : (
)}
) : null}
{step === "trial_summary" ? (
خلاصه Trial
پس از ثبتنام و ساخت workspace، trial از Billing / commercial subscriptions فعال
میشود — بدون اعداد ساختگی در این صفحه.
{selectedBundle ? (
باندل: {selectedBundle}
) : (
باندل انتخاب نشده — میتوانید بعداً در Billing انتخاب کنید.
)}
- ✓ ساخت tenant و زیردامنه
- ✓ داشبورد تجاری و لانچر اپها
- ✓ دامنه اختصاصی پس از entitlement
) : null}
{step === "create_workspace" ? (
ثبتنام / ورود
انتخابهای ارزیابی در session میماند و پس از ورود به onboarding منتقل میشود.
) : null}
);
}