@strapi-community/plugin-better-auth-dashboard 1.0.0-alpha.1 → 1.0.0-alpha.10
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/README.md +101 -0
- package/dist/admin/{Root-hwPhIfaT.mjs → Root-6Fpvuogl.mjs} +2435 -1507
- package/dist/admin/{Root-Bl4iPGDu.js → Root-CN4cZKCJ.js} +2427 -1499
- package/dist/admin/index-WTRykUoM.js +116 -0
- package/dist/admin/index-X2-Fh6sz.mjs +117 -0
- package/dist/admin/index.js +2 -2
- package/dist/admin/index.mjs +2 -2
- package/dist/server/index.js +261 -19
- package/dist/server/index.mjs +261 -19
- package/package.json +10 -8
- package/dist/admin/index-A9PUvldu.js +0 -26
- package/dist/admin/index-Cvcysa5M.mjs +0 -27
|
@@ -3,13 +3,15 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
3
3
|
const jsxRuntime = require("react/jsx-runtime");
|
|
4
4
|
const react = require("react");
|
|
5
5
|
const reactQuery = require("react-query");
|
|
6
|
+
const reactRouterDom = require("react-router-dom");
|
|
6
7
|
const designSystem = require("@strapi/design-system");
|
|
7
|
-
const
|
|
8
|
+
const admin = require("@strapi/strapi/admin");
|
|
8
9
|
const client$2 = require("@better-auth/infra/client");
|
|
9
10
|
const client$1 = require("better-auth/client");
|
|
11
|
+
const reactIntl = require("react-intl");
|
|
12
|
+
const index = require("./index-WTRykUoM.js");
|
|
10
13
|
const icons = require("@strapi/icons");
|
|
11
|
-
const
|
|
12
|
-
const index = require("./index-A9PUvldu.js");
|
|
14
|
+
const styled = require("styled-components");
|
|
13
15
|
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
14
16
|
const styled__default = /* @__PURE__ */ _interopDefault(styled);
|
|
15
17
|
const dashPathMethods = () => ({
|
|
@@ -103,6 +105,315 @@ const client = client$1.createAuthClient({
|
|
|
103
105
|
headers: getAuthHeaders()
|
|
104
106
|
}
|
|
105
107
|
});
|
|
108
|
+
const getTranslation = (id) => `${index.PLUGIN_ID}.${id}`;
|
|
109
|
+
const HEIGHT_TOP_NAVIGATION = "6.4rem";
|
|
110
|
+
const HEIGHT_TOP_NAVIGATION_MEDIUM = "5.6rem";
|
|
111
|
+
const WIDTH_SIDE_NAVIGATION = "23.2rem";
|
|
112
|
+
const MainSubNav = styled__default.default(designSystem.SubNav)`
|
|
113
|
+
width: 100%;
|
|
114
|
+
height: 100%;
|
|
115
|
+
overflow: hidden;
|
|
116
|
+
background-color: ${({ theme }) => theme.colors.neutral0};
|
|
117
|
+
display: flex;
|
|
118
|
+
flex-direction: column;
|
|
119
|
+
border-right: 0;
|
|
120
|
+
box-shadow: none;
|
|
121
|
+
position: relative;
|
|
122
|
+
|
|
123
|
+
${({ theme }) => theme.breakpoints.medium} {
|
|
124
|
+
width: ${WIDTH_SIDE_NAVIGATION};
|
|
125
|
+
position: sticky;
|
|
126
|
+
top: 0;
|
|
127
|
+
border-right: 1px solid ${({ theme }) => theme.colors.neutral150};
|
|
128
|
+
overscroll-behavior: contain;
|
|
129
|
+
}
|
|
130
|
+
`;
|
|
131
|
+
const Main = ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx(MainSubNav, { ...props, children });
|
|
132
|
+
const StyledLink = styled__default.default(reactRouterDom.NavLink)`
|
|
133
|
+
display: flex;
|
|
134
|
+
align-items: center;
|
|
135
|
+
justify-content: space-between;
|
|
136
|
+
text-decoration: none;
|
|
137
|
+
height: 32px;
|
|
138
|
+
color: ${({ theme }) => theme.colors.neutral800};
|
|
139
|
+
|
|
140
|
+
&.active > div {
|
|
141
|
+
background-color: ${({ theme }) => theme.colors.primary100};
|
|
142
|
+
color: ${({ theme }) => theme.colors.primary700};
|
|
143
|
+
font-weight: 500;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
&:hover.active > div {
|
|
147
|
+
background-color: ${({ theme }) => theme.colors.primary100};
|
|
148
|
+
color: ${({ theme }) => theme.colors.primary700};
|
|
149
|
+
font-weight: 500;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
&:hover > div {
|
|
153
|
+
background-color: ${({ theme }) => theme.colors.neutral100};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
&:focus-visible {
|
|
157
|
+
outline-offset: -2px;
|
|
158
|
+
}
|
|
159
|
+
`;
|
|
160
|
+
const Link = (props) => {
|
|
161
|
+
const { to, label, endAction, handleClick, ...rest } = props;
|
|
162
|
+
return /* @__PURE__ */ jsxRuntime.jsx(StyledLink, { to, ...rest, onClick: handleClick, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
163
|
+
designSystem.Box,
|
|
164
|
+
{
|
|
165
|
+
width: "100%",
|
|
166
|
+
paddingLeft: 3,
|
|
167
|
+
paddingRight: 3,
|
|
168
|
+
paddingTop: { initial: 1, large: 0 },
|
|
169
|
+
paddingBottom: { initial: 1, large: 0 },
|
|
170
|
+
borderRadius: 1,
|
|
171
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
172
|
+
designSystem.Flex,
|
|
173
|
+
{
|
|
174
|
+
justifyContent: "space-between",
|
|
175
|
+
width: "100%",
|
|
176
|
+
gap: { initial: 2, large: 1 },
|
|
177
|
+
children: [
|
|
178
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
179
|
+
designSystem.Typography,
|
|
180
|
+
{
|
|
181
|
+
tag: "div",
|
|
182
|
+
lineHeight: "32px",
|
|
183
|
+
width: { initial: "80dvw", medium: "100%" },
|
|
184
|
+
overflow: "hidden",
|
|
185
|
+
style: { textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
186
|
+
children: label
|
|
187
|
+
}
|
|
188
|
+
),
|
|
189
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 2, children: endAction })
|
|
190
|
+
]
|
|
191
|
+
}
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
) });
|
|
195
|
+
};
|
|
196
|
+
const StyledHeader = styled__default.default(designSystem.Flex)`
|
|
197
|
+
flex: 0 0 ${HEIGHT_TOP_NAVIGATION};
|
|
198
|
+
height: ${HEIGHT_TOP_NAVIGATION};
|
|
199
|
+
|
|
200
|
+
${({ theme }) => theme.breakpoints.medium} {
|
|
201
|
+
flex: 0 0 ${HEIGHT_TOP_NAVIGATION_MEDIUM};
|
|
202
|
+
height: ${HEIGHT_TOP_NAVIGATION_MEDIUM};
|
|
203
|
+
}
|
|
204
|
+
`;
|
|
205
|
+
const Header = ({ label }) => {
|
|
206
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
207
|
+
StyledHeader,
|
|
208
|
+
{
|
|
209
|
+
justifyContent: "space-between",
|
|
210
|
+
paddingLeft: 5,
|
|
211
|
+
paddingRight: 5,
|
|
212
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "beta", tag: "h2", children: label })
|
|
213
|
+
}
|
|
214
|
+
);
|
|
215
|
+
};
|
|
216
|
+
const Sections = ({
|
|
217
|
+
children,
|
|
218
|
+
...props
|
|
219
|
+
}) => {
|
|
220
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
221
|
+
designSystem.Box,
|
|
222
|
+
{
|
|
223
|
+
paddingTop: { initial: 5, large: 4 },
|
|
224
|
+
paddingBottom: { initial: 5, large: 4 },
|
|
225
|
+
paddingLeft: { initial: 3, large: 0 },
|
|
226
|
+
paddingRight: { initial: 3, large: 0 },
|
|
227
|
+
maxWidth: { initial: "100%", medium: WIDTH_SIDE_NAVIGATION },
|
|
228
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { tag: "ul", gap: 6, direction: "column", alignItems: "stretch", ...props, children: children.map((child, index2) => {
|
|
229
|
+
return /* @__PURE__ */ jsxRuntime.jsx("li", { children: child }, index2);
|
|
230
|
+
}) })
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
};
|
|
234
|
+
const Section = ({
|
|
235
|
+
label,
|
|
236
|
+
children,
|
|
237
|
+
badgeLabel
|
|
238
|
+
}) => {
|
|
239
|
+
const listId = react.useId();
|
|
240
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
|
|
241
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
242
|
+
designSystem.Box,
|
|
243
|
+
{
|
|
244
|
+
paddingLeft: {
|
|
245
|
+
initial: 3,
|
|
246
|
+
large: 5
|
|
247
|
+
},
|
|
248
|
+
paddingRight: {
|
|
249
|
+
initial: 3,
|
|
250
|
+
large: 5
|
|
251
|
+
},
|
|
252
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { position: "relative", justifyContent: "space-between", gap: 2, children: [
|
|
253
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", children: label }) }) }),
|
|
254
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 1, children: badgeLabel && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "neutral150", textColor: "neutral600", children: badgeLabel }) })
|
|
255
|
+
] })
|
|
256
|
+
}
|
|
257
|
+
),
|
|
258
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
259
|
+
designSystem.Flex,
|
|
260
|
+
{
|
|
261
|
+
tag: "ol",
|
|
262
|
+
id: listId,
|
|
263
|
+
direction: "column",
|
|
264
|
+
gap: { initial: 2, large: "2px" },
|
|
265
|
+
alignItems: "stretch",
|
|
266
|
+
marginLeft: {
|
|
267
|
+
initial: 0,
|
|
268
|
+
large: 2
|
|
269
|
+
},
|
|
270
|
+
marginRight: {
|
|
271
|
+
initial: 0,
|
|
272
|
+
large: 2
|
|
273
|
+
},
|
|
274
|
+
children: children.map((child, index2) => {
|
|
275
|
+
return /* @__PURE__ */ jsxRuntime.jsx("li", { children: child }, index2);
|
|
276
|
+
})
|
|
277
|
+
}
|
|
278
|
+
)
|
|
279
|
+
] });
|
|
280
|
+
};
|
|
281
|
+
styled__default.default.button`
|
|
282
|
+
cursor: pointer;
|
|
283
|
+
width: 100%;
|
|
284
|
+
border: none;
|
|
285
|
+
padding: 0;
|
|
286
|
+
background: transparent;
|
|
287
|
+
display: flex;
|
|
288
|
+
align-items: center;
|
|
289
|
+
border-radius: ${({ theme }) => theme.borderRadius};
|
|
290
|
+
padding-left: ${({ theme }) => theme.spaces[3]};
|
|
291
|
+
padding-right: ${({ theme }) => theme.spaces[3]};
|
|
292
|
+
padding-top: ${({ theme }) => theme.spaces[2]};
|
|
293
|
+
padding-bottom: ${({ theme }) => theme.spaces[2]};
|
|
294
|
+
|
|
295
|
+
&:hover {
|
|
296
|
+
background-color: ${({ theme }) => theme.colors.neutral100};
|
|
297
|
+
}
|
|
298
|
+
`;
|
|
299
|
+
styled__default.default.li`
|
|
300
|
+
${StyledLink} > div {
|
|
301
|
+
padding-left: 36px;
|
|
302
|
+
}
|
|
303
|
+
`;
|
|
304
|
+
styled__default.default(designSystem.Box)`
|
|
305
|
+
width: 100%;
|
|
306
|
+
|
|
307
|
+
${MainSubNav} {
|
|
308
|
+
background-color: transparent;
|
|
309
|
+
border-right: none;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
${({ theme }) => theme.breakpoints.medium} {
|
|
313
|
+
overflow: hidden;
|
|
314
|
+
|
|
315
|
+
${MainSubNav} {
|
|
316
|
+
top: 0;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
`;
|
|
320
|
+
const Content = ({ children }) => {
|
|
321
|
+
return /* @__PURE__ */ jsxRuntime.jsx(designSystem.ScrollArea, { children });
|
|
322
|
+
};
|
|
323
|
+
const SubNav = {
|
|
324
|
+
Main,
|
|
325
|
+
Content,
|
|
326
|
+
Header,
|
|
327
|
+
Link,
|
|
328
|
+
Sections,
|
|
329
|
+
Section
|
|
330
|
+
};
|
|
331
|
+
const sections = [
|
|
332
|
+
{
|
|
333
|
+
id: "api-roles",
|
|
334
|
+
intlLabel: {
|
|
335
|
+
id: getTranslation("settings.general"),
|
|
336
|
+
defaultMessage: "General"
|
|
337
|
+
},
|
|
338
|
+
links: [
|
|
339
|
+
{
|
|
340
|
+
id: "overview",
|
|
341
|
+
intlLabel: {
|
|
342
|
+
id: getTranslation("settings.overview"),
|
|
343
|
+
defaultMessage: "Overview"
|
|
344
|
+
},
|
|
345
|
+
to: "/plugins/better-auth-dashboard/overview",
|
|
346
|
+
testid: "nav-overview"
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
id: "users",
|
|
350
|
+
intlLabel: {
|
|
351
|
+
id: getTranslation("settings.users"),
|
|
352
|
+
defaultMessage: "User Management"
|
|
353
|
+
},
|
|
354
|
+
to: "/plugins/better-auth-dashboard/users",
|
|
355
|
+
testid: "nav-users"
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
id: "organizations",
|
|
359
|
+
intlLabel: {
|
|
360
|
+
id: getTranslation("settings.organizations"),
|
|
361
|
+
defaultMessage: "Organization Management"
|
|
362
|
+
},
|
|
363
|
+
to: "/plugins/better-auth-dashboard/organizations",
|
|
364
|
+
testid: "nav-organizations"
|
|
365
|
+
}
|
|
366
|
+
]
|
|
367
|
+
}
|
|
368
|
+
];
|
|
369
|
+
const Nav = ({ isFullPage = false, orgEnabled = false }) => {
|
|
370
|
+
const { formatMessage } = reactIntl.useIntl();
|
|
371
|
+
const overviewRBAC = admin.useRBAC(index.PERMISSIONS.overview);
|
|
372
|
+
const userRBAC = admin.useRBAC(index.PERMISSIONS.user);
|
|
373
|
+
const organizationRBAC = admin.useRBAC(index.PERMISSIONS.organization);
|
|
374
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(SubNav.Main, { children: [
|
|
375
|
+
!isFullPage && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
376
|
+
/* @__PURE__ */ jsxRuntime.jsx(SubNav.Header, { label: "Authentication" }),
|
|
377
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, {})
|
|
378
|
+
] }),
|
|
379
|
+
/* @__PURE__ */ jsxRuntime.jsxs(SubNav.Content, { children: [
|
|
380
|
+
isFullPage && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
381
|
+
/* @__PURE__ */ jsxRuntime.jsx(SubNav.Header, { label: "Authentication" }),
|
|
382
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, {})
|
|
383
|
+
] }),
|
|
384
|
+
/* @__PURE__ */ jsxRuntime.jsx(SubNav.Sections, { "data-testid": "main-nav", children: sections.map((section) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
385
|
+
SubNav.Section,
|
|
386
|
+
{
|
|
387
|
+
label: formatMessage(section.intlLabel),
|
|
388
|
+
children: section.links.filter((link) => orgEnabled || link.id !== "organizations").filter((link) => {
|
|
389
|
+
if (link.id === "overview") {
|
|
390
|
+
return overviewRBAC.allowedActions.canRead;
|
|
391
|
+
}
|
|
392
|
+
if (link.id === "users") {
|
|
393
|
+
return userRBAC.allowedActions.canRead;
|
|
394
|
+
}
|
|
395
|
+
if (link.id === "organizations") {
|
|
396
|
+
return organizationRBAC.allowedActions.canRead;
|
|
397
|
+
}
|
|
398
|
+
return true;
|
|
399
|
+
}).map((link) => {
|
|
400
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
401
|
+
SubNav.Link,
|
|
402
|
+
{
|
|
403
|
+
to: link.to,
|
|
404
|
+
label: formatMessage(link.intlLabel),
|
|
405
|
+
"data-testid": link.testid,
|
|
406
|
+
children: formatMessage(link.intlLabel)
|
|
407
|
+
},
|
|
408
|
+
link.id
|
|
409
|
+
);
|
|
410
|
+
})
|
|
411
|
+
},
|
|
412
|
+
section.id
|
|
413
|
+
)) })
|
|
414
|
+
] })
|
|
415
|
+
] });
|
|
416
|
+
};
|
|
106
417
|
function useDashConfig() {
|
|
107
418
|
return reactQuery.useQuery({
|
|
108
419
|
queryKey: ["dash-config"],
|
|
@@ -119,6 +430,74 @@ function useDashConfig() {
|
|
|
119
430
|
function hasPlugin(config, id) {
|
|
120
431
|
return config?.plugins.some((p) => p.id === id) ?? false;
|
|
121
432
|
}
|
|
433
|
+
const RESPONSIVE_DEFAULT_SPACING = {
|
|
434
|
+
initial: 4,
|
|
435
|
+
medium: 6,
|
|
436
|
+
large: 10
|
|
437
|
+
};
|
|
438
|
+
const App = () => {
|
|
439
|
+
const { data: config, isLoading, isError, error } = useDashConfig();
|
|
440
|
+
const orgEnabled = hasPlugin(config, "organization");
|
|
441
|
+
const orgOptionsQuery = reactQuery.useQuery({
|
|
442
|
+
queryKey: ["dash-org-options"],
|
|
443
|
+
queryFn: async () => {
|
|
444
|
+
const result = await client.dash.organization.options();
|
|
445
|
+
if (result.error) return { teamsEnabled: false };
|
|
446
|
+
return result.data ?? { teamsEnabled: false };
|
|
447
|
+
},
|
|
448
|
+
enabled: orgEnabled
|
|
449
|
+
});
|
|
450
|
+
const teamsEnabled = orgEnabled ? orgOptionsQuery.data?.teamsEnabled ?? false : false;
|
|
451
|
+
if (isLoading) {
|
|
452
|
+
return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", alignItems: "center", padding: 12, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: "Loading Better Auth dashboard…" }) });
|
|
453
|
+
}
|
|
454
|
+
if (isError) {
|
|
455
|
+
return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 6, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
456
|
+
designSystem.Alert,
|
|
457
|
+
{
|
|
458
|
+
closeLabel: "Close",
|
|
459
|
+
title: "Error loading configuration",
|
|
460
|
+
variant: "danger",
|
|
461
|
+
children: error?.message
|
|
462
|
+
}
|
|
463
|
+
) });
|
|
464
|
+
}
|
|
465
|
+
if (!config) return null;
|
|
466
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
467
|
+
"div",
|
|
468
|
+
{
|
|
469
|
+
"data-testid": "dashboard-root",
|
|
470
|
+
style: { display: "flex", flex: "1 1 auto", minHeight: "100vh" },
|
|
471
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(admin.Layouts.Root, { sideNav: /* @__PURE__ */ jsxRuntime.jsx(Nav, { orgEnabled }), children: [
|
|
472
|
+
/* @__PURE__ */ jsxRuntime.jsx(admin.Page.Title, { children: "Authentication" }),
|
|
473
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
474
|
+
designSystem.Box,
|
|
475
|
+
{
|
|
476
|
+
display: { initial: "block", medium: "none" },
|
|
477
|
+
paddingLeft: RESPONSIVE_DEFAULT_SPACING,
|
|
478
|
+
paddingRight: RESPONSIVE_DEFAULT_SPACING,
|
|
479
|
+
paddingTop: RESPONSIVE_DEFAULT_SPACING,
|
|
480
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(admin.BackButton, { fallback: "/plugins/better-auth-dashboard" })
|
|
481
|
+
}
|
|
482
|
+
),
|
|
483
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Outlet, { context: { config, teamsEnabled } })
|
|
484
|
+
] })
|
|
485
|
+
}
|
|
486
|
+
);
|
|
487
|
+
};
|
|
488
|
+
const ProtectedApp = () => {
|
|
489
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
490
|
+
admin.Page.Protect,
|
|
491
|
+
{
|
|
492
|
+
permissions: [
|
|
493
|
+
...index.PERMISSIONS.overview,
|
|
494
|
+
...index.PERMISSIONS.user,
|
|
495
|
+
...index.PERMISSIONS.organization
|
|
496
|
+
],
|
|
497
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(App, {})
|
|
498
|
+
}
|
|
499
|
+
);
|
|
500
|
+
};
|
|
122
501
|
function ConfirmDialog({
|
|
123
502
|
title,
|
|
124
503
|
message,
|
|
@@ -145,8 +524,9 @@ function ConfirmDialog({
|
|
|
145
524
|
);
|
|
146
525
|
}
|
|
147
526
|
const DASH_CONTEXT_HEADER = "X-Dash-Context";
|
|
148
|
-
const withContext = (ctx) => ({
|
|
527
|
+
const withContext = (ctx, headers) => ({
|
|
149
528
|
headers: {
|
|
529
|
+
...headers,
|
|
150
530
|
[DASH_CONTEXT_HEADER]: btoa(JSON.stringify(ctx))
|
|
151
531
|
}
|
|
152
532
|
});
|
|
@@ -454,11 +834,22 @@ function UserCombobox({
|
|
|
454
834
|
queryKey: ["user-combobox-search", search],
|
|
455
835
|
queryFn: async () => {
|
|
456
836
|
const whereClause = search ? JSON.stringify([
|
|
457
|
-
{
|
|
837
|
+
{
|
|
838
|
+
field: "email",
|
|
839
|
+
operator: "contains",
|
|
840
|
+
value: search,
|
|
841
|
+
connector: "OR"
|
|
842
|
+
},
|
|
843
|
+
{
|
|
844
|
+
field: "name",
|
|
845
|
+
operator: "contains",
|
|
846
|
+
value: search,
|
|
847
|
+
connector: "OR"
|
|
848
|
+
}
|
|
458
849
|
]) : void 0;
|
|
459
850
|
const result = await client.dash.listUsers({
|
|
460
851
|
query: {
|
|
461
|
-
limit: 20,
|
|
852
|
+
limit: search ? 100 : 20,
|
|
462
853
|
offset: 0,
|
|
463
854
|
sortBy: "createdAt",
|
|
464
855
|
sortOrder: "desc",
|
|
@@ -510,7 +901,7 @@ function CreateOrganizationDialog({ teamsEnabled, onClose }) {
|
|
|
510
901
|
...logo ? { logo } : {},
|
|
511
902
|
...teamsEnabled && !skipDefaultTeam && defaultTeamName ? { defaultTeamName } : {}
|
|
512
903
|
},
|
|
513
|
-
withContext({ userId: ownerId, skipDefaultTeam })
|
|
904
|
+
withContext({ userId: ownerId, skipDefaultTeam }, getAuthHeaders())
|
|
514
905
|
);
|
|
515
906
|
if (result.error)
|
|
516
907
|
throw new Error(result.error.message ?? "Create failed");
|
|
@@ -546,6 +937,7 @@ function CreateOrganizationDialog({ teamsEnabled, onClose }) {
|
|
|
546
937
|
loading: createMutation.isLoading,
|
|
547
938
|
disabled: !name || !slug || !ownerId,
|
|
548
939
|
onClick: () => createMutation.mutate(),
|
|
940
|
+
"data-testid": "create-org-submit",
|
|
549
941
|
children: "Create organization"
|
|
550
942
|
}
|
|
551
943
|
)
|
|
@@ -633,6 +1025,159 @@ function CreateOrganizationDialog({ teamsEnabled, onClose }) {
|
|
|
633
1025
|
function slugify(str) {
|
|
634
1026
|
return str.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
635
1027
|
}
|
|
1028
|
+
const EntryRow = styled__default.default.div`
|
|
1029
|
+
display: flex;
|
|
1030
|
+
align-items: center;
|
|
1031
|
+
justify-content: space-between;
|
|
1032
|
+
gap: 8px;
|
|
1033
|
+
padding: 8px 12px;
|
|
1034
|
+
width: 100%;
|
|
1035
|
+
box-sizing: border-box;
|
|
1036
|
+
background: #ffffff;
|
|
1037
|
+
border: 1px solid ${({ theme }) => theme.colors?.neutral200 ?? "#dcdce4"};
|
|
1038
|
+
border-radius: 4px;
|
|
1039
|
+
min-width: 0;
|
|
1040
|
+
`;
|
|
1041
|
+
function getDisplayLabel(doc) {
|
|
1042
|
+
for (const key of ["name", "title", "email", "username", "label", "slug"]) {
|
|
1043
|
+
const v = doc[key];
|
|
1044
|
+
if (typeof v === "string" && v.trim()) return v;
|
|
1045
|
+
}
|
|
1046
|
+
return String(doc.documentId ?? doc.id ?? "");
|
|
1047
|
+
}
|
|
1048
|
+
function normalizeValue(val, cache) {
|
|
1049
|
+
if (!val) return [];
|
|
1050
|
+
if (typeof val === "object" && !Array.isArray(val) && val !== null && "set" in val) {
|
|
1051
|
+
const items2 = val.set ?? [];
|
|
1052
|
+
return items2.filter((item) => item?.documentId).map((item) => ({
|
|
1053
|
+
documentId: item.documentId,
|
|
1054
|
+
label: cache.get(item.documentId) ?? item.documentId
|
|
1055
|
+
}));
|
|
1056
|
+
}
|
|
1057
|
+
const items = Array.isArray(val) ? val : [val];
|
|
1058
|
+
return items.filter(
|
|
1059
|
+
(item) => typeof item === "object" && item !== null && "documentId" in item
|
|
1060
|
+
).map((item) => ({
|
|
1061
|
+
documentId: String(item.documentId),
|
|
1062
|
+
label: getDisplayLabel(item)
|
|
1063
|
+
}));
|
|
1064
|
+
}
|
|
1065
|
+
function RelationField({
|
|
1066
|
+
name,
|
|
1067
|
+
label,
|
|
1068
|
+
attribute,
|
|
1069
|
+
value,
|
|
1070
|
+
onChange,
|
|
1071
|
+
readOnly = false
|
|
1072
|
+
}) {
|
|
1073
|
+
const { get } = admin.useFetchClient();
|
|
1074
|
+
const cache = react.useRef(/* @__PURE__ */ new Map());
|
|
1075
|
+
const [search, setSearch] = react.useState("");
|
|
1076
|
+
const [addKey, setAddKey] = react.useState(0);
|
|
1077
|
+
const target = attribute.target ?? "";
|
|
1078
|
+
const relationType = attribute.relation ?? "manyToOne";
|
|
1079
|
+
const isMulti = relationType === "oneToMany" || relationType === "manyToMany";
|
|
1080
|
+
const currentDocs = normalizeValue(value, cache.current);
|
|
1081
|
+
const selectedIds = new Set(currentDocs.map((d) => d.documentId));
|
|
1082
|
+
const { data: searchResults = [], isLoading } = reactQuery.useQuery({
|
|
1083
|
+
queryKey: ["relation-search", target, search],
|
|
1084
|
+
queryFn: async () => {
|
|
1085
|
+
const params = new URLSearchParams({
|
|
1086
|
+
uid: target,
|
|
1087
|
+
"pagination[pageSize]": "50"
|
|
1088
|
+
});
|
|
1089
|
+
if (search) params.set("filters[name][$containsi]", search);
|
|
1090
|
+
const { data } = await get(
|
|
1091
|
+
`/better-auth-dashboard/db?${params}`
|
|
1092
|
+
);
|
|
1093
|
+
const docs = data.results ?? [];
|
|
1094
|
+
for (const doc of docs) {
|
|
1095
|
+
if (doc.documentId) {
|
|
1096
|
+
cache.current.set(String(doc.documentId), getDisplayLabel(doc));
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
return docs.map((doc) => ({
|
|
1100
|
+
documentId: String(doc.documentId ?? ""),
|
|
1101
|
+
label: getDisplayLabel(doc)
|
|
1102
|
+
})).filter((d) => d.documentId);
|
|
1103
|
+
},
|
|
1104
|
+
keepPreviousData: true,
|
|
1105
|
+
enabled: !readOnly
|
|
1106
|
+
});
|
|
1107
|
+
const availableOptions = searchResults.filter(
|
|
1108
|
+
(r) => !selectedIds.has(r.documentId)
|
|
1109
|
+
);
|
|
1110
|
+
const commit = (ids) => {
|
|
1111
|
+
onChange(name, { set: ids.map((id) => ({ documentId: id })) });
|
|
1112
|
+
};
|
|
1113
|
+
const handleSelect = (val) => {
|
|
1114
|
+
if (!val) return;
|
|
1115
|
+
const opt = searchResults.find((r) => r.documentId === val);
|
|
1116
|
+
if (opt) cache.current.set(val, opt.label);
|
|
1117
|
+
if (isMulti) {
|
|
1118
|
+
if (selectedIds.has(val)) return;
|
|
1119
|
+
commit([...currentDocs.map((d) => d.documentId), val]);
|
|
1120
|
+
} else {
|
|
1121
|
+
commit([val]);
|
|
1122
|
+
}
|
|
1123
|
+
setSearch("");
|
|
1124
|
+
setAddKey((k) => k + 1);
|
|
1125
|
+
};
|
|
1126
|
+
const handleRemove = (docId) => {
|
|
1127
|
+
commit(
|
|
1128
|
+
currentDocs.filter((d) => d.documentId !== docId).map((d) => d.documentId)
|
|
1129
|
+
);
|
|
1130
|
+
};
|
|
1131
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { style: { width: "100%" }, children: [
|
|
1132
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: label }),
|
|
1133
|
+
!readOnly && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1134
|
+
designSystem.Combobox,
|
|
1135
|
+
{
|
|
1136
|
+
value: "",
|
|
1137
|
+
onChange: (val) => handleSelect(val),
|
|
1138
|
+
onInputChange: (e) => setSearch(e.target.value),
|
|
1139
|
+
loading: isLoading,
|
|
1140
|
+
placeholder: "Search…",
|
|
1141
|
+
children: availableOptions.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.ComboboxOption, { value: opt.documentId, children: opt.label }, opt.documentId))
|
|
1142
|
+
},
|
|
1143
|
+
addKey
|
|
1144
|
+
),
|
|
1145
|
+
currentDocs.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1146
|
+
designSystem.Flex,
|
|
1147
|
+
{
|
|
1148
|
+
direction: "column",
|
|
1149
|
+
gap: 1,
|
|
1150
|
+
style: { marginTop: 8, width: "100%" },
|
|
1151
|
+
children: currentDocs.map((doc) => /* @__PURE__ */ jsxRuntime.jsxs(EntryRow, { children: [
|
|
1152
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1153
|
+
designSystem.Typography,
|
|
1154
|
+
{
|
|
1155
|
+
variant: "omega",
|
|
1156
|
+
textColor: "neutral800",
|
|
1157
|
+
style: {
|
|
1158
|
+
overflow: "hidden",
|
|
1159
|
+
textOverflow: "ellipsis",
|
|
1160
|
+
whiteSpace: "nowrap",
|
|
1161
|
+
flex: 1,
|
|
1162
|
+
minWidth: 0
|
|
1163
|
+
},
|
|
1164
|
+
children: doc.label
|
|
1165
|
+
}
|
|
1166
|
+
),
|
|
1167
|
+
!readOnly && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1168
|
+
designSystem.IconButton,
|
|
1169
|
+
{
|
|
1170
|
+
label: "Remove",
|
|
1171
|
+
size: "S",
|
|
1172
|
+
onClick: () => handleRemove(doc.documentId),
|
|
1173
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
|
|
1174
|
+
}
|
|
1175
|
+
)
|
|
1176
|
+
] }, doc.documentId))
|
|
1177
|
+
}
|
|
1178
|
+
)
|
|
1179
|
+
] });
|
|
1180
|
+
}
|
|
636
1181
|
function makeLabel(name) {
|
|
637
1182
|
return name.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase()).trim();
|
|
638
1183
|
}
|
|
@@ -756,24 +1301,15 @@ function DynamicField({
|
|
|
756
1301
|
] });
|
|
757
1302
|
}
|
|
758
1303
|
if (type === "relation") {
|
|
759
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
760
|
-
|
|
1304
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1305
|
+
RelationField,
|
|
761
1306
|
{
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
{
|
|
769
|
-
value: value != null ? String(value) : "",
|
|
770
|
-
onChange: (e) => onChange(name, e.target.value),
|
|
771
|
-
disabled: readOnly,
|
|
772
|
-
placeholder: "ID…"
|
|
773
|
-
}
|
|
774
|
-
),
|
|
775
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
|
|
776
|
-
]
|
|
1307
|
+
name,
|
|
1308
|
+
label,
|
|
1309
|
+
attribute,
|
|
1310
|
+
value,
|
|
1311
|
+
onChange,
|
|
1312
|
+
readOnly
|
|
777
1313
|
}
|
|
778
1314
|
);
|
|
779
1315
|
}
|
|
@@ -813,6 +1349,21 @@ function CustomFieldsSection({
|
|
|
813
1349
|
)) })
|
|
814
1350
|
] });
|
|
815
1351
|
}
|
|
1352
|
+
function EditViewSidePanels({ model, documentId, document: document2 }) {
|
|
1353
|
+
const panels = index.getEditViewSidePanels(model);
|
|
1354
|
+
if (panels.length === 0) return null;
|
|
1355
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: panels.map(({ id, title, Component }) => /* @__PURE__ */ jsxRuntime.jsxs(FormSection, { children: [
|
|
1356
|
+
/* @__PURE__ */ jsxRuntime.jsx(SectionLabel, { children: title }),
|
|
1357
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1358
|
+
Component,
|
|
1359
|
+
{
|
|
1360
|
+
model,
|
|
1361
|
+
documentId,
|
|
1362
|
+
document: document2
|
|
1363
|
+
}
|
|
1364
|
+
)
|
|
1365
|
+
] }, id)) });
|
|
1366
|
+
}
|
|
816
1367
|
const SYSTEM_FIELDS = /* @__PURE__ */ new Set([
|
|
817
1368
|
"id",
|
|
818
1369
|
"documentId",
|
|
@@ -844,6 +1395,8 @@ function useModelSchema(model) {
|
|
|
844
1395
|
if (SYSTEM_FIELDS.has(name)) continue;
|
|
845
1396
|
if (attr.type === "relation" && typeof attr.target === "string" && (attr.target.startsWith("plugin::users-permissions") || attr.target.startsWith("admin::")))
|
|
846
1397
|
continue;
|
|
1398
|
+
const baOptions = attr.pluginOptions?.["better-auth"];
|
|
1399
|
+
if (baOptions?.managed === true) continue;
|
|
847
1400
|
attributes[name] = attr;
|
|
848
1401
|
}
|
|
849
1402
|
return attributes;
|
|
@@ -896,12 +1449,13 @@ function OrganizationDetail({
|
|
|
896
1449
|
const schemaQuery = useModelSchema("organization");
|
|
897
1450
|
const qc = reactQuery.useQueryClient();
|
|
898
1451
|
const { toggleNotification } = admin.useNotification();
|
|
1452
|
+
const { get, put } = admin.useFetchClient();
|
|
899
1453
|
const orgQuery = reactQuery.useQuery({
|
|
900
1454
|
queryKey: ["dash-org", organizationId],
|
|
901
1455
|
queryFn: async () => {
|
|
902
1456
|
const result = await client.dash.organization[organizationId](
|
|
903
1457
|
{},
|
|
904
|
-
withContext({ organizationId })
|
|
1458
|
+
withContext({ organizationId }, getAuthHeaders())
|
|
905
1459
|
);
|
|
906
1460
|
if (result.error)
|
|
907
1461
|
throw new Error(result.error.message ?? "Failed to load org");
|
|
@@ -911,7 +1465,7 @@ function OrganizationDetail({
|
|
|
911
1465
|
const membersQuery = reactQuery.useQuery({
|
|
912
1466
|
queryKey: ["dash-org-members", organizationId],
|
|
913
1467
|
queryFn: async () => {
|
|
914
|
-
const result = await client.dash.organization[organizationId].members({}, withContext({ organizationId }));
|
|
1468
|
+
const result = await client.dash.organization[organizationId].members({}, withContext({ organizationId }, getAuthHeaders()));
|
|
915
1469
|
if (result.error) throw new Error(result.error.message ?? "Failed");
|
|
916
1470
|
return result.data ?? [];
|
|
917
1471
|
}
|
|
@@ -920,7 +1474,7 @@ function OrganizationDetail({
|
|
|
920
1474
|
queryKey: ["dash-org-teams", organizationId],
|
|
921
1475
|
queryFn: async () => {
|
|
922
1476
|
if (!teamsEnabled) return [];
|
|
923
|
-
const result = await client.dash.organization[organizationId].teams({}, withContext({ organizationId }));
|
|
1477
|
+
const result = await client.dash.organization[organizationId].teams({}, withContext({ organizationId }, getAuthHeaders()));
|
|
924
1478
|
if (result.error) throw new Error(result.error.message ?? "Failed");
|
|
925
1479
|
return result.data ?? [];
|
|
926
1480
|
},
|
|
@@ -929,11 +1483,29 @@ function OrganizationDetail({
|
|
|
929
1483
|
const ssoQuery = reactQuery.useQuery({
|
|
930
1484
|
queryKey: ["dash-org-sso", organizationId],
|
|
931
1485
|
queryFn: async () => {
|
|
932
|
-
const result = await client.dash.organization[organizationId].ssoProviders({}, withContext({ organizationId }));
|
|
1486
|
+
const result = await client.dash.organization[organizationId].ssoProviders({}, withContext({ organizationId }, getAuthHeaders()));
|
|
1487
|
+
if (result.error) throw new Error(result.error.message ?? "Failed");
|
|
1488
|
+
return result.data ?? [];
|
|
1489
|
+
}
|
|
1490
|
+
});
|
|
1491
|
+
const invitationsQuery = reactQuery.useQuery({
|
|
1492
|
+
queryKey: ["dash-org-invitations", organizationId],
|
|
1493
|
+
queryFn: async () => {
|
|
1494
|
+
const result = await client.dash.organization[organizationId].invitations({}, withContext({ organizationId }, getAuthHeaders()));
|
|
933
1495
|
if (result.error) throw new Error(result.error.message ?? "Failed");
|
|
934
1496
|
return result.data ?? [];
|
|
935
1497
|
}
|
|
936
1498
|
});
|
|
1499
|
+
const strapiOrgQuery = reactQuery.useQuery({
|
|
1500
|
+
queryKey: ["dash-strapi-org", organizationId],
|
|
1501
|
+
enabled: !!orgQuery.data,
|
|
1502
|
+
queryFn: async () => {
|
|
1503
|
+
const { data } = await get(
|
|
1504
|
+
`/better-auth-dashboard/db?uid=plugin::better-auth.organization&filters[id][$eq]=${organizationId}&pagination[pageSize]=1`
|
|
1505
|
+
);
|
|
1506
|
+
return data.results?.[0] ?? null;
|
|
1507
|
+
}
|
|
1508
|
+
});
|
|
937
1509
|
const [activeTab, setActiveTab] = react.useState("details");
|
|
938
1510
|
const [editName, setEditName] = react.useState(void 0);
|
|
939
1511
|
const [editSlug, setEditSlug] = react.useState(void 0);
|
|
@@ -946,12 +1518,15 @@ function OrganizationDetail({
|
|
|
946
1518
|
const [confirmDeleteSsoId, setConfirmDeleteSsoId] = react.useState(
|
|
947
1519
|
null
|
|
948
1520
|
);
|
|
1521
|
+
const [inviteEmail, setInviteEmail] = react.useState("");
|
|
1522
|
+
const [inviteRole, setInviteRole] = react.useState("member");
|
|
1523
|
+
const [confirmCancelInvitationId, setConfirmCancelInvitationId] = react.useState(null);
|
|
949
1524
|
const handleExtraChange = (name, value) => {
|
|
950
1525
|
setEditExtra((prev) => ({ ...prev, [name]: value }));
|
|
951
1526
|
};
|
|
952
1527
|
const org = orgQuery.data;
|
|
953
1528
|
const extraData = {
|
|
954
|
-
...
|
|
1529
|
+
...strapiOrgQuery.data ?? {},
|
|
955
1530
|
...editExtra
|
|
956
1531
|
};
|
|
957
1532
|
const updateOrgMutation = reactQuery.useMutation({
|
|
@@ -960,17 +1535,21 @@ function OrganizationDetail({
|
|
|
960
1535
|
if (editName !== void 0) body.name = editName;
|
|
961
1536
|
if (editSlug !== void 0) body.slug = editSlug;
|
|
962
1537
|
if (editLogo !== void 0) body.logo = editLogo;
|
|
963
|
-
const
|
|
964
|
-
|
|
965
|
-
|
|
1538
|
+
const documentId = strapiOrgQuery.data?.documentId;
|
|
1539
|
+
if (!documentId)
|
|
1540
|
+
throw new Error("Could not resolve documentId for organization");
|
|
1541
|
+
await put(
|
|
1542
|
+
`/better-auth-dashboard/db/${documentId}?uid=plugin::better-auth.organization`,
|
|
1543
|
+
body
|
|
966
1544
|
);
|
|
967
|
-
if (result.error)
|
|
968
|
-
throw new Error(result.error.message ?? "Update failed");
|
|
969
|
-
return result.data;
|
|
970
1545
|
},
|
|
971
|
-
onSuccess: () => {
|
|
1546
|
+
onSuccess: async () => {
|
|
1547
|
+
await qc.invalidateQueries({
|
|
1548
|
+
queryKey: ["dash-strapi-org", organizationId]
|
|
1549
|
+
});
|
|
972
1550
|
qc.invalidateQueries({ queryKey: ["dash-org", organizationId] });
|
|
973
1551
|
qc.invalidateQueries({ queryKey: ["dash-organizations"] });
|
|
1552
|
+
qc.invalidateQueries({ queryKey: ["dash-strapi-org", organizationId] });
|
|
974
1553
|
setEditName(void 0);
|
|
975
1554
|
setEditSlug(void 0);
|
|
976
1555
|
setEditLogo(void 0);
|
|
@@ -994,7 +1573,7 @@ function OrganizationDetail({
|
|
|
994
1573
|
mutationFn: async () => {
|
|
995
1574
|
const result = await client.dash.organization.addMember(
|
|
996
1575
|
{ userId: addUserId, role: addRole },
|
|
997
|
-
withContext({ organizationId })
|
|
1576
|
+
withContext({ organizationId }, getAuthHeaders())
|
|
998
1577
|
);
|
|
999
1578
|
if (result.error)
|
|
1000
1579
|
throw new Error(result.error.message ?? "Add member failed");
|
|
@@ -1017,7 +1596,7 @@ function OrganizationDetail({
|
|
|
1017
1596
|
mutationFn: async (memberId) => {
|
|
1018
1597
|
const result = await client.dash.organization.removeMember(
|
|
1019
1598
|
{ memberId },
|
|
1020
|
-
withContext({ organizationId })
|
|
1599
|
+
withContext({ organizationId }, getAuthHeaders())
|
|
1021
1600
|
);
|
|
1022
1601
|
if (result.error) throw new Error(result.error.message ?? "Failed");
|
|
1023
1602
|
},
|
|
@@ -1040,7 +1619,7 @@ function OrganizationDetail({
|
|
|
1040
1619
|
}) => {
|
|
1041
1620
|
const result = await client.dash.organization.updateMemberRole(
|
|
1042
1621
|
{ memberId, role },
|
|
1043
|
-
withContext({ organizationId })
|
|
1622
|
+
withContext({ organizationId }, getAuthHeaders())
|
|
1044
1623
|
);
|
|
1045
1624
|
if (result.error) throw new Error(result.error.message ?? "Failed");
|
|
1046
1625
|
return result.data;
|
|
@@ -1061,7 +1640,7 @@ function OrganizationDetail({
|
|
|
1061
1640
|
mutationFn: async () => {
|
|
1062
1641
|
const result = await client.dash.organization.createTeam(
|
|
1063
1642
|
{ name: newTeamName },
|
|
1064
|
-
withContext({ organizationId })
|
|
1643
|
+
withContext({ organizationId }, getAuthHeaders())
|
|
1065
1644
|
);
|
|
1066
1645
|
if (result.error)
|
|
1067
1646
|
throw new Error(result.error.message ?? "Create team failed");
|
|
@@ -1083,7 +1662,7 @@ function OrganizationDetail({
|
|
|
1083
1662
|
mutationFn: async (teamId) => {
|
|
1084
1663
|
const result = await client.dash.organization.deleteTeam(
|
|
1085
1664
|
{ teamId },
|
|
1086
|
-
withContext({ organizationId })
|
|
1665
|
+
withContext({ organizationId }, getAuthHeaders())
|
|
1087
1666
|
);
|
|
1088
1667
|
if (result.error) throw new Error(result.error.message ?? "Failed");
|
|
1089
1668
|
},
|
|
@@ -1101,7 +1680,10 @@ function OrganizationDetail({
|
|
|
1101
1680
|
});
|
|
1102
1681
|
const deleteSsoMutation = reactQuery.useMutation({
|
|
1103
1682
|
mutationFn: async (providerId) => {
|
|
1104
|
-
const result = await client.dash.organization[organizationId].ssoProvider.delete(
|
|
1683
|
+
const result = await client.dash.organization[organizationId].ssoProvider.delete(
|
|
1684
|
+
{ providerId },
|
|
1685
|
+
withContext({ organizationId }, getAuthHeaders())
|
|
1686
|
+
);
|
|
1105
1687
|
if (result.error) throw new Error(result.error.message ?? "Failed");
|
|
1106
1688
|
},
|
|
1107
1689
|
onSuccess: () => {
|
|
@@ -1116,9 +1698,74 @@ function OrganizationDetail({
|
|
|
1116
1698
|
});
|
|
1117
1699
|
}
|
|
1118
1700
|
});
|
|
1701
|
+
const inviteMemberMutation = reactQuery.useMutation({
|
|
1702
|
+
mutationFn: async () => {
|
|
1703
|
+
const result = await client.dash.organization.inviteMember(
|
|
1704
|
+
{ email: inviteEmail, role: inviteRole, invitedBy: "" },
|
|
1705
|
+
withContext({ organizationId }, getAuthHeaders())
|
|
1706
|
+
);
|
|
1707
|
+
if (result.error) throw new Error(result.error.message ?? "Failed");
|
|
1708
|
+
return result.data;
|
|
1709
|
+
},
|
|
1710
|
+
onSuccess: () => {
|
|
1711
|
+
qc.invalidateQueries({
|
|
1712
|
+
queryKey: ["dash-org-invitations", organizationId]
|
|
1713
|
+
});
|
|
1714
|
+
setInviteEmail("");
|
|
1715
|
+
setInviteRole("member");
|
|
1716
|
+
toggleNotification({ type: "success", message: "Invitation sent" });
|
|
1717
|
+
},
|
|
1718
|
+
onError: (err) => {
|
|
1719
|
+
toggleNotification({
|
|
1720
|
+
type: "danger",
|
|
1721
|
+
message: err.message ?? "Failed to invite"
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1724
|
+
});
|
|
1725
|
+
const cancelInvitationMutation = reactQuery.useMutation({
|
|
1726
|
+
mutationFn: async (invitationId) => {
|
|
1727
|
+
const result = await client.dash.organization.cancelInvitation(
|
|
1728
|
+
{ invitationId },
|
|
1729
|
+
withContext({ organizationId }, getAuthHeaders())
|
|
1730
|
+
);
|
|
1731
|
+
if (result.error) throw new Error(result.error.message ?? "Failed");
|
|
1732
|
+
},
|
|
1733
|
+
onSuccess: () => {
|
|
1734
|
+
setConfirmCancelInvitationId(null);
|
|
1735
|
+
qc.invalidateQueries({
|
|
1736
|
+
queryKey: ["dash-org-invitations", organizationId]
|
|
1737
|
+
});
|
|
1738
|
+
toggleNotification({ type: "success", message: "Invitation cancelled" });
|
|
1739
|
+
},
|
|
1740
|
+
onError: (err) => {
|
|
1741
|
+
toggleNotification({
|
|
1742
|
+
type: "danger",
|
|
1743
|
+
message: err.message ?? "Failed to cancel"
|
|
1744
|
+
});
|
|
1745
|
+
}
|
|
1746
|
+
});
|
|
1747
|
+
const resendInvitationMutation = reactQuery.useMutation({
|
|
1748
|
+
mutationFn: async (invitationId) => {
|
|
1749
|
+
const result = await client.dash.organization.resendInvitation(
|
|
1750
|
+
{ invitationId },
|
|
1751
|
+
withContext({ organizationId }, getAuthHeaders())
|
|
1752
|
+
);
|
|
1753
|
+
if (result.error) throw new Error(result.error.message ?? "Failed");
|
|
1754
|
+
},
|
|
1755
|
+
onSuccess: () => {
|
|
1756
|
+
toggleNotification({ type: "success", message: "Invitation resent" });
|
|
1757
|
+
},
|
|
1758
|
+
onError: (err) => {
|
|
1759
|
+
toggleNotification({
|
|
1760
|
+
type: "danger",
|
|
1761
|
+
message: err.message ?? "Failed to resend"
|
|
1762
|
+
});
|
|
1763
|
+
}
|
|
1764
|
+
});
|
|
1119
1765
|
const members = membersQuery.data ?? [];
|
|
1120
1766
|
const teams = teamsQuery.data ?? [];
|
|
1121
1767
|
const ssoProviders = ssoQuery.data ?? [];
|
|
1768
|
+
const invitations = invitationsQuery.data ?? [];
|
|
1122
1769
|
const hasOrgEdits = editName !== void 0 || editSlug !== void 0 || editLogo !== void 0 || Object.keys(editExtra).length > 0;
|
|
1123
1770
|
const customFields = Object.entries(schemaQuery.data ?? {}).filter(([name]) => !STANDARD_ORG_FIELDS.has(name)).map(([name, attribute]) => ({ name, attribute }));
|
|
1124
1771
|
const detailsFooter = activeTab === "details" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
@@ -1175,6 +1822,11 @@ function OrganizationDetail({
|
|
|
1175
1822
|
teams.length,
|
|
1176
1823
|
")"
|
|
1177
1824
|
] }),
|
|
1825
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tabs.Trigger, { value: "invitations", children: [
|
|
1826
|
+
"Invitations (",
|
|
1827
|
+
invitations.length,
|
|
1828
|
+
")"
|
|
1829
|
+
] }),
|
|
1178
1830
|
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tabs.Trigger, { value: "sso", children: [
|
|
1179
1831
|
"SSO (",
|
|
1180
1832
|
ssoProviders.length,
|
|
@@ -1229,33 +1881,43 @@ function OrganizationDetail({
|
|
|
1229
1881
|
}
|
|
1230
1882
|
)
|
|
1231
1883
|
] }),
|
|
1232
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1233
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1234
|
-
|
|
1235
|
-
|
|
1884
|
+
/* @__PURE__ */ jsxRuntime.jsxs(EditSidebar, { children: [
|
|
1885
|
+
/* @__PURE__ */ jsxRuntime.jsxs(FormSection, { children: [
|
|
1886
|
+
/* @__PURE__ */ jsxRuntime.jsx(SectionLabel, { children: "Details" }),
|
|
1887
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1888
|
+
"div",
|
|
1889
|
+
{
|
|
1890
|
+
style: {
|
|
1891
|
+
display: "grid",
|
|
1892
|
+
gridTemplateColumns: "1fr 1fr",
|
|
1893
|
+
gap: 12
|
|
1894
|
+
},
|
|
1895
|
+
children: [
|
|
1896
|
+
/* @__PURE__ */ jsxRuntime.jsxs(MetaItem, { style: { gridColumn: "1 / -1" }, children: [
|
|
1897
|
+
/* @__PURE__ */ jsxRuntime.jsx(MetaKey, { children: "Organization ID" }),
|
|
1898
|
+
/* @__PURE__ */ jsxRuntime.jsx(MonoChip, { children: org?.id })
|
|
1899
|
+
] }),
|
|
1900
|
+
/* @__PURE__ */ jsxRuntime.jsxs(MetaItem, { children: [
|
|
1901
|
+
/* @__PURE__ */ jsxRuntime.jsx(MetaKey, { children: "Members" }),
|
|
1902
|
+
/* @__PURE__ */ jsxRuntime.jsx(MetaVal, { children: org?.memberCount ?? 0 })
|
|
1903
|
+
] }),
|
|
1904
|
+
/* @__PURE__ */ jsxRuntime.jsxs(MetaItem, { children: [
|
|
1905
|
+
/* @__PURE__ */ jsxRuntime.jsx(MetaKey, { children: "Created" }),
|
|
1906
|
+
/* @__PURE__ */ jsxRuntime.jsx(MetaVal, { children: org?.createdAt ? new Date(org.createdAt).toLocaleDateString() : "—" })
|
|
1907
|
+
] })
|
|
1908
|
+
]
|
|
1909
|
+
}
|
|
1910
|
+
)
|
|
1911
|
+
] }),
|
|
1912
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1913
|
+
EditViewSidePanels,
|
|
1236
1914
|
{
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
gap: 12
|
|
1241
|
-
},
|
|
1242
|
-
children: [
|
|
1243
|
-
/* @__PURE__ */ jsxRuntime.jsxs(MetaItem, { style: { gridColumn: "1 / -1" }, children: [
|
|
1244
|
-
/* @__PURE__ */ jsxRuntime.jsx(MetaKey, { children: "Organization ID" }),
|
|
1245
|
-
/* @__PURE__ */ jsxRuntime.jsx(MonoChip, { children: org?.id })
|
|
1246
|
-
] }),
|
|
1247
|
-
/* @__PURE__ */ jsxRuntime.jsxs(MetaItem, { children: [
|
|
1248
|
-
/* @__PURE__ */ jsxRuntime.jsx(MetaKey, { children: "Members" }),
|
|
1249
|
-
/* @__PURE__ */ jsxRuntime.jsx(MetaVal, { children: org?.memberCount ?? 0 })
|
|
1250
|
-
] }),
|
|
1251
|
-
/* @__PURE__ */ jsxRuntime.jsxs(MetaItem, { children: [
|
|
1252
|
-
/* @__PURE__ */ jsxRuntime.jsx(MetaKey, { children: "Created" }),
|
|
1253
|
-
/* @__PURE__ */ jsxRuntime.jsx(MetaVal, { children: org?.createdAt ? new Date(org.createdAt).toLocaleDateString() : "—" })
|
|
1254
|
-
] })
|
|
1255
|
-
]
|
|
1915
|
+
model: "plugin::better-auth.organization",
|
|
1916
|
+
documentId: strapiOrgQuery.data?.documentId,
|
|
1917
|
+
document: strapiOrgQuery.data ?? void 0
|
|
1256
1918
|
}
|
|
1257
1919
|
)
|
|
1258
|
-
] })
|
|
1920
|
+
] })
|
|
1259
1921
|
] }) }),
|
|
1260
1922
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "members", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 5, paddingTop: 6, children: [
|
|
1261
1923
|
/* @__PURE__ */ jsxRuntime.jsxs(FormSection, { children: [
|
|
@@ -1430,7 +2092,127 @@ function OrganizationDetail({
|
|
|
1430
2092
|
},
|
|
1431
2093
|
provider.id
|
|
1432
2094
|
)) })
|
|
1433
|
-
] }) }) })
|
|
2095
|
+
] }) }) }),
|
|
2096
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "invitations", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 5, paddingTop: 6, children: [
|
|
2097
|
+
/* @__PURE__ */ jsxRuntime.jsxs(FormSection, { children: [
|
|
2098
|
+
/* @__PURE__ */ jsxRuntime.jsx(SectionLabel, { children: "Invite member by email" }),
|
|
2099
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Grid.Root, { gap: 3, children: [
|
|
2100
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 8, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
|
|
2101
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Email address" }),
|
|
2102
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2103
|
+
designSystem.TextInput,
|
|
2104
|
+
{
|
|
2105
|
+
type: "email",
|
|
2106
|
+
value: inviteEmail,
|
|
2107
|
+
onChange: (e) => setInviteEmail(e.target.value),
|
|
2108
|
+
placeholder: "user@example.com"
|
|
2109
|
+
}
|
|
2110
|
+
)
|
|
2111
|
+
] }) }),
|
|
2112
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 4, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
|
|
2113
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Role" }),
|
|
2114
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2115
|
+
designSystem.SingleSelect,
|
|
2116
|
+
{
|
|
2117
|
+
value: inviteRole,
|
|
2118
|
+
onChange: (v) => setInviteRole(String(v)),
|
|
2119
|
+
"aria-label": "Invite role",
|
|
2120
|
+
children: [
|
|
2121
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "member", children: "Member" }),
|
|
2122
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "admin", children: "Admin" }),
|
|
2123
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "owner", children: "Owner" })
|
|
2124
|
+
]
|
|
2125
|
+
}
|
|
2126
|
+
)
|
|
2127
|
+
] }) })
|
|
2128
|
+
] }),
|
|
2129
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2130
|
+
designSystem.Button,
|
|
2131
|
+
{
|
|
2132
|
+
size: "S",
|
|
2133
|
+
startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
|
|
2134
|
+
disabled: !inviteEmail,
|
|
2135
|
+
loading: inviteMemberMutation.isLoading,
|
|
2136
|
+
onClick: () => inviteMemberMutation.mutate(),
|
|
2137
|
+
children: "Send invitation"
|
|
2138
|
+
}
|
|
2139
|
+
) })
|
|
2140
|
+
] }),
|
|
2141
|
+
/* @__PURE__ */ jsxRuntime.jsxs(FormSection, { children: [
|
|
2142
|
+
/* @__PURE__ */ jsxRuntime.jsxs(SectionLabel, { children: [
|
|
2143
|
+
"Invitations (",
|
|
2144
|
+
invitations.length,
|
|
2145
|
+
")"
|
|
2146
|
+
] }),
|
|
2147
|
+
invitationsQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral500", children: "Loading…" }) : invitations.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: "No invitations yet." }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 2, children: invitations.map((inv) => {
|
|
2148
|
+
const statusColor = {
|
|
2149
|
+
pending: "#f59e0b",
|
|
2150
|
+
accepted: "#5cb176",
|
|
2151
|
+
rejected: "#d02b20",
|
|
2152
|
+
canceled: "#8e8ea9"
|
|
2153
|
+
};
|
|
2154
|
+
const color = statusColor[inv.status] ?? "#8e8ea9";
|
|
2155
|
+
const isPending = inv.status === "pending";
|
|
2156
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(AccountRow, { children: [
|
|
2157
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2158
|
+
designSystem.Flex,
|
|
2159
|
+
{
|
|
2160
|
+
direction: "column",
|
|
2161
|
+
gap: 1,
|
|
2162
|
+
alignItems: "flex-start",
|
|
2163
|
+
style: { flex: 1 },
|
|
2164
|
+
children: [
|
|
2165
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "semiBold", children: inv.user?.name ?? inv.email }),
|
|
2166
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: [
|
|
2167
|
+
inv.email,
|
|
2168
|
+
" · role: ",
|
|
2169
|
+
inv.role
|
|
2170
|
+
] }),
|
|
2171
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: [
|
|
2172
|
+
"Expires",
|
|
2173
|
+
" ",
|
|
2174
|
+
new Date(inv.expiresAt).toLocaleDateString()
|
|
2175
|
+
] })
|
|
2176
|
+
]
|
|
2177
|
+
}
|
|
2178
|
+
),
|
|
2179
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
|
|
2180
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2181
|
+
MonoChip,
|
|
2182
|
+
{
|
|
2183
|
+
style: {
|
|
2184
|
+
color,
|
|
2185
|
+
borderColor: color,
|
|
2186
|
+
background: `${color}18`
|
|
2187
|
+
},
|
|
2188
|
+
children: inv.status
|
|
2189
|
+
}
|
|
2190
|
+
),
|
|
2191
|
+
isPending && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2192
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2193
|
+
designSystem.Button,
|
|
2194
|
+
{
|
|
2195
|
+
size: "S",
|
|
2196
|
+
variant: "secondary",
|
|
2197
|
+
loading: resendInvitationMutation.isLoading,
|
|
2198
|
+
onClick: () => resendInvitationMutation.mutate(inv.id),
|
|
2199
|
+
children: "Resend"
|
|
2200
|
+
}
|
|
2201
|
+
),
|
|
2202
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2203
|
+
designSystem.IconButton,
|
|
2204
|
+
{
|
|
2205
|
+
label: "Cancel invitation",
|
|
2206
|
+
onClick: () => setConfirmCancelInvitationId(inv.id),
|
|
2207
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
|
|
2208
|
+
}
|
|
2209
|
+
)
|
|
2210
|
+
] })
|
|
2211
|
+
] })
|
|
2212
|
+
] }, inv.id);
|
|
2213
|
+
}) })
|
|
2214
|
+
] })
|
|
2215
|
+
] }) })
|
|
1434
2216
|
] }),
|
|
1435
2217
|
confirmRemoveMemberId && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1436
2218
|
ConfirmDialog,
|
|
@@ -1464,6 +2246,17 @@ function OrganizationDetail({
|
|
|
1464
2246
|
onConfirm: () => deleteSsoMutation.mutate(confirmDeleteSsoId),
|
|
1465
2247
|
onCancel: () => setConfirmDeleteSsoId(null)
|
|
1466
2248
|
}
|
|
2249
|
+
),
|
|
2250
|
+
confirmCancelInvitationId && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2251
|
+
ConfirmDialog,
|
|
2252
|
+
{
|
|
2253
|
+
title: "Cancel invitation",
|
|
2254
|
+
message: "Are you sure you want to cancel this invitation? The invite link will no longer work.",
|
|
2255
|
+
confirmLabel: "Cancel invitation",
|
|
2256
|
+
loading: cancelInvitationMutation.isLoading,
|
|
2257
|
+
onConfirm: () => cancelInvitationMutation.mutate(confirmCancelInvitationId),
|
|
2258
|
+
onCancel: () => setConfirmCancelInvitationId(null)
|
|
2259
|
+
}
|
|
1467
2260
|
)
|
|
1468
2261
|
]
|
|
1469
2262
|
}
|
|
@@ -1485,7 +2278,7 @@ function TeamRow({
|
|
|
1485
2278
|
organizationId
|
|
1486
2279
|
].teams[team.id].members(
|
|
1487
2280
|
{ params: { orgId: organizationId, teamId: team.id } },
|
|
1488
|
-
withContext({ organizationId })
|
|
2281
|
+
withContext({ organizationId }, getAuthHeaders())
|
|
1489
2282
|
);
|
|
1490
2283
|
if (result.error) throw new Error(result.error.message ?? "Failed");
|
|
1491
2284
|
return result.data ?? [];
|
|
@@ -1497,7 +2290,7 @@ function TeamRow({
|
|
|
1497
2290
|
mutationFn: async () => {
|
|
1498
2291
|
const result = await client.dash.organization.addTeamMember(
|
|
1499
2292
|
{ teamId: team.id, userId: addUserId },
|
|
1500
|
-
withContext({ organizationId })
|
|
2293
|
+
withContext({ organizationId }, getAuthHeaders())
|
|
1501
2294
|
);
|
|
1502
2295
|
if (result.error) throw new Error(result.error.message ?? "Failed");
|
|
1503
2296
|
return result.data;
|
|
@@ -1513,7 +2306,7 @@ function TeamRow({
|
|
|
1513
2306
|
mutationFn: async (userId) => {
|
|
1514
2307
|
const result = await client.dash.organization.removeTeamMember(
|
|
1515
2308
|
{ teamId: team.id, userId },
|
|
1516
|
-
withContext({ organizationId })
|
|
2309
|
+
withContext({ organizationId }, getAuthHeaders())
|
|
1517
2310
|
);
|
|
1518
2311
|
if (result.error) throw new Error(result.error.message ?? "Failed");
|
|
1519
2312
|
},
|
|
@@ -1609,12 +2402,12 @@ function TeamRow({
|
|
|
1609
2402
|
)
|
|
1610
2403
|
] });
|
|
1611
2404
|
}
|
|
1612
|
-
const PAGE_SIZE$
|
|
1613
|
-
const fadeUp$
|
|
2405
|
+
const PAGE_SIZE$1 = 25;
|
|
2406
|
+
const fadeUp$2 = styled.keyframes`
|
|
1614
2407
|
from { opacity: 0; transform: translateY(6px); }
|
|
1615
2408
|
to { opacity: 1; transform: translateY(0); }
|
|
1616
2409
|
`;
|
|
1617
|
-
const Wrap$
|
|
2410
|
+
const Wrap$2 = styled__default.default.div`
|
|
1618
2411
|
padding: 28px 32px;
|
|
1619
2412
|
background: #f6f6f9;
|
|
1620
2413
|
min-height: 100%;
|
|
@@ -1622,39 +2415,39 @@ const Wrap$3 = styled__default.default.div`
|
|
|
1622
2415
|
flex-direction: column;
|
|
1623
2416
|
gap: 20px;
|
|
1624
2417
|
`;
|
|
1625
|
-
const PageHeader$
|
|
2418
|
+
const PageHeader$1 = styled__default.default.div`
|
|
1626
2419
|
display: flex;
|
|
1627
2420
|
justify-content: space-between;
|
|
1628
2421
|
align-items: flex-start;
|
|
1629
2422
|
`;
|
|
1630
|
-
const TitleBlock$
|
|
2423
|
+
const TitleBlock$1 = styled__default.default.div`
|
|
1631
2424
|
display: flex;
|
|
1632
2425
|
flex-direction: column;
|
|
1633
2426
|
gap: 4px;
|
|
1634
2427
|
`;
|
|
1635
|
-
const PageTitle$
|
|
2428
|
+
const PageTitle$1 = styled__default.default.h1`
|
|
1636
2429
|
margin: 0;
|
|
1637
2430
|
font-size: 22px;
|
|
1638
2431
|
font-weight: 800;
|
|
1639
2432
|
color: #32324d;
|
|
1640
2433
|
letter-spacing: -0.03em;
|
|
1641
2434
|
`;
|
|
1642
|
-
const PageSubtitle$
|
|
2435
|
+
const PageSubtitle$1 = styled__default.default.p`
|
|
1643
2436
|
margin: 0;
|
|
1644
2437
|
font-size: 12px;
|
|
1645
2438
|
color: #8e8ea9;
|
|
1646
2439
|
`;
|
|
1647
|
-
const TableCard$
|
|
2440
|
+
const TableCard$1 = styled__default.default.div`
|
|
1648
2441
|
background: #ffffff;
|
|
1649
2442
|
border: 1px solid #eaeaef;
|
|
1650
2443
|
border-radius: 10px;
|
|
1651
2444
|
overflow: hidden;
|
|
1652
2445
|
`;
|
|
1653
|
-
const Table$
|
|
2446
|
+
const Table$1 = styled__default.default.table`
|
|
1654
2447
|
width: 100%;
|
|
1655
2448
|
border-collapse: collapse;
|
|
1656
2449
|
`;
|
|
1657
|
-
const TH$
|
|
2450
|
+
const TH$1 = styled__default.default.th`
|
|
1658
2451
|
text-align: left;
|
|
1659
2452
|
padding: 10px 14px;
|
|
1660
2453
|
font-size: 10px;
|
|
@@ -1668,13 +2461,13 @@ const TH$2 = styled__default.default.th`
|
|
|
1668
2461
|
&:first-child { padding-left: 20px; }
|
|
1669
2462
|
&:last-child { padding-right: 20px; }
|
|
1670
2463
|
`;
|
|
1671
|
-
const THCheck$1 = styled__default.default(TH$
|
|
2464
|
+
const THCheck$1 = styled__default.default(TH$1)`
|
|
1672
2465
|
width: 44px;
|
|
1673
2466
|
`;
|
|
1674
|
-
const THActions$1 = styled__default.default(TH$
|
|
2467
|
+
const THActions$1 = styled__default.default(TH$1)`
|
|
1675
2468
|
width: 80px;
|
|
1676
2469
|
`;
|
|
1677
|
-
const TD$
|
|
2470
|
+
const TD$1 = styled__default.default.td`
|
|
1678
2471
|
padding: 11px 14px;
|
|
1679
2472
|
font-size: 12px;
|
|
1680
2473
|
color: #32324d;
|
|
@@ -1683,14 +2476,14 @@ const TD$2 = styled__default.default.td`
|
|
|
1683
2476
|
&:first-child { padding-left: 20px; }
|
|
1684
2477
|
&:last-child { padding-right: 20px; }
|
|
1685
2478
|
`;
|
|
1686
|
-
const TDCheck$1 = styled__default.default(TD$
|
|
2479
|
+
const TDCheck$1 = styled__default.default(TD$1)`
|
|
1687
2480
|
width: 44px;
|
|
1688
2481
|
`;
|
|
1689
|
-
const TDActions$1 = styled__default.default(TD$
|
|
2482
|
+
const TDActions$1 = styled__default.default(TD$1)`
|
|
1690
2483
|
width: 80px;
|
|
1691
2484
|
`;
|
|
1692
|
-
const TR$
|
|
1693
|
-
animation: ${fadeUp$
|
|
2485
|
+
const TR$1 = styled__default.default.tr`
|
|
2486
|
+
animation: ${fadeUp$2} 280ms ease both;
|
|
1694
2487
|
animation-delay: ${(p) => (p.$i ?? 0) * 25}ms;
|
|
1695
2488
|
background: ${(p) => p.$selected ? "#f0f0ff" : "transparent"};
|
|
1696
2489
|
transition: background 120ms ease;
|
|
@@ -1777,7 +2570,9 @@ function OrgAvatar({ name, logo }) {
|
|
|
1777
2570
|
return /* @__PURE__ */ jsxRuntime.jsx(OrgLogoFallback, { $bg: color.bg, $fg: color.fg, children: initials });
|
|
1778
2571
|
}
|
|
1779
2572
|
function OrganizationsPage({ teamsEnabled }) {
|
|
2573
|
+
const rbac = admin.useRBAC(index.PERMISSIONS.organization);
|
|
1780
2574
|
const qc = reactQuery.useQueryClient();
|
|
2575
|
+
const { toggleNotification } = admin.useNotification();
|
|
1781
2576
|
const [page, setPage] = react.useState(1);
|
|
1782
2577
|
const [search, setSearch] = react.useState("");
|
|
1783
2578
|
const [searchInput, setSearchInput] = react.useState("");
|
|
@@ -1786,13 +2581,13 @@ function OrganizationsPage({ teamsEnabled }) {
|
|
|
1786
2581
|
const [detailOrgId, setDetailOrgId] = react.useState(null);
|
|
1787
2582
|
const [confirmDelete, setConfirmDelete] = react.useState(null);
|
|
1788
2583
|
const [confirmDeleteMany, setConfirmDeleteMany] = react.useState(false);
|
|
1789
|
-
const offset = (page - 1) * PAGE_SIZE$
|
|
2584
|
+
const offset = (page - 1) * PAGE_SIZE$1;
|
|
1790
2585
|
const orgsQuery = reactQuery.useQuery({
|
|
1791
2586
|
queryKey: ["dash-organizations", page, search],
|
|
1792
2587
|
queryFn: async () => {
|
|
1793
2588
|
const result = await client.dash.listOrganizations({
|
|
1794
2589
|
query: {
|
|
1795
|
-
limit: PAGE_SIZE$
|
|
2590
|
+
limit: PAGE_SIZE$1,
|
|
1796
2591
|
offset,
|
|
1797
2592
|
sortBy: "createdAt",
|
|
1798
2593
|
sortOrder: "desc",
|
|
@@ -1809,7 +2604,7 @@ function OrganizationsPage({ teamsEnabled }) {
|
|
|
1809
2604
|
mutationFn: async (organizationId) => {
|
|
1810
2605
|
const result = await client.dash.organization.delete(
|
|
1811
2606
|
{ organizationId },
|
|
1812
|
-
withContext({ organizationId })
|
|
2607
|
+
withContext({ organizationId }, getAuthHeaders())
|
|
1813
2608
|
);
|
|
1814
2609
|
if (result.error)
|
|
1815
2610
|
throw new Error(result.error.message ?? "Delete failed");
|
|
@@ -1817,27 +2612,44 @@ function OrganizationsPage({ teamsEnabled }) {
|
|
|
1817
2612
|
onSuccess: () => {
|
|
1818
2613
|
setConfirmDelete(null);
|
|
1819
2614
|
qc.invalidateQueries({ queryKey: ["dash-organizations"] });
|
|
2615
|
+
toggleNotification({ type: "success", message: "Organization deleted" });
|
|
2616
|
+
},
|
|
2617
|
+
onError: (err) => {
|
|
2618
|
+
toggleNotification({
|
|
2619
|
+
type: "danger",
|
|
2620
|
+
message: err.message ?? "Failed to delete organization"
|
|
2621
|
+
});
|
|
1820
2622
|
}
|
|
1821
2623
|
});
|
|
1822
2624
|
const deleteManyMutation = reactQuery.useMutation({
|
|
1823
2625
|
mutationFn: async (organizationIds) => {
|
|
1824
2626
|
const result = await client.dash.organization.deleteMany(
|
|
1825
2627
|
{},
|
|
1826
|
-
withContext({ organizationIds })
|
|
2628
|
+
withContext({ organizationIds }, getAuthHeaders())
|
|
1827
2629
|
);
|
|
1828
2630
|
if (result.error)
|
|
1829
2631
|
throw new Error(result.error.message ?? "Delete failed");
|
|
1830
2632
|
return result.data;
|
|
1831
2633
|
},
|
|
1832
|
-
onSuccess: () => {
|
|
2634
|
+
onSuccess: (_data, organizationIds) => {
|
|
1833
2635
|
setConfirmDeleteMany(false);
|
|
1834
2636
|
setSelected(/* @__PURE__ */ new Set());
|
|
1835
2637
|
qc.invalidateQueries({ queryKey: ["dash-organizations"] });
|
|
2638
|
+
toggleNotification({
|
|
2639
|
+
type: "success",
|
|
2640
|
+
message: `${organizationIds.length} organization${organizationIds.length !== 1 ? "s" : ""} deleted`
|
|
2641
|
+
});
|
|
2642
|
+
},
|
|
2643
|
+
onError: (err) => {
|
|
2644
|
+
toggleNotification({
|
|
2645
|
+
type: "danger",
|
|
2646
|
+
message: err.message ?? "Failed to delete organizations"
|
|
2647
|
+
});
|
|
1836
2648
|
}
|
|
1837
2649
|
});
|
|
1838
2650
|
const orgs = orgsQuery.data && "organizations" in orgsQuery.data ? orgsQuery.data.organizations : [];
|
|
1839
2651
|
const total = orgsQuery.data && "total" in orgsQuery.data ? orgsQuery.data.total : 0;
|
|
1840
|
-
const pageCount = Math.ceil(total / PAGE_SIZE$
|
|
2652
|
+
const pageCount = Math.ceil(total / PAGE_SIZE$1);
|
|
1841
2653
|
const allSelected = orgs.length > 0 && orgs.every((o) => selected.has(o.id));
|
|
1842
2654
|
const someSelected = selected.size > 0;
|
|
1843
2655
|
const toggleSelect = (id) => {
|
|
@@ -1857,212 +2669,220 @@ function OrganizationsPage({ teamsEnabled }) {
|
|
|
1857
2669
|
setSearch(searchInput);
|
|
1858
2670
|
setPage(1);
|
|
1859
2671
|
};
|
|
1860
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
) }),
|
|
1922
|
-
/* @__PURE__ */ jsxRuntime.jsx(TH$2, { children: "Name" }),
|
|
1923
|
-
/* @__PURE__ */ jsxRuntime.jsx(TH$2, { children: "Slug" }),
|
|
1924
|
-
/* @__PURE__ */ jsxRuntime.jsx(TH$2, { children: "Members" }),
|
|
1925
|
-
/* @__PURE__ */ jsxRuntime.jsx(TH$2, { children: "Created" }),
|
|
1926
|
-
/* @__PURE__ */ jsxRuntime.jsx(THActions$1, {})
|
|
1927
|
-
] }) }),
|
|
1928
|
-
/* @__PURE__ */ jsxRuntime.jsx("tbody", { children: orgs.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1929
|
-
TD$2,
|
|
1930
|
-
{
|
|
1931
|
-
colSpan: 6,
|
|
1932
|
-
style: {
|
|
1933
|
-
textAlign: "center",
|
|
1934
|
-
padding: "40px",
|
|
1935
|
-
color: "#8e8ea9"
|
|
1936
|
-
},
|
|
1937
|
-
"data-testid": "orgs-empty",
|
|
1938
|
-
children: search ? `No organizations matching "${search}"` : "No organizations found"
|
|
1939
|
-
}
|
|
1940
|
-
) }) : orgs.map((org, i) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1941
|
-
TR$2,
|
|
1942
|
-
{
|
|
1943
|
-
$selected: selected.has(org.id),
|
|
1944
|
-
$i: i,
|
|
1945
|
-
"data-testid": "org-row",
|
|
1946
|
-
children: [
|
|
1947
|
-
/* @__PURE__ */ jsxRuntime.jsx(TDCheck$1, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2672
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2673
|
+
admin.Page.Protect,
|
|
2674
|
+
{
|
|
2675
|
+
permissions: index.PERMISSIONS.organization.filter(
|
|
2676
|
+
(p) => p.action === `plugin::${index.PLUGIN_ID}.organization.read`
|
|
2677
|
+
),
|
|
2678
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(Wrap$2, { "data-testid": "organizations-page", children: [
|
|
2679
|
+
/* @__PURE__ */ jsxRuntime.jsxs(PageHeader$1, { children: [
|
|
2680
|
+
/* @__PURE__ */ jsxRuntime.jsxs(TitleBlock$1, { children: [
|
|
2681
|
+
/* @__PURE__ */ jsxRuntime.jsx(PageTitle$1, { children: "Organizations" }),
|
|
2682
|
+
/* @__PURE__ */ jsxRuntime.jsxs(PageSubtitle$1, { children: [
|
|
2683
|
+
total.toLocaleString(),
|
|
2684
|
+
" total"
|
|
2685
|
+
] })
|
|
2686
|
+
] }),
|
|
2687
|
+
rbac.allowedActions.canCreate && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2688
|
+
designSystem.Button,
|
|
2689
|
+
{
|
|
2690
|
+
startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
|
|
2691
|
+
onClick: () => setShowCreate(true),
|
|
2692
|
+
"data-testid": "create-org-btn",
|
|
2693
|
+
children: "Create organization"
|
|
2694
|
+
}
|
|
2695
|
+
)
|
|
2696
|
+
] }),
|
|
2697
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Toolbar$1, { children: [
|
|
2698
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.SearchForm, { onSubmit: handleSearch, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2699
|
+
designSystem.Searchbar,
|
|
2700
|
+
{
|
|
2701
|
+
clearLabel: "Clear",
|
|
2702
|
+
name: "search",
|
|
2703
|
+
placeholder: "Search organizations…",
|
|
2704
|
+
value: searchInput,
|
|
2705
|
+
onChange: (e) => setSearchInput(e.target.value),
|
|
2706
|
+
onClear: () => {
|
|
2707
|
+
setSearchInput("");
|
|
2708
|
+
setSearch("");
|
|
2709
|
+
setPage(1);
|
|
2710
|
+
},
|
|
2711
|
+
children: "Search organizations"
|
|
2712
|
+
}
|
|
2713
|
+
) }),
|
|
2714
|
+
someSelected && rbac.allowedActions.canDelete && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2715
|
+
designSystem.Button,
|
|
2716
|
+
{
|
|
2717
|
+
variant: "danger-light",
|
|
2718
|
+
size: "S",
|
|
2719
|
+
onClick: () => setConfirmDeleteMany(true),
|
|
2720
|
+
"data-testid": "delete-selected-orgs-btn",
|
|
2721
|
+
children: [
|
|
2722
|
+
"Delete ",
|
|
2723
|
+
selected.size,
|
|
2724
|
+
" selected"
|
|
2725
|
+
]
|
|
2726
|
+
}
|
|
2727
|
+
)
|
|
2728
|
+
] }),
|
|
2729
|
+
orgsQuery.isError && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#d02b20", fontSize: 12, padding: "8px 0" }, children: orgsQuery.error instanceof Error ? orgsQuery.error.message : "An error occurred" }),
|
|
2730
|
+
/* @__PURE__ */ jsxRuntime.jsx(TableCard$1, { children: orgsQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: "Loading organizations…" }) }) : /* @__PURE__ */ jsxRuntime.jsxs(Table$1, { children: [
|
|
2731
|
+
/* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
|
|
2732
|
+
rbac.allowedActions.canDelete && /* @__PURE__ */ jsxRuntime.jsx(THCheck$1, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1948
2733
|
designSystem.Checkbox,
|
|
1949
2734
|
{
|
|
1950
|
-
checked:
|
|
1951
|
-
onCheckedChange:
|
|
1952
|
-
"aria-label":
|
|
2735
|
+
checked: allSelected,
|
|
2736
|
+
onCheckedChange: handleSelectAll,
|
|
2737
|
+
"aria-label": "Select all"
|
|
1953
2738
|
}
|
|
1954
2739
|
) }),
|
|
1955
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
}
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2740
|
+
/* @__PURE__ */ jsxRuntime.jsx(TH$1, { children: "Name" }),
|
|
2741
|
+
/* @__PURE__ */ jsxRuntime.jsx(TH$1, { children: "Slug" }),
|
|
2742
|
+
/* @__PURE__ */ jsxRuntime.jsx(TH$1, { children: "Members" }),
|
|
2743
|
+
/* @__PURE__ */ jsxRuntime.jsx(TH$1, { children: "Created" }),
|
|
2744
|
+
/* @__PURE__ */ jsxRuntime.jsx(THActions$1, {})
|
|
2745
|
+
] }) }),
|
|
2746
|
+
/* @__PURE__ */ jsxRuntime.jsx("tbody", { children: orgs.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2747
|
+
TD$1,
|
|
2748
|
+
{
|
|
2749
|
+
colSpan: 6,
|
|
2750
|
+
style: {
|
|
2751
|
+
textAlign: "center",
|
|
2752
|
+
padding: "40px",
|
|
2753
|
+
color: "#8e8ea9"
|
|
2754
|
+
},
|
|
2755
|
+
"data-testid": "orgs-empty",
|
|
2756
|
+
children: search ? `No organizations matching "${search}"` : "No organizations found"
|
|
2757
|
+
}
|
|
2758
|
+
) }) : orgs.map((org, i) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2759
|
+
TR$1,
|
|
2760
|
+
{
|
|
2761
|
+
$selected: selected.has(org.id),
|
|
2762
|
+
$i: i,
|
|
2763
|
+
"data-testid": "org-row",
|
|
2764
|
+
children: [
|
|
2765
|
+
rbac.allowedActions.canDelete && /* @__PURE__ */ jsxRuntime.jsx(TDCheck$1, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2766
|
+
designSystem.Checkbox,
|
|
2767
|
+
{
|
|
2768
|
+
checked: selected.has(org.id),
|
|
2769
|
+
onCheckedChange: () => toggleSelect(org.id),
|
|
2770
|
+
"aria-label": `Select ${org.name}`
|
|
2771
|
+
}
|
|
2772
|
+
) }),
|
|
2773
|
+
/* @__PURE__ */ jsxRuntime.jsx(TD$1, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, children: [
|
|
2774
|
+
/* @__PURE__ */ jsxRuntime.jsx(OrgAvatar, { name: org.name, logo: org.logo }),
|
|
2775
|
+
/* @__PURE__ */ jsxRuntime.jsx(OrgName, { children: org.name })
|
|
2776
|
+
] }) }),
|
|
2777
|
+
/* @__PURE__ */ jsxRuntime.jsx(TD$1, { children: /* @__PURE__ */ jsxRuntime.jsx(SlugChip, { children: org.slug }) }),
|
|
2778
|
+
/* @__PURE__ */ jsxRuntime.jsx(TD$1, { children: /* @__PURE__ */ jsxRuntime.jsx(CountChip, { children: org.memberCount }) }),
|
|
2779
|
+
/* @__PURE__ */ jsxRuntime.jsx(TD$1, { children: /* @__PURE__ */ jsxRuntime.jsx(DateText$1, { children: new Date(org.createdAt).toLocaleDateString() }) }),
|
|
2780
|
+
/* @__PURE__ */ jsxRuntime.jsx(TDActions$1, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, justifyContent: "flex-end", children: [
|
|
2781
|
+
rbac.allowedActions.canUpdate && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2782
|
+
designSystem.IconButton,
|
|
2783
|
+
{
|
|
2784
|
+
label: "Edit organization",
|
|
2785
|
+
onClick: () => setDetailOrgId(org.id),
|
|
2786
|
+
"data-testid": "edit-org-btn",
|
|
2787
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(icons.Pencil, {})
|
|
2788
|
+
}
|
|
2789
|
+
),
|
|
2790
|
+
rbac.allowedActions.canDelete && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2791
|
+
designSystem.IconButton,
|
|
2792
|
+
{
|
|
2793
|
+
label: "Delete organization",
|
|
2794
|
+
onClick: () => setConfirmDelete(org.id),
|
|
2795
|
+
"data-testid": "delete-org-btn",
|
|
2796
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
|
|
2797
|
+
}
|
|
2798
|
+
)
|
|
2799
|
+
] }) })
|
|
2800
|
+
]
|
|
2801
|
+
},
|
|
2802
|
+
org.id
|
|
2803
|
+
)) })
|
|
2804
|
+
] }) }),
|
|
2805
|
+
pageCount > 1 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
|
|
2806
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2807
|
+
designSystem.Button,
|
|
2808
|
+
{
|
|
2809
|
+
variant: "tertiary",
|
|
2810
|
+
size: "S",
|
|
2811
|
+
disabled: page === 1,
|
|
2812
|
+
onClick: () => setPage((p) => p - 1),
|
|
2813
|
+
children: "Previous"
|
|
2814
|
+
}
|
|
2815
|
+
),
|
|
2816
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2817
|
+
designSystem.Button,
|
|
2818
|
+
{
|
|
2819
|
+
variant: "tertiary",
|
|
2820
|
+
size: "S",
|
|
2821
|
+
disabled: page >= pageCount,
|
|
2822
|
+
onClick: () => setPage((p) => p + 1),
|
|
2823
|
+
children: "Next"
|
|
2824
|
+
}
|
|
2825
|
+
)
|
|
2826
|
+
] }) }),
|
|
2827
|
+
rbac.allowedActions.canCreate && showCreate && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2828
|
+
CreateOrganizationDialog,
|
|
2829
|
+
{
|
|
2830
|
+
teamsEnabled,
|
|
2831
|
+
onClose: () => setShowCreate(false)
|
|
2832
|
+
}
|
|
2833
|
+
),
|
|
2834
|
+
rbac.allowedActions.canUpdate && detailOrgId && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2835
|
+
OrganizationDetail,
|
|
2836
|
+
{
|
|
2837
|
+
organizationId: detailOrgId,
|
|
2838
|
+
teamsEnabled,
|
|
2839
|
+
onClose: () => setDetailOrgId(null)
|
|
2840
|
+
}
|
|
2841
|
+
),
|
|
2842
|
+
rbac.allowedActions.canDelete && confirmDelete && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2843
|
+
ConfirmDialog,
|
|
2844
|
+
{
|
|
2845
|
+
title: "Delete organization",
|
|
2846
|
+
message: "Are you sure you want to delete this organization? All members and teams will be removed. This action cannot be undone.",
|
|
2847
|
+
confirmLabel: "Delete",
|
|
2848
|
+
loading: deleteMutation.isLoading,
|
|
2849
|
+
onConfirm: () => deleteMutation.mutate(confirmDelete),
|
|
2850
|
+
onCancel: () => setConfirmDelete(null)
|
|
2851
|
+
}
|
|
2852
|
+
),
|
|
2853
|
+
rbac.allowedActions.canDelete && confirmDeleteMany && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2854
|
+
ConfirmDialog,
|
|
2855
|
+
{
|
|
2856
|
+
title: `Delete ${selected.size} organization${selected.size !== 1 ? "s" : ""}`,
|
|
2857
|
+
message: `Are you sure you want to delete ${selected.size} organization${selected.size !== 1 ? "s" : ""}? This action cannot be undone.`,
|
|
2858
|
+
confirmLabel: "Delete all",
|
|
2859
|
+
loading: deleteManyMutation.isLoading,
|
|
2860
|
+
onConfirm: () => deleteManyMutation.mutate([...selected]),
|
|
2861
|
+
onCancel: () => setConfirmDeleteMany(false)
|
|
2862
|
+
}
|
|
2863
|
+
)
|
|
2864
|
+
] })
|
|
2865
|
+
}
|
|
2866
|
+
);
|
|
2867
|
+
}
|
|
2868
|
+
const PALETTE = [
|
|
2869
|
+
{ bg: "#EAF5FF", fg: "#0C75AF" },
|
|
2870
|
+
{ bg: "#F0F0FF", fg: "#4945FF" },
|
|
2871
|
+
{ bg: "#EAFBE7", fg: "#328048" },
|
|
2872
|
+
{ bg: "#FFF3D3", fg: "#8E6A00" },
|
|
2873
|
+
{ bg: "#FCECEA", fg: "#D02B20" },
|
|
2874
|
+
{ bg: "#F6ECFC", fg: "#8312D1" },
|
|
2875
|
+
{ bg: "#E8F8F5", fg: "#1A7F64" }
|
|
2876
|
+
];
|
|
2877
|
+
const Circle = styled__default.default.div`
|
|
2878
|
+
width: ${(p) => p.$size}px;
|
|
2879
|
+
height: ${(p) => p.$size}px;
|
|
2880
|
+
border-radius: 50%;
|
|
2881
|
+
background: ${(p) => p.$bg};
|
|
2882
|
+
color: ${(p) => p.$fg};
|
|
2883
|
+
display: inline-flex;
|
|
2884
|
+
align-items: center;
|
|
2885
|
+
justify-content: center;
|
|
2066
2886
|
font-size: ${(p) => Math.round(p.$size * 0.38)}px;
|
|
2067
2887
|
font-weight: 700;
|
|
2068
2888
|
flex-shrink: 0;
|
|
@@ -2105,95 +2925,143 @@ function Avatar({
|
|
|
2105
2925
|
const color = getColor(name || "?");
|
|
2106
2926
|
return /* @__PURE__ */ jsxRuntime.jsx(Circle, { $bg: color.bg, $fg: color.fg, $size: size, children: getInitials(name || "?") });
|
|
2107
2927
|
}
|
|
2108
|
-
const
|
|
2109
|
-
|
|
2928
|
+
const T = {
|
|
2929
|
+
bg: "#f6f6f9",
|
|
2930
|
+
bgCard: "#ffffff",
|
|
2931
|
+
border: "#eaeaef",
|
|
2932
|
+
borderHover: "rgba(73,69,255,0.35)",
|
|
2933
|
+
accent: "#4945ff",
|
|
2934
|
+
green: "#5cb176",
|
|
2935
|
+
greenDim: "rgba(92,177,118,0.12)",
|
|
2936
|
+
amber: "#d9822f",
|
|
2937
|
+
red: "#d02b20",
|
|
2938
|
+
redDim: "rgba(208,43,32,0.1)",
|
|
2939
|
+
purple: "#8460b8",
|
|
2940
|
+
textPrimary: "#32324d",
|
|
2941
|
+
textSecondary: "#666687",
|
|
2942
|
+
textMuted: "#b8b8c7",
|
|
2943
|
+
mono: `'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace`
|
|
2944
|
+
};
|
|
2945
|
+
const fadeUp$1 = styled.keyframes`
|
|
2946
|
+
from { opacity: 0; transform: translateY(10px); }
|
|
2110
2947
|
to { opacity: 1; transform: translateY(0); }
|
|
2111
2948
|
`;
|
|
2112
|
-
const Wrap$
|
|
2113
|
-
padding: 28px 32px;
|
|
2114
|
-
background:
|
|
2949
|
+
const Wrap$1 = styled__default.default.div`
|
|
2950
|
+
padding: 28px 32px 48px;
|
|
2951
|
+
background: ${T.bg};
|
|
2115
2952
|
min-height: 100%;
|
|
2116
2953
|
display: flex;
|
|
2117
2954
|
flex-direction: column;
|
|
2118
|
-
gap:
|
|
2955
|
+
gap: 22px;
|
|
2119
2956
|
`;
|
|
2120
|
-
const
|
|
2121
|
-
display:
|
|
2122
|
-
|
|
2123
|
-
gap:
|
|
2124
|
-
|
|
2125
|
-
@media (max-width: 768px) { grid-template-columns: repeat(2, 1fr); }
|
|
2957
|
+
const SectionDivider = styled__default.default.div`
|
|
2958
|
+
display: flex;
|
|
2959
|
+
align-items: center;
|
|
2960
|
+
gap: 10px;
|
|
2961
|
+
margin-bottom: -6px;
|
|
2126
2962
|
`;
|
|
2127
|
-
const
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2963
|
+
const DivLabel = styled__default.default.span`
|
|
2964
|
+
font-size: 9px;
|
|
2965
|
+
font-weight: 700;
|
|
2966
|
+
letter-spacing: 0.14em;
|
|
2967
|
+
text-transform: uppercase;
|
|
2968
|
+
color: ${T.textMuted};
|
|
2969
|
+
white-space: nowrap;
|
|
2132
2970
|
`;
|
|
2133
|
-
const
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2971
|
+
const DivLine = styled__default.default.div`
|
|
2972
|
+
flex: 1;
|
|
2973
|
+
height: 1px;
|
|
2974
|
+
background: ${T.border};
|
|
2137
2975
|
`;
|
|
2138
2976
|
const Card = styled__default.default.div`
|
|
2139
|
-
background:
|
|
2140
|
-
border: 1px solid
|
|
2141
|
-
border-radius:
|
|
2977
|
+
background: ${T.bgCard};
|
|
2978
|
+
border: 1px solid ${T.border};
|
|
2979
|
+
border-radius: 12px;
|
|
2142
2980
|
overflow: hidden;
|
|
2143
2981
|
position: relative;
|
|
2144
|
-
animation: ${fadeUp$
|
|
2982
|
+
animation: ${fadeUp$1} 380ms ease both;
|
|
2145
2983
|
animation-delay: ${(p) => (p.$delay ?? 0) * 55}ms;
|
|
2146
|
-
transition: border-color
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2984
|
+
transition: border-color 200ms, box-shadow 200ms;
|
|
2985
|
+
&:hover {
|
|
2986
|
+
border-color: ${T.borderHover};
|
|
2987
|
+
box-shadow: 0 0 0 1px ${T.borderHover}, 0 8px 32px rgba(124,109,250,0.07);
|
|
2988
|
+
}
|
|
2989
|
+
`;
|
|
2990
|
+
const PillGroup = styled__default.default.div`
|
|
2991
|
+
display: flex;
|
|
2992
|
+
background: rgba(0,0,0,0.03);
|
|
2993
|
+
border: 1px solid ${T.border};
|
|
2994
|
+
border-radius: 9px;
|
|
2995
|
+
padding: 3px;
|
|
2996
|
+
gap: 2px;
|
|
2997
|
+
`;
|
|
2998
|
+
const Pill = styled__default.default.button`
|
|
2999
|
+
appearance: none;
|
|
3000
|
+
border: none;
|
|
3001
|
+
cursor: pointer;
|
|
3002
|
+
padding: 5px 16px;
|
|
3003
|
+
border-radius: 6px;
|
|
3004
|
+
font-size: 11px;
|
|
3005
|
+
font-weight: 600;
|
|
3006
|
+
letter-spacing: 0.03em;
|
|
3007
|
+
transition: background 180ms, color 180ms;
|
|
3008
|
+
background: ${(p) => p.$active ? T.accent : "transparent"};
|
|
3009
|
+
color: ${(p) => p.$active ? "#fff" : T.textSecondary};
|
|
2159
3010
|
&:hover {
|
|
2160
|
-
|
|
2161
|
-
|
|
3011
|
+
background: ${(p) => p.$active ? T.accent : "rgba(0,0,0,0.05)"};
|
|
3012
|
+
color: ${(p) => p.$active ? "#fff" : T.textPrimary};
|
|
2162
3013
|
}
|
|
2163
3014
|
`;
|
|
2164
|
-
const
|
|
2165
|
-
|
|
3015
|
+
const StatGrid = styled__default.default.div`
|
|
3016
|
+
display: grid;
|
|
3017
|
+
grid-template-columns: repeat(5, 1fr);
|
|
3018
|
+
gap: 10px;
|
|
3019
|
+
@media (max-width: 1200px) { grid-template-columns: repeat(3, 1fr); }
|
|
3020
|
+
`;
|
|
3021
|
+
const StatCard = styled__default.default(Card)`
|
|
3022
|
+
padding: 18px 20px 0;
|
|
3023
|
+
&::after {
|
|
3024
|
+
content: '';
|
|
3025
|
+
position: absolute;
|
|
3026
|
+
top: 0; left: 0; right: 0;
|
|
3027
|
+
height: 2px;
|
|
3028
|
+
background: ${(p) => p.$accent};
|
|
3029
|
+
opacity: 0.75;
|
|
3030
|
+
}
|
|
2166
3031
|
`;
|
|
2167
3032
|
const StatLabel = styled__default.default.div`
|
|
2168
|
-
font-size:
|
|
3033
|
+
font-size: 9px;
|
|
2169
3034
|
font-weight: 700;
|
|
2170
|
-
letter-spacing: 0.
|
|
3035
|
+
letter-spacing: 0.1em;
|
|
2171
3036
|
text-transform: uppercase;
|
|
2172
|
-
color:
|
|
2173
|
-
margin-bottom:
|
|
3037
|
+
color: ${T.textMuted};
|
|
3038
|
+
margin-bottom: 10px;
|
|
2174
3039
|
`;
|
|
2175
3040
|
const StatValue = styled__default.default.div`
|
|
2176
3041
|
font-size: 30px;
|
|
2177
3042
|
font-weight: 800;
|
|
2178
|
-
color:
|
|
3043
|
+
color: ${T.textPrimary};
|
|
2179
3044
|
line-height: 1;
|
|
2180
|
-
letter-spacing: -0.
|
|
3045
|
+
letter-spacing: -0.045em;
|
|
2181
3046
|
font-variant-numeric: tabular-nums;
|
|
2182
3047
|
margin-bottom: 10px;
|
|
2183
3048
|
`;
|
|
2184
3049
|
const TrendBadge = styled__default.default.span`
|
|
2185
3050
|
display: inline-flex;
|
|
2186
3051
|
align-items: center;
|
|
2187
|
-
gap:
|
|
2188
|
-
padding: 2px
|
|
3052
|
+
gap: 3px;
|
|
3053
|
+
padding: 2px 8px;
|
|
2189
3054
|
border-radius: 20px;
|
|
2190
|
-
font-size:
|
|
3055
|
+
font-size: 9px;
|
|
2191
3056
|
font-weight: 700;
|
|
2192
|
-
|
|
2193
|
-
|
|
3057
|
+
letter-spacing: 0.03em;
|
|
3058
|
+
background: ${(p) => p.$pos ? T.greenDim : T.redDim};
|
|
3059
|
+
color: ${(p) => p.$pos ? T.green : T.red};
|
|
3060
|
+
margin-bottom: 12px;
|
|
2194
3061
|
`;
|
|
2195
3062
|
const SparkWrap = styled__default.default.div`
|
|
2196
|
-
height:
|
|
3063
|
+
height: 44px;
|
|
3064
|
+
margin: 0 -20px;
|
|
2197
3065
|
`;
|
|
2198
3066
|
const ChartCard = styled__default.default(Card)`
|
|
2199
3067
|
padding: 20px 20px 12px;
|
|
@@ -2205,31 +3073,42 @@ const ChartHeader = styled__default.default.div`
|
|
|
2205
3073
|
display: flex;
|
|
2206
3074
|
align-items: center;
|
|
2207
3075
|
justify-content: space-between;
|
|
3076
|
+
gap: 12px;
|
|
2208
3077
|
flex-shrink: 0;
|
|
2209
3078
|
`;
|
|
2210
3079
|
const ChartTitle = styled__default.default.div`
|
|
2211
|
-
font-size:
|
|
3080
|
+
font-size: 12px;
|
|
2212
3081
|
font-weight: 700;
|
|
2213
|
-
color:
|
|
3082
|
+
color: ${T.textPrimary};
|
|
3083
|
+
letter-spacing: 0.01em;
|
|
2214
3084
|
`;
|
|
2215
|
-
const
|
|
3085
|
+
const SeriesRow = styled__default.default.div`
|
|
2216
3086
|
display: flex;
|
|
2217
3087
|
align-items: center;
|
|
2218
|
-
gap:
|
|
3088
|
+
gap: 6px;
|
|
2219
3089
|
`;
|
|
2220
|
-
const
|
|
3090
|
+
const SeriesBtn = styled__default.default.button`
|
|
3091
|
+
appearance: none;
|
|
3092
|
+
border: 1px solid ${(p) => p.$on ? `${p.$c}55` : T.border};
|
|
3093
|
+
background: ${(p) => p.$on ? `${p.$c}18` : "transparent"};
|
|
3094
|
+
color: ${(p) => p.$on ? p.$c : T.textMuted};
|
|
3095
|
+
font-size: 10px;
|
|
3096
|
+
font-weight: 600;
|
|
3097
|
+
padding: 3px 10px 3px 8px;
|
|
3098
|
+
border-radius: 6px;
|
|
3099
|
+
cursor: pointer;
|
|
2221
3100
|
display: flex;
|
|
2222
3101
|
align-items: center;
|
|
2223
3102
|
gap: 5px;
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
3103
|
+
transition: all 160ms;
|
|
3104
|
+
&:hover {
|
|
3105
|
+
border-color: ${(p) => `${p.$c}88`};
|
|
3106
|
+
color: ${(p) => p.$c};
|
|
3107
|
+
background: ${(p) => `${p.$c}22`};
|
|
3108
|
+
}
|
|
2229
3109
|
`;
|
|
2230
|
-
const
|
|
2231
|
-
width:
|
|
2232
|
-
height: 8px;
|
|
3110
|
+
const SDot = styled__default.default.span`
|
|
3111
|
+
width: 6px; height: 6px;
|
|
2233
3112
|
border-radius: 50%;
|
|
2234
3113
|
background: ${(p) => p.$c};
|
|
2235
3114
|
display: inline-block;
|
|
@@ -2237,140 +3116,188 @@ const LegendDot = styled__default.default.span`
|
|
|
2237
3116
|
`;
|
|
2238
3117
|
const HoverInfo = styled__default.default.div`
|
|
2239
3118
|
font-size: 11px;
|
|
2240
|
-
color:
|
|
3119
|
+
color: ${T.textSecondary};
|
|
2241
3120
|
font-variant-numeric: tabular-nums;
|
|
2242
|
-
|
|
3121
|
+
font-family: ${T.mono};
|
|
3122
|
+
min-height: 15px;
|
|
3123
|
+
`;
|
|
3124
|
+
const TwoPanel = styled__default.default.div`
|
|
3125
|
+
display: grid;
|
|
3126
|
+
grid-template-columns: ${(p) => p.$ratio ?? "1fr 288px"};
|
|
3127
|
+
gap: 10px;
|
|
3128
|
+
align-items: stretch;
|
|
3129
|
+
@media (max-width: 900px) { grid-template-columns: 1fr; }
|
|
2243
3130
|
`;
|
|
2244
3131
|
const FeedCard = styled__default.default(Card)`
|
|
2245
3132
|
display: flex;
|
|
2246
3133
|
flex-direction: column;
|
|
2247
|
-
|
|
3134
|
+
overflow: hidden;
|
|
2248
3135
|
`;
|
|
2249
|
-
const
|
|
2250
|
-
padding: 16px
|
|
2251
|
-
border-bottom: 1px solid
|
|
2252
|
-
|
|
2253
|
-
font-size: 10px;
|
|
3136
|
+
const FeedHead = styled__default.default.div`
|
|
3137
|
+
padding: 13px 16px 9px;
|
|
3138
|
+
border-bottom: 1px solid ${T.border};
|
|
3139
|
+
font-size: 11px;
|
|
2254
3140
|
font-weight: 700;
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
3141
|
+
color: ${T.textPrimary};
|
|
3142
|
+
flex-shrink: 0;
|
|
3143
|
+
display: flex;
|
|
3144
|
+
justify-content: space-between;
|
|
3145
|
+
align-items: center;
|
|
2258
3146
|
`;
|
|
2259
|
-
const
|
|
3147
|
+
const FeedSelect = styled__default.default.select`
|
|
3148
|
+
appearance: none;
|
|
3149
|
+
border: 1px solid ${T.border};
|
|
3150
|
+
border-radius: 6px;
|
|
3151
|
+
background: ${T.bgCard};
|
|
3152
|
+
color: ${T.textSecondary};
|
|
3153
|
+
font-size: 10px;
|
|
3154
|
+
font-weight: 600;
|
|
3155
|
+
padding: 3px 22px 3px 8px;
|
|
3156
|
+
cursor: pointer;
|
|
3157
|
+
outline: none;
|
|
3158
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23666687'/%3E%3C/svg%3E");
|
|
3159
|
+
background-repeat: no-repeat;
|
|
3160
|
+
background-position: right 7px center;
|
|
3161
|
+
transition: border-color 160ms;
|
|
3162
|
+
&:hover, &:focus { border-color: ${T.accent}; color: ${T.textPrimary}; }
|
|
3163
|
+
`;
|
|
3164
|
+
const FeedScroll = styled__default.default.div`
|
|
2260
3165
|
flex: 1;
|
|
2261
3166
|
overflow-y: auto;
|
|
2262
|
-
|
|
2263
|
-
&::-webkit-scrollbar { width:
|
|
2264
|
-
&::-webkit-scrollbar-
|
|
2265
|
-
&::-webkit-scrollbar-thumb { background: #d9d8ff; border-radius: 2px; }
|
|
3167
|
+
min-height: 0;
|
|
3168
|
+
&::-webkit-scrollbar { width: 4px; }
|
|
3169
|
+
&::-webkit-scrollbar-thumb { background: ${T.textMuted}; border-radius: 2px; }
|
|
2266
3170
|
`;
|
|
2267
3171
|
const FeedItem = styled__default.default.div`
|
|
3172
|
+
padding: 9px 14px;
|
|
3173
|
+
border-bottom: 1px solid #f0f0f5;
|
|
2268
3174
|
display: flex;
|
|
2269
|
-
|
|
2270
|
-
gap:
|
|
2271
|
-
|
|
2272
|
-
&:hover { background: #
|
|
3175
|
+
flex-direction: column;
|
|
3176
|
+
gap: 3px;
|
|
3177
|
+
transition: background 130ms;
|
|
3178
|
+
&:hover { background: #fafaff; }
|
|
3179
|
+
&:last-child { border-bottom: none; }
|
|
2273
3180
|
`;
|
|
2274
|
-
const
|
|
2275
|
-
|
|
2276
|
-
|
|
3181
|
+
const FeedTop = styled__default.default.div`
|
|
3182
|
+
display: flex;
|
|
3183
|
+
align-items: center;
|
|
3184
|
+
justify-content: space-between;
|
|
3185
|
+
gap: 6px;
|
|
2277
3186
|
`;
|
|
2278
|
-
const FeedName = styled__default.default.
|
|
2279
|
-
font-size:
|
|
3187
|
+
const FeedName = styled__default.default.span`
|
|
3188
|
+
font-size: 11px;
|
|
2280
3189
|
font-weight: 600;
|
|
2281
|
-
color:
|
|
2282
|
-
white-space: nowrap;
|
|
3190
|
+
color: ${T.textPrimary};
|
|
2283
3191
|
overflow: hidden;
|
|
2284
3192
|
text-overflow: ellipsis;
|
|
3193
|
+
white-space: nowrap;
|
|
2285
3194
|
`;
|
|
2286
|
-
const
|
|
3195
|
+
const FeedEmail = styled__default.default.span`
|
|
2287
3196
|
font-size: 10px;
|
|
2288
|
-
color:
|
|
2289
|
-
white-space: nowrap;
|
|
3197
|
+
color: ${T.textSecondary};
|
|
2290
3198
|
overflow: hidden;
|
|
2291
3199
|
text-overflow: ellipsis;
|
|
2292
|
-
|
|
3200
|
+
white-space: nowrap;
|
|
3201
|
+
display: block;
|
|
2293
3202
|
`;
|
|
2294
|
-
const
|
|
2295
|
-
font-size:
|
|
2296
|
-
color:
|
|
3203
|
+
const FeedMeta = styled__default.default.span`
|
|
3204
|
+
font-size: 9px;
|
|
3205
|
+
color: ${T.textMuted};
|
|
2297
3206
|
white-space: nowrap;
|
|
2298
3207
|
font-variant-numeric: tabular-nums;
|
|
2299
|
-
margin-top: 1px;
|
|
2300
3208
|
`;
|
|
2301
|
-
const
|
|
2302
|
-
|
|
2303
|
-
|
|
3209
|
+
const RtnCard = styled__default.default(Card)`
|
|
3210
|
+
padding: 20px;
|
|
3211
|
+
display: flex;
|
|
3212
|
+
flex-direction: column;
|
|
3213
|
+
gap: 12px;
|
|
2304
3214
|
`;
|
|
2305
|
-
const
|
|
2306
|
-
|
|
2307
|
-
|
|
3215
|
+
const RtnRow = styled__default.default.div`
|
|
3216
|
+
display: flex;
|
|
3217
|
+
align-items: center;
|
|
3218
|
+
gap: 10px;
|
|
3219
|
+
padding: 4px 6px;
|
|
3220
|
+
border-radius: 7px;
|
|
3221
|
+
transition: background 130ms;
|
|
3222
|
+
background: ${(p) => p.$hov ? "#f0f0f6" : "transparent"};
|
|
3223
|
+
cursor: default;
|
|
2308
3224
|
`;
|
|
2309
|
-
const
|
|
2310
|
-
text-align: left;
|
|
2311
|
-
padding: 10px 14px;
|
|
3225
|
+
const RtnLabel = styled__default.default.span`
|
|
2312
3226
|
font-size: 10px;
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
text-
|
|
2316
|
-
letter-spacing: 0.06em;
|
|
2317
|
-
border-bottom: 1px solid #eaeaef;
|
|
2318
|
-
background: #fafafa;
|
|
3227
|
+
color: ${T.textSecondary};
|
|
3228
|
+
min-width: 80px;
|
|
3229
|
+
text-align: right;
|
|
2319
3230
|
white-space: nowrap;
|
|
2320
|
-
&:first-child { padding-left: 20px; }
|
|
2321
|
-
&:last-child { padding-right: 20px; }
|
|
2322
3231
|
`;
|
|
2323
|
-
const
|
|
2324
|
-
|
|
2325
|
-
|
|
3232
|
+
const RtnSize = styled__default.default.span`
|
|
3233
|
+
font-size: 9px;
|
|
3234
|
+
color: ${T.textMuted};
|
|
3235
|
+
min-width: 52px;
|
|
3236
|
+
text-align: right;
|
|
3237
|
+
white-space: nowrap;
|
|
3238
|
+
font-variant-numeric: tabular-nums;
|
|
2326
3239
|
`;
|
|
2327
|
-
const
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
border-
|
|
2332
|
-
|
|
2333
|
-
&:first-child { padding-left: 20px; }
|
|
2334
|
-
&:last-child { padding-right: 20px; }
|
|
3240
|
+
const RtnTrack = styled__default.default.div`
|
|
3241
|
+
flex: 1;
|
|
3242
|
+
height: 10px;
|
|
3243
|
+
background: #eaeaef;
|
|
3244
|
+
border-radius: 4px;
|
|
3245
|
+
overflow: hidden;
|
|
2335
3246
|
`;
|
|
2336
|
-
const
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
3247
|
+
const RtnBar = styled__default.default.div`
|
|
3248
|
+
width: ${(p) => Math.max(p.$w, 0.5)}%;
|
|
3249
|
+
height: 100%;
|
|
3250
|
+
background: hsl(${(p) => p.$hue}, 60%, 50%);
|
|
3251
|
+
border-radius: 4px;
|
|
3252
|
+
transition: width 0.55s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
3253
|
+
`;
|
|
3254
|
+
const RtnPct = styled__default.default.span`
|
|
2342
3255
|
font-size: 10px;
|
|
2343
3256
|
font-weight: 700;
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
display: inline-block;
|
|
2350
|
-
width: 5px;
|
|
2351
|
-
height: 5px;
|
|
2352
|
-
border-radius: 50%;
|
|
2353
|
-
background: ${(p) => p.$banned ? "#d02b20" : p.$verified ? "#5cb176" : "#b8b8c7"};
|
|
2354
|
-
}
|
|
3257
|
+
color: hsl(${(p) => p.$hue}, 60%, 60%);
|
|
3258
|
+
min-width: 38px;
|
|
3259
|
+
text-align: right;
|
|
3260
|
+
font-variant-numeric: tabular-nums;
|
|
3261
|
+
font-family: ${T.mono};
|
|
2355
3262
|
`;
|
|
2356
|
-
const
|
|
3263
|
+
const RtnTip = styled__default.default.div`
|
|
3264
|
+
font-size: 10px;
|
|
3265
|
+
color: ${T.textSecondary};
|
|
3266
|
+
min-height: 14px;
|
|
3267
|
+
font-variant-numeric: tabular-nums;
|
|
3268
|
+
padding: 0 6px;
|
|
3269
|
+
`;
|
|
3270
|
+
const RingGrid = styled__default.default.div`
|
|
2357
3271
|
display: flex;
|
|
2358
|
-
|
|
3272
|
+
flex-direction: column;
|
|
2359
3273
|
gap: 10px;
|
|
2360
|
-
margin-bottom: -8px;
|
|
2361
3274
|
`;
|
|
2362
|
-
const
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
white-space: nowrap;
|
|
3275
|
+
const RingCard = styled__default.default(Card)`
|
|
3276
|
+
padding: 18px 20px;
|
|
3277
|
+
display: flex;
|
|
3278
|
+
align-items: center;
|
|
3279
|
+
gap: 16px;
|
|
3280
|
+
flex: 1;
|
|
2369
3281
|
`;
|
|
2370
|
-
const
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
3282
|
+
const RingInfo = styled__default.default.div`
|
|
3283
|
+
display: flex;
|
|
3284
|
+
flex-direction: column;
|
|
3285
|
+
gap: 4px;
|
|
3286
|
+
`;
|
|
3287
|
+
const RingVal = styled__default.default.div`
|
|
3288
|
+
font-size: 24px;
|
|
3289
|
+
font-weight: 800;
|
|
3290
|
+
color: ${T.textPrimary};
|
|
3291
|
+
letter-spacing: -0.04em;
|
|
3292
|
+
font-variant-numeric: tabular-nums;
|
|
3293
|
+
line-height: 1;
|
|
3294
|
+
`;
|
|
3295
|
+
const RingLabel = styled__default.default.div`
|
|
3296
|
+
font-size: 9px;
|
|
3297
|
+
font-weight: 700;
|
|
3298
|
+
text-transform: uppercase;
|
|
3299
|
+
letter-spacing: 0.1em;
|
|
3300
|
+
color: ${T.textMuted};
|
|
2374
3301
|
`;
|
|
2375
3302
|
const Empty = styled__default.default.div`
|
|
2376
3303
|
display: flex;
|
|
@@ -2378,32 +3305,52 @@ const Empty = styled__default.default.div`
|
|
|
2378
3305
|
align-items: center;
|
|
2379
3306
|
justify-content: center;
|
|
2380
3307
|
padding: 32px;
|
|
2381
|
-
color:
|
|
3308
|
+
color: ${T.textMuted};
|
|
2382
3309
|
font-size: 12px;
|
|
2383
|
-
gap:
|
|
3310
|
+
gap: 8px;
|
|
2384
3311
|
`;
|
|
3312
|
+
function useCountUp(target, duration = 850) {
|
|
3313
|
+
const [count, setCount] = react.useState(0);
|
|
3314
|
+
const prevRef = react.useRef(0);
|
|
3315
|
+
react.useEffect(() => {
|
|
3316
|
+
let raf;
|
|
3317
|
+
let start = 0;
|
|
3318
|
+
const from = prevRef.current;
|
|
3319
|
+
const diff = target - from;
|
|
3320
|
+
const step = (ts) => {
|
|
3321
|
+
if (!start) start = ts;
|
|
3322
|
+
const progress = Math.min((ts - start) / duration, 1);
|
|
3323
|
+
const eased = 1 - (1 - progress) ** 3;
|
|
3324
|
+
setCount(Math.round(from + eased * diff));
|
|
3325
|
+
if (progress < 1) raf = requestAnimationFrame(step);
|
|
3326
|
+
else prevRef.current = target;
|
|
3327
|
+
};
|
|
3328
|
+
raf = requestAnimationFrame(step);
|
|
3329
|
+
return () => cancelAnimationFrame(raf);
|
|
3330
|
+
}, [target, duration]);
|
|
3331
|
+
return count;
|
|
3332
|
+
}
|
|
2385
3333
|
function Sparkline({
|
|
2386
3334
|
data,
|
|
2387
3335
|
color,
|
|
2388
3336
|
id
|
|
2389
3337
|
}) {
|
|
2390
3338
|
const W = 300;
|
|
2391
|
-
const H =
|
|
3339
|
+
const H = 44;
|
|
2392
3340
|
if (data.length < 2) return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "100%", height: H });
|
|
2393
3341
|
const min = Math.min(...data);
|
|
2394
3342
|
const max = Math.max(...data, min + 1);
|
|
2395
|
-
const range = max - min;
|
|
2396
3343
|
const pts = data.map((v, i) => ({
|
|
2397
3344
|
x: i / (data.length - 1) * W,
|
|
2398
|
-
y: H -
|
|
3345
|
+
y: H - 3 - (v - min) / (max - min) * (H - 8)
|
|
2399
3346
|
}));
|
|
2400
|
-
const line = pts.reduce((
|
|
3347
|
+
const line = pts.reduce((a, p, i) => {
|
|
2401
3348
|
if (i === 0) return `M ${p.x} ${p.y}`;
|
|
2402
3349
|
const pr = pts[i - 1];
|
|
2403
3350
|
const cx = (pr.x + p.x) / 2;
|
|
2404
|
-
return `${
|
|
3351
|
+
return `${a} C ${cx} ${pr.y} ${cx} ${p.y} ${p.x} ${p.y}`;
|
|
2405
3352
|
}, "");
|
|
2406
|
-
const area = `${line} L ${pts
|
|
3353
|
+
const area = `${line} L ${pts.at(-1).x} ${H} L 0 ${H} Z`;
|
|
2407
3354
|
const gid = `spk-${id}`;
|
|
2408
3355
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2409
3356
|
"svg",
|
|
@@ -2412,10 +3359,10 @@ function Sparkline({
|
|
|
2412
3359
|
height: H,
|
|
2413
3360
|
viewBox: `0 0 ${W} ${H}`,
|
|
2414
3361
|
preserveAspectRatio: "none",
|
|
2415
|
-
"aria-hidden":
|
|
3362
|
+
"aria-hidden": true,
|
|
2416
3363
|
children: [
|
|
2417
3364
|
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs("linearGradient", { id: gid, x1: "0", y1: "0", x2: "0", y2: "1", children: [
|
|
2418
|
-
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: color, stopOpacity: "0.
|
|
3365
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: color, stopOpacity: "0.32" }),
|
|
2419
3366
|
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: color, stopOpacity: "0" })
|
|
2420
3367
|
] }) }),
|
|
2421
3368
|
/* @__PURE__ */ jsxRuntime.jsx("path", { d: area, fill: `url(#${gid})` }),
|
|
@@ -2424,57 +3371,58 @@ function Sparkline({
|
|
|
2424
3371
|
{
|
|
2425
3372
|
d: line,
|
|
2426
3373
|
stroke: color,
|
|
2427
|
-
strokeWidth: "
|
|
3374
|
+
strokeWidth: "1.6",
|
|
2428
3375
|
fill: "none",
|
|
2429
|
-
strokeLinecap: "round"
|
|
2430
|
-
strokeLinejoin: "round"
|
|
3376
|
+
strokeLinecap: "round"
|
|
2431
3377
|
}
|
|
2432
3378
|
)
|
|
2433
3379
|
]
|
|
2434
3380
|
}
|
|
2435
3381
|
);
|
|
2436
3382
|
}
|
|
2437
|
-
const
|
|
2438
|
-
{ key: "totalUsers", color:
|
|
2439
|
-
{ key: "newUsers", color:
|
|
2440
|
-
{ key: "activeUsers", color:
|
|
3383
|
+
const ALL_SERIES = [
|
|
3384
|
+
{ key: "totalUsers", color: T.accent, label: "Total" },
|
|
3385
|
+
{ key: "newUsers", color: T.green, label: "New" },
|
|
3386
|
+
{ key: "activeUsers", color: T.amber, label: "Active" }
|
|
2441
3387
|
];
|
|
2442
|
-
function
|
|
3388
|
+
function GrowthChart({
|
|
2443
3389
|
data,
|
|
2444
3390
|
hovered,
|
|
2445
|
-
onHover
|
|
3391
|
+
onHover,
|
|
3392
|
+
activeSeries
|
|
2446
3393
|
}) {
|
|
2447
3394
|
const W = 600;
|
|
2448
|
-
const H =
|
|
2449
|
-
const PL =
|
|
3395
|
+
const H = 200;
|
|
3396
|
+
const PL = 40;
|
|
2450
3397
|
const PR = 12;
|
|
2451
3398
|
const PT = 12;
|
|
2452
3399
|
const PB = 28;
|
|
2453
3400
|
const CW = W - PL - PR;
|
|
2454
3401
|
const CH = H - PT - PB;
|
|
2455
|
-
if (data.length
|
|
3402
|
+
if (!data.length) {
|
|
2456
3403
|
return /* @__PURE__ */ jsxRuntime.jsxs(Empty, { children: [
|
|
2457
3404
|
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 24 }, children: "📊" }),
|
|
2458
3405
|
/* @__PURE__ */ jsxRuntime.jsx("div", { children: "No growth data for this period" })
|
|
2459
3406
|
] });
|
|
2460
3407
|
}
|
|
2461
|
-
const
|
|
3408
|
+
const active = ALL_SERIES.filter((s) => activeSeries.has(s.key));
|
|
3409
|
+
const allVals = data.flatMap((d) => active.map((s) => d[s.key]));
|
|
2462
3410
|
const maxV = Math.max(...allVals, 10);
|
|
2463
3411
|
const yMax = Math.ceil(maxV * 1.15);
|
|
2464
3412
|
const xp = (i) => PL + i / Math.max(data.length - 1, 1) * CW;
|
|
2465
3413
|
const yp = (v) => PT + (1 - v / yMax) * CH;
|
|
2466
3414
|
const smooth = (vals) => {
|
|
2467
3415
|
const pts = vals.map((v, i) => ({ x: xp(i), y: yp(v) }));
|
|
2468
|
-
const path = pts.reduce((
|
|
3416
|
+
const path = pts.reduce((a, p, i) => {
|
|
2469
3417
|
if (i === 0) return `M ${p.x} ${p.y}`;
|
|
2470
3418
|
const pr = pts[i - 1];
|
|
2471
3419
|
const cx = (pr.x + p.x) / 2;
|
|
2472
|
-
return `${
|
|
3420
|
+
return `${a} C ${cx} ${pr.y} ${cx} ${p.y} ${p.x} ${p.y}`;
|
|
2473
3421
|
}, "");
|
|
2474
|
-
const area = `${path} L ${pts
|
|
3422
|
+
const area = `${path} L ${pts.at(-1).x} ${PT + CH} L ${pts[0].x} ${PT + CH} Z`;
|
|
2475
3423
|
return { pts, path, area };
|
|
2476
3424
|
};
|
|
2477
|
-
const lines =
|
|
3425
|
+
const lines = active.map((s) => ({
|
|
2478
3426
|
...s,
|
|
2479
3427
|
...smooth(data.map((d) => d[s.key]))
|
|
2480
3428
|
}));
|
|
@@ -2497,17 +3445,17 @@ function AreaChart({
|
|
|
2497
3445
|
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: lines.map((s) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2498
3446
|
"linearGradient",
|
|
2499
3447
|
{
|
|
2500
|
-
id: `ag-${s.
|
|
3448
|
+
id: `ag-${s.key}`,
|
|
2501
3449
|
x1: "0",
|
|
2502
3450
|
y1: "0",
|
|
2503
3451
|
x2: "0",
|
|
2504
3452
|
y2: "1",
|
|
2505
3453
|
children: [
|
|
2506
|
-
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: s.color, stopOpacity: "0.
|
|
3454
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: s.color, stopOpacity: "0.18" }),
|
|
2507
3455
|
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: s.color, stopOpacity: "0" })
|
|
2508
3456
|
]
|
|
2509
3457
|
},
|
|
2510
|
-
s.
|
|
3458
|
+
s.key
|
|
2511
3459
|
)) }),
|
|
2512
3460
|
yTicks.map((t) => /* @__PURE__ */ jsxRuntime.jsxs("g", { children: [
|
|
2513
3461
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2527,7 +3475,7 @@ function AreaChart({
|
|
|
2527
3475
|
x: PL - 6,
|
|
2528
3476
|
y: t.y + 4,
|
|
2529
3477
|
textAnchor: "end",
|
|
2530
|
-
fill:
|
|
3478
|
+
fill: T.textMuted,
|
|
2531
3479
|
fontSize: "9",
|
|
2532
3480
|
children: t.v >= 1e3 ? `${(t.v / 1e3).toFixed(1)}k` : t.v
|
|
2533
3481
|
}
|
|
@@ -2544,25 +3492,18 @@ function AreaChart({
|
|
|
2544
3492
|
strokeWidth: "1"
|
|
2545
3493
|
}
|
|
2546
3494
|
),
|
|
2547
|
-
lines.map((s) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2548
|
-
"path",
|
|
2549
|
-
{
|
|
2550
|
-
d: s.area,
|
|
2551
|
-
fill: `url(#ag-${s.color.replace("#", "")})`
|
|
2552
|
-
},
|
|
2553
|
-
`a-${s.color}`
|
|
2554
|
-
)),
|
|
3495
|
+
lines.map((s) => /* @__PURE__ */ jsxRuntime.jsx("path", { d: s.area, fill: `url(#ag-${s.key})` }, `area-${s.key}`)),
|
|
2555
3496
|
lines.map((s) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2556
3497
|
"path",
|
|
2557
3498
|
{
|
|
2558
3499
|
d: s.path,
|
|
2559
3500
|
stroke: s.color,
|
|
2560
|
-
strokeWidth: "
|
|
3501
|
+
strokeWidth: "2",
|
|
2561
3502
|
fill: "none",
|
|
2562
3503
|
strokeLinecap: "round",
|
|
2563
3504
|
strokeLinejoin: "round"
|
|
2564
3505
|
},
|
|
2565
|
-
`
|
|
3506
|
+
`line-${s.key}`
|
|
2566
3507
|
)),
|
|
2567
3508
|
data.map((d, i) => {
|
|
2568
3509
|
if (i % step !== 0 && i !== data.length - 1) return null;
|
|
@@ -2572,7 +3513,7 @@ function AreaChart({
|
|
|
2572
3513
|
x: xp(i),
|
|
2573
3514
|
y: H - 6,
|
|
2574
3515
|
textAnchor: "middle",
|
|
2575
|
-
fill:
|
|
3516
|
+
fill: T.textMuted,
|
|
2576
3517
|
fontSize: "9",
|
|
2577
3518
|
children: d.label
|
|
2578
3519
|
},
|
|
@@ -2582,7 +3523,7 @@ function AreaChart({
|
|
|
2582
3523
|
data.map((d, i) => {
|
|
2583
3524
|
const isHov = hovered === i;
|
|
2584
3525
|
return (
|
|
2585
|
-
// biome-ignore lint/a11y/noStaticElementInteractions: SVG chart hover
|
|
3526
|
+
// biome-ignore lint/a11y/noStaticElementInteractions: SVG chart hover
|
|
2586
3527
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2587
3528
|
"g",
|
|
2588
3529
|
{
|
|
@@ -2607,10 +3548,9 @@ function AreaChart({
|
|
|
2607
3548
|
y1: PT,
|
|
2608
3549
|
x2: xp(i),
|
|
2609
3550
|
y2: PT + CH,
|
|
2610
|
-
stroke: "
|
|
3551
|
+
stroke: "rgba(50,50,77,0.2)",
|
|
2611
3552
|
strokeWidth: "1",
|
|
2612
|
-
strokeDasharray: "3 3"
|
|
2613
|
-
opacity: "0.35"
|
|
3553
|
+
strokeDasharray: "3 3"
|
|
2614
3554
|
}
|
|
2615
3555
|
),
|
|
2616
3556
|
lines.map((s) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2620,11 +3560,11 @@ function AreaChart({
|
|
|
2620
3560
|
cy: yp(d[s.key]),
|
|
2621
3561
|
r: isHov ? 4 : 2.5,
|
|
2622
3562
|
fill: s.color,
|
|
2623
|
-
stroke:
|
|
3563
|
+
stroke: T.bg,
|
|
2624
3564
|
strokeWidth: isHov ? 2 : 1,
|
|
2625
|
-
opacity: isHov ? 1 : 0.
|
|
3565
|
+
opacity: isHov ? 1 : 0.5
|
|
2626
3566
|
},
|
|
2627
|
-
s.
|
|
3567
|
+
s.key
|
|
2628
3568
|
))
|
|
2629
3569
|
]
|
|
2630
3570
|
},
|
|
@@ -2636,23 +3576,70 @@ function AreaChart({
|
|
|
2636
3576
|
}
|
|
2637
3577
|
);
|
|
2638
3578
|
}
|
|
3579
|
+
function ProgressRing({
|
|
3580
|
+
value,
|
|
3581
|
+
max,
|
|
3582
|
+
color,
|
|
3583
|
+
size = 56
|
|
3584
|
+
}) {
|
|
3585
|
+
const r = (size - 9) / 2;
|
|
3586
|
+
const circ = 2 * Math.PI * r;
|
|
3587
|
+
const pct = Math.min(value / Math.max(max, 1), 1);
|
|
3588
|
+
const offset = circ * (1 - pct);
|
|
3589
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3590
|
+
"svg",
|
|
3591
|
+
{
|
|
3592
|
+
width: size,
|
|
3593
|
+
height: size,
|
|
3594
|
+
style: { flexShrink: 0 },
|
|
3595
|
+
"aria-hidden": true,
|
|
3596
|
+
children: [
|
|
3597
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3598
|
+
"circle",
|
|
3599
|
+
{
|
|
3600
|
+
cx: size / 2,
|
|
3601
|
+
cy: size / 2,
|
|
3602
|
+
r,
|
|
3603
|
+
fill: "none",
|
|
3604
|
+
stroke: "#e5e7eb",
|
|
3605
|
+
strokeWidth: "7"
|
|
3606
|
+
}
|
|
3607
|
+
),
|
|
3608
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3609
|
+
"circle",
|
|
3610
|
+
{
|
|
3611
|
+
cx: size / 2,
|
|
3612
|
+
cy: size / 2,
|
|
3613
|
+
r,
|
|
3614
|
+
fill: "none",
|
|
3615
|
+
stroke: color,
|
|
3616
|
+
strokeWidth: "7",
|
|
3617
|
+
strokeDasharray: `${circ} ${circ}`,
|
|
3618
|
+
strokeDashoffset: offset,
|
|
3619
|
+
strokeLinecap: "round",
|
|
3620
|
+
transform: `rotate(-90 ${size / 2} ${size / 2})`,
|
|
3621
|
+
style: {
|
|
3622
|
+
transition: "stroke-dashoffset 0.9s cubic-bezier(0.4,0,0.2,1)"
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
)
|
|
3626
|
+
]
|
|
3627
|
+
}
|
|
3628
|
+
);
|
|
3629
|
+
}
|
|
2639
3630
|
function relTime(date) {
|
|
2640
|
-
const
|
|
2641
|
-
const
|
|
2642
|
-
if (
|
|
2643
|
-
if (
|
|
2644
|
-
const
|
|
2645
|
-
if (
|
|
2646
|
-
return `${Math.floor(
|
|
3631
|
+
const diff = Date.now() - new Date(date).getTime();
|
|
3632
|
+
const mins = Math.floor(diff / 6e4);
|
|
3633
|
+
if (mins < 1) return "just now";
|
|
3634
|
+
if (mins < 60) return `${mins}m`;
|
|
3635
|
+
const hrs = Math.floor(mins / 60);
|
|
3636
|
+
if (hrs < 24) return `${hrs}h`;
|
|
3637
|
+
return `${Math.floor(hrs / 24)}d`;
|
|
2647
3638
|
}
|
|
2648
|
-
function
|
|
2649
|
-
return
|
|
2650
|
-
month: "short",
|
|
2651
|
-
day: "numeric",
|
|
2652
|
-
year: "numeric"
|
|
2653
|
-
});
|
|
3639
|
+
function rateHue(r) {
|
|
3640
|
+
return r >= 70 ? 142 : r >= 40 ? 38 : 4;
|
|
2654
3641
|
}
|
|
2655
|
-
function
|
|
3642
|
+
function StatItem({
|
|
2656
3643
|
id,
|
|
2657
3644
|
label,
|
|
2658
3645
|
value,
|
|
@@ -2661,26 +3648,39 @@ function StatCardItem({
|
|
|
2661
3648
|
color,
|
|
2662
3649
|
delay = 0
|
|
2663
3650
|
}) {
|
|
3651
|
+
const animated = useCountUp(value);
|
|
2664
3652
|
const isPos = pct === void 0 || pct >= 0;
|
|
2665
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2666
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
"%"
|
|
2674
|
-
] })
|
|
3653
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(StatCard, { $delay: delay, $accent: color, children: [
|
|
3654
|
+
/* @__PURE__ */ jsxRuntime.jsx(StatLabel, { children: label }),
|
|
3655
|
+
/* @__PURE__ */ jsxRuntime.jsx(StatValue, { children: animated.toLocaleString() }),
|
|
3656
|
+
pct !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs(TrendBadge, { $pos: isPos, children: [
|
|
3657
|
+
isPos ? "↑" : "↓",
|
|
3658
|
+
" ",
|
|
3659
|
+
Math.abs(pct).toFixed(1),
|
|
3660
|
+
"%"
|
|
2675
3661
|
] }),
|
|
2676
3662
|
sparkline && sparkline.length > 1 && /* @__PURE__ */ jsxRuntime.jsx(SparkWrap, { children: /* @__PURE__ */ jsxRuntime.jsx(Sparkline, { data: sparkline, color, id }) })
|
|
2677
3663
|
] });
|
|
2678
3664
|
}
|
|
2679
3665
|
function OverviewPage() {
|
|
3666
|
+
const { get } = admin.useFetchClient();
|
|
2680
3667
|
const [period, setPeriod] = react.useState(
|
|
2681
3668
|
"weekly"
|
|
2682
3669
|
);
|
|
3670
|
+
const [feedMode, setFeedMode] = react.useState("signups");
|
|
2683
3671
|
const [hovIdx, setHovIdx] = react.useState(null);
|
|
3672
|
+
const [rtnHov, setRtnHov] = react.useState(null);
|
|
3673
|
+
const [activeSeries, setActiveSeries] = react.useState(
|
|
3674
|
+
() => /* @__PURE__ */ new Set(["totalUsers", "newUsers", "activeUsers"])
|
|
3675
|
+
);
|
|
3676
|
+
const toggleSeries = (key) => {
|
|
3677
|
+
setActiveSeries((prev) => {
|
|
3678
|
+
const next = new Set(prev);
|
|
3679
|
+
if (next.has(key) && next.size > 1) next.delete(key);
|
|
3680
|
+
else next.add(key);
|
|
3681
|
+
return next;
|
|
3682
|
+
});
|
|
3683
|
+
};
|
|
2684
3684
|
const statsQuery = reactQuery.useQuery({
|
|
2685
3685
|
queryKey: ["dash-user-stats"],
|
|
2686
3686
|
queryFn: async () => {
|
|
@@ -2697,12 +3697,10 @@ function OverviewPage() {
|
|
|
2697
3697
|
return r.data;
|
|
2698
3698
|
}
|
|
2699
3699
|
});
|
|
2700
|
-
const
|
|
2701
|
-
queryKey: ["dash-
|
|
3700
|
+
const retentionQuery = reactQuery.useQuery({
|
|
3701
|
+
queryKey: ["dash-user-retention", period],
|
|
2702
3702
|
queryFn: async () => {
|
|
2703
|
-
const r = await client.dash.
|
|
2704
|
-
query: { page: 1, limit: 10 }
|
|
2705
|
-
});
|
|
3703
|
+
const r = await client.dash.userRetentionData({ query: { period } });
|
|
2706
3704
|
if (r.error) throw new Error(r.error.message ?? "Failed");
|
|
2707
3705
|
return r.data;
|
|
2708
3706
|
}
|
|
@@ -2711,12 +3709,22 @@ function OverviewPage() {
|
|
|
2711
3709
|
queryKey: ["dash-recent-users"],
|
|
2712
3710
|
queryFn: async () => {
|
|
2713
3711
|
const r = await client.dash.listUsers({
|
|
2714
|
-
query: {
|
|
3712
|
+
query: { limit: 12, offset: 0, sortBy: "createdAt", sortOrder: "desc" }
|
|
2715
3713
|
});
|
|
2716
3714
|
if (r.error) throw new Error(r.error.message ?? "Failed");
|
|
2717
3715
|
return r.data;
|
|
2718
3716
|
}
|
|
2719
3717
|
});
|
|
3718
|
+
const sessionsQuery = reactQuery.useQuery({
|
|
3719
|
+
queryKey: ["dash-recent-sessions"],
|
|
3720
|
+
queryFn: async () => {
|
|
3721
|
+
const { data } = await get(
|
|
3722
|
+
"/better-auth-dashboard/db?uid=plugin::better-auth.session&pagination[pageSize]=12&sort[0]=updatedAt:desc"
|
|
3723
|
+
);
|
|
3724
|
+
return data.results ?? [];
|
|
3725
|
+
},
|
|
3726
|
+
refetchInterval: 3e4
|
|
3727
|
+
});
|
|
2720
3728
|
const orgsQuery = reactQuery.useQuery({
|
|
2721
3729
|
queryKey: ["dash-orgs-count"],
|
|
2722
3730
|
queryFn: async () => {
|
|
@@ -2727,596 +3735,403 @@ function OverviewPage() {
|
|
|
2727
3735
|
return r.data;
|
|
2728
3736
|
}
|
|
2729
3737
|
});
|
|
3738
|
+
const sessionsRaw = sessionsQuery.data ?? [];
|
|
3739
|
+
const activeUserIds = [];
|
|
3740
|
+
const lastActiveByUserId = /* @__PURE__ */ new Map();
|
|
3741
|
+
for (const s of sessionsRaw) {
|
|
3742
|
+
const uid = String(s.userId);
|
|
3743
|
+
if (!lastActiveByUserId.has(uid)) {
|
|
3744
|
+
lastActiveByUserId.set(uid, s.updatedAt ?? s.createdAt);
|
|
3745
|
+
activeUserIds.push(uid);
|
|
3746
|
+
}
|
|
3747
|
+
}
|
|
3748
|
+
const activeUsersQuery = reactQuery.useQuery({
|
|
3749
|
+
queryKey: ["dash-active-users", activeUserIds],
|
|
3750
|
+
queryFn: async () => {
|
|
3751
|
+
if (activeUserIds.length === 0) return [];
|
|
3752
|
+
const params = new URLSearchParams({ uid: "plugin::better-auth.user" });
|
|
3753
|
+
for (let i = 0; i < activeUserIds.length; i++) {
|
|
3754
|
+
params.set(`filters[id][$in][${i}]`, activeUserIds[i]);
|
|
3755
|
+
}
|
|
3756
|
+
params.set("pagination[pageSize]", "12");
|
|
3757
|
+
const { data } = await get(
|
|
3758
|
+
`/better-auth-dashboard/db?${params}`
|
|
3759
|
+
);
|
|
3760
|
+
return data.results ?? [];
|
|
3761
|
+
},
|
|
3762
|
+
enabled: feedMode === "active" && activeUserIds.length > 0
|
|
3763
|
+
});
|
|
3764
|
+
const chartRef = react.useRef(null);
|
|
3765
|
+
const [chartHeight, setChartHeight] = react.useState(0);
|
|
3766
|
+
react.useEffect(() => {
|
|
3767
|
+
const el = chartRef.current;
|
|
3768
|
+
if (!el) return;
|
|
3769
|
+
const ro = new ResizeObserver((entries) => {
|
|
3770
|
+
for (const entry of entries) setChartHeight(entry.contentRect.height);
|
|
3771
|
+
});
|
|
3772
|
+
ro.observe(el);
|
|
3773
|
+
return () => ro.disconnect();
|
|
3774
|
+
}, []);
|
|
2730
3775
|
if (statsQuery.isLoading) {
|
|
2731
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3776
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3777
|
+
designSystem.Flex,
|
|
3778
|
+
{
|
|
3779
|
+
justifyContent: "center",
|
|
3780
|
+
alignItems: "center",
|
|
3781
|
+
padding: 12,
|
|
3782
|
+
style: { background: T.bg, minHeight: "100%" },
|
|
3783
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: "Loading…" })
|
|
3784
|
+
}
|
|
3785
|
+
);
|
|
2732
3786
|
}
|
|
2733
3787
|
const stats = statsQuery.data;
|
|
2734
3788
|
if (!stats) return null;
|
|
2735
3789
|
const graphData = graphQuery.data?.data ?? [];
|
|
2736
|
-
const
|
|
2737
|
-
const
|
|
3790
|
+
const rtnData = retentionQuery.data?.data ?? [];
|
|
3791
|
+
const users = usersQuery.data?.users ?? [];
|
|
2738
3792
|
const orgCount = orgsQuery.data?.total ?? 0;
|
|
3793
|
+
const activeUserMap = /* @__PURE__ */ new Map();
|
|
3794
|
+
for (const u of activeUsersQuery.data ?? []) {
|
|
3795
|
+
activeUserMap.set(String(u.id), u);
|
|
3796
|
+
}
|
|
3797
|
+
const sortedActiveUsers = activeUserIds.map((uid) => activeUserMap.get(uid)).filter((u) => u !== void 0);
|
|
2739
3798
|
const totalSpark = graphData.map((d) => d.totalUsers);
|
|
2740
3799
|
const newSpark = graphData.map((d) => d.newUsers);
|
|
2741
3800
|
const activeSpark = graphData.map((d) => d.activeUsers);
|
|
2742
3801
|
const hovRow = hovIdx !== null ? graphData[hovIdx] : null;
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
designSystem.SingleSelect,
|
|
2756
|
-
{
|
|
2757
|
-
value: period,
|
|
2758
|
-
onChange: (v) => setPeriod(v),
|
|
2759
|
-
size: "S",
|
|
2760
|
-
"aria-label": "Select period",
|
|
2761
|
-
children: [
|
|
2762
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "daily", children: "Daily" }),
|
|
2763
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "weekly", children: "Weekly" }),
|
|
2764
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "monthly", children: "Monthly" })
|
|
2765
|
-
]
|
|
2766
|
-
}
|
|
2767
|
-
) })
|
|
2768
|
-
] }),
|
|
2769
|
-
/* @__PURE__ */ jsxRuntime.jsxs(Divider, { children: [
|
|
2770
|
-
/* @__PURE__ */ jsxRuntime.jsx(DivLabel, { children: "Sign-ups" }),
|
|
2771
|
-
/* @__PURE__ */ jsxRuntime.jsx(DivLine, {})
|
|
2772
|
-
] }),
|
|
2773
|
-
/* @__PURE__ */ jsxRuntime.jsxs(StatRow, { children: [
|
|
2774
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2775
|
-
StatCardItem,
|
|
2776
|
-
{
|
|
2777
|
-
id: "total",
|
|
2778
|
-
label: "Total Users",
|
|
2779
|
-
value: stats.total ?? 0,
|
|
2780
|
-
sparkline: totalSpark,
|
|
2781
|
-
color: "#4945FF",
|
|
2782
|
-
delay: 0
|
|
2783
|
-
}
|
|
2784
|
-
),
|
|
2785
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2786
|
-
StatCardItem,
|
|
2787
|
-
{
|
|
2788
|
-
id: "d-sig",
|
|
2789
|
-
label: "Daily Sign-ups",
|
|
2790
|
-
value: stats.daily.signUps ?? 0,
|
|
2791
|
-
pct: stats.daily.percentage ?? void 0,
|
|
2792
|
-
sparkline: newSpark,
|
|
2793
|
-
color: "#5CB176",
|
|
2794
|
-
delay: 1
|
|
2795
|
-
}
|
|
2796
|
-
),
|
|
2797
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2798
|
-
StatCardItem,
|
|
2799
|
-
{
|
|
2800
|
-
id: "w-sig",
|
|
2801
|
-
label: "Weekly Sign-ups",
|
|
2802
|
-
value: stats.weekly.signUps ?? 0,
|
|
2803
|
-
pct: stats.weekly.percentage ?? void 0,
|
|
2804
|
-
sparkline: newSpark,
|
|
2805
|
-
color: "#5CB176",
|
|
2806
|
-
delay: 2
|
|
2807
|
-
}
|
|
2808
|
-
),
|
|
2809
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2810
|
-
StatCardItem,
|
|
2811
|
-
{
|
|
2812
|
-
id: "m-sig",
|
|
2813
|
-
label: "Monthly Sign-ups",
|
|
2814
|
-
value: stats.monthly.signUps ?? 0,
|
|
2815
|
-
pct: stats.monthly.percentage ?? void 0,
|
|
2816
|
-
sparkline: newSpark,
|
|
2817
|
-
color: "#5CB176",
|
|
2818
|
-
delay: 3
|
|
2819
|
-
}
|
|
3802
|
+
const rtnHovRow = rtnHov !== null ? rtnData[rtnHov] : null;
|
|
3803
|
+
const activeMax = Math.max(
|
|
3804
|
+
stats.activeUsers.daily.active ?? 0,
|
|
3805
|
+
stats.activeUsers.weekly.active ?? 0,
|
|
3806
|
+
stats.activeUsers.monthly.active ?? 0,
|
|
3807
|
+
1
|
|
3808
|
+
);
|
|
3809
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3810
|
+
admin.Page.Protect,
|
|
3811
|
+
{
|
|
3812
|
+
permissions: index.PERMISSIONS.overview.filter(
|
|
3813
|
+
(p) => p.action === `plugin::${index.PLUGIN_ID}.overview.read`
|
|
2820
3814
|
),
|
|
2821
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
3815
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(Wrap$1, { "data-testid": "overview-page", children: [
|
|
3816
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "flex-end", children: [
|
|
3817
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
3818
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3819
|
+
"div",
|
|
3820
|
+
{
|
|
3821
|
+
style: {
|
|
3822
|
+
fontSize: 22,
|
|
3823
|
+
fontWeight: 800,
|
|
3824
|
+
color: T.textPrimary,
|
|
3825
|
+
letterSpacing: "-0.03em"
|
|
3826
|
+
},
|
|
3827
|
+
children: "Overview"
|
|
3828
|
+
}
|
|
3829
|
+
),
|
|
3830
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: T.textSecondary, marginTop: 4 }, children: (/* @__PURE__ */ new Date()).toLocaleDateString(void 0, {
|
|
3831
|
+
weekday: "long",
|
|
3832
|
+
year: "numeric",
|
|
3833
|
+
month: "long",
|
|
3834
|
+
day: "numeric"
|
|
3835
|
+
}) })
|
|
3836
|
+
] }),
|
|
3837
|
+
/* @__PURE__ */ jsxRuntime.jsx(PillGroup, { children: ["daily", "weekly", "monthly"].map((p) => /* @__PURE__ */ jsxRuntime.jsx(Pill, { $active: period === p, onClick: () => setPeriod(p), children: p[0].toUpperCase() + p.slice(1) }, p)) })
|
|
2840
3838
|
] }),
|
|
2841
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
alignItems: "center",
|
|
2847
|
-
style: { flex: 1, minHeight: 180 },
|
|
2848
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: "Loading…" })
|
|
2849
|
-
}
|
|
2850
|
-
) : /* @__PURE__ */ jsxRuntime.jsx(AreaChart, { data: graphData, hovered: hovIdx, onHover: setHovIdx })
|
|
2851
|
-
] }),
|
|
2852
|
-
/* @__PURE__ */ jsxRuntime.jsxs(FeedCard, { $delay: 6, children: [
|
|
2853
|
-
/* @__PURE__ */ jsxRuntime.jsx(FeedHeader, { children: "Recent Sessions" }),
|
|
2854
|
-
/* @__PURE__ */ jsxRuntime.jsx(FeedList, { children: sessionsQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(Empty, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: "Loading…" }) }) : sessions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(Empty, { children: "No recent sessions" }) : sessions.map((s) => /* @__PURE__ */ jsxRuntime.jsxs(FeedItem, { children: [
|
|
3839
|
+
/* @__PURE__ */ jsxRuntime.jsxs(SectionDivider, { children: [
|
|
3840
|
+
/* @__PURE__ */ jsxRuntime.jsx(DivLabel, { children: "Metrics" }),
|
|
3841
|
+
/* @__PURE__ */ jsxRuntime.jsx(DivLine, {})
|
|
3842
|
+
] }),
|
|
3843
|
+
/* @__PURE__ */ jsxRuntime.jsxs(StatGrid, { children: [
|
|
2855
3844
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2856
|
-
|
|
3845
|
+
StatItem,
|
|
2857
3846
|
{
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
3847
|
+
id: "total",
|
|
3848
|
+
label: "Total Users",
|
|
3849
|
+
value: stats.total ?? 0,
|
|
3850
|
+
sparkline: totalSpark,
|
|
3851
|
+
color: T.accent,
|
|
3852
|
+
delay: 0
|
|
2861
3853
|
}
|
|
2862
|
-
),
|
|
2863
|
-
/* @__PURE__ */ jsxRuntime.jsxs(FeedContent, { children: [
|
|
2864
|
-
/* @__PURE__ */ jsxRuntime.jsx(FeedName, { children: s.user?.name ?? "Unknown" }),
|
|
2865
|
-
/* @__PURE__ */ jsxRuntime.jsx(FeedSub, { children: s.user?.email ?? "" }),
|
|
2866
|
-
/* @__PURE__ */ jsxRuntime.jsx(FeedTime, { children: relTime(s.createdAt) })
|
|
2867
|
-
] })
|
|
2868
|
-
] }, s.id)) })
|
|
2869
|
-
] })
|
|
2870
|
-
] }),
|
|
2871
|
-
/* @__PURE__ */ jsxRuntime.jsxs(Divider, { children: [
|
|
2872
|
-
/* @__PURE__ */ jsxRuntime.jsx(DivLabel, { children: "Active Users" }),
|
|
2873
|
-
/* @__PURE__ */ jsxRuntime.jsx(DivLine, {})
|
|
2874
|
-
] }),
|
|
2875
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ActiveRow, { children: [
|
|
2876
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2877
|
-
StatCardItem,
|
|
2878
|
-
{
|
|
2879
|
-
id: "d-act",
|
|
2880
|
-
label: "Daily Active",
|
|
2881
|
-
value: stats.activeUsers.daily.active ?? 0,
|
|
2882
|
-
pct: stats.activeUsers.daily.percentage ?? void 0,
|
|
2883
|
-
sparkline: activeSpark,
|
|
2884
|
-
color: "#E57553",
|
|
2885
|
-
delay: 7
|
|
2886
|
-
}
|
|
2887
|
-
),
|
|
2888
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2889
|
-
StatCardItem,
|
|
2890
|
-
{
|
|
2891
|
-
id: "w-act",
|
|
2892
|
-
label: "Weekly Active",
|
|
2893
|
-
value: stats.activeUsers.weekly.active ?? 0,
|
|
2894
|
-
pct: stats.activeUsers.weekly.percentage ?? void 0,
|
|
2895
|
-
sparkline: activeSpark,
|
|
2896
|
-
color: "#E57553",
|
|
2897
|
-
delay: 8
|
|
2898
|
-
}
|
|
2899
|
-
),
|
|
2900
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2901
|
-
StatCardItem,
|
|
2902
|
-
{
|
|
2903
|
-
id: "m-act",
|
|
2904
|
-
label: "Monthly Active",
|
|
2905
|
-
value: stats.activeUsers.monthly.active ?? 0,
|
|
2906
|
-
pct: stats.activeUsers.monthly.percentage ?? void 0,
|
|
2907
|
-
sparkline: activeSpark,
|
|
2908
|
-
color: "#E57553",
|
|
2909
|
-
delay: 9
|
|
2910
|
-
}
|
|
2911
|
-
)
|
|
2912
|
-
] }),
|
|
2913
|
-
/* @__PURE__ */ jsxRuntime.jsxs(Divider, { children: [
|
|
2914
|
-
/* @__PURE__ */ jsxRuntime.jsx(DivLabel, { children: "Recent Users" }),
|
|
2915
|
-
/* @__PURE__ */ jsxRuntime.jsx(DivLine, {})
|
|
2916
|
-
] }),
|
|
2917
|
-
/* @__PURE__ */ jsxRuntime.jsx(UsersCard, { $delay: 10, children: /* @__PURE__ */ jsxRuntime.jsx(TableWrap, { children: /* @__PURE__ */ jsxRuntime.jsxs(Table$1, { children: [
|
|
2918
|
-
/* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx("tr", { children: ["User", "Email", "Joined", "Status"].map((h) => /* @__PURE__ */ jsxRuntime.jsx(TH$1, { children: h }, h)) }) }),
|
|
2919
|
-
/* @__PURE__ */ jsxRuntime.jsx("tbody", { children: usersQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx(TD$1, { colSpan: 4, style: { textAlign: "center", padding: 28 }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: "Loading users…" }) }) }) : recentUsers.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2920
|
-
TD$1,
|
|
2921
|
-
{
|
|
2922
|
-
colSpan: 4,
|
|
2923
|
-
style: {
|
|
2924
|
-
textAlign: "center",
|
|
2925
|
-
padding: 28,
|
|
2926
|
-
color: "#8e8ea9"
|
|
2927
|
-
},
|
|
2928
|
-
children: "No users yet"
|
|
2929
|
-
}
|
|
2930
|
-
) }) : recentUsers.map((u) => /* @__PURE__ */ jsxRuntime.jsxs(TR$1, { children: [
|
|
2931
|
-
/* @__PURE__ */ jsxRuntime.jsx(TD$1, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, children: [
|
|
2932
|
-
/* @__PURE__ */ jsxRuntime.jsx(Avatar, { name: u.name, src: u.image, size: 26 }),
|
|
2933
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 600, fontSize: 12 }, children: u.name })
|
|
2934
|
-
] }) }),
|
|
2935
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2936
|
-
TD$1,
|
|
2937
|
-
{
|
|
2938
|
-
style: {
|
|
2939
|
-
color: "#8e8ea9",
|
|
2940
|
-
fontFamily: "monospace",
|
|
2941
|
-
fontSize: 11
|
|
2942
|
-
},
|
|
2943
|
-
children: u.email
|
|
2944
|
-
}
|
|
2945
|
-
),
|
|
2946
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2947
|
-
TD$1,
|
|
2948
|
-
{
|
|
2949
|
-
style: {
|
|
2950
|
-
color: "#8e8ea9",
|
|
2951
|
-
fontSize: 11,
|
|
2952
|
-
whiteSpace: "nowrap"
|
|
2953
|
-
},
|
|
2954
|
-
children: fmtDate(u.createdAt)
|
|
2955
|
-
}
|
|
2956
|
-
),
|
|
2957
|
-
/* @__PURE__ */ jsxRuntime.jsx(TD$1, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2958
|
-
StatusChip$1,
|
|
2959
|
-
{
|
|
2960
|
-
$verified: u.emailVerified,
|
|
2961
|
-
$banned: u.banned,
|
|
2962
|
-
children: u.banned ? "Banned" : u.emailVerified ? "Verified" : "Unverified"
|
|
2963
|
-
}
|
|
2964
|
-
) })
|
|
2965
|
-
] }, u.id)) })
|
|
2966
|
-
] }) }) })
|
|
2967
|
-
] });
|
|
2968
|
-
}
|
|
2969
|
-
const PAGE_SIZE$1 = 25;
|
|
2970
|
-
const fadeUp$1 = styled.keyframes`
|
|
2971
|
-
from { opacity: 0; transform: translateY(6px); }
|
|
2972
|
-
to { opacity: 1; transform: translateY(0); }
|
|
2973
|
-
`;
|
|
2974
|
-
const Wrap$1 = styled__default.default.div`
|
|
2975
|
-
padding: 28px 32px;
|
|
2976
|
-
background: #f6f6f9;
|
|
2977
|
-
min-height: 100%;
|
|
2978
|
-
display: flex;
|
|
2979
|
-
flex-direction: column;
|
|
2980
|
-
gap: 20px;
|
|
2981
|
-
`;
|
|
2982
|
-
const PageHeader$1 = styled__default.default.div`
|
|
2983
|
-
display: flex;
|
|
2984
|
-
justify-content: space-between;
|
|
2985
|
-
align-items: flex-start;
|
|
2986
|
-
`;
|
|
2987
|
-
const TitleBlock$1 = styled__default.default.div`
|
|
2988
|
-
display: flex;
|
|
2989
|
-
flex-direction: column;
|
|
2990
|
-
gap: 4px;
|
|
2991
|
-
`;
|
|
2992
|
-
const PageTitle$1 = styled__default.default.h1`
|
|
2993
|
-
margin: 0;
|
|
2994
|
-
font-size: 22px;
|
|
2995
|
-
font-weight: 800;
|
|
2996
|
-
color: #32324d;
|
|
2997
|
-
letter-spacing: -0.03em;
|
|
2998
|
-
`;
|
|
2999
|
-
const PageSubtitle$1 = styled__default.default.p`
|
|
3000
|
-
margin: 0;
|
|
3001
|
-
font-size: 12px;
|
|
3002
|
-
color: #8e8ea9;
|
|
3003
|
-
`;
|
|
3004
|
-
const TableCard$1 = styled__default.default.div`
|
|
3005
|
-
background: #ffffff;
|
|
3006
|
-
border: 1px solid #eaeaef;
|
|
3007
|
-
border-radius: 10px;
|
|
3008
|
-
overflow: hidden;
|
|
3009
|
-
`;
|
|
3010
|
-
const ColumnHeader = styled__default.default.div`
|
|
3011
|
-
padding: 10px 20px;
|
|
3012
|
-
display: flex;
|
|
3013
|
-
align-items: center;
|
|
3014
|
-
gap: 12px;
|
|
3015
|
-
background: #fafafa;
|
|
3016
|
-
border-bottom: 1px solid #eaeaef;
|
|
3017
|
-
font-size: 10px;
|
|
3018
|
-
font-weight: 700;
|
|
3019
|
-
text-transform: uppercase;
|
|
3020
|
-
letter-spacing: 0.08em;
|
|
3021
|
-
color: #8e8ea9;
|
|
3022
|
-
`;
|
|
3023
|
-
const ColumnHeaderRight = styled__default.default.div`
|
|
3024
|
-
margin-left: auto;
|
|
3025
|
-
font-size: 10px;
|
|
3026
|
-
font-weight: 700;
|
|
3027
|
-
text-transform: uppercase;
|
|
3028
|
-
letter-spacing: 0.08em;
|
|
3029
|
-
color: #8e8ea9;
|
|
3030
|
-
`;
|
|
3031
|
-
const UserGroup = styled__default.default.div`
|
|
3032
|
-
border-bottom: 1px solid #eaeaef;
|
|
3033
|
-
animation: ${fadeUp$1} 280ms ease both;
|
|
3034
|
-
animation-delay: ${(p) => (p.$i ?? 0) * 30}ms;
|
|
3035
|
-
&:last-child { border-bottom: none; }
|
|
3036
|
-
`;
|
|
3037
|
-
const UserRowHeader = styled__default.default.div`
|
|
3038
|
-
padding: 14px 20px;
|
|
3039
|
-
display: flex;
|
|
3040
|
-
align-items: center;
|
|
3041
|
-
gap: 12px;
|
|
3042
|
-
border-bottom: 1px solid #f5f5f9;
|
|
3043
|
-
cursor: default;
|
|
3044
|
-
&:hover { background: #fafafe; }
|
|
3045
|
-
`;
|
|
3046
|
-
const UserInfo = styled__default.default.div`
|
|
3047
|
-
flex: 1;
|
|
3048
|
-
display: flex;
|
|
3049
|
-
flex-direction: column;
|
|
3050
|
-
gap: 2px;
|
|
3051
|
-
`;
|
|
3052
|
-
const UserName$1 = styled__default.default.span`
|
|
3053
|
-
font-size: 12px;
|
|
3054
|
-
font-weight: 600;
|
|
3055
|
-
color: #32324d;
|
|
3056
|
-
`;
|
|
3057
|
-
const UserEmail = styled__default.default.span`
|
|
3058
|
-
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
|
3059
|
-
font-size: 11px;
|
|
3060
|
-
color: #8e8ea9;
|
|
3061
|
-
`;
|
|
3062
|
-
const SessionCountChip = styled__default.default.span`
|
|
3063
|
-
display: inline-flex;
|
|
3064
|
-
align-items: center;
|
|
3065
|
-
padding: 2px 8px;
|
|
3066
|
-
border-radius: 20px;
|
|
3067
|
-
font-size: 10px;
|
|
3068
|
-
font-weight: 700;
|
|
3069
|
-
background: #f0f0ff;
|
|
3070
|
-
color: #4945ff;
|
|
3071
|
-
`;
|
|
3072
|
-
const SessionCard = styled__default.default.div`
|
|
3073
|
-
padding: 10px 20px 10px 64px;
|
|
3074
|
-
border-top: 1px solid #f5f5f9;
|
|
3075
|
-
background: #fafafa;
|
|
3076
|
-
display: flex;
|
|
3077
|
-
align-items: flex-start;
|
|
3078
|
-
justify-content: space-between;
|
|
3079
|
-
gap: 16px;
|
|
3080
|
-
&:hover { background: #f5f5ff; }
|
|
3081
|
-
`;
|
|
3082
|
-
const SessionMeta = styled__default.default.div`
|
|
3083
|
-
display: flex;
|
|
3084
|
-
flex-direction: column;
|
|
3085
|
-
gap: 3px;
|
|
3086
|
-
flex: 1;
|
|
3087
|
-
min-width: 0;
|
|
3088
|
-
`;
|
|
3089
|
-
const IpChip = styled__default.default.span`
|
|
3090
|
-
display: inline-block;
|
|
3091
|
-
background: #f0f0ff;
|
|
3092
|
-
color: #4945ff;
|
|
3093
|
-
border-radius: 5px;
|
|
3094
|
-
padding: 2px 6px;
|
|
3095
|
-
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
|
3096
|
-
font-size: 11px;
|
|
3097
|
-
font-weight: 600;
|
|
3098
|
-
`;
|
|
3099
|
-
const TimestampText = styled__default.default.span`
|
|
3100
|
-
font-size: 11px;
|
|
3101
|
-
color: #8e8ea9;
|
|
3102
|
-
font-variant-numeric: tabular-nums;
|
|
3103
|
-
`;
|
|
3104
|
-
const AgentText = styled__default.default.span`
|
|
3105
|
-
font-size: 11px;
|
|
3106
|
-
color: #b8b8c7;
|
|
3107
|
-
overflow: hidden;
|
|
3108
|
-
text-overflow: ellipsis;
|
|
3109
|
-
white-space: nowrap;
|
|
3110
|
-
display: block;
|
|
3111
|
-
max-width: 400px;
|
|
3112
|
-
`;
|
|
3113
|
-
const EmptyState = styled__default.default.div`
|
|
3114
|
-
display: flex;
|
|
3115
|
-
justify-content: center;
|
|
3116
|
-
align-items: center;
|
|
3117
|
-
padding: 48px;
|
|
3118
|
-
font-size: 12px;
|
|
3119
|
-
color: #8e8ea9;
|
|
3120
|
-
`;
|
|
3121
|
-
function SessionsPage() {
|
|
3122
|
-
const qc = reactQuery.useQueryClient();
|
|
3123
|
-
const [page, setPage] = react.useState(1);
|
|
3124
|
-
const [selected, setSelected] = react.useState(/* @__PURE__ */ new Set());
|
|
3125
|
-
const [confirmRevokeSessionId, setConfirmRevokeSessionId] = react.useState(null);
|
|
3126
|
-
const [confirmRevokeMany, setConfirmRevokeMany] = react.useState(false);
|
|
3127
|
-
const sessionsQuery = reactQuery.useQuery({
|
|
3128
|
-
queryKey: ["dash-all-sessions", page],
|
|
3129
|
-
queryFn: async () => {
|
|
3130
|
-
const result = await client.dash.listAllSessions({});
|
|
3131
|
-
if (result.error)
|
|
3132
|
-
throw new Error(result.error.message ?? "Failed to load sessions");
|
|
3133
|
-
return result.data ?? [];
|
|
3134
|
-
},
|
|
3135
|
-
keepPreviousData: true
|
|
3136
|
-
});
|
|
3137
|
-
const revokeSessionMutation = reactQuery.useMutation({
|
|
3138
|
-
mutationFn: async (sessionId) => {
|
|
3139
|
-
const result = await client.dash.sessions.revoke(
|
|
3140
|
-
{},
|
|
3141
|
-
withContext({ sessionId })
|
|
3142
|
-
);
|
|
3143
|
-
if (result.error)
|
|
3144
|
-
throw new Error(result.error.message ?? "Revoke failed");
|
|
3145
|
-
},
|
|
3146
|
-
onSuccess: () => {
|
|
3147
|
-
setConfirmRevokeSessionId(null);
|
|
3148
|
-
qc.invalidateQueries({ queryKey: ["dash-all-sessions"] });
|
|
3149
|
-
}
|
|
3150
|
-
});
|
|
3151
|
-
const revokeManyMutation = reactQuery.useMutation({
|
|
3152
|
-
mutationFn: async (userIds) => {
|
|
3153
|
-
const result = await client.dash.sessions.revokeMany(
|
|
3154
|
-
{},
|
|
3155
|
-
withContext({ userIds })
|
|
3156
|
-
);
|
|
3157
|
-
if (result.error)
|
|
3158
|
-
throw new Error(result.error.message ?? "Revoke failed");
|
|
3159
|
-
return result.data;
|
|
3160
|
-
},
|
|
3161
|
-
onSuccess: () => {
|
|
3162
|
-
setConfirmRevokeMany(false);
|
|
3163
|
-
setSelected(/* @__PURE__ */ new Set());
|
|
3164
|
-
qc.invalidateQueries({ queryKey: ["dash-all-sessions"] });
|
|
3165
|
-
}
|
|
3166
|
-
});
|
|
3167
|
-
const usersWithSessions = sessionsQuery.data ?? [];
|
|
3168
|
-
const allUserIds = usersWithSessions.map((u) => u.id);
|
|
3169
|
-
const allSelected = allUserIds.length > 0 && allUserIds.every((id) => selected.has(id));
|
|
3170
|
-
const someSelected = selected.size > 0;
|
|
3171
|
-
const toggleSelect = (id) => {
|
|
3172
|
-
setSelected((prev) => {
|
|
3173
|
-
const next = new Set(prev);
|
|
3174
|
-
if (next.has(id)) next.delete(id);
|
|
3175
|
-
else next.add(id);
|
|
3176
|
-
return next;
|
|
3177
|
-
});
|
|
3178
|
-
};
|
|
3179
|
-
const handleSelectAll = () => {
|
|
3180
|
-
if (allSelected) setSelected(/* @__PURE__ */ new Set());
|
|
3181
|
-
else setSelected(new Set(allUserIds));
|
|
3182
|
-
};
|
|
3183
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(Wrap$1, { "data-testid": "sessions-page", children: [
|
|
3184
|
-
/* @__PURE__ */ jsxRuntime.jsxs(PageHeader$1, { children: [
|
|
3185
|
-
/* @__PURE__ */ jsxRuntime.jsxs(TitleBlock$1, { children: [
|
|
3186
|
-
/* @__PURE__ */ jsxRuntime.jsx(PageTitle$1, { children: "Sessions" }),
|
|
3187
|
-
/* @__PURE__ */ jsxRuntime.jsx(PageSubtitle$1, { children: usersWithSessions.length > 0 ? `${usersWithSessions.length} user${usersWithSessions.length !== 1 ? "s" : ""} with active sessions` : "Active sessions across all users" })
|
|
3188
|
-
] }),
|
|
3189
|
-
someSelected && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3190
|
-
designSystem.Button,
|
|
3191
|
-
{
|
|
3192
|
-
variant: "danger-light",
|
|
3193
|
-
size: "S",
|
|
3194
|
-
onClick: () => setConfirmRevokeMany(true),
|
|
3195
|
-
"data-testid": "revoke-selected-btn",
|
|
3196
|
-
children: [
|
|
3197
|
-
"Revoke sessions for ",
|
|
3198
|
-
selected.size,
|
|
3199
|
-
" user",
|
|
3200
|
-
selected.size !== 1 ? "s" : ""
|
|
3201
|
-
]
|
|
3202
|
-
}
|
|
3203
|
-
)
|
|
3204
|
-
] }),
|
|
3205
|
-
sessionsQuery.isError && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#d02b20", fontSize: 12, padding: "8px 0" }, children: sessionsQuery.error instanceof Error ? sessionsQuery.error.message : "An error occurred" }),
|
|
3206
|
-
/* @__PURE__ */ jsxRuntime.jsx(TableCard$1, { children: sessionsQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: "Loading sessions…" }) }) : usersWithSessions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { children: "No active sessions" }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3207
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ColumnHeader, { children: [
|
|
3208
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3209
|
-
designSystem.Checkbox,
|
|
3210
|
-
{
|
|
3211
|
-
checked: allSelected,
|
|
3212
|
-
onCheckedChange: handleSelectAll,
|
|
3213
|
-
"aria-label": "Select all users"
|
|
3214
|
-
}
|
|
3215
|
-
),
|
|
3216
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "User / Sessions" }),
|
|
3217
|
-
/* @__PURE__ */ jsxRuntime.jsx(ColumnHeaderRight, { children: "Sessions" })
|
|
3218
|
-
] }),
|
|
3219
|
-
usersWithSessions.map((userRow, i) => /* @__PURE__ */ jsxRuntime.jsxs(UserGroup, { $i: i, "data-testid": "session-user-row", children: [
|
|
3220
|
-
/* @__PURE__ */ jsxRuntime.jsxs(UserRowHeader, { children: [
|
|
3854
|
+
),
|
|
3221
3855
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3222
|
-
|
|
3856
|
+
StatItem,
|
|
3223
3857
|
{
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3858
|
+
id: "d-sig",
|
|
3859
|
+
label: "Daily Sign-ups",
|
|
3860
|
+
value: stats.daily.signUps ?? 0,
|
|
3861
|
+
pct: stats.daily.percentage ?? void 0,
|
|
3862
|
+
sparkline: newSpark,
|
|
3863
|
+
color: T.green,
|
|
3864
|
+
delay: 1
|
|
3227
3865
|
}
|
|
3228
3866
|
),
|
|
3229
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3867
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3868
|
+
StatItem,
|
|
3869
|
+
{
|
|
3870
|
+
id: "w-sig",
|
|
3871
|
+
label: "Weekly Sign-ups",
|
|
3872
|
+
value: stats.weekly.signUps ?? 0,
|
|
3873
|
+
pct: stats.weekly.percentage ?? void 0,
|
|
3874
|
+
sparkline: newSpark,
|
|
3875
|
+
color: T.green,
|
|
3876
|
+
delay: 2
|
|
3877
|
+
}
|
|
3878
|
+
),
|
|
3879
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3880
|
+
StatItem,
|
|
3881
|
+
{
|
|
3882
|
+
id: "m-sig",
|
|
3883
|
+
label: "Monthly Sign-ups",
|
|
3884
|
+
value: stats.monthly.signUps ?? 0,
|
|
3885
|
+
pct: stats.monthly.percentage ?? void 0,
|
|
3886
|
+
sparkline: newSpark,
|
|
3887
|
+
color: T.green,
|
|
3888
|
+
delay: 3
|
|
3889
|
+
}
|
|
3890
|
+
),
|
|
3891
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3892
|
+
StatItem,
|
|
3893
|
+
{
|
|
3894
|
+
id: "orgs",
|
|
3895
|
+
label: "Organizations",
|
|
3896
|
+
value: orgCount,
|
|
3897
|
+
color: T.purple,
|
|
3898
|
+
delay: 4
|
|
3899
|
+
}
|
|
3900
|
+
)
|
|
3239
3901
|
] }),
|
|
3240
|
-
|
|
3241
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3242
|
-
|
|
3902
|
+
/* @__PURE__ */ jsxRuntime.jsxs(SectionDivider, { children: [
|
|
3903
|
+
/* @__PURE__ */ jsxRuntime.jsx(DivLabel, { children: "Growth" }),
|
|
3904
|
+
/* @__PURE__ */ jsxRuntime.jsx(DivLine, {})
|
|
3905
|
+
] }),
|
|
3906
|
+
/* @__PURE__ */ jsxRuntime.jsxs(TwoPanel, { children: [
|
|
3907
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ChartCard, { ref: chartRef, $delay: 5, children: [
|
|
3908
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ChartHeader, { children: [
|
|
3909
|
+
/* @__PURE__ */ jsxRuntime.jsx(ChartTitle, { children: "User Growth" }),
|
|
3910
|
+
/* @__PURE__ */ jsxRuntime.jsx(SeriesRow, { children: ALL_SERIES.map((s) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3911
|
+
SeriesBtn,
|
|
3912
|
+
{
|
|
3913
|
+
$on: activeSeries.has(s.key),
|
|
3914
|
+
$c: s.color,
|
|
3915
|
+
onClick: () => toggleSeries(s.key),
|
|
3916
|
+
children: [
|
|
3917
|
+
/* @__PURE__ */ jsxRuntime.jsx(SDot, { $c: s.color }),
|
|
3918
|
+
s.label
|
|
3919
|
+
]
|
|
3920
|
+
},
|
|
3921
|
+
s.key
|
|
3922
|
+
)) })
|
|
3923
|
+
] }),
|
|
3924
|
+
/* @__PURE__ */ jsxRuntime.jsx(HoverInfo, { children: hovRow ? `${hovRow.label} · ${hovRow.totalUsers.toLocaleString()} total · +${hovRow.newUsers.toLocaleString()} new · ${hovRow.activeUsers.toLocaleString()} active` : "Hover the chart to inspect a data point" }),
|
|
3925
|
+
graphQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3243
3926
|
designSystem.Flex,
|
|
3244
3927
|
{
|
|
3245
|
-
|
|
3928
|
+
justifyContent: "center",
|
|
3246
3929
|
alignItems: "center",
|
|
3247
|
-
style: {
|
|
3248
|
-
children:
|
|
3249
|
-
session.ipAddress && /* @__PURE__ */ jsxRuntime.jsx(IpChip, { children: session.ipAddress }),
|
|
3250
|
-
/* @__PURE__ */ jsxRuntime.jsxs(TimestampText, { children: [
|
|
3251
|
-
"Created ",
|
|
3252
|
-
new Date(session.createdAt).toLocaleString(),
|
|
3253
|
-
" ",
|
|
3254
|
-
"· Expires",
|
|
3255
|
-
" ",
|
|
3256
|
-
new Date(session.expiresAt).toLocaleString()
|
|
3257
|
-
] })
|
|
3258
|
-
]
|
|
3930
|
+
style: { flex: 1, minHeight: 170 },
|
|
3931
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: "Loading…" })
|
|
3259
3932
|
}
|
|
3260
|
-
)
|
|
3261
|
-
|
|
3933
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
3934
|
+
GrowthChart,
|
|
3935
|
+
{
|
|
3936
|
+
data: graphData,
|
|
3937
|
+
hovered: hovIdx,
|
|
3938
|
+
onHover: setHovIdx,
|
|
3939
|
+
activeSeries
|
|
3940
|
+
}
|
|
3941
|
+
)
|
|
3262
3942
|
] }),
|
|
3263
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3264
|
-
|
|
3943
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3944
|
+
FeedCard,
|
|
3265
3945
|
{
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3946
|
+
$delay: 6,
|
|
3947
|
+
style: chartHeight > 0 ? { height: chartHeight } : void 0,
|
|
3948
|
+
children: [
|
|
3949
|
+
/* @__PURE__ */ jsxRuntime.jsxs(FeedHead, { children: [
|
|
3950
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: feedMode === "signups" ? "Recent Sign-ups" : "Recently Active" }),
|
|
3951
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3952
|
+
FeedSelect,
|
|
3953
|
+
{
|
|
3954
|
+
value: feedMode,
|
|
3955
|
+
onChange: (e) => setFeedMode(e.target.value),
|
|
3956
|
+
children: [
|
|
3957
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "signups", children: "Recent Sign-ups" }),
|
|
3958
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "active", children: "Recently Active" })
|
|
3959
|
+
]
|
|
3960
|
+
}
|
|
3961
|
+
)
|
|
3962
|
+
] }),
|
|
3963
|
+
/* @__PURE__ */ jsxRuntime.jsx(FeedScroll, { children: feedMode === "signups" ? usersQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: "Loading…" }) }) : users.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(Empty, { children: "No users yet" }) : users.map((u) => /* @__PURE__ */ jsxRuntime.jsxs(FeedItem, { children: [
|
|
3964
|
+
/* @__PURE__ */ jsxRuntime.jsxs(FeedTop, { children: [
|
|
3965
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3966
|
+
designSystem.Flex,
|
|
3967
|
+
{
|
|
3968
|
+
alignItems: "center",
|
|
3969
|
+
gap: 1,
|
|
3970
|
+
style: { minWidth: 0 },
|
|
3971
|
+
children: [
|
|
3972
|
+
/* @__PURE__ */ jsxRuntime.jsx(Avatar, { name: u.name, src: u.image, size: 20 }),
|
|
3973
|
+
/* @__PURE__ */ jsxRuntime.jsx(FeedName, { title: u.name, children: u.name })
|
|
3974
|
+
]
|
|
3975
|
+
}
|
|
3976
|
+
),
|
|
3977
|
+
/* @__PURE__ */ jsxRuntime.jsx(FeedMeta, { children: relTime(u.createdAt) })
|
|
3978
|
+
] }),
|
|
3979
|
+
/* @__PURE__ */ jsxRuntime.jsx(FeedEmail, { title: u.email, children: u.email })
|
|
3980
|
+
] }, u.id)) : activeUsersQuery.isLoading || sessionsQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: "Loading…" }) }) : sortedActiveUsers.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(Empty, { children: "No recent activity" }) : sortedActiveUsers.map((u) => /* @__PURE__ */ jsxRuntime.jsxs(FeedItem, { children: [
|
|
3981
|
+
/* @__PURE__ */ jsxRuntime.jsxs(FeedTop, { children: [
|
|
3982
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 1, style: { minWidth: 0 }, children: [
|
|
3983
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3984
|
+
Avatar,
|
|
3985
|
+
{
|
|
3986
|
+
name: u.name,
|
|
3987
|
+
src: u.image ?? void 0,
|
|
3988
|
+
size: 20
|
|
3989
|
+
}
|
|
3990
|
+
),
|
|
3991
|
+
/* @__PURE__ */ jsxRuntime.jsx(FeedName, { title: u.name, children: u.name })
|
|
3992
|
+
] }),
|
|
3993
|
+
/* @__PURE__ */ jsxRuntime.jsx(FeedMeta, { children: relTime(
|
|
3994
|
+
lastActiveByUserId.get(String(u.id)) ?? u.createdAt
|
|
3995
|
+
) })
|
|
3996
|
+
] }),
|
|
3997
|
+
/* @__PURE__ */ jsxRuntime.jsx(FeedEmail, { title: u.email, children: u.email })
|
|
3998
|
+
] }, u.documentId)) })
|
|
3999
|
+
]
|
|
3271
4000
|
}
|
|
3272
4001
|
)
|
|
3273
|
-
] },
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
4002
|
+
] }),
|
|
4003
|
+
/* @__PURE__ */ jsxRuntime.jsxs(SectionDivider, { children: [
|
|
4004
|
+
/* @__PURE__ */ jsxRuntime.jsx(DivLabel, { children: "Retention & Activity" }),
|
|
4005
|
+
/* @__PURE__ */ jsxRuntime.jsx(DivLine, {})
|
|
4006
|
+
] }),
|
|
4007
|
+
/* @__PURE__ */ jsxRuntime.jsxs(TwoPanel, { children: [
|
|
4008
|
+
/* @__PURE__ */ jsxRuntime.jsxs(RtnCard, { $delay: 7, children: [
|
|
4009
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ChartHeader, { children: [
|
|
4010
|
+
/* @__PURE__ */ jsxRuntime.jsx(ChartTitle, { children: "Cohort Retention" }),
|
|
4011
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { alignItems: "center", gap: 2, children: [
|
|
4012
|
+
{ hue: 142, label: "≥70%" },
|
|
4013
|
+
{ hue: 38, label: "40–70%" },
|
|
4014
|
+
{ hue: 4, label: "<40%" }
|
|
4015
|
+
].map(({ hue, label }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4016
|
+
designSystem.Flex,
|
|
4017
|
+
{
|
|
4018
|
+
alignItems: "center",
|
|
4019
|
+
gap: 1,
|
|
4020
|
+
style: { marginLeft: 8 },
|
|
4021
|
+
children: [
|
|
4022
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4023
|
+
"span",
|
|
4024
|
+
{
|
|
4025
|
+
style: {
|
|
4026
|
+
width: 8,
|
|
4027
|
+
height: 8,
|
|
4028
|
+
borderRadius: "50%",
|
|
4029
|
+
background: `hsl(${hue},60%,50%)`,
|
|
4030
|
+
display: "inline-block",
|
|
4031
|
+
flexShrink: 0
|
|
4032
|
+
}
|
|
4033
|
+
}
|
|
4034
|
+
),
|
|
4035
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 9, color: T.textMuted }, children: label })
|
|
4036
|
+
]
|
|
4037
|
+
},
|
|
4038
|
+
hue
|
|
4039
|
+
)) })
|
|
4040
|
+
] }),
|
|
4041
|
+
/* @__PURE__ */ jsxRuntime.jsx(RtnTip, { children: rtnHovRow ? `Cohort ${rtnHovRow.label} · ${rtnHovRow.cohortSize.toLocaleString()} users · active ${rtnHovRow.activeStart} – ${rtnHovRow.activeEnd}` : "Hover a row to see cohort details" }),
|
|
4042
|
+
retentionQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
4043
|
+
designSystem.Flex,
|
|
4044
|
+
{
|
|
4045
|
+
justifyContent: "center",
|
|
4046
|
+
alignItems: "center",
|
|
4047
|
+
style: { minHeight: 80 },
|
|
4048
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: "Loading…" })
|
|
4049
|
+
}
|
|
4050
|
+
) : rtnData.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs(Empty, { children: [
|
|
4051
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 22 }, children: "📉" }),
|
|
4052
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { children: "No retention data for this period" })
|
|
4053
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: 5 }, children: rtnData.map((row, i) => {
|
|
4054
|
+
const hue = rateHue(row.retentionRate);
|
|
4055
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4056
|
+
RtnRow,
|
|
4057
|
+
{
|
|
4058
|
+
$hov: rtnHov === i,
|
|
4059
|
+
onMouseEnter: () => setRtnHov(i),
|
|
4060
|
+
onMouseLeave: () => setRtnHov(null),
|
|
4061
|
+
children: [
|
|
4062
|
+
/* @__PURE__ */ jsxRuntime.jsx(RtnLabel, { children: row.label }),
|
|
4063
|
+
/* @__PURE__ */ jsxRuntime.jsx(RtnSize, { children: row.cohortSize.toLocaleString() }),
|
|
4064
|
+
/* @__PURE__ */ jsxRuntime.jsx(RtnTrack, { children: /* @__PURE__ */ jsxRuntime.jsx(RtnBar, { $w: row.retentionRate, $hue: hue }) }),
|
|
4065
|
+
/* @__PURE__ */ jsxRuntime.jsxs(RtnPct, { $hue: hue, children: [
|
|
4066
|
+
row.retentionRate.toFixed(1),
|
|
4067
|
+
"%"
|
|
4068
|
+
] })
|
|
4069
|
+
]
|
|
4070
|
+
},
|
|
4071
|
+
row.n
|
|
4072
|
+
);
|
|
4073
|
+
}) })
|
|
4074
|
+
] }),
|
|
4075
|
+
/* @__PURE__ */ jsxRuntime.jsx(RingGrid, { children: [
|
|
4076
|
+
{
|
|
4077
|
+
label: "Daily Active",
|
|
4078
|
+
value: stats.activeUsers.daily.active,
|
|
4079
|
+
pct: stats.activeUsers.daily.percentage,
|
|
4080
|
+
color: T.amber,
|
|
4081
|
+
sparkline: activeSpark,
|
|
4082
|
+
delay: 8
|
|
4083
|
+
},
|
|
4084
|
+
{
|
|
4085
|
+
label: "Weekly Active",
|
|
4086
|
+
value: stats.activeUsers.weekly.active,
|
|
4087
|
+
pct: stats.activeUsers.weekly.percentage,
|
|
4088
|
+
color: T.green,
|
|
4089
|
+
sparkline: activeSpark,
|
|
4090
|
+
delay: 9
|
|
4091
|
+
},
|
|
4092
|
+
{
|
|
4093
|
+
label: "Monthly Active",
|
|
4094
|
+
value: stats.activeUsers.monthly.active,
|
|
4095
|
+
pct: stats.activeUsers.monthly.percentage,
|
|
4096
|
+
color: T.accent,
|
|
4097
|
+
sparkline: activeSpark,
|
|
4098
|
+
delay: 10
|
|
4099
|
+
}
|
|
4100
|
+
].map(({ label, value, pct, color, delay }) => {
|
|
4101
|
+
const isPos = pct == null || pct >= 0;
|
|
4102
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(RingCard, { $delay: delay, children: [
|
|
4103
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4104
|
+
ProgressRing,
|
|
4105
|
+
{
|
|
4106
|
+
value: value ?? 0,
|
|
4107
|
+
max: activeMax,
|
|
4108
|
+
color,
|
|
4109
|
+
size: 56
|
|
4110
|
+
}
|
|
4111
|
+
),
|
|
4112
|
+
/* @__PURE__ */ jsxRuntime.jsxs(RingInfo, { children: [
|
|
4113
|
+
/* @__PURE__ */ jsxRuntime.jsx(RingLabel, { children: label }),
|
|
4114
|
+
/* @__PURE__ */ jsxRuntime.jsx(RingVal, { children: (value ?? 0).toLocaleString() }),
|
|
4115
|
+
pct != null && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4116
|
+
TrendBadge,
|
|
4117
|
+
{
|
|
4118
|
+
$pos: isPos,
|
|
4119
|
+
style: { marginTop: 4, marginBottom: 0 },
|
|
4120
|
+
children: [
|
|
4121
|
+
isPos ? "↑" : "↓",
|
|
4122
|
+
" ",
|
|
4123
|
+
Math.abs(pct).toFixed(1),
|
|
4124
|
+
"%"
|
|
4125
|
+
]
|
|
4126
|
+
}
|
|
4127
|
+
)
|
|
4128
|
+
] })
|
|
4129
|
+
] }, label);
|
|
4130
|
+
}) })
|
|
4131
|
+
] })
|
|
4132
|
+
] })
|
|
4133
|
+
}
|
|
4134
|
+
);
|
|
3320
4135
|
}
|
|
3321
4136
|
function useUsers(options = {}) {
|
|
3322
4137
|
const { limit = 25, offset = 0, search } = options;
|
|
@@ -3327,7 +4142,14 @@ function useUsers(options = {}) {
|
|
|
3327
4142
|
{
|
|
3328
4143
|
field: "email",
|
|
3329
4144
|
operator: "contains",
|
|
3330
|
-
value: search
|
|
4145
|
+
value: search,
|
|
4146
|
+
connector: "OR"
|
|
4147
|
+
},
|
|
4148
|
+
{
|
|
4149
|
+
field: "name",
|
|
4150
|
+
operator: "contains",
|
|
4151
|
+
value: search,
|
|
4152
|
+
connector: "OR"
|
|
3331
4153
|
}
|
|
3332
4154
|
]) : void 0;
|
|
3333
4155
|
const result = await client.dash.listUsers({
|
|
@@ -3556,6 +4378,45 @@ const ReadOnlyCodeInput = styled__default.default.div`
|
|
|
3556
4378
|
word-break: break-all;
|
|
3557
4379
|
overflow-wrap: anywhere;
|
|
3558
4380
|
`;
|
|
4381
|
+
const SessionCard = styled__default.default.div`
|
|
4382
|
+
display: flex;
|
|
4383
|
+
align-items: flex-start;
|
|
4384
|
+
justify-content: space-between;
|
|
4385
|
+
gap: 12px;
|
|
4386
|
+
padding: 10px 14px;
|
|
4387
|
+
background: white;
|
|
4388
|
+
border: 1px solid #eaeaef;
|
|
4389
|
+
border-radius: 8px;
|
|
4390
|
+
`;
|
|
4391
|
+
const SessionMeta = styled__default.default.div`
|
|
4392
|
+
display: flex;
|
|
4393
|
+
flex-direction: column;
|
|
4394
|
+
gap: 3px;
|
|
4395
|
+
flex: 1;
|
|
4396
|
+
min-width: 0;
|
|
4397
|
+
`;
|
|
4398
|
+
const IpChip = styled__default.default.span`
|
|
4399
|
+
display: inline-block;
|
|
4400
|
+
background: #f0f0ff;
|
|
4401
|
+
color: #4945ff;
|
|
4402
|
+
border-radius: 5px;
|
|
4403
|
+
padding: 2px 6px;
|
|
4404
|
+
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
|
4405
|
+
font-size: 11px;
|
|
4406
|
+
font-weight: 600;
|
|
4407
|
+
`;
|
|
4408
|
+
const TimestampText = styled__default.default.span`
|
|
4409
|
+
font-size: 11px;
|
|
4410
|
+
color: #8e8ea9;
|
|
4411
|
+
`;
|
|
4412
|
+
const AgentText = styled__default.default.span`
|
|
4413
|
+
font-size: 11px;
|
|
4414
|
+
color: #b8b8c7;
|
|
4415
|
+
overflow: hidden;
|
|
4416
|
+
text-overflow: ellipsis;
|
|
4417
|
+
white-space: nowrap;
|
|
4418
|
+
display: block;
|
|
4419
|
+
`;
|
|
3559
4420
|
const STANDARD_FIELDS = /* @__PURE__ */ new Set([
|
|
3560
4421
|
"id",
|
|
3561
4422
|
"name",
|
|
@@ -3577,11 +4438,15 @@ function UserDetailDrawer({
|
|
|
3577
4438
|
}) {
|
|
3578
4439
|
const qc = reactQuery.useQueryClient();
|
|
3579
4440
|
const { toggleNotification } = admin.useNotification();
|
|
4441
|
+
const { get, put } = admin.useFetchClient();
|
|
3580
4442
|
const schemaQuery = useModelSchema("user");
|
|
3581
4443
|
const userQuery = reactQuery.useQuery({
|
|
3582
4444
|
queryKey: ["dash-user", userId],
|
|
3583
4445
|
queryFn: async () => {
|
|
3584
|
-
const result = await client.dash.user(
|
|
4446
|
+
const result = await client.dash.user(
|
|
4447
|
+
{},
|
|
4448
|
+
withContext({ userId }, getAuthHeaders())
|
|
4449
|
+
);
|
|
3585
4450
|
if (result.error)
|
|
3586
4451
|
throw new Error(result.error.message ?? "Failed to load user");
|
|
3587
4452
|
return result.data;
|
|
@@ -3592,12 +4457,33 @@ function UserDetailDrawer({
|
|
|
3592
4457
|
queryFn: async () => {
|
|
3593
4458
|
const result = await client.dash.userOrganizations(
|
|
3594
4459
|
{},
|
|
3595
|
-
withContext({ userId })
|
|
4460
|
+
withContext({ userId }, getAuthHeaders())
|
|
3596
4461
|
);
|
|
3597
4462
|
if (result.error) throw new Error(result.error.message ?? "Failed");
|
|
3598
4463
|
return result.data?.organizations ?? [];
|
|
3599
4464
|
}
|
|
3600
4465
|
});
|
|
4466
|
+
const sessionsQuery = reactQuery.useQuery({
|
|
4467
|
+
queryKey: ["dash-user-sessions", userId],
|
|
4468
|
+
queryFn: async () => {
|
|
4469
|
+
const { data } = await get(
|
|
4470
|
+
`/better-auth-dashboard/db?uid=plugin::better-auth.session&filters[userId][$eq]=${userId}&sort[0]=createdAt:desc&pagination[pageSize]=50`
|
|
4471
|
+
);
|
|
4472
|
+
return data.results ?? [];
|
|
4473
|
+
}
|
|
4474
|
+
});
|
|
4475
|
+
const strapiUserQuery = reactQuery.useQuery({
|
|
4476
|
+
queryKey: ["dash-strapi-user", userId],
|
|
4477
|
+
enabled: !!schemaQuery.data,
|
|
4478
|
+
queryFn: async () => {
|
|
4479
|
+
const relationFields = Object.entries(schemaQuery.data).filter(([, attr]) => attr.type === "relation").map(([fieldName]) => fieldName);
|
|
4480
|
+
const populateParam = relationFields.length > 0 ? `&populate=${encodeURIComponent(relationFields.join(","))}` : "";
|
|
4481
|
+
const { data } = await get(
|
|
4482
|
+
`/better-auth-dashboard/db?uid=plugin::better-auth.user&filters[id][$eq]=${userId}&pagination[pageSize]=1${populateParam}`
|
|
4483
|
+
);
|
|
4484
|
+
return data.results?.[0] ?? null;
|
|
4485
|
+
}
|
|
4486
|
+
});
|
|
3601
4487
|
const [activeTab, setActiveTab] = react.useState("profile");
|
|
3602
4488
|
const [editName, setEditName] = react.useState(void 0);
|
|
3603
4489
|
const [editEmail, setEditEmail] = react.useState(void 0);
|
|
@@ -3608,6 +4494,8 @@ function UserDetailDrawer({
|
|
|
3608
4494
|
const [banReason, setBanReason] = react.useState("");
|
|
3609
4495
|
const [banExpiresDays, setBanExpiresDays] = react.useState("");
|
|
3610
4496
|
const [confirmRevokeAll, setConfirmRevokeAll] = react.useState(false);
|
|
4497
|
+
const [confirmRevokeSessionId, setConfirmRevokeSessionId] = react.useState(null);
|
|
4498
|
+
const [confirmBan, setConfirmBan] = react.useState(false);
|
|
3611
4499
|
const [confirmUnban, setConfirmUnban] = react.useState(false);
|
|
3612
4500
|
const [confirmUnlinkAccountId, setConfirmUnlinkAccountId] = react.useState(null);
|
|
3613
4501
|
const [confirmDisable2FA, setConfirmDisable2FA] = react.useState(false);
|
|
@@ -3626,26 +4514,33 @@ function UserDetailDrawer({
|
|
|
3626
4514
|
setEditExtra((prev) => ({ ...prev, [name]: value }));
|
|
3627
4515
|
};
|
|
3628
4516
|
const extraData = {
|
|
3629
|
-
...
|
|
4517
|
+
...strapiUserQuery.data ?? {},
|
|
3630
4518
|
...editExtra
|
|
3631
4519
|
};
|
|
3632
4520
|
const updateMutation = reactQuery.useMutation({
|
|
3633
4521
|
mutationFn: async () => {
|
|
3634
|
-
const body = {
|
|
4522
|
+
const body = {};
|
|
3635
4523
|
if (editName !== void 0) body.name = editName;
|
|
3636
4524
|
if (editEmail !== void 0) body.email = editEmail;
|
|
3637
4525
|
if (editEmailVerified !== void 0)
|
|
3638
4526
|
body.emailVerified = editEmailVerified;
|
|
3639
4527
|
if (editImage !== void 0) body.image = editImage;
|
|
3640
|
-
const
|
|
3641
|
-
|
|
3642
|
-
|
|
4528
|
+
const documentId = strapiUserQuery.data?.documentId;
|
|
4529
|
+
if (!documentId) throw new Error("Could not resolve documentId for user");
|
|
4530
|
+
const updateUser = await client.dash.updateUser(
|
|
4531
|
+
{ userId, ...body },
|
|
4532
|
+
withContext({ userId }, getAuthHeaders())
|
|
3643
4533
|
);
|
|
3644
|
-
if (
|
|
3645
|
-
|
|
3646
|
-
|
|
4534
|
+
if (!updateUser.data) throw new Error("Update failed");
|
|
4535
|
+
if (Object.keys(editExtra).length > 0) {
|
|
4536
|
+
await put(
|
|
4537
|
+
`/better-auth-dashboard/db/${documentId}?uid=plugin::better-auth.user`,
|
|
4538
|
+
editExtra
|
|
4539
|
+
);
|
|
4540
|
+
}
|
|
3647
4541
|
},
|
|
3648
|
-
onSuccess: () => {
|
|
4542
|
+
onSuccess: async () => {
|
|
4543
|
+
await qc.invalidateQueries({ queryKey: ["dash-strapi-user", userId] });
|
|
3649
4544
|
qc.invalidateQueries({ queryKey: ["dash-user", userId] });
|
|
3650
4545
|
qc.invalidateQueries({ queryKey: ["dash-users"] });
|
|
3651
4546
|
setEditName(void 0);
|
|
@@ -3666,11 +4561,32 @@ function UserDetailDrawer({
|
|
|
3666
4561
|
});
|
|
3667
4562
|
}
|
|
3668
4563
|
});
|
|
4564
|
+
const revokeSessionMutation = reactQuery.useMutation({
|
|
4565
|
+
mutationFn: async (sessionId) => {
|
|
4566
|
+
const result = await client.dash.sessions.revoke(
|
|
4567
|
+
{},
|
|
4568
|
+
withContext({ sessionId, userId }, getAuthHeaders())
|
|
4569
|
+
);
|
|
4570
|
+
if (result.error)
|
|
4571
|
+
throw new Error(result.error.message ?? "Revoke failed");
|
|
4572
|
+
},
|
|
4573
|
+
onSuccess: () => {
|
|
4574
|
+
setConfirmRevokeSessionId(null);
|
|
4575
|
+
qc.invalidateQueries({ queryKey: ["dash-user-sessions", userId] });
|
|
4576
|
+
toggleNotification({ type: "success", message: "Session revoked" });
|
|
4577
|
+
},
|
|
4578
|
+
onError: (err) => {
|
|
4579
|
+
toggleNotification({
|
|
4580
|
+
type: "danger",
|
|
4581
|
+
message: err.message ?? "Failed to revoke session"
|
|
4582
|
+
});
|
|
4583
|
+
}
|
|
4584
|
+
});
|
|
3669
4585
|
const passwordMutation = reactQuery.useMutation({
|
|
3670
4586
|
mutationFn: async () => {
|
|
3671
4587
|
const result = await client.dash.setPassword(
|
|
3672
4588
|
{ password: newPassword },
|
|
3673
|
-
withContext({ userId })
|
|
4589
|
+
withContext({ userId }, getAuthHeaders())
|
|
3674
4590
|
);
|
|
3675
4591
|
if (result.error)
|
|
3676
4592
|
throw new Error(result.error.message ?? "Password update failed");
|
|
@@ -3698,11 +4614,15 @@ function UserDetailDrawer({
|
|
|
3698
4614
|
const days = parseInt(banExpiresDays, 10);
|
|
3699
4615
|
if (!Number.isNaN(days)) body.banExpires = Date.now() + days * 864e5;
|
|
3700
4616
|
}
|
|
3701
|
-
const result = await client.dash.banUser(
|
|
4617
|
+
const result = await client.dash.banUser(
|
|
4618
|
+
body,
|
|
4619
|
+
withContext({ userId }, getAuthHeaders())
|
|
4620
|
+
);
|
|
3702
4621
|
if (result.error) throw new Error(result.error.message ?? "Ban failed");
|
|
3703
4622
|
return result.data;
|
|
3704
4623
|
},
|
|
3705
4624
|
onSuccess: () => {
|
|
4625
|
+
setConfirmBan(false);
|
|
3706
4626
|
qc.invalidateQueries({ queryKey: ["dash-user", userId] });
|
|
3707
4627
|
qc.invalidateQueries({ queryKey: ["dash-users"] });
|
|
3708
4628
|
setBanReason("");
|
|
@@ -3718,7 +4638,10 @@ function UserDetailDrawer({
|
|
|
3718
4638
|
});
|
|
3719
4639
|
const unbanMutation = reactQuery.useMutation({
|
|
3720
4640
|
mutationFn: async () => {
|
|
3721
|
-
const result = await client.dash.unbanUser(
|
|
4641
|
+
const result = await client.dash.unbanUser(
|
|
4642
|
+
{},
|
|
4643
|
+
withContext({ userId }, getAuthHeaders())
|
|
4644
|
+
);
|
|
3722
4645
|
if (result.error) throw new Error(result.error.message ?? "Unban failed");
|
|
3723
4646
|
return result.data;
|
|
3724
4647
|
},
|
|
@@ -3739,7 +4662,7 @@ function UserDetailDrawer({
|
|
|
3739
4662
|
mutationFn: async (providerId) => {
|
|
3740
4663
|
const result = await client.dash.unlinkAccount(
|
|
3741
4664
|
{ providerId },
|
|
3742
|
-
withContext({ userId })
|
|
4665
|
+
withContext({ userId }, getAuthHeaders())
|
|
3743
4666
|
);
|
|
3744
4667
|
if (result.error)
|
|
3745
4668
|
throw new Error(result.error.message ?? "Failed to unlink account");
|
|
@@ -3760,7 +4683,7 @@ function UserDetailDrawer({
|
|
|
3760
4683
|
mutationFn: async () => {
|
|
3761
4684
|
const result = await client.dash.sessions.revokeAll(
|
|
3762
4685
|
{ userId },
|
|
3763
|
-
withContext({})
|
|
4686
|
+
withContext({}, getAuthHeaders())
|
|
3764
4687
|
);
|
|
3765
4688
|
if (result.error)
|
|
3766
4689
|
throw new Error(result.error.message ?? "Revoke failed");
|
|
@@ -3782,7 +4705,7 @@ function UserDetailDrawer({
|
|
|
3782
4705
|
const callbackUrl = new URL(window.location.href, window.location.origin);
|
|
3783
4706
|
const result = await client.dash.sendVerificationEmail(
|
|
3784
4707
|
{ callbackUrl: callbackUrl.toString() },
|
|
3785
|
-
withContext({ userId })
|
|
4708
|
+
withContext({ userId }, getAuthHeaders())
|
|
3786
4709
|
);
|
|
3787
4710
|
if (result.error) throw new Error(result.error.message ?? "Failed");
|
|
3788
4711
|
return result.data;
|
|
@@ -3805,7 +4728,7 @@ function UserDetailDrawer({
|
|
|
3805
4728
|
const callbackUrl = new URL(window.location.href, window.location.origin);
|
|
3806
4729
|
const result = await client.dash.sendResetPasswordEmail(
|
|
3807
4730
|
{ callbackUrl: callbackUrl.toString() },
|
|
3808
|
-
withContext({ userId })
|
|
4731
|
+
withContext({ userId }, getAuthHeaders())
|
|
3809
4732
|
);
|
|
3810
4733
|
if (result.error) throw new Error(result.error.message ?? "Failed");
|
|
3811
4734
|
return result.data;
|
|
@@ -3827,7 +4750,7 @@ function UserDetailDrawer({
|
|
|
3827
4750
|
mutationFn: async () => {
|
|
3828
4751
|
const result = await client.dash.enableTwoFactor(
|
|
3829
4752
|
{},
|
|
3830
|
-
withContext({ userId })
|
|
4753
|
+
withContext({ userId }, getAuthHeaders())
|
|
3831
4754
|
);
|
|
3832
4755
|
if (result.error)
|
|
3833
4756
|
throw new Error(result.error.message ?? "Failed to enable 2FA");
|
|
@@ -3852,7 +4775,7 @@ function UserDetailDrawer({
|
|
|
3852
4775
|
mutationFn: async () => {
|
|
3853
4776
|
const result = await client.dash.disableTwoFactor(
|
|
3854
4777
|
{},
|
|
3855
|
-
withContext({ userId })
|
|
4778
|
+
withContext({ userId }, getAuthHeaders())
|
|
3856
4779
|
);
|
|
3857
4780
|
if (result.error)
|
|
3858
4781
|
throw new Error(result.error.message ?? "Failed to disable 2FA");
|
|
@@ -3880,7 +4803,7 @@ function UserDetailDrawer({
|
|
|
3880
4803
|
mutationFn: async () => {
|
|
3881
4804
|
const result = await client.dash.viewTwoFactorTotpUri(
|
|
3882
4805
|
{},
|
|
3883
|
-
withContext({ userId })
|
|
4806
|
+
withContext({ userId }, getAuthHeaders())
|
|
3884
4807
|
);
|
|
3885
4808
|
if (result.error)
|
|
3886
4809
|
throw new Error(result.error.message ?? "Failed to load TOTP URI");
|
|
@@ -3903,7 +4826,7 @@ function UserDetailDrawer({
|
|
|
3903
4826
|
mutationFn: async () => {
|
|
3904
4827
|
const result = await client.dash.viewBackupCodes(
|
|
3905
4828
|
{},
|
|
3906
|
-
withContext({ userId })
|
|
4829
|
+
withContext({ userId }, getAuthHeaders())
|
|
3907
4830
|
);
|
|
3908
4831
|
if (result.error)
|
|
3909
4832
|
throw new Error(result.error.message ?? "Failed to load backup codes");
|
|
@@ -3926,7 +4849,7 @@ function UserDetailDrawer({
|
|
|
3926
4849
|
mutationFn: async () => {
|
|
3927
4850
|
const result = await client.dash.generateBackupCodes(
|
|
3928
4851
|
{},
|
|
3929
|
-
withContext({ userId })
|
|
4852
|
+
withContext({ userId }, getAuthHeaders())
|
|
3930
4853
|
);
|
|
3931
4854
|
if (result.error)
|
|
3932
4855
|
throw new Error(
|
|
@@ -4122,7 +5045,15 @@ function UserDetailDrawer({
|
|
|
4122
5045
|
}
|
|
4123
5046
|
)
|
|
4124
5047
|
] })
|
|
4125
|
-
] })
|
|
5048
|
+
] }),
|
|
5049
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5050
|
+
EditViewSidePanels,
|
|
5051
|
+
{
|
|
5052
|
+
model: "plugin::better-auth.user",
|
|
5053
|
+
documentId: strapiUserQuery.data?.documentId,
|
|
5054
|
+
document: strapiUserQuery.data ?? void 0
|
|
5055
|
+
}
|
|
5056
|
+
)
|
|
4126
5057
|
] })
|
|
4127
5058
|
] }) }),
|
|
4128
5059
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "security", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 5, paddingTop: 6, children: [
|
|
@@ -4318,7 +5249,8 @@ function UserDetailDrawer({
|
|
|
4318
5249
|
] })
|
|
4319
5250
|
] })
|
|
4320
5251
|
] }) }),
|
|
4321
|
-
banEnabled && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "ban", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap:
|
|
5252
|
+
banEnabled && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "ban", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 5, paddingTop: 6, children: user?.banned ? /* @__PURE__ */ jsxRuntime.jsxs(FormSection, { children: [
|
|
5253
|
+
/* @__PURE__ */ jsxRuntime.jsx(SectionLabel, { children: "Ban status" }),
|
|
4322
5254
|
/* @__PURE__ */ jsxRuntime.jsxs(WarnCard, { children: [
|
|
4323
5255
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4324
5256
|
designSystem.Typography,
|
|
@@ -4333,7 +5265,7 @@ function UserDetailDrawer({
|
|
|
4333
5265
|
"Reason: ",
|
|
4334
5266
|
user.banReason
|
|
4335
5267
|
] }),
|
|
4336
|
-
user.banExpires
|
|
5268
|
+
user.banExpires ? /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "danger600", children: [
|
|
4337
5269
|
"Expires:",
|
|
4338
5270
|
" ",
|
|
4339
5271
|
new Date(user.banExpires).toLocaleDateString(
|
|
@@ -4345,8 +5277,7 @@ function UserDetailDrawer({
|
|
|
4345
5277
|
day: "numeric"
|
|
4346
5278
|
}
|
|
4347
5279
|
)
|
|
4348
|
-
] }),
|
|
4349
|
-
!user.banExpires && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "danger600", children: "Duration: Permanent" })
|
|
5280
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "danger600", children: "Duration: Permanent" })
|
|
4350
5281
|
] }),
|
|
4351
5282
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4352
5283
|
designSystem.Button,
|
|
@@ -4356,27 +5287,28 @@ function UserDetailDrawer({
|
|
|
4356
5287
|
children: "Lift ban"
|
|
4357
5288
|
}
|
|
4358
5289
|
) })
|
|
4359
|
-
] }) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5290
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(FormSection, { children: [
|
|
5291
|
+
/* @__PURE__ */ jsxRuntime.jsx(SectionLabel, { children: "Apply ban" }),
|
|
5292
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5293
|
+
designSystem.Field.Root,
|
|
5294
|
+
{
|
|
5295
|
+
hint: "Optional — will be shown to the user",
|
|
5296
|
+
style: { width: "100%" },
|
|
5297
|
+
children: [
|
|
5298
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Reason" }),
|
|
5299
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5300
|
+
designSystem.TextInput,
|
|
5301
|
+
{
|
|
5302
|
+
value: banReason,
|
|
5303
|
+
onChange: (e) => setBanReason(e.target.value),
|
|
5304
|
+
placeholder: "e.g. Violated terms of service"
|
|
5305
|
+
}
|
|
5306
|
+
),
|
|
5307
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
|
|
5308
|
+
]
|
|
5309
|
+
}
|
|
5310
|
+
),
|
|
4360
5311
|
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Grid.Root, { gap: 4, children: [
|
|
4361
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 12, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4362
|
-
designSystem.Field.Root,
|
|
4363
|
-
{
|
|
4364
|
-
hint: "Optional — will be shown to the user",
|
|
4365
|
-
style: { width: "100%" },
|
|
4366
|
-
children: [
|
|
4367
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Reason" }),
|
|
4368
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4369
|
-
designSystem.TextInput,
|
|
4370
|
-
{
|
|
4371
|
-
value: banReason,
|
|
4372
|
-
onChange: (e) => setBanReason(e.target.value),
|
|
4373
|
-
placeholder: "e.g. Violated terms of service"
|
|
4374
|
-
}
|
|
4375
|
-
),
|
|
4376
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
|
|
4377
|
-
]
|
|
4378
|
-
}
|
|
4379
|
-
) }),
|
|
4380
5312
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 6, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4381
5313
|
designSystem.Field.Root,
|
|
4382
5314
|
{
|
|
@@ -4397,24 +5329,13 @@ function UserDetailDrawer({
|
|
|
4397
5329
|
]
|
|
4398
5330
|
}
|
|
4399
5331
|
) }),
|
|
4400
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 6, children:
|
|
4401
|
-
designSystem.Flex,
|
|
4402
|
-
{
|
|
4403
|
-
direction: "column",
|
|
4404
|
-
justifyContent: "flex-end",
|
|
4405
|
-
style: { height: "100%", paddingBottom: 4 },
|
|
4406
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs(PreviewPill, { children: [
|
|
4407
|
-
"⏱ Expires ",
|
|
4408
|
-
banExpiryPreview
|
|
4409
|
-
] })
|
|
4410
|
-
}
|
|
4411
|
-
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
5332
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 6, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4412
5333
|
designSystem.Flex,
|
|
4413
5334
|
{
|
|
4414
5335
|
direction: "column",
|
|
4415
5336
|
justifyContent: "flex-end",
|
|
4416
5337
|
style: { height: "100%", paddingBottom: 4 },
|
|
4417
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(PreviewPill, { children: "♾ Permanent ban" })
|
|
5338
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(PreviewPill, { children: banExpiryPreview ? `⏱ Expires ${banExpiryPreview}` : "♾ Permanent ban" })
|
|
4418
5339
|
}
|
|
4419
5340
|
) })
|
|
4420
5341
|
] }),
|
|
@@ -4422,31 +5343,68 @@ function UserDetailDrawer({
|
|
|
4422
5343
|
designSystem.Button,
|
|
4423
5344
|
{
|
|
4424
5345
|
variant: "danger",
|
|
4425
|
-
|
|
4426
|
-
onClick: () => banMutation.mutate(),
|
|
5346
|
+
onClick: () => setConfirmBan(true),
|
|
4427
5347
|
children: "Ban user"
|
|
4428
5348
|
}
|
|
4429
5349
|
) })
|
|
4430
5350
|
] }) }) }),
|
|
4431
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "sessions", children: /* @__PURE__ */ jsxRuntime.
|
|
4432
|
-
/* @__PURE__ */ jsxRuntime.
|
|
4433
|
-
|
|
4434
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
4435
|
-
/* @__PURE__ */ jsxRuntime.
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
5351
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "sessions", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 5, paddingTop: 6, children: [
|
|
5352
|
+
/* @__PURE__ */ jsxRuntime.jsxs(FormSection, { children: [
|
|
5353
|
+
/* @__PURE__ */ jsxRuntime.jsx(SectionLabel, { children: "Session management" }),
|
|
5354
|
+
/* @__PURE__ */ jsxRuntime.jsxs(AccountRow, { children: [
|
|
5355
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 1, alignItems: "flex-start", children: [
|
|
5356
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "semiBold", children: "Revoke all sessions" }),
|
|
5357
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: "Signs the user out immediately on all devices." })
|
|
5358
|
+
] }),
|
|
5359
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5360
|
+
designSystem.Button,
|
|
5361
|
+
{
|
|
5362
|
+
variant: "danger-light",
|
|
5363
|
+
size: "S",
|
|
5364
|
+
onClick: () => setConfirmRevokeAll(true),
|
|
5365
|
+
style: { flexShrink: 0 },
|
|
5366
|
+
children: "Revoke all"
|
|
5367
|
+
}
|
|
5368
|
+
)
|
|
5369
|
+
] })
|
|
5370
|
+
] }),
|
|
5371
|
+
/* @__PURE__ */ jsxRuntime.jsxs(FormSection, { children: [
|
|
5372
|
+
/* @__PURE__ */ jsxRuntime.jsx(SectionLabel, { children: "Active sessions" }),
|
|
5373
|
+
sessionsQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: "Loading sessions…" }) }) : (sessionsQuery.data ?? []).length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: "No active sessions." }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 2, alignItems: "stretch", children: (sessionsQuery.data ?? []).map((session) => /* @__PURE__ */ jsxRuntime.jsxs(SessionCard, { children: [
|
|
5374
|
+
/* @__PURE__ */ jsxRuntime.jsxs(SessionMeta, { children: [
|
|
5375
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5376
|
+
designSystem.Flex,
|
|
5377
|
+
{
|
|
5378
|
+
gap: 2,
|
|
5379
|
+
alignItems: "center",
|
|
5380
|
+
style: { flexWrap: "wrap" },
|
|
5381
|
+
children: [
|
|
5382
|
+
session.ipAddress && /* @__PURE__ */ jsxRuntime.jsx(IpChip, { children: session.ipAddress }),
|
|
5383
|
+
/* @__PURE__ */ jsxRuntime.jsxs(TimestampText, { children: [
|
|
5384
|
+
"Created",
|
|
5385
|
+
" ",
|
|
5386
|
+
new Date(session.createdAt).toLocaleString(),
|
|
5387
|
+
" · Expires",
|
|
5388
|
+
" ",
|
|
5389
|
+
new Date(session.expiresAt).toLocaleString()
|
|
5390
|
+
] })
|
|
5391
|
+
]
|
|
5392
|
+
}
|
|
5393
|
+
),
|
|
5394
|
+
session.userAgent && /* @__PURE__ */ jsxRuntime.jsx(AgentText, { children: session.userAgent })
|
|
5395
|
+
] }),
|
|
5396
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5397
|
+
designSystem.IconButton,
|
|
5398
|
+
{
|
|
5399
|
+
label: "Revoke session",
|
|
5400
|
+
onClick: () => setConfirmRevokeSessionId(String(session.id)),
|
|
5401
|
+
style: { flexShrink: 0 },
|
|
5402
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
|
|
5403
|
+
}
|
|
5404
|
+
)
|
|
5405
|
+
] }, session.documentId)) })
|
|
4448
5406
|
] })
|
|
4449
|
-
] }) })
|
|
5407
|
+
] }) }),
|
|
4450
5408
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "organizations", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 5, paddingTop: 6, children: /* @__PURE__ */ jsxRuntime.jsxs(FormSection, { children: [
|
|
4451
5409
|
/* @__PURE__ */ jsxRuntime.jsx(SectionLabel, { children: "Memberships" }),
|
|
4452
5410
|
orgsQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral500", children: "Loading…" }) : (orgsQuery.data ?? []).length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: "Not a member of any organizations." }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 2, children: (orgsQuery.data ?? []).map(
|
|
@@ -4483,6 +5441,29 @@ function UserDetailDrawer({
|
|
|
4483
5441
|
onCancel: () => setConfirmRevokeAll(false)
|
|
4484
5442
|
}
|
|
4485
5443
|
),
|
|
5444
|
+
confirmRevokeSessionId && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5445
|
+
ConfirmDialog,
|
|
5446
|
+
{
|
|
5447
|
+
title: "Revoke session",
|
|
5448
|
+
message: "Are you sure you want to revoke this session? The user will be signed out on this device.",
|
|
5449
|
+
confirmLabel: "Revoke",
|
|
5450
|
+
loading: revokeSessionMutation.isLoading,
|
|
5451
|
+
onConfirm: () => revokeSessionMutation.mutate(confirmRevokeSessionId),
|
|
5452
|
+
onCancel: () => setConfirmRevokeSessionId(null)
|
|
5453
|
+
}
|
|
5454
|
+
),
|
|
5455
|
+
confirmBan && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5456
|
+
ConfirmDialog,
|
|
5457
|
+
{
|
|
5458
|
+
title: "Ban user",
|
|
5459
|
+
message: banReason ? `Ban this user for the following reason: "${banReason}"? They will be prevented from signing in.` : "Are you sure you want to ban this user? They will be prevented from signing in.",
|
|
5460
|
+
confirmLabel: "Ban user",
|
|
5461
|
+
variant: "danger",
|
|
5462
|
+
loading: banMutation.isLoading,
|
|
5463
|
+
onConfirm: () => banMutation.mutate(),
|
|
5464
|
+
onCancel: () => setConfirmBan(false)
|
|
5465
|
+
}
|
|
5466
|
+
),
|
|
4486
5467
|
confirmUnban && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4487
5468
|
ConfirmDialog,
|
|
4488
5469
|
{
|
|
@@ -4712,7 +5693,9 @@ const Toolbar = styled__default.default.div`
|
|
|
4712
5693
|
gap: 12px;
|
|
4713
5694
|
`;
|
|
4714
5695
|
function UsersPage({ config }) {
|
|
5696
|
+
const rbac = admin.useRBAC(index.PERMISSIONS.user);
|
|
4715
5697
|
const qc = reactQuery.useQueryClient();
|
|
5698
|
+
const { toggleNotification } = admin.useNotification();
|
|
4716
5699
|
const banEnabled = hasPlugin(config, "admin");
|
|
4717
5700
|
const emailVerificationEnabled = config.emailVerification.sendVerificationEmailEnabled;
|
|
4718
5701
|
const twoFactorEnabled = hasPlugin(config, "two-factor");
|
|
@@ -4736,7 +5719,10 @@ function UsersPage({ config }) {
|
|
|
4736
5719
|
const pageCount = Math.ceil(total / PAGE_SIZE);
|
|
4737
5720
|
const deleteMutation = reactQuery.useMutation({
|
|
4738
5721
|
mutationFn: async (userId) => {
|
|
4739
|
-
const result = await client.dash.deleteUser(
|
|
5722
|
+
const result = await client.dash.deleteUser(
|
|
5723
|
+
{},
|
|
5724
|
+
withContext({ userId }, getAuthHeaders())
|
|
5725
|
+
);
|
|
4740
5726
|
if (result.error)
|
|
4741
5727
|
throw new Error(result.error.message ?? "Delete failed");
|
|
4742
5728
|
},
|
|
@@ -4744,38 +5730,65 @@ function UsersPage({ config }) {
|
|
|
4744
5730
|
setConfirmDelete(null);
|
|
4745
5731
|
qc.invalidateQueries({ queryKey: ["dash-users"] });
|
|
4746
5732
|
qc.invalidateQueries({ queryKey: ["dash-user-stats"] });
|
|
5733
|
+
toggleNotification({ type: "success", message: "User deleted" });
|
|
5734
|
+
},
|
|
5735
|
+
onError: (err) => {
|
|
5736
|
+
toggleNotification({
|
|
5737
|
+
type: "danger",
|
|
5738
|
+
message: err.message ?? "Failed to delete user"
|
|
5739
|
+
});
|
|
4747
5740
|
}
|
|
4748
5741
|
});
|
|
4749
5742
|
const deleteManyMutation = reactQuery.useMutation({
|
|
4750
5743
|
mutationFn: async (userIds) => {
|
|
4751
5744
|
const result = await client.dash.deleteManyUsers(
|
|
4752
5745
|
{},
|
|
4753
|
-
withContext({ userIds })
|
|
5746
|
+
withContext({ userIds }, getAuthHeaders())
|
|
4754
5747
|
);
|
|
4755
5748
|
if (result.error)
|
|
4756
5749
|
throw new Error(result.error.message ?? "Delete failed");
|
|
4757
5750
|
return result.data;
|
|
4758
5751
|
},
|
|
4759
|
-
onSuccess: () => {
|
|
5752
|
+
onSuccess: (_data, userIds) => {
|
|
4760
5753
|
setConfirmDeleteMany(false);
|
|
4761
5754
|
setSelected(/* @__PURE__ */ new Set());
|
|
4762
5755
|
qc.invalidateQueries({ queryKey: ["dash-users"] });
|
|
4763
5756
|
qc.invalidateQueries({ queryKey: ["dash-user-stats"] });
|
|
5757
|
+
toggleNotification({
|
|
5758
|
+
type: "success",
|
|
5759
|
+
message: `${userIds.length} user${userIds.length !== 1 ? "s" : ""} deleted`
|
|
5760
|
+
});
|
|
5761
|
+
},
|
|
5762
|
+
onError: (err) => {
|
|
5763
|
+
toggleNotification({
|
|
5764
|
+
type: "danger",
|
|
5765
|
+
message: err.message ?? "Failed to delete users"
|
|
5766
|
+
});
|
|
4764
5767
|
}
|
|
4765
5768
|
});
|
|
4766
5769
|
const banManyMutation = reactQuery.useMutation({
|
|
4767
5770
|
mutationFn: async (userIds) => {
|
|
4768
5771
|
const result = await client.dash.banManyUsers(
|
|
4769
5772
|
{},
|
|
4770
|
-
withContext({ userIds })
|
|
5773
|
+
withContext({ userIds }, getAuthHeaders())
|
|
4771
5774
|
);
|
|
4772
5775
|
if (result.error) throw new Error(result.error.message ?? "Ban failed");
|
|
4773
5776
|
return result.data;
|
|
4774
5777
|
},
|
|
4775
|
-
onSuccess: () => {
|
|
5778
|
+
onSuccess: (_data, userIds) => {
|
|
4776
5779
|
setConfirmBanMany(false);
|
|
4777
5780
|
setSelected(/* @__PURE__ */ new Set());
|
|
4778
5781
|
qc.invalidateQueries({ queryKey: ["dash-users"] });
|
|
5782
|
+
toggleNotification({
|
|
5783
|
+
type: "success",
|
|
5784
|
+
message: `${userIds.length} user${userIds.length !== 1 ? "s" : ""} banned`
|
|
5785
|
+
});
|
|
5786
|
+
},
|
|
5787
|
+
onError: (err) => {
|
|
5788
|
+
toggleNotification({
|
|
5789
|
+
type: "danger",
|
|
5790
|
+
message: err.message ?? "Failed to ban users"
|
|
5791
|
+
});
|
|
4779
5792
|
}
|
|
4780
5793
|
});
|
|
4781
5794
|
const toggleSelect = (id) => {
|
|
@@ -4800,334 +5813,244 @@ function UsersPage({ config }) {
|
|
|
4800
5813
|
setSearch(searchInput);
|
|
4801
5814
|
setPage(1);
|
|
4802
5815
|
};
|
|
4803
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
) }),
|
|
4882
|
-
/* @__PURE__ */ jsxRuntime.jsx(TH, { children: "Name" }),
|
|
4883
|
-
/* @__PURE__ */ jsxRuntime.jsx(TH, { children: "Email" }),
|
|
4884
|
-
/* @__PURE__ */ jsxRuntime.jsx(TH, { children: "Status" }),
|
|
4885
|
-
/* @__PURE__ */ jsxRuntime.jsx(TH, { children: "Created" }),
|
|
4886
|
-
/* @__PURE__ */ jsxRuntime.jsx(THActions, {})
|
|
4887
|
-
] }) }),
|
|
4888
|
-
/* @__PURE__ */ jsxRuntime.jsx("tbody", { children: users.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4889
|
-
TD,
|
|
4890
|
-
{
|
|
4891
|
-
colSpan: 6,
|
|
4892
|
-
style: {
|
|
4893
|
-
textAlign: "center",
|
|
4894
|
-
padding: "40px",
|
|
4895
|
-
color: "#8e8ea9"
|
|
4896
|
-
},
|
|
4897
|
-
"data-testid": "users-empty",
|
|
4898
|
-
children: search ? `No users matching "${search}"` : "No users found"
|
|
4899
|
-
}
|
|
4900
|
-
) }) : users.map((user, i) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4901
|
-
TR,
|
|
4902
|
-
{
|
|
4903
|
-
$selected: selected.has(user.id),
|
|
4904
|
-
$i: i,
|
|
4905
|
-
"data-testid": "user-row",
|
|
4906
|
-
children: [
|
|
4907
|
-
/* @__PURE__ */ jsxRuntime.jsx(TDCheck, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
5816
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
5817
|
+
admin.Page.Protect,
|
|
5818
|
+
{
|
|
5819
|
+
permissions: index.PERMISSIONS.user.filter(
|
|
5820
|
+
(p) => p.action === `plugin::${index.PLUGIN_ID}.user.read`
|
|
5821
|
+
),
|
|
5822
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(Wrap, { "data-testid": "users-page", children: [
|
|
5823
|
+
/* @__PURE__ */ jsxRuntime.jsxs(PageHeader, { children: [
|
|
5824
|
+
/* @__PURE__ */ jsxRuntime.jsxs(TitleBlock, { children: [
|
|
5825
|
+
/* @__PURE__ */ jsxRuntime.jsx(PageTitle, { children: "Users" }),
|
|
5826
|
+
/* @__PURE__ */ jsxRuntime.jsxs(PageSubtitle, { children: [
|
|
5827
|
+
total.toLocaleString(),
|
|
5828
|
+
" total",
|
|
5829
|
+
data?.onlineUsers ? ` · ${data.onlineUsers} online` : ""
|
|
5830
|
+
] })
|
|
5831
|
+
] }),
|
|
5832
|
+
rbac.allowedActions.canCreate && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5833
|
+
designSystem.Button,
|
|
5834
|
+
{
|
|
5835
|
+
startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
|
|
5836
|
+
onClick: () => setShowCreate(true),
|
|
5837
|
+
"data-testid": "create-user-btn",
|
|
5838
|
+
children: "Create user"
|
|
5839
|
+
}
|
|
5840
|
+
)
|
|
5841
|
+
] }),
|
|
5842
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Toolbar, { children: [
|
|
5843
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.SearchForm, { onSubmit: handleSearch, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
5844
|
+
designSystem.Searchbar,
|
|
5845
|
+
{
|
|
5846
|
+
clearLabel: "Clear",
|
|
5847
|
+
name: "search",
|
|
5848
|
+
placeholder: "Search by email…",
|
|
5849
|
+
value: searchInput,
|
|
5850
|
+
onChange: (e) => setSearchInput(e.target.value),
|
|
5851
|
+
onClear: () => {
|
|
5852
|
+
setSearchInput("");
|
|
5853
|
+
setSearch("");
|
|
5854
|
+
setPage(1);
|
|
5855
|
+
},
|
|
5856
|
+
"data-testid": "user-search",
|
|
5857
|
+
children: "Search users"
|
|
5858
|
+
}
|
|
5859
|
+
) }),
|
|
5860
|
+
someSelected && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
|
|
5861
|
+
rbac.allowedActions.canDelete && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5862
|
+
designSystem.Button,
|
|
5863
|
+
{
|
|
5864
|
+
variant: "danger-light",
|
|
5865
|
+
size: "S",
|
|
5866
|
+
onClick: () => setConfirmDeleteMany(true),
|
|
5867
|
+
"data-testid": "delete-selected-btn",
|
|
5868
|
+
children: [
|
|
5869
|
+
"Delete ",
|
|
5870
|
+
selected.size,
|
|
5871
|
+
" selected"
|
|
5872
|
+
]
|
|
5873
|
+
}
|
|
5874
|
+
),
|
|
5875
|
+
banEnabled && rbac.allowedActions.canUpdate && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5876
|
+
designSystem.Button,
|
|
5877
|
+
{
|
|
5878
|
+
variant: "secondary",
|
|
5879
|
+
size: "S",
|
|
5880
|
+
onClick: () => setConfirmBanMany(true),
|
|
5881
|
+
children: [
|
|
5882
|
+
"Ban ",
|
|
5883
|
+
selected.size,
|
|
5884
|
+
" selected"
|
|
5885
|
+
]
|
|
5886
|
+
}
|
|
5887
|
+
)
|
|
5888
|
+
] })
|
|
5889
|
+
] }),
|
|
5890
|
+
isError && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#d02b20", fontSize: 12, padding: "8px 0" }, children: error?.message }),
|
|
5891
|
+
/* @__PURE__ */ jsxRuntime.jsx(TableCard, { children: isLoading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: "Loading users…" }) }) : /* @__PURE__ */ jsxRuntime.jsxs(Table, { children: [
|
|
5892
|
+
/* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
|
|
5893
|
+
(rbac.allowedActions.canDelete || rbac.allowedActions.canUpdate) && /* @__PURE__ */ jsxRuntime.jsx(THCheck, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4908
5894
|
designSystem.Checkbox,
|
|
4909
5895
|
{
|
|
4910
|
-
checked:
|
|
4911
|
-
onCheckedChange:
|
|
4912
|
-
"aria-label":
|
|
5896
|
+
checked: allSelected,
|
|
5897
|
+
onCheckedChange: handleSelectAll,
|
|
5898
|
+
"aria-label": "Select all"
|
|
4913
5899
|
}
|
|
4914
5900
|
) }),
|
|
4915
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
),
|
|
4924
|
-
/* @__PURE__ */ jsxRuntime.jsx(UserName, { children: user.name })
|
|
4925
|
-
] }) }),
|
|
4926
|
-
/* @__PURE__ */ jsxRuntime.jsx(TD, { children: /* @__PURE__ */ jsxRuntime.jsx(MonoText, { children: user.email }) }),
|
|
4927
|
-
/* @__PURE__ */ jsxRuntime.jsx(TD, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, children: [
|
|
4928
|
-
user.emailVerified ? /* @__PURE__ */ jsxRuntime.jsx(StatusChip, { $variant: "verified", children: "Verified" }) : /* @__PURE__ */ jsxRuntime.jsx(StatusChip, { $variant: "unverified", children: "Unverified" }),
|
|
4929
|
-
user.banned && /* @__PURE__ */ jsxRuntime.jsx(StatusChip, { $variant: "banned", children: "Banned" })
|
|
4930
|
-
] }) }),
|
|
4931
|
-
/* @__PURE__ */ jsxRuntime.jsx(TD, { children: /* @__PURE__ */ jsxRuntime.jsx(DateText, { children: new Date(user.createdAt).toLocaleDateString() }) }),
|
|
4932
|
-
/* @__PURE__ */ jsxRuntime.jsx(TDActions, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, justifyContent: "flex-end", children: [
|
|
4933
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4934
|
-
designSystem.IconButton,
|
|
4935
|
-
{
|
|
4936
|
-
label: "Edit user",
|
|
4937
|
-
onClick: () => setDetailUserId(user.id),
|
|
4938
|
-
"data-testid": "edit-user-btn",
|
|
4939
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(icons.Pencil, {})
|
|
4940
|
-
}
|
|
4941
|
-
),
|
|
4942
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4943
|
-
designSystem.IconButton,
|
|
4944
|
-
{
|
|
4945
|
-
label: "Delete user",
|
|
4946
|
-
onClick: () => setConfirmDelete(user.id),
|
|
4947
|
-
"data-testid": "delete-user-btn",
|
|
4948
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
|
|
4949
|
-
}
|
|
4950
|
-
)
|
|
4951
|
-
] }) })
|
|
4952
|
-
]
|
|
4953
|
-
},
|
|
4954
|
-
user.id
|
|
4955
|
-
)) })
|
|
4956
|
-
] }) }),
|
|
4957
|
-
pageCount > 1 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4958
|
-
designSystem.Pagination,
|
|
4959
|
-
{
|
|
4960
|
-
activePage: page,
|
|
4961
|
-
pageCount,
|
|
4962
|
-
onChangePage: setPage
|
|
4963
|
-
}
|
|
4964
|
-
) }),
|
|
4965
|
-
showCreate && /* @__PURE__ */ jsxRuntime.jsx(CreateUserDialog, { onClose: () => setShowCreate(false) }),
|
|
4966
|
-
detailUserId && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4967
|
-
UserDetailDrawer,
|
|
4968
|
-
{
|
|
4969
|
-
userId: detailUserId,
|
|
4970
|
-
banEnabled,
|
|
4971
|
-
emailVerificationEnabled,
|
|
4972
|
-
twoFactorEnabled,
|
|
4973
|
-
onClose: () => setDetailUserId(null)
|
|
4974
|
-
}
|
|
4975
|
-
),
|
|
4976
|
-
confirmDelete && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4977
|
-
ConfirmDialog,
|
|
4978
|
-
{
|
|
4979
|
-
title: "Delete user",
|
|
4980
|
-
message: "Are you sure you want to delete this user? This action cannot be undone.",
|
|
4981
|
-
confirmLabel: "Delete",
|
|
4982
|
-
loading: deleteMutation.isLoading,
|
|
4983
|
-
onConfirm: () => deleteMutation.mutate(confirmDelete),
|
|
4984
|
-
onCancel: () => setConfirmDelete(null)
|
|
4985
|
-
}
|
|
4986
|
-
),
|
|
4987
|
-
confirmDeleteMany && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4988
|
-
ConfirmDialog,
|
|
4989
|
-
{
|
|
4990
|
-
title: `Delete ${selected.size} user${selected.size !== 1 ? "s" : ""}`,
|
|
4991
|
-
message: `Are you sure you want to delete ${selected.size} user${selected.size !== 1 ? "s" : ""}? This action cannot be undone.`,
|
|
4992
|
-
confirmLabel: "Delete all",
|
|
4993
|
-
loading: deleteManyMutation.isLoading,
|
|
4994
|
-
onConfirm: () => deleteManyMutation.mutate([...selected]),
|
|
4995
|
-
onCancel: () => setConfirmDeleteMany(false)
|
|
4996
|
-
}
|
|
4997
|
-
),
|
|
4998
|
-
confirmBanMany && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4999
|
-
ConfirmDialog,
|
|
5000
|
-
{
|
|
5001
|
-
title: `Ban ${selected.size} user${selected.size !== 1 ? "s" : ""}`,
|
|
5002
|
-
message: `Are you sure you want to ban ${selected.size} user${selected.size !== 1 ? "s" : ""}? They will be prevented from signing in.`,
|
|
5003
|
-
confirmLabel: "Ban all",
|
|
5004
|
-
variant: "danger",
|
|
5005
|
-
loading: banManyMutation.isLoading,
|
|
5006
|
-
onConfirm: () => banManyMutation.mutate([...selected]),
|
|
5007
|
-
onCancel: () => setConfirmBanMany(false)
|
|
5008
|
-
}
|
|
5009
|
-
)
|
|
5010
|
-
] });
|
|
5011
|
-
}
|
|
5012
|
-
const Accent = styled__default.default.div`
|
|
5013
|
-
height: 3px;
|
|
5014
|
-
background: linear-gradient(90deg, #4945ff 0%, #7b79ff 55%, #9593ff 100%);
|
|
5015
|
-
flex-shrink: 0;
|
|
5016
|
-
`;
|
|
5017
|
-
const BrandIcon = styled__default.default.div`
|
|
5018
|
-
width: 30px;
|
|
5019
|
-
height: 30px;
|
|
5020
|
-
border-radius: 7px;
|
|
5021
|
-
background: linear-gradient(135deg, #4945ff 0%, #7b79ff 100%);
|
|
5022
|
-
display: flex;
|
|
5023
|
-
align-items: center;
|
|
5024
|
-
justify-content: center;
|
|
5025
|
-
color: white;
|
|
5026
|
-
font-size: 12px;
|
|
5027
|
-
font-weight: 900;
|
|
5028
|
-
flex-shrink: 0;
|
|
5029
|
-
letter-spacing: -0.06em;
|
|
5030
|
-
user-select: none;
|
|
5031
|
-
`;
|
|
5032
|
-
const PathTag = styled__default.default.code`
|
|
5033
|
-
padding: 2px 8px;
|
|
5034
|
-
background: #f0f0ff;
|
|
5035
|
-
border: 1px solid #d9d8ff;
|
|
5036
|
-
border-radius: 4px;
|
|
5037
|
-
font-size: 11px;
|
|
5038
|
-
color: #4945ff;
|
|
5039
|
-
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
|
5040
|
-
font-weight: 500;
|
|
5041
|
-
`;
|
|
5042
|
-
function App() {
|
|
5043
|
-
const { data: config, isLoading, isError, error } = useDashConfig();
|
|
5044
|
-
const orgEnabled = hasPlugin(config, "organization");
|
|
5045
|
-
const orgOptionsQuery = reactQuery.useQuery({
|
|
5046
|
-
queryKey: ["dash-org-options"],
|
|
5047
|
-
queryFn: async () => {
|
|
5048
|
-
const result = await client.dash.organization.options();
|
|
5049
|
-
if (result.error) return { teamsEnabled: false };
|
|
5050
|
-
return result.data ?? { teamsEnabled: false };
|
|
5051
|
-
},
|
|
5052
|
-
enabled: orgEnabled
|
|
5053
|
-
});
|
|
5054
|
-
const teamsEnabled = orgEnabled ? orgOptionsQuery.data?.teamsEnabled ?? false : false;
|
|
5055
|
-
if (isLoading) {
|
|
5056
|
-
return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", alignItems: "center", padding: 12, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: "Loading Better Auth dashboard…" }) });
|
|
5057
|
-
}
|
|
5058
|
-
if (isError) {
|
|
5059
|
-
return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 6, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
5060
|
-
designSystem.Alert,
|
|
5061
|
-
{
|
|
5062
|
-
closeLabel: "Close",
|
|
5063
|
-
title: "Error loading configuration",
|
|
5064
|
-
variant: "danger",
|
|
5065
|
-
children: error?.message
|
|
5066
|
-
}
|
|
5067
|
-
) });
|
|
5068
|
-
}
|
|
5069
|
-
if (!config) return null;
|
|
5070
|
-
return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { background: "neutral100", minHeight: "100vh", "data-testid": "dashboard-root", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tabs.Root, { defaultValue: "overview", children: [
|
|
5071
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5072
|
-
designSystem.Box,
|
|
5073
|
-
{
|
|
5074
|
-
background: "neutral0",
|
|
5075
|
-
borderColor: "neutral150",
|
|
5076
|
-
borderStyle: "solid",
|
|
5077
|
-
borderWidth: "1px",
|
|
5078
|
-
style: { overflow: "hidden" },
|
|
5079
|
-
children: [
|
|
5080
|
-
/* @__PURE__ */ jsxRuntime.jsx(Accent, {}),
|
|
5081
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5082
|
-
designSystem.Box,
|
|
5901
|
+
/* @__PURE__ */ jsxRuntime.jsx(TH, { children: "Name" }),
|
|
5902
|
+
/* @__PURE__ */ jsxRuntime.jsx(TH, { children: "Email" }),
|
|
5903
|
+
/* @__PURE__ */ jsxRuntime.jsx(TH, { children: "Status" }),
|
|
5904
|
+
/* @__PURE__ */ jsxRuntime.jsx(TH, { children: "Created" }),
|
|
5905
|
+
/* @__PURE__ */ jsxRuntime.jsx(THActions, {})
|
|
5906
|
+
] }) }),
|
|
5907
|
+
/* @__PURE__ */ jsxRuntime.jsx("tbody", { children: users.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
5908
|
+
TD,
|
|
5083
5909
|
{
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5910
|
+
colSpan: 6,
|
|
5911
|
+
style: {
|
|
5912
|
+
textAlign: "center",
|
|
5913
|
+
padding: "40px",
|
|
5914
|
+
color: "#8e8ea9"
|
|
5915
|
+
},
|
|
5916
|
+
"data-testid": "users-empty",
|
|
5917
|
+
children: search ? `No users matching "${search}"` : "No users found"
|
|
5918
|
+
}
|
|
5919
|
+
) }) : users.map((user, i) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5920
|
+
TR,
|
|
5921
|
+
{
|
|
5922
|
+
$selected: selected.has(user.id),
|
|
5923
|
+
$i: i,
|
|
5924
|
+
"data-testid": "user-row",
|
|
5088
5925
|
children: [
|
|
5089
|
-
/* @__PURE__ */ jsxRuntime.
|
|
5090
|
-
designSystem.
|
|
5926
|
+
(rbac.allowedActions.canDelete || rbac.allowedActions.canUpdate) && /* @__PURE__ */ jsxRuntime.jsx(TDCheck, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
5927
|
+
designSystem.Checkbox,
|
|
5091
5928
|
{
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
children: [
|
|
5096
|
-
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
|
|
5097
|
-
/* @__PURE__ */ jsxRuntime.jsx(BrandIcon, { children: "BA" }),
|
|
5098
|
-
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
|
|
5099
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "beta", textColor: "neutral800", children: "Better Auth" }),
|
|
5100
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: "2px", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: "Authentication Dashboard" }) })
|
|
5101
|
-
] })
|
|
5102
|
-
] }),
|
|
5103
|
-
/* @__PURE__ */ jsxRuntime.jsx(PathTag, { children: config.basePath })
|
|
5104
|
-
]
|
|
5929
|
+
checked: selected.has(user.id),
|
|
5930
|
+
onCheckedChange: () => toggleSelect(user.id),
|
|
5931
|
+
"aria-label": `Select ${user.name}`
|
|
5105
5932
|
}
|
|
5106
|
-
),
|
|
5107
|
-
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.
|
|
5108
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5109
|
-
|
|
5110
|
-
orgEnabled && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5111
|
-
designSystem.Tabs.Trigger,
|
|
5933
|
+
) }),
|
|
5934
|
+
/* @__PURE__ */ jsxRuntime.jsx(TD, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, children: [
|
|
5935
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5936
|
+
Avatar,
|
|
5112
5937
|
{
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5938
|
+
name: user.name ?? "",
|
|
5939
|
+
src: user.image,
|
|
5940
|
+
size: 28
|
|
5116
5941
|
}
|
|
5117
5942
|
),
|
|
5118
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5119
|
-
] })
|
|
5943
|
+
/* @__PURE__ */ jsxRuntime.jsx(UserName, { children: user.name })
|
|
5944
|
+
] }) }),
|
|
5945
|
+
/* @__PURE__ */ jsxRuntime.jsx(TD, { children: /* @__PURE__ */ jsxRuntime.jsx(MonoText, { children: user.email }) }),
|
|
5946
|
+
/* @__PURE__ */ jsxRuntime.jsx(TD, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, children: [
|
|
5947
|
+
user.emailVerified ? /* @__PURE__ */ jsxRuntime.jsx(StatusChip, { $variant: "verified", children: "Verified" }) : /* @__PURE__ */ jsxRuntime.jsx(StatusChip, { $variant: "unverified", children: "Unverified" }),
|
|
5948
|
+
user.banned && /* @__PURE__ */ jsxRuntime.jsx(StatusChip, { $variant: "banned", children: "Banned" })
|
|
5949
|
+
] }) }),
|
|
5950
|
+
/* @__PURE__ */ jsxRuntime.jsx(TD, { children: /* @__PURE__ */ jsxRuntime.jsx(DateText, { children: new Date(user.createdAt).toLocaleDateString() }) }),
|
|
5951
|
+
/* @__PURE__ */ jsxRuntime.jsx(TDActions, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, justifyContent: "flex-end", children: [
|
|
5952
|
+
rbac.allowedActions.canUpdate && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5953
|
+
designSystem.IconButton,
|
|
5954
|
+
{
|
|
5955
|
+
label: "Edit user",
|
|
5956
|
+
onClick: () => setDetailUserId(user.id),
|
|
5957
|
+
"data-testid": "edit-user-btn",
|
|
5958
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(icons.Pencil, {})
|
|
5959
|
+
}
|
|
5960
|
+
),
|
|
5961
|
+
rbac.allowedActions.canDelete && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5962
|
+
designSystem.IconButton,
|
|
5963
|
+
{
|
|
5964
|
+
label: "Delete user",
|
|
5965
|
+
onClick: () => setConfirmDelete(user.id),
|
|
5966
|
+
"data-testid": "delete-user-btn",
|
|
5967
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
|
|
5968
|
+
}
|
|
5969
|
+
)
|
|
5970
|
+
] }) })
|
|
5120
5971
|
]
|
|
5972
|
+
},
|
|
5973
|
+
user.id
|
|
5974
|
+
)) })
|
|
5975
|
+
] }) }),
|
|
5976
|
+
pageCount > 1 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
|
|
5977
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5978
|
+
designSystem.Button,
|
|
5979
|
+
{
|
|
5980
|
+
variant: "tertiary",
|
|
5981
|
+
size: "S",
|
|
5982
|
+
disabled: page === 1,
|
|
5983
|
+
onClick: () => setPage((p) => p - 1),
|
|
5984
|
+
children: "Previous"
|
|
5985
|
+
}
|
|
5986
|
+
),
|
|
5987
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5988
|
+
designSystem.Button,
|
|
5989
|
+
{
|
|
5990
|
+
variant: "tertiary",
|
|
5991
|
+
size: "S",
|
|
5992
|
+
disabled: page >= pageCount,
|
|
5993
|
+
onClick: () => setPage((p) => p + 1),
|
|
5994
|
+
children: "Next"
|
|
5121
5995
|
}
|
|
5122
5996
|
)
|
|
5123
|
-
]
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5997
|
+
] }) }),
|
|
5998
|
+
rbac.allowedActions.canCreate && showCreate && /* @__PURE__ */ jsxRuntime.jsx(CreateUserDialog, { onClose: () => setShowCreate(false) }),
|
|
5999
|
+
rbac.allowedActions.canUpdate && detailUserId && /* @__PURE__ */ jsxRuntime.jsx(
|
|
6000
|
+
UserDetailDrawer,
|
|
6001
|
+
{
|
|
6002
|
+
userId: detailUserId,
|
|
6003
|
+
banEnabled,
|
|
6004
|
+
emailVerificationEnabled,
|
|
6005
|
+
twoFactorEnabled,
|
|
6006
|
+
onClose: () => setDetailUserId(null)
|
|
6007
|
+
}
|
|
6008
|
+
),
|
|
6009
|
+
rbac.allowedActions.canDelete && confirmDelete && /* @__PURE__ */ jsxRuntime.jsx(
|
|
6010
|
+
ConfirmDialog,
|
|
6011
|
+
{
|
|
6012
|
+
title: "Delete user",
|
|
6013
|
+
message: "Are you sure you want to delete this user? This action cannot be undone.",
|
|
6014
|
+
confirmLabel: "Delete",
|
|
6015
|
+
loading: deleteMutation.isLoading,
|
|
6016
|
+
onConfirm: () => deleteMutation.mutate(confirmDelete),
|
|
6017
|
+
onCancel: () => setConfirmDelete(null)
|
|
6018
|
+
}
|
|
6019
|
+
),
|
|
6020
|
+
rbac.allowedActions.canDelete && confirmDeleteMany && /* @__PURE__ */ jsxRuntime.jsx(
|
|
6021
|
+
ConfirmDialog,
|
|
6022
|
+
{
|
|
6023
|
+
title: `Delete ${selected.size} user${selected.size !== 1 ? "s" : ""}`,
|
|
6024
|
+
message: `Are you sure you want to delete ${selected.size} user${selected.size !== 1 ? "s" : ""}? This action cannot be undone.`,
|
|
6025
|
+
confirmLabel: "Delete all",
|
|
6026
|
+
loading: deleteManyMutation.isLoading,
|
|
6027
|
+
onConfirm: () => deleteManyMutation.mutate([...selected]),
|
|
6028
|
+
onCancel: () => setConfirmDeleteMany(false)
|
|
6029
|
+
}
|
|
6030
|
+
),
|
|
6031
|
+
rbac.allowedActions.canUpdate && confirmBanMany && /* @__PURE__ */ jsxRuntime.jsx(
|
|
6032
|
+
ConfirmDialog,
|
|
6033
|
+
{
|
|
6034
|
+
title: `Ban ${selected.size} user${selected.size !== 1 ? "s" : ""}`,
|
|
6035
|
+
message: `Are you sure you want to ban ${selected.size} user${selected.size !== 1 ? "s" : ""}? They will be prevented from signing in.`,
|
|
6036
|
+
confirmLabel: "Ban all",
|
|
6037
|
+
variant: "danger",
|
|
6038
|
+
loading: banManyMutation.isLoading,
|
|
6039
|
+
onConfirm: () => banManyMutation.mutate([...selected]),
|
|
6040
|
+
onCancel: () => setConfirmBanMany(false)
|
|
6041
|
+
}
|
|
6042
|
+
)
|
|
6043
|
+
] })
|
|
6044
|
+
}
|
|
6045
|
+
);
|
|
6046
|
+
}
|
|
6047
|
+
function UsersRoute() {
|
|
6048
|
+
const { config } = reactRouterDom.useOutletContext();
|
|
6049
|
+
return /* @__PURE__ */ jsxRuntime.jsx(UsersPage, { config });
|
|
6050
|
+
}
|
|
6051
|
+
function OrganizationsRoute() {
|
|
6052
|
+
const { teamsEnabled } = reactRouterDom.useOutletContext();
|
|
6053
|
+
return /* @__PURE__ */ jsxRuntime.jsx(OrganizationsPage, { teamsEnabled });
|
|
5131
6054
|
}
|
|
5132
6055
|
function Root() {
|
|
5133
6056
|
const queryClient = react.useMemo(
|
|
@@ -5142,7 +6065,12 @@ function Root() {
|
|
|
5142
6065
|
}),
|
|
5143
6066
|
[]
|
|
5144
6067
|
);
|
|
5145
|
-
return /* @__PURE__ */ jsxRuntime.jsx(reactQuery.QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
6068
|
+
return /* @__PURE__ */ jsxRuntime.jsx(reactQuery.QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Routes, { children: /* @__PURE__ */ jsxRuntime.jsxs(reactRouterDom.Route, { path: "/", element: /* @__PURE__ */ jsxRuntime.jsx(ProtectedApp, {}), children: [
|
|
6069
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { index: true, element: /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Navigate, { to: "overview", replace: true }) }),
|
|
6070
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { path: "overview", element: /* @__PURE__ */ jsxRuntime.jsx(OverviewPage, {}) }),
|
|
6071
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { path: "users", element: /* @__PURE__ */ jsxRuntime.jsx(UsersRoute, {}) }),
|
|
6072
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { path: "organizations", element: /* @__PURE__ */ jsxRuntime.jsx(OrganizationsRoute, {}) })
|
|
6073
|
+
] }) }) });
|
|
5146
6074
|
}
|
|
5147
6075
|
exports.Root = Root;
|
|
5148
6076
|
exports.default = Root;
|