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 | null; trial_ends_at?: string | null; } | null): TrialState | null { if (!sub?.status) 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 ?? null, quotas: [] }; } /** * Entitlement check — Commercial Runtime only. 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, }; } return { has_access: false, reason: commercial.message || "بررسی entitlement در دسترس نیست", unavailable: true, }; } export async function loadWorkspaceSnapshot(): Promise< AdapterResult > { try { const tenantCtx = await api.tenantContext.current(); const tenantId = tenantCtx.tenant.id; const dash = await commercialGet<{ subscription?: { status?: string | null; plan_code?: string | null; bundle_code?: string | null; trial_ends_at?: string | null; } | null; 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(tenantId)}`); const commercialSub = dash.ok ? dash.data.subscription ?? null : null; const status = commercialSub?.status ?? tenantCtx.subscription_status ?? null; const trial = computeTrial(commercialSub); const usage = await loadCommercialUsage(tenantId); if (trial && usage.availability === "ready") { trial.quotas = usage.data; } const domainEval = await evaluateDomainPolicy({ tenant_id: tenantId, subscription_status: status, }); const pinned = dash.ok ? dash.data.pinned_product_codes ?? [] : []; const recent = dash.ok ? dash.data.recent_product_codes ?? [] : []; const quick = dash.ok ? dash.data.quick_actions ?? [] : []; const domainData = domainEval.data; const params = domainData.parameters || {}; const snapshot: CommercialWorkspaceSnapshot = { tenant_id: tenantId, tenant_name: tenantCtx.tenant.name, slug: tenantCtx.tenant.slug, status: tenantCtx.tenant.status, plan_name: commercialSub?.plan_code ?? 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 ?? commercialSub?.bundle_code ?? null : commercialSub?.bundle_code ?? 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: "commercial.runtime" }; } 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]; }