@burdenoff/microfe-store 2026.1028.1 → 2026.1102.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/StoreAdminRoutes.js +22 -12
  2. package/dist/StoreAdminRoutes.js.map +1 -1
  3. package/dist/StoreRoot.js +47 -36
  4. package/dist/StoreRoot.js.map +1 -1
  5. package/dist/StoreRoutes.js +72 -59
  6. package/dist/StoreRoutes.js.map +1 -1
  7. package/dist/components/TemplateRevisionsQueue.d.ts +10 -0
  8. package/dist/components/TemplateRevisionsQueue.js +300 -0
  9. package/dist/components/TemplateRevisionsQueue.js.map +1 -0
  10. package/dist/components/WorkflowTemplatePreviewPanel.d.ts +50 -0
  11. package/dist/components/WorkflowTemplatePreviewPanel.js +221 -0
  12. package/dist/components/WorkflowTemplatePreviewPanel.js.map +1 -0
  13. package/dist/components/governance/ReportListingCard.d.ts +11 -0
  14. package/dist/components/governance/ReportListingCard.js +100 -0
  15. package/dist/components/governance/ReportListingCard.js.map +1 -0
  16. package/dist/pages/AdminDashboardPage.js +48 -27
  17. package/dist/pages/AdminDashboardPage.js.map +1 -1
  18. package/dist/pages/AdminGovernancePage.d.ts +3 -0
  19. package/dist/pages/AdminGovernancePage.js +883 -0
  20. package/dist/pages/AdminGovernancePage.js.map +1 -0
  21. package/dist/pages/AdminSubmissionsQueuePage.js +468 -335
  22. package/dist/pages/AdminSubmissionsQueuePage.js.map +1 -1
  23. package/dist/pages/ProductDetailPage.js +220 -215
  24. package/dist/pages/ProductDetailPage.js.map +1 -1
  25. package/dist/pages/PublisherGovernancePage.d.ts +3 -0
  26. package/dist/pages/PublisherGovernancePage.js +230 -0
  27. package/dist/pages/PublisherGovernancePage.js.map +1 -0
  28. package/dist/utils/governance.d.ts +158 -0
  29. package/dist/utils/governance.js +129 -0
  30. package/dist/utils/governance.js.map +1 -0
  31. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkflowTemplatePreviewPanel.js","names":[],"sources":["../../src/components/WorkflowTemplatePreviewPanel.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport {\n ChevronDown,\n ChevronUp,\n Eraser,\n FileJson,\n KeyRound,\n Network,\n PlugZap,\n TextCursorInput,\n} from 'lucide-react';\n\n/**\n * Reviewer preview of a community workflow template (BOFF-10463): what the\n * template does before a store admin approves it. Mirrors the store service's\n * `WorkflowTemplatePreview` type.\n */\nexport type WorkflowTemplateFieldRef = {\n nodeId?: string | null;\n nodeType?: string | null;\n nodeLabel?: string | null;\n field: string;\n};\n\nexport type WorkflowTemplatePreview = {\n nodeTypes: Array<{\n nodeType: string;\n nodeVersion: string;\n publishType: string;\n displayName: string;\n }>;\n nodeCount: number;\n edgeCount: number;\n requiredInputs: Array<{\n name: string;\n type: string;\n label: string;\n required: boolean;\n usedBy: string[];\n }>;\n requiredCredentials: Array<{\n name: string;\n kind: string;\n nodeId?: string | null;\n nodeType?: string | null;\n nodeLabel?: string | null;\n }>;\n cleaningReport?: {\n connectionsCleared: number;\n secretsRemoved: number;\n pinnedDataRemoved: boolean;\n clearedConnections: WorkflowTemplateFieldRef[];\n removedSecrets: WorkflowTemplateFieldRef[];\n } | null;\n definitionJson?: Record<string, unknown> | null;\n};\n\n/** GraphQL selection for a `WorkflowTemplatePreview` field. */\nexport const WORKFLOW_TEMPLATE_PREVIEW_FIELDS = /* GraphQL */ `\n nodeTypes { nodeType nodeVersion publishType displayName }\n nodeCount\n edgeCount\n requiredInputs { name type label required usedBy }\n requiredCredentials { name kind nodeId nodeType nodeLabel }\n cleaningReport {\n connectionsCleared\n secretsRemoved\n pinnedDataRemoved\n clearedConnections { nodeId nodeType nodeLabel field }\n removedSecrets { nodeId nodeType nodeLabel field }\n }\n definitionJson\n`;\n\nfunction nodeName(ref: { nodeLabel?: string | null; nodeId?: string | null }): string {\n return ref.nodeLabel ?? ref.nodeId ?? 'Workflow';\n}\n\nconst SECTION_TITLE =\n 'mb-2 flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wide text-[var(--color-text-secondary)]';\nconst CHIP =\n 'inline-flex items-center gap-1 rounded-md border border-[var(--color-border-seam)] bg-[var(--color-surface-default)] px-2 py-1 text-xs text-[var(--color-text-primary)]';\n\nconst FieldRefList: FC<{ refs: WorkflowTemplateFieldRef[]; total: number; testId: string }> = ({\n refs,\n total,\n testId,\n}) => (\n <ul className=\"mt-1.5 space-y-1\" data-testid={testId}>\n {/* A field path is unique within its node, so node id + path is a stable key. */}\n {refs.map((ref) => (\n <li\n key={`${ref.nodeId ?? 'root'}:${ref.field}`}\n className=\"text-xs text-[var(--color-text-secondary)]\"\n >\n <span className=\"font-medium text-[var(--color-text-primary)]\">{nodeName(ref)}</span>\n {ref.nodeType && <span className=\"text-[var(--color-text-muted)]\"> ({ref.nodeType})</span>}\n <span className=\"font-mono\"> · {ref.field}</span>\n </li>\n ))}\n {total > refs.length && (\n <li className=\"text-xs text-[var(--color-text-muted)]\">\n and {total - refs.length} more not listed\n </li>\n )}\n </ul>\n);\n\ntype Preview = WorkflowTemplatePreview;\ntype CleaningReport = NonNullable<Preview['cleaningReport']>;\n\nconst MUTED = 'text-xs text-[var(--color-text-muted)]';\nconst ROW = 'text-xs text-[var(--color-text-secondary)]';\n\nfunction plural(count: number, word: string): string {\n return `${count} ${word}${count === 1 ? '' : 's'}`;\n}\n\nconst NodeTypesSection: FC<{ preview: Preview }> = ({ preview }) => (\n <div>\n <h4 className={SECTION_TITLE}>\n <Network className=\"size-3.5\" />\n Nodes ({plural(preview.nodeCount, 'node')}, {plural(preview.edgeCount, 'connection')},{' '}\n {plural(preview.nodeTypes.length, 'node type')})\n </h4>\n {preview.nodeTypes.length === 0 ? (\n <p className={MUTED}>No node types found.</p>\n ) : (\n <div className=\"flex flex-wrap gap-1.5\" data-testid=\"template-node-types\">\n {preview.nodeTypes.map((node) => (\n <span key={node.nodeType} className={CHIP} title={node.nodeType}>\n {node.displayName}\n <span className=\"font-mono text-[var(--color-text-muted)]\">\n {node.nodeType} · v{node.nodeVersion}\n </span>\n {node.publishType === 'STORE' && (\n <span className=\"rounded bg-[var(--color-status-info-bg-subtle)] px-1 text-[10px] text-[var(--color-status-info-text)]\">\n Store\n </span>\n )}\n </span>\n ))}\n </div>\n )}\n </div>\n);\n\nconst RequiredInputsSection: FC<{ inputs: Preview['requiredInputs'] }> = ({ inputs }) => (\n <div>\n <h4 className={SECTION_TITLE}>\n <TextCursorInput className=\"size-3.5\" />\n Required inputs ({inputs.length})\n </h4>\n {inputs.length === 0 ? (\n <p className={MUTED}>The template reads no variables the importer has to fill in.</p>\n ) : (\n <ul className=\"space-y-1\" data-testid=\"template-required-inputs\">\n {inputs.map((input) => (\n <li key={input.name} className={ROW}>\n <span className=\"font-mono text-[var(--color-text-primary)]\">{input.name}</span>\n {input.type === 'secret' && (\n <span className=\"ml-1 rounded bg-[var(--color-status-warning-bg-subtle)] px-1 text-[10px] text-[var(--color-status-warning-text)]\">\n secret\n </span>\n )}\n {input.usedBy.length > 0 && <span> · used by {input.usedBy.join(', ')}</span>}\n </li>\n ))}\n </ul>\n )}\n </div>\n);\n\nconst RequiredCredentialsSection: FC<{ credentials: Preview['requiredCredentials'] }> = ({\n credentials,\n}) => (\n <div>\n <h4 className={SECTION_TITLE}>\n <PlugZap className=\"size-3.5\" />\n Required credentials ({credentials.length})\n </h4>\n {credentials.length === 0 ? (\n <p className={MUTED}>No credentials needed.</p>\n ) : (\n <ul className=\"space-y-1\" data-testid=\"template-required-credentials\">\n {credentials.map((credential) => {\n const where = credential.nodeLabel ?? credential.nodeId;\n const kind =\n credential.kind === 'connection'\n ? 'saved connection (cleared at submission)'\n : 'credential lookup';\n return (\n <li\n key={`${credential.kind}-${credential.name}-${credential.nodeId ?? ''}`}\n className={ROW}\n >\n <span className=\"font-medium text-[var(--color-text-primary)]\">\n {credential.name}\n </span>\n <span> · {kind}</span>\n {where && <span> · {where}</span>}\n </li>\n );\n })}\n </ul>\n )}\n </div>\n);\n\nconst CleaningReportDetails: FC<{ report: CleaningReport }> = ({ report }) => (\n <div className=\"space-y-3 text-xs\" data-testid=\"template-cleaning-report\">\n <div className=\"flex flex-wrap gap-1.5\">\n <span className={CHIP}>\n <PlugZap className=\"size-3\" />\n {plural(report.connectionsCleared, 'connection')} cleared\n </span>\n <span className={CHIP}>\n <KeyRound className=\"size-3\" />\n {plural(report.secretsRemoved, 'secret')} removed\n </span>\n <span className={CHIP}>\n {report.pinnedDataRemoved ? 'Pinned run data removed' : 'No pinned run data'}\n </span>\n </div>\n {report.clearedConnections.length > 0 && (\n <div>\n <p className=\"font-medium text-[var(--color-text-secondary)]\">Connections cleared</p>\n <FieldRefList\n refs={report.clearedConnections}\n total={report.connectionsCleared}\n testId=\"template-cleared-connections\"\n />\n </div>\n )}\n {report.removedSecrets.length > 0 && (\n <div>\n <p className=\"font-medium text-[var(--color-text-secondary)]\">Secrets removed</p>\n <FieldRefList\n refs={report.removedSecrets}\n total={report.secretsRemoved}\n testId=\"template-removed-secrets\"\n />\n </div>\n )}\n </div>\n);\n\nconst CleaningReportSection: FC<{ report: Preview['cleaningReport'] }> = ({ report }) => (\n <div>\n <h4 className={SECTION_TITLE}>\n <Eraser className=\"size-3.5\" />\n Cleaning report\n </h4>\n {report ? (\n <CleaningReportDetails report={report} />\n ) : (\n <p className={MUTED}>\n No cleaning report was recorded for this template (it was stored before reports were kept).\n </p>\n )}\n </div>\n);\n\nconst DefinitionSection: FC<{ definition: Record<string, unknown> }> = ({ definition }) => {\n const [open, setOpen] = useState(false);\n return (\n <div>\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-[var(--color-border-default)] bg-[var(--color-surface-default)] px-3 py-1.5 text-xs font-medium text-[var(--color-text-secondary)] transition-colors hover:bg-[var(--color-surface-hover)]\"\n >\n <FileJson className=\"size-3.5\" />\n {open ? 'Hide definition' : 'View cleaned definition'}\n {open ? <ChevronUp className=\"size-3\" /> : <ChevronDown className=\"size-3\" />}\n </button>\n {open && (\n <pre className=\"mt-2 max-h-72 overflow-auto rounded-lg border border-[var(--color-border-seam)] bg-[var(--color-surface-default)] p-4 text-[11px] leading-relaxed text-[var(--color-text-primary)]\">\n {JSON.stringify(definition, null, 2)}\n </pre>\n )}\n </div>\n );\n};\n\nexport const WorkflowTemplatePreviewPanel: FC<{ preview: WorkflowTemplatePreview }> = ({\n preview,\n}) => (\n <div className=\"space-y-5\" data-testid=\"workflow-template-preview\">\n <NodeTypesSection preview={preview} />\n <div className=\"grid gap-5 sm:grid-cols-2\">\n <RequiredInputsSection inputs={preview.requiredInputs} />\n <RequiredCredentialsSection credentials={preview.requiredCredentials} />\n </div>\n <CleaningReportSection report={preview.cleaningReport} />\n {preview.definitionJson && <DefinitionSection definition={preview.definitionJson} />}\n </div>\n);\n"],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { FC } from 'react';
