@burdenoff/microfe-admin 2026.610.1 → 2026.625.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.
- package/dist/admin/components/AccessDenied.js +2 -2
- package/dist/admin/components/AccessDenied.js.map +1 -1
- package/dist/admin/components/ActivityFeed.js +4 -4
- package/dist/admin/components/ActivityFeed.js.map +1 -1
- package/dist/admin/components/Charts.js +4 -4
- package/dist/admin/components/Charts.js.map +1 -1
- package/dist/admin/components/ConfirmDialog.js +6 -6
- package/dist/admin/components/ConfirmDialog.js.map +1 -1
- package/dist/admin/components/DataTable.js +8 -8
- package/dist/admin/components/DataTable.js.map +1 -1
- package/dist/admin/components/FormSheet.js +7 -7
- package/dist/admin/components/FormSheet.js.map +1 -1
- package/dist/admin/components/MetricCard.js +43 -40
- package/dist/admin/components/MetricCard.js.map +1 -1
- package/dist/admin/components/PageLayout.js +28 -22
- package/dist/admin/components/PageLayout.js.map +1 -1
- package/dist/admin/components/PermissionGuard.js +1 -1
- package/dist/admin/components/PermissionGuard.js.map +1 -1
- package/dist/admin/components/RowActions.js +2 -2
- package/dist/admin/components/RowActions.js.map +1 -1
- package/dist/admin/components/TenantMembersPanel.js +15 -15
- package/dist/admin/components/TenantMembersPanel.js.map +1 -1
- package/dist/admin/components/TenantSsoPanel.js +14 -14
- package/dist/admin/components/TenantSsoPanel.js.map +1 -1
- package/dist/admin/components/Toast.js +9 -9
- package/dist/admin/components/Toast.js.map +1 -1
- package/dist/admin/components/forms/SubscriptionForm.js +10 -10
- package/dist/admin/components/forms/SubscriptionForm.js.map +1 -1
- package/dist/admin/components/forms/TenantForm.js +8 -8
- package/dist/admin/components/forms/TenantForm.js.map +1 -1
- package/dist/admin/components/forms/TicketForm.js +2 -2
- package/dist/admin/components/forms/TicketForm.js.map +1 -1
- package/dist/admin/components/forms/UserForm.js +8 -8
- package/dist/admin/components/forms/UserForm.js.map +1 -1
- package/dist/admin/components/ui.js +58 -51
- package/dist/admin/components/ui.js.map +1 -1
- package/dist/admin/constants/index.js +8 -8
- package/dist/admin/constants/index.js.map +1 -1
- package/dist/admin/pages/AuditLogsPage.js +10 -8
- package/dist/admin/pages/AuditLogsPage.js.map +1 -1
- package/dist/admin/pages/BillingPage.js +51 -43
- package/dist/admin/pages/BillingPage.js.map +1 -1
- package/dist/admin/pages/CommunicationsPage.js +5 -2
- package/dist/admin/pages/CommunicationsPage.js.map +1 -1
- package/dist/admin/pages/ContentPagesPage.js +2 -0
- package/dist/admin/pages/ContentPagesPage.js.map +1 -1
- package/dist/admin/pages/DashboardPage.js +7 -4
- package/dist/admin/pages/DashboardPage.js.map +1 -1
- package/dist/admin/pages/FeatureFlagsPage.js +6 -4
- package/dist/admin/pages/FeatureFlagsPage.js.map +1 -1
- package/dist/admin/pages/OrganizationsListPage.js +8 -6
- package/dist/admin/pages/OrganizationsListPage.js.map +1 -1
- package/dist/admin/pages/ProductsPage.js +10 -7
- package/dist/admin/pages/ProductsPage.js.map +1 -1
- package/dist/admin/pages/ReferralsPage.js +27 -23
- package/dist/admin/pages/ReferralsPage.js.map +1 -1
- package/dist/admin/pages/RolesPermissionsPage.js +11 -8
- package/dist/admin/pages/RolesPermissionsPage.js.map +1 -1
- package/dist/admin/pages/SREPage.js +12 -8
- package/dist/admin/pages/SREPage.js.map +1 -1
- package/dist/admin/pages/SandboxSettingsPage.js +21 -20
- package/dist/admin/pages/SandboxSettingsPage.js.map +1 -1
- package/dist/admin/pages/SettingsPage.js +154 -142
- package/dist/admin/pages/SettingsPage.js.map +1 -1
- package/dist/admin/pages/SyncFailuresPage.js +14 -11
- package/dist/admin/pages/SyncFailuresPage.js.map +1 -1
- package/dist/admin/pages/TenantDetailPage.js +22 -22
- package/dist/admin/pages/TenantDetailPage.js.map +1 -1
- package/dist/admin/pages/TenantFormRoutePage.js +2 -2
- package/dist/admin/pages/TenantFormRoutePage.js.map +1 -1
- package/dist/admin/pages/TenantsListPage.js +7 -5
- package/dist/admin/pages/TenantsListPage.js.map +1 -1
- package/dist/admin/pages/UsersListPage.js +8 -6
- package/dist/admin/pages/UsersListPage.js.map +1 -1
- package/dist/admin/pages/WaitlistPage.js +9 -7
- package/dist/admin/pages/WaitlistPage.js.map +1 -1
- package/dist/admin/pages/WorkflowsPage.js +6 -3
- package/dist/admin/pages/WorkflowsPage.js.map +1 -1
- package/dist/admin/pages/WorkspacesListPage.js +9 -8
- package/dist/admin/pages/WorkspacesListPage.js.map +1 -1
- package/package.json +2 -2
|
@@ -83,6 +83,7 @@ function T() {
|
|
|
83
83
|
denyMessage: "You don't have permission to view audit logs.",
|
|
84
84
|
children: /* @__PURE__ */ y(r, {
|
|
85
85
|
title: "Audit Logs",
|
|
86
|
+
gradientWord: "Logs",
|
|
86
87
|
description: `Security and compliance audit trail${N ? ` · ${N} total` : ""}`,
|
|
87
88
|
actions: /* @__PURE__ */ y(x, {
|
|
88
89
|
label: "Export CSV",
|
|
@@ -107,7 +108,7 @@ function T() {
|
|
|
107
108
|
className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between",
|
|
108
109
|
children: [/* @__PURE__ */ b("div", {
|
|
109
110
|
className: "relative max-w-sm flex-1",
|
|
110
|
-
children: [/* @__PURE__ */ y(h, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-
|
|
111
|
+
children: [/* @__PURE__ */ y(h, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ y(n, {
|
|
111
112
|
placeholder: "Search by action, actor, or resource...",
|
|
112
113
|
value: v,
|
|
113
114
|
onChange: (e) => C(e.target.value),
|
|
@@ -123,7 +124,7 @@ function T() {
|
|
|
123
124
|
})]
|
|
124
125
|
}),
|
|
125
126
|
A && /* @__PURE__ */ b("div", {
|
|
126
|
-
className: "rounded-md border border-
|
|
127
|
+
className: "rounded-md border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text",
|
|
127
128
|
children: ["Failed to load audit logs: ", A.message]
|
|
128
129
|
}),
|
|
129
130
|
/* @__PURE__ */ y(a, {
|
|
@@ -134,10 +135,10 @@ function T() {
|
|
|
134
135
|
render: (e) => /* @__PURE__ */ b("div", {
|
|
135
136
|
className: "flex items-center gap-2",
|
|
136
137
|
children: [/* @__PURE__ */ y("span", {
|
|
137
|
-
className: "text-
|
|
138
|
+
className: "text-text-secondary",
|
|
138
139
|
children: w(e.action)
|
|
139
140
|
}), /* @__PURE__ */ y("span", {
|
|
140
|
-
className: "font-mono text-sm font-medium text-
|
|
141
|
+
className: "font-mono text-sm font-medium text-text-primary",
|
|
141
142
|
children: e.action
|
|
142
143
|
})]
|
|
143
144
|
})
|
|
@@ -152,7 +153,7 @@ function T() {
|
|
|
152
153
|
className: "truncate text-sm",
|
|
153
154
|
children: e.actorId
|
|
154
155
|
}), /* @__PURE__ */ y("div", {
|
|
155
|
-
className: "text-xs text-
|
|
156
|
+
className: "text-xs text-text-secondary capitalize",
|
|
156
157
|
children: e.actorType
|
|
157
158
|
})]
|
|
158
159
|
})
|
|
@@ -167,7 +168,7 @@ function T() {
|
|
|
167
168
|
className: "truncate text-sm",
|
|
168
169
|
children: e.resource
|
|
169
170
|
}), e.resourceId && /* @__PURE__ */ b("div", {
|
|
170
|
-
className: "truncate text-xs text-
|
|
171
|
+
className: "truncate text-xs text-text-secondary",
|
|
171
172
|
children: ["#", String(e.resourceId).slice(-8)]
|
|
172
173
|
})]
|
|
173
174
|
})
|
|
@@ -177,7 +178,7 @@ function T() {
|
|
|
177
178
|
header: "Tenant",
|
|
178
179
|
width: "130px",
|
|
179
180
|
render: (e) => /* @__PURE__ */ y("span", {
|
|
180
|
-
className: "text-xs text-
|
|
181
|
+
className: "text-xs text-text-secondary",
|
|
181
182
|
children: e.tenantId ? `#${e.tenantId.slice(-8)}` : "—"
|
|
182
183
|
})
|
|
183
184
|
},
|
|
@@ -186,7 +187,7 @@ function T() {
|
|
|
186
187
|
header: "IP",
|
|
187
188
|
width: "120px",
|
|
188
189
|
render: (e) => /* @__PURE__ */ y("span", {
|
|
189
|
-
className: "font-mono text-xs text-
|
|
190
|
+
className: "font-mono text-xs text-text-secondary",
|
|
190
191
|
children: e.ipAddress ?? "—"
|
|
191
192
|
})
|
|
192
193
|
},
|
|
@@ -201,6 +202,7 @@ function T() {
|
|
|
201
202
|
keyExtractor: (e) => e.id,
|
|
202
203
|
loading: k,
|
|
203
204
|
emptyState: /* @__PURE__ */ y(t, {
|
|
205
|
+
illustration: "empty-files",
|
|
204
206
|
icon: /* @__PURE__ */ y(p, { className: "h-8 w-8" }),
|
|
205
207
|
title: "No audit logs found",
|
|
206
208
|
description: "Audit events will appear here when actions are performed"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuditLogsPage.js","names":[],"sources":["../../../src/admin/pages/AuditLogsPage.tsx"],"sourcesContent":["import { useState, useMemo, useCallback } from 'react';\nimport { FileText, Search, Download, User, Settings, Shield, Database, Key } from 'lucide-react';\nimport { SaveToFilesButton } from '@burdenoff/fe-libs/storage';\nimport {\n PageLayout,\n Input,\n DataTable,\n Pagination,\n EmptyState,\n Button,\n PermissionGuard,\n} from '../components';\nimport { useAdminPermissions } from '../hooks/useAdminPermissions';\nimport { formatRelativeTime } from '../utils/formatters';\nimport { useListAuditLogsQuery } from '../../generated/global-operations';\n\nconst PAGE_SIZE = 20;\n\nconst ACTION_ICONS: Record<string, React.ReactNode> = {\n auth: <User className=\"h-3 w-3\" />,\n user: <User className=\"h-3 w-3\" />,\n settings: <Settings className=\"h-3 w-3\" />,\n rbac: <Shield className=\"h-3 w-3\" />,\n data: <Database className=\"h-3 w-3\" />,\n api: <Key className=\"h-3 w-3\" />,\n};\n\nfunction getActionIcon(action: string) {\n const prefix = action.split('.')[0];\n return ACTION_ICONS[prefix] ?? <FileText className=\"h-3 w-3\" />;\n}\n\n/**\n * Audit logs page — connected to real backend\n */\nexport function AuditLogsPage() {\n const { canViewAuditLogs, isLoading: permissionsLoading } = useAdminPermissions();\n\n const [page, setPage] = useState(1);\n const [search, setSearch] = useState('');\n const [resourceFilter, setResourceFilter] = useState('');\n\n const skipQueries = permissionsLoading || !canViewAuditLogs;\n\n const { data, loading, error } = useListAuditLogsQuery({\n variables: {\n resource: resourceFilter || undefined,\n limit: PAGE_SIZE,\n offset: (page - 1) * PAGE_SIZE,\n },\n fetchPolicy: 'cache-and-network',\n skip: skipQueries,\n });\n\n const auditLogs = useMemo(() => {\n const edges = data?.auditLogs?.edges ?? [];\n return edges.map((e: (typeof edges)[number]) => e.node);\n }, [data]);\n\n type AuditLogNode = (typeof auditLogs)[number];\n\n const filteredLogs = useMemo(() => {\n if (!search) return auditLogs;\n const q = search.toLowerCase();\n return auditLogs.filter(\n (log: AuditLogNode) =>\n log.action.toLowerCase().includes(q) ||\n log.actorId.toLowerCase().includes(q) ||\n log.resource.toLowerCase().includes(q) ||\n log.tenantId.toLowerCase().includes(q)\n );\n }, [auditLogs, search]);\n\n const total = data?.auditLogs?.totalCount ?? 0;\n const totalPages = Math.ceil(total / PAGE_SIZE);\n\n const buildAuditCsvBlob = useCallback((): Blob | null => {\n const rows = filteredLogs;\n if (rows.length === 0) return null;\n\n const headers = [\n 'id',\n 'action',\n 'actorId',\n 'actorType',\n 'resource',\n 'resourceId',\n 'tenantId',\n 'ipAddress',\n 'userAgent',\n 'changes',\n 'metadata',\n 'createdAt',\n ];\n\n const escape = (value: unknown): string => {\n if (value === null || value === undefined) return '';\n const str = typeof value === 'object' ? JSON.stringify(value) : String(value);\n // RFC 4180: wrap in quotes if it contains quote, comma, or newline; double internal quotes\n if (/[\",\\n\\r]/.test(str)) {\n return `\"${str.replace(/\"/g, '\"\"')}\"`;\n }\n return str;\n };\n\n const csvLines = [\n headers.join(','),\n ...rows.map((log: AuditLogNode) =>\n [\n escape(log.id),\n escape(log.action),\n escape(log.actorId),\n escape(log.actorType),\n escape(log.resource),\n escape(log.resourceId),\n escape(log.tenantId),\n escape(log.ipAddress),\n escape((log as { userAgent?: unknown }).userAgent),\n escape((log as { changes?: unknown }).changes),\n escape((log as { metadata?: unknown }).metadata),\n escape(log.createdAt),\n ].join(',')\n ),\n ];\n\n return new Blob([csvLines.join('\\n')], { type: 'text/csv;charset=utf-8;' });\n }, [filteredLogs]);\n\n const exportToCsv = useCallback(() => {\n const blob = buildAuditCsvBlob();\n if (!blob) return;\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n const stamp = new Date().toISOString().replace(/[:.]/g, '-');\n link.href = url;\n link.setAttribute('download', `audit-logs-${stamp}.csv`);\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n }, [buildAuditCsvBlob]);\n\n const columns = [\n {\n key: 'action',\n header: 'Action',\n render: (log: (typeof auditLogs)[number]) => (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-muted-foreground\">{getActionIcon(log.action)}</span>\n <span className=\"font-mono text-sm font-medium text-foreground\">{log.action}</span>\n </div>\n ),\n },\n {\n key: 'actorId',\n header: 'Actor',\n width: '180px',\n render: (log: (typeof auditLogs)[number]) => (\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm\">{log.actorId}</div>\n <div className=\"text-xs text-muted-foreground capitalize\">{log.actorType}</div>\n </div>\n ),\n },\n {\n key: 'resource',\n header: 'Resource',\n width: '150px',\n render: (log: (typeof auditLogs)[number]) => (\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm\">{log.resource}</div>\n {log.resourceId && (\n <div className=\"truncate text-xs text-muted-foreground\">\n #{String(log.resourceId).slice(-8)}\n </div>\n )}\n </div>\n ),\n },\n {\n key: 'tenantId',\n header: 'Tenant',\n width: '130px',\n render: (log: (typeof auditLogs)[number]) => (\n <span className=\"text-xs text-muted-foreground\">\n {log.tenantId ? `#${log.tenantId.slice(-8)}` : '—'}\n </span>\n ),\n },\n {\n key: 'ipAddress',\n header: 'IP',\n width: '120px',\n render: (log: (typeof auditLogs)[number]) => (\n <span className=\"font-mono text-xs text-muted-foreground\">{log.ipAddress ?? '—'}</span>\n ),\n },\n {\n key: 'createdAt',\n header: 'Time',\n width: '110px',\n render: (log: (typeof auditLogs)[number]) => formatRelativeTime(log.createdAt),\n },\n ];\n\n return (\n <PermissionGuard\n allowed={canViewAuditLogs}\n isLoading={permissionsLoading}\n pageTitle=\"Audit Logs\"\n denyMessage=\"You don't have permission to view audit logs.\"\n >\n <PageLayout\n title=\"Audit Logs\"\n description={`Security and compliance audit trail${total ? ` · ${total} total` : ''}`}\n actions={\n <SaveToFilesButton\n label=\"Export CSV\"\n disabled={loading || filteredLogs.length === 0}\n testId=\"audit-logs-export\"\n defaultFolderPath=\"/Admin/Audit Logs\"\n source={{\n kind: 'blob',\n filename: `audit-logs-${new Date().toISOString().replace(/[:.]/g, '-')}.csv`,\n mimeType: 'text/csv',\n getBlob: async () => {\n const blob = buildAuditCsvBlob();\n return blob ? { blob } : null;\n },\n }}\n onDownload={exportToCsv}\n />\n }\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"relative max-w-sm flex-1\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground\" />\n <Input\n placeholder=\"Search by action, actor, or resource...\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className=\"pl-9\"\n />\n </div>\n <Input\n placeholder=\"Filter by resource (e.g. tenant, user)...\"\n value={resourceFilter}\n onChange={(e) => {\n setPage(1);\n setResourceFilter(e.target.value);\n }}\n className=\"max-w-xs\"\n />\n </div>\n\n {error && (\n <div className=\"rounded-md border border-destructive/50 bg-destructive/10 p-4 text-sm text-destructive\">\n Failed to load audit logs: {error.message}\n </div>\n )}\n\n <DataTable\n columns={columns}\n data={filteredLogs}\n keyExtractor={(l) => l.id}\n loading={loading}\n emptyState={\n <EmptyState\n icon={<FileText className=\"h-8 w-8\" />}\n title=\"No audit logs found\"\n description=\"Audit events will appear here when actions are performed\"\n />\n }\n />\n\n {total > PAGE_SIZE && (\n <Pagination\n page={page}\n totalPages={totalPages}\n totalItems={total}\n pageSize={PAGE_SIZE}\n onPageChange={setPage}\n />\n )}\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AuditLogsPage.js","names":[],"sources":["../../../src/admin/pages/AuditLogsPage.tsx"],"sourcesContent":["import { useState, useMemo, useCallback } from 'react';\nimport { FileText, Search, Download, User, Settings, Shield, Database, Key } from 'lucide-react';\nimport { SaveToFilesButton } from '@burdenoff/fe-libs/storage';\nimport {\n PageLayout,\n Input,\n DataTable,\n Pagination,\n EmptyState,\n Button,\n PermissionGuard,\n} from '../components';\nimport { useAdminPermissions } from '../hooks/useAdminPermissions';\nimport { formatRelativeTime } from '../utils/formatters';\nimport { useListAuditLogsQuery } from '../../generated/global-operations';\n\nconst PAGE_SIZE = 20;\n\nconst ACTION_ICONS: Record<string, React.ReactNode> = {\n auth: <User className=\"h-3 w-3\" />,\n user: <User className=\"h-3 w-3\" />,\n settings: <Settings className=\"h-3 w-3\" />,\n rbac: <Shield className=\"h-3 w-3\" />,\n data: <Database className=\"h-3 w-3\" />,\n api: <Key className=\"h-3 w-3\" />,\n};\n\nfunction getActionIcon(action: string) {\n const prefix = action.split('.')[0];\n return ACTION_ICONS[prefix] ?? <FileText className=\"h-3 w-3\" />;\n}\n\n/**\n * Audit logs page — connected to real backend\n */\nexport function AuditLogsPage() {\n const { canViewAuditLogs, isLoading: permissionsLoading } = useAdminPermissions();\n\n const [page, setPage] = useState(1);\n const [search, setSearch] = useState('');\n const [resourceFilter, setResourceFilter] = useState('');\n\n const skipQueries = permissionsLoading || !canViewAuditLogs;\n\n const { data, loading, error } = useListAuditLogsQuery({\n variables: {\n resource: resourceFilter || undefined,\n limit: PAGE_SIZE,\n offset: (page - 1) * PAGE_SIZE,\n },\n fetchPolicy: 'cache-and-network',\n skip: skipQueries,\n });\n\n const auditLogs = useMemo(() => {\n const edges = data?.auditLogs?.edges ?? [];\n return edges.map((e: (typeof edges)[number]) => e.node);\n }, [data]);\n\n type AuditLogNode = (typeof auditLogs)[number];\n\n const filteredLogs = useMemo(() => {\n if (!search) return auditLogs;\n const q = search.toLowerCase();\n return auditLogs.filter(\n (log: AuditLogNode) =>\n log.action.toLowerCase().includes(q) ||\n log.actorId.toLowerCase().includes(q) ||\n log.resource.toLowerCase().includes(q) ||\n log.tenantId.toLowerCase().includes(q)\n );\n }, [auditLogs, search]);\n\n const total = data?.auditLogs?.totalCount ?? 0;\n const totalPages = Math.ceil(total / PAGE_SIZE);\n\n const buildAuditCsvBlob = useCallback((): Blob | null => {\n const rows = filteredLogs;\n if (rows.length === 0) return null;\n\n const headers = [\n 'id',\n 'action',\n 'actorId',\n 'actorType',\n 'resource',\n 'resourceId',\n 'tenantId',\n 'ipAddress',\n 'userAgent',\n 'changes',\n 'metadata',\n 'createdAt',\n ];\n\n const escape = (value: unknown): string => {\n if (value === null || value === undefined) return '';\n const str = typeof value === 'object' ? JSON.stringify(value) : String(value);\n // RFC 4180: wrap in quotes if it contains quote, comma, or newline; double internal quotes\n if (/[\",\\n\\r]/.test(str)) {\n return `\"${str.replace(/\"/g, '\"\"')}\"`;\n }\n return str;\n };\n\n const csvLines = [\n headers.join(','),\n ...rows.map((log: AuditLogNode) =>\n [\n escape(log.id),\n escape(log.action),\n escape(log.actorId),\n escape(log.actorType),\n escape(log.resource),\n escape(log.resourceId),\n escape(log.tenantId),\n escape(log.ipAddress),\n escape((log as { userAgent?: unknown }).userAgent),\n escape((log as { changes?: unknown }).changes),\n escape((log as { metadata?: unknown }).metadata),\n escape(log.createdAt),\n ].join(',')\n ),\n ];\n\n return new Blob([csvLines.join('\\n')], { type: 'text/csv;charset=utf-8;' });\n }, [filteredLogs]);\n\n const exportToCsv = useCallback(() => {\n const blob = buildAuditCsvBlob();\n if (!blob) return;\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n const stamp = new Date().toISOString().replace(/[:.]/g, '-');\n link.href = url;\n link.setAttribute('download', `audit-logs-${stamp}.csv`);\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n }, [buildAuditCsvBlob]);\n\n const columns = [\n {\n key: 'action',\n header: 'Action',\n render: (log: (typeof auditLogs)[number]) => (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-text-secondary\">{getActionIcon(log.action)}</span>\n <span className=\"font-mono text-sm font-medium text-text-primary\">{log.action}</span>\n </div>\n ),\n },\n {\n key: 'actorId',\n header: 'Actor',\n width: '180px',\n render: (log: (typeof auditLogs)[number]) => (\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm\">{log.actorId}</div>\n <div className=\"text-xs text-text-secondary capitalize\">{log.actorType}</div>\n </div>\n ),\n },\n {\n key: 'resource',\n header: 'Resource',\n width: '150px',\n render: (log: (typeof auditLogs)[number]) => (\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm\">{log.resource}</div>\n {log.resourceId && (\n <div className=\"truncate text-xs text-text-secondary\">\n #{String(log.resourceId).slice(-8)}\n </div>\n )}\n </div>\n ),\n },\n {\n key: 'tenantId',\n header: 'Tenant',\n width: '130px',\n render: (log: (typeof auditLogs)[number]) => (\n <span className=\"text-xs text-text-secondary\">\n {log.tenantId ? `#${log.tenantId.slice(-8)}` : '—'}\n </span>\n ),\n },\n {\n key: 'ipAddress',\n header: 'IP',\n width: '120px',\n render: (log: (typeof auditLogs)[number]) => (\n <span className=\"font-mono text-xs text-text-secondary\">{log.ipAddress ?? '—'}</span>\n ),\n },\n {\n key: 'createdAt',\n header: 'Time',\n width: '110px',\n render: (log: (typeof auditLogs)[number]) => formatRelativeTime(log.createdAt),\n },\n ];\n\n return (\n <PermissionGuard\n allowed={canViewAuditLogs}\n isLoading={permissionsLoading}\n pageTitle=\"Audit Logs\"\n denyMessage=\"You don't have permission to view audit logs.\"\n >\n <PageLayout\n title=\"Audit Logs\"\n gradientWord=\"Logs\"\n description={`Security and compliance audit trail${total ? ` · ${total} total` : ''}`}\n actions={\n <SaveToFilesButton\n label=\"Export CSV\"\n disabled={loading || filteredLogs.length === 0}\n testId=\"audit-logs-export\"\n defaultFolderPath=\"/Admin/Audit Logs\"\n source={{\n kind: 'blob',\n filename: `audit-logs-${new Date().toISOString().replace(/[:.]/g, '-')}.csv`,\n mimeType: 'text/csv',\n getBlob: async () => {\n const blob = buildAuditCsvBlob();\n return blob ? { blob } : null;\n },\n }}\n onDownload={exportToCsv}\n />\n }\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"relative max-w-sm flex-1\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search by action, actor, or resource...\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className=\"pl-9\"\n />\n </div>\n <Input\n placeholder=\"Filter by resource (e.g. tenant, user)...\"\n value={resourceFilter}\n onChange={(e) => {\n setPage(1);\n setResourceFilter(e.target.value);\n }}\n className=\"max-w-xs\"\n />\n </div>\n\n {error && (\n <div className=\"rounded-md border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text\">\n Failed to load audit logs: {error.message}\n </div>\n )}\n\n <DataTable\n columns={columns}\n data={filteredLogs}\n keyExtractor={(l) => l.id}\n loading={loading}\n emptyState={\n <EmptyState\n illustration=\"empty-files\"\n icon={<FileText className=\"h-8 w-8\" />}\n title=\"No audit logs found\"\n description=\"Audit events will appear here when actions are performed\"\n />\n }\n />\n\n {total > PAGE_SIZE && (\n <Pagination\n page={page}\n totalPages={totalPages}\n totalItems={total}\n pageSize={PAGE_SIZE}\n onPageChange={setPage}\n />\n )}\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,68 +1,76 @@
|
|
|
1
|
-
import { Badge as e
|
|
2
|
-
import { PageLayout as
|
|
3
|
-
import { formatCurrency as
|
|
4
|
-
import { MetricCard as
|
|
5
|
-
import { DataTable as
|
|
1
|
+
import { Badge as e } from "../components/ui.js";
|
|
2
|
+
import { PageLayout as t } from "../components/PageLayout.js";
|
|
3
|
+
import { formatCurrency as n, formatDateTime as r } from "../utils/formatters.js";
|
|
4
|
+
import { MetricCard as i, MetricsGrid as a } from "../components/MetricCard.js";
|
|
5
|
+
import { DataTable as o } from "../components/DataTable.js";
|
|
6
6
|
import "../components/index.js";
|
|
7
|
-
import { useListAllPlansQuery as
|
|
8
|
-
import { ArrowUpRight as
|
|
9
|
-
import { jsx as
|
|
7
|
+
import { useListAllPlansQuery as s, useListAllQuotasQuery as c, useListRefundRecordsQuery as l } from "../../generated/global-operations.js";
|
|
8
|
+
import { ArrowUpRight as u, CreditCard as d, Download as f, Plus as p, Receipt as m, Shield as h } from "lucide-react";
|
|
9
|
+
import { jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
10
|
+
import { CTAOverflowMenu as v } from "@burdenoff/fe-libs/ui";
|
|
10
11
|
//#region src/admin/pages/BillingPage.tsx
|
|
11
12
|
function y() {
|
|
12
|
-
let { data: y, loading: b } =
|
|
13
|
+
let { data: y, loading: b } = s(), { data: x, loading: S } = c(), { data: C, loading: w } = l({ variables: {
|
|
13
14
|
page: 1,
|
|
14
15
|
pageSize: 10
|
|
15
16
|
} }), T = y?.getAllPlans || [], E = x?.getAllQuotas || [], D = C?.getRefundRecords?.records || [];
|
|
16
|
-
return /* @__PURE__ */
|
|
17
|
+
return /* @__PURE__ */ g(t, {
|
|
17
18
|
title: "Billing & Subscriptions",
|
|
19
|
+
gradientWord: "Subscriptions",
|
|
18
20
|
description: "Manage platform-wide plans, quotas, and refunds",
|
|
19
|
-
actions: /* @__PURE__ */ v
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
21
|
+
actions: /* @__PURE__ */ g(v, {
|
|
22
|
+
primary: {
|
|
23
|
+
label: "Create Plan",
|
|
24
|
+
icon: /* @__PURE__ */ g(p, { className: "mr-2 h-4 w-4" }),
|
|
25
|
+
onSelect: () => {}
|
|
26
|
+
},
|
|
27
|
+
actions: [{
|
|
28
|
+
label: "Export Audit",
|
|
29
|
+
icon: /* @__PURE__ */ g(f, { className: "h-4 w-4" }),
|
|
30
|
+
onSelect: () => {}
|
|
31
|
+
}]
|
|
25
32
|
}),
|
|
26
|
-
children: /* @__PURE__ */
|
|
33
|
+
children: /* @__PURE__ */ _("div", {
|
|
27
34
|
className: "space-y-8",
|
|
28
35
|
children: [
|
|
29
|
-
/* @__PURE__ */
|
|
30
|
-
/* @__PURE__ */
|
|
36
|
+
/* @__PURE__ */ _(a, { children: [
|
|
37
|
+
/* @__PURE__ */ g(i, {
|
|
31
38
|
title: "Total Revenue (30d)",
|
|
39
|
+
hero: !0,
|
|
32
40
|
value: "$124,500",
|
|
33
|
-
icon: /* @__PURE__ */
|
|
41
|
+
icon: /* @__PURE__ */ g(d, { className: "h-4 w-4" }),
|
|
34
42
|
trend: "up"
|
|
35
43
|
}),
|
|
36
|
-
/* @__PURE__ */
|
|
44
|
+
/* @__PURE__ */ g(i, {
|
|
37
45
|
title: "Active Subscriptions",
|
|
38
46
|
value: "1,248",
|
|
39
|
-
icon: /* @__PURE__ */
|
|
47
|
+
icon: /* @__PURE__ */ g(h, { className: "h-4 w-4" })
|
|
40
48
|
}),
|
|
41
|
-
/* @__PURE__ */
|
|
49
|
+
/* @__PURE__ */ g(i, {
|
|
42
50
|
title: "Pending Refunds",
|
|
43
51
|
value: C?.getRefundRecords?.totalCount ?? 0,
|
|
44
|
-
icon: /* @__PURE__ */
|
|
52
|
+
icon: /* @__PURE__ */ g(u, { className: "h-4 w-4" }),
|
|
45
53
|
priority: "high"
|
|
46
54
|
}),
|
|
47
|
-
/* @__PURE__ */
|
|
55
|
+
/* @__PURE__ */ g(i, {
|
|
48
56
|
title: "Upcoming Invoices",
|
|
49
57
|
value: "42",
|
|
50
|
-
icon: /* @__PURE__ */
|
|
58
|
+
icon: /* @__PURE__ */ g(m, { className: "h-4 w-4" })
|
|
51
59
|
})
|
|
52
60
|
] }),
|
|
53
|
-
/* @__PURE__ */
|
|
61
|
+
/* @__PURE__ */ _("div", {
|
|
54
62
|
className: "grid gap-8 lg:grid-cols-2",
|
|
55
|
-
children: [/* @__PURE__ */
|
|
63
|
+
children: [/* @__PURE__ */ _("div", {
|
|
56
64
|
className: "space-y-4",
|
|
57
|
-
children: [/* @__PURE__ */
|
|
65
|
+
children: [/* @__PURE__ */ g("h2", {
|
|
58
66
|
className: "text-lg font-semibold",
|
|
59
67
|
children: "Platform Plans"
|
|
60
|
-
}), /* @__PURE__ */
|
|
68
|
+
}), /* @__PURE__ */ g(o, {
|
|
61
69
|
columns: [
|
|
62
70
|
{
|
|
63
71
|
key: "name",
|
|
64
72
|
header: "Plan Name",
|
|
65
|
-
render: (e) => /* @__PURE__ */
|
|
73
|
+
render: (e) => /* @__PURE__ */ g("span", {
|
|
66
74
|
className: "font-medium",
|
|
67
75
|
children: e.name
|
|
68
76
|
})
|
|
@@ -70,7 +78,7 @@ function y() {
|
|
|
70
78
|
{
|
|
71
79
|
key: "price",
|
|
72
80
|
header: "Price",
|
|
73
|
-
render: (e) =>
|
|
81
|
+
render: (e) => n(e.price, e.currency)
|
|
74
82
|
},
|
|
75
83
|
{
|
|
76
84
|
key: "duration",
|
|
@@ -79,7 +87,7 @@ function y() {
|
|
|
79
87
|
{
|
|
80
88
|
key: "isActive",
|
|
81
89
|
header: "Status",
|
|
82
|
-
render: (t) => /* @__PURE__ */
|
|
90
|
+
render: (t) => /* @__PURE__ */ g(e, {
|
|
83
91
|
variant: t.isActive ? "success" : "secondary",
|
|
84
92
|
children: t.isActive ? "Active" : "Inactive"
|
|
85
93
|
})
|
|
@@ -89,12 +97,12 @@ function y() {
|
|
|
89
97
|
keyExtractor: (e) => e.id,
|
|
90
98
|
loading: b
|
|
91
99
|
})]
|
|
92
|
-
}), /* @__PURE__ */
|
|
100
|
+
}), /* @__PURE__ */ _("div", {
|
|
93
101
|
className: "space-y-4",
|
|
94
|
-
children: [/* @__PURE__ */
|
|
102
|
+
children: [/* @__PURE__ */ g("h2", {
|
|
95
103
|
className: "text-lg font-semibold",
|
|
96
104
|
children: "Resource Quotas"
|
|
97
|
-
}), /* @__PURE__ */
|
|
105
|
+
}), /* @__PURE__ */ g(o, {
|
|
98
106
|
columns: [
|
|
99
107
|
{
|
|
100
108
|
key: "name",
|
|
@@ -107,7 +115,7 @@ function y() {
|
|
|
107
115
|
{
|
|
108
116
|
key: "isActive",
|
|
109
117
|
header: "Status",
|
|
110
|
-
render: (t) => /* @__PURE__ */
|
|
118
|
+
render: (t) => /* @__PURE__ */ g(e, {
|
|
111
119
|
variant: t.isActive ? "success" : "secondary",
|
|
112
120
|
children: t.isActive ? "Active" : "Inactive"
|
|
113
121
|
})
|
|
@@ -119,22 +127,22 @@ function y() {
|
|
|
119
127
|
})]
|
|
120
128
|
})]
|
|
121
129
|
}),
|
|
122
|
-
/* @__PURE__ */
|
|
130
|
+
/* @__PURE__ */ _("div", {
|
|
123
131
|
className: "space-y-4",
|
|
124
|
-
children: [/* @__PURE__ */
|
|
132
|
+
children: [/* @__PURE__ */ g("h2", {
|
|
125
133
|
className: "text-lg font-semibold",
|
|
126
134
|
children: "Recent Refund Records"
|
|
127
|
-
}), /* @__PURE__ */
|
|
135
|
+
}), /* @__PURE__ */ g(o, {
|
|
128
136
|
columns: [
|
|
129
137
|
{
|
|
130
138
|
key: "amount",
|
|
131
139
|
header: "Amount",
|
|
132
|
-
render: (e) =>
|
|
140
|
+
render: (e) => n(e.amount, e.currency)
|
|
133
141
|
},
|
|
134
142
|
{
|
|
135
143
|
key: "status",
|
|
136
144
|
header: "Status",
|
|
137
|
-
render: (t) => /* @__PURE__ */
|
|
145
|
+
render: (t) => /* @__PURE__ */ g(e, {
|
|
138
146
|
variant: t.status === "COMPLETED" ? "success" : "warning",
|
|
139
147
|
children: t.status
|
|
140
148
|
})
|
|
@@ -146,7 +154,7 @@ function y() {
|
|
|
146
154
|
{
|
|
147
155
|
key: "createdAt",
|
|
148
156
|
header: "Date",
|
|
149
|
-
render: (e) =>
|
|
157
|
+
render: (e) => r(e.createdAt)
|
|
150
158
|
}
|
|
151
159
|
],
|
|
152
160
|
data: D,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BillingPage.js","names":[],"sources":["../../../src/admin/pages/BillingPage.tsx"],"sourcesContent":["import { CreditCard, Plus, ArrowUpRight, Shield, Receipt, Download } from 'lucide-react';\nimport { PageLayout,
|
|
1
|
+
{"version":3,"file":"BillingPage.js","names":[],"sources":["../../../src/admin/pages/BillingPage.tsx"],"sourcesContent":["import { CreditCard, Plus, ArrowUpRight, Shield, Receipt, Download } from 'lucide-react';\nimport { CTAOverflowMenu } from '@burdenoff/fe-libs/ui';\nimport { PageLayout, DataTable, Badge, MetricCard, MetricsGrid } from '../components';\nimport {\n useListAllPlansQuery,\n useListAllQuotasQuery,\n useListRefundRecordsQuery,\n} from '../../generated/global-operations';\nimport { formatCurrency, formatDateTime } from '../utils/formatters';\n\nexport function BillingPage() {\n const { data: plansData, loading: plansLoading } = useListAllPlansQuery();\n const { data: quotasData, loading: quotasLoading } = useListAllQuotasQuery();\n const { data: refundsData, loading: refundsLoading } = useListRefundRecordsQuery({\n variables: { page: 1, pageSize: 10 },\n });\n\n const plans = plansData?.getAllPlans || [];\n const quotas = quotasData?.getAllQuotas || [];\n const refunds = refundsData?.getRefundRecords?.records || [];\n\n const planColumns = [\n {\n key: 'name',\n header: 'Plan Name',\n render: (plan: any) => <span className=\"font-medium\">{plan.name}</span>,\n },\n {\n key: 'price',\n header: 'Price',\n render: (plan: any) => formatCurrency(plan.price, plan.currency),\n },\n {\n key: 'duration',\n header: 'Interval',\n },\n {\n key: 'isActive',\n header: 'Status',\n render: (plan: any) => (\n <Badge variant={plan.isActive ? 'success' : 'secondary'}>\n {plan.isActive ? 'Active' : 'Inactive'}\n </Badge>\n ),\n },\n ];\n\n const quotaColumns = [\n {\n key: 'name',\n header: 'Quota Name',\n },\n {\n key: 'quotaType',\n header: 'Metric Type',\n },\n {\n key: 'isActive',\n header: 'Status',\n render: (quota: any) => (\n <Badge variant={quota.isActive ? 'success' : 'secondary'}>\n {quota.isActive ? 'Active' : 'Inactive'}\n </Badge>\n ),\n },\n ];\n\n const refundColumns = [\n {\n key: 'amount',\n header: 'Amount',\n render: (refund: any) => formatCurrency(refund.amount, refund.currency),\n },\n {\n key: 'status',\n header: 'Status',\n render: (refund: any) => (\n <Badge variant={refund.status === 'COMPLETED' ? 'success' : 'warning'}>\n {refund.status}\n </Badge>\n ),\n },\n {\n key: 'reason',\n header: 'Reason',\n },\n {\n key: 'createdAt',\n header: 'Date',\n render: (refund: any) => formatDateTime(refund.createdAt),\n },\n ];\n\n return (\n <PageLayout\n title=\"Billing & Subscriptions\"\n gradientWord=\"Subscriptions\"\n description=\"Manage platform-wide plans, quotas, and refunds\"\n actions={\n <CTAOverflowMenu\n primary={{\n label: 'Create Plan',\n icon: <Plus className=\"mr-2 h-4 w-4\" />,\n onSelect: () => {},\n }}\n actions={[\n {\n label: 'Export Audit',\n icon: <Download className=\"h-4 w-4\" />,\n onSelect: () => {},\n },\n ]}\n />\n }\n >\n <div className=\"space-y-8\">\n <MetricsGrid>\n <MetricCard\n title=\"Total Revenue (30d)\"\n hero\n value=\"$124,500\"\n icon={<CreditCard className=\"h-4 w-4\" />}\n trend=\"up\"\n />\n <MetricCard\n title=\"Active Subscriptions\"\n value=\"1,248\"\n icon={<Shield className=\"h-4 w-4\" />}\n />\n <MetricCard\n title=\"Pending Refunds\"\n value={refundsData?.getRefundRecords?.totalCount ?? 0}\n icon={<ArrowUpRight className=\"h-4 w-4\" />}\n priority=\"high\"\n />\n <MetricCard title=\"Upcoming Invoices\" value=\"42\" icon={<Receipt className=\"h-4 w-4\" />} />\n </MetricsGrid>\n\n <div className=\"grid gap-8 lg:grid-cols-2\">\n <div className=\"space-y-4\">\n <h2 className=\"text-lg font-semibold\">Platform Plans</h2>\n <DataTable\n columns={planColumns}\n data={plans}\n keyExtractor={(p) => p.id}\n loading={plansLoading}\n />\n </div>\n <div className=\"space-y-4\">\n <h2 className=\"text-lg font-semibold\">Resource Quotas</h2>\n <DataTable\n columns={quotaColumns}\n data={quotas}\n keyExtractor={(q) => q.id}\n loading={quotasLoading}\n />\n </div>\n </div>\n\n <div className=\"space-y-4\">\n <h2 className=\"text-lg font-semibold\">Recent Refund Records</h2>\n <DataTable\n columns={refundColumns}\n data={refunds}\n keyExtractor={(r) => r.id}\n loading={refundsLoading}\n />\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -18,6 +18,7 @@ function b() {
|
|
|
18
18
|
};
|
|
19
19
|
return /* @__PURE__ */ v(r, {
|
|
20
20
|
title: "Communications",
|
|
21
|
+
gradientWord: "Communications",
|
|
21
22
|
description: "Manage platform newsletters and system notifications",
|
|
22
23
|
actions: /* @__PURE__ */ y(t, { children: [/* @__PURE__ */ v(h, { className: "mr-2 h-4 w-4" }), "Create Newsletter"] }),
|
|
23
24
|
children: /* @__PURE__ */ y("div", {
|
|
@@ -25,6 +26,7 @@ function b() {
|
|
|
25
26
|
children: [/* @__PURE__ */ y(s, { children: [
|
|
26
27
|
/* @__PURE__ */ v(o, {
|
|
27
28
|
title: "Total Subscribers",
|
|
29
|
+
hero: !0,
|
|
28
30
|
value: E?.totalSubscribers ?? 0,
|
|
29
31
|
loading: x,
|
|
30
32
|
icon: /* @__PURE__ */ v(_, { className: "h-4 w-4" })
|
|
@@ -60,10 +62,10 @@ function b() {
|
|
|
60
62
|
render: (e) => /* @__PURE__ */ y("div", {
|
|
61
63
|
className: "flex flex-col",
|
|
62
64
|
children: [/* @__PURE__ */ v("span", {
|
|
63
|
-
className: "font-medium text-
|
|
65
|
+
className: "font-medium text-text-primary",
|
|
64
66
|
children: e.title
|
|
65
67
|
}), /* @__PURE__ */ v("span", {
|
|
66
|
-
className: "text-xs text-
|
|
68
|
+
className: "text-xs text-text-secondary",
|
|
67
69
|
children: e.subject
|
|
68
70
|
})]
|
|
69
71
|
})
|
|
@@ -103,6 +105,7 @@ function b() {
|
|
|
103
105
|
keyExtractor: (e) => e.id,
|
|
104
106
|
loading: C,
|
|
105
107
|
emptyState: /* @__PURE__ */ v(n, {
|
|
108
|
+
illustration: "empty-notifications",
|
|
106
109
|
icon: /* @__PURE__ */ v(p, { className: "h-8 w-8" }),
|
|
107
110
|
title: "No newsletters found",
|
|
108
111
|
description: "Create your first newsletter to communicate with users"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommunicationsPage.js","names":[],"sources":["../../../src/admin/pages/CommunicationsPage.tsx"],"sourcesContent":["import { Mail, Plus, Send, Users, Eye, MousePointer2 } from 'lucide-react';\nimport {\n PageLayout,\n Button,\n DataTable,\n Badge,\n MetricCard,\n MetricsGrid,\n EmptyState,\n} from '../components';\nimport {\n useListNewslettersQuery,\n useGetNewsletterStatsQuery,\n useSendNewsletterMutation,\n} from '../../generated/global-operations';\nimport { formatDateTime, formatPercentage } from '../utils/formatters';\n\nexport function CommunicationsPage() {\n const { data: statsData, loading: statsLoading } = useGetNewsletterStatsQuery();\n const {\n data: listData,\n loading: listLoading,\n refetch,\n } = useListNewslettersQuery({\n variables: { limit: 50 },\n });\n\n const [sendNewsletter] = useSendNewsletterMutation();\n\n const stats = statsData?.newsletterStats;\n const newsletters = listData?.newsletters || [];\n\n const handleSend = async (id: string) => {\n if (confirm('Are you sure you want to send this newsletter to all subscribers?')) {\n try {\n await sendNewsletter({ variables: { id } });\n refetch();\n } catch (err) {\n console.error('Failed to send newsletter:', err);\n }\n }\n };\n\n const columns = [\n {\n key: 'title',\n header: 'Title',\n render: (news: any) => (\n <div className=\"flex flex-col\">\n <span className=\"font-medium text-
|
|
1
|
+
{"version":3,"file":"CommunicationsPage.js","names":[],"sources":["../../../src/admin/pages/CommunicationsPage.tsx"],"sourcesContent":["import { Mail, Plus, Send, Users, Eye, MousePointer2 } from 'lucide-react';\nimport {\n PageLayout,\n Button,\n DataTable,\n Badge,\n MetricCard,\n MetricsGrid,\n EmptyState,\n} from '../components';\nimport {\n useListNewslettersQuery,\n useGetNewsletterStatsQuery,\n useSendNewsletterMutation,\n} from '../../generated/global-operations';\nimport { formatDateTime, formatPercentage } from '../utils/formatters';\n\nexport function CommunicationsPage() {\n const { data: statsData, loading: statsLoading } = useGetNewsletterStatsQuery();\n const {\n data: listData,\n loading: listLoading,\n refetch,\n } = useListNewslettersQuery({\n variables: { limit: 50 },\n });\n\n const [sendNewsletter] = useSendNewsletterMutation();\n\n const stats = statsData?.newsletterStats;\n const newsletters = listData?.newsletters || [];\n\n const handleSend = async (id: string) => {\n if (confirm('Are you sure you want to send this newsletter to all subscribers?')) {\n try {\n await sendNewsletter({ variables: { id } });\n refetch();\n } catch (err) {\n console.error('Failed to send newsletter:', err);\n }\n }\n };\n\n const columns = [\n {\n key: 'title',\n header: 'Title',\n render: (news: any) => (\n <div className=\"flex flex-col\">\n <span className=\"font-medium text-text-primary\">{news.title}</span>\n <span className=\"text-xs text-text-secondary\">{news.subject}</span>\n </div>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n render: (news: any) => {\n const variants: Record<\n string,\n 'default' | 'secondary' | 'success' | 'warning' | 'destructive' | 'outline'\n > = {\n SENT: 'success',\n SCHEDULED: 'warning',\n DRAFT: 'secondary',\n };\n return <Badge variant={variants[news.status] || 'outline'}>{news.status}</Badge>;\n },\n },\n {\n key: 'sentAt',\n header: 'Sent Date',\n render: (news: any) => (news.sentAt ? formatDateTime(news.sentAt) : '-'),\n },\n {\n key: 'actions',\n header: '',\n align: 'right' as const,\n render: (news: any) => (\n <div className=\"flex justify-end gap-2\">\n {news.status === 'DRAFT' && (\n <Button size=\"sm\" onClick={() => handleSend(news.id)}>\n <Send className=\"mr-2 h-3 w-3\" />\n Send Now\n </Button>\n )}\n </div>\n ),\n },\n ];\n\n return (\n <PageLayout\n title=\"Communications\"\n gradientWord=\"Communications\"\n description=\"Manage platform newsletters and system notifications\"\n actions={\n <Button>\n <Plus className=\"mr-2 h-4 w-4\" />\n Create Newsletter\n </Button>\n }\n >\n <div className=\"space-y-8\">\n <MetricsGrid>\n <MetricCard\n title=\"Total Subscribers\"\n hero\n value={stats?.totalSubscribers ?? 0}\n loading={statsLoading}\n icon={<Users className=\"h-4 w-4\" />}\n />\n <MetricCard\n title=\"Newsletters Sent\"\n value={stats?.totalSent ?? 0}\n loading={statsLoading}\n icon={<Send className=\"h-4 w-4\" />}\n />\n <MetricCard\n title=\"Avg. Open Rate\"\n value={formatPercentage(stats?.averageOpenRate ?? 0)}\n loading={statsLoading}\n icon={<Eye className=\"h-4 w-4\" />}\n />\n <MetricCard\n title=\"Avg. Click Rate\"\n value={formatPercentage(stats?.averageClickRate ?? 0)}\n loading={statsLoading}\n icon={<MousePointer2 className=\"h-4 w-4\" />}\n />\n </MetricsGrid>\n\n <div className=\"space-y-4\">\n <h2 className=\"text-lg font-semibold\">Newsletters</h2>\n <DataTable\n columns={columns}\n data={newsletters}\n keyExtractor={(n) => n.id}\n loading={listLoading}\n emptyState={\n <EmptyState\n illustration=\"empty-notifications\"\n icon={<Mail className=\"h-8 w-8\" />}\n title=\"No newsletters found\"\n description=\"Create your first newsletter to communicate with users\"\n />\n }\n />\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;AAiBA,SAAgB,IAAqB;CACnC,IAAM,EAAE,MAAM,GAAW,SAAS,MAAiB,GAA4B,EACzE,EACJ,MAAM,GACN,SAAS,GACT,eACE,EAAwB,EAC1B,WAAW,EAAE,OAAO,IAAI,EACzB,CAAC,EAEI,CAAC,KAAkB,GAA2B,EAE9C,IAAQ,GAAW,iBACnB,IAAc,GAAU,eAAe,EAAE,EAEzC,IAAa,OAAO,MAAe;AACvC,MAAI,QAAQ,oEAAoE,CAC9E,KAAI;AAEF,GADA,MAAM,EAAe,EAAE,WAAW,EAAE,OAAI,EAAE,CAAC,EAC3C,GAAS;WACF,GAAK;AACZ,WAAQ,MAAM,8BAA8B,EAAI;;;AAqDtD,QACE,kBAAC,GAAD;EACE,OAAM;EACN,cAAa;EACb,aAAY;EACZ,SACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAAA,oBAE1B,EAAA,CAAA;YAGX,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD,EAAA,UAAA;IACE,kBAAC,GAAD;KACE,OAAM;KACN,MAAA;KACA,OAAO,GAAO,oBAAoB;KAClC,SAAS;KACT,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;KACnC,CAAA;IACF,kBAAC,GAAD;KACE,OAAM;KACN,OAAO,GAAO,aAAa;KAC3B,SAAS;KACT,MAAM,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;KAClC,CAAA;IACF,kBAAC,GAAD;KACE,OAAM;KACN,OAAO,EAAiB,GAAO,mBAAmB,EAAE;KACpD,SAAS;KACT,MAAM,kBAAC,GAAD,EAAK,WAAU,WAAY,CAAA;KACjC,CAAA;IACF,kBAAC,GAAD;KACE,OAAM;KACN,OAAO,EAAiB,GAAO,oBAAoB,EAAE;KACrD,SAAS;KACT,MAAM,kBAAC,GAAD,EAAe,WAAU,WAAY,CAAA;KAC3C,CAAA;IACU,EAAA,CAAA,EAEd,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eAAwB;KAAgB,CAAA,EACtD,kBAAC,GAAD;KACW,SA5FH;MACd;OACE,KAAK;OACL,QAAQ;OACR,SAAS,MACP,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAAiC,EAAK;SAAa,CAAA,EACnE,kBAAC,QAAD;SAAM,WAAU;mBAA+B,EAAK;SAAe,CAAA,CAC/D;;OAET;MACD;OACE,KAAK;OACL,QAAQ;OACR,SAAS,MASA,kBAAC,GAAD;QAAO,SALV;SACF,MAAM;SACN,WAAW;SACX,OAAO;SACR,CAC+B,EAAK,WAAW;kBAAY,EAAK;QAAe,CAAA;OAEnF;MACD;OACE,KAAK;OACL,QAAQ;OACR,SAAS,MAAe,EAAK,SAAS,EAAe,EAAK,OAAO,GAAG;OACrE;MACD;OACE,KAAK;OACL,QAAQ;OACR,OAAO;OACP,SAAS,MACP,kBAAC,OAAD;QAAK,WAAU;kBACZ,EAAK,WAAW,WACf,kBAAC,GAAD;SAAQ,MAAK;SAAK,eAAe,EAAW,EAAK,GAAG;mBAApD,CACE,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAAA,WAE1B;;QAEP,CAAA;OAET;MACF;KA+CS,MAAM;KACN,eAAe,MAAM,EAAE;KACvB,SAAS;KACT,YACE,kBAAC,GAAD;MACE,cAAa;MACb,MAAM,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;MAClC,OAAM;MACN,aAAY;MACZ,CAAA;KAEJ,CAAA,CACE;MACF;;EACK,CAAA"}
|
|
@@ -347,6 +347,7 @@ function b() {
|
|
|
347
347
|
children: [
|
|
348
348
|
/* @__PURE__ */ d(ee, {
|
|
349
349
|
title: "Content",
|
|
350
|
+
gradientWord: "Content",
|
|
350
351
|
description: `Blog posts, case studies, and changelog entries${F ? ` · ${F} total` : ""}`,
|
|
351
352
|
actions: /* @__PURE__ */ f(r, {
|
|
352
353
|
onClick: Oe,
|
|
@@ -410,6 +411,7 @@ function b() {
|
|
|
410
411
|
keyExtractor: (e) => e.id,
|
|
411
412
|
loading: Te,
|
|
412
413
|
emptyState: /* @__PURE__ */ d(i, {
|
|
414
|
+
illustration: "empty-files",
|
|
413
415
|
icon: /* @__PURE__ */ d(de, { className: "h-8 w-8" }),
|
|
414
416
|
title: "No content yet",
|
|
415
417
|
description: "Create a blog post, case study, or changelog entry to get started."
|