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

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-QYGGdXAd.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);
@@ -1104,7 +1115,7 @@ function OrganizationDetail({
1104
1115
  };
1105
1116
  const org = orgQuery.data;
1106
1117
  const extraData = {
1107
- ...org,
1118
+ ...strapiOrgQuery.data ?? {},
1108
1119
  ...editExtra
1109
1120
  };
1110
1121
  const updateOrgMutation = useMutation({
@@ -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 : [];
@@ -3253,7 +3282,7 @@ function OverviewPage() {
3253
3282
  queryKey: ["dash-recent-sessions"],
3254
3283
  queryFn: async () => {
3255
3284
  const { data } = await get(
3256
- "/better-auth-dashboard/db?uid=plugin::better-auth.session&pagination[pageSize]=12&sort[0]=createdAt:desc"
3285
+ "/better-auth-dashboard/db?uid=plugin::better-auth.session&pagination[pageSize]=12&sort[0]=updatedAt:desc"
3257
3286
  );
3258
3287
  return data.results ?? [];
3259
3288
  },
@@ -3269,6 +3298,43 @@ 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
+ const uid = String(s.userId);
3306
+ if (!lastActiveByUserId.has(uid)) {
3307
+ lastActiveByUserId.set(uid, s.updatedAt ?? s.createdAt);
3308
+ activeUserIds.push(uid);
3309
+ }
3310
+ }
3311
+ const activeUsersQuery = useQuery({
3312
+ queryKey: ["dash-active-users", activeUserIds],
3313
+ queryFn: async () => {
3314
+ if (activeUserIds.length === 0) return [];
3315
+ const params = new URLSearchParams({ uid: "plugin::better-auth.user" });
3316
+ for (let i = 0; i < activeUserIds.length; i++) {
3317
+ params.set(`filters[id][$in][${i}]`, activeUserIds[i]);
3318
+ }
3319
+ params.set("pagination[pageSize]", "12");
3320
+ const { data } = await get(
3321
+ `/better-auth-dashboard/db?${params}`
3322
+ );
3323
+ return data.results ?? [];
3324
+ },
3325
+ enabled: feedMode === "active" && activeUserIds.length > 0
3326
+ });
3327
+ const chartRef = useRef(null);
3328
+ const [chartHeight, setChartHeight] = useState(0);
3329
+ useEffect(() => {
3330
+ const el = chartRef.current;
3331
+ if (!el) return;
3332
+ const ro = new ResizeObserver((entries) => {
3333
+ for (const entry of entries) setChartHeight(entry.contentRect.height);
3334
+ });
3335
+ ro.observe(el);
3336
+ return () => ro.disconnect();
3337
+ }, []);
3272
3338
  if (statsQuery.isLoading) {
3273
3339
  return /* @__PURE__ */ jsx(
3274
3340
  Flex,
@@ -3286,8 +3352,12 @@ function OverviewPage() {
3286
3352
  const graphData = graphQuery.data?.data ?? [];
3287
3353
  const rtnData = retentionQuery.data?.data ?? [];
3288
3354
  const users = usersQuery.data?.users ?? [];
3289
- const sessions = sessionsQuery.data ?? [];
3290
3355
  const orgCount = orgsQuery.data?.total ?? 0;
3356
+ const activeUserMap = /* @__PURE__ */ new Map();
3357
+ for (const u of activeUsersQuery.data ?? []) {
3358
+ activeUserMap.set(String(u.id), u);
3359
+ }
3360
+ const sortedActiveUsers = activeUserIds.map((uid) => activeUserMap.get(uid)).filter((u) => u !== void 0);
3291
3361
  const totalSpark = graphData.map((d) => d.totalUsers);
3292
3362
  const newSpark = graphData.map((d) => d.newUsers);
3293
3363
  const activeSpark = graphData.map((d) => d.activeUsers);
@@ -3391,7 +3461,7 @@ function OverviewPage() {
3391
3461
  /* @__PURE__ */ jsx(DivLine, {})
3392
3462
  ] }),
3393
3463
  /* @__PURE__ */ jsxs(TwoPanel, { children: [
3394
- /* @__PURE__ */ jsxs(ChartCard, { $delay: 5, children: [
3464
+ /* @__PURE__ */ jsxs(ChartCard, { ref: chartRef, $delay: 5, children: [
3395
3465
  /* @__PURE__ */ jsxs(ChartHeader, { children: [
3396
3466
  /* @__PURE__ */ jsx(ChartTitle, { children: "User Growth" }),
3397
3467
  /* @__PURE__ */ jsx(SeriesRow, { children: ALL_SERIES.map((s) => /* @__PURE__ */ jsxs(
@@ -3427,57 +3497,57 @@ function OverviewPage() {
3427
3497
  }
3428
3498
  )
3429
3499
  ] }),
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 })
3500
+ /* @__PURE__ */ jsxs(
3501
+ FeedCard,
3502
+ {
3503
+ $delay: 6,
3504
+ style: chartHeight > 0 ? { height: chartHeight } : void 0,
3505
+ children: [
3506
+ /* @__PURE__ */ jsxs(FeedHead, { children: [
3507
+ /* @__PURE__ */ jsx("span", { children: feedMode === "signups" ? "Recent Sign-ups" : "Recently Active" }),
3508
+ /* @__PURE__ */ jsxs(
3509
+ FeedSelect,
3510
+ {
3511
+ value: feedMode,
3512
+ onChange: (e) => setFeedMode(e.target.value),
3513
+ children: [
3514
+ /* @__PURE__ */ jsx("option", { value: "signups", children: "Recent Sign-ups" }),
3515
+ /* @__PURE__ */ jsx("option", { value: "active", children: "Recently Active" })
3516
+ ]
3517
+ }
3518
+ )
3450
3519
  ] }),
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
- ] })
3520
+ /* @__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: [
3521
+ /* @__PURE__ */ jsxs(FeedTop, { children: [
3522
+ /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 1, style: { minWidth: 0 }, children: [
3523
+ /* @__PURE__ */ jsx(Avatar, { name: u.name, src: u.image, size: 20 }),
3524
+ /* @__PURE__ */ jsx(FeedName, { title: u.name, children: u.name })
3525
+ ] }),
3526
+ /* @__PURE__ */ jsx(FeedMeta, { children: relTime(u.createdAt) })
3527
+ ] }),
3528
+ /* @__PURE__ */ jsx(FeedEmail, { title: u.email, children: u.email })
3529
+ ] }, 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: [
3530
+ /* @__PURE__ */ jsxs(FeedTop, { children: [
3531
+ /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 1, style: { minWidth: 0 }, children: [
3532
+ /* @__PURE__ */ jsx(
3533
+ Avatar,
3534
+ {
3535
+ name: u.name,
3536
+ src: u.image ?? void 0,
3537
+ size: 20
3538
+ }
3539
+ ),
3540
+ /* @__PURE__ */ jsx(FeedName, { title: u.name, children: u.name })
3541
+ ] }),
3542
+ /* @__PURE__ */ jsx(FeedMeta, { children: relTime(
3543
+ lastActiveByUserId.get(String(u.id)) ?? u.createdAt
3544
+ ) })
3545
+ ] }),
3546
+ /* @__PURE__ */ jsx(FeedEmail, { title: u.email, children: u.email })
3547
+ ] }, u.documentId)) })
3548
+ ]
3549
+ }
3550
+ )
3481
3551
  ] }),
