import { useEffect, useMemo, useState } from "react"; import { Building2, Plus, Search } from "lucide-react"; import { motion } from "framer-motion"; import { appClient } from "@/api/appClient"; import PageHeader from "../components/shared/PageHeader"; import StatusBadge from "../components/shared/StatusBadge"; import EmptyState from "../components/shared/EmptyState"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Label } from "@/components/ui/label"; import { useToast } from "@/components/ui/use-toast"; const defaultForm = { name: "", owner_email: "", plan: "starter", status: "active", currency: "NGN", payment_provider: "paystack", vm_limit: 5, cpu_limit: 16, ram_limit_mb: 16384, disk_limit_gb: 500 }; function slugify(value) { return String(value || "") .trim() .toLowerCase() .replace(/[^a-z0-9\s-]/g, "") .replace(/\s+/g, "-"); } export default function Tenants() { const { toast } = useToast(); const [tenants, setTenants] = useState([]); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [search, setSearch] = useState(""); const [showDialog, setShowDialog] = useState(false); const [editingTenant, setEditingTenant] = useState(null); const [form, setForm] = useState(defaultForm); async function loadData() { try { setLoading(true); const payload = await appClient.entities.Tenant.list("-created_date", 500); setTenants(payload || []); } catch (error) { toast({ title: "Load failed", description: error?.message || "Unable to load tenants.", variant: "destructive" }); } finally { setLoading(false); } } useEffect(() => { void loadData(); }, []); function openCreateDialog() { setEditingTenant(null); setForm(defaultForm); setShowDialog(true); } function openEditDialog(tenant) { setEditingTenant(tenant); setForm({ name: tenant.name || "", owner_email: tenant.owner_email || "", plan: String(tenant.plan || "starter").toLowerCase(), status: String(tenant.status || "active").toLowerCase(), currency: String(tenant.currency || "NGN").toUpperCase(), payment_provider: String(tenant.payment_provider || "paystack").toLowerCase(), vm_limit: Number(tenant.vm_limit || 0), cpu_limit: Number(tenant.cpu_limit || 0), ram_limit_mb: Number(tenant.ram_limit_mb || 0), disk_limit_gb: Number(tenant.disk_limit_gb || 0) }); setShowDialog(true); } async function handleSave() { if (!form.name || !form.owner_email) { return; } try { setSaving(true); const payload = { ...form, slug: editingTenant ? undefined : slugify(form.name), balance: editingTenant ? undefined : 0, member_emails: editingTenant ? undefined : [] }; if (editingTenant) { await appClient.entities.Tenant.update(editingTenant.id, payload); } else { await appClient.entities.Tenant.create(payload); } await loadData(); setShowDialog(false); toast({ title: editingTenant ? "Tenant updated" : "Tenant created", description: form.name }); } catch (error) { toast({ title: "Save failed", description: error?.message || "Unable to save tenant.", variant: "destructive" }); } finally { setSaving(false); } } async function handleDelete(tenant) { try { await appClient.entities.Tenant.delete(tenant.id); await loadData(); toast({ title: "Tenant deleted", description: tenant.name, variant: "destructive" }); } catch (error) { toast({ title: "Delete failed", description: error?.message || "Unable to delete tenant.", variant: "destructive" }); } } const filteredTenants = useMemo(() => { const needle = search.trim().toLowerCase(); if (!needle) return tenants; return tenants.filter((tenant) => { return ( String(tenant.name || "").toLowerCase().includes(needle) || String(tenant.owner_email || "").toLowerCase().includes(needle) || String(tenant.plan || "").toLowerCase().includes(needle) ); }); }, [search, tenants]); if (loading) { return (
); } return (
setSearch(event.target.value)} className="pl-9" />
{filteredTenants.length === 0 ? ( New Tenant } /> ) : (
{filteredTenants.map((tenant, index) => (

{tenant.name}

{tenant.owner_email}

Plan:{" "} {tenant.plan || "-"}
VMs:{" "} {tenant.vm_limit || 0} max
Balance:{" "} {tenant.currency} {(tenant.balance || 0).toLocaleString()}
Payment:{" "} {tenant.payment_provider || "-"}
))}
)} {editingTenant ? "Update Tenant" : "Create Tenant"}
setForm((prev) => ({ ...prev, name: event.target.value }))} className="mt-1" />
setForm((prev) => ({ ...prev, owner_email: event.target.value })) } className="mt-1" />
setForm((prev) => ({ ...prev, vm_limit: Number(event.target.value || 0) })) } className="mt-1" />
setForm((prev) => ({ ...prev, cpu_limit: Number(event.target.value || 0) })) } className="mt-1" />
setForm((prev) => ({ ...prev, ram_limit_mb: Number(event.target.value || 0) })) } className="mt-1" />
setForm((prev) => ({ ...prev, disk_limit_gb: Number(event.target.value || 0) })) } className="mt-1" />
); }