import React, { useCallback, useEffect, useMemo, useState, useRef } from "react";
import {
Box,
Paper,
Typography,
Button,
Stack,
Alert,
FormControl,
InputLabel,
MenuItem,
Select,
CircularProgress,
Tooltip
} from "@mui/material";
import {
ContentCopy as CopyIcon,
DeleteOutline as DeleteIcon,
Refresh as RefreshIcon,
Key as KeyIcon,
} from "@mui/icons-material";
import { AgGridReact } from "ag-grid-react";
import { ModuleRegistry, AllCommunityModule, themeQuartz } from "ag-grid-community";
// IMPORTANT: Do NOT import global AG Grid CSS here to avoid overriding other pages.
// We rely on the project's existing CSS and themeQuartz class name like other MagicUI pages.
ModuleRegistry.registerModules([AllCommunityModule]);
// Match the palette used on other pages (see Site_List / Device_List)
const MAGIC_UI = {
shellBg:
"radial-gradient(120% 120% at 0% 0%, rgba(76, 186, 255, 0.16), transparent 55%), " +
"radial-gradient(120% 120% at 100% 0%, rgba(214, 130, 255, 0.18), transparent 60%), #040711",
panelBg:
"linear-gradient(135deg, rgba(10, 16, 31, 0.98) 0%, rgba(6, 10, 24, 0.94) 60%, rgba(15, 6, 26, 0.96) 100%)",
panelBorder: "rgba(148, 163, 184, 0.35)",
textBright: "#e2e8f0",
textMuted: "#94a3b8",
accentA: "#7dd3fc",
accentB: "#c084fc",
};
// Generate a scoped Quartz theme class (same pattern as other pages)
const gridTheme = themeQuartz.withParams({
accentColor: "#8b5cf6",
backgroundColor: "#070b1a",
browserColorScheme: "dark",
fontFamily: { googleFont: "IBM Plex Sans" },
foregroundColor: "#f4f7ff",
headerFontSize: 13,
});
const themeClassName = gridTheme.themeName || "ag-theme-quartz";
const TTL_PRESETS = [
{ value: 1, label: "1 hour" },
{ value: 3, label: "3 hours" },
{ value: 6, label: "6 hours" },
{ value: 12, label: "12 hours" },
{ value: 24, label: "24 hours" },
];
const determineStatus = (record) => {
if (!record) return "expired";
const maxUses = Number.isFinite(record?.max_uses) ? record.max_uses : 1;
const useCount = Number.isFinite(record?.use_count) ? record.use_count : 0;
if (useCount >= Math.max(1, maxUses || 1)) return "used";
if (!record.expires_at) return "expired";
const expires = new Date(record.expires_at);
if (Number.isNaN(expires.getTime())) return "expired";
return expires.getTime() > Date.now() ? "active" : "expired";
};
const formatDateTime = (value) => {
if (!value) return "—";
const date = new Date(value);
if (Number.isNaN(date.getTime())) return value;
return date.toLocaleString();
};
const maskCode = (code) => {
if (!code) return "—";
const parts = code.split("-");
if (parts.length <= 1) {
const prefix = code.slice(0, 4);
return `${prefix}${"•".repeat(Math.max(0, code.length - prefix.length))}`;
}
return parts
.map((part, idx) => (idx === 0 || idx === parts.length - 1 ? part : "•".repeat(part.length)))
.join("-");
};
export default function EnrollmentCodes() {
const [codes, setCodes] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const [feedback, setFeedback] = useState(null);
const [statusFilter, setStatusFilter] = useState("all");
const [ttlHours, setTtlHours] = useState(6);
const [generating, setGenerating] = useState(false);
const [maxUses, setMaxUses] = useState(2);
const gridRef = useRef(null);
const fetchCodes = useCallback(async () => {
setLoading(true);
setError("");
try {
const query = statusFilter === "all" ? "" : `?status=${encodeURIComponent(statusFilter)}`;
const resp = await fetch(`/api/admin/enrollment-codes${query}`, { credentials: "include" });
if (!resp.ok) {
const body = await resp.json().catch(() => ({}));
throw new Error(body.error || `Request failed (${resp.status})`);
}
const data = await resp.json();
setCodes(Array.isArray(data.codes) ? data.codes : []);
} catch (err) {
setError(err.message || "Unable to load codes");
} finally {
setLoading(false);
}
}, [statusFilter]);
useEffect(() => { fetchCodes(); }, [fetchCodes]);
const handleGenerate = useCallback(async () => {
setGenerating(true);
try {
const resp = await fetch("/api/admin/enrollment-codes", {
method: "POST",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ttl_hours: ttlHours, max_uses: maxUses }),
});
if (!resp.ok) {
const body = await resp.json().catch(() => ({}));
throw new Error(body.error || `Request failed (${resp.status})`);
}
await fetchCodes();
setFeedback({ type: "success", message: "New installer code created" });
} catch (err) {
setFeedback({ type: "error", message: err.message });
} finally {
setGenerating(false);
}
}, [ttlHours, maxUses, fetchCodes]);
const handleCopy = (code) => {
if (!code) return;
try {
if (navigator.clipboard?.writeText) {
navigator.clipboard.writeText(code);
setFeedback({ type: "success", message: "Code copied to clipboard" });
}
} catch (_) {}
};
const handleDelete = async (id) => {
if (!id) return;
if (!window.confirm("Delete this installer code?")) return;
try {
const resp = await fetch(`/api/admin/enrollment-codes/${id}`, {
method: "DELETE",
credentials: "include",
});
if (!resp.ok) {
const body = await resp.json().catch(() => ({}));
throw new Error(body.error || `Request failed (${resp.status})`);
}
await fetchCodes();
setFeedback({ type: "success", message: "Code deleted" });
} catch (err) {
setFeedback({ type: "error", message: err.message });
}
};
const columns = useMemo(() => [
{
headerName: "Status",
field: "status",
cellRenderer: (params) => {
const status = determineStatus(params.data);
const color =
status === "active" ? "#34d399" :
status === "used" ? "#7dd3fc" :
"#fbbf24";
return {status};
},
minWidth: 100
},
{
headerName: "Installer Code",
field: "code",
cellRenderer: (params) => (
{maskCode(params.value)}
),
minWidth: 340
},
{ headerName: "Expires At",
field: "expires_at",
valueFormatter: p => formatDateTime(p.value)
},
{ headerName: "Created By", field: "created_by_user_id" },
{
headerName: "Usage",
valueGetter: (p) => `${p.data.use_count || 0} / ${p.data.max_uses || 1}`,
cellStyle: { fontFamily: "monospace" },
width: 120
},
{ headerName: "Last Used", field: "last_used_at", valueFormatter: p => formatDateTime(p.value) },
{ headerName: "Used By GUID", field: "used_by_guid" },
{
headerName: "Actions",
cellRenderer: (params) => {
const record = params.data;
const disableDelete = (record.use_count || 0) !== 0;
return (
);
},
width: 160
}
], []);
const defaultColDef = useMemo(() => ({
sortable: true,
filter: true,
resizable: true,
flex: 1,
minWidth: 140,
}), []);
return (
{/* Hero header */}
Enrollment Installer Codes
: null}
onClick={handleGenerate}
sx={{ background: "linear-gradient(135deg,#7dd3fc,#c084fc)", borderRadius: 999 }}
>
{generating ? "Generating…" : "Generate Code"}
} onClick={fetchCodes} disabled={loading}>
Refresh
{/* Controls */}
Status
Duration
Allowed Uses
{feedback && (
setFeedback(null)}>
{feedback.message}
)}
{error && (
{error}
)}
{/* Grid wrapper — all overrides are SCOPED to this instance via inline CSS vars */}
);
}