3482
3552
  /* @__PURE__ */ jsxs(SectionDivider, { children: [
3483
3553
  /* @__PURE__ */ jsx(DivLabel, { children: "Retention & Activity" }),
@@ -3853,9 +3923,10 @@ const SessionCard = styled.div`
3853
3923
  align-items: flex-start;
3854
3924
  justify-content: space-between;
3855
3925
  gap: 12px;
3856
- padding: 10px 0;
3857
- border-bottom: 1px solid #f5f5f9;
3858
- &:last-child { border-bottom: none; }
3926
+ padding: 10px 14px;
3927
+ background: white;
3928
+ border: 1px solid #eaeaef;
3929
+ border-radius: 8px;
3859
3930
  `;
3860
3931
  const SessionMeta = styled.div`
3861
3932
  display: flex;
@@ -3961,6 +4032,7 @@ function UserDetailDrawer({
3961
4032
  const [banExpiresDays, setBanExpiresDays] = useState("");
3962
4033
  const [confirmRevokeAll, setConfirmRevokeAll] = useState(false);
3963
4034
  const [confirmRevokeSessionId, setConfirmRevokeSessionId] = useState(null);
4035
+ const [confirmBan, setConfirmBan] = useState(false);
3964
4036
  const [confirmUnban, setConfirmUnban] = useState(false);
3965
4037
  const [confirmUnlinkAccountId, setConfirmUnlinkAccountId] = useState(null);
3966
4038
  const [confirmDisable2FA, setConfirmDisable2FA] = useState(false);
@@ -3984,33 +4056,18 @@ function UserDetailDrawer({
3984
4056
  };
3985
4057
  const updateMutation = useMutation({
3986
4058
  mutationFn: async () => {
3987
- const baBody = {};
3988
- if (editName !== void 0) baBody.name = editName;
3989
- if (editEmail !== void 0) baBody.email = editEmail;
4059
+ const body = { ...editExtra };
4060
+ if (editName !== void 0) body.name = editName;
4061
+ if (editEmail !== void 0) body.email = editEmail;
3990
4062
  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);
4063
+ body.emailVerified = editEmailVerified;
4064
+ if (editImage !== void 0) body.image = editImage;
4065
+ const documentId = strapiUserQuery.data?.documentId;
4066
+ if (!documentId) throw new Error("Could not resolve documentId for user");
4067
+ await put(
4068
+ `/better-auth-dashboard/db/${documentId}?uid=plugin::better-auth.user`,
4069
+ body
4070
+ );
4014
4071
  },
4015
4072
  onSuccess: () => {
4016
4073
  qc.invalidateQueries({ queryKey: ["dash-user", userId] });
@@ -4038,7 +4095,7 @@ function UserDetailDrawer({
4038
4095
  mutationFn: async (sessionId) => {
4039
4096
  const result = await client.dash.sessions.revoke(
4040
4097
  {},
4041
- withContext({ sessionId })
4098
+ withContext({ sessionId, userId })
4042
4099
  );
4043
4100
  if (result.error)
4044
4101
  throw new Error(result.error.message ?? "Revoke failed");
@@ -4092,6 +4149,7 @@ function UserDetailDrawer({
4092
4149
  return result.data;
4093
4150
  },
4094
4151
  onSuccess: () => {
4152
+ setConfirmBan(false);
4095
4153
  qc.invalidateQueries({ queryKey: ["dash-user", userId] });
4096
4154
  qc.invalidateQueries({ queryKey: ["dash-users"] });
4097
4155
  setBanReason("");
@@ -4707,7 +4765,8 @@ function UserDetailDrawer({
4707
4765
  ] })
4708
4766
  ] })
4709
4767
  ] }) }),
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: [
4768
+ banEnabled && /* @__PURE__ */ jsx(Tabs.Content, { value: "ban", children: /* @__PURE__ */ jsx(Flex, { direction: "column", gap: 5, paddingTop: 6, children: user?.banned ? /* @__PURE__ */ jsxs(FormSection, { children: [
4769
+ /* @__PURE__ */ jsx(SectionLabel, { children: "Ban status" }),
4711
4770
  /* @__PURE__ */ jsxs(WarnCard, { children: [
4712
4771
  /* @__PURE__ */ jsx(
4713
4772
  Typography,
@@ -4722,7 +4781,7 @@ function UserDetailDrawer({
4722
4781
  "Reason: ",
4723
4782
  user.banReason
4724
4783
  ] }),
4725
- user.banExpires && /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "danger600", children: [
4784
+ user.banExpires ? /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "danger600", children: [
4726
4785
  "Expires:",
4727
4786
  " ",
4728
4787
  new Date(user.banExpires).toLocaleDateString(
@@ -4734,8 +4793,7 @@ function UserDetailDrawer({
4734
4793
  day: "numeric"
4735
4794
  }
4736
4795
  )
4737
- ] }),
4738
- !user.banExpires && /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "danger600", children: "Duration: Permanent" })
4796
+ ] }) : /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "danger600", children: "Duration: Permanent" })
4739
4797
  ] }),
4740
4798
  /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(
4741
4799
  Button,
@@ -4745,27 +4803,28 @@ function UserDetailDrawer({
4745
4803
  children: "Lift ban"
4746
4804
  }
4747
4805
  ) })
4748
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
4806
+ ] }) : /* @__PURE__ */ jsxs(FormSection, { children: [
4807
+ /* @__PURE__ */ jsx(SectionLabel, { children: "Apply ban" }),
4808
+ /* @__PURE__ */ jsxs(
4809
+ Field.Root,
4810
+ {
4811
+ hint: "Optional — will be shown to the user",
4812
+ style: { width: "100%" },
4813
+ children: [
4814
+ /* @__PURE__ */ jsx(Field.Label, { children: "Reason" }),
4815
+ /* @__PURE__ */ jsx(
4816
+ TextInput,
4817
+ {
4818
+ value: banReason,
4819
+ onChange: (e) => setBanReason(e.target.value),
4820
+ placeholder: "e.g. Violated terms of service"
4821
+ }
4822
+ ),
4823
+ /* @__PURE__ */ jsx(Field.Hint, {})
4824
+ ]
4825
+ }
4826
+ ),
4749
4827
  /* @__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
4828
  /* @__PURE__ */ jsx(Grid.Item, { col: 6, children: /* @__PURE__ */ jsxs(
4770
4829
  Field.Root,
4771
4830
  {
@@ -4786,24 +4845,13 @@ function UserDetailDrawer({
4786
4845
  ]
4787
4846
  }
4788
4847
  ) }),
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(
4848
+ /* @__PURE__ */ jsx(Grid.Item, { col: 6, children: /* @__PURE__ */ jsx(
4801
4849
  Flex,
4802
4850
  {
4803
4851
  direction: "column",
4804
4852
  justifyContent: "flex-end",
4805
4853
  style: { height: "100%", paddingBottom: 4 },
4806
- children: /* @__PURE__ */ jsx(PreviewPill, { children: "♾ Permanent ban" })
4854
+ children: /* @__PURE__ */ jsx(PreviewPill, { children: banExpiryPreview ? `⏱ Expires ${banExpiryPreview}` : "♾ Permanent ban" })
4807
4855
  }
4808
4856
  ) })
4809
4857
  ] }),
@@ -4811,8 +4859,7 @@ function UserDetailDrawer({
4811
4859
  Button,
4812
4860
  {
4813
4861
  variant: "danger",
4814
- loading: banMutation.isLoading,
4815
- onClick: () => banMutation.mutate(),
4862
+ onClick: () => setConfirmBan(true),
4816
4863
  children: "Ban user"
4817
4864
  }
4818
4865
  ) })
@@ -4839,19 +4886,27 @@ function UserDetailDrawer({
4839
4886
  ] }),
4840
4887
  /* @__PURE__ */ jsxs(FormSection, { children: [
4841
4888
  /* @__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: [
4889
+ 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
4890
  /* @__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
- ] }),
4891
+ /* @__PURE__ */ jsxs(
4892
+ Flex,
4893
+ {
4894
+ gap: 2,
4895
+ alignItems: "center",
4896
+ style: { flexWrap: "wrap" },
4897
+ children: [
4898
+ session.ipAddress && /* @__PURE__ */ jsx(IpChip, { children: session.ipAddress }),
4899
+ /* @__PURE__ */ jsxs(TimestampText, { children: [
4900
+ "Created",
4901
+ " ",
4902
+ new Date(session.createdAt).toLocaleString(),
4903
+ " · Expires",
4904
+ " ",
4905
+ new Date(session.expiresAt).toLocaleString()
4906
+ ] })
4907
+ ]
4908
+ }
4909
+ ),
4855
4910
  session.userAgent && /* @__PURE__ */ jsx(AgentText, { children: session.userAgent })
4856
4911
  ] }),
4857
4912
  /* @__PURE__ */ jsx(
@@ -4913,6 +4968,18 @@ function UserDetailDrawer({
4913
4968
  onCancel: () => setConfirmRevokeSessionId(null)
4914
4969
  }
4915
4970
  ),
4971
+ confirmBan && /* @__PURE__ */ jsx(
4972
+ ConfirmDialog,
4973
+ {
4974
+ title: "Ban user",
4975
+ 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.",
4976
+ confirmLabel: "Ban user",
4977
+ variant: "danger",
4978
+ loading: banMutation.isLoading,
4979
+ onConfirm: () => banMutation.mutate(),
4980
+ onCancel: () => setConfirmBan(false)
4981
+ }
4982
+ ),
4916
4983
  confirmUnban && /* @__PURE__ */ jsx(
4917
4984
  ConfirmDialog,
4918
4985
  {
@@ -5143,6 +5210,7 @@ const Toolbar = styled.div`
5143
5210
  `;
5144
5211
  function UsersPage({ config }) {
5145
5212
  const qc = useQueryClient();
5213
+ const { toggleNotification } = useNotification();
5146
5214
  const banEnabled = hasPlugin(config, "admin");
5147
5215
  const emailVerificationEnabled = config.emailVerification.sendVerificationEmailEnabled;
5148
5216
  const twoFactorEnabled = hasPlugin(config, "two-factor");
@@ -5174,6 +5242,13 @@ function UsersPage({ config }) {
5174
5242
  setConfirmDelete(null);
5175
5243
  qc.invalidateQueries({ queryKey: ["dash-users"] });
5176
5244
  qc.invalidateQueries({ queryKey: ["dash-user-stats"] });
5245
+ toggleNotification({ type: "success", message: "User deleted" });
5246
+ },
5247
+ onError: (err) => {
5248
+ toggleNotification({
5249
+ type: "danger",
5250
+ message: err.message ?? "Failed to delete user"
5251
+ });
5177
5252
  }
5178
5253
  });
5179
5254
  const deleteManyMutation = useMutation({
@@ -5186,11 +5261,21 @@ function UsersPage({ config }) {
5186
5261
  throw new Error(result.error.message ?? "Delete failed");
5187
5262
  return result.data;
5188
5263
  },
5189
- onSuccess: () => {
5264
+ onSuccess: (_data, userIds) => {
5190
5265
  setConfirmDeleteMany(false);
5191
5266
  setSelected(/* @__PURE__ */ new Set());
5192
5267
  qc.invalidateQueries({ queryKey: ["dash-users"] });
5193
5268
  qc.invalidateQueries({ queryKey: ["dash-user-stats"] });
5269
+ toggleNotification({
5270
+ type: "success",
5271
+ message: `${userIds.length} user${userIds.length !== 1 ? "s" : ""} deleted`
5272
+ });
5273
+ },
5274
+ onError: (err) => {
5275
+ toggleNotification({
5276
+ type: "danger",
5277
+ message: err.message ?? "Failed to delete users"
5278
+ });
5194
5279
  }
5195
5280
  });
5196
5281
  const banManyMutation = useMutation({
@@ -5202,10 +5287,20 @@ function UsersPage({ config }) {
5202
5287
  if (result.error) throw new Error(result.error.message ?? "Ban failed");
5203
5288
  return result.data;
5204
5289
  },
5205
- onSuccess: () => {
5290
+ onSuccess: (_data, userIds) => {
5206
5291
  setConfirmBanMany(false);
5207
5292
  setSelected(/* @__PURE__ */ new Set());
5208
5293
  qc.invalidateQueries({ queryKey: ["dash-users"] });
5294
+ toggleNotification({
5295
+ type: "success",
5296
+ message: `${userIds.length} user${userIds.length !== 1 ? "s" : ""} banned`
5297
+ });
5298
+ },
5299
+ onError: (err) => {
5300
+ toggleNotification({
5301
+ type: "danger",
5302
+ message: err.message ?? "Failed to ban users"
5303
+ });
5209
5304
  }
5210
5305
  });
5211
5306
  const toggleSelect = (id) => {
@@ -5384,14 +5479,28 @@ function UsersPage({ config }) {
5384
5479
  user.id
5385
5480
  )) })
5386
5481
  ] }) }),
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
- ) }),
5482
+ pageCount > 1 && /* @__PURE__ */ jsx(Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
5483
+ /* @__PURE__ */ jsx(
5484
+ Button,
5485
+ {
5486
+ variant: "tertiary",
5487
+ size: "S",
5488
+ disabled: page === 1,
5489
+ onClick: () => setPage((p) => p - 1),
5490
+ children: "Previous"
5491
+ }
5492
+ ),
5493
+ /* @__PURE__ */ jsx(
5494
+ Button,
5495
+ {
5496
+ variant: "tertiary",
5497
+ size: "S",
5498
+ disabled: page >= pageCount,
5499
+ onClick: () => setPage((p) => p + 1),
5500
+ children: "Next"
5501
+ }
5502
+ )
5503
+ ] }) }),
5395
5504
  showCreate && /* @__PURE__ */ jsx(CreateUserDialog, { onClose: () => setShowCreate(false) }),
5396
5505
  detailUserId && /* @__PURE__ */ jsx(
5397
5506
  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-CFxaaNC8.js"))
59
59
  });
60
60
  },
61
61
  bootstrap() {