"use client"; import { FormEvent, useCallback, useEffect, useState } from "react"; import Link from "next/link"; import { useParams } from "next/navigation"; import { api, ApiError, type Tenant, type TenantDomain, } from "@/lib/api"; import { getStoredToken } from "@/lib/auth"; import { identityApi, type AdminUser, type TenantMember } from "@/lib/identity"; import { AdminPageHeader, Banner, Button, Field, SelectField, StatusBadge, TextField, cardClass, inputClass, } from "@/components/admin/controls"; import { commercialGet, commercialPost } from "@/modules/commercial/adapters/http"; const TENANT_STATUSES = [ "draft", "pending_activation", "active", "suspended", "archived", "inactive", ].map((s) => ({ value: s, label: s })); const SUBSCRIPTION_STATUSES = [ "trialing", "active", "past_due", "canceled", "expired", ].map((s) => ({ value: s, label: s })); type CommercialPlanOption = { plan_code: string; display_name: string; }; type CommercialSubscriptionView = { status?: string | null; plan_code?: string | null; bundle_code?: string | null; pricing_item_code?: string | null; trial_ends_at?: string | null; }; export default function TenantDetailPage() { const params = useParams<{ id: string }>(); const tenantId = params.id; const [tenant, setTenant] = useState(null); const [domains, setDomains] = useState([]); const [subscription, setSubscription] = useState(null); const [members, setMembers] = useState([]); const [plans, setPlans] = useState([]); const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); const load = useCallback(async () => { setLoading(true); setError(""); const token = getStoredToken(); try { const t = await api.platformTenants.get(tenantId); setTenant(t); } catch (e) { setError(e instanceof ApiError ? e.message : "دریافت Tenant ناموفق بود"); setLoading(false); return; } const [d, dash, plansRes, mem, usersRes] = await Promise.allSettled([ api.domains.listForTenant(tenantId), commercialGet<{ subscription?: CommercialSubscriptionView | null }>( `/api/v1/commercial/dashboard?tenant_id=${encodeURIComponent(tenantId)}` ), commercialGet<{ items?: Array>; plans?: Array> }>( "/api/v1/commercial/plans?status=published&limit=100" ), token ? identityApi.listMembers(token, tenantId) : Promise.resolve([]), token ? identityApi.listUsers(token, { limit: 100 }) : Promise.resolve([]), ]); if (d.status === "fulfilled") setDomains(d.value); if (dash.status === "fulfilled" && dash.value.ok) { setSubscription(dash.value.data.subscription ?? null); } else { setSubscription(null); } if (plansRes.status === "fulfilled" && plansRes.value.ok) { const raw = plansRes.value.data.items ?? plansRes.value.data.plans ?? []; setPlans( raw .map((p) => ({ plan_code: String(p.plan_code || p.stable_code || ""), display_name: String(p.display_name || p.plan_code || p.stable_code || ""), })) .filter((p) => p.plan_code) ); } else { setPlans([]); } if (mem.status === "fulfilled") setMembers(mem.value); if (usersRes.status === "fulfilled") setUsers(usersRes.value); setLoading(false); }, [tenantId]); useEffect(() => { void load(); }, [load]); if (loading) { return

در حال بارگذاری...

; } if (!tenant) { return (
{error || "Tenant یافت نشد"} ← بازگشت به لیست
); } return (
← بازگشت به لیست
} /> {error && {error}}
); } function TenantEditCard({ tenant, onUpdated, onError, }: { tenant: Tenant; onUpdated: (t: Tenant) => void; onError: (m: string) => void; }) { const [name, setName] = useState(tenant.name); const [status, setStatus] = useState(tenant.status); const [ownerUserId, setOwnerUserId] = useState(tenant.owner_user_id || ""); const [saving, setSaving] = useState(false); const [success, setSuccess] = useState(""); const handleSubmit = async (e: FormEvent) => { e.preventDefault(); setSuccess(""); onError(""); setSaving(true); try { const updated = await api.platformTenants.update(tenant.id, { name, status, owner_user_id: ownerUserId.trim() || null, }); onUpdated(updated); setSuccess("تغییرات ذخیره شد."); } catch (err) { onError(err instanceof ApiError ? err.message : "ذخیره ناموفق بود"); } finally { setSaving(false); } }; return (

اطلاعات پایه

{success && {success}} ); } function DomainsCard({ tenantId, domains, onChanged, }: { tenantId: string; domains: TenantDomain[]; onChanged: () => void; }) { const [domain, setDomain] = useState(""); const [type, setType] = useState<"subdomain" | "custom_domain">("custom_domain"); const [saving, setSaving] = useState(false); const [error, setError] = useState(""); const handleAdd = async (e: FormEvent) => { e.preventDefault(); setError(""); if (!domain.trim()) return; setSaving(true); try { await api.domains.create(tenantId, { domain: domain.trim(), domain_type: type }); setDomain(""); onChanged(); } catch (err) { setError(err instanceof ApiError ? err.message : "افزودن دامنه ناموفق بود"); } finally { setSaving(false); } }; return (

دامنه‌ها

{domains.length === 0 ? (

دامنه‌ای ثبت نشده است.

) : (
    {domains.map((d) => (
  • {d.domain} {d.domain_type}
  • ))}
)} {error && {error}}
setType(v as "subdomain" | "custom_domain")} options={[ { value: "custom_domain", label: "دامنه اختصاصی" }, { value: "subdomain", label: "زیردامنه" }, ]} disabled={saving} />
); } function SubscriptionCard({ tenantId, subscription, plans, onChanged, }: { tenantId: string; subscription: CommercialSubscriptionView | null; plans: CommercialPlanOption[]; onChanged: () => void; }) { const [planCode, setPlanCode] = useState(plans[0]?.plan_code || ""); const [status, setStatus] = useState("active"); const [saving, setSaving] = useState(false); const [error, setError] = useState(""); useEffect(() => { if (!planCode && plans[0]) setPlanCode(plans[0].plan_code); }, [plans, planCode]); const handleSubmit = async (e: FormEvent) => { e.preventDefault(); setError(""); if (!planCode) { setError("ابتدا یک پلن commercial انتخاب کنید."); return; } setSaving(true); try { const res = await commercialPost("/api/v1/commercial/subscriptions", { tenant_id: tenantId, plan_code: planCode, status, }); if (!res.ok) { setError(res.message || "ثبت اشتراک commercial ناموفق بود"); return; } onChanged(); } catch (err) { setError(err instanceof Error ? err.message : "ثبت اشتراک ناموفق بود"); } finally { setSaving(false); } }; const planName = subscription?.plan_code ? plans.find((p) => p.plan_code === subscription.plan_code)?.display_name || subscription.plan_code : null; return (

اشتراک (Commercial Runtime)

{subscription?.status ? (
پلن {planName || "—"}
وضعیت
) : (

اشتراک commercial فعالی ثبت نشده است.

)} {error && {error}}

{subscription?.status ? "تغییر/ثبت اشتراک جدید" : "ثبت اشتراک"}

{plans.length === 0 ? (

ابتدا از{" "} Admin Commercial → Plans {" "} یک پلن منتشر کنید.

) : ( <> ({ value: p.plan_code, label: p.display_name }))} /> )}
); } function MembersCard({ tenantId, members, users, onChanged, }: { tenantId: string; members: TenantMember[]; users: AdminUser[]; onChanged: () => void; }) { const [userId, setUserId] = useState(""); const [role, setRole] = useState<"tenant_admin" | "tenant_member">("tenant_member"); const [saving, setSaving] = useState(false); const [error, setError] = useState(""); const userLabel = (id: string) => { const u = users.find((x) => x.id === id); return u ? u.display_name || u.username || u.mobile || u.email : id; }; const handleAdd = async (e: FormEvent) => { e.preventDefault(); setError(""); const token = getStoredToken(); if (!token) { setError("توکن یافت نشد."); return; } if (!userId) { setError("یک کاربر انتخاب کنید."); return; } setSaving(true); try { await identityApi.addMember(token, tenantId, { user_id: userId, role }); setUserId(""); onChanged(); } catch (err) { setError(err instanceof Error ? err.message : "افزودن عضو ناموفق بود"); } finally { setSaving(false); } }; return (

اعضا

{members.length === 0 ? (

عضوی ثبت نشده است.

) : (
    {members.map((m) => (
  • {userLabel(m.user_id)} {m.role === "tenant_admin" ? "مدیر" : "عضو"}
  • ))}
)} {error && {error}}

افزودن عضو

{users.length === 0 ? (

کاربری برای افزودن یافت نشد. ابتدا از بخش «کاربران» کاربر بسازید.

) : ( <> setRole(v as "tenant_admin" | "tenant_member")} options={[ { value: "tenant_member", label: "عضو" }, { value: "tenant_admin", label: "مدیر" }, ]} disabled={saving} /> )}
); }