2
+
3
+ /**
4
+ * Lets a buyer report a listing to the store team (BOFF-10635): abuse,
5
+ * misleading or broken listings, intellectual-property or security concerns.
6
+ * Reporting again replaces the buyer's earlier report for the same listing.
7
+ */
8
+ export declare const ReportListingCard: FC<{
9
+ productId: string;
10
+ productName: string;
11
+ }>;
@@ -0,0 +1,100 @@
1
+ import { useStore as e } from "../../providers/StoreProvider.js";
2
+ import { LISTING_REPORT_REASONS as t, REPORT_LISTING_MUTATION as n, runGovernanceOperation as r } from "../../utils/governance.js";
3
+ import { useTr as i } from "../../shared/hooks/useTr.js";
4
+ import { useState as a } from "react";
5
+ import { Flag as o } from "lucide-react";
6
+ import { jsx as s, jsxs as c } from "react/jsx-runtime";
7
+ import { GlassCard as l } from "@burdenoff/fe-libs/ui";
8
+ //#region src/components/governance/ReportListingCard.tsx
9
+ var u = ({ productId: u, productName: d }) => {
10
+ let { authToken: f } = e(), p = i(), [m, h] = a(!1), [g, _] = a("BROKEN"), [v, y] = a(""), [b, x] = a(!1), [S, C] = a(null), [w, T] = a(!1), E = async () => {
11
+ x(!0), C(null);
12
+ try {
13
+ await r(n, { input: {
14
+ productId: u,
15
+ reason: g,
16
+ details: v.trim() || null
17
+ } }, f, p("governance.report.failed", "Could not send your report")), T(!0), h(!1), y("");
18
+ } catch (e) {
19
+ C(e instanceof Error ? e.message : p("governance.report.failed", "Could not send your report"));
20
+ } finally {
21
+ x(!1);
22
+ }
23
+ };
24
+ return /* @__PURE__ */ c(l, {
25
+ className: "mb-6 p-4",
26
+ children: [
27
+ /* @__PURE__ */ c("h3", {
28
+ className: "flex items-center gap-2 font-medium text-text-primary",
29
+ children: [/* @__PURE__ */ s(o, { className: "size-4" }), p("governance.report.title", "Report this listing")]
30
+ }),
31
+ w && !m && /* @__PURE__ */ s("p", {
32
+ role: "status",
33
+ className: "mt-2 text-sm text-[var(--color-status-success-text)]",
34
+ children: p("governance.report.thanks", "Thanks. The store team reviews every report and acts under the marketplace policy.")
35
+ }),
36
+ m ? /* @__PURE__ */ c("form", {
37
+ className: "mt-3 space-y-2",
38
+ onSubmit: (e) => {
39
+ e.preventDefault(), E();
40
+ },
41
+ children: [
42
+ /* @__PURE__ */ s("label", {
43
+ className: "block text-xs text-text-secondary",
44
+ htmlFor: "report-listing-reason",
45
+ children: p("governance.report.reason", "What is wrong?")
46
+ }),
47
+ /* @__PURE__ */ s("select", {
48
+ id: "report-listing-reason",
49
+ value: g,
50
+ onChange: (e) => _(e.target.value),
51
+ className: "w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-3 py-2 text-sm text-text-primary",
52
+ children: t.map((e) => /* @__PURE__ */ s("option", {
53
+ value: e.value,
54
+ children: e.label
55
+ }, e.value))
56
+ }),
57
+ /* @__PURE__ */ s("textarea", {
58
+ "aria-label": p("governance.report.details", "Details"),
59
+ value: v,
60
+ onChange: (e) => y(e.target.value),
61
+ rows: 3,
62
+ maxLength: 2e3,
63
+ placeholder: p("governance.report.placeholder", "What happened with {{name}}? Include steps or links if you can.", { name: d }),
64
+ className: "w-full resize-none rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-3 py-2 text-sm text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]"
65
+ }),
66
+ S && /* @__PURE__ */ s("p", {
67
+ role: "alert",
68
+ className: "text-xs text-[var(--color-status-error-text)]",
69
+ children: S
70
+ }),
71
+ /* @__PURE__ */ c("div", {
72
+ className: "flex justify-end gap-2",
73
+ children: [/* @__PURE__ */ s("button", {
74
+ type: "button",
75
+ onClick: () => h(!1),
76
+ className: "rounded-md border border-[var(--color-border-default)] px-3 py-1.5 text-xs text-text-secondary hover:bg-[var(--color-surface-hover)]",
77
+ children: p("common.cancel", "Cancel")
78
+ }), /* @__PURE__ */ s("button", {
79
+ type: "submit",
80
+ disabled: b,
81
+ className: "rounded-md bg-[var(--color-action-danger-bg)] px-3 py-1.5 text-xs font-semibold text-[var(--color-action-danger-text)] hover:opacity-90 disabled:opacity-50",
82
+ children: p("governance.report.submit", "Send report")
83
+ })]
84
+ })
85
+ ]
86
+ }) : /* @__PURE__ */ s("button", {
87
+ type: "button",
88
+ onClick: () => {
89
+ h(!0), C(null);
90
+ },
91
+ className: "mt-3 text-sm text-text-link hover:underline",
92
+ children: w ? p("governance.report.update", "Update your report") : p("governance.report.open", "Something wrong with this listing? Tell the store team")
93
+ })
94
+ ]
95
+ });
96
+ };
97
+ //#endregion
98
+ export { u as ReportListingCard };
99
+
100
+ //# sourceMappingURL=ReportListingCard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReportListingCard.js","names":[],"sources":["../../../src/components/governance/ReportListingCard.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { Flag } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { useStore } from '../../providers/StoreProvider';\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n LISTING_REPORT_REASONS,\n REPORT_LISTING_MUTATION,\n runGovernanceOperation,\n type ListingReportReason,\n} from '../../utils/governance';\n\n/**\n * Lets a buyer report a listing to the store team (BOFF-10635): abuse,\n * misleading or broken listings, intellectual-property or security concerns.\n * Reporting again replaces the buyer's earlier report for the same listing.\n */\nexport const ReportListingCard: FC<{ productId: string; productName: string }> = ({\n productId,\n productName,\n}) => {\n const { authToken } = useStore();\n const tr = useTr();\n const [open, setOpen] = useState(false);\n const [reason, setReason] = useState<ListingReportReason>('BROKEN');\n const [details, setDetails] = useState('');\n const [submitting, setSubmitting] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [submitted, setSubmitted] = useState(false);\n\n const submit = async () => {\n setSubmitting(true);\n setError(null);\n try {\n await runGovernanceOperation(\n REPORT_LISTING_MUTATION,\n { input: { productId, reason, details: details.trim() || null } },\n authToken,\n tr('governance.report.failed', 'Could not send your report')\n );\n setSubmitted(true);\n setOpen(false);\n setDetails('');\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('governance.report.failed', 'Could not send your report')\n );\n } finally {\n setSubmitting(false);\n }\n };\n\n return (\n <GlassCard className=\"mb-6 p-4\">\n <h3 className=\"flex items-center gap-2 font-medium text-text-primary\">\n <Flag className=\"size-4\" />\n {tr('governance.report.title', 'Report this listing')}\n </h3>\n {submitted && !open && (\n <p role=\"status\" className=\"mt-2 text-sm text-[var(--color-status-success-text)]\">\n {tr(\n 'governance.report.thanks',\n 'Thanks. The store team reviews every report and acts under the marketplace policy.'\n )}\n </p>\n )}\n {!open ? (\n <button\n type=\"button\"\n onClick={() => {\n setOpen(true);\n setError(null);\n }}\n className=\"mt-3 text-sm text-text-link hover:underline\"\n >\n {submitted\n ? tr('governance.report.update', 'Update your report')\n : tr(\n 'governance.report.open',\n 'Something wrong with this listing? Tell the store team'\n )}\n </button>\n ) : (\n <form\n className=\"mt-3 space-y-2\"\n onSubmit={(e) => {\n e.preventDefault();\n void submit();\n }}\n >\n <label className=\"block text-xs text-text-secondary\" htmlFor=\"report-listing-reason\">\n {tr('governance.report.reason', 'What is wrong?')}\n </label>\n <select\n id=\"report-listing-reason\"\n value={reason}\n onChange={(e) => setReason(e.target.value as ListingReportReason)}\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-3 py-2 text-sm text-text-primary\"\n >\n {LISTING_REPORT_REASONS.map((r) => (\n <option key={r.value} value={r.value}>\n {r.label}\n </option>\n ))}\n </select>\n <textarea\n aria-label={tr('governance.report.details', 'Details')}\n value={details}\n onChange={(e) => setDetails(e.target.value)}\n rows={3}\n maxLength={2000}\n placeholder={tr(\n 'governance.report.placeholder',\n 'What happened with {{name}}? Include steps or links if you can.',\n { name: productName }\n )}\n className=\"w-full resize-none rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-3 py-2 text-sm text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n />\n {error && (\n <p role=\"alert\" className=\"text-xs text-[var(--color-status-error-text)]\">\n {error}\n </p>\n )}\n <div className=\"flex justify-end gap-2\">\n <button\n type=\"button\"\n onClick={() => setOpen(false)}\n className=\"rounded-md border border-[var(--color-border-default)] px-3 py-1.5 text-xs text-text-secondary hover:bg-[var(--color-surface-hover)]\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={submitting}\n className=\"rounded-md bg-[var(--color-action-danger-bg)] px-3 py-1.5 text-xs font-semibold text-[var(--color-action-danger-text)] hover:opacity-90 disabled:opacity-50\"\n >\n {tr('governance.report.submit', 'Send report')}\n </button>\n </div>\n </form>\n )}\n </GlassCard>\n );\n};\n"],"mappings":";;;;;;;;AAkBA,IAAa,KAAqE,EAChF,cACA,qBACI;CACJ,IAAM,EAAE,iBAAc,GAAU,EAC1B,IAAK,GAAO,EACZ,CAAC,GAAM,KAAW,EAAS,GAAM,EACjC,CAAC,GAAQ,KAAa,EAA8B,SAAS,EAC7D,CAAC,GAAS,KAAc,EAAS,GAAG,EACpC,CAAC,GAAY,KAAiB,EAAS,GAAM,EAC7C,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,CAAC,GAAW,KAAgB,EAAS,GAAM,EAE3C,IAAS,YAAY;AAEzB,EADA,EAAc,GAAK,EACnB,EAAS,KAAK;AACd,MAAI;AASF,GARA,MAAM,EACJ,GACA,EAAE,OAAO;IAAE;IAAW;IAAQ,SAAS,EAAQ,MAAM,IAAI;IAAM,EAAE,EACjE,GACA,EAAG,4BAA4B,6BAA6B,CAC7D,EACD,EAAa,GAAK,EAClB,EAAQ,GAAM,EACd,EAAW,GAAG;WACP,GAAK;AACZ,KACE,aAAe,QACX,EAAI,UACJ,EAAG,4BAA4B,6BAA6B,CACjE;YACO;AACR,KAAc,GAAM;;;AAIxB,QACE,kBAAC,GAAD;EAAW,WAAU;YAArB;GACE,kBAAC,MAAD;IAAI,WAAU;cAAd,CACE,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA,EAC1B,EAAG,2BAA2B,sBAAsB,CAClD;;GACJ,KAAa,CAAC,KACb,kBAAC,KAAD;IAAG,MAAK;IAAS,WAAU;cACxB,EACC,4BACA,qFACD;IACC,CAAA;GAEJ,IAiBA,kBAAC,QAAD;IACE,WAAU;IACV,WAAW,MAAM;AAEV,KADL,EAAE,gBAAgB,EACb,GAAQ;;cAJjB;KAOE,kBAAC,SAAD;MAAO,WAAU;MAAoC,SAAQ;gBAC1D,EAAG,4BAA4B,iBAAiB;MAC3C,CAAA;KACR,kBAAC,UAAD;MACE,IAAG;MACH,OAAO;MACP,WAAW,MAAM,EAAU,EAAE,OAAO,MAA6B;MACjE,WAAU;gBAET,EAAuB,KAAK,MAC3B,kBAAC,UAAD;OAAsB,OAAO,EAAE;iBAC5B,EAAE;OACI,EAFI,EAAE,MAEN,CACT;MACK,CAAA;KACT,kBAAC,YAAD;MACE,cAAY,EAAG,6BAA6B,UAAU;MACtD,OAAO;MACP,WAAW,MAAM,EAAW,EAAE,OAAO,MAAM;MAC3C,MAAM;MACN,WAAW;MACX,aAAa,EACX,iCACA,mEACA,EAAE,MAAM,GAAa,CACtB;MACD,WAAU;MACV,CAAA;KACD,KACC,kBAAC,KAAD;MAAG,MAAK;MAAQ,WAAU;gBACvB;MACC,CAAA;KAEN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAQ,GAAM;OAC7B,WAAU;iBAET,EAAG,iBAAiB,SAAS;OACvB,CAAA,EACT,kBAAC,UAAD;OACE,MAAK;OACL,UAAU;OACV,WAAU;iBAET,EAAG,4BAA4B,cAAc;OACvC,CAAA,CACL;;KACD;QAxEP,kBAAC,UAAD;IACE,MAAK;IACL,eAAe;AAEb,KADA,EAAQ,GAAK,EACb,EAAS,KAAK;;IAEhB,WAAU;cAET,IACG,EAAG,4BAA4B,qBAAqB,GACpD,EACE,0BACA,yDACD;IACE,CAAA;GA4DD"}
@@ -1,8 +1,9 @@
1
1
  import { useStore as e } from "../providers/StoreProvider.js";
2
2
  import { formatPriceForModel as t } from "../utils/index.js";
3
- import { useCallback as n, useEffect as r, useState as i } from "react";
4
- import { useNavigate as a } from "react-router";
5
- import { ArrowRight as o, Check as s, CheckCircle as c, ChevronDown as l, ChevronUp as u, Clock as d, Container as f, ExternalLink as p, LayoutDashboard as m, MessageSquareWarning as h, PackageCheck as g, RefreshCw as _, Store as v, TrendingUp as y, Users as b, X as x, XCircle as S } from "lucide-react";
3
+ import { GOVERNANCE_STATS_QUERY as n, runGovernanceOperation as r } from "../utils/governance.js";
4
+ import { useCallback as i, useEffect as a, useState as o } from "react";
5
+ import { useNavigate as s } from "react-router";
6
+ import { ArrowRight as c, Check as l, CheckCircle as u, ChevronDown as d, ChevronUp as f, Clock as p, Container as m, ExternalLink as h, Gavel as ee, LayoutDashboard as te, MessageSquareWarning as g, PackageCheck as _, RefreshCw as v, Store as y, TrendingUp as ne, Users as b, X as x, XCircle as S } from "lucide-react";
6
7
  import { graphqlFetch as C } from "@burdenoff/fe-libs/shared/graphql";
7
8
  import { Fragment as w, jsx as T, jsxs as E } from "react/jsx-runtime";
8
9
  import { GlassCard as D, IllustratedEmptyState as O } from "@burdenoff/fe-libs/ui";
@@ -31,7 +32,7 @@ function F(e) {
31
32
  return r < 24 ? `${r}h ago` : `${Math.floor(r / 24)}d ago`;
32
33
  }
33
34
  var I = () => {
34
- let M = a(), { authToken: I, basePath: L } = e(), [R, z] = i({
35
+ let M = s(), { authToken: I, basePath: L } = e(), [R, z] = o({
35
36
  pendingCount: 0,
36
37
  totalStores: 0,
37
38
  recentPending: [],
@@ -39,7 +40,7 @@ var I = () => {
39
40
  recentRejected: [],
40
41
  loading: !0,
41
42
  error: null
42
- }), [B, V] = i(null), [H, U] = i(null), [W, G] = i(""), [K, q] = i(null), [J, Y] = i(null), X = n(async () => {
43
+ }), [B, V] = o(null), [H, U] = o(null), [W, G] = o(""), [K, q] = o(null), [J, Y] = o(null), X = i(async () => {
43
44
  z((e) => ({
44
45
  ...e,
45
46
  loading: !0,
@@ -69,10 +70,21 @@ var I = () => {
69
70
  }));
70
71
  }
71
72
  }, [I]);
72
- r(() => {
73
+ a(() => {
73
74
  X();
74
75
  }, [X]);
75
- let Z = async (e) => {
76
+ let [Z, Q] = o(null);
77
+ a(() => {
78
+ let e = !1;
79
+ return r(n, void 0, I, "Failed to load governance counters").then((t) => {
80
+ e || Q(t.storeGovernanceStats);
81
+ }).catch(() => {
82
+ e || Q(null);
83
+ }), () => {
84
+ e = !0;
85
+ };
86
+ }, [I, R.loading]);
87
+ let $ = Z ? Z.openListingReports + Z.openReviewReports + Z.pendingAppeals : null, re = async (e) => {
76
88
  V(e), q(null);
77
89
  try {
78
90
  let t = await C({
@@ -88,7 +100,7 @@ var I = () => {
88
100
  } finally {
89
101
  V(null);
90
102
  }
91
- }, Q = async (e) => {
103
+ }, ie = async (e) => {
92
104
  if (W.trim()) {
93
105
  V(e), q(null);
94
106
  try {
@@ -109,20 +121,29 @@ var I = () => {
109
121
  V(null);
110
122
  }
111
123
  }
112
- }, $ = [
124
+ }, ae = [
113
125
  {
114
126
  label: "Pending review",
115
127
  value: R.pendingCount,
116
- icon: g,
128
+ icon: _,
117
129
  href: `${L}/submissions`,
118
130
  accent: R.pendingCount > 0 ? "border-[var(--color-status-warning-text)]/30 bg-[var(--color-status-warning-bg-subtle)]/20" : "",
119
131
  valueClass: R.pendingCount > 0 ? "text-[var(--color-status-warning-text)]" : "text-[var(--color-text-primary)]",
120
- cta: "Review queue"
132
+ cta: Z && Z.overdueSubmissions > 0 ? `Review queue · ${Z.overdueSubmissions} overdue` : "Review queue"
133
+ },
134
+ {
135
+ label: "Governance",
136
+ value: $ ?? "—",
137
+ icon: ee,
138
+ href: `${L}/governance`,
139
+ accent: $ && $ > 0 ? "border-[var(--color-status-warning-text)]/30 bg-[var(--color-status-warning-bg-subtle)]/20" : "",
140
+ valueClass: $ && $ > 0 ? "text-[var(--color-status-warning-text)]" : "text-[var(--color-text-primary)]",
141
+ cta: "Reports, takedowns & appeals"
121
142
  },
122
143
  {
123
144
  label: "Stores in registry",
124
145
  value: R.totalStores,
125
- icon: v,
146
+ icon: y,
126
147
  href: `${L}/stores`,
127
148
  accent: "",
128
149
  valueClass: "text-[var(--color-text-primary)]",
@@ -131,7 +152,7 @@ var I = () => {
131
152
  {
132
153
  label: "Review moderation",
133
154
  value: "—",
134
- icon: h,
155
+ icon: g,
135
156
  href: `${L}/reviews`,
136
157
  accent: "",
137
158
  valueClass: "text-[var(--color-text-primary)]",
@@ -149,7 +170,7 @@ var I = () => {
149
170
  {
150
171
  label: "Sandbox build checks",
151
172
  value: "—",
152
- icon: f,
173
+ icon: m,
153
174
  href: `${L}/sandbox-builds`,
154
175
  accent: "",
155
176
  valueClass: "text-[var(--color-text-primary)]",
@@ -166,7 +187,7 @@ var I = () => {
166
187
  className: "flex items-center justify-between",
167
188
  children: [/* @__PURE__ */ E("div", {
168
189
  className: "flex items-center gap-2.5",
169
- children: [/* @__PURE__ */ T(m, { className: "size-5 text-[var(--color-text-primary)]" }), /* @__PURE__ */ E("div", { children: [/* @__PURE__ */ T("h1", {
190
+ children: [/* @__PURE__ */ T(te, { className: "size-5 text-[var(--color-text-primary)]" }), /* @__PURE__ */ E("div", { children: [/* @__PURE__ */ T("h1", {
170
191
  className: "text-lg font-semibold text-[var(--color-text-primary)]",
171
192
  children: "Store Admin Dashboard"
172
193
  }), /* @__PURE__ */ T("p", {
@@ -178,7 +199,7 @@ var I = () => {
178
199
  onClick: () => void X(),
179
200
  disabled: R.loading,
180
201
  className: "inline-flex items-center gap-2 rounded-md border border-[var(--color-border-default)] bg-[var(--color-surface-default)] px-3 py-1.5 text-sm text-[var(--color-text-primary)] hover:bg-[var(--color-surface-hover)] transition-colors disabled:opacity-50",
181
- children: [/* @__PURE__ */ T(_, { className: `size-4 ${R.loading ? "animate-spin" : ""}` }), "Refresh"]
202
+ children: [/* @__PURE__ */ T(v, { className: `size-4 ${R.loading ? "animate-spin" : ""}` }), "Refresh"]
182
203
  })]
183
204
  })
184
205
  })
@@ -192,7 +213,7 @@ var I = () => {
192
213
  }),
193
214
  /* @__PURE__ */ T("section", { children: /* @__PURE__ */ T("div", {
194
215
  className: "grid grid-cols-2 gap-4 lg:grid-cols-4",
195
- children: $.map((e) => {
216
+ children: ae.map((e) => {
196
217
  let t = e.icon;
197
218
  return /* @__PURE__ */ E("button", {
198
219
  type: "button",
@@ -212,7 +233,7 @@ var I = () => {
212
233
  })]
213
234
  }), /* @__PURE__ */ E("div", {
214
235
  className: "mt-3 flex items-center gap-1 text-xs font-medium text-[var(--color-text-accent)] group-hover:underline",
215
- children: [e.cta, /* @__PURE__ */ T(o, { className: "size-3" })]
236
+ children: [e.cta, /* @__PURE__ */ T(c, { className: "size-3" })]
216
237
  })]
217
238
  }, e.label);
218
239
  })
@@ -233,7 +254,7 @@ var I = () => {
233
254
  type: "button",
234
255
  onClick: () => M(`${L}/submissions`),
235
256
  className: "inline-flex items-center gap-1.5 rounded-md border border-[var(--color-border-default)] bg-[var(--color-surface-default)] px-3 py-1.5 text-xs font-medium text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] hover:text-[var(--color-text-primary)] transition-colors",
236
- children: ["View all", /* @__PURE__ */ T(p, { className: "size-3.5" })]
257
+ children: ["View all", /* @__PURE__ */ T(h, { className: "size-3.5" })]
237
258
  })]
238
259
  }),
239
260
  K && /* @__PURE__ */ T("div", {
@@ -289,7 +310,7 @@ var I = () => {
289
310
  /* @__PURE__ */ T("span", { children: t(e.price, e.currency, e.pricingModel) }),
290
311
  e.submittedAt && /* @__PURE__ */ E(w, { children: [/* @__PURE__ */ T("span", { children: "·" }), /* @__PURE__ */ E("span", {
291
312
  className: "flex items-center gap-1",
292
- children: [/* @__PURE__ */ T(d, { className: "size-3" }), F(e.submittedAt)]
313
+ children: [/* @__PURE__ */ T(p, { className: "size-3" }), F(e.submittedAt)]
293
314
  })] })
294
315
  ]
295
316
  })]
@@ -301,14 +322,14 @@ var I = () => {
301
322
  type: "button",
302
323
  onClick: () => Y(J === e.id ? null : e.id),
303
324
  className: "inline-flex items-center gap-1 rounded-md border border-[var(--color-border-default)] px-2.5 py-1.5 text-xs text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] transition-colors",
304
- children: [J === e.id ? /* @__PURE__ */ T(u, { className: "size-3.5" }) : /* @__PURE__ */ T(l, { className: "size-3.5" }), "Details"]
325
+ children: [J === e.id ? /* @__PURE__ */ T(f, { className: "size-3.5" }) : /* @__PURE__ */ T(d, { className: "size-3.5" }), "Details"]
305
326
  }),
306
327
  /* @__PURE__ */ E("button", {
307
328
  type: "button",
308
- onClick: () => void Z(e.id),
329
+ onClick: () => void re(e.id),
309
330
  disabled: B === e.id,
310
331
  className: "inline-flex items-center gap-1 rounded-md bg-[var(--color-surface-success)] px-3 py-1.5 text-xs font-semibold text-[var(--color-text-on-success)] hover:opacity-90 disabled:opacity-50 transition-opacity",
311
- children: [/* @__PURE__ */ T(s, { className: "size-3.5" }), "Approve"]
332
+ children: [/* @__PURE__ */ T(l, { className: "size-3.5" }), "Approve"]
312
333
  }),
313
334
  /* @__PURE__ */ E("button", {
314
335
  type: "button",
@@ -394,7 +415,7 @@ var I = () => {
394
415
  children: [
395
416
  e.productId.slice(0, 16),
396
417
  "…",
397
- /* @__PURE__ */ T(p, { className: "size-3" })
418
+ /* @__PURE__ */ T(h, { className: "size-3" })
398
419
  ]
399
420
  })
400
421
  })] }),
@@ -438,7 +459,7 @@ var I = () => {
438
459
  children: "Cancel"
439
460
  }), /* @__PURE__ */ T("button", {
440
461
  type: "button",
441
- onClick: () => void Q(e.id),
462
+ onClick: () => void ie(e.id),
442
463
  disabled: !W.trim() || B === e.id,
443
464
  className: "rounded-md bg-[var(--color-surface-danger)] px-3 py-1 text-xs font-semibold text-[var(--color-text-on-danger)] hover:opacity-90 disabled:opacity-50 transition-opacity",
444
465
  children: "Submit rejection"
@@ -453,7 +474,7 @@ var I = () => {
453
474
  ] }),
454
475
  /* @__PURE__ */ E("section", { children: [/* @__PURE__ */ E("div", {
455
476
  className: "mb-4 flex items-center gap-2",
456
- children: [/* @__PURE__ */ T(y, { className: "size-4 text-[var(--color-text-secondary)]" }), /* @__PURE__ */ T("h2", {
477
+ children: [/* @__PURE__ */ T(ne, { className: "size-4 text-[var(--color-text-secondary)]" }), /* @__PURE__ */ T("h2", {
457
478
  className: "text-base font-semibold text-[var(--color-text-primary)]",
458
479
  children: "Recent Activity"
459
480
  })]
@@ -466,7 +487,7 @@ var I = () => {
466
487
  children: [
467
488
  /* @__PURE__ */ T("div", {
468
489
  className: "flex size-7 items-center justify-center rounded-md bg-[var(--color-status-success-bg-subtle)]",
469
- children: /* @__PURE__ */ T(c, { className: "size-4 text-[var(--color-status-success-text)]" })
490
+ children: /* @__PURE__ */ T(u, { className: "size-4 text-[var(--color-status-success-text)]" })
470
491
  }),
471
492
  /* @__PURE__ */ T("h3", {
472
493
  className: "text-sm font-semibold text-[var(--color-text-primary)]",
@@ -1 +1 @@
1
- {"version":3,"file":"AdminDashboardPage.js","names":[],"sources":["../../src/pages/AdminDashboardPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useCallback, useEffect, useState } from 'react';\nimport {\n LayoutDashboard,\n PackageCheck,\n Store as StoreIcon,\n MessageSquareWarning,\n Users,\n Check,\n X,\n ExternalLink,\n CheckCircle,\n XCircle,\n RefreshCw,\n ChevronDown,\n ChevronUp,\n ArrowRight,\n Clock,\n TrendingUp,\n Container,\n} from 'lucide-react';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport { GlassCard, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport { useNavigate } from 'react-router';\nimport { useStore } from '../providers/StoreProvider';\nimport { formatPriceForModel } from '../utils';\n\ntype RecentProduct = {\n id: string;\n slug: string;\n name: string;\n type: string;\n nature?: string | null;\n category?: string | null;\n icon?: string | null;\n price: number;\n currency: string;\n pricingModel: string;\n description?: string | null;\n submittedAt?: string | null;\n submittedBy?: string | null;\n productId?: string | null;\n publisher?: { name: string; email: string } | null;\n};\n\ntype ActivityProduct = {\n id: string;\n slug: string;\n name: string;\n icon?: string | null;\n type?: string;\n status: string;\n publishedAt?: string | null;\n reviewedAt?: string | null;\n reviewedBy?: string | null;\n rejectedReason?: string | null;\n};\n\ntype DashboardState = {\n pendingCount: number;\n totalStores: number;\n recentPending: RecentProduct[];\n recentPublished: ActivityProduct[];\n recentRejected: ActivityProduct[];\n loading: boolean;\n error: string | null;\n};\n\nconst DASHBOARD_QUERY = /* GraphQL */ `\n query AdminDashboardData {\n recentPending: pendingReviewProducts(limit: 5) {\n totalCount\n products {\n id\n slug\n name\n type\n nature\n category\n icon\n price\n currency\n pricingModel\n description\n submittedAt\n submittedBy\n productId\n publisher {\n name\n email\n }\n }\n }\n stores {\n id\n }\n recentPublished: products(filter: { status: PUBLISHED }, limit: 5) {\n id\n slug\n name\n icon\n type\n status\n publishedAt\n reviewedBy\n }\n recentRejected: products(filter: { status: REJECTED }, limit: 5) {\n id\n slug\n name\n icon\n type\n status\n reviewedAt\n rejectedReason\n }\n }\n`;\n\nconst APPROVE_MUTATION = /* GraphQL */ `\n mutation ApproveProduct($id: ID!) {\n approveProduct(id: $id) {\n id\n status\n }\n }\n`;\n\nconst REJECT_MUTATION = /* GraphQL */ `\n mutation RejectProduct($id: ID!, $reason: String!) {\n rejectProduct(id: $id, reason: $reason) {\n id\n status\n }\n }\n`;\n\nconst DELIST_MUTATION = /* GraphQL */ `\n mutation DelistProduct($id: ID!, $reason: String) {\n delistProduct(id: $id, reason: $reason) {\n id\n status\n }\n }\n`;\n\nconst TYPE_COLORS: Record<string, string> = {\n NODE: 'bg-[var(--color-status-info-bg-subtle)] text-[var(--color-status-info-text)]',\n WORKFLOW: 'bg-[var(--color-status-warning-bg-subtle)] text-[var(--color-status-warning-text)]',\n INTEGRATION: 'bg-[var(--color-status-success-bg-subtle)] text-[var(--color-status-success-text)]',\n VIBEMODULE: 'bg-[var(--color-surface-accent-muted)] text-[var(--color-text-accent)]',\n};\n\nfunction typeColor(type?: string | null) {\n if (!type) return 'bg-[var(--color-surface-muted)] text-[var(--color-text-secondary)]';\n return TYPE_COLORS[type] ?? 'bg-[var(--color-surface-muted)] text-[var(--color-text-secondary)]';\n}\n\nfunction formatDate(iso?: string | null) {\n if (!iso) return '—';\n return new Date(iso).toLocaleDateString(undefined, {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n });\n}\n\nfunction timeAgo(iso?: string | null) {\n if (!iso) return '—';\n const diff = Date.now() - new Date(iso).getTime();\n const mins = Math.floor(diff / 60000);\n if (mins < 60) return `${mins}m ago`;\n const hrs = Math.floor(mins / 60);\n if (hrs < 24) return `${hrs}h ago`;\n const days = Math.floor(hrs / 24);\n return `${days}d ago`;\n}\n\nexport const AdminDashboardPage: FC = () => {\n const navigate = useNavigate();\n const { authToken, basePath } = useStore();\n\n const [state, setState] = useState<DashboardState>({\n pendingCount: 0,\n totalStores: 0,\n recentPending: [],\n recentPublished: [],\n recentRejected: [],\n loading: true,\n error: null,\n });\n const [actingId, setActingId] = useState<string | null>(null);\n const [rejectingId, setRejectingId] = useState<string | null>(null);\n const [rejectReason, setRejectReason] = useState('');\n const [actionError, setActionError] = useState<string | null>(null);\n const [viewingId, setViewingId] = useState<string | null>(null);\n\n const load = useCallback(async () => {\n setState((prev) => ({ ...prev, loading: true, error: null }));\n try {\n const result = await graphqlFetch<{\n recentPending: { totalCount: number; products: RecentProduct[] };\n stores: Array<{ id: string }>;\n recentPublished: ActivityProduct[];\n recentRejected: ActivityProduct[];\n }>({\n gateway: 'global',\n query: DASHBOARD_QUERY,\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Failed to load dashboard');\n }\n setState({\n pendingCount: result.data?.recentPending.totalCount ?? 0,\n totalStores: result.data?.stores.length ?? 0,\n recentPending: result.data?.recentPending.products ?? [],\n recentPublished: result.data?.recentPublished ?? [],\n recentRejected: result.data?.recentRejected ?? [],\n loading: false,\n error: null,\n });\n } catch (err) {\n setState((prev) => ({\n ...prev,\n loading: false,\n error: err instanceof Error ? err.message : 'Failed to load dashboard',\n }));\n }\n }, [authToken]);\n\n useEffect(() => {\n void load();\n }, [load]);\n\n const approve = async (id: string) => {\n setActingId(id);\n setActionError(null);\n try {\n const result = await graphqlFetch({\n gateway: 'global',\n query: APPROVE_MUTATION,\n variables: { id },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Approval failed');\n }\n await load();\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Approval failed');\n } finally {\n setActingId(null);\n }\n };\n\n const reject = async (id: string) => {\n if (!rejectReason.trim()) return;\n setActingId(id);\n setActionError(null);\n try {\n const result = await graphqlFetch({\n gateway: 'global',\n query: REJECT_MUTATION,\n variables: { id, reason: rejectReason.trim() },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Rejection failed');\n }\n setRejectingId(null);\n setRejectReason('');\n await load();\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Rejection failed');\n } finally {\n setActingId(null);\n }\n };\n\n const delist = async (id: string) => {\n setActingId(id);\n setActionError(null);\n try {\n const result = await graphqlFetch({\n gateway: 'global',\n query: DELIST_MUTATION,\n variables: { id, reason: rejectReason.trim() || undefined },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Delist failed');\n }\n setRejectingId(null);\n setRejectReason('');\n await load();\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Delist failed');\n } finally {\n setActingId(null);\n }\n };\n\n const kpiTiles = [\n {\n label: 'Pending review',\n value: state.pendingCount,\n icon: PackageCheck,\n href: `${basePath}/submissions`,\n accent:\n state.pendingCount > 0\n ? 'border-[var(--color-status-warning-text)]/30 bg-[var(--color-status-warning-bg-subtle)]/20'\n : '',\n valueClass:\n state.pendingCount > 0\n ? 'text-[var(--color-status-warning-text)]'\n : 'text-[var(--color-text-primary)]',\n cta: 'Review queue',\n },\n {\n label: 'Stores in registry',\n value: state.totalStores,\n icon: StoreIcon,\n href: `${basePath}/stores`,\n accent: '',\n valueClass: 'text-[var(--color-text-primary)]',\n cta: 'Manage stores',\n },\n {\n label: 'Review moderation',\n value: '—',\n icon: MessageSquareWarning,\n href: `${basePath}/reviews`,\n accent: '',\n valueClass: 'text-[var(--color-text-primary)]',\n cta: 'Moderate reviews',\n },\n {\n label: 'Publishers',\n value: '—',\n icon: Users,\n href: `${basePath}/dashboard`,\n accent: '',\n valueClass: 'text-[var(--color-text-primary)]',\n cta: 'View publishers',\n },\n {\n label: 'Sandbox build checks',\n value: '—',\n icon: Container,\n href: `${basePath}/sandbox-builds`,\n accent: '',\n valueClass: 'text-[var(--color-text-primary)]',\n cta: 'View build log',\n },\n ];\n\n return (\n <div className=\"min-h-screen bg-[var(--color-surface-default)]\">\n {/* Header */}\n <header className=\"sticky top-0 z-10 border-b border-[var(--color-border-seam)] bg-[var(--color-surface-raised)] shadow-[var(--shadow-elevation-1)]\">\n <div className=\"mx-auto max-w-6xl px-6 py-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2.5\">\n <LayoutDashboard className=\"size-5 text-[var(--color-text-primary)]\" />\n <div>\n <h1 className=\"text-lg font-semibold text-[var(--color-text-primary)]\">\n Store Admin Dashboard\n </h1>\n <p className=\"text-xs text-[var(--color-text-secondary)]\">\n Marketplace control room — submissions, registry, moderation\n </p>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={() => void load()}\n disabled={state.loading}\n className=\"inline-flex items-center gap-2 rounded-md border border-[var(--color-border-default)] bg-[var(--color-surface-default)] px-3 py-1.5 text-sm text-[var(--color-text-primary)] hover:bg-[var(--color-surface-hover)] transition-colors disabled:opacity-50\"\n >\n <RefreshCw className={`size-4 ${state.loading ? 'animate-spin' : ''}`} />\n Refresh\n </button>\n </div>\n </div>\n </header>\n\n <main className=\"mx-auto max-w-6xl space-y-8 p-6\">\n {state.error && (\n <div\n role=\"alert\"\n className=\"rounded-lg border border-[var(--color-border-danger)] bg-[var(--color-surface-danger-muted)] px-4 py-3 text-sm text-[var(--color-text-danger)]\"\n >\n {state.error}\n </div>\n )}\n\n {/* KPI tiles */}\n <section>\n <div className=\"grid grid-cols-2 gap-4 lg:grid-cols-4\">\n {kpiTiles.map((tile) => {\n const Icon = tile.icon;\n return (\n <button\n key={tile.label}\n type=\"button\"\n onClick={() => navigate(tile.href)}\n className={`group relative overflow-hidden rounded-xl border border-[var(--color-border-seam)] bg-[var(--color-surface-raised)] p-5 text-left shadow-[var(--shadow-elevation-1)] transition-all hover:border-[var(--color-border-strong)] hover:shadow-[var(--shadow-elevation-2)] ${tile.accent}`}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div>\n <p className=\"text-xs font-medium text-[var(--color-text-secondary)] uppercase tracking-wide\">\n {tile.label}\n </p>\n <p className={`mt-2 text-3xl font-bold tabular-nums ${tile.valueClass}`}>\n {state.loading ? (\n <span className=\"inline-block w-8 animate-pulse rounded bg-[var(--color-surface-muted)] h-8\" />\n ) : (\n tile.value\n )}\n </p>\n </div>\n <div className=\"rounded-lg bg-[var(--color-surface-muted)] p-2\">\n <Icon className=\"size-5 text-[var(--color-text-secondary)]\" />\n </div>\n </div>\n <div className=\"mt-3 flex items-center gap-1 text-xs font-medium text-[var(--color-text-accent)] group-hover:underline\">\n {tile.cta}\n <ArrowRight className=\"size-3\" />\n </div>\n </button>\n );\n })}\n </div>\n </section>\n\n {/* Pending submissions */}\n <section>\n <div className=\"mb-4 flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <h2 className=\"text-base font-semibold text-[var(--color-text-primary)]\">\n Pending Submissions\n </h2>\n {state.pendingCount > 0 && (\n <span className=\"rounded-full bg-[var(--color-status-warning-bg-subtle)] px-2 py-0.5 text-xs font-semibold text-[var(--color-status-warning-text)]\">\n {state.pendingCount} waiting\n </span>\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/submissions`)}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-[var(--color-border-default)] bg-[var(--color-surface-default)] px-3 py-1.5 text-xs font-medium text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] hover:text-[var(--color-text-primary)] transition-colors\"\n >\n View all\n <ExternalLink className=\"size-3.5\" />\n </button>\n </div>\n\n {actionError && (\n <div\n role=\"alert\"\n className=\"mb-3 rounded-lg border border-[var(--color-border-danger)] bg-[var(--color-surface-danger-muted)] px-4 py-2.5 text-sm text-[var(--color-text-danger)]\"\n >\n {actionError}\n </div>\n )}\n\n {state.loading ? (\n <div className=\"space-y-3\">\n {[1, 2, 3].map((i) => (\n <div\n key={i}\n className=\"h-20 animate-pulse rounded-xl bg-[var(--color-surface-muted)]\"\n />\n ))}\n </div>\n ) : state.recentPending.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title=\"All caught up\"\n description=\"No submissions awaiting review. New products will appear here as publishers send them in.\"\n />\n ) : (\n <div className=\"overflow-hidden rounded-xl border border-[var(--color-border-seam)] bg-[var(--color-surface-raised)] shadow-[var(--shadow-elevation-1)]\">\n <ul className=\"divide-y divide-[var(--color-border-seam)]\">\n {state.recentPending.map((p) => (\n <li key={p.id} className=\"overflow-hidden\">\n <div className=\"flex items-center gap-4 px-5 py-4\">\n {p.icon ? (\n <img\n src={p.icon}\n alt=\"\"\n className=\"size-10 shrink-0 rounded-lg object-cover\"\n />\n ) : (\n <div className=\"flex size-10 shrink-0 items-center justify-center rounded-lg bg-[var(--color-surface-muted)] text-xs font-bold text-[var(--color-text-muted)] uppercase\">\n {p.type.slice(0, 2)}\n </div>\n )}\n\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <p className=\"font-medium text-[var(--color-text-primary)] truncate\">\n {p.name}\n </p>\n <span\n className={`shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide ${typeColor(p.type)}`}\n >\n {p.type}\n </span>\n </div>\n <div className=\"mt-0.5 flex items-center gap-3 flex-wrap text-xs text-[var(--color-text-secondary)]\">\n <span>{p.publisher?.name ?? 'Unknown publisher'}</span>\n <span>·</span>\n <span>{formatPriceForModel(p.price, p.currency, p.pricingModel)}</span>\n {p.submittedAt && (\n <>\n <span>·</span>\n <span className=\"flex items-center gap-1\">\n <Clock className=\"size-3\" />\n {timeAgo(p.submittedAt)}\n </span>\n </>\n )}\n </div>\n </div>\n\n <div className=\"shrink-0 flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setViewingId(viewingId === p.id ? null : p.id)}\n className=\"inline-flex items-center gap-1 rounded-md border border-[var(--color-border-default)] px-2.5 py-1.5 text-xs text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] transition-colors\"\n >\n {viewingId === p.id ? (\n <ChevronUp className=\"size-3.5\" />\n ) : (\n <ChevronDown className=\"size-3.5\" />\n )}\n Details\n </button>\n <button\n type=\"button\"\n onClick={() => void approve(p.id)}\n disabled={actingId === p.id}\n className=\"inline-flex items-center gap-1 rounded-md bg-[var(--color-surface-success)] px-3 py-1.5 text-xs font-semibold text-[var(--color-text-on-success)] hover:opacity-90 disabled:opacity-50 transition-opacity\"\n >\n <Check className=\"size-3.5\" />\n Approve\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setRejectingId(p.id);\n setRejectReason('');\n setActionError(null);\n }}\n disabled={actingId === p.id}\n className=\"inline-flex items-center gap-1 rounded-md border border-[var(--color-border-danger)] bg-[var(--color-surface-danger-muted)] px-3 py-1.5 text-xs font-semibold text-[var(--color-text-danger)] hover:bg-[var(--color-surface-danger)] hover:text-[var(--color-text-on-danger)] disabled:opacity-50 transition-colors\"\n >\n <X className=\"size-3.5\" />\n Reject\n </button>\n </div>\n </div>\n\n {viewingId === p.id && (\n <div className=\"border-t border-[var(--color-border-seam)] bg-[var(--color-surface-muted)] px-5 py-4\">\n <dl className=\"grid grid-cols-2 gap-x-8 gap-y-3 text-xs sm:grid-cols-3\">\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">Slug</dt>\n <dd className=\"mt-0.5 font-mono text-[var(--color-text-primary)]\">\n {p.slug}\n </dd>\n </div>\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">Type</dt>\n <dd className=\"mt-0.5 text-[var(--color-text-primary)]\">\n {p.type} · {p.nature ?? '—'}\n </dd>\n </div>\n {p.category && (\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">\n Category\n </dt>\n <dd className=\"mt-0.5 text-[var(--color-text-primary)]\">\n {p.category}\n </dd>\n </div>\n )}\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">\n Pricing\n </dt>\n <dd className=\"mt-0.5 text-[var(--color-text-primary)]\">\n {formatPriceForModel(p.price, p.currency, p.pricingModel)}{' '}\n <span className=\"text-[var(--color-text-muted)]\">\n ({p.pricingModel})\n </span>\n </dd>\n </div>\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">\n Publisher\n </dt>\n <dd className=\"mt-0.5 text-[var(--color-text-primary)]\">\n {p.publisher?.name ?? '—'}\n {p.publisher?.email && (\n <span className=\"block text-[var(--color-text-muted)]\">\n {p.publisher.email}\n </span>\n )}\n </dd>\n </div>\n {p.productId && (\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">\n Devportal app\n </dt>\n <dd className=\"mt-0.5\">\n <a\n href={`/devportal/apps/${p.productId}`}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"inline-flex items-center gap-1 font-mono text-[var(--color-text-accent)] hover:underline\"\n >\n {p.productId.slice(0, 16)}…\n <ExternalLink className=\"size-3\" />\n </a>\n </dd>\n </div>\n )}\n {p.description && (\n <div className=\"col-span-2 sm:col-span-3\">\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">\n Description\n </dt>\n <dd className=\"mt-0.5 text-[var(--color-text-primary)]\">\n {p.description}\n </dd>\n </div>\n )}\n </dl>\n </div>\n )}\n\n {rejectingId === p.id && (\n <div className=\"border-t border-[var(--color-border-seam)] bg-[var(--color-surface-danger-muted)]/30 px-5 py-4\">\n <p className=\"mb-2 text-xs font-semibold text-[var(--color-text-danger)]\">\n Provide a rejection reason (the publisher will see this)\n </p>\n <textarea\n id={`reject-reason-${p.id}`}\n aria-label=\"Rejection reason\"\n value={rejectReason}\n onChange={(e) => setRejectReason(e.target.value)}\n rows={2}\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-surface-default)] px-3 py-2 text-sm text-[var(--color-text-primary)] focus:border-[var(--color-border-accent)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] resize-none\"\n placeholder=\"Explain why this submission is being rejected…\"\n />\n <div className=\"mt-2 flex justify-end gap-2\">\n <button\n type=\"button\"\n onClick={() => {\n setRejectingId(null);\n setRejectReason('');\n }}\n className=\"rounded-md border border-[var(--color-border-default)] bg-[var(--color-surface-default)] px-3 py-1 text-xs font-medium text-[var(--color-text-primary)] hover:bg-[var(--color-surface-hover)] transition-colors\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => void reject(p.id)}\n disabled={!rejectReason.trim() || actingId === p.id}\n className=\"rounded-md bg-[var(--color-surface-danger)] px-3 py-1 text-xs font-semibold text-[var(--color-text-on-danger)] hover:opacity-90 disabled:opacity-50 transition-opacity\"\n >\n Submit rejection\n </button>\n </div>\n </div>\n )}\n </li>\n ))}\n </ul>\n </div>\n )}\n </section>\n\n {/* Recent Activity */}\n <section>\n <div className=\"mb-4 flex items-center gap-2\">\n <TrendingUp className=\"size-4 text-[var(--color-text-secondary)]\" />\n <h2 className=\"text-base font-semibold text-[var(--color-text-primary)]\">\n Recent Activity\n </h2>\n </div>\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n {/* Recently Approved */}\n <GlassCard className=\"overflow-hidden p-0\">\n <div className=\"flex items-center gap-2.5 border-b border-[var(--color-border-seam)] px-4 py-3\">\n <div className=\"flex size-7 items-center justify-center rounded-md bg-[var(--color-status-success-bg-subtle)]\">\n <CheckCircle className=\"size-4 text-[var(--color-status-success-text)]\" />\n </div>\n <h3 className=\"text-sm font-semibold text-[var(--color-text-primary)]\">\n Recently Approved\n </h3>\n <span className=\"ml-auto text-xs text-[var(--color-text-muted)]\">Last 5</span>\n </div>\n {state.loading ? (\n <div className=\"space-y-3 p-4\">\n {[1, 2, 3].map((i) => (\n <div\n key={i}\n className=\"h-10 animate-pulse rounded-lg bg-[var(--color-surface-muted)]\"\n />\n ))}\n </div>\n ) : state.recentPublished.length === 0 ? (\n <p className=\"px-4 py-8 text-center text-sm text-[var(--color-text-muted)]\">\n No approved products yet.\n </p>\n ) : (\n <ul className=\"divide-y divide-[var(--color-border-seam)]\">\n {state.recentPublished.map((p) => (\n <li key={p.id} className=\"flex items-center gap-3 px-4 py-3\">\n {p.icon ? (\n <img\n src={p.icon}\n alt=\"\"\n className=\"size-8 shrink-0 rounded-lg object-cover\"\n />\n ) : (\n <div className=\"flex size-8 shrink-0 items-center justify-center rounded-lg bg-[var(--color-surface-muted)] text-xs font-bold text-[var(--color-text-muted)] uppercase\">\n {(p.type ?? '?').slice(0, 2)}\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-[var(--color-text-primary)]\">\n {p.name}\n </p>\n <p className=\"text-xs text-[var(--color-text-secondary)]\">\n {p.reviewedBy ? `by ${p.reviewedBy} · ` : ''}\n {formatDate(p.publishedAt)}\n </p>\n </div>\n <span className=\"shrink-0 rounded-full bg-[var(--color-status-success-bg-subtle)] px-2.5 py-1 text-xs font-medium text-[var(--color-status-success-text)]\">\n Published\n </span>\n </li>\n ))}\n </ul>\n )}\n </GlassCard>\n\n {/* Recently Rejected */}\n <GlassCard className=\"overflow-hidden p-0\">\n <div className=\"flex items-center gap-2.5 border-b border-[var(--color-border-seam)] px-4 py-3\">\n <div className=\"flex size-7 items-center justify-center rounded-md bg-[var(--color-status-error-bg-subtle)]\">\n <XCircle className=\"size-4 text-[var(--color-status-error-text)]\" />\n </div>\n <h3 className=\"text-sm font-semibold text-[var(--color-text-primary)]\">\n Recently Rejected\n </h3>\n <span className=\"ml-auto text-xs text-[var(--color-text-muted)]\">Last 5</span>\n </div>\n {state.loading ? (\n <div className=\"space-y-3 p-4\">\n {[1, 2, 3].map((i) => (\n <div\n key={i}\n className=\"h-10 animate-pulse rounded-lg bg-[var(--color-surface-muted)]\"\n />\n ))}\n </div>\n ) : state.recentRejected.length === 0 ? (\n <p className=\"px-4 py-8 text-center text-sm text-[var(--color-text-muted)]\">\n No rejected products yet.\n </p>\n ) : (\n <ul className=\"divide-y divide-[var(--color-border-seam)]\">\n {state.recentRejected.map((p) => (\n <li key={p.id} className=\"flex items-center gap-3 px-4 py-3\">\n {p.icon ? (\n <img\n src={p.icon}\n alt=\"\"\n className=\"size-8 shrink-0 rounded-lg object-cover\"\n />\n ) : (\n <div className=\"flex size-8 shrink-0 items-center justify-center rounded-lg bg-[var(--color-surface-muted)] text-xs font-bold text-[var(--color-text-muted)] uppercase\">\n {(p.type ?? '?').slice(0, 2)}\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-[var(--color-text-primary)]\">\n {p.name}\n </p>\n {p.rejectedReason && (\n <p\n className=\"truncate text-xs text-[var(--color-text-muted)]\"\n title={p.rejectedReason}\n >\n \"{p.rejectedReason}\"\n </p>\n )}\n <p className=\"text-xs text-[var(--color-text-secondary)]\">\n {formatDate(p.reviewedAt)}\n </p>\n </div>\n <span className=\"shrink-0 rounded-full bg-[var(--color-status-error-bg-subtle)] px-2.5 py-1 text-xs font-medium text-[var(--color-status-error-text)]\">\n Rejected\n </span>\n </li>\n ))}\n </ul>\n )}\n </GlassCard>\n </div>\n </section>\n </main>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"AdminDashboardPage.js","names":[],"sources":["../../src/pages/AdminDashboardPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useCallback, useEffect, useState } from 'react';\nimport {\n LayoutDashboard,\n PackageCheck,\n Store as StoreIcon,\n MessageSquareWarning,\n Users,\n Check,\n X,\n ExternalLink,\n CheckCircle,\n XCircle,\n RefreshCw,\n ChevronDown,\n ChevronUp,\n ArrowRight,\n Clock,\n TrendingUp,\n Container,\n Gavel,\n} from 'lucide-react';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport { GlassCard, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport { useNavigate } from 'react-router';\nimport { useStore } from '../providers/StoreProvider';\nimport { formatPriceForModel } from '../utils';\nimport {\n GOVERNANCE_STATS_QUERY,\n runGovernanceOperation,\n type GovernanceStats,\n} from '../utils/governance';\n\ntype RecentProduct = {\n id: string;\n slug: string;\n name: string;\n type: string;\n nature?: string | null;\n category?: string | null;\n icon?: string | null;\n price: number;\n currency: string;\n pricingModel: string;\n description?: string | null;\n submittedAt?: string | null;\n submittedBy?: string | null;\n productId?: string | null;\n publisher?: { name: string; email: string } | null;\n};\n\ntype ActivityProduct = {\n id: string;\n slug: string;\n name: string;\n icon?: string | null;\n type?: string;\n status: string;\n publishedAt?: string | null;\n reviewedAt?: string | null;\n reviewedBy?: string | null;\n rejectedReason?: string | null;\n};\n\ntype DashboardState = {\n pendingCount: number;\n totalStores: number;\n recentPending: RecentProduct[];\n recentPublished: ActivityProduct[];\n recentRejected: ActivityProduct[];\n loading: boolean;\n error: string | null;\n};\n\nconst DASHBOARD_QUERY = /* GraphQL */ `\n query AdminDashboardData {\n recentPending: pendingReviewProducts(limit: 5) {\n totalCount\n products {\n id\n slug\n name\n type\n nature\n category\n icon\n price\n currency\n pricingModel\n description\n submittedAt\n submittedBy\n productId\n publisher {\n name\n email\n }\n }\n }\n stores {\n id\n }\n recentPublished: products(filter: { status: PUBLISHED }, limit: 5) {\n id\n slug\n name\n icon\n type\n status\n publishedAt\n reviewedBy\n }\n recentRejected: products(filter: { status: REJECTED }, limit: 5) {\n id\n slug\n name\n icon\n type\n status\n reviewedAt\n rejectedReason\n }\n }\n`;\n\nconst APPROVE_MUTATION = /* GraphQL */ `\n mutation ApproveProduct($id: ID!) {\n approveProduct(id: $id) {\n id\n status\n }\n }\n`;\n\nconst REJECT_MUTATION = /* GraphQL */ `\n mutation RejectProduct($id: ID!, $reason: String!) {\n rejectProduct(id: $id, reason: $reason) {\n id\n status\n }\n }\n`;\n\nconst DELIST_MUTATION = /* GraphQL */ `\n mutation DelistProduct($id: ID!, $reason: String) {\n delistProduct(id: $id, reason: $reason) {\n id\n status\n }\n }\n`;\n\nconst TYPE_COLORS: Record<string, string> = {\n NODE: 'bg-[var(--color-status-info-bg-subtle)] text-[var(--color-status-info-text)]',\n WORKFLOW: 'bg-[var(--color-status-warning-bg-subtle)] text-[var(--color-status-warning-text)]',\n INTEGRATION: 'bg-[var(--color-status-success-bg-subtle)] text-[var(--color-status-success-text)]',\n VIBEMODULE: 'bg-[var(--color-surface-accent-muted)] text-[var(--color-text-accent)]',\n};\n\nfunction typeColor(type?: string | null) {\n if (!type) return 'bg-[var(--color-surface-muted)] text-[var(--color-text-secondary)]';\n return TYPE_COLORS[type] ?? 'bg-[var(--color-surface-muted)] text-[var(--color-text-secondary)]';\n}\n\nfunction formatDate(iso?: string | null) {\n if (!iso) return '—';\n return new Date(iso).toLocaleDateString(undefined, {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n });\n}\n\nfunction timeAgo(iso?: string | null) {\n if (!iso) return '—';\n const diff = Date.now() - new Date(iso).getTime();\n const mins = Math.floor(diff / 60000);\n if (mins < 60) return `${mins}m ago`;\n const hrs = Math.floor(mins / 60);\n if (hrs < 24) return `${hrs}h ago`;\n const days = Math.floor(hrs / 24);\n return `${days}d ago`;\n}\n\nexport const AdminDashboardPage: FC = () => {\n const navigate = useNavigate();\n const { authToken, basePath } = useStore();\n\n const [state, setState] = useState<DashboardState>({\n pendingCount: 0,\n totalStores: 0,\n recentPending: [],\n recentPublished: [],\n recentRejected: [],\n loading: true,\n error: null,\n });\n const [actingId, setActingId] = useState<string | null>(null);\n const [rejectingId, setRejectingId] = useState<string | null>(null);\n const [rejectReason, setRejectReason] = useState('');\n const [actionError, setActionError] = useState<string | null>(null);\n const [viewingId, setViewingId] = useState<string | null>(null);\n\n const load = useCallback(async () => {\n setState((prev) => ({ ...prev, loading: true, error: null }));\n try {\n const result = await graphqlFetch<{\n recentPending: { totalCount: number; products: RecentProduct[] };\n stores: Array<{ id: string }>;\n recentPublished: ActivityProduct[];\n recentRejected: ActivityProduct[];\n }>({\n gateway: 'global',\n query: DASHBOARD_QUERY,\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Failed to load dashboard');\n }\n setState({\n pendingCount: result.data?.recentPending.totalCount ?? 0,\n totalStores: result.data?.stores.length ?? 0,\n recentPending: result.data?.recentPending.products ?? [],\n recentPublished: result.data?.recentPublished ?? [],\n recentRejected: result.data?.recentRejected ?? [],\n loading: false,\n error: null,\n });\n } catch (err) {\n setState((prev) => ({\n ...prev,\n loading: false,\n error: err instanceof Error ? err.message : 'Failed to load dashboard',\n }));\n }\n }, [authToken]);\n\n useEffect(() => {\n void load();\n }, [load]);\n\n // Review SLA and governance counters (BOFF-10635). Loaded separately so a\n // governance outage never blanks the rest of the dashboard.\n const [governance, setGovernance] = useState<GovernanceStats | null>(null);\n useEffect(() => {\n let cancelled = false;\n runGovernanceOperation<{ storeGovernanceStats: GovernanceStats }>(\n GOVERNANCE_STATS_QUERY,\n undefined,\n authToken,\n 'Failed to load governance counters'\n )\n .then((data) => {\n if (!cancelled) setGovernance(data.storeGovernanceStats);\n })\n .catch(() => {\n if (!cancelled) setGovernance(null);\n });\n return () => {\n cancelled = true;\n };\n }, [authToken, state.loading]);\n const governanceOpen = governance\n ? governance.openListingReports + governance.openReviewReports + governance.pendingAppeals\n : null;\n\n const approve = async (id: string) => {\n setActingId(id);\n setActionError(null);\n try {\n const result = await graphqlFetch({\n gateway: 'global',\n query: APPROVE_MUTATION,\n variables: { id },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Approval failed');\n }\n await load();\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Approval failed');\n } finally {\n setActingId(null);\n }\n };\n\n const reject = async (id: string) => {\n if (!rejectReason.trim()) return;\n setActingId(id);\n setActionError(null);\n try {\n const result = await graphqlFetch({\n gateway: 'global',\n query: REJECT_MUTATION,\n variables: { id, reason: rejectReason.trim() },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Rejection failed');\n }\n setRejectingId(null);\n setRejectReason('');\n await load();\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Rejection failed');\n } finally {\n setActingId(null);\n }\n };\n\n const delist = async (id: string) => {\n setActingId(id);\n setActionError(null);\n try {\n const result = await graphqlFetch({\n gateway: 'global',\n query: DELIST_MUTATION,\n variables: { id, reason: rejectReason.trim() || undefined },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Delist failed');\n }\n setRejectingId(null);\n setRejectReason('');\n await load();\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Delist failed');\n } finally {\n setActingId(null);\n }\n };\n\n const kpiTiles = [\n {\n label: 'Pending review',\n value: state.pendingCount,\n icon: PackageCheck,\n href: `${basePath}/submissions`,\n accent:\n state.pendingCount > 0\n ? 'border-[var(--color-status-warning-text)]/30 bg-[var(--color-status-warning-bg-subtle)]/20'\n : '',\n valueClass:\n state.pendingCount > 0\n ? 'text-[var(--color-status-warning-text)]'\n : 'text-[var(--color-text-primary)]',\n cta:\n governance && governance.overdueSubmissions > 0\n ? `Review queue · ${governance.overdueSubmissions} overdue`\n : 'Review queue',\n },\n {\n label: 'Governance',\n value: governanceOpen ?? '—',\n icon: Gavel,\n href: `${basePath}/governance`,\n accent:\n governanceOpen && governanceOpen > 0\n ? 'border-[var(--color-status-warning-text)]/30 bg-[var(--color-status-warning-bg-subtle)]/20'\n : '',\n valueClass:\n governanceOpen && governanceOpen > 0\n ? 'text-[var(--color-status-warning-text)]'\n : 'text-[var(--color-text-primary)]',\n cta: 'Reports, takedowns & appeals',\n },\n {\n label: 'Stores in registry',\n value: state.totalStores,\n icon: StoreIcon,\n href: `${basePath}/stores`,\n accent: '',\n valueClass: 'text-[var(--color-text-primary)]',\n cta: 'Manage stores',\n },\n {\n label: 'Review moderation',\n value: '—',\n icon: MessageSquareWarning,\n href: `${basePath}/reviews`,\n accent: '',\n valueClass: 'text-[var(--color-text-primary)]',\n cta: 'Moderate reviews',\n },\n {\n label: 'Publishers',\n value: '—',\n icon: Users,\n href: `${basePath}/dashboard`,\n accent: '',\n valueClass: 'text-[var(--color-text-primary)]',\n cta: 'View publishers',\n },\n {\n label: 'Sandbox build checks',\n value: '—',\n icon: Container,\n href: `${basePath}/sandbox-builds`,\n accent: '',\n valueClass: 'text-[var(--color-text-primary)]',\n cta: 'View build log',\n },\n ];\n\n return (\n <div className=\"min-h-screen bg-[var(--color-surface-default)]\">\n {/* Header */}\n <header className=\"sticky top-0 z-10 border-b border-[var(--color-border-seam)] bg-[var(--color-surface-raised)] shadow-[var(--shadow-elevation-1)]\">\n <div className=\"mx-auto max-w-6xl px-6 py-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2.5\">\n <LayoutDashboard className=\"size-5 text-[var(--color-text-primary)]\" />\n <div>\n <h1 className=\"text-lg font-semibold text-[var(--color-text-primary)]\">\n Store Admin Dashboard\n </h1>\n <p className=\"text-xs text-[var(--color-text-secondary)]\">\n Marketplace control room — submissions, registry, moderation\n </p>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={() => void load()}\n disabled={state.loading}\n className=\"inline-flex items-center gap-2 rounded-md border border-[var(--color-border-default)] bg-[var(--color-surface-default)] px-3 py-1.5 text-sm text-[var(--color-text-primary)] hover:bg-[var(--color-surface-hover)] transition-colors disabled:opacity-50\"\n >\n <RefreshCw className={`size-4 ${state.loading ? 'animate-spin' : ''}`} />\n Refresh\n </button>\n </div>\n </div>\n </header>\n\n <main className=\"mx-auto max-w-6xl space-y-8 p-6\">\n {state.error && (\n <div\n role=\"alert\"\n className=\"rounded-lg border border-[var(--color-border-danger)] bg-[var(--color-surface-danger-muted)] px-4 py-3 text-sm text-[var(--color-text-danger)]\"\n >\n {state.error}\n </div>\n )}\n\n {/* KPI tiles */}\n <section>\n <div className=\"grid grid-cols-2 gap-4 lg:grid-cols-4\">\n {kpiTiles.map((tile) => {\n const Icon = tile.icon;\n return (\n <button\n key={tile.label}\n type=\"button\"\n onClick={() => navigate(tile.href)}\n className={`group relative overflow-hidden rounded-xl border border-[var(--color-border-seam)] bg-[var(--color-surface-raised)] p-5 text-left shadow-[var(--shadow-elevation-1)] transition-all hover:border-[var(--color-border-strong)] hover:shadow-[var(--shadow-elevation-2)] ${tile.accent}`}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div>\n <p className=\"text-xs font-medium text-[var(--color-text-secondary)] uppercase tracking-wide\">\n {tile.label}\n </p>\n <p className={`mt-2 text-3xl font-bold tabular-nums ${tile.valueClass}`}>\n {state.loading ? (\n <span className=\"inline-block w-8 animate-pulse rounded bg-[var(--color-surface-muted)] h-8\" />\n ) : (\n tile.value\n )}\n </p>\n </div>\n <div className=\"rounded-lg bg-[var(--color-surface-muted)] p-2\">\n <Icon className=\"size-5 text-[var(--color-text-secondary)]\" />\n </div>\n </div>\n <div className=\"mt-3 flex items-center gap-1 text-xs font-medium text-[var(--color-text-accent)] group-hover:underline\">\n {tile.cta}\n <ArrowRight className=\"size-3\" />\n </div>\n </button>\n );\n })}\n </div>\n </section>\n\n {/* Pending submissions */}\n <section>\n <div className=\"mb-4 flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <h2 className=\"text-base font-semibold text-[var(--color-text-primary)]\">\n Pending Submissions\n </h2>\n {state.pendingCount > 0 && (\n <span className=\"rounded-full bg-[var(--color-status-warning-bg-subtle)] px-2 py-0.5 text-xs font-semibold text-[var(--color-status-warning-text)]\">\n {state.pendingCount} waiting\n </span>\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/submissions`)}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-[var(--color-border-default)] bg-[var(--color-surface-default)] px-3 py-1.5 text-xs font-medium text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] hover:text-[var(--color-text-primary)] transition-colors\"\n >\n View all\n <ExternalLink className=\"size-3.5\" />\n </button>\n </div>\n\n {actionError && (\n <div\n role=\"alert\"\n className=\"mb-3 rounded-lg border border-[var(--color-border-danger)] bg-[var(--color-surface-danger-muted)] px-4 py-2.5 text-sm text-[var(--color-text-danger)]\"\n >\n {actionError}\n </div>\n )}\n\n {state.loading ? (\n <div className=\"space-y-3\">\n {[1, 2, 3].map((i) => (\n <div\n key={i}\n className=\"h-20 animate-pulse rounded-xl bg-[var(--color-surface-muted)]\"\n />\n ))}\n </div>\n ) : state.recentPending.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title=\"All caught up\"\n description=\"No submissions awaiting review. New products will appear here as publishers send them in.\"\n />\n ) : (\n <div className=\"overflow-hidden rounded-xl border border-[var(--color-border-seam)] bg-[var(--color-surface-raised)] shadow-[var(--shadow-elevation-1)]\">\n <ul className=\"divide-y divide-[var(--color-border-seam)]\">\n {state.recentPending.map((p) => (\n <li key={p.id} className=\"overflow-hidden\">\n <div className=\"flex items-center gap-4 px-5 py-4\">\n {p.icon ? (\n <img\n src={p.icon}\n alt=\"\"\n className=\"size-10 shrink-0 rounded-lg object-cover\"\n />\n ) : (\n <div className=\"flex size-10 shrink-0 items-center justify-center rounded-lg bg-[var(--color-surface-muted)] text-xs font-bold text-[var(--color-text-muted)] uppercase\">\n {p.type.slice(0, 2)}\n </div>\n )}\n\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <p className=\"font-medium text-[var(--color-text-primary)] truncate\">\n {p.name}\n </p>\n <span\n className={`shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide ${typeColor(p.type)}`}\n >\n {p.type}\n </span>\n </div>\n <div className=\"mt-0.5 flex items-center gap-3 flex-wrap text-xs text-[var(--color-text-secondary)]\">\n <span>{p.publisher?.name ?? 'Unknown publisher'}</span>\n <span>·</span>\n <span>{formatPriceForModel(p.price, p.currency, p.pricingModel)}</span>\n {p.submittedAt && (\n <>\n <span>·</span>\n <span className=\"flex items-center gap-1\">\n <Clock className=\"size-3\" />\n {timeAgo(p.submittedAt)}\n </span>\n </>\n )}\n </div>\n </div>\n\n <div className=\"shrink-0 flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setViewingId(viewingId === p.id ? null : p.id)}\n className=\"inline-flex items-center gap-1 rounded-md border border-[var(--color-border-default)] px-2.5 py-1.5 text-xs text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] transition-colors\"\n >\n {viewingId === p.id ? (\n <ChevronUp className=\"size-3.5\" />\n ) : (\n <ChevronDown className=\"size-3.5\" />\n )}\n Details\n </button>\n <button\n type=\"button\"\n onClick={() => void approve(p.id)}\n disabled={actingId === p.id}\n className=\"inline-flex items-center gap-1 rounded-md bg-[var(--color-surface-success)] px-3 py-1.5 text-xs font-semibold text-[var(--color-text-on-success)] hover:opacity-90 disabled:opacity-50 transition-opacity\"\n >\n <Check className=\"size-3.5\" />\n Approve\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setRejectingId(p.id);\n setRejectReason('');\n setActionError(null);\n }}\n disabled={actingId === p.id}\n className=\"inline-flex items-center gap-1 rounded-md border border-[var(--color-border-danger)] bg-[var(--color-surface-danger-muted)] px-3 py-1.5 text-xs font-semibold text-[var(--color-text-danger)] hover:bg-[var(--color-surface-danger)] hover:text-[var(--color-text-on-danger)] disabled:opacity-50 transition-colors\"\n >\n <X className=\"size-3.5\" />\n Reject\n </button>\n </div>\n </div>\n\n {viewingId === p.id && (\n <div className=\"border-t border-[var(--color-border-seam)] bg-[var(--color-surface-muted)] px-5 py-4\">\n <dl className=\"grid grid-cols-2 gap-x-8 gap-y-3 text-xs sm:grid-cols-3\">\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">Slug</dt>\n <dd className=\"mt-0.5 font-mono text-[var(--color-text-primary)]\">\n {p.slug}\n </dd>\n </div>\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">Type</dt>\n <dd className=\"mt-0.5 text-[var(--color-text-primary)]\">\n {p.type} · {p.nature ?? '—'}\n </dd>\n </div>\n {p.category && (\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">\n Category\n </dt>\n <dd className=\"mt-0.5 text-[var(--color-text-primary)]\">\n {p.category}\n </dd>\n </div>\n )}\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">\n Pricing\n </dt>\n <dd className=\"mt-0.5 text-[var(--color-text-primary)]\">\n {formatPriceForModel(p.price, p.currency, p.pricingModel)}{' '}\n <span className=\"text-[var(--color-text-muted)]\">\n ({p.pricingModel})\n </span>\n </dd>\n </div>\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">\n Publisher\n </dt>\n <dd className=\"mt-0.5 text-[var(--color-text-primary)]\">\n {p.publisher?.name ?? '—'}\n {p.publisher?.email && (\n <span className=\"block text-[var(--color-text-muted)]\">\n {p.publisher.email}\n </span>\n )}\n </dd>\n </div>\n {p.productId && (\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">\n Devportal app\n </dt>\n <dd className=\"mt-0.5\">\n <a\n href={`/devportal/apps/${p.productId}`}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"inline-flex items-center gap-1 font-mono text-[var(--color-text-accent)] hover:underline\"\n >\n {p.productId.slice(0, 16)}…\n <ExternalLink className=\"size-3\" />\n </a>\n </dd>\n </div>\n )}\n {p.description && (\n <div className=\"col-span-2 sm:col-span-3\">\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">\n Description\n </dt>\n <dd className=\"mt-0.5 text-[var(--color-text-primary)]\">\n {p.description}\n </dd>\n </div>\n )}\n </dl>\n </div>\n )}\n\n {rejectingId === p.id && (\n <div className=\"border-t border-[var(--color-border-seam)] bg-[var(--color-surface-danger-muted)]/30 px-5 py-4\">\n <p className=\"mb-2 text-xs font-semibold text-[var(--color-text-danger)]\">\n Provide a rejection reason (the publisher will see this)\n </p>\n <textarea\n id={`reject-reason-${p.id}`}\n aria-label=\"Rejection reason\"\n value={rejectReason}\n onChange={(e) => setRejectReason(e.target.value)}\n rows={2}\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-surface-default)] px-3 py-2 text-sm text-[var(--color-text-primary)] focus:border-[var(--color-border-accent)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] resize-none\"\n placeholder=\"Explain why this submission is being rejected…\"\n />\n <div className=\"mt-2 flex justify-end gap-2\">\n <button\n type=\"button\"\n onClick={() => {\n setRejectingId(null);\n setRejectReason('');\n }}\n className=\"rounded-md border border-[var(--color-border-default)] bg-[var(--color-surface-default)] px-3 py-1 text-xs font-medium text-[var(--color-text-primary)] hover:bg-[var(--color-surface-hover)] transition-colors\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => void reject(p.id)}\n disabled={!rejectReason.trim() || actingId === p.id}\n className=\"rounded-md bg-[var(--color-surface-danger)] px-3 py-1 text-xs font-semibold text-[var(--color-text-on-danger)] hover:opacity-90 disabled:opacity-50 transition-opacity\"\n >\n Submit rejection\n </button>\n </div>\n </div>\n )}\n </li>\n ))}\n </ul>\n </div>\n )}\n </section>\n\n {/* Recent Activity */}\n <section>\n <div className=\"mb-4 flex items-center gap-2\">\n <TrendingUp className=\"size-4 text-[var(--color-text-secondary)]\" />\n <h2 className=\"text-base font-semibold text-[var(--color-text-primary)]\">\n Recent Activity\n </h2>\n </div>\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n {/* Recently Approved */}\n <GlassCard className=\"overflow-hidden p-0\">\n <div className=\"flex items-center gap-2.5 border-b border-[var(--color-border-seam)] px-4 py-3\">\n <div className=\"flex size-7 items-center justify-center rounded-md bg-[var(--color-status-success-bg-subtle)]\">\n <CheckCircle className=\"size-4 text-[var(--color-status-success-text)]\" />\n </div>\n <h3 className=\"text-sm font-semibold text-[var(--color-text-primary)]\">\n Recently Approved\n </h3>\n <span className=\"ml-auto text-xs text-[var(--color-text-muted)]\">Last 5</span>\n </div>\n {state.loading ? (\n <div className=\"space-y-3 p-4\">\n {[1, 2, 3].map((i) => (\n <div\n key={i}\n className=\"h-10 animate-pulse rounded-lg bg-[var(--color-surface-muted)]\"\n />\n ))}\n </div>\n ) : state.recentPublished.length === 0 ? (\n <p className=\"px-4 py-8 text-center text-sm text-[var(--color-text-muted)]\">\n No approved products yet.\n </p>\n ) : (\n <ul className=\"divide-y divide-[var(--color-border-seam)]\">\n {state.recentPublished.map((p) => (\n <li key={p.id} className=\"flex items-center gap-3 px-4 py-3\">\n {p.icon ? (\n <img\n src={p.icon}\n alt=\"\"\n className=\"size-8 shrink-0 rounded-lg object-cover\"\n />\n ) : (\n <div className=\"flex size-8 shrink-0 items-center justify-center rounded-lg bg-[var(--color-surface-muted)] text-xs font-bold text-[var(--color-text-muted)] uppercase\">\n {(p.type ?? '?').slice(0, 2)}\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-[var(--color-text-primary)]\">\n {p.name}\n </p>\n <p className=\"text-xs text-[var(--color-text-secondary)]\">\n {p.reviewedBy ? `by ${p.reviewedBy} · ` : ''}\n {formatDate(p.publishedAt)}\n </p>\n </div>\n <span className=\"shrink-0 rounded-full bg-[var(--color-status-success-bg-subtle)] px-2.5 py-1 text-xs font-medium text-[var(--color-status-success-text)]\">\n Published\n </span>\n </li>\n ))}\n </ul>\n )}\n </GlassCard>\n\n {/* Recently Rejected */}\n <GlassCard className=\"overflow-hidden p-0\">\n <div className=\"flex items-center gap-2.5 border-b border-[var(--color-border-seam)] px-4 py-3\">\n <div className=\"flex size-7 items-center justify-center rounded-md bg-[var(--color-status-error-bg-subtle)]\">\n <XCircle className=\"size-4 text-[var(--color-status-error-text)]\" />\n </div>\n <h3 className=\"text-sm font-semibold text-[var(--color-text-primary)]\">\n Recently Rejected\n </h3>\n <span className=\"ml-auto text-xs text-[var(--color-text-muted)]\">Last 5</span>\n </div>\n {state.loading ? (\n <div className=\"space-y-3 p-4\">\n {[1, 2, 3].map((i) => (\n <div\n key={i}\n className=\"h-10 animate-pulse rounded-lg bg-[var(--color-surface-muted)]\"\n />\n ))}\n </div>\n ) : state.recentRejected.length === 0 ? (\n <p className=\"px-4 py-8 text-center text-sm text-[var(--color-text-muted)]\">\n No rejected products yet.\n </p>\n ) : (\n <ul className=\"divide-y divide-[var(--color-border-seam)]\">\n {state.recentRejected.map((p) => (\n <li key={p.id} className=\"flex items-center gap-3 px-4 py-3\">\n {p.icon ? (\n <img\n src={p.icon}\n alt=\"\"\n className=\"size-8 shrink-0 rounded-lg object-cover\"\n />\n ) : (\n <div className=\"flex size-8 shrink-0 items-center justify-center rounded-lg bg-[var(--color-surface-muted)] text-xs font-bold text-[var(--color-text-muted)] uppercase\">\n {(p.type ?? '?').slice(0, 2)}\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-[var(--color-text-primary)]\">\n {p.name}\n </p>\n {p.rejectedReason && (\n <p\n className=\"truncate text-xs text-[var(--color-text-muted)]\"\n title={p.rejectedReason}\n >\n \"{p.rejectedReason}\"\n </p>\n )}\n <p className=\"text-xs text-[var(--color-text-secondary)]\">\n {formatDate(p.reviewedAt)}\n </p>\n </div>\n <span className=\"shrink-0 rounded-full bg-[var(--color-status-error-bg-subtle)] px-2.5 py-1 text-xs font-medium text-[var(--color-status-error-text)]\">\n Rejected\n </span>\n </li>\n ))}\n </ul>\n )}\n </GlassCard>\n </div>\n </section>\n </main>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;AA0EA,IAAM,IAAgC,+1BAmDhC,IAAiC,gHASjC,IAAgC,iJAkBhC,IAAsC;CAC1C,MAAM;CACN,UAAU;CACV,aAAa;CACb,YAAY;CACb;AAED,SAAS,EAAU,GAAsB;AAEvC,QADK,IACE,EAAY,MAAS,uEADV;;AAIpB,SAAS,EAAW,GAAqB;AAEvC,QADK,IACE,IAAI,KAAK,EAAI,CAAC,mBAAmB,KAAA,GAAW;EACjD,OAAO;EACP,KAAK;EACL,MAAM;EACP,CAAC,GALe;;AAQnB,SAAS,EAAQ,GAAqB;AACpC,KAAI,CAAC,EAAK,QAAO;CACjB,IAAM,IAAO,KAAK,KAAK,GAAG,IAAI,KAAK,EAAI,CAAC,SAAS,EAC3C,IAAO,KAAK,MAAM,IAAO,IAAM;AACrC,KAAI,IAAO,GAAI,QAAO,GAAG,EAAK;CAC9B,IAAM,IAAM,KAAK,MAAM,IAAO,GAAG;AAGjC,QAFI,IAAM,KAAW,GAAG,EAAI,SAErB,GADM,KAAK,MAAM,IAAM,GAAG,CAClB;;AAGjB,IAAa,UAA+B;CAC1C,IAAM,IAAW,GAAa,EACxB,EAAE,cAAW,gBAAa,GAAU,EAEpC,CAAC,GAAO,KAAY,EAAyB;EACjD,cAAc;EACd,aAAa;EACb,eAAe,EAAE;EACjB,iBAAiB,EAAE;EACnB,gBAAgB,EAAE;EAClB,SAAS;EACT,OAAO;EACR,CAAC,EACI,CAAC,GAAU,KAAe,EAAwB,KAAK,EACvD,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAC7D,CAAC,GAAc,KAAmB,EAAS,GAAG,EAC9C,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAC7D,CAAC,GAAW,KAAgB,EAAwB,KAAK,EAEzD,IAAO,EAAY,YAAY;AACnC,KAAU,OAAU;GAAE,GAAG;GAAM,SAAS;GAAM,OAAO;GAAM,EAAE;AAC7D,MAAI;GACF,IAAM,IAAS,MAAM,EAKlB;IACD,SAAS;IACT,OAAO;IACP,WAAW,KAAa,KAAA;IACzB,CAAC;AACF,OAAI,EAAO,QAAQ,OACjB,OAAU,MAAM,EAAO,OAAO,IAAI,WAAW,2BAA2B;AAE1E,KAAS;IACP,cAAc,EAAO,MAAM,cAAc,cAAc;IACvD,aAAa,EAAO,MAAM,OAAO,UAAU;IAC3C,eAAe,EAAO,MAAM,cAAc,YAAY,EAAE;IACxD,iBAAiB,EAAO,MAAM,mBAAmB,EAAE;IACnD,gBAAgB,EAAO,MAAM,kBAAkB,EAAE;IACjD,SAAS;IACT,OAAO;IACR,CAAC;WACK,GAAK;AACZ,MAAU,OAAU;IAClB,GAAG;IACH,SAAS;IACT,OAAO,aAAe,QAAQ,EAAI,UAAU;IAC7C,EAAE;;IAEJ,CAAC,EAAU,CAAC;AAEf,SAAgB;AACT,KAAM;IACV,CAAC,EAAK,CAAC;CAIV,IAAM,CAAC,GAAY,KAAiB,EAAiC,KAAK;AAC1E,SAAgB;EACd,IAAI,IAAY;AAahB,SAZA,EACE,GACA,KAAA,GACA,GACA,qCACD,CACE,MAAM,MAAS;AACd,GAAK,KAAW,EAAc,EAAK,qBAAqB;IACxD,CACD,YAAY;AACX,GAAK,KAAW,EAAc,KAAK;IACnC,QACS;AACX,OAAY;;IAEb,CAAC,GAAW,EAAM,QAAQ,CAAC;CAC9B,IAAM,IAAiB,IACnB,EAAW,qBAAqB,EAAW,oBAAoB,EAAW,iBAC1E,MAEE,KAAU,OAAO,MAAe;AAEpC,EADA,EAAY,EAAG,EACf,EAAe,KAAK;AACpB,MAAI;GACF,IAAM,IAAS,MAAM,EAAa;IAChC,SAAS;IACT,OAAO;IACP,WAAW,EAAE,OAAI;IACjB,WAAW,KAAa,KAAA;IACzB,CAAC;AACF,OAAI,EAAO,QAAQ,OACjB,OAAU,MAAM,EAAO,OAAO,IAAI,WAAW,kBAAkB;AAEjE,SAAM,GAAM;WACL,GAAK;AACZ,KAAe,aAAe,QAAQ,EAAI,UAAU,kBAAkB;YAC9D;AACR,KAAY,KAAK;;IAIf,KAAS,OAAO,MAAe;AAC9B,QAAa,MAAM,EAExB;GADA,EAAY,EAAG,EACf,EAAe,KAAK;AACpB,OAAI;IACF,IAAM,IAAS,MAAM,EAAa;KAChC,SAAS;KACT,OAAO;KACP,WAAW;MAAE;MAAI,QAAQ,EAAa,MAAM;MAAE;KAC9C,WAAW,KAAa,KAAA;KACzB,CAAC;AACF,QAAI,EAAO,QAAQ,OACjB,OAAU,MAAM,EAAO,OAAO,IAAI,WAAW,mBAAmB;AAIlE,IAFA,EAAe,KAAK,EACpB,EAAgB,GAAG,EACnB,MAAM,GAAM;YACL,GAAK;AACZ,MAAe,aAAe,QAAQ,EAAI,UAAU,mBAAmB;aAC/D;AACR,MAAY,KAAK;;;IA2Bf,KAAW;EACf;GACE,OAAO;GACP,OAAO,EAAM;GACb,MAAM;GACN,MAAM,GAAG,EAAS;GAClB,QACE,EAAM,eAAe,IACjB,+FACA;GACN,YACE,EAAM,eAAe,IACjB,4CACA;GACN,KACE,KAAc,EAAW,qBAAqB,IAC1C,kBAAkB,EAAW,mBAAmB,YAChD;GACP;EACD;GACE,OAAO;GACP,OAAO,KAAkB;GACzB,MAAM;GACN,MAAM,GAAG,EAAS;GAClB,QACE,KAAkB,IAAiB,IAC/B,+FACA;GACN,YACE,KAAkB,IAAiB,IAC/B,4CACA;GACN,KAAK;GACN;EACD;GACE,OAAO;GACP,OAAO,EAAM;GACb,MAAM;GACN,MAAM,GAAG,EAAS;GAClB,QAAQ;GACR,YAAY;GACZ,KAAK;GACN;EACD;GACE,OAAO;GACP,OAAO;GACP,MAAM;GACN,MAAM,GAAG,EAAS;GAClB,QAAQ;GACR,YAAY;GACZ,KAAK;GACN;EACD;GACE,OAAO;GACP,OAAO;GACP,MAAM;GACN,MAAM,GAAG,EAAS;GAClB,QAAQ;GACR,YAAY;GACZ,KAAK;GACN;EACD;GACE,OAAO;GACP,OAAO;GACP,MAAM;GACN,MAAM,GAAG,EAAS;GAClB,QAAQ;GACR,YAAY;GACZ,KAAK;GACN;EACF;AAED,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CAEE,kBAAC,UAAD;GAAQ,WAAU;aAChB,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,IAAD,EAAiB,WAAU,2CAA4C,CAAA,EACvE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;OAAI,WAAU;iBAAyD;OAElE,CAAA,EACL,kBAAC,KAAD;OAAG,WAAU;iBAA6C;OAEtD,CAAA,CACA,EAAA,CAAA,CACF;SACN,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,KAAK,GAAM;MAC1B,UAAU,EAAM;MAChB,WAAU;gBAJZ,CAME,kBAAC,GAAD,EAAW,WAAW,UAAU,EAAM,UAAU,iBAAiB,MAAQ,CAAA,EAAA,UAElE;QACL;;IACF,CAAA;GACC,CAAA,EAET,kBAAC,QAAD;GAAM,WAAU;aAAhB;IACG,EAAM,SACL,kBAAC,OAAD;KACE,MAAK;KACL,WAAU;eAET,EAAM;KACH,CAAA;IAIR,kBAAC,WAAD,EAAA,UACE,kBAAC,OAAD;KAAK,WAAU;eACZ,GAAS,KAAK,MAAS;MACtB,IAAM,IAAO,EAAK;AAClB,aACE,kBAAC,UAAD;OAEE,MAAK;OACL,eAAe,EAAS,EAAK,KAAK;OAClC,WAAW,0QAA0Q,EAAK;iBAJ5R,CAME,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAK;SACJ,CAAA,EACJ,kBAAC,KAAD;SAAG,WAAW,wCAAwC,EAAK;mBACxD,EAAM,UACL,kBAAC,QAAD,EAAM,WAAU,8EAA+E,CAAA,GAE/F,EAAK;SAEL,CAAA,CACA,EAAA,CAAA,EACN,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,GAAD,EAAM,WAAU,6CAA8C,CAAA;SAC1D,CAAA,CACF;WACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACG,EAAK,KACN,kBAAC,GAAD,EAAY,WAAU,UAAW,CAAA,CAC7B;UACC;SA1BF,EAAK,MA0BH;OAEX;KACE,CAAA,EACE,CAAA;IAGV,kBAAC,WAAD,EAAA,UAAA;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,MAAD;QAAI,WAAU;kBAA2D;QAEpE,CAAA,EACJ,EAAM,eAAe,KACpB,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CACG,EAAM,cAAa,WACf;UAEL;UACN,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAS,GAAG,EAAS,cAAc;OAClD,WAAU;iBAHZ,CAIC,YAEC,kBAAC,GAAD,EAAc,WAAU,YAAa,CAAA,CAC9B;SACL;;KAEL,KACC,kBAAC,OAAD;MACE,MAAK;MACL,WAAU;gBAET;MACG,CAAA;KAGP,EAAM,UACL,kBAAC,OAAD;MAAK,WAAU;gBACZ;OAAC;OAAG;OAAG;OAAE,CAAC,KAAK,MACd,kBAAC,OAAD,EAEE,WAAU,iEACV,EAFK,EAEL,CACF;MACE,CAAA,GACJ,EAAM,cAAc,WAAW,IACjC,kBAAC,GAAD;MACE,cAAa;MACb,OAAM;MACN,aAAY;MACZ,CAAA,GAEF,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAM,cAAc,KAAK,MACxB,kBAAC,MAAD;QAAe,WAAU;kBAAzB;SACE,kBAAC,OAAD;UAAK,WAAU;oBAAf;WACG,EAAE,OACD,kBAAC,OAAD;YACE,KAAK,EAAE;YACP,KAAI;YACJ,WAAU;YACV,CAAA,GAEF,kBAAC,OAAD;YAAK,WAAU;sBACZ,EAAE,KAAK,MAAM,GAAG,EAAE;YACf,CAAA;WAGR,kBAAC,OAAD;YAAK,WAAU;sBAAf,CACE,kBAAC,OAAD;aAAK,WAAU;uBAAf,CACE,kBAAC,KAAD;cAAG,WAAU;wBACV,EAAE;cACD,CAAA,EACJ,kBAAC,QAAD;cACE,WAAW,uFAAuF,EAAU,EAAE,KAAK;wBAElH,EAAE;cACE,CAAA,CACH;gBACN,kBAAC,OAAD;aAAK,WAAU;uBAAf;cACE,kBAAC,QAAD,EAAA,UAAO,EAAE,WAAW,QAAQ,qBAA2B,CAAA;cACvD,kBAAC,QAAD,EAAA,UAAM,KAAQ,CAAA;cACd,kBAAC,QAAD,EAAA,UAAO,EAAoB,EAAE,OAAO,EAAE,UAAU,EAAE,aAAa,EAAQ,CAAA;cACtE,EAAE,eACD,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,QAAD,EAAA,UAAM,KAAQ,CAAA,EACd,kBAAC,QAAD;eAAM,WAAU;yBAAhB,CACE,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA,EAC3B,EAAQ,EAAE,YAAY,CAClB;iBACN,EAAA,CAAA;cAED;eACF;;WAEN,kBAAC,OAAD;YAAK,WAAU;sBAAf;aACE,kBAAC,UAAD;cACE,MAAK;cACL,eAAe,EAAa,MAAc,EAAE,KAAK,OAAO,EAAE,GAAG;cAC7D,WAAU;wBAHZ,CAKG,MAAc,EAAE,KACf,kBAAC,GAAD,EAAW,WAAU,YAAa,CAAA,GAElC,kBAAC,GAAD,EAAa,WAAU,YAAa,CAAA,EACpC,UAEK;;aACT,kBAAC,UAAD;cACE,MAAK;cACL,eAAe,KAAK,GAAQ,EAAE,GAAG;cACjC,UAAU,MAAa,EAAE;cACzB,WAAU;wBAJZ,CAME,kBAAC,GAAD,EAAO,WAAU,YAAa,CAAA,EAAA,UAEvB;;aACT,kBAAC,UAAD;cACE,MAAK;cACL,eAAe;AAGb,eAFA,EAAe,EAAE,GAAG,EACpB,EAAgB,GAAG,EACnB,EAAe,KAAK;;cAEtB,UAAU,MAAa,EAAE;cACzB,WAAU;wBARZ,CAUE,kBAAC,GAAD,EAAG,WAAU,YAAa,CAAA,EAAA,SAEnB;;aACL;;WACF;;SAEL,MAAc,EAAE,MACf,kBAAC,OAAD;UAAK,WAAU;oBACb,kBAAC,MAAD;WAAI,WAAU;qBAAd;YACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;aAAI,WAAU;uBAAiD;aAAS,CAAA,EACxE,kBAAC,MAAD;aAAI,WAAU;uBACX,EAAE;aACA,CAAA,CACD,EAAA,CAAA;YACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;aAAI,WAAU;uBAAiD;aAAS,CAAA,EACxE,kBAAC,MAAD;aAAI,WAAU;uBAAd;cACG,EAAE;cAAK;cAAI,EAAE,UAAU;cACrB;eACD,EAAA,CAAA;YACL,EAAE,YACD,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;aAAI,WAAU;uBAAiD;aAE1D,CAAA,EACL,kBAAC,MAAD;aAAI,WAAU;uBACX,EAAE;aACA,CAAA,CACD,EAAA,CAAA;YAER,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;aAAI,WAAU;uBAAiD;aAE1D,CAAA,EACL,kBAAC,MAAD;aAAI,WAAU;uBAAd;cACG,EAAoB,EAAE,OAAO,EAAE,UAAU,EAAE,aAAa;cAAE;cAC3D,kBAAC,QAAD;eAAM,WAAU;yBAAhB;gBAAiD;gBAC7C,EAAE;gBAAa;gBACZ;;cACJ;eACD,EAAA,CAAA;YACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;aAAI,WAAU;uBAAiD;aAE1D,CAAA,EACL,kBAAC,MAAD;aAAI,WAAU;uBAAd,CACG,EAAE,WAAW,QAAQ,KACrB,EAAE,WAAW,SACZ,kBAAC,QAAD;cAAM,WAAU;wBACb,EAAE,UAAU;cACR,CAAA,CAEN;eACD,EAAA,CAAA;YACL,EAAE,aACD,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;aAAI,WAAU;uBAAiD;aAE1D,CAAA,EACL,kBAAC,MAAD;aAAI,WAAU;uBACZ,kBAAC,KAAD;cACE,MAAM,mBAAmB,EAAE;cAC3B,QAAO;cACP,KAAI;cACJ,WAAU;wBAJZ;eAMG,EAAE,UAAU,MAAM,GAAG,GAAG;eAAC;eAC1B,kBAAC,GAAD,EAAc,WAAU,UAAW,CAAA;eACjC;;aACD,CAAA,CACD,EAAA,CAAA;YAEP,EAAE,eACD,kBAAC,OAAD;aAAK,WAAU;uBAAf,CACE,kBAAC,MAAD;cAAI,WAAU;wBAAiD;cAE1D,CAAA,EACL,kBAAC,MAAD;cAAI,WAAU;wBACX,EAAE;cACA,CAAA,CACD;;YAEL;;UACD,CAAA;SAGP,MAAgB,EAAE,MACjB,kBAAC,OAAD;UAAK,WAAU;oBAAf;WACE,kBAAC,KAAD;YAAG,WAAU;sBAA6D;YAEtE,CAAA;WACJ,kBAAC,YAAD;YACE,IAAI,iBAAiB,EAAE;YACvB,cAAW;YACX,OAAO;YACP,WAAW,MAAM,EAAgB,EAAE,OAAO,MAAM;YAChD,MAAM;YACN,WAAU;YACV,aAAY;YACZ,CAAA;WACF,kBAAC,OAAD;YAAK,WAAU;sBAAf,CACE,kBAAC,UAAD;aACE,MAAK;aACL,eAAe;AAEb,cADA,EAAe,KAAK,EACpB,EAAgB,GAAG;;aAErB,WAAU;uBACX;aAEQ,CAAA,EACT,kBAAC,UAAD;aACE,MAAK;aACL,eAAe,KAAK,GAAO,EAAE,GAAG;aAChC,UAAU,CAAC,EAAa,MAAM,IAAI,MAAa,EAAE;aACjD,WAAU;uBACX;aAEQ,CAAA,CACL;;WACF;;SAEL;UApMI,EAAE,GAoMN,CACL;OACC,CAAA;MACD,CAAA;KAEA,EAAA,CAAA;IAGV,kBAAC,WAAD,EAAA,UAAA,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,IAAD,EAAY,WAAU,6CAA8C,CAAA,EACpE,kBAAC,MAAD;MAAI,WAAU;gBAA2D;MAEpE,CAAA,CACD;QACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CAEE,kBAAC,GAAD;MAAW,WAAU;gBAArB,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,GAAD,EAAa,WAAU,kDAAmD,CAAA;SACtE,CAAA;QACN,kBAAC,MAAD;SAAI,WAAU;mBAAyD;SAElE,CAAA;QACL,kBAAC,QAAD;SAAM,WAAU;mBAAiD;SAAa,CAAA;QAC1E;UACL,EAAM,UACL,kBAAC,OAAD;OAAK,WAAU;iBACZ;QAAC;QAAG;QAAG;QAAE,CAAC,KAAK,MACd,kBAAC,OAAD,EAEE,WAAU,iEACV,EAFK,EAEL,CACF;OACE,CAAA,GACJ,EAAM,gBAAgB,WAAW,IACnC,kBAAC,KAAD;OAAG,WAAU;iBAA+D;OAExE,CAAA,GAEJ,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAM,gBAAgB,KAAK,MAC1B,kBAAC,MAAD;QAAe,WAAU;kBAAzB;SACG,EAAE,OACD,kBAAC,OAAD;UACE,KAAK,EAAE;UACP,KAAI;UACJ,WAAU;UACV,CAAA,GAEF,kBAAC,OAAD;UAAK,WAAU;qBACX,EAAE,QAAQ,KAAK,MAAM,GAAG,EAAE;UACxB,CAAA;SAER,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAE;WACD,CAAA,EACJ,kBAAC,KAAD;WAAG,WAAU;qBAAb,CACG,EAAE,aAAa,MAAM,EAAE,WAAW,OAAO,IACzC,EAAW,EAAE,YAAY,CACxB;aACA;;SACN,kBAAC,QAAD;UAAM,WAAU;oBAA2I;UAEpJ,CAAA;SACJ;UAxBI,EAAE,GAwBN,CACL;OACC,CAAA,CAEG;SAGZ,kBAAC,GAAD;MAAW,WAAU;gBAArB,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,GAAD,EAAS,WAAU,gDAAiD,CAAA;SAChE,CAAA;QACN,kBAAC,MAAD;SAAI,WAAU;mBAAyD;SAElE,CAAA;QACL,kBAAC,QAAD;SAAM,WAAU;mBAAiD;SAAa,CAAA;QAC1E;UACL,EAAM,UACL,kBAAC,OAAD;OAAK,WAAU;iBACZ;QAAC;QAAG;QAAG;QAAE,CAAC,KAAK,MACd,kBAAC,OAAD,EAEE,WAAU,iEACV,EAFK,EAEL,CACF;OACE,CAAA,GACJ,EAAM,eAAe,WAAW,IAClC,kBAAC,KAAD;OAAG,WAAU;iBAA+D;OAExE,CAAA,GAEJ,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAM,eAAe,KAAK,MACzB,kBAAC,MAAD;QAAe,WAAU;kBAAzB;SACG,EAAE,OACD,kBAAC,OAAD;UACE,KAAK,EAAE;UACP,KAAI;UACJ,WAAU;UACV,CAAA,GAEF,kBAAC,OAAD;UAAK,WAAU;qBACX,EAAE,QAAQ,KAAK,MAAM,GAAG,EAAE;UACxB,CAAA;SAER,kBAAC,OAAD;UAAK,WAAU;oBAAf;WACE,kBAAC,KAAD;YAAG,WAAU;sBACV,EAAE;YACD,CAAA;WACH,EAAE,kBACD,kBAAC,KAAD;YACE,WAAU;YACV,OAAO,EAAE;sBAFX;aAGC;aACG,EAAE;aAAe;aACjB;;WAEN,kBAAC,KAAD;YAAG,WAAU;sBACV,EAAW,EAAE,WAAW;YACvB,CAAA;WACA;;SACN,kBAAC,QAAD;UAAM,WAAU;oBAAuI;UAEhJ,CAAA;SACJ;UA/BI,EAAE,GA+BN,CACL;OACC,CAAA,CAEG;QACR;OACE,EAAA,CAAA;IACL;KACH"}