@burdenoff/microfe-admin 2026.624.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/DataTable.js +5 -5
- package/dist/admin/components/DataTable.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/MetricCard.js +40 -37
- 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/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 +48 -41
- 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 +94 -77
- package/dist/admin/pages/AuditLogsPage.js.map +1 -1
- package/dist/admin/pages/BillingPage.js +70 -18
- package/dist/admin/pages/BillingPage.js.map +1 -1
- package/dist/admin/pages/CommunicationsPage.js +98 -90
- package/dist/admin/pages/CommunicationsPage.js.map +1 -1
- package/dist/admin/pages/ContentPagesPage.js +276 -259
- package/dist/admin/pages/ContentPagesPage.js.map +1 -1
- package/dist/admin/pages/DashboardPage.js +114 -82
- package/dist/admin/pages/DashboardPage.js.map +1 -1
- package/dist/admin/pages/FeatureFlagsPage.js +94 -74
- package/dist/admin/pages/FeatureFlagsPage.js.map +1 -1
- package/dist/admin/pages/OrganizationsListPage.js +106 -86
- package/dist/admin/pages/OrganizationsListPage.js.map +1 -1
- package/dist/admin/pages/ProductsPage.js +139 -128
- package/dist/admin/pages/ProductsPage.js.map +1 -1
- package/dist/admin/pages/ReferralsPage.js +74 -52
- package/dist/admin/pages/ReferralsPage.js.map +1 -1
- package/dist/admin/pages/RolesPermissionsPage.js +137 -116
- package/dist/admin/pages/RolesPermissionsPage.js.map +1 -1
- package/dist/admin/pages/SREPage.js +38 -32
- package/dist/admin/pages/SREPage.js.map +1 -1
- package/dist/admin/pages/SandboxSettingsPage.js +361 -322
- package/dist/admin/pages/SandboxSettingsPage.js.map +1 -1
- package/dist/admin/pages/SettingsPage.js +145 -130
- package/dist/admin/pages/SettingsPage.js.map +1 -1
- package/dist/admin/pages/SyncFailuresPage.js +123 -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 +88 -71
- package/dist/admin/pages/TenantsListPage.js.map +1 -1
- package/dist/admin/pages/UsersListPage.js +129 -112
- package/dist/admin/pages/UsersListPage.js.map +1 -1
- package/dist/admin/pages/WaitlistPage.js +80 -63
- package/dist/admin/pages/WaitlistPage.js.map +1 -1
- package/dist/admin/pages/WorkflowsPage.js +108 -100
- package/dist/admin/pages/WorkflowsPage.js.map +1 -1
- package/dist/admin/pages/WorkspacesListPage.js +62 -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,32 +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",
|
|
171
|
+
description: `Product waitlist entries${Z ? ` \u00b7 ${Z} total` : ""}`,
|
|
170
172
|
children: /* @__PURE__ */ b("div", {
|
|
171
173
|
className: "space-y-4",
|
|
172
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
|
+
}),
|
|
173
189
|
/* @__PURE__ */ b("div", {
|
|
174
190
|
className: "flex flex-wrap items-center gap-3",
|
|
175
191
|
children: [/* @__PURE__ */ b("div", {
|
|
176
192
|
className: "relative max-w-sm flex-1",
|
|
177
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, {
|
|
178
194
|
placeholder: "Search by email, name, or company...",
|
|
179
|
-
value:
|
|
195
|
+
value: F,
|
|
180
196
|
onChange: (e) => {
|
|
181
|
-
|
|
197
|
+
P(1), I(e.target.value);
|
|
182
198
|
},
|
|
183
199
|
className: "pl-9"
|
|
184
200
|
})]
|
|
185
201
|
}), /* @__PURE__ */ b("select", {
|
|
186
|
-
value:
|
|
202
|
+
value: L,
|
|
187
203
|
onChange: (e) => {
|
|
188
|
-
|
|
204
|
+
P(1), R(e.target.value);
|
|
189
205
|
},
|
|
190
206
|
className: "rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm",
|
|
191
207
|
children: [
|
|
@@ -208,27 +224,28 @@ function k() {
|
|
|
208
224
|
]
|
|
209
225
|
})]
|
|
210
226
|
}),
|
|
211
|
-
|
|
227
|
+
U && /* @__PURE__ */ b("div", {
|
|
212
228
|
className: "rounded-md border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text",
|
|
213
|
-
children: ["Failed to load waitlist: ",
|
|
229
|
+
children: ["Failed to load waitlist: ", U.message]
|
|
214
230
|
}),
|
|
215
231
|
/* @__PURE__ */ y(o, {
|
|
216
|
-
columns:
|
|
217
|
-
data:
|
|
232
|
+
columns: ae,
|
|
233
|
+
data: $,
|
|
218
234
|
keyExtractor: (e) => e.id,
|
|
219
|
-
loading:
|
|
235
|
+
loading: V,
|
|
220
236
|
emptyState: /* @__PURE__ */ y(n, {
|
|
237
|
+
illustration: "empty-search",
|
|
221
238
|
icon: /* @__PURE__ */ y(m, { className: "h-8 w-8" }),
|
|
222
239
|
title: "No waitlist entries",
|
|
223
240
|
description: "No entries match your current filters"
|
|
224
241
|
})
|
|
225
242
|
}),
|
|
226
|
-
|
|
227
|
-
page:
|
|
228
|
-
totalPages:
|
|
229
|
-
totalItems:
|
|
230
|
-
pageSize:
|
|
231
|
-
onPageChange:
|
|
243
|
+
Z > w && /* @__PURE__ */ y(s, {
|
|
244
|
+
page: N,
|
|
245
|
+
totalPages: Q,
|
|
246
|
+
totalItems: Z,
|
|
247
|
+
pageSize: w,
|
|
248
|
+
onPageChange: P
|
|
232
249
|
})
|
|
233
250
|
]
|
|
234
251
|
})
|
|
@@ -236,6 +253,6 @@ function k() {
|
|
|
236
253
|
});
|
|
237
254
|
}
|
|
238
255
|
//#endregion
|
|
239
|
-
export {
|
|
256
|
+
export { A as WaitlistPage };
|
|
240
257
|
|
|
241
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 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 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,120 +7,128 @@ 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",
|
|
19
|
+
gradientWord: "Workflows",
|
|
18
20
|
description: "Manage and monitor platform workflows",
|
|
19
21
|
actions: /* @__PURE__ */ y(t, { children: [/* @__PURE__ */ v(g, { className: "mr-2 h-4 w-4" }), "Create Workflow"] }),
|
|
20
22
|
children: /* @__PURE__ */ y("div", {
|
|
21
23
|
className: "space-y-8",
|
|
22
|
-
children: [
|
|
23
|
-
/* @__PURE__ */ v(
|
|
24
|
-
|
|
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
|
-
|
|
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
|
+
})]
|
|
61
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
|
+
})
|
|
62
125
|
})]
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
{
|
|
66
|
-
key: "workflowName",
|
|
67
|
-
header: "Workflow",
|
|
68
|
-
render: (e) => /* @__PURE__ */ y("div", {
|
|
69
|
-
className: "flex flex-col",
|
|
70
|
-
children: [/* @__PURE__ */ v("span", {
|
|
71
|
-
className: "font-medium text-text-primary",
|
|
72
|
-
children: e.workflowName
|
|
73
|
-
}), /* @__PURE__ */ y("span", {
|
|
74
|
-
className: "text-xs text-text-secondary",
|
|
75
|
-
children: ["v", e.workflowVersion]
|
|
76
|
-
})]
|
|
77
|
-
})
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
key: "runId",
|
|
81
|
-
header: "Run ID",
|
|
82
|
-
render: (e) => /* @__PURE__ */ y("code", {
|
|
83
|
-
className: "text-xs",
|
|
84
|
-
children: [e.runId.substring(0, 8), "..."]
|
|
85
|
-
})
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
key: "status",
|
|
89
|
-
header: "Status",
|
|
90
|
-
render: (t) => /* @__PURE__ */ v(e, {
|
|
91
|
-
variant: {
|
|
92
|
-
RUNNING: "success",
|
|
93
|
-
PENDING: "warning",
|
|
94
|
-
FAILED: "destructive",
|
|
95
|
-
COMPLETED: "default"
|
|
96
|
-
}[t.status] || "outline",
|
|
97
|
-
children: t.status
|
|
98
|
-
})
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
key: "currentNode",
|
|
102
|
-
header: "Current Node"
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
key: "createdAt",
|
|
106
|
-
header: "Started At",
|
|
107
|
-
render: (e) => a(e.createdAt)
|
|
108
|
-
}
|
|
109
|
-
],
|
|
110
|
-
data: T,
|
|
111
|
-
keyExtractor: (e) => e.runId,
|
|
112
|
-
loading: C,
|
|
113
|
-
emptyState: /* @__PURE__ */ v(n, {
|
|
114
|
-
icon: /* @__PURE__ */ v(d, { className: "h-8 w-8" }),
|
|
115
|
-
title: "No active workflows",
|
|
116
|
-
description: "There are currently no workflows running in the system"
|
|
117
|
-
})
|
|
118
|
-
})]
|
|
119
|
-
})]
|
|
126
|
+
})
|
|
127
|
+
]
|
|
120
128
|
})
|
|
121
129
|
});
|
|
122
130
|
}
|
|
123
131
|
//#endregion
|
|
124
|
-
export {
|
|
132
|
+
export { x as WorkflowsPage };
|
|
125
133
|
|
|
126
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 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 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 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"}
|