"use client";
import { FormEvent, Suspense, useEffect, useMemo, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { AuthGuard } from "@/components/AuthGuard";
import { Button, Container } from "@/components/ui";
import { api, ApiError, type TenantContext } from "@/lib/api";
import { useMe } from "@/hooks/useMe";
import { FeatureLock } from "@/modules/commercial/components";
import { evaluateDomainPolicy, loadWorkspaceSnapshot } from "@/modules/commercial/adapters";
import { commercialPost } from "@/modules/commercial/adapters/http";
import {
loadAssessmentSession,
saveAssessmentSession,
} from "@/modules/commercial/session";
type Step = "business" | "branding" | "domain" | "review";
const STEPS: { id: Step; label: string }[] = [
{ id: "business", label: "اطلاعات کسبوکار" },
{ id: "branding", label: "برندینگ" },
{ id: "domain", label: "دامنه" },
{ id: "review", label: "بازبینی و تکمیل" },
];
function slugify(value: string): string {
return value
.trim()
.toLowerCase()
.replace(/\s+/g, "-")
.replace(/[^a-z0-9-]/g, "")
.replace(/-+/g, "-")
.replace(/^-|-$/g, "");
}
const inputClass =
"mt-1 w-full rounded-xl border border-gray-200 px-4 py-2.5 focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary disabled:bg-gray-50";
function StepIndicator({ current }: { current: Step }) {
const currentIndex = STEPS.findIndex((s) => s.id === current);
return (
{STEPS.map((step, index) => {
const isActive = step.id === current;
const isDone = index < currentIndex;
return (
-
{index + 1}
{step.label}
{index < STEPS.length - 1 && /}
);
})}
);
}
function OnboardingWizard() {
const router = useRouter();
const searchParams = useSearchParams();
const intentParam = searchParams.get("intent");
const { me, loading: meLoading } = useMe();
const [step, setStep] = useState("business");
const [ctx, setCtx] = useState(null);
const [bootstrapping, setBootstrapping] = useState(true);
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
const [domainUnlocked, setDomainUnlocked] = useState(false);
const [domainReason, setDomainReason] = useState(null);
const session = useMemo(() => loadAssessmentSession(), []);
const [businessName, setBusinessName] = useState("");
const [slug, setSlug] = useState("");
const [slugTouched, setSlugTouched] = useState(false);
const [businessType, setBusinessType] = useState(
(typeof session.answers.business_type_code === "string"
? session.answers.business_type_code
: intentParam) || ""
);
const [primaryColor, setPrimaryColor] = useState("#0284c7");
const [secondaryColor, setSecondaryColor] = useState("#0f172a");
const [logoUrl, setLogoUrl] = useState("");
const [customDomain, setCustomDomain] = useState("");
useEffect(() => {
if (meLoading) return;
if (!me) {
setBootstrapping(false);
return;
}
if (!me.onboarding_required) {
router.replace("/dashboard");
return;
}
if (me.current_tenant_id) {
api.tenantContext
.current()
.then(async (tenantCtx) => {
setCtx(tenantCtx);
const snap = await loadWorkspaceSnapshot();
if (snap.data) {
setDomainUnlocked(snap.data.custom_domain_unlocked);
setDomainReason(snap.data.domain_policy?.reason ?? null);
} else {
const pol = await evaluateDomainPolicy({
tenant_id: tenantCtx.tenant.id,
subscription_status: tenantCtx.subscription_status,
});
setDomainUnlocked(Boolean(pol.data.custom_domain_allowed));
setDomainReason(pol.data.reason ?? null);
}
setStep("branding");
})
.catch(() => undefined)
.finally(() => setBootstrapping(false));
} else {
setBootstrapping(false);
}
}, [me, meLoading, router]);
const handleBusinessNameChange = (value: string) => {
setBusinessName(value);
if (!slugTouched) setSlug(slugify(value));
};
const handleCreateTenant = async (e: FormEvent) => {
e.preventDefault();
setError("");
setLoading(true);
try {
const result = await api.onboarding.createTenant({
business_name: businessName,
slug,
business_type: businessType || undefined,
});
setCtx(result);
const pol = await evaluateDomainPolicy({
tenant_id: result.tenant.id,
subscription_status: result.subscription_status,
});
setDomainUnlocked(Boolean(pol.data.custom_domain_allowed));
setDomainReason(pol.data.reason ?? null);
saveAssessmentSession({
...loadAssessmentSession(),
answers: {
...loadAssessmentSession().answers,
business_type_code: businessType,
},
});
setStep("branding");
} catch (err) {
setError(err instanceof ApiError ? err.message : "ایجاد workspace ناموفق بود");
} finally {
setLoading(false);
}
};
const handleBranding = async (e: FormEvent) => {
e.preventDefault();
if (!ctx) return;
setError("");
setLoading(true);
try {
const result = await api.onboarding.updateBranding(ctx.tenant.id, {
primary_color: primaryColor || undefined,
secondary_color: secondaryColor || undefined,
logo_url: logoUrl.trim() || undefined,
});
setCtx(result);
setStep("domain");
} catch (err) {
setError(err instanceof ApiError ? err.message : "ذخیره برندینگ ناموفق بود");
} finally {
setLoading(false);
}
};
const handleDomain = async (e: FormEvent) => {
e.preventDefault();
if (!ctx) return;
setError("");
setLoading(true);
try {
let result = ctx;
if (customDomain.trim()) {
if (!domainUnlocked) {
setError(
domainReason ||
"دامنه اختصاصی بر اساس سیاست تجاری قفل است. از زیردامنه پلتفرم استفاده کنید."
);
setLoading(false);
return;
}
result = await api.onboarding.updateDomain(ctx.tenant.id, {
custom_domain: customDomain.trim(),
});
setCtx(result);
}
setStep("review");
} catch (err) {
setError(err instanceof ApiError ? err.message : "ثبت دامنه ناموفق بود");
} finally {
setLoading(false);
}
};
const handleComplete = async () => {
if (!ctx) return;
setError("");
setLoading(true);
try {
await api.onboarding.complete(ctx.tenant.id);
const session = loadAssessmentSession();
if (session.activate_trial || session.selected_bundle_code) {
await commercialPost("/api/v1/commercial/subscriptions", {
tenant_id: ctx.tenant.id,
mode: "trial",
status: "trialing",
bundle_code: session.selected_bundle_code || undefined,
answers: session.answers,
}).catch(() => null);
}
router.push("/dashboard");
} catch (err) {
setError(err instanceof ApiError ? err.message : "تکمیل onboarding ناموفق بود");
setLoading(false);
}
};
if (meLoading || bootstrapping) {
return (
در حال بارگذاری...
);
}
const selectedBundle = loadAssessmentSession().selected_bundle_code;
return (
راهاندازی workspace
پس از تکمیل به داشبورد تجاری هدایت میشوید.
{error ? (
{error}
) : null}
{step === "business" ? (
) : null}
{step === "branding" && ctx ? (
) : null}
{step === "domain" && ctx ? (
}
>
) : null}
{step === "review" && ctx ? (
نام: {ctx.tenant.name}
slug:{" "}
{ctx.tenant.slug}
) : null}
);
}
export default function OnboardingPage() {
return (
در حال بارگذاری...
}
>
);
}