@strapi-community/plugin-better-auth-dashboard 1.0.0-alpha.3 → 1.0.0-alpha.4

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.
@@ -1,13 +1,13 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
  import { useEffect, useState, useRef, useMemo } from "react";
3
3
  import { useQuery, useQueryClient, useMutation, QueryClient, QueryClientProvider } from "react-query";
4
- import { Modal, Typography, Button, Portal, Flex, IconButton, Box, Field, TextInput, Combobox, ComboboxOption, Checkbox, SingleSelect, SingleSelectOption, NumberInput, JSONInput, Textarea, Alert, Tabs, Grid, SearchForm, Searchbar, Loader, Badge, Pagination } from "@strapi/design-system";
4
+ import { Modal, Typography, Button, Portal, Flex, IconButton, Box, Field, TextInput, Combobox, ComboboxOption, Checkbox, SingleSelect, SingleSelectOption, NumberInput, JSONInput, Textarea, Alert, Tabs, Grid, SearchForm, Searchbar, Loader, Badge } from "@strapi/design-system";
5
5
  import styled, { keyframes } from "styled-components";
6
6
  import { dashClient } from "@better-auth/infra/client";
7
7
  import { createAuthClient, InferPlugin } from "better-auth/client";
8
8
  import { Cross, Images, Trash, Plus, Pencil } from "@strapi/icons";
9
9
  import { useNotification, useFetchClient } from "@strapi/strapi/admin";
10
- import { g as getMediaLibraryComponent, P as PluginIcon } from "./index-BpruO4vo.mjs";
10
+ import { g as getMediaLibraryComponent, P as PluginIcon } from "./index-QVlTR8eL.mjs";
11
11
  const dashPathMethods = () => ({
12
12
  id: "dash-path-methods",
13
13
  pathMethods: {
@@ -1038,6 +1038,7 @@ function OrganizationDetail({
1038
1038
  const schemaQuery = useModelSchema("organization");
1039
1039
  const qc = useQueryClient();
1040
1040
  const { toggleNotification } = useNotification();
1041
+ const { get, put } = useFetchClient();
1041
1042
  const orgQuery = useQuery({
1042
1043
  queryKey: ["dash-org", organizationId],
1043
1044
  queryFn: async () => {
@@ -1084,6 +1085,16 @@ function OrganizationDetail({
1084
1085
  return result.data ?? [];
1085
1086
  }
1086
1087
  });
1088
+ const strapiOrgQuery = useQuery({
1089
+ queryKey: ["dash-strapi-org", organizationId],
1090
+ enabled: !!orgQuery.data,
1091
+ queryFn: async () => {
1092
+ const { data } = await get(
1093
+ `/better-auth-dashboard/db?uid=plugin::better-auth.organization&filters[id][$eq]=${organizationId}&pagination[pageSize]=1`
1094
+ );
1095
+ return data.results?.[0] ?? null;
1096
+ }
1097
+ });
1087
1098
  const [activeTab, setActiveTab] = useState("details");
1088
1099
  const [editName, setEditName] = useState(void 0);
1089
1100
  const [editSlug, setEditSlug] = useState(void 0);
@@ -1113,13 +1124,13 @@ function OrganizationDetail({
1113
1124
  if (editName !== void 0) body.name = editName;
1114
1125
  if (editSlug !== void 0) body.slug = editSlug;
1115
1126
  if (editLogo !== void 0) body.logo = editLogo;
1116
- const result = await client.dash.organization.update(
1117
- body,
1118
- withContext({ organizationId })
1127
+ const documentId = strapiOrgQuery.data?.documentId;
1128
+ if (!documentId)
1129
+ throw new Error("Could not resolve documentId for organization");
1130
+ await put(
1131
+ `/better-auth-dashboard/db/${documentId}?uid=plugin::better-auth.organization`,
1132
+ body
1119
1133
  );
1120
- if (result.error)
1121
- throw new Error(result.error.message ?? "Update failed");
1122
- return result.data;
1123
1134
  },
1124
1135
  onSuccess: () => {
1125
1136
  qc.invalidateQueries({ queryKey: ["dash-org", organizationId] });
@@ -2132,6 +2143,7 @@ function OrgAvatar({ name, logo }) {
2132
2143
  }
2133
2144
  function OrganizationsPage({ teamsEnabled }) {
2134
2145
  const qc = useQueryClient();
2146
+ const { toggleNotification } = useNotification();
2135
2147
  const [page, setPage] = useState(1);
2136
2148
  const [search, setSearch] = useState("");
2137
2149
  const [searchInput, setSearchInput] = useState("");
@@ -2171,6 +2183,13 @@ function OrganizationsPage({ teamsEnabled }) {
2171
2183
  onSuccess: () => {
2172
2184
  setConfirmDelete(null);
2173
2185
  qc.invalidateQueries({ queryKey: ["dash-organizations"] });
2186
+ toggleNotification({ type: "success", message: "Organization deleted" });
2187
+ },
2188
+ onError: (err) => {
2189
+ toggleNotification({
2190
+ type: "danger",
2191
+ message: err.message ?? "Failed to delete organization"
2192
+ });
2174
2193
  }
2175
2194
  });
2176
2195
  const deleteManyMutation = useMutation({
@@ -2183,10 +2202,20 @@ function OrganizationsPage({ teamsEnabled }) {
2183
2202
  throw new Error(result.error.message ?? "Delete failed");
2184
2203
  return result.data;
2185
2204
  },
2186
- onSuccess: () => {
2205
+ onSuccess: (_data, organizationIds) => {
2187
2206
  setConfirmDeleteMany(false);
2188
2207
  setSelected(/* @__PURE__ */ new Set());
2189
2208
  qc.invalidateQueries({ queryKey: ["dash-organizations"] });
2209
+ toggleNotification({
2210
+ type: "success",
2211
+ message: `${organizationIds.length} organization${organizationIds.length !== 1 ? "s" : ""} deleted`
2212
+ });
2213
+ },
2214
+ onError: (err) => {
2215
+ toggleNotification({
2216
+ type: "danger",
2217
+ message: err.message ?? "Failed to delete organizations"
2218
+ });
2190
2219
  }
2191
2220
  });
2192
2221
  const orgs = orgsQuery.data && "organizations" in orgsQuery.data ? orgsQuery.data.organizations : [];
@@ -3269,6 +3298,42 @@ function OverviewPage() {
3269
3298
  return r.data;
3270
3299
  }
3271
3300
  });
3301
+ const sessionsRaw = sessionsQuery.data ?? [];
3302
+ const activeUserIds = [];
3303
+ const lastActiveByUserId = /* @__PURE__ */ new Map();
3304
+ for (const s of sessionsRaw) {
3305
+ if (!lastActiveByUserId.has(s.userId)) {
3306
+ lastActiveByUserId.set(s.userId, s.createdAt);
3307
+ activeUserIds.push(s.userId);
3308
+ }
3309
+ }
3310
+ const activeUsersQuery = useQuery({
3311
+ queryKey: ["dash-active-users", activeUserIds],
3312
+ queryFn: async () => {
3313
+ if (activeUserIds.length === 0) return [];
3314
+ const params = new URLSearchParams({ uid: "plugin::better-auth.user" });
3315
+ for (let i = 0; i < activeUserIds.length; i++) {
3316
+ params.set(`filters[id][$in][${i}]`, activeUserIds[i]);
3317
+ }
3318
+ params.set("pagination[pageSize]", "12");
3319
+ const { data } = await get(
3320
+ `/better-auth-dashboard/db?${params}`
3321
+ );
3322
+ return data.results ?? [];
3323
+ },
3324
+ enabled: feedMode === "active" && activeUserIds.length > 0
3325
+ });
3326
+ const chartRef = useRef(null);
3327
+ const [chartHeight, setChartHeight] = useState(0);
3328
+ useEffect(() => {
3329
+ const el = chartRef.current;
3330
+ if (!el) return;
3331
+ const ro = new ResizeObserver((entries) => {
3332
+ for (const entry of entries) setChartHeight(entry.contentRect.height);
3333
+ });
3334
+ ro.observe(el);
3335
+ return () => ro.disconnect();
3336
+ }, []);
3272
3337
  if (statsQuery.isLoading) {
3273
3338
  return /* @__PURE__ */ jsx(
3274
3339
  Flex,
@@ -3286,8 +3351,12 @@ function OverviewPage() {
3286
3351
  const graphData = graphQuery.data?.data ?? [];
3287
3352
  const rtnData = retentionQuery.data?.data ?? [];
3288
3353
  const users = usersQuery.data?.users ?? [];
3289
- const sessions = sessionsQuery.data ?? [];
3290
3354
  const orgCount = orgsQuery.data?.total ?? 0;
3355
+ const activeUserMap = /* @__PURE__ */ new Map();
3356
+ for (const u of activeUsersQuery.data ?? []) {
3357
+ activeUserMap.set(String(u.id), u);
3358
+ }
3359
+ const sortedActiveUsers = activeUserIds.map((uid) => activeUserMap.get(uid)).filter((u) => u !== void 0);
3291
3360
  const totalSpark = graphData.map((d) => d.totalUsers);
3292
3361
  const newSpark = graphData.map((d) => d.newUsers);
3293
3362
  const activeSpark = graphData.map((d) => d.activeUsers);
@@ -3391,7 +3460,7 @@ function OverviewPage() {
3391
3460
  /* @__PURE__ */ jsx(DivLine, {})
3392
3461
  ] }),
3393
3462
  /* @__PURE__ */ jsxs(TwoPanel, { children: [
3394
- /* @__PURE__ */ jsxs(ChartCard, { $delay: 5, children: [
3463
+ /* @__PURE__ */ jsxs(ChartCard, { ref: chartRef, $delay: 5, children: [
3395
3464
  /* @__PURE__ */ jsxs(ChartHeader, { children: [
3396
3465
  /* @__PURE__ */ jsx(ChartTitle, { children: "User Growth" }),
3397
3466
  /* @__PURE__ */ jsx(SeriesRow, { children: ALL_SERIES.map((s) => /* @__PURE__ */ jsxs(
@@ -3427,57 +3496,57 @@ function OverviewPage() {
3427
3496
  }
3428
3497
  )
3429
3498
  ] }),
3430
- /* @__PURE__ */ jsxs(FeedCard, { $delay: 6, children: [
3431
- /* @__PURE__ */ jsxs(FeedHead, { children: [
3432
- /* @__PURE__ */ jsx("span", { children: feedMode === "signups" ? "Recent Sign-ups" : "Recent Active" }),
3433
- /* @__PURE__ */ jsxs(
3434
- FeedSelect,
3435
- {
3436
- value: feedMode,
3437
- onChange: (e) => setFeedMode(e.target.value),
3438
- children: [
3439
- /* @__PURE__ */ jsx("option", { value: "signups", children: "Recent Sign-ups" }),
3440
- /* @__PURE__ */ jsx("option", { value: "active", children: "Recent Active" })
3441
- ]
3442
- }
3443
- )
3444
- ] }),
3445
- /* @__PURE__ */ jsx(FeedScroll, { children: feedMode === "signups" ? usersQuery.isLoading ? /* @__PURE__ */ jsx(Flex, { justifyContent: "center", padding: 4, children: /* @__PURE__ */ jsx(Loader, { children: "Loading…" }) }) : users.length === 0 ? /* @__PURE__ */ jsx(Empty, { children: "No users yet" }) : users.map((u) => /* @__PURE__ */ jsxs(FeedItem, { children: [
3446
- /* @__PURE__ */ jsxs(FeedTop, { children: [
3447
- /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 1, style: { minWidth: 0 }, children: [
3448
- /* @__PURE__ */ jsx(Avatar, { name: u.name, src: u.image, size: 20 }),
3449
- /* @__PURE__ */ jsx(FeedName, { title: u.name, children: u.name })
3499
+ /* @__PURE__ */ jsxs(
3500
+ FeedCard,
3501
+ {
3502
+ $delay: 6,
3503
+ style: chartHeight > 0 ? { height: chartHeight } : void 0,
3504
+ children: [
3505
+ /* @__PURE__ */ jsxs(FeedHead, { children: [
3506
+ /* @__PURE__ */ jsx("span", { children: feedMode === "signups" ? "Recent Sign-ups" : "Recently Active" }),
3507
+ /* @__PURE__ */ jsxs(
3508
+ FeedSelect,
3509
+ {
3510
+ value: feedMode,
3511
+ onChange: (e) => setFeedMode(e.target.value),
3512
+ children: [
3513
+ /* @__PURE__ */ jsx("option", { value: "signups", children: "Recent Sign-ups" }),
3514
+ /* @__PURE__ */ jsx("option", { value: "active", children: "Recently Active" })
3515
+ ]
3516
+ }
3517
+ )
3450
3518
  ] }),
3451
- /* @__PURE__ */ jsx(FeedMeta, { children: relTime(u.createdAt) })
3452
- ] }),
3453
- /* @__PURE__ */ jsx(FeedEmail, { title: u.email, children: u.email })
3454
- ] }, u.id)) : sessionsQuery.isLoading ? /* @__PURE__ */ jsx(Flex, { justifyContent: "center", padding: 4, children: /* @__PURE__ */ jsx(Loader, { children: "Loading…" }) }) : sessions.length === 0 ? /* @__PURE__ */ jsx(Empty, { children: "No sessions yet" }) : sessions.map((s) => /* @__PURE__ */ jsxs(FeedItem, { children: [
3455
- /* @__PURE__ */ jsxs(FeedTop, { children: [
3456
- /* @__PURE__ */ jsx(
3457
- FeedName,
3458
- {
3459
- title: s.userAgent ?? void 0,
3460
- style: { fontFamily: T.mono, fontSize: 10 },
3461
- children: s.ipAddress ?? "—"
3462
- }
3463
- ),
3464
- /* @__PURE__ */ jsx(FeedMeta, { children: relTime(s.createdAt) })
3465
- ] }),
3466
- /* @__PURE__ */ jsx(
3467
- FeedEmail,
3468
- {
3469
- title: s.userAgent ?? void 0,
3470
- style: {
3471
- fontSize: 9,
3472
- overflow: "hidden",
3473
- textOverflow: "ellipsis",
3474
- whiteSpace: "nowrap"
3475
- },
3476
- children: s.userAgent ?? "Unknown agent"
3477
- }
3478
- )
3479
- ] }, s.documentId)) })
3480
- ] })
3519
+ /* @__PURE__ */ jsx(FeedScroll, { children: feedMode === "signups" ? usersQuery.isLoading ? /* @__PURE__ */ jsx(Flex, { justifyContent: "center", padding: 4, children: /* @__PURE__ */ jsx(Loader, { children: "Loading…" }) }) : users.length === 0 ? /* @__PURE__ */ jsx(Empty, { children: "No users yet" }) : users.map((u) => /* @__PURE__ */ jsxs(FeedItem, { children: [
3520
+ /* @__PURE__ */ jsxs(FeedTop, { children: [
3521
+ /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 1, style: { minWidth: 0 }, children: [
3522
+ /* @__PURE__ */ jsx(Avatar, { name: u.name, src: u.image, size: 20 }),
3523
+ /* @__PURE__ */ jsx(FeedName, { title: u.name, children: u.name })
3524
+ ] }),
3525
+ /* @__PURE__ */ jsx(FeedMeta, { children: relTime(u.createdAt) })
3526
+ ] }),
3527
+ /* @__PURE__ */ jsx(FeedEmail, { title: u.email, children: u.email })
3528
+ ] }, u.id)) : activeUsersQuery.isLoading || sessionsQuery.isLoading ? /* @__PURE__ */ jsx(Flex, { justifyContent: "center", padding: 4, children: /* @__PURE__ */ jsx(Loader, { children: "Loading…" }) }) : sortedActiveUsers.length === 0 ? /* @__PURE__ */ jsx(Empty, { children: "No recent activity" }) : sortedActiveUsers.map((u) => /* @__PURE__ */ jsxs(FeedItem, { children: [
3529
+ /* @__PURE__ */ jsxs(FeedTop, { children: [
3530
+ /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 1, style: { minWidth: 0 }, children: [
3531
+ /* @__PURE__ */ jsx(
3532
+ Avatar,
3533
+ {
3534
+ name: u.name,
3535
+ src: u.image ?? void 0,
3536
+ size: 20
3537
+ }
3538
+ ),
3539
+ /* @__PURE__ */ jsx(FeedName, { title: u.name, children: u.name })
3540
+ ] }),
3541
+ /* @__PURE__ */ jsx(FeedMeta, { children: relTime(
3542
+ lastActiveByUserId.get(String(u.id)) ?? u.createdAt
3543
+ ) })
3544
+ ] }),
3545
+ /* @__PURE__ */ jsx(FeedEmail, { title: u.email, children: u.email })
3546
+ ] }, u.documentId)) })
3547
+ ]
3548
+ }
3549
+ )
3481
3550
  ] }),
3482
3551
  /* @__PURE__ */ jsxs(SectionDivider, { children: [
3483
3552
  /* @__PURE__ */ jsx(DivLabel, { children: "Retention & Activity" }),
@@ -3853,9 +3922,10 @@ const SessionCard = styled.div`
3853
3922
  align-items: flex-start;
3854
3923
  justify-content: space-between;
3855
3924
  gap: 12px;
3856
- padding: 10px 0;
3857
- border-bottom: 1px solid #f5f5f9;
3858
- &:last-child { border-bottom: none; }
3925
+ padding: 10px 14px;
3926
+ background: white;
3927
+ border: 1px solid #eaeaef;
3928
+ border-radius: 8px;
3859
3929
  `;
3860
3930
  const SessionMeta = styled.div`
3861
3931
  display: flex;
@@ -3961,6 +4031,7 @@ function UserDetailDrawer({
3961
4031
  const [banExpiresDays, setBanExpiresDays] = useState("");
3962
4032
  const [confirmRevokeAll, setConfirmRevokeAll] = useState(false);
3963
4033
  const [confirmRevokeSessionId, setConfirmRevokeSessionId] = useState(null);
4034
+ const [confirmBan, setConfirmBan] = useState(false);
3964
4035
  const [confirmUnban, setConfirmUnban] = useState(false);
3965
4036
  const [confirmUnlinkAccountId, setConfirmUnlinkAccountId] = useState(null);
3966
4037
  const [confirmDisable2FA, setConfirmDisable2FA] = useState(false);
@@ -3984,33 +4055,18 @@ function UserDetailDrawer({
3984
4055
  };
3985
4056
  const updateMutation = useMutation({
3986
4057
  mutationFn: async () => {
3987
- const baBody = {};
3988
- if (editName !== void 0) baBody.name = editName;
3989
- if (editEmail !== void 0) baBody.email = editEmail;
4058
+ const body = { ...editExtra };
4059
+ if (editName !== void 0) body.name = editName;
4060
+ if (editEmail !== void 0) body.email = editEmail;
3990
4061
  if (editEmailVerified !== void 0)
3991
- baBody.emailVerified = editEmailVerified;
3992
- if (editImage !== void 0) baBody.image = editImage;
3993
- const ops = [];
3994
- if (Object.keys(baBody).length > 0) {
3995
- ops.push(
3996
- client.dash.updateUser(baBody, withContext({ userId })).then((result) => {
3997
- if (result.error)
3998
- throw new Error(result.error.message ?? "Update failed");
3999
- })
4000
- );
4001
- }
4002
- if (Object.keys(editExtra).length > 0) {
4003
- const documentId = strapiUserQuery.data?.documentId;
4004
- if (!documentId)
4005
- throw new Error("Could not resolve documentId for user");
4006
- ops.push(
4007
- put(
4008
- `/better-auth-dashboard/db/${documentId}?uid=plugin::better-auth.user`,
4009
- editExtra
4010
- )
4011
- );
4012
- }
4013
- await Promise.all(ops);
4062
+ body.emailVerified = editEmailVerified;
4063
+ if (editImage !== void 0) body.image = editImage;
4064
+ const documentId = strapiUserQuery.data?.documentId;
4065
+ if (!documentId) throw new Error("Could not resolve documentId for user");
4066
+ await put(
4067
+ `/better-auth-dashboard/db/${documentId}?uid=plugin::better-auth.user`,
4068
+ body
4069
+ );
4014
4070
  },
4015
4071
  onSuccess: () => {
4016
4072
  qc.invalidateQueries({ queryKey: ["dash-user", userId] });
@@ -4092,6 +4148,7 @@ function UserDetailDrawer({
4092
4148
  return result.data;
4093
4149
  },
4094
4150
  onSuccess: () => {
4151
+ setConfirmBan(false);
4095
4152
  qc.invalidateQueries({ queryKey: ["dash-user", userId] });
4096
4153
  qc.invalidateQueries({ queryKey: ["dash-users"] });
4097
4154
  setBanReason("");
@@ -4707,7 +4764,8 @@ function UserDetailDrawer({
4707
4764
  ] })
4708
4765
  ] })
4709
4766
  ] }) }),
4710
- banEnabled && /* @__PURE__ */ jsx(Tabs.Content, { value: "ban", children: /* @__PURE__ */ jsx(Flex, { direction: "column", gap: 4, paddingTop: 6, children: user?.banned ? /* @__PURE__ */ jsxs(Fragment, { children: [
4767
+ banEnabled && /* @__PURE__ */ jsx(Tabs.Content, { value: "ban", children: /* @__PURE__ */ jsx(Flex, { direction: "column", gap: 5, paddingTop: 6, children: user?.banned ? /* @__PURE__ */ jsxs(FormSection, { children: [
4768
+ /* @__PURE__ */ jsx(SectionLabel, { children: "Ban status" }),
4711
4769
  /* @__PURE__ */ jsxs(WarnCard, { children: [
4712
4770
  /* @__PURE__ */ jsx(
4713
4771
  Typography,
@@ -4722,7 +4780,7 @@ function UserDetailDrawer({
4722
4780
  "Reason: ",
4723
4781
  user.banReason
4724
4782
  ] }),
4725
- user.banExpires && /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "danger600", children: [
4783
+ user.banExpires ? /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "danger600", children: [
4726
4784
  "Expires:",
4727
4785
  " ",
4728
4786
  new Date(user.banExpires).toLocaleDateString(
@@ -4734,8 +4792,7 @@ function UserDetailDrawer({
4734
4792
  day: "numeric"
4735
4793
  }
4736
4794
  )
4737
- ] }),
4738
- !user.banExpires && /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "danger600", children: "Duration: Permanent" })
4795
+ ] }) : /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "danger600", children: "Duration: Permanent" })
4739
4796
  ] }),
4740
4797
  /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(
4741
4798
  Button,
@@ -4745,27 +4802,28 @@ function UserDetailDrawer({
4745
4802
  children: "Lift ban"
4746
4803
  }
4747
4804
  ) })
4748
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
4805
+ ] }) : /* @__PURE__ */ jsxs(FormSection, { children: [
4806
+ /* @__PURE__ */ jsx(SectionLabel, { children: "Apply ban" }),
4807
+ /* @__PURE__ */ jsxs(
4808
+ Field.Root,
4809
+ {
4810
+ hint: "Optional — will be shown to the user",
4811
+ style: { width: "100%" },
4812
+ children: [
4813
+ /* @__PURE__ */ jsx(Field.Label, { children: "Reason" }),
4814
+ /* @__PURE__ */ jsx(
4815
+ TextInput,
4816
+ {
4817
+ value: banReason,
4818
+ onChange: (e) => setBanReason(e.target.value),
4819
+ placeholder: "e.g. Violated terms of service"
4820
+ }
4821
+ ),
4822
+ /* @__PURE__ */ jsx(Field.Hint, {})
4823
+ ]
4824
+ }
4825
+ ),
4749
4826
  /* @__PURE__ */ jsxs(Grid.Root, { gap: 4, children: [
4750
- /* @__PURE__ */ jsx(Grid.Item, { col: 12, children: /* @__PURE__ */ jsxs(
4751
- Field.Root,
4752
- {
4753
- hint: "Optional — will be shown to the user",
4754
- style: { width: "100%" },
4755
- children: [
4756
- /* @__PURE__ */ jsx(Field.Label, { children: "Reason" }),
4757
- /* @__PURE__ */ jsx(
4758
- TextInput,
4759
- {
4760
- value: banReason,
4761
- onChange: (e) => setBanReason(e.target.value),
4762
- placeholder: "e.g. Violated terms of service"
4763
- }
4764
- ),
4765
- /* @__PURE__ */ jsx(Field.Hint, {})
4766
- ]
4767
- }
4768
- ) }),
4769
4827
  /* @__PURE__ */ jsx(Grid.Item, { col: 6, children: /* @__PURE__ */ jsxs(
4770
4828
  Field.Root,
4771
4829
  {
@@ -4786,24 +4844,13 @@ function UserDetailDrawer({
4786
4844
  ]
4787
4845
  }
4788
4846
  ) }),
4789
- /* @__PURE__ */ jsx(Grid.Item, { col: 6, children: banExpiryPreview ? /* @__PURE__ */ jsx(
4790
- Flex,
4791
- {
4792
- direction: "column",
4793
- justifyContent: "flex-end",
4794
- style: { height: "100%", paddingBottom: 4 },
4795
- children: /* @__PURE__ */ jsxs(PreviewPill, { children: [
4796
- "⏱ Expires ",
4797
- banExpiryPreview
4798
- ] })
4799
- }
4800
- ) : /* @__PURE__ */ jsx(
4847
+ /* @__PURE__ */ jsx(Grid.Item, { col: 6, children: /* @__PURE__ */ jsx(
4801
4848
  Flex,
4802
4849
  {
4803
4850
  direction: "column",
4804
4851
  justifyContent: "flex-end",
4805
4852
  style: { height: "100%", paddingBottom: 4 },
4806
- children: /* @__PURE__ */ jsx(PreviewPill, { children: "♾ Permanent ban" })
4853
+ children: /* @__PURE__ */ jsx(PreviewPill, { children: banExpiryPreview ? `⏱ Expires ${banExpiryPreview}` : "♾ Permanent ban" })
4807
4854
  }
4808
4855
  ) })
4809
4856
  ] }),
@@ -4811,8 +4858,7 @@ function UserDetailDrawer({
4811
4858
  Button,
4812
4859
  {
4813
4860
  variant: "danger",
4814
- loading: banMutation.isLoading,
4815
- onClick: () => banMutation.mutate(),
4861
+ onClick: () => setConfirmBan(true),
4816
4862
  children: "Ban user"
4817
4863
  }
4818
4864
  ) })
@@ -4839,19 +4885,27 @@ function UserDetailDrawer({
4839
4885
  ] }),
4840
4886
  /* @__PURE__ */ jsxs(FormSection, { children: [
4841
4887
  /* @__PURE__ */ jsx(SectionLabel, { children: "Active sessions" }),
4842
- sessionsQuery.isLoading ? /* @__PURE__ */ jsx(Flex, { justifyContent: "center", padding: 4, children: /* @__PURE__ */ jsx(Loader, { children: "Loading sessions…" }) }) : (sessionsQuery.data ?? []).length === 0 ? /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: "No active sessions." }) : /* @__PURE__ */ jsx(Flex, { direction: "column", gap: 0, alignItems: "stretch", children: (sessionsQuery.data ?? []).map((session) => /* @__PURE__ */ jsxs(SessionCard, { children: [
4888
+ sessionsQuery.isLoading ? /* @__PURE__ */ jsx(Flex, { justifyContent: "center", padding: 4, children: /* @__PURE__ */ jsx(Loader, { children: "Loading sessions…" }) }) : (sessionsQuery.data ?? []).length === 0 ? /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: "No active sessions." }) : /* @__PURE__ */ jsx(Flex, { direction: "column", gap: 2, alignItems: "stretch", children: (sessionsQuery.data ?? []).map((session) => /* @__PURE__ */ jsxs(SessionCard, { children: [
4843
4889
  /* @__PURE__ */ jsxs(SessionMeta, { children: [
4844
- /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", style: { flexWrap: "wrap" }, children: [
4845
- session.ipAddress && /* @__PURE__ */ jsx(IpChip, { children: session.ipAddress }),
4846
- /* @__PURE__ */ jsxs(TimestampText, { children: [
4847
- "Created",
4848
- " ",
4849
- new Date(session.createdAt).toLocaleString(),
4850
- " · Expires",
4851
- " ",
4852
- new Date(session.expiresAt).toLocaleString()
4853
- ] })
4854
- ] }),
4890
+ /* @__PURE__ */ jsxs(
4891
+ Flex,
4892
+ {
4893
+ gap: 2,
4894
+ alignItems: "center",
4895
+ style: { flexWrap: "wrap" },
4896
+ children: [
4897
+ session.ipAddress && /* @__PURE__ */ jsx(IpChip, { children: session.ipAddress }),
4898
+ /* @__PURE__ */ jsxs(TimestampText, { children: [
4899
+ "Created",
4900
+ " ",
4901
+ new Date(session.createdAt).toLocaleString(),
4902
+ " · Expires",
4903
+ " ",
4904
+ new Date(session.expiresAt).toLocaleString()
4905
+ ] })
4906
+ ]
4907
+ }
4908
+ ),
4855
4909
  session.userAgent && /* @__PURE__ */ jsx(AgentText, { children: session.userAgent })
4856
4910
  ] }),
4857
4911
  /* @__PURE__ */ jsx(
@@ -4913,6 +4967,18 @@ function UserDetailDrawer({
4913
4967
  onCancel: () => setConfirmRevokeSessionId(null)
4914
4968
  }
4915
4969
  ),
4970
+ confirmBan && /* @__PURE__ */ jsx(
4971
+ ConfirmDialog,
4972
+ {
4973
+ title: "Ban user",
4974
+ 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.",
4975
+ confirmLabel: "Ban user",
4976
+ variant: "danger",
4977
+ loading: banMutation.isLoading,
4978
+ onConfirm: () => banMutation.mutate(),
4979
+ onCancel: () => setConfirmBan(false)
4980
+ }
4981
+ ),
4916
4982
  confirmUnban && /* @__PURE__ */ jsx(
4917
4983
  ConfirmDialog,
4918
4984
  {
@@ -5143,6 +5209,7 @@ const Toolbar = styled.div`
5143
5209
  `;
5144
5210
  function UsersPage({ config }) {
5145
5211
  const qc = useQueryClient();
5212
+ const { toggleNotification } = useNotification();
5146
5213
  const banEnabled = hasPlugin(config, "admin");
5147
5214
  const emailVerificationEnabled = config.emailVerification.sendVerificationEmailEnabled;
5148
5215
  const twoFactorEnabled = hasPlugin(config, "two-factor");
@@ -5174,6 +5241,13 @@ function UsersPage({ config }) {
5174
5241
  setConfirmDelete(null);
5175
5242
  qc.invalidateQueries({ queryKey: ["dash-users"] });
5176
5243
  qc.invalidateQueries({ queryKey: ["dash-user-stats"] });
5244
+ toggleNotification({ type: "success", message: "User deleted" });
5245
+ },
5246
+ onError: (err) => {
5247
+ toggleNotification({
5248
+ type: "danger",
5249
+ message: err.message ?? "Failed to delete user"
5250
+ });
5177
5251
  }
5178
5252
  });
5179
5253
  const deleteManyMutation = useMutation({
@@ -5186,11 +5260,21 @@ function UsersPage({ config }) {
5186
5260
  throw new Error(result.error.message ?? "Delete failed");
5187
5261
  return result.data;
5188
5262
  },
5189
- onSuccess: () => {
5263
+ onSuccess: (_data, userIds) => {
5190
5264
  setConfirmDeleteMany(false);
5191
5265
  setSelected(/* @__PURE__ */ new Set());
5192
5266
  qc.invalidateQueries({ queryKey: ["dash-users"] });
5193
5267
  qc.invalidateQueries({ queryKey: ["dash-user-stats"] });
5268
+ toggleNotification({
5269
+ type: "success",
5270
+ message: `${userIds.length} user${userIds.length !== 1 ? "s" : ""} deleted`
5271
+ });
5272
+ },
5273
+ onError: (err) => {
5274
+ toggleNotification({
5275
+ type: "danger",
5276
+ message: err.message ?? "Failed to delete users"
5277
+ });
5194
5278
  }
5195
5279
  });
5196
5280
  const banManyMutation = useMutation({
@@ -5202,10 +5286,20 @@ function UsersPage({ config }) {
5202
5286
  if (result.error) throw new Error(result.error.message ?? "Ban failed");
5203
5287
  return result.data;
5204
5288
  },
5205
- onSuccess: () => {
5289
+ onSuccess: (_data, userIds) => {
5206
5290
  setConfirmBanMany(false);
5207
5291
  setSelected(/* @__PURE__ */ new Set());
5208
5292
  qc.invalidateQueries({ queryKey: ["dash-users"] });
5293
+ toggleNotification({
5294
+ type: "success",
5295
+ message: `${userIds.length} user${userIds.length !== 1 ? "s" : ""} banned`
5296
+ });
5297
+ },
5298
+ onError: (err) => {
5299
+ toggleNotification({
5300
+ type: "danger",
5301
+ message: err.message ?? "Failed to ban users"
5302
+ });
5209
5303
  }
5210
5304
  });
5211
5305
  const toggleSelect = (id) => {
@@ -5384,14 +5478,28 @@ function UsersPage({ config }) {
5384
5478
  user.id
5385
5479
  )) })
5386
5480
  ] }) }),
5387
- pageCount > 1 && /* @__PURE__ */ jsx(Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsx(
5388
- Pagination,
5389
- {
5390
- activePage: page,
5391
- pageCount,
5392
- onChangePage: setPage
5393
- }
5394
- ) }),
5481
+ pageCount > 1 && /* @__PURE__ */ jsx(Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
5482
+ /* @__PURE__ */ jsx(
5483
+ Button,
5484
+ {
5485
+ variant: "tertiary",
5486
+ size: "S",
5487
+ disabled: page === 1,
5488
+ onClick: () => setPage((p) => p - 1),
5489
+ children: "Previous"
5490
+ }
5491
+ ),
5492
+ /* @__PURE__ */ jsx(
5493
+ Button,
5494
+ {
5495
+ variant: "tertiary",
5496
+ size: "S",
5497
+ disabled: page >= pageCount,
5498
+ onClick: () => setPage((p) => p + 1),
5499
+ children: "Next"
5500
+ }
5501
+ )
5502
+ ] }) }),
5395
5503
  showCreate && /* @__PURE__ */ jsx(CreateUserDialog, { onClose: () => setShowCreate(false) }),
5396
5504
  detailUserId && /* @__PURE__ */ jsx(
5397
5505
  UserDetailDrawer,
@@ -55,7 +55,7 @@ const index = {
55
55
  id: `${PLUGIN_ID}.plugin.name`,
56
56
  defaultMessage: "Auth Dashboard"
57
57
  },
58
- Component: async () => Promise.resolve().then(() => require("./Root-BnRbzS-u.js"))
58
+ Component: async () => Promise.resolve().then(() => require("./Root-7EredGQZ.js"))
59
59
  });
60
60
  },
61
61
  bootstrap() {
@@ -54,7 +54,7 @@ const index = {
54
54
  id: `${PLUGIN_ID}.plugin.name`,
55
55
  defaultMessage: "Auth Dashboard"
56
56
  },
57
- Component: async () => import("./Root-DBjGZL7H.mjs")
57
+ Component: async () => import("./Root-B1LuKMEv.mjs")
58
58
  });
59
59
  },
60
60
  bootstrap() {
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3
- const index = require("./index-xZ2FHX3i.js");
3
+ const index = require("./index-CIxfFlzU.js");
4
4
  exports.default = index.index;
@@ -1,4 +1,4 @@
1
- import { i } from "./index-BpruO4vo.mjs";
1
+ import { i } from "./index-QVlTR8eL.mjs";
2
2
  export {
3
3
  i as default
4
4
  };