import { api } from "@/lib/api"; import type { AdapterResult, CommercialProduct, CommercialWorkspaceSnapshot, SetupChecklistItem, TrialState, } from "../types"; import { resolveProductLaunchUrl } from "../types"; import { evaluateDomainPolicy } from "./policies"; import { loadCommercialUsage } from "./usage"; import { commercialGet, commercialPost } from "./http"; function computeTrial(sub: { status: string; trial_ends_at: string | null; } | null): TrialState | null { if (!sub) return null; const active = sub.status === "trialing"; let days_remaining: number | null = null; if (sub.trial_ends_at) { const end = new Date(sub.trial_ends_at).getTime(); days_remaining = Math.max(0, Math.ceil((end - Date.now()) / 86400000)); } return { active, days_remaining, ends_at: sub.trial_ends_at, quotas: [] }; } /** * Entitlement check — prefers commercial entitlement API; never local capability lists. * Safe deny when unavailable. */ export async function checkFeatureAccess( tenantId: string, featureKey: string ): Promise { const commercial = await commercialPost<{ has_access?: boolean; allowed?: boolean; reason?: string | null; required_plan?: string | null; required_capability?: string | null; required_bundle?: string | null; }>("/api/v1/commercial/entitlements/check", { tenant_id: tenantId, feature_key: featureKey, capability_code: featureKey, }); if (commercial.ok) { const allowed = Boolean(commercial.data.has_access ?? commercial.data.allowed); return { has_access: allowed, reason: commercial.data.reason ?? null, required_plan: commercial.data.required_plan ?? null, required_capability: commercial.data.required_capability ?? null, required_bundle: commercial.data.required_bundle ?? null, }; } try { const r = await api.subscriptions.checkFeature(tenantId, featureKey); return { has_access: r.has_access, reason: r.reason }; } catch { return { has_access: false, reason: commercial.message || "بررسی entitlement در دسترس نیست", unavailable: true, }; } } export async function loadWorkspaceSnapshot(): Promise< AdapterResult > { try { const tenantCtx = await api.tenantContext.current(); let subscription: Awaited> | null = null; try { subscription = await api.subscriptions.get(tenantCtx.tenant.id); } catch { subscription = null; } const status = subscription?.status ?? tenantCtx.subscription_status; const trial = computeTrial(subscription); const usage = await loadCommercialUsage(tenantCtx.tenant.id); if (trial && usage.availability === "ready") { trial.quotas = usage.data; } const domainEval = await evaluateDomainPolicy({ tenant_id: tenantCtx.tenant.id, subscription_status: status, }); let pinned: string[] = []; let recent: string[] = []; let quick: CommercialWorkspaceSnapshot["quick_actions"] = []; const dash = await commercialGet<{ pinned_product_codes?: string[]; recent_product_codes?: string[]; quick_actions?: CommercialWorkspaceSnapshot["quick_actions"]; locked_features?: CommercialWorkspaceSnapshot["locked_features"]; workspace_health?: string | null; checklist_completion_percent?: number | null; product_codes?: string[]; capability_codes?: string[]; bundle_code?: string | null; }>(`/api/v1/commercial/dashboard?tenant_id=${encodeURIComponent(tenantCtx.tenant.id)}`); if (dash.ok) { pinned = dash.data.pinned_product_codes ?? []; recent = dash.data.recent_product_codes ?? []; quick = dash.data.quick_actions ?? []; } const domainData = domainEval.data; const params = domainData.parameters || {}; const snapshot: CommercialWorkspaceSnapshot = { tenant_id: tenantCtx.tenant.id, tenant_name: tenantCtx.tenant.name, slug: tenantCtx.tenant.slug, status: tenantCtx.tenant.status, plan_name: tenantCtx.plan_name, subscription_status: status, onboarding_completed: tenantCtx.tenant.onboarding_completed, primary_domain: tenantCtx.primary_domain, domains: tenantCtx.domains.map((d) => ({ id: d.id, domain: d.domain, domain_type: d.domain_type, is_verified: d.is_verified, })), role: tenantCtx.role, trial, custom_domain_unlocked: Boolean(domainData.custom_domain_allowed), domain_policy: { ...domainData, ssl_ready: domainData.ssl_ready ?? (typeof params.ssl_ready === "boolean" ? params.ssl_ready : undefined), dns_instructions: domainData.dns_instructions ?? (Array.isArray(params.dns_instructions) ? (params.dns_instructions as NonNullable< CommercialWorkspaceSnapshot["domain_policy"] >["dns_instructions"]) : undefined), verification_state: domainData.verification_state ?? (typeof params.verification_state === "string" ? params.verification_state : null), }, pinned_product_codes: pinned, recent_product_codes: recent, quick_actions: quick, product_codes: dash.ok ? dash.data.product_codes ?? [] : [], capability_codes: dash.ok ? dash.data.capability_codes ?? [] : [], bundle_code: dash.ok ? dash.data.bundle_code ?? null : null, locked_features: dash.ok ? dash.data.locked_features ?? [] : [], workspace_health: dash.ok ? dash.data.workspace_health ?? null : null, checklist_completion_percent: dash.ok ? dash.data.checklist_completion_percent ?? null : null, }; return { availability: "ready", data: snapshot, source: "core.tenant+commercial" }; } catch (err) { return { availability: "error", data: null, message: err instanceof Error ? err.message : "خطا در بارگذاری workspace", source: "none", }; } } /** Checklist from discovered products' launch metadata — no hardcoded product list. */ export function buildSetupChecklist( products: CommercialProduct[], opts?: { installedCodes?: string[] } ): SetupChecklistItem[] { const installed = new Set(opts?.installedCodes ?? []); const fromProducts = products .filter((p) => p.visibility !== "hidden") .map((p) => ({ id: `product:${p.product_code}`, label: p.display_name, href: resolveProductLaunchUrl(p), product_code: p.product_code, done: installed.has(p.product_code), required: false, })); const workspaceActions = products.length === 0 ? [ { id: "workspace", label: "Workspace", href: "/dashboard", required: true }, { id: "billing", label: "Billing", href: "/billing", required: false }, ] : []; return [...workspaceActions, ...fromProducts]; }