@burdenoff/microfe-admin 2026.625.1 → 2026.626.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.map +1 -1
- package/dist/admin/components/ConfirmDialog.js +1 -1
- package/dist/admin/components/ConfirmDialog.js.map +1 -1
- package/dist/admin/components/FormSheet.js +1 -1
- package/dist/admin/components/FormSheet.js.map +1 -1
- package/dist/admin/components/TenantSsoPanel.js +1 -1
- package/dist/admin/components/TenantSsoPanel.js.map +1 -1
- package/dist/admin/components/Toast.js +11 -11
- package/dist/admin/components/Toast.js.map +1 -1
- package/dist/admin/components/forms/SubscriptionForm.js +2 -2
- package/dist/admin/components/forms/SubscriptionForm.js.map +1 -1
- package/dist/admin/components/forms/TenantForm.js +1 -1
- package/dist/admin/components/forms/UserForm.js +1 -1
- package/dist/admin/components/ui.js +3 -3
- package/dist/admin/components/ui.js.map +1 -1
- package/dist/admin/constants/index.js +33 -24
- package/dist/admin/constants/index.js.map +1 -1
- package/dist/admin/pages/AuditLogsPage.js +92 -77
- package/dist/admin/pages/AuditLogsPage.js.map +1 -1
- package/dist/admin/pages/BillingPage.js +95 -51
- package/dist/admin/pages/BillingPage.js.map +1 -1
- package/dist/admin/pages/CommunicationsPage.js +97 -92
- package/dist/admin/pages/CommunicationsPage.js.map +1 -1
- package/dist/admin/pages/ContentPagesPage.js +274 -259
- package/dist/admin/pages/ContentPagesPage.js.map +1 -1
- package/dist/admin/pages/DashboardPage.js +113 -84
- package/dist/admin/pages/DashboardPage.js.map +1 -1
- package/dist/admin/pages/FeatureFlagsPage.js +93 -75
- package/dist/admin/pages/FeatureFlagsPage.js.map +1 -1
- package/dist/admin/pages/OrganizationsListPage.js +105 -87
- package/dist/admin/pages/OrganizationsListPage.js.map +1 -1
- package/dist/admin/pages/ProductsPage.js +138 -130
- package/dist/admin/pages/ProductsPage.js.map +1 -1
- package/dist/admin/pages/ReferralsPage.js +70 -52
- package/dist/admin/pages/ReferralsPage.js.map +1 -1
- package/dist/admin/pages/RolesPermissionsPage.js +136 -118
- package/dist/admin/pages/RolesPermissionsPage.js.map +1 -1
- package/dist/admin/pages/SREPage.js +34 -32
- package/dist/admin/pages/SREPage.js.map +1 -1
- package/dist/admin/pages/SandboxSettingsPage.js +360 -322
- package/dist/admin/pages/SandboxSettingsPage.js.map +1 -1
- package/dist/admin/pages/SettingsPage.js +63 -60
- package/dist/admin/pages/SettingsPage.js.map +1 -1
- package/dist/admin/pages/SyncFailuresPage.js +120 -118
- package/dist/admin/pages/SyncFailuresPage.js.map +1 -1
- package/dist/admin/pages/TenantDetailPage.js +261 -218
- package/dist/admin/pages/TenantDetailPage.js.map +1 -1
- package/dist/admin/pages/TenantFormRoutePage.js +65 -61
- package/dist/admin/pages/TenantFormRoutePage.js.map +1 -1
- package/dist/admin/pages/TenantsListPage.js +86 -71
- package/dist/admin/pages/TenantsListPage.js.map +1 -1
- package/dist/admin/pages/UsersListPage.js +128 -113
- package/dist/admin/pages/UsersListPage.js.map +1 -1
- package/dist/admin/pages/WaitlistPage.js +78 -63
- package/dist/admin/pages/WaitlistPage.js.map +1 -1
- package/dist/admin/pages/WorkflowsPage.js +107 -102
- package/dist/admin/pages/WorkflowsPage.js.map +1 -1
- package/dist/admin/pages/WorkspacesListPage.js +61 -49
- package/dist/admin/pages/WorkspacesListPage.js.map +1 -1
- package/dist/node_modules/@hookform/resolvers/dist/resolvers.js +2 -2
- package/dist/node_modules/@hookform/resolvers/dist/resolvers.js.map +1 -1
- package/dist/node_modules/@hookform/resolvers/zod/dist/zod.js +35 -26
- package/dist/node_modules/@hookform/resolvers/zod/dist/zod.js.map +1 -1
- package/dist/node_modules/react-hook-form/dist/index.esm.js +609 -516
- package/dist/node_modules/react-hook-form/dist/index.esm.js.map +1 -1
- package/package.json +2 -2
|
@@ -8,14 +8,15 @@ import "../components/index.js";
|
|
|
8
8
|
import { useCallback as l, useEffect as u, useMemo as d, useState as f } from "react";
|
|
9
9
|
import { Check as p, ClipboardList as m, Search as h, Trash2 as g, X as _ } from "lucide-react";
|
|
10
10
|
import { Fragment as v, jsx as y, jsxs as b } from "react/jsx-runtime";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
11
|
+
import { EmphasisPanel as x, PagePurpose as ee } from "@burdenoff/fe-libs/ui";
|
|
12
|
+
import { gql as S } from "@apollo/client";
|
|
13
|
+
import { useGatewayClient as C } from "@burdenoff/fe-libs/shared/graphql";
|
|
13
14
|
//#region src/admin/pages/WaitlistPage.tsx
|
|
14
|
-
var
|
|
15
|
+
var w = 20, T = {
|
|
15
16
|
PENDING: "outline",
|
|
16
17
|
APPROVED: "default",
|
|
17
18
|
REJECTED: "destructive"
|
|
18
|
-
},
|
|
19
|
+
}, E = S`
|
|
19
20
|
query GetWaitlist($filter: WaitlistFilterInput) {
|
|
20
21
|
waitlist(filter: $filter) {
|
|
21
22
|
id
|
|
@@ -31,70 +32,70 @@ var C = 20, w = {
|
|
|
31
32
|
createdAt
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
|
-
`,
|
|
35
|
+
`, D = S`
|
|
35
36
|
mutation ApproveWaitlist($input: ApproveWaitlistInput!) {
|
|
36
37
|
approveWaitlist(input: $input) {
|
|
37
38
|
id
|
|
38
39
|
status
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
|
-
`,
|
|
42
|
+
`, O = S`
|
|
42
43
|
mutation RejectWaitlist($input: RejectWaitlistInput!) {
|
|
43
44
|
rejectWaitlist(input: $input) {
|
|
44
45
|
id
|
|
45
46
|
status
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
|
-
`,
|
|
49
|
+
`, k = S`
|
|
49
50
|
mutation RemoveFromWaitlist($id: ID!) {
|
|
50
51
|
removeFromWaitlist(id: $id)
|
|
51
52
|
}
|
|
52
53
|
`;
|
|
53
|
-
function
|
|
54
|
-
let { canViewWaitlist:
|
|
54
|
+
function A() {
|
|
55
|
+
let { canViewWaitlist: S, canManageWaitlist: A, isLoading: j } = e(), M = C("global"), [N, P] = f(1), [F, I] = f(""), [L, R] = f(""), [z, B] = f(null), [V, H] = f(!1), [U, W] = f(null), [G, K] = f(0), q = d(() => {
|
|
55
56
|
let e = {};
|
|
56
|
-
return
|
|
57
|
-
}, [
|
|
57
|
+
return L && (e.status = L), Object.keys(e).length > 0 ? e : void 0;
|
|
58
|
+
}, [L]), J = l(() => K((e) => e + 1), []);
|
|
58
59
|
u(() => {
|
|
59
|
-
|
|
60
|
-
query:
|
|
61
|
-
variables: { filter:
|
|
60
|
+
j || !S || (H(!0), M.query({
|
|
61
|
+
query: E,
|
|
62
|
+
variables: { filter: q },
|
|
62
63
|
fetchPolicy: "network-only"
|
|
63
64
|
}).then((e) => {
|
|
64
65
|
let t = e.data;
|
|
65
|
-
|
|
66
|
-
}).catch((e) =>
|
|
66
|
+
B(t ?? null), W(null);
|
|
67
|
+
}).catch((e) => W({ message: e.message })).finally(() => H(!1)));
|
|
67
68
|
}, [
|
|
69
|
+
M,
|
|
70
|
+
q,
|
|
68
71
|
j,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
x,
|
|
72
|
-
W
|
|
72
|
+
S,
|
|
73
|
+
G
|
|
73
74
|
]);
|
|
74
|
-
let
|
|
75
|
-
if (!
|
|
76
|
-
let e =
|
|
77
|
-
return
|
|
78
|
-
}, [
|
|
79
|
-
await
|
|
80
|
-
mutation:
|
|
75
|
+
let Y = d(() => z?.waitlist ?? [], [z]), X = d(() => {
|
|
76
|
+
if (!F) return Y;
|
|
77
|
+
let e = F.toLowerCase();
|
|
78
|
+
return Y.filter((t) => t.email.toLowerCase().includes(e) || t.name && t.name.toLowerCase().includes(e) || t.company && t.company.toLowerCase().includes(e));
|
|
79
|
+
}, [Y, F]), Z = X.length, Q = Math.ceil(Z / w), $ = X.slice((N - 1) * w, N * w), te = d(() => Y.filter((e) => e.status === "PENDING").length, [Y]), ne = l(async (e) => {
|
|
80
|
+
await M.mutate({
|
|
81
|
+
mutation: D,
|
|
81
82
|
variables: { input: { id: e } }
|
|
82
|
-
}),
|
|
83
|
-
}, [
|
|
83
|
+
}), J();
|
|
84
|
+
}, [M, J]), re = l(async (e) => {
|
|
84
85
|
let t = window.prompt("Rejection reason (optional):");
|
|
85
|
-
await
|
|
86
|
-
mutation:
|
|
86
|
+
await M.mutate({
|
|
87
|
+
mutation: O,
|
|
87
88
|
variables: { input: {
|
|
88
89
|
id: e,
|
|
89
90
|
reason: t
|
|
90
91
|
} }
|
|
91
|
-
}),
|
|
92
|
-
}, [
|
|
93
|
-
window.confirm("Remove this waitlist entry?") && (await
|
|
94
|
-
mutation:
|
|
92
|
+
}), J();
|
|
93
|
+
}, [M, J]), ie = l(async (e) => {
|
|
94
|
+
window.confirm("Remove this waitlist entry?") && (await M.mutate({
|
|
95
|
+
mutation: k,
|
|
95
96
|
variables: { id: e }
|
|
96
|
-
}),
|
|
97
|
-
}, [
|
|
97
|
+
}), J());
|
|
98
|
+
}, [M, J]), ae = [
|
|
98
99
|
{
|
|
99
100
|
key: "email",
|
|
100
101
|
header: "Email",
|
|
@@ -123,7 +124,7 @@ function k() {
|
|
|
123
124
|
header: "Status",
|
|
124
125
|
width: "100px",
|
|
125
126
|
render: (e) => /* @__PURE__ */ y(t, {
|
|
126
|
-
variant:
|
|
127
|
+
variant: T[e.status] ?? "outline",
|
|
127
128
|
className: "text-xs capitalize",
|
|
128
129
|
children: e.status.toLowerCase()
|
|
129
130
|
})
|
|
@@ -134,24 +135,24 @@ function k() {
|
|
|
134
135
|
width: "110px",
|
|
135
136
|
render: (e) => a(e.createdAt)
|
|
136
137
|
},
|
|
137
|
-
...
|
|
138
|
+
...A ? [{
|
|
138
139
|
key: "actions",
|
|
139
140
|
header: "Actions",
|
|
140
141
|
width: "140px",
|
|
141
142
|
render: (e) => /* @__PURE__ */ b("div", {
|
|
142
143
|
className: "flex items-center gap-1",
|
|
143
144
|
children: [e.status === "PENDING" && /* @__PURE__ */ b(v, { children: [/* @__PURE__ */ y("button", {
|
|
144
|
-
onClick: () =>
|
|
145
|
-
className: "rounded p-1 text-
|
|
145
|
+
onClick: () => ne(e.id),
|
|
146
|
+
className: "rounded p-1 text-status-success-text hover:bg-status-success-bg",
|
|
146
147
|
title: "Approve",
|
|
147
148
|
children: /* @__PURE__ */ y(p, { className: "h-4 w-4" })
|
|
148
149
|
}), /* @__PURE__ */ y("button", {
|
|
149
|
-
onClick: () =>
|
|
150
|
-
className: "rounded p-1 text-
|
|
150
|
+
onClick: () => re(e.id),
|
|
151
|
+
className: "rounded p-1 text-status-error-text hover:bg-status-error-bg",
|
|
151
152
|
title: "Reject",
|
|
152
153
|
children: /* @__PURE__ */ y(_, { className: "h-4 w-4" })
|
|
153
154
|
})] }), /* @__PURE__ */ y("button", {
|
|
154
|
-
onClick: () =>
|
|
155
|
+
onClick: () => ie(e.id),
|
|
155
156
|
className: "rounded p-1 text-text-secondary hover:bg-bg-sunken",
|
|
156
157
|
title: "Remove",
|
|
157
158
|
children: /* @__PURE__ */ y(g, { className: "h-4 w-4" })
|
|
@@ -160,33 +161,47 @@ function k() {
|
|
|
160
161
|
}] : []
|
|
161
162
|
];
|
|
162
163
|
return /* @__PURE__ */ y(c, {
|
|
163
|
-
allowed:
|
|
164
|
-
isLoading:
|
|
164
|
+
allowed: S,
|
|
165
|
+
isLoading: j,
|
|
165
166
|
pageTitle: "Waitlist",
|
|
166
167
|
denyMessage: "You don't have permission to view the waitlist.",
|
|
167
168
|
children: /* @__PURE__ */ y(i, {
|
|
168
169
|
title: "Waitlist Management",
|
|
169
170
|
gradientWord: "Management",
|
|
170
|
-
description: `Product waitlist entries${
|
|
171
|
+
description: `Product waitlist entries${Z ? ` \u00b7 ${Z} total` : ""}`,
|
|
171
172
|
children: /* @__PURE__ */ b("div", {
|
|
172
173
|
className: "space-y-4",
|
|
173
174
|
children: [
|
|
175
|
+
/* @__PURE__ */ y(ee, { children: "People who signed up for early access to a product land here before they have an account. Use this page to review who's waiting, then approve the ones you want to let in (which provisions their access) or reject the rest — clearing the pending queue keeps onboarding moving." }),
|
|
176
|
+
/* @__PURE__ */ b(x, {
|
|
177
|
+
className: "flex items-center gap-4 p-5",
|
|
178
|
+
children: [/* @__PURE__ */ y("div", {
|
|
179
|
+
className: "flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-action-primary-bg/15 text-action-primary-bg",
|
|
180
|
+
children: /* @__PURE__ */ y(m, { className: "h-6 w-6" })
|
|
181
|
+
}), /* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("div", {
|
|
182
|
+
className: "text-3xl font-bold tabular-nums text-text-primary",
|
|
183
|
+
children: te
|
|
184
|
+
}), /* @__PURE__ */ y("p", {
|
|
185
|
+
className: "text-sm text-text-secondary",
|
|
186
|
+
children: "pending entries awaiting review"
|
|
187
|
+
})] })]
|
|
188
|
+
}),
|
|
174
189
|
/* @__PURE__ */ b("div", {
|
|
175
190
|
className: "flex flex-wrap items-center gap-3",
|
|
176
191
|
children: [/* @__PURE__ */ b("div", {
|
|
177
192
|
className: "relative max-w-sm flex-1",
|
|
178
193
|
children: [/* @__PURE__ */ y(h, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ y(r, {
|
|
179
194
|
placeholder: "Search by email, name, or company...",
|
|
180
|
-
value:
|
|
195
|
+
value: F,
|
|
181
196
|
onChange: (e) => {
|
|
182
|
-
|
|
197
|
+
P(1), I(e.target.value);
|
|
183
198
|
},
|
|
184
199
|
className: "pl-9"
|
|
185
200
|
})]
|
|
186
201
|
}), /* @__PURE__ */ b("select", {
|
|
187
|
-
value:
|
|
202
|
+
value: L,
|
|
188
203
|
onChange: (e) => {
|
|
189
|
-
|
|
204
|
+
P(1), R(e.target.value);
|
|
190
205
|
},
|
|
191
206
|
className: "rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm",
|
|
192
207
|
children: [
|
|
@@ -209,15 +224,15 @@ function k() {
|
|
|
209
224
|
]
|
|
210
225
|
})]
|
|
211
226
|
}),
|
|
212
|
-
|
|
227
|
+
U && /* @__PURE__ */ b("div", {
|
|
213
228
|
className: "rounded-md border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text",
|
|
214
|
-
children: ["Failed to load waitlist: ",
|
|
229
|
+
children: ["Failed to load waitlist: ", U.message]
|
|
215
230
|
}),
|
|
216
231
|
/* @__PURE__ */ y(o, {
|
|
217
|
-
columns:
|
|
218
|
-
data:
|
|
232
|
+
columns: ae,
|
|
233
|
+
data: $,
|
|
219
234
|
keyExtractor: (e) => e.id,
|
|
220
|
-
loading:
|
|
235
|
+
loading: V,
|
|
221
236
|
emptyState: /* @__PURE__ */ y(n, {
|
|
222
237
|
illustration: "empty-search",
|
|
223
238
|
icon: /* @__PURE__ */ y(m, { className: "h-8 w-8" }),
|
|
@@ -225,12 +240,12 @@ function k() {
|
|
|
225
240
|
description: "No entries match your current filters"
|
|
226
241
|
})
|
|
227
242
|
}),
|
|
228
|
-
|
|
229
|
-
page:
|
|
230
|
-
totalPages:
|
|
231
|
-
totalItems:
|
|
232
|
-
pageSize:
|
|
233
|
-
onPageChange:
|
|
243
|
+
Z > w && /* @__PURE__ */ y(s, {
|
|
244
|
+
page: N,
|
|
245
|
+
totalPages: Q,
|
|
246
|
+
totalItems: Z,
|
|
247
|
+
pageSize: w,
|
|
248
|
+
onPageChange: P
|
|
234
249
|
})
|
|
235
250
|
]
|
|
236
251
|
})
|
|
@@ -238,6 +253,6 @@ function k() {
|
|
|
238
253
|
});
|
|
239
254
|
}
|
|
240
255
|
//#endregion
|
|
241
|
-
export {
|
|
256
|
+
export { A as WaitlistPage };
|
|
242
257
|
|
|
243
258
|
//# sourceMappingURL=WaitlistPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WaitlistPage.js","names":[],"sources":["../../../src/admin/pages/WaitlistPage.tsx"],"sourcesContent":["import { useState, useMemo, useCallback, useEffect } from 'react';\nimport { ClipboardList, Search, Check, X, Trash2 } from 'lucide-react';\nimport { useAdminPermissions } from '../hooks/useAdminPermissions';\nimport {\n PageLayout,\n Input,\n DataTable,\n Pagination,\n EmptyState,\n Badge,\n PermissionGuard,\n} from '../components';\nimport { formatDate } from '../utils/formatters';\nimport { useGatewayClient } from '@burdenoff/fe-libs/shared/graphql';\nimport { gql } from '@apollo/client';\n\nconst PAGE_SIZE = 20;\n\ntype WaitlistStatus = 'PENDING' | 'APPROVED' | 'REJECTED';\n\nconst STATUS_VARIANT: Record<WaitlistStatus, 'default' | 'secondary' | 'outline' | 'destructive'> =\n {\n PENDING: 'outline',\n APPROVED: 'default',\n REJECTED: 'destructive',\n };\n\nconst GET_WAITLIST = gql`\n query GetWaitlist($filter: WaitlistFilterInput) {\n waitlist(filter: $filter) {\n id\n email\n name\n company\n platformProductId\n status\n reason\n rejectionReason\n processedAt\n processedBy\n createdAt\n }\n }\n`;\n\nconst APPROVE_WAITLIST = gql`\n mutation ApproveWaitlist($input: ApproveWaitlistInput!) {\n approveWaitlist(input: $input) {\n id\n status\n }\n }\n`;\n\nconst REJECT_WAITLIST = gql`\n mutation RejectWaitlist($input: RejectWaitlistInput!) {\n rejectWaitlist(input: $input) {\n id\n status\n }\n }\n`;\n\nconst REMOVE_WAITLIST = gql`\n mutation RemoveFromWaitlist($id: ID!) {\n removeFromWaitlist(id: $id)\n }\n`;\n\ntype WaitlistItem = {\n id: string;\n email: string;\n name: string | null;\n company: string | null;\n platformProductId: string;\n status: WaitlistStatus;\n reason: string | null;\n rejectionReason: string | null;\n processedAt: string | null;\n processedBy: string | null;\n createdAt: string;\n};\n\nexport function WaitlistPage() {\n const {\n canViewWaitlist,\n canManageWaitlist,\n isLoading: permissionsLoading,\n } = useAdminPermissions();\n const globalClient = useGatewayClient('global');\n\n const [page, setPage] = useState(1);\n const [search, setSearch] = useState('');\n const [statusFilter, setStatusFilter] = useState<WaitlistStatus | ''>('');\n const [data, setData] = useState<{ waitlist?: WaitlistItem[] } | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<{ message: string } | null>(null);\n const [fetchCount, setFetchCount] = useState(0);\n\n const filter = useMemo(() => {\n const f: Record<string, unknown> = {};\n if (statusFilter) f.status = statusFilter;\n return Object.keys(f).length > 0 ? f : undefined;\n }, [statusFilter]);\n\n const refetch = useCallback(() => setFetchCount((c) => c + 1), []);\n\n useEffect(() => {\n if (permissionsLoading || !canViewWaitlist) return;\n setLoading(true);\n globalClient\n .query({ query: GET_WAITLIST, variables: { filter }, fetchPolicy: 'network-only' })\n .then((result) => {\n const d = result.data as { waitlist?: WaitlistItem[] } | undefined;\n setData(d ?? null);\n setError(null);\n })\n .catch((err: Error) => setError({ message: err.message }))\n .finally(() => setLoading(false));\n }, [globalClient, filter, permissionsLoading, canViewWaitlist, fetchCount]);\n\n const allEntries: WaitlistItem[] = useMemo(\n () => (data?.waitlist ?? []) as WaitlistItem[],\n [data]\n );\n\n // Client-side search filter (email, name, company)\n const filtered = useMemo(() => {\n if (!search) return allEntries;\n const q = search.toLowerCase();\n return allEntries.filter(\n (e) =>\n e.email.toLowerCase().includes(q) ||\n (e.name && e.name.toLowerCase().includes(q)) ||\n (e.company && e.company.toLowerCase().includes(q))\n );\n }, [allEntries, search]);\n\n const total = filtered.length;\n const totalPages = Math.ceil(total / PAGE_SIZE);\n const paged = filtered.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE);\n\n const handleApprove = useCallback(\n async (id: string) => {\n await globalClient.mutate({ mutation: APPROVE_WAITLIST, variables: { input: { id } } });\n refetch();\n },\n [globalClient, refetch]\n );\n\n const handleReject = useCallback(\n async (id: string) => {\n const reason = window.prompt('Rejection reason (optional):');\n await globalClient.mutate({\n mutation: REJECT_WAITLIST,\n variables: { input: { id, reason } },\n });\n refetch();\n },\n [globalClient, refetch]\n );\n\n const handleRemove = useCallback(\n async (id: string) => {\n if (!window.confirm('Remove this waitlist entry?')) return;\n await globalClient.mutate({ mutation: REMOVE_WAITLIST, variables: { id } });\n refetch();\n },\n [globalClient, refetch]\n );\n\n const columns = [\n {\n key: 'email',\n header: 'Email',\n render: (e: WaitlistItem) => (\n <div className=\"min-w-0\">\n <div className=\"font-medium text-text-primary\">{e.email}</div>\n {e.name && <div className=\"text-xs text-text-secondary\">{e.name}</div>}\n </div>\n ),\n },\n {\n key: 'company',\n header: 'Company',\n width: '140px',\n render: (e: WaitlistItem) => (\n <span className=\"text-sm text-text-secondary\">{e.company || '-'}</span>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n width: '100px',\n render: (e: WaitlistItem) => (\n <Badge variant={STATUS_VARIANT[e.status] ?? 'outline'} className=\"text-xs capitalize\">\n {e.status.toLowerCase()}\n </Badge>\n ),\n },\n {\n key: 'createdAt',\n header: 'Joined',\n width: '110px',\n render: (e: WaitlistItem) => formatDate(e.createdAt),\n },\n ...(canManageWaitlist\n ? [\n {\n key: 'actions',\n header: 'Actions',\n width: '140px',\n render: (e: WaitlistItem) => (\n <div className=\"flex items-center gap-1\">\n {e.status === 'PENDING' && (\n <>\n <button\n onClick={() => handleApprove(e.id)}\n className=\"rounded p-1 text-green-500 hover:bg-green-500/10\"\n title=\"Approve\"\n >\n <Check className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => handleReject(e.id)}\n className=\"rounded p-1 text-red-500 hover:bg-red-500/10\"\n title=\"Reject\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </>\n )}\n <button\n onClick={() => handleRemove(e.id)}\n className=\"rounded p-1 text-text-secondary hover:bg-bg-sunken\"\n title=\"Remove\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n ),\n },\n ]\n : []),\n ];\n\n return (\n <PermissionGuard\n allowed={canViewWaitlist}\n isLoading={permissionsLoading}\n pageTitle=\"Waitlist\"\n denyMessage=\"You don't have permission to view the waitlist.\"\n >\n <PageLayout\n title=\"Waitlist Management\"\n gradientWord=\"Management\"\n description={`Product waitlist entries${total ? ` \\u00b7 ${total} total` : ''}`}\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-3\">\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 email, name, or company...\"\n value={search}\n onChange={(e) => {\n setPage(1);\n setSearch(e.target.value);\n }}\n className=\"pl-9\"\n />\n </div>\n <select\n value={statusFilter}\n onChange={(e) => {\n setPage(1);\n setStatusFilter(e.target.value as WaitlistStatus | '');\n }}\n className=\"rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm\"\n >\n <option value=\"\">All statuses</option>\n <option value=\"PENDING\">Pending</option>\n <option value=\"APPROVED\">Approved</option>\n <option value=\"REJECTED\">Rejected</option>\n </select>\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 waitlist: {error.message}\n </div>\n )}\n\n <DataTable\n columns={columns}\n data={paged}\n keyExtractor={(e) => e.id}\n loading={loading}\n emptyState={\n <EmptyState\n illustration=\"empty-search\"\n icon={<ClipboardList className=\"h-8 w-8\" />}\n title=\"No waitlist entries\"\n description=\"No entries match your current filters\"\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":"WaitlistPage.js","names":[],"sources":["../../../src/admin/pages/WaitlistPage.tsx"],"sourcesContent":["import { useState, useMemo, useCallback, useEffect } from 'react';\nimport { ClipboardList, Search, Check, X, Trash2 } from 'lucide-react';\nimport { useAdminPermissions } from '../hooks/useAdminPermissions';\nimport {\n PageLayout,\n Input,\n DataTable,\n Pagination,\n EmptyState,\n Badge,\n PermissionGuard,\n} from '../components';\nimport { formatDate } from '../utils/formatters';\nimport { PagePurpose, EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport { useGatewayClient } from '@burdenoff/fe-libs/shared/graphql';\nimport { gql } from '@apollo/client';\n\nconst PAGE_SIZE = 20;\n\ntype WaitlistStatus = 'PENDING' | 'APPROVED' | 'REJECTED';\n\nconst STATUS_VARIANT: Record<WaitlistStatus, 'default' | 'secondary' | 'outline' | 'destructive'> =\n {\n PENDING: 'outline',\n APPROVED: 'default',\n REJECTED: 'destructive',\n };\n\nconst GET_WAITLIST = gql`\n query GetWaitlist($filter: WaitlistFilterInput) {\n waitlist(filter: $filter) {\n id\n email\n name\n company\n platformProductId\n status\n reason\n rejectionReason\n processedAt\n processedBy\n createdAt\n }\n }\n`;\n\nconst APPROVE_WAITLIST = gql`\n mutation ApproveWaitlist($input: ApproveWaitlistInput!) {\n approveWaitlist(input: $input) {\n id\n status\n }\n }\n`;\n\nconst REJECT_WAITLIST = gql`\n mutation RejectWaitlist($input: RejectWaitlistInput!) {\n rejectWaitlist(input: $input) {\n id\n status\n }\n }\n`;\n\nconst REMOVE_WAITLIST = gql`\n mutation RemoveFromWaitlist($id: ID!) {\n removeFromWaitlist(id: $id)\n }\n`;\n\ntype WaitlistItem = {\n id: string;\n email: string;\n name: string | null;\n company: string | null;\n platformProductId: string;\n status: WaitlistStatus;\n reason: string | null;\n rejectionReason: string | null;\n processedAt: string | null;\n processedBy: string | null;\n createdAt: string;\n};\n\nexport function WaitlistPage() {\n const {\n canViewWaitlist,\n canManageWaitlist,\n isLoading: permissionsLoading,\n } = useAdminPermissions();\n const globalClient = useGatewayClient('global');\n\n const [page, setPage] = useState(1);\n const [search, setSearch] = useState('');\n const [statusFilter, setStatusFilter] = useState<WaitlistStatus | ''>('');\n const [data, setData] = useState<{ waitlist?: WaitlistItem[] } | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<{ message: string } | null>(null);\n const [fetchCount, setFetchCount] = useState(0);\n\n const filter = useMemo(() => {\n const f: Record<string, unknown> = {};\n if (statusFilter) f.status = statusFilter;\n return Object.keys(f).length > 0 ? f : undefined;\n }, [statusFilter]);\n\n const refetch = useCallback(() => setFetchCount((c) => c + 1), []);\n\n useEffect(() => {\n if (permissionsLoading || !canViewWaitlist) return;\n setLoading(true);\n globalClient\n .query({ query: GET_WAITLIST, variables: { filter }, fetchPolicy: 'network-only' })\n .then((result) => {\n const d = result.data as { waitlist?: WaitlistItem[] } | undefined;\n setData(d ?? null);\n setError(null);\n })\n .catch((err: Error) => setError({ message: err.message }))\n .finally(() => setLoading(false));\n }, [globalClient, filter, permissionsLoading, canViewWaitlist, fetchCount]);\n\n const allEntries: WaitlistItem[] = useMemo(\n () => (data?.waitlist ?? []) as WaitlistItem[],\n [data]\n );\n\n // Client-side search filter (email, name, company)\n const filtered = useMemo(() => {\n if (!search) return allEntries;\n const q = search.toLowerCase();\n return allEntries.filter(\n (e) =>\n e.email.toLowerCase().includes(q) ||\n (e.name && e.name.toLowerCase().includes(q)) ||\n (e.company && e.company.toLowerCase().includes(q))\n );\n }, [allEntries, search]);\n\n const total = filtered.length;\n const totalPages = Math.ceil(total / PAGE_SIZE);\n const paged = filtered.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE);\n const pendingCount = useMemo(\n () => allEntries.filter((e) => e.status === 'PENDING').length,\n [allEntries]\n );\n\n const handleApprove = useCallback(\n async (id: string) => {\n await globalClient.mutate({ mutation: APPROVE_WAITLIST, variables: { input: { id } } });\n refetch();\n },\n [globalClient, refetch]\n );\n\n const handleReject = useCallback(\n async (id: string) => {\n const reason = window.prompt('Rejection reason (optional):');\n await globalClient.mutate({\n mutation: REJECT_WAITLIST,\n variables: { input: { id, reason } },\n });\n refetch();\n },\n [globalClient, refetch]\n );\n\n const handleRemove = useCallback(\n async (id: string) => {\n if (!window.confirm('Remove this waitlist entry?')) return;\n await globalClient.mutate({ mutation: REMOVE_WAITLIST, variables: { id } });\n refetch();\n },\n [globalClient, refetch]\n );\n\n const columns = [\n {\n key: 'email',\n header: 'Email',\n render: (e: WaitlistItem) => (\n <div className=\"min-w-0\">\n <div className=\"font-medium text-text-primary\">{e.email}</div>\n {e.name && <div className=\"text-xs text-text-secondary\">{e.name}</div>}\n </div>\n ),\n },\n {\n key: 'company',\n header: 'Company',\n width: '140px',\n render: (e: WaitlistItem) => (\n <span className=\"text-sm text-text-secondary\">{e.company || '-'}</span>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n width: '100px',\n render: (e: WaitlistItem) => (\n <Badge variant={STATUS_VARIANT[e.status] ?? 'outline'} className=\"text-xs capitalize\">\n {e.status.toLowerCase()}\n </Badge>\n ),\n },\n {\n key: 'createdAt',\n header: 'Joined',\n width: '110px',\n render: (e: WaitlistItem) => formatDate(e.createdAt),\n },\n ...(canManageWaitlist\n ? [\n {\n key: 'actions',\n header: 'Actions',\n width: '140px',\n render: (e: WaitlistItem) => (\n <div className=\"flex items-center gap-1\">\n {e.status === 'PENDING' && (\n <>\n <button\n onClick={() => handleApprove(e.id)}\n className=\"rounded p-1 text-status-success-text hover:bg-status-success-bg\"\n title=\"Approve\"\n >\n <Check className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => handleReject(e.id)}\n className=\"rounded p-1 text-status-error-text hover:bg-status-error-bg\"\n title=\"Reject\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </>\n )}\n <button\n onClick={() => handleRemove(e.id)}\n className=\"rounded p-1 text-text-secondary hover:bg-bg-sunken\"\n title=\"Remove\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n ),\n },\n ]\n : []),\n ];\n\n return (\n <PermissionGuard\n allowed={canViewWaitlist}\n isLoading={permissionsLoading}\n pageTitle=\"Waitlist\"\n denyMessage=\"You don't have permission to view the waitlist.\"\n >\n <PageLayout\n title=\"Waitlist Management\"\n gradientWord=\"Management\"\n description={`Product waitlist entries${total ? ` \\u00b7 ${total} total` : ''}`}\n >\n <div className=\"space-y-4\">\n <PagePurpose>\n People who signed up for early access to a product land here before they have an\n account. Use this page to review who's waiting, then approve the ones you want to\n let in (which provisions their access) or reject the rest — clearing the pending queue\n keeps onboarding moving.\n </PagePurpose>\n\n {/* Emphasis: pending entries — the count that needs action. */}\n <EmphasisPanel className=\"flex items-center gap-4 p-5\">\n <div className=\"flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-action-primary-bg/15 text-action-primary-bg\">\n <ClipboardList className=\"h-6 w-6\" />\n </div>\n <div>\n <div className=\"text-3xl font-bold tabular-nums text-text-primary\">\n {pendingCount}\n </div>\n <p className=\"text-sm text-text-secondary\">pending entries awaiting review</p>\n </div>\n </EmphasisPanel>\n\n <div className=\"flex flex-wrap items-center gap-3\">\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 email, name, or company...\"\n value={search}\n onChange={(e) => {\n setPage(1);\n setSearch(e.target.value);\n }}\n className=\"pl-9\"\n />\n </div>\n <select\n value={statusFilter}\n onChange={(e) => {\n setPage(1);\n setStatusFilter(e.target.value as WaitlistStatus | '');\n }}\n className=\"rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm\"\n >\n <option value=\"\">All statuses</option>\n <option value=\"PENDING\">Pending</option>\n <option value=\"APPROVED\">Approved</option>\n <option value=\"REJECTED\">Rejected</option>\n </select>\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 waitlist: {error.message}\n </div>\n )}\n\n <DataTable\n columns={columns}\n data={paged}\n keyExtractor={(e) => e.id}\n loading={loading}\n emptyState={\n <EmptyState\n illustration=\"empty-search\"\n icon={<ClipboardList className=\"h-8 w-8\" />}\n title=\"No waitlist entries\"\n description=\"No entries match your current filters\"\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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"}
|
|
@@ -7,12 +7,13 @@ import "../components/index.js";
|
|
|
7
7
|
import { useGetWorkflowSystemOverviewQuery as l, useListActiveWorkflowsQuery as u } from "../../generated/wspace-operations.js";
|
|
8
8
|
import { Activity as d, AlertCircle as f, Clock as p, GitBranch as m, Play as h, Plus as g, Search as _ } from "lucide-react";
|
|
9
9
|
import { jsx as v, jsxs as y } from "react/jsx-runtime";
|
|
10
|
+
import { PagePurpose as b } from "@burdenoff/fe-libs/ui";
|
|
10
11
|
//#region src/admin/pages/WorkflowsPage.tsx
|
|
11
|
-
function
|
|
12
|
-
let { data:
|
|
12
|
+
function x() {
|
|
13
|
+
let { data: x, loading: S } = l(), { data: C, loading: w } = u({ variables: {
|
|
13
14
|
limit: 10,
|
|
14
15
|
offset: 0
|
|
15
|
-
} }),
|
|
16
|
+
} }), T = x?.systemOverview, E = C?.activeWorkflows || [];
|
|
16
17
|
return /* @__PURE__ */ v(i, {
|
|
17
18
|
title: "Workflows",
|
|
18
19
|
gradientWord: "Workflows",
|
|
@@ -20,110 +21,114 @@ function b() {
|
|
|
20
21
|
actions: /* @__PURE__ */ y(t, { children: [/* @__PURE__ */ v(g, { className: "mr-2 h-4 w-4" }), "Create Workflow"] }),
|
|
21
22
|
children: /* @__PURE__ */ y("div", {
|
|
22
23
|
className: "space-y-8",
|
|
23
|
-
children: [
|
|
24
|
-
/* @__PURE__ */ v(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
className: "
|
|
24
|
+
children: [
|
|
25
|
+
/* @__PURE__ */ v(b, { children: "Workflows are the automated, multi-step processes running across the platform. Use this page to see how many are defined, watch which runs are live right now, and quickly spot ones that have failed or are stuck in the queue so you can investigate." }),
|
|
26
|
+
/* @__PURE__ */ y(s, { children: [
|
|
27
|
+
/* @__PURE__ */ v(o, {
|
|
28
|
+
title: "Total Workflows",
|
|
29
|
+
hero: !0,
|
|
30
|
+
value: T?.totalWorkflows ?? 0,
|
|
31
|
+
loading: S,
|
|
32
|
+
icon: /* @__PURE__ */ v(m, { className: "h-4 w-4" })
|
|
33
|
+
}),
|
|
34
|
+
/* @__PURE__ */ v(o, {
|
|
35
|
+
title: "Active Runs",
|
|
36
|
+
value: T?.activeWorkflows ?? 0,
|
|
37
|
+
loading: S,
|
|
38
|
+
icon: /* @__PURE__ */ v(h, { className: "h-4 w-4" }),
|
|
39
|
+
trend: T?.activeWorkflows && T.activeWorkflows > 0 ? "up" : "neutral"
|
|
40
|
+
}),
|
|
41
|
+
/* @__PURE__ */ v(o, {
|
|
42
|
+
title: "Failed (24h)",
|
|
43
|
+
value: T?.failedWorkflows ?? 0,
|
|
44
|
+
loading: S,
|
|
45
|
+
icon: /* @__PURE__ */ v(f, { className: "h-4 w-4" }),
|
|
46
|
+
priority: T?.failedWorkflows && T.failedWorkflows > 0 ? "high" : "normal"
|
|
47
|
+
}),
|
|
48
|
+
/* @__PURE__ */ v(o, {
|
|
49
|
+
title: "Queued Jobs",
|
|
50
|
+
value: T?.queuedJobs ?? 0,
|
|
51
|
+
loading: S,
|
|
52
|
+
icon: /* @__PURE__ */ v(p, { className: "h-4 w-4" })
|
|
53
|
+
})
|
|
54
|
+
] }),
|
|
55
|
+
/* @__PURE__ */ y("div", {
|
|
56
|
+
className: "space-y-4",
|
|
57
|
+
children: [/* @__PURE__ */ y("div", {
|
|
58
|
+
className: "flex items-center justify-between",
|
|
59
|
+
children: [/* @__PURE__ */ v("h2", {
|
|
60
|
+
className: "text-lg font-semibold",
|
|
61
|
+
children: "Active Workflow Runs"
|
|
62
|
+
}), /* @__PURE__ */ y("div", {
|
|
63
|
+
className: "relative max-w-sm flex-1 ml-4",
|
|
64
|
+
children: [/* @__PURE__ */ v(_, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ v(r, {
|
|
65
|
+
placeholder: "Search active runs...",
|
|
66
|
+
className: "pl-9"
|
|
67
|
+
})]
|
|
63
68
|
})]
|
|
69
|
+
}), /* @__PURE__ */ v(c, {
|
|
70
|
+
columns: [
|
|
71
|
+
{
|
|
72
|
+
key: "workflowName",
|
|
73
|
+
header: "Workflow",
|
|
74
|
+
render: (e) => /* @__PURE__ */ y("div", {
|
|
75
|
+
className: "flex flex-col",
|
|
76
|
+
children: [/* @__PURE__ */ v("span", {
|
|
77
|
+
className: "font-medium text-text-primary",
|
|
78
|
+
children: e.workflowName
|
|
79
|
+
}), /* @__PURE__ */ y("span", {
|
|
80
|
+
className: "text-xs text-text-secondary",
|
|
81
|
+
children: ["v", e.workflowVersion]
|
|
82
|
+
})]
|
|
83
|
+
})
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
key: "runId",
|
|
87
|
+
header: "Run ID",
|
|
88
|
+
render: (e) => /* @__PURE__ */ y("code", {
|
|
89
|
+
className: "text-xs",
|
|
90
|
+
children: [e.runId.substring(0, 8), "..."]
|
|
91
|
+
})
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
key: "status",
|
|
95
|
+
header: "Status",
|
|
96
|
+
render: (t) => /* @__PURE__ */ v(e, {
|
|
97
|
+
variant: {
|
|
98
|
+
RUNNING: "success",
|
|
99
|
+
PENDING: "warning",
|
|
100
|
+
FAILED: "destructive",
|
|
101
|
+
COMPLETED: "default"
|
|
102
|
+
}[t.status] || "outline",
|
|
103
|
+
children: t.status
|
|
104
|
+
})
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
key: "currentNode",
|
|
108
|
+
header: "Current Node"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
key: "createdAt",
|
|
112
|
+
header: "Started At",
|
|
113
|
+
render: (e) => a(e.createdAt)
|
|
114
|
+
}
|
|
115
|
+
],
|
|
116
|
+
data: E,
|
|
117
|
+
keyExtractor: (e) => e.runId,
|
|
118
|
+
loading: w,
|
|
119
|
+
emptyState: /* @__PURE__ */ v(n, {
|
|
120
|
+
illustration: "empty-data",
|
|
121
|
+
icon: /* @__PURE__ */ v(d, { className: "h-8 w-8" }),
|
|
122
|
+
title: "No active workflows",
|
|
123
|
+
description: "There are currently no workflows running in the system"
|
|
124
|
+
})
|
|
64
125
|
})]
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
{
|
|
68
|
-
key: "workflowName",
|
|
69
|
-
header: "Workflow",
|
|
70
|
-
render: (e) => /* @__PURE__ */ y("div", {
|
|
71
|
-
className: "flex flex-col",
|
|
72
|
-
children: [/* @__PURE__ */ v("span", {
|
|
73
|
-
className: "font-medium text-text-primary",
|
|
74
|
-
children: e.workflowName
|
|
75
|
-
}), /* @__PURE__ */ y("span", {
|
|
76
|
-
className: "text-xs text-text-secondary",
|
|
77
|
-
children: ["v", e.workflowVersion]
|
|
78
|
-
})]
|
|
79
|
-
})
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
key: "runId",
|
|
83
|
-
header: "Run ID",
|
|
84
|
-
render: (e) => /* @__PURE__ */ y("code", {
|
|
85
|
-
className: "text-xs",
|
|
86
|
-
children: [e.runId.substring(0, 8), "..."]
|
|
87
|
-
})
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
key: "status",
|
|
91
|
-
header: "Status",
|
|
92
|
-
render: (t) => /* @__PURE__ */ v(e, {
|
|
93
|
-
variant: {
|
|
94
|
-
RUNNING: "success",
|
|
95
|
-
PENDING: "warning",
|
|
96
|
-
FAILED: "destructive",
|
|
97
|
-
COMPLETED: "default"
|
|
98
|
-
}[t.status] || "outline",
|
|
99
|
-
children: t.status
|
|
100
|
-
})
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
key: "currentNode",
|
|
104
|
-
header: "Current Node"
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
key: "createdAt",
|
|
108
|
-
header: "Started At",
|
|
109
|
-
render: (e) => a(e.createdAt)
|
|
110
|
-
}
|
|
111
|
-
],
|
|
112
|
-
data: T,
|
|
113
|
-
keyExtractor: (e) => e.runId,
|
|
114
|
-
loading: C,
|
|
115
|
-
emptyState: /* @__PURE__ */ v(n, {
|
|
116
|
-
illustration: "empty-data",
|
|
117
|
-
icon: /* @__PURE__ */ v(d, { className: "h-8 w-8" }),
|
|
118
|
-
title: "No active workflows",
|
|
119
|
-
description: "There are currently no workflows running in the system"
|
|
120
|
-
})
|
|
121
|
-
})]
|
|
122
|
-
})]
|
|
126
|
+
})
|
|
127
|
+
]
|
|
123
128
|
})
|
|
124
129
|
});
|
|
125
130
|
}
|
|
126
131
|
//#endregion
|
|
127
|
-
export {
|
|
132
|
+
export { x as WorkflowsPage };
|
|
128
133
|
|
|
129
134
|
//# sourceMappingURL=WorkflowsPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkflowsPage.js","names":[],"sources":["../../../src/admin/pages/WorkflowsPage.tsx"],"sourcesContent":["import { GitBranch, Search, Plus, Activity, AlertCircle, Clock, Play } from 'lucide-react';\nimport {\n PageLayout,\n Input,\n Button,\n EmptyState,\n MetricCard,\n MetricsGrid,\n DataTable,\n Badge,\n} from '../components';\nimport {\n useGetWorkflowSystemOverviewQuery,\n useListActiveWorkflowsQuery,\n} from '../../generated/wspace-operations';\nimport { formatDateTime } from '../utils/formatters';\n\nexport function WorkflowsPage() {\n const { data: overviewData, loading: overviewLoading } = useGetWorkflowSystemOverviewQuery();\n\n const { data: listData, loading: listLoading } = useListActiveWorkflowsQuery({\n variables: { limit: 10, offset: 0 },\n });\n\n const overview = overviewData?.systemOverview;\n const activeRuns = listData?.activeWorkflows || [];\n\n const columns = [\n {\n key: 'workflowName',\n header: 'Workflow',\n render: (run: any) => (\n <div className=\"flex flex-col\">\n <span className=\"font-medium text-text-primary\">{run.workflowName}</span>\n <span className=\"text-xs text-text-secondary\">v{run.workflowVersion}</span>\n </div>\n ),\n },\n {\n key: 'runId',\n header: 'Run ID',\n render: (run: any) => <code className=\"text-xs\">{run.runId.substring(0, 8)}...</code>,\n },\n {\n key: 'status',\n header: 'Status',\n render: (run: any) => {\n const variants: Record<\n string,\n 'default' | 'secondary' | 'success' | 'warning' | 'destructive' | 'outline'\n > = {\n RUNNING: 'success',\n PENDING: 'warning',\n FAILED: 'destructive',\n COMPLETED: 'default',\n };\n return <Badge variant={variants[run.status] || 'outline'}>{run.status}</Badge>;\n },\n },\n {\n key: 'currentNode',\n header: 'Current Node',\n },\n {\n key: 'createdAt',\n header: 'Started At',\n render: (run: any) => formatDateTime(run.createdAt),\n },\n ];\n\n return (\n <PageLayout\n title=\"Workflows\"\n gradientWord=\"Workflows\"\n description=\"Manage and monitor platform workflows\"\n actions={\n <Button>\n <Plus className=\"mr-2 h-4 w-4\" />\n Create Workflow\n </Button>\n }\n >\n <div className=\"space-y-8\">\n <MetricsGrid>\n <MetricCard\n title=\"Total Workflows\"\n hero\n value={overview?.totalWorkflows ?? 0}\n loading={overviewLoading}\n icon={<GitBranch className=\"h-4 w-4\" />}\n />\n <MetricCard\n title=\"Active Runs\"\n value={overview?.activeWorkflows ?? 0}\n loading={overviewLoading}\n icon={<Play className=\"h-4 w-4\" />}\n trend={overview?.activeWorkflows && overview.activeWorkflows > 0 ? 'up' : 'neutral'}\n />\n <MetricCard\n title=\"Failed (24h)\"\n value={overview?.failedWorkflows ?? 0}\n loading={overviewLoading}\n icon={<AlertCircle className=\"h-4 w-4\" />}\n priority={overview?.failedWorkflows && overview.failedWorkflows > 0 ? 'high' : 'normal'}\n />\n <MetricCard\n title=\"Queued Jobs\"\n value={overview?.queuedJobs ?? 0}\n loading={overviewLoading}\n icon={<Clock className=\"h-4 w-4\" />}\n />\n </MetricsGrid>\n\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold\">Active Workflow Runs</h2>\n <div className=\"relative max-w-sm flex-1 ml-4\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input placeholder=\"Search active runs...\" className=\"pl-9\" />\n </div>\n </div>\n\n <DataTable\n columns={columns}\n data={activeRuns as any[]}\n keyExtractor={(run) => run.runId}\n loading={listLoading}\n emptyState={\n <EmptyState\n illustration=\"empty-data\"\n icon={<Activity className=\"h-8 w-8\" />}\n title=\"No active workflows\"\n description=\"There are currently no workflows running in the system\"\n />\n }\n />\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"WorkflowsPage.js","names":[],"sources":["../../../src/admin/pages/WorkflowsPage.tsx"],"sourcesContent":["import { GitBranch, Search, Plus, Activity, AlertCircle, Clock, Play } from 'lucide-react';\nimport {\n PageLayout,\n Input,\n Button,\n EmptyState,\n MetricCard,\n MetricsGrid,\n DataTable,\n Badge,\n} from '../components';\nimport {\n useGetWorkflowSystemOverviewQuery,\n useListActiveWorkflowsQuery,\n} from '../../generated/wspace-operations';\nimport { formatDateTime } from '../utils/formatters';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\n\nexport function WorkflowsPage() {\n const { data: overviewData, loading: overviewLoading } = useGetWorkflowSystemOverviewQuery();\n\n const { data: listData, loading: listLoading } = useListActiveWorkflowsQuery({\n variables: { limit: 10, offset: 0 },\n });\n\n const overview = overviewData?.systemOverview;\n const activeRuns = listData?.activeWorkflows || [];\n\n const columns = [\n {\n key: 'workflowName',\n header: 'Workflow',\n render: (run: any) => (\n <div className=\"flex flex-col\">\n <span className=\"font-medium text-text-primary\">{run.workflowName}</span>\n <span className=\"text-xs text-text-secondary\">v{run.workflowVersion}</span>\n </div>\n ),\n },\n {\n key: 'runId',\n header: 'Run ID',\n render: (run: any) => <code className=\"text-xs\">{run.runId.substring(0, 8)}...</code>,\n },\n {\n key: 'status',\n header: 'Status',\n render: (run: any) => {\n const variants: Record<\n string,\n 'default' | 'secondary' | 'success' | 'warning' | 'destructive' | 'outline'\n > = {\n RUNNING: 'success',\n PENDING: 'warning',\n FAILED: 'destructive',\n COMPLETED: 'default',\n };\n return <Badge variant={variants[run.status] || 'outline'}>{run.status}</Badge>;\n },\n },\n {\n key: 'currentNode',\n header: 'Current Node',\n },\n {\n key: 'createdAt',\n header: 'Started At',\n render: (run: any) => formatDateTime(run.createdAt),\n },\n ];\n\n return (\n <PageLayout\n title=\"Workflows\"\n gradientWord=\"Workflows\"\n description=\"Manage and monitor platform workflows\"\n actions={\n <Button>\n <Plus className=\"mr-2 h-4 w-4\" />\n Create Workflow\n </Button>\n }\n >\n <div className=\"space-y-8\">\n <PagePurpose>\n Workflows are the automated, multi-step processes running across the platform. Use this\n page to see how many are defined, watch which runs are live right now, and quickly spot\n ones that have failed or are stuck in the queue so you can investigate.\n </PagePurpose>\n\n <MetricsGrid>\n <MetricCard\n title=\"Total Workflows\"\n hero\n value={overview?.totalWorkflows ?? 0}\n loading={overviewLoading}\n icon={<GitBranch className=\"h-4 w-4\" />}\n />\n <MetricCard\n title=\"Active Runs\"\n value={overview?.activeWorkflows ?? 0}\n loading={overviewLoading}\n icon={<Play className=\"h-4 w-4\" />}\n trend={overview?.activeWorkflows && overview.activeWorkflows > 0 ? 'up' : 'neutral'}\n />\n <MetricCard\n title=\"Failed (24h)\"\n value={overview?.failedWorkflows ?? 0}\n loading={overviewLoading}\n icon={<AlertCircle className=\"h-4 w-4\" />}\n priority={overview?.failedWorkflows && overview.failedWorkflows > 0 ? 'high' : 'normal'}\n />\n <MetricCard\n title=\"Queued Jobs\"\n value={overview?.queuedJobs ?? 0}\n loading={overviewLoading}\n icon={<Clock className=\"h-4 w-4\" />}\n />\n </MetricsGrid>\n\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold\">Active Workflow Runs</h2>\n <div className=\"relative max-w-sm flex-1 ml-4\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input placeholder=\"Search active runs...\" className=\"pl-9\" />\n </div>\n </div>\n\n <DataTable\n columns={columns}\n data={activeRuns as any[]}\n keyExtractor={(run) => run.runId}\n loading={listLoading}\n emptyState={\n <EmptyState\n illustration=\"empty-data\"\n icon={<Activity className=\"h-8 w-8\" />}\n title=\"No active workflows\"\n description=\"There are currently no workflows running in the system\"\n />\n }\n />\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;AAkBA,SAAgB,IAAgB;CAC9B,IAAM,EAAE,MAAM,GAAc,SAAS,MAAoB,GAAmC,EAEtF,EAAE,MAAM,GAAU,SAAS,MAAgB,EAA4B,EAC3E,WAAW;EAAE,OAAO;EAAI,QAAQ;EAAG,EACpC,CAAC,EAEI,IAAW,GAAc,gBACzB,IAAa,GAAU,mBAAmB,EAAE;AA6ClD,QACE,kBAAC,GAAD;EACE,OAAM;EACN,cAAa;EACb,aAAY;EACZ,SACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAAA,kBAE1B,EAAA,CAAA;YAGX,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,GAAD,EAAA,UAAa,2PAIC,CAAA;IAEd,kBAAC,GAAD,EAAA,UAAA;KACE,kBAAC,GAAD;MACE,OAAM;MACN,MAAA;MACA,OAAO,GAAU,kBAAkB;MACnC,SAAS;MACT,MAAM,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA;MACvC,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,GAAU,mBAAmB;MACpC,SAAS;MACT,MAAM,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;MAClC,OAAO,GAAU,mBAAmB,EAAS,kBAAkB,IAAI,OAAO;MAC1E,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,GAAU,mBAAmB;MACpC,SAAS;MACT,MAAM,kBAAC,GAAD,EAAa,WAAU,WAAY,CAAA;MACzC,UAAU,GAAU,mBAAmB,EAAS,kBAAkB,IAAI,SAAS;MAC/E,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,GAAU,cAAc;MAC/B,SAAS;MACT,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;MACnC,CAAA;KACU,EAAA,CAAA;IAEd,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,MAAD;OAAI,WAAU;iBAAwB;OAAyB,CAAA,EAC/D,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,wEAAyE,CAAA,EAC3F,kBAAC,GAAD;QAAO,aAAY;QAAwB,WAAU;QAAS,CAAA,CAC1D;SACF;SAEN,kBAAC,GAAD;MACW,SAtGH;OACd;QACE,KAAK;QACL,QAAQ;QACR,SAAS,MACP,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,QAAD;UAAM,WAAU;oBAAiC,EAAI;UAAoB,CAAA,EACzE,kBAAC,QAAD;UAAM,WAAU;oBAAhB,CAA8C,KAAE,EAAI,gBAAuB;YACvE;;QAET;OACD;QACE,KAAK;QACL,QAAQ;QACR,SAAS,MAAa,kBAAC,QAAD;SAAM,WAAU;mBAAhB,CAA2B,EAAI,MAAM,UAAU,GAAG,EAAE,EAAC,MAAU;;QACtF;OACD;QACE,KAAK;QACL,QAAQ;QACR,SAAS,MAUA,kBAAC,GAAD;SAAO,SANV;UACF,SAAS;UACT,SAAS;UACT,QAAQ;UACR,WAAW;UACZ,CAC+B,EAAI,WAAW;mBAAY,EAAI;SAAe,CAAA;QAEjF;OACD;QACE,KAAK;QACL,QAAQ;QACT;OACD;QACE,KAAK;QACL,QAAQ;QACR,SAAS,MAAa,EAAe,EAAI,UAAU;QACpD;OACF;MA8DS,MAAM;MACN,eAAe,MAAQ,EAAI;MAC3B,SAAS;MACT,YACE,kBAAC,GAAD;OACE,cAAa;OACb,MAAM,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;OACtC,OAAM;OACN,aAAY;OACZ,CAAA;MAEJ,CAAA,CACE;;IACF;;EACK,CAAA"}
|