@withpotter/pages-admin 0.13.0-next.124 → 0.13.0-next.59

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.
Files changed (260) hide show
  1. package/dist/admin/_money/cash-postings.js +3 -20
  2. package/dist/admin/_money/cash-postings.js.map +1 -1
  3. package/dist/admin/_money/cash.module.css +0 -10
  4. package/dist/admin/_money/finder-format.js +0 -1
  5. package/dist/admin/_money/finder-format.js.map +1 -1
  6. package/dist/admin/_money/format.js +0 -1
  7. package/dist/admin/_money/format.js.map +1 -1
  8. package/dist/admin/_overview/activation-panel.js +6 -6
  9. package/dist/admin/_overview/activation-panel.js.map +1 -1
  10. package/dist/admin/_overview/attention-groups.js +8 -8
  11. package/dist/admin/_overview/attention-groups.js.map +1 -1
  12. package/dist/admin/_overview/attention-panel.js +33 -37
  13. package/dist/admin/_overview/attention-panel.js.map +1 -1
  14. package/dist/admin/_overview/attention.module.css +3 -0
  15. package/dist/admin/_overview/control-room.js +13 -41
  16. package/dist/admin/_overview/control-room.js.map +1 -1
  17. package/dist/admin/_overview/kpi-row.js +7 -7
  18. package/dist/admin/_overview/kpi-row.js.map +1 -1
  19. package/dist/admin/_overview/leaders-panel.js +1 -1
  20. package/dist/admin/_overview/leaders-panel.js.map +1 -1
  21. package/dist/admin/_overview/overdue-outstanding.js +1 -2
  22. package/dist/admin/_overview/overdue-outstanding.js.map +1 -1
  23. package/dist/admin/_overview/portfolio-panel.js +5 -5
  24. package/dist/admin/_overview/portfolio-panel.js.map +1 -1
  25. package/dist/admin/_overview/position-panel.js +12 -8
  26. package/dist/admin/_overview/position-panel.js.map +1 -1
  27. package/dist/admin/_pos/pos-figures.js +5 -18
  28. package/dist/admin/_pos/pos-figures.js.map +1 -1
  29. package/dist/admin/_pos/pos-rules.js +0 -2
  30. package/dist/admin/_pos/pos-rules.js.map +1 -1
  31. package/dist/admin/_pos/pos-words.js +1 -6
  32. package/dist/admin/_pos/pos-words.js.map +1 -1
  33. package/dist/admin/_pos/use-pos.js +1 -70
  34. package/dist/admin/_pos/use-pos.js.map +1 -1
  35. package/dist/admin/_shell/nav-config.js +3 -3
  36. package/dist/admin/_shell/nav-config.js.map +1 -1
  37. package/dist/admin/_shell/nav-filter.js +2 -4
  38. package/dist/admin/_shell/nav-filter.js.map +1 -1
  39. package/dist/admin/_shell/nav-row.js +2 -5
  40. package/dist/admin/_shell/nav-row.js.map +1 -1
  41. package/dist/admin/_shell/sidebar-footer.js +0 -20
  42. package/dist/admin/_shell/sidebar-footer.js.map +1 -1
  43. package/dist/admin/_shell/sidebar-footer.module.css +0 -17
  44. package/dist/admin/_shell/sidebar.js +15 -25
  45. package/dist/admin/_shell/sidebar.js.map +1 -1
  46. package/dist/admin/_shell/sidebar.module.css +0 -69
  47. package/dist/admin/_shell/use-fluid-hover.js +6 -37
  48. package/dist/admin/_shell/use-fluid-hover.js.map +1 -1
  49. package/dist/admin/_ui/admin.css +0 -7
  50. package/dist/admin/_ui/data-table.js +1 -1
  51. package/dist/admin/_ui/data-table.js.map +1 -1
  52. package/dist/admin/_ui/list-card.js +1 -1
  53. package/dist/admin/_ui/list-card.js.map +1 -1
  54. package/dist/admin/_ui/list-card.module.css +16 -2
  55. package/dist/admin/_ui/money-view.js +0 -1
  56. package/dist/admin/_ui/money-view.js.map +1 -1
  57. package/dist/admin/_ui/page-header.js +4 -31
  58. package/dist/admin/_ui/page-header.js.map +1 -1
  59. package/dist/admin/_ui/panel.module.css +10 -0
  60. package/dist/admin/_ui/stat-card.js +2 -11
  61. package/dist/admin/_ui/stat-card.js.map +1 -1
  62. package/dist/admin/_ui/trend.js +2 -2
  63. package/dist/admin/_ui/trend.js.map +1 -1
  64. package/dist/admin/accounts/account-panel.js +6 -49
  65. package/dist/admin/accounts/account-panel.js.map +1 -1
  66. package/dist/admin/accounts/account-panel.module.css +0 -73
  67. package/dist/admin/accounts/account-row.js +2 -11
  68. package/dist/admin/accounts/account-row.js.map +1 -1
  69. package/dist/admin/accounts/account-vitals.js +13 -15
  70. package/dist/admin/accounts/account-vitals.js.map +1 -1
  71. package/dist/admin/accounts/accounts-table.js +8 -39
  72. package/dist/admin/accounts/accounts-table.js.map +1 -1
  73. package/dist/admin/accounts/accounts-workspace.js +2 -2
  74. package/dist/admin/accounts/accounts-workspace.js.map +1 -1
  75. package/dist/admin/accounts/owner-session.js +6 -0
  76. package/dist/admin/accounts/owner-session.js.map +1 -1
  77. package/dist/admin/accounts/rows.module.css +0 -17
  78. package/dist/admin/audit/status-pill.js +0 -6
  79. package/dist/admin/audit/status-pill.js.map +1 -1
  80. package/dist/admin/audit/status-pill.module.css +0 -4
  81. package/dist/admin/commerce/commerce-overview.js +2 -2
  82. package/dist/admin/commerce/commerce-overview.js.map +1 -1
  83. package/dist/admin/commerce/merchant-meta.js +0 -1
  84. package/dist/admin/commerce/merchant-meta.js.map +1 -1
  85. package/dist/admin/commerce/merchant-view.js +2 -2
  86. package/dist/admin/commerce/merchant-view.js.map +1 -1
  87. package/dist/admin/feedback/prompt-operations.module.css +2 -0
  88. package/dist/admin/feedback/prompt-preview.module.css +2 -0
  89. package/dist/admin/hooks.js +1 -57
  90. package/dist/admin/hooks.js.map +1 -1
  91. package/dist/admin/layout.js +2 -8
  92. package/dist/admin/layout.js.map +1 -1
  93. package/dist/admin/page.js +8 -28
  94. package/dist/admin/page.js.map +1 -1
  95. package/dist/admin/payout-tiers/_tier-dialog.js +21 -31
  96. package/dist/admin/payout-tiers/_tier-dialog.js.map +1 -1
  97. package/dist/admin/payout-tiers/tier-preview-result.js +9 -10
  98. package/dist/admin/payout-tiers/tier-preview-result.js.map +1 -1
  99. package/dist/admin/payout-tiers/tiers-table.js +7 -6
  100. package/dist/admin/payout-tiers/tiers-table.js.map +1 -1
  101. package/dist/admin/payout-tiers/tiers-workspace.js +2 -2
  102. package/dist/admin/payout-tiers/tiers-workspace.js.map +1 -1
  103. package/dist/admin/pos/pos-workspace.js +2 -6
  104. package/dist/admin/pos/pos-workspace.js.map +1 -1
  105. package/dist/admin/pos/shifts-tab.js +9 -16
  106. package/dist/admin/pos/shifts-tab.js.map +1 -1
  107. package/dist/admin/revenue/cohort-panel.js +6 -6
  108. package/dist/admin/revenue/cohort-panel.js.map +1 -1
  109. package/dist/admin/revenue/health-panel.js +21 -22
  110. package/dist/admin/revenue/health-panel.js.map +1 -1
  111. package/dist/admin/revenue/plan-mix-panel.js +3 -3
  112. package/dist/admin/revenue/plan-mix-panel.js.map +1 -1
  113. package/dist/admin/revenue/quality-panel.js +19 -19
  114. package/dist/admin/revenue/quality-panel.js.map +1 -1
  115. package/dist/admin/revenue/revenue-kpis.js +50 -111
  116. package/dist/admin/revenue/revenue-kpis.js.map +1 -1
  117. package/dist/admin/revenue/revenue-overview.js +46 -39
  118. package/dist/admin/revenue/revenue-overview.js.map +1 -1
  119. package/dist/admin/revenue/revenue-retention.js +13 -17
  120. package/dist/admin/revenue/revenue-retention.js.map +1 -1
  121. package/dist/admin/revenue/revenue-workspace.js +6 -53
  122. package/dist/admin/revenue/revenue-workspace.js.map +1 -1
  123. package/dist/admin/revenue/subscriptions-panel.js +14 -14
  124. package/dist/admin/revenue/subscriptions-panel.js.map +1 -1
  125. package/dist/admin/revenue/top-tenants-panel.js +4 -4
  126. package/dist/admin/revenue/top-tenants-panel.js.map +1 -1
  127. package/dist/admin/settings/account-tab.js +25 -1
  128. package/dist/admin/settings/account-tab.js.map +1 -1
  129. package/dist/admin/settings/general-tab.js +2 -2
  130. package/dist/admin/settings/general-tab.js.map +1 -1
  131. package/dist/admin/settings/system-status-card.js +30 -0
  132. package/dist/admin/settings/system-status-card.js.map +1 -0
  133. package/dist/admin/support/queue-card.module.css +4 -1
  134. package/dist/admin/tenants/[id]/commerce/format.js +1 -2
  135. package/dist/admin/tenants/[id]/commerce/format.js.map +1 -1
  136. package/dist/admin/tenants/[id]/commerce/recent-orders.js +1 -1
  137. package/dist/admin/tenants/[id]/commerce/recent-orders.js.map +1 -1
  138. package/dist/admin/tenants/[id]/page.js +10 -37
  139. package/dist/admin/tenants/[id]/page.js.map +1 -1
  140. package/dist/admin/tenants/[id]/tenant-layout.module.css +4 -1
  141. package/dist/admin/tenants/[id]/tenant-overview.js +2 -3
  142. package/dist/admin/tenants/[id]/tenant-overview.js.map +1 -1
  143. package/dist/admin/tenants/[id]/tenant-overview.module.css +1 -1
  144. package/dist/admin/tenants/[id]/tenant-pos.js +16 -64
  145. package/dist/admin/tenants/[id]/tenant-pos.js.map +1 -1
  146. package/dist/admin/tenants/[id]/tenant-wallet-card.js +2 -3
  147. package/dist/admin/tenants/[id]/tenant-wallet-card.js.map +1 -1
  148. package/dist/admin/tenants/[id]/verification/document-viewer.js +52 -3
  149. package/dist/admin/tenants/[id]/verification/document-viewer.js.map +1 -1
  150. package/dist/admin/tenants/[id]/verification/document-viewer.module.css +0 -51
  151. package/dist/admin/tenants/[id]/verification/tenant-verification.js +0 -10
  152. package/dist/admin/tenants/[id]/verification/tenant-verification.js.map +1 -1
  153. package/dist/admin/tenants/tenant-list-row.js +1 -2
  154. package/dist/admin/tenants/tenant-list-row.js.map +1 -1
  155. package/dist/admin/tenants/tenants-table.js +2 -3
  156. package/dist/admin/tenants/tenants-table.js.map +1 -1
  157. package/dist/admin/tenants/tenants-workspace.js +2 -2
  158. package/dist/admin/tenants/tenants-workspace.js.map +1 -1
  159. package/dist/admin/verification/verification-page.js +3 -19
  160. package/dist/admin/verification/verification-page.js.map +1 -1
  161. package/dist/admin/verification/verification.module.css +0 -10
  162. package/dist/admin/verification/waiting-case-actions.js +68 -0
  163. package/dist/admin/verification/waiting-case-actions.js.map +1 -0
  164. package/dist/admin/verification/waiting-cases.js +48 -66
  165. package/dist/admin/verification/waiting-cases.js.map +1 -1
  166. package/package.json +5 -5
  167. package/dist/admin/_pos/olive-balance.js +0 -40
  168. package/dist/admin/_pos/olive-balance.js.map +0 -1
  169. package/dist/admin/_pos/olive-state.js +0 -23
  170. package/dist/admin/_pos/olive-state.js.map +0 -1
  171. package/dist/admin/_pos/payments-model.js +0 -55
  172. package/dist/admin/_pos/payments-model.js.map +0 -1
  173. package/dist/admin/_pos/tenant-operators.js +0 -63
  174. package/dist/admin/_pos/tenant-operators.js.map +0 -1
  175. package/dist/admin/_pos/tenant-outlets.js +0 -77
  176. package/dist/admin/_pos/tenant-outlets.js.map +0 -1
  177. package/dist/admin/_pos/tenant-pos-figures.js +0 -82
  178. package/dist/admin/_pos/tenant-pos-figures.js.map +0 -1
  179. package/dist/admin/_pos/tenant-requests.js +0 -64
  180. package/dist/admin/_pos/tenant-requests.js.map +0 -1
  181. package/dist/admin/_pos/tenant-transactions.js +0 -145
  182. package/dist/admin/_pos/tenant-transactions.js.map +0 -1
  183. package/dist/admin/_shell/nav-fold.js +0 -14
  184. package/dist/admin/_shell/nav-fold.js.map +0 -1
  185. package/dist/admin/_shell/nav-group.js +0 -78
  186. package/dist/admin/_shell/nav-group.js.map +0 -1
  187. package/dist/admin/_shell/use-open-groups.js +0 -64
  188. package/dist/admin/_shell/use-open-groups.js.map +0 -1
  189. package/dist/admin/_ui/page-header.module.css +0 -45
  190. package/dist/admin/accounts/account-sessions.js +0 -70
  191. package/dist/admin/accounts/account-sessions.js.map +0 -1
  192. package/dist/admin/accounts/account-sessions.module.css +0 -55
  193. package/dist/admin/accounts/account-stage-filter.js +0 -29
  194. package/dist/admin/accounts/account-stage-filter.js.map +0 -1
  195. package/dist/admin/accounts/account-stage.js +0 -81
  196. package/dist/admin/accounts/account-stage.js.map +0 -1
  197. package/dist/admin/accounts/hint.js +0 -12
  198. package/dist/admin/accounts/hint.js.map +0 -1
  199. package/dist/admin/payout-tiers/requirement-labels.js +0 -70
  200. package/dist/admin/payout-tiers/requirement-labels.js.map +0 -1
  201. package/dist/admin/pos/transactions-tab.js +0 -214
  202. package/dist/admin/pos/transactions-tab.js.map +0 -1
  203. package/dist/admin/revenue/channel-panel.js +0 -49
  204. package/dist/admin/revenue/channel-panel.js.map +0 -1
  205. package/dist/admin/revenue/channel-rows.js +0 -17
  206. package/dist/admin/revenue/channel-rows.js.map +0 -1
  207. package/dist/admin/revenue/download-button.js +0 -40
  208. package/dist/admin/revenue/download-button.js.map +0 -1
  209. package/dist/admin/revenue/earnings/earnings-colors.js +0 -25
  210. package/dist/admin/revenue/earnings/earnings-colors.js.map +0 -1
  211. package/dist/admin/revenue/earnings/earnings-legend.js +0 -75
  212. package/dist/admin/revenue/earnings/earnings-legend.js.map +0 -1
  213. package/dist/admin/revenue/earnings/earnings-summary.js +0 -308
  214. package/dist/admin/revenue/earnings/earnings-summary.js.map +0 -1
  215. package/dist/admin/revenue/earnings/earnings-table.js +0 -143
  216. package/dist/admin/revenue/earnings/earnings-table.js.map +0 -1
  217. package/dist/admin/revenue/earnings/earnings.module.css +0 -201
  218. package/dist/admin/revenue/earnings/use-earnings.js +0 -27
  219. package/dist/admin/revenue/earnings/use-earnings.js.map +0 -1
  220. package/dist/admin/revenue/ledger-model.js +0 -104
  221. package/dist/admin/revenue/ledger-model.js.map +0 -1
  222. package/dist/admin/revenue/ledger.module.css +0 -144
  223. package/dist/admin/revenue/merchant-headline.js +0 -37
  224. package/dist/admin/revenue/merchant-headline.js.map +0 -1
  225. package/dist/admin/revenue/merchant-payments-view.js +0 -234
  226. package/dist/admin/revenue/merchant-payments-view.js.map +0 -1
  227. package/dist/admin/revenue/method-cards.js +0 -40
  228. package/dist/admin/revenue/method-cards.js.map +0 -1
  229. package/dist/admin/revenue/payment-drawer.js +0 -53
  230. package/dist/admin/revenue/payment-drawer.js.map +0 -1
  231. package/dist/admin/revenue/plan-lines.js +0 -36
  232. package/dist/admin/revenue/plan-lines.js.map +0 -1
  233. package/dist/admin/revenue/plan-payment-drawer.js +0 -42
  234. package/dist/admin/revenue/plan-payment-drawer.js.map +0 -1
  235. package/dist/admin/revenue/plans-panel.js +0 -48
  236. package/dist/admin/revenue/plans-panel.js.map +0 -1
  237. package/dist/admin/revenue/platform-fees.js +0 -9
  238. package/dist/admin/revenue/platform-fees.js.map +0 -1
  239. package/dist/admin/revenue/print-summary-button.js +0 -27
  240. package/dist/admin/revenue/print-summary-button.js.map +0 -1
  241. package/dist/admin/revenue/subscriptions-view.js +0 -213
  242. package/dist/admin/revenue/subscriptions-view.js.map +0 -1
  243. package/dist/admin/revenue/summary-download.js +0 -38
  244. package/dist/admin/revenue/summary-download.js.map +0 -1
  245. package/dist/admin/revenue/summary-print.js +0 -82
  246. package/dist/admin/revenue/summary-print.js.map +0 -1
  247. package/dist/admin/revenue/summary-rows.js +0 -44
  248. package/dist/admin/revenue/summary-rows.js.map +0 -1
  249. package/dist/admin/revenue/url-filters.js +0 -36
  250. package/dist/admin/revenue/url-filters.js.map +0 -1
  251. package/dist/admin/revenue/use-url-filters.js +0 -24
  252. package/dist/admin/revenue/use-url-filters.js.map +0 -1
  253. package/dist/admin/tenants/[id]/use-tenant-wallet.js +0 -23
  254. package/dist/admin/tenants/[id]/use-tenant-wallet.js.map +0 -1
  255. package/dist/admin/tenants/[id]/verification/document-file.js +0 -78
  256. package/dist/admin/tenants/[id]/verification/document-file.js.map +0 -1
  257. package/dist/admin/tenants/[id]/verification/sent-files.js +0 -33
  258. package/dist/admin/tenants/[id]/verification/sent-files.js.map +0 -1
  259. package/dist/admin/verification/preview-dialog.js +0 -111
  260. package/dist/admin/verification/preview-dialog.js.map +0 -1
@@ -3,9 +3,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
4
  import { Button, OverflowItem, OverflowMenu, Th } from "@withpotter/ui-kit";
5
5
  import { toCsv } from "@withpotter/lib/csv";
6
- import {
7
- platformAdminApi
8
- } from "@withpotter/lib/admin-api";
6
+ import { platformAdminApi } from "@withpotter/lib/admin-api";
9
7
  import {
10
8
  ColumnPicker,
11
9
  FilterDates,
@@ -15,27 +13,20 @@ import {
15
13
  import { usePlatformAccounts } from "../hooks";
16
14
  import { AccountPanel } from "./account-panel";
17
15
  import { ListCard, ListPager } from "../_ui/list-card";
18
- import { ACTIVE_HINT, kycLabel, STAGE_LABELS } from "./account-stage";
19
- import { AccountStageFilter } from "./account-stage-filter";
20
- import { Hint } from "./hint";
21
16
  import { AccountListRow, PROVIDER_LABELS } from "./account-row";
22
17
  import { PROVIDER_LABELS as PROVIDER_LABELS2 } from "./account-row";
23
18
  const OPTIONAL_COLUMNS = [
24
19
  { key: "provider", label: "Provider" },
25
- { key: "kyc", label: "KYC / KYB" },
26
- { key: "stage", label: "Stage" },
27
20
  { key: "emailVerified", label: "Email verified" },
28
21
  { key: "workspaces", label: "Workspaces" },
29
22
  { key: "lastLogin", label: "Last login" },
30
23
  { key: "joined", label: "Joined" },
31
- { key: "firstSale", label: "First sale" },
24
+ { key: "mfa", label: "MFA enabled" },
32
25
  { key: "activeSessions", label: "Active sessions" },
33
26
  { key: "lastSeen", label: "Last seen" }
34
27
  ];
35
28
  const DEFAULT_VISIBLE = [
36
29
  "provider",
37
- "kyc",
38
- "stage",
39
30
  "emailVerified",
40
31
  "workspaces",
41
32
  "lastLogin",
@@ -64,15 +55,13 @@ function toCsvRow(a, visible) {
64
55
  cells.push(PROVIDER_LABELS[a.authProvider] ?? a.authProvider);
65
56
  }
66
57
  cells.push(a.status);
67
- if (visible.has("kyc")) cells.push(kycLabel(a.kycStatus) ?? "");
68
- if (visible.has("stage")) cells.push(a.stage ? STAGE_LABELS[a.stage] : "");
69
58
  if (visible.has("emailVerified")) {
70
59
  cells.push(a.emailVerified ? "Verified" : "Unverified");
71
60
  }
72
61
  if (visible.has("workspaces")) cells.push(String(a.workspaceCount ?? 0));
73
62
  if (visible.has("lastLogin")) cells.push(day(a.lastLoginAt) || "Never");
74
63
  if (visible.has("joined")) cells.push(day(a.createdAt));
75
- if (visible.has("firstSale")) cells.push(day(a.firstSaleAt) || "None yet");
64
+ if (visible.has("mfa")) cells.push(a.mfaEnabled ? "Yes" : "No");
76
65
  if (visible.has("activeSessions")) cells.push(String(a.activeSessions ?? 0));
77
66
  if (visible.has("lastSeen")) cells.push(day(a.lastSeenAt) || "Never");
78
67
  return cells;
@@ -84,7 +73,6 @@ function AccountsTable({
84
73
  const [search, setSearch] = useState("");
85
74
  const [joinedFrom, setJoinedFrom] = useState("");
86
75
  const [joinedTo, setJoinedTo] = useState("");
87
- const [stage, setStage] = useState("");
88
76
  const [page, setPage] = useState(1);
89
77
  const [visible, setVisible] = useState(readVisibleColumns);
90
78
  const [exporting, setExporting] = useState(false);
@@ -93,8 +81,7 @@ function AccountsTable({
93
81
  pageSize: 20,
94
82
  search: search || void 0,
95
83
  joinedFrom: joinedFrom || void 0,
96
- joinedTo: joinedTo || void 0,
97
- stage: stage || void 0
84
+ joinedTo: joinedTo || void 0
98
85
  });
99
86
  const current = data?.rows.find((row) => row.id === selected?.id) ?? selected;
100
87
  const totalPages = data ? Math.max(1, Math.ceil(data.total / data.pageSize)) : 1;
@@ -113,19 +100,16 @@ function AccountsTable({
113
100
  pageSize: 5e3,
114
101
  search: search || void 0,
115
102
  joinedFrom: joinedFrom || void 0,
116
- joinedTo: joinedTo || void 0,
117
- stage: stage || void 0
103
+ joinedTo: joinedTo || void 0
118
104
  });
119
105
  const header = ["Name", "Email"];
120
106
  if (visible.has("provider")) header.push("Provider");
121
107
  header.push("Status");
122
- if (visible.has("kyc")) header.push("KYC / KYB");
123
- if (visible.has("stage")) header.push("Stage");
124
108
  if (visible.has("emailVerified")) header.push("Email verified");
125
109
  if (visible.has("workspaces")) header.push("Workspaces");
126
110
  if (visible.has("lastLogin")) header.push("Last login");
127
111
  if (visible.has("joined")) header.push("Joined");
128
- if (visible.has("firstSale")) header.push("First sale");
112
+ if (visible.has("mfa")) header.push("MFA enabled");
129
113
  if (visible.has("activeSessions")) header.push("Active sessions");
130
114
  if (visible.has("lastSeen")) header.push("Last seen");
131
115
  const rows = [header, ...page2.rows.map((a2) => toCsvRow(a2, visible))];
@@ -155,7 +139,7 @@ function AccountsTable({
155
139
  onRetry: () => void refetch(),
156
140
  errorTitle: "Failed to load accounts.",
157
141
  isEmpty: data?.rows.length === 0,
158
- emptyTitle: "No accounts match these filters.",
142
+ emptyTitle: "No accounts match this search or join date.",
159
143
  controls: /* @__PURE__ */ jsxs(Filters, { label: "Filter accounts", children: [
160
144
  /* @__PURE__ */ jsx(
161
145
  FilterSearch,
@@ -165,16 +149,6 @@ function AccountsTable({
165
149
  placeholder: "Search by email or name\u2026"
166
150
  }
167
151
  ),
168
- /* @__PURE__ */ jsx(
169
- AccountStageFilter,
170
- {
171
- value: stage,
172
- onChange: (v) => {
173
- setStage(v);
174
- setPage(1);
175
- }
176
- }
177
- ),
178
152
  /* @__PURE__ */ jsx(
179
153
  FilterDates,
180
154
  {
@@ -220,16 +194,11 @@ function AccountsTable({
220
194
  /* @__PURE__ */ jsx(Th, { children: "Account" }),
221
195
  visible.has("provider") && /* @__PURE__ */ jsx(Th, { children: "Provider" }),
222
196
  /* @__PURE__ */ jsx(Th, { children: "Status" }),
223
- visible.has("kyc") && /* @__PURE__ */ jsx(Th, { children: "KYC / KYB" }),
224
- visible.has("stage") && /* @__PURE__ */ jsxs(Th, { children: [
225
- "Stage",
226
- /* @__PURE__ */ jsx(Hint, { label: ACTIVE_HINT })
227
- ] }),
228
197
  visible.has("emailVerified") && /* @__PURE__ */ jsx(Th, { children: "Email" }),
229
198
  visible.has("workspaces") && /* @__PURE__ */ jsx(Th, { align: "right", children: "Workspaces" }),
230
199
  visible.has("lastLogin") && /* @__PURE__ */ jsx(Th, { children: "Last login" }),
231
200
  visible.has("joined") && /* @__PURE__ */ jsx(Th, { children: "Joined" }),
232
- visible.has("firstSale") && /* @__PURE__ */ jsx(Th, { children: "First sale" }),
201
+ visible.has("mfa") && /* @__PURE__ */ jsx(Th, { children: "MFA" }),
233
202
  visible.has("activeSessions") && /* @__PURE__ */ jsx(Th, { align: "right", children: "Sessions" }),
234
203
  visible.has("lastSeen") && /* @__PURE__ */ jsx(Th, { children: "Last seen" }),
235
204
  /* @__PURE__ */ jsx(Th, { align: "right", children: /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Actions" }) })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/accounts/accounts-table.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { Button, OverflowItem, OverflowMenu, Th } from '@withpotter/ui-kit';\nimport { toCsv } from '@withpotter/lib/csv';\nimport {\n platformAdminApi,\n type AccountRow,\n type AccountStage,\n} from '@withpotter/lib/admin-api';\nimport {\n ColumnPicker,\n FilterDates,\n FilterSearch,\n Filters,\n type ColumnOption,\n} from '../_ui';\nimport { usePlatformAccounts } from '../hooks';\nimport { AccountPanel, type AccountSummary } from './account-panel';\nimport { ListCard, ListPager } from '../_ui/list-card';\nimport { ACTIVE_HINT, kycLabel, STAGE_LABELS } from './account-stage';\nimport { AccountStageFilter } from './account-stage-filter';\nimport { Hint } from './hint';\nimport { AccountListRow, PROVIDER_LABELS } from './account-row';\n\nexport { PROVIDER_LABELS } from './account-row';\n\nconst OPTIONAL_COLUMNS: ColumnOption[] = [\n { key: 'provider', label: 'Provider' },\n { key: 'kyc', label: 'KYC / KYB' },\n { key: 'stage', label: 'Stage' },\n { key: 'emailVerified', label: 'Email verified' },\n { key: 'workspaces', label: 'Workspaces' },\n { key: 'lastLogin', label: 'Last login' },\n { key: 'joined', label: 'Joined' },\n { key: 'firstSale', label: 'First sale' },\n { key: 'activeSessions', label: 'Active sessions' },\n { key: 'lastSeen', label: 'Last seen' },\n];\nconst DEFAULT_VISIBLE = [\n 'provider',\n 'kyc',\n 'stage',\n 'emailVerified',\n 'workspaces',\n 'lastLogin',\n 'joined',\n];\nconst COLUMNS_KEY = 'adm-accounts-columns';\n\nfunction readVisibleColumns(): Set<string> {\n if (typeof window === 'undefined') return new Set(DEFAULT_VISIBLE);\n try {\n const raw = window.localStorage.getItem(COLUMNS_KEY);\n return raw\n ? new Set(raw.split(',').filter(Boolean))\n : new Set(DEFAULT_VISIBLE);\n } catch {\n return new Set(DEFAULT_VISIBLE);\n }\n}\n\nfunction writeVisibleColumns(cols: Set<string>): void {\n try {\n window.localStorage.setItem(COLUMNS_KEY, [...cols].join(','));\n } catch {\n /* storage unavailable (private mode / quota) — non-fatal */\n }\n}\n\nconst day = (iso?: string | null) =>\n iso ? new Date(iso).toLocaleDateString() : '';\n\n/** One row's values, in the same column order the table renders. */\nfunction toCsvRow(a: AccountRow, visible: Set<string>): string[] {\n const cells: string[] = [a.name ?? '', a.email];\n if (visible.has('provider')) {\n cells.push(PROVIDER_LABELS[a.authProvider] ?? a.authProvider);\n }\n cells.push(a.status);\n if (visible.has('kyc')) cells.push(kycLabel(a.kycStatus) ?? '');\n if (visible.has('stage')) cells.push(a.stage ? STAGE_LABELS[a.stage] : '');\n if (visible.has('emailVerified')) {\n cells.push(a.emailVerified ? 'Verified' : 'Unverified');\n }\n if (visible.has('workspaces')) cells.push(String(a.workspaceCount ?? 0));\n if (visible.has('lastLogin')) cells.push(day(a.lastLoginAt) || 'Never');\n if (visible.has('joined')) cells.push(day(a.createdAt));\n if (visible.has('firstSale')) cells.push(day(a.firstSaleAt) || 'None yet');\n if (visible.has('activeSessions')) cells.push(String(a.activeSessions ?? 0));\n if (visible.has('lastSeen')) cells.push(day(a.lastSeenAt) || 'Never');\n return cells;\n}\n\n/** Every account, searchable and filterable by stage and join date. */\nexport function AccountsTable({\n selected,\n onSelect,\n}: {\n selected: AccountSummary | null;\n onSelect: (account: AccountSummary | null) => void;\n}) {\n const [search, setSearch] = useState('');\n const [joinedFrom, setJoinedFrom] = useState('');\n const [joinedTo, setJoinedTo] = useState('');\n const [stage, setStage] = useState<AccountStage | ''>('');\n const [page, setPage] = useState(1);\n const [visible, setVisible] = useState<Set<string>>(readVisibleColumns);\n const [exporting, setExporting] = useState(false);\n const { data, isLoading, isError, refetch } = usePlatformAccounts({\n page,\n pageSize: 20,\n search: search || undefined,\n joinedFrom: joinedFrom || undefined,\n joinedTo: joinedTo || undefined,\n stage: stage || undefined,\n });\n // Show the freshest copy of the selected account after an action refetches.\n const current = data?.rows.find((row) => row.id === selected?.id) ?? selected;\n const totalPages = data\n ? Math.max(1, Math.ceil(data.total / data.pageSize))\n : 1;\n const filter = (set: (v: string) => void) => (v: string) => {\n set(v);\n setPage(1);\n };\n\n const changeColumns = (next: Set<string>) => {\n setVisible(next);\n writeVisibleColumns(next);\n };\n\n // Every account matching the current search/join-date filter, not just\n // this page — a export of \"what I'm looking at\" that stops at 20 rows\n // because of pagination would just be confusing.\n const exportCsv = async () => {\n setExporting(true);\n try {\n const page = await platformAdminApi.accounts({\n pageSize: 5000,\n search: search || undefined,\n joinedFrom: joinedFrom || undefined,\n joinedTo: joinedTo || undefined,\n stage: stage || undefined,\n });\n const header = ['Name', 'Email'];\n if (visible.has('provider')) header.push('Provider');\n header.push('Status');\n if (visible.has('kyc')) header.push('KYC / KYB');\n if (visible.has('stage')) header.push('Stage');\n if (visible.has('emailVerified')) header.push('Email verified');\n if (visible.has('workspaces')) header.push('Workspaces');\n if (visible.has('lastLogin')) header.push('Last login');\n if (visible.has('joined')) header.push('Joined');\n if (visible.has('firstSale')) header.push('First sale');\n if (visible.has('activeSessions')) header.push('Active sessions');\n if (visible.has('lastSeen')) header.push('Last seen');\n const rows = [header, ...page.rows.map((a) => toCsvRow(a, visible))];\n const url = URL.createObjectURL(\n new Blob([toCsv(rows)], { type: 'text/csv' }),\n );\n const a = document.createElement('a');\n a.href = url;\n a.download = 'accounts.csv';\n a.click();\n URL.revokeObjectURL(url);\n } finally {\n setExporting(false);\n }\n };\n\n const columns = 3 + visible.size; // Account + Status + Actions, plus optional\n\n return (\n <>\n <ListCard\n title=\"All accounts\"\n count={data?.total}\n columns={columns}\n minWidth={880}\n isLoading={isLoading}\n isError={isError}\n onRetry={() => void refetch()}\n errorTitle=\"Failed to load accounts.\"\n isEmpty={data?.rows.length === 0}\n emptyTitle=\"No accounts match these filters.\"\n controls={\n <Filters label=\"Filter accounts\">\n <FilterSearch\n value={search}\n onChange={filter(setSearch)}\n placeholder=\"Search by email or name…\"\n />\n <AccountStageFilter\n value={stage}\n onChange={(v: AccountStage | '') => {\n setStage(v);\n setPage(1);\n }}\n />\n <FilterDates\n label=\"Joined\"\n fromLabel=\"Joined from\"\n toLabel=\"Joined to\"\n from={joinedFrom}\n to={joinedTo}\n onFrom={filter(setJoinedFrom)}\n onTo={filter(setJoinedTo)}\n />\n {(joinedFrom || joinedTo) && (\n <Button\n variant=\"ghost\"\n onClick={() => {\n setJoinedFrom('');\n setJoinedTo('');\n setPage(1);\n }}\n >\n Clear\n </Button>\n )}\n <ColumnPicker\n options={OPTIONAL_COLUMNS}\n visible={visible}\n onChange={changeColumns}\n />\n <OverflowMenu label=\"Export\">\n <OverflowItem\n onClick={() => void exportCsv()}\n disabled={exporting || !data?.total}\n >\n {exporting ? 'Exporting…' : 'Export CSV'}\n </OverflowItem>\n </OverflowMenu>\n </Filters>\n }\n head={\n <tr>\n <Th>Account</Th>\n {visible.has('provider') && <Th>Provider</Th>}\n <Th>Status</Th>\n {visible.has('kyc') && <Th>KYC / KYB</Th>}\n {visible.has('stage') && (\n <Th>\n Stage\n <Hint label={ACTIVE_HINT} />\n </Th>\n )}\n {visible.has('emailVerified') && <Th>Email</Th>}\n {visible.has('workspaces') && <Th align=\"right\">Workspaces</Th>}\n {visible.has('lastLogin') && <Th>Last login</Th>}\n {visible.has('joined') && <Th>Joined</Th>}\n {visible.has('firstSale') && <Th>First sale</Th>}\n {visible.has('activeSessions') && <Th align=\"right\">Sessions</Th>}\n {visible.has('lastSeen') && <Th>Last seen</Th>}\n <Th align=\"right\">\n <span className=\"sr-only\">Actions</span>\n </Th>\n </tr>\n }\n footer={\n <ListPager\n page={page}\n totalPages={totalPages}\n total={data?.total}\n noun=\"accounts\"\n onPage={setPage}\n />\n }\n >\n {data?.rows.map((a) => (\n <AccountListRow\n key={a.id}\n a={a}\n active={a.id === selected?.id}\n onOpen={() => onSelect(a)}\n visible={visible}\n />\n ))}\n </ListCard>\n <AccountPanel account={current} onClose={() => onSelect(null)} />\n </>\n );\n}\n"],"mappings":"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","names":["PROVIDER_LABELS","page","a"]}
1
+ {"version":3,"sources":["../../../src/admin/accounts/accounts-table.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { Button, OverflowItem, OverflowMenu, Th } from '@withpotter/ui-kit';\nimport { toCsv } from '@withpotter/lib/csv';\nimport { platformAdminApi, type AccountRow } from '@withpotter/lib/admin-api';\nimport {\n ColumnPicker,\n FilterDates,\n FilterSearch,\n Filters,\n type ColumnOption,\n} from '../_ui';\nimport { usePlatformAccounts } from '../hooks';\nimport { AccountPanel, type AccountSummary } from './account-panel';\nimport { ListCard, ListPager } from '../_ui/list-card';\nimport { AccountListRow, PROVIDER_LABELS } from './account-row';\n\nexport { PROVIDER_LABELS } from './account-row';\n\nconst OPTIONAL_COLUMNS: ColumnOption[] = [\n { key: 'provider', label: 'Provider' },\n { key: 'emailVerified', label: 'Email verified' },\n { key: 'workspaces', label: 'Workspaces' },\n { key: 'lastLogin', label: 'Last login' },\n { key: 'joined', label: 'Joined' },\n { key: 'mfa', label: 'MFA enabled' },\n { key: 'activeSessions', label: 'Active sessions' },\n { key: 'lastSeen', label: 'Last seen' },\n];\nconst DEFAULT_VISIBLE = [\n 'provider',\n 'emailVerified',\n 'workspaces',\n 'lastLogin',\n 'joined',\n];\nconst COLUMNS_KEY = 'adm-accounts-columns';\n\nfunction readVisibleColumns(): Set<string> {\n if (typeof window === 'undefined') return new Set(DEFAULT_VISIBLE);\n try {\n const raw = window.localStorage.getItem(COLUMNS_KEY);\n return raw\n ? new Set(raw.split(',').filter(Boolean))\n : new Set(DEFAULT_VISIBLE);\n } catch {\n return new Set(DEFAULT_VISIBLE);\n }\n}\n\nfunction writeVisibleColumns(cols: Set<string>): void {\n try {\n window.localStorage.setItem(COLUMNS_KEY, [...cols].join(','));\n } catch {\n /* storage unavailable (private mode / quota) — non-fatal */\n }\n}\n\nconst day = (iso?: string | null) =>\n iso ? new Date(iso).toLocaleDateString() : '';\n\n/** One row's values, in the same column order the table renders. */\nfunction toCsvRow(a: AccountRow, visible: Set<string>): string[] {\n const cells: string[] = [a.name ?? '', a.email];\n if (visible.has('provider')) {\n cells.push(PROVIDER_LABELS[a.authProvider] ?? a.authProvider);\n }\n cells.push(a.status);\n if (visible.has('emailVerified')) {\n cells.push(a.emailVerified ? 'Verified' : 'Unverified');\n }\n if (visible.has('workspaces')) cells.push(String(a.workspaceCount ?? 0));\n if (visible.has('lastLogin')) cells.push(day(a.lastLoginAt) || 'Never');\n if (visible.has('joined')) cells.push(day(a.createdAt));\n if (visible.has('mfa')) cells.push(a.mfaEnabled ? 'Yes' : 'No');\n if (visible.has('activeSessions')) cells.push(String(a.activeSessions ?? 0));\n if (visible.has('lastSeen')) cells.push(day(a.lastSeenAt) || 'Never');\n return cells;\n}\n\n/** Every account, searchable and filterable by join date. */\nexport function AccountsTable({\n selected,\n onSelect,\n}: {\n selected: AccountSummary | null;\n onSelect: (account: AccountSummary | null) => void;\n}) {\n const [search, setSearch] = useState('');\n const [joinedFrom, setJoinedFrom] = useState('');\n const [joinedTo, setJoinedTo] = useState('');\n const [page, setPage] = useState(1);\n const [visible, setVisible] = useState<Set<string>>(readVisibleColumns);\n const [exporting, setExporting] = useState(false);\n const { data, isLoading, isError, refetch } = usePlatformAccounts({\n page,\n pageSize: 20,\n search: search || undefined,\n joinedFrom: joinedFrom || undefined,\n joinedTo: joinedTo || undefined,\n });\n // Show the freshest copy of the selected account after an action refetches.\n const current = data?.rows.find((row) => row.id === selected?.id) ?? selected;\n const totalPages = data\n ? Math.max(1, Math.ceil(data.total / data.pageSize))\n : 1;\n const filter = (set: (v: string) => void) => (v: string) => {\n set(v);\n setPage(1);\n };\n\n const changeColumns = (next: Set<string>) => {\n setVisible(next);\n writeVisibleColumns(next);\n };\n\n // Every account matching the current search/join-date filter, not just\n // this page — a export of \"what I'm looking at\" that stops at 20 rows\n // because of pagination would just be confusing.\n const exportCsv = async () => {\n setExporting(true);\n try {\n const page = await platformAdminApi.accounts({\n pageSize: 5000,\n search: search || undefined,\n joinedFrom: joinedFrom || undefined,\n joinedTo: joinedTo || undefined,\n });\n const header = ['Name', 'Email'];\n if (visible.has('provider')) header.push('Provider');\n header.push('Status');\n if (visible.has('emailVerified')) header.push('Email verified');\n if (visible.has('workspaces')) header.push('Workspaces');\n if (visible.has('lastLogin')) header.push('Last login');\n if (visible.has('joined')) header.push('Joined');\n if (visible.has('mfa')) header.push('MFA enabled');\n if (visible.has('activeSessions')) header.push('Active sessions');\n if (visible.has('lastSeen')) header.push('Last seen');\n const rows = [header, ...page.rows.map((a) => toCsvRow(a, visible))];\n const url = URL.createObjectURL(\n new Blob([toCsv(rows)], { type: 'text/csv' }),\n );\n const a = document.createElement('a');\n a.href = url;\n a.download = 'accounts.csv';\n a.click();\n URL.revokeObjectURL(url);\n } finally {\n setExporting(false);\n }\n };\n\n const columns = 3 + visible.size; // Account + Status + Actions, plus optional\n\n return (\n <>\n <ListCard\n title=\"All accounts\"\n count={data?.total}\n columns={columns}\n minWidth={880}\n isLoading={isLoading}\n isError={isError}\n onRetry={() => void refetch()}\n errorTitle=\"Failed to load accounts.\"\n isEmpty={data?.rows.length === 0}\n emptyTitle=\"No accounts match this search or join date.\"\n controls={\n <Filters label=\"Filter accounts\">\n <FilterSearch\n value={search}\n onChange={filter(setSearch)}\n placeholder=\"Search by email or name…\"\n />\n <FilterDates\n label=\"Joined\"\n fromLabel=\"Joined from\"\n toLabel=\"Joined to\"\n from={joinedFrom}\n to={joinedTo}\n onFrom={filter(setJoinedFrom)}\n onTo={filter(setJoinedTo)}\n />\n {(joinedFrom || joinedTo) && (\n <Button\n variant=\"ghost\"\n onClick={() => {\n setJoinedFrom('');\n setJoinedTo('');\n setPage(1);\n }}\n >\n Clear\n </Button>\n )}\n <ColumnPicker\n options={OPTIONAL_COLUMNS}\n visible={visible}\n onChange={changeColumns}\n />\n <OverflowMenu label=\"Export\">\n <OverflowItem\n onClick={() => void exportCsv()}\n disabled={exporting || !data?.total}\n >\n {exporting ? 'Exporting…' : 'Export CSV'}\n </OverflowItem>\n </OverflowMenu>\n </Filters>\n }\n head={\n <tr>\n <Th>Account</Th>\n {visible.has('provider') && <Th>Provider</Th>}\n <Th>Status</Th>\n {visible.has('emailVerified') && <Th>Email</Th>}\n {visible.has('workspaces') && <Th align=\"right\">Workspaces</Th>}\n {visible.has('lastLogin') && <Th>Last login</Th>}\n {visible.has('joined') && <Th>Joined</Th>}\n {visible.has('mfa') && <Th>MFA</Th>}\n {visible.has('activeSessions') && <Th align=\"right\">Sessions</Th>}\n {visible.has('lastSeen') && <Th>Last seen</Th>}\n <Th align=\"right\">\n <span className=\"sr-only\">Actions</span>\n </Th>\n </tr>\n }\n footer={\n <ListPager\n page={page}\n totalPages={totalPages}\n total={data?.total}\n noun=\"accounts\"\n onPage={setPage}\n />\n }\n >\n {data?.rows.map((a) => (\n <AccountListRow\n key={a.id}\n a={a}\n active={a.id === selected?.id}\n onOpen={() => onSelect(a)}\n visible={visible}\n />\n ))}\n </ListCard>\n <AccountPanel account={current} onClose={() => onSelect(null)} />\n </>\n );\n}\n"],"mappings":"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","names":["PROVIDER_LABELS","page","a"]}
@@ -45,8 +45,8 @@ function AccountsWorkspace() {
45
45
  /* @__PURE__ */ jsx(
46
46
  PageHeader,
47
47
  {
48
- title: "User accounts",
49
- description: `Everyone who signs in to ${ADMIN_NAME}, across all workspaces.`,
48
+ title: "People",
49
+ description: `Every person registered on ${ADMIN_NAME}.`,
50
50
  actions: tab === "overview" && /* @__PURE__ */ jsx("div", { className: "adm-toolbar", children: /* @__PURE__ */ jsx(
51
51
  PeriodSelector,
52
52
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/accounts/accounts-workspace.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo, useState } from 'react';\nimport { ADMIN_NAME } from '../_shell/admin-brand';\nimport {\n PageHeader,\n PageTabs,\n PeriodSelector,\n customRange,\n periodPhrase,\n rangeFromKey,\n usePersistedDateRange,\n useTabParam,\n} from '../_ui';\nimport { usePlatformAccountsAnalytics } from '../hooks';\nimport { AccountPanel, type AccountSummary } from './account-panel';\nimport { AccountsAnalytics } from './accounts-analytics';\nimport { AccountsTable } from './accounts-table';\n\nconst TABS = [\n { id: 'overview', label: 'Overview' },\n { id: 'all', label: 'All accounts' },\n] as const;\n\nconst KEYS = TABS.map((t) => t.id);\n\n/** People: the period's picture on Overview, every account on its own tab. */\nexport function AccountsWorkspace() {\n const [tab, setTab] = useTabParam(KEYS);\n const [selected, setSelected] = useState<AccountSummary | null>(null);\n const {\n rangeKey,\n setRangeKey,\n customStart,\n customEnd,\n onCustomChange,\n compare,\n } = usePersistedDateRange();\n const range = useMemo(\n () =>\n rangeKey === 'custom'\n ? customRange(customStart, customEnd)\n : rangeFromKey(rangeKey),\n [rangeKey, customStart, customEnd],\n );\n const analytics = usePlatformAccountsAnalytics({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n const span = periodPhrase(range);\n\n return (\n <div>\n <PageHeader\n title=\"User accounts\"\n description={`Everyone who signs in to ${ADMIN_NAME}, across all workspaces.`}\n actions={\n tab === 'overview' && (\n <div className=\"adm-toolbar\">\n <PeriodSelector\n value={rangeKey}\n onChange={setRangeKey}\n customStart={customStart}\n customEnd={customEnd}\n onCustomChange={onCustomChange}\n />\n </div>\n )\n }\n />\n <PageTabs tabs={TABS} active={tab} onChange={setTab} />\n\n {tab === 'overview' && (\n <>\n <AccountsAnalytics\n data={analytics.data}\n isLoading={analytics.isLoading}\n isError={analytics.isError}\n onRetry={() => void analytics.refetch()}\n caption={`${span[0].toUpperCase()}${span.slice(1)}`}\n span={span}\n compare={compare}\n onSelectAccount={setSelected}\n />\n <AccountPanel account={selected} onClose={() => setSelected(null)} />\n </>\n )}\n\n {tab === 'all' && (\n <AccountsTable selected={selected} onSelect={setSelected} />\n )}\n </div>\n );\n}\n"],"mappings":";AA2Dc,SAcN,UAdM,KAcN,YAdM;AAzDd,SAAS,SAAS,gBAAgB;AAClC,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,oCAAoC;AAC7C,SAAS,oBAAyC;AAClD,SAAS,yBAAyB;AAClC,SAAS,qBAAqB;AAE9B,MAAM,OAAO;AAAA,EACX,EAAE,IAAI,YAAY,OAAO,WAAW;AAAA,EACpC,EAAE,IAAI,OAAO,OAAO,eAAe;AACrC;AAEA,MAAM,OAAO,KAAK,IAAI,CAAC,MAAM,EAAE,EAAE;AAG1B,SAAS,oBAAoB;AAClC,QAAM,CAAC,KAAK,MAAM,IAAI,YAAY,IAAI;AACtC,QAAM,CAAC,UAAU,WAAW,IAAI,SAAgC,IAAI;AACpE,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,sBAAsB;AAC1B,QAAM,QAAQ;AAAA,IACZ,MACE,aAAa,WACT,YAAY,aAAa,SAAS,IAClC,aAAa,QAAQ;AAAA,IAC3B,CAAC,UAAU,aAAa,SAAS;AAAA,EACnC;AACA,QAAM,YAAY,6BAA6B;AAAA,IAC7C,WAAW,MAAM;AAAA,IACjB,SAAS,MAAM;AAAA,EACjB,CAAC;AACD,QAAM,OAAO,aAAa,KAAK;AAE/B,SACE,qBAAC,SACC;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,aAAa,4BAA4B,UAAU;AAAA,QACnD,SACE,QAAQ,cACN,oBAAC,SAAI,WAAU,eACb;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,YACP,UAAU;AAAA,YACV;AAAA,YACA;AAAA,YACA;AAAA;AAAA,QACF,GACF;AAAA;AAAA,IAGN;AAAA,IACA,oBAAC,YAAS,MAAM,MAAM,QAAQ,KAAK,UAAU,QAAQ;AAAA,IAEpD,QAAQ,cACP,iCACE;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAM,UAAU;AAAA,UAChB,WAAW,UAAU;AAAA,UACrB,SAAS,UAAU;AAAA,UACnB,SAAS,MAAM,KAAK,UAAU,QAAQ;AAAA,UACtC,SAAS,GAAG,KAAK,CAAC,EAAE,YAAY,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC;AAAA,UACjD;AAAA,UACA;AAAA,UACA,iBAAiB;AAAA;AAAA,MACnB;AAAA,MACA,oBAAC,gBAAa,SAAS,UAAU,SAAS,MAAM,YAAY,IAAI,GAAG;AAAA,OACrE;AAAA,IAGD,QAAQ,SACP,oBAAC,iBAAc,UAAoB,UAAU,aAAa;AAAA,KAE9D;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/accounts/accounts-workspace.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo, useState } from 'react';\nimport { ADMIN_NAME } from '../_shell/admin-brand';\nimport {\n PageHeader,\n PageTabs,\n PeriodSelector,\n customRange,\n periodPhrase,\n rangeFromKey,\n usePersistedDateRange,\n useTabParam,\n} from '../_ui';\nimport { usePlatformAccountsAnalytics } from '../hooks';\nimport { AccountPanel, type AccountSummary } from './account-panel';\nimport { AccountsAnalytics } from './accounts-analytics';\nimport { AccountsTable } from './accounts-table';\n\nconst TABS = [\n { id: 'overview', label: 'Overview' },\n { id: 'all', label: 'All accounts' },\n] as const;\n\nconst KEYS = TABS.map((t) => t.id);\n\n/** People: the period's picture on Overview, every account on its own tab. */\nexport function AccountsWorkspace() {\n const [tab, setTab] = useTabParam(KEYS);\n const [selected, setSelected] = useState<AccountSummary | null>(null);\n const {\n rangeKey,\n setRangeKey,\n customStart,\n customEnd,\n onCustomChange,\n compare,\n } = usePersistedDateRange();\n const range = useMemo(\n () =>\n rangeKey === 'custom'\n ? customRange(customStart, customEnd)\n : rangeFromKey(rangeKey),\n [rangeKey, customStart, customEnd],\n );\n const analytics = usePlatformAccountsAnalytics({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n const span = periodPhrase(range);\n\n return (\n <div>\n <PageHeader\n title=\"People\"\n description={`Every person registered on ${ADMIN_NAME}.`}\n actions={\n tab === 'overview' && (\n <div className=\"adm-toolbar\">\n <PeriodSelector\n value={rangeKey}\n onChange={setRangeKey}\n customStart={customStart}\n customEnd={customEnd}\n onCustomChange={onCustomChange}\n />\n </div>\n )\n }\n />\n <PageTabs tabs={TABS} active={tab} onChange={setTab} />\n\n {tab === 'overview' && (\n <>\n <AccountsAnalytics\n data={analytics.data}\n isLoading={analytics.isLoading}\n isError={analytics.isError}\n onRetry={() => void analytics.refetch()}\n caption={`${span[0].toUpperCase()}${span.slice(1)}`}\n span={span}\n compare={compare}\n onSelectAccount={setSelected}\n />\n <AccountPanel account={selected} onClose={() => setSelected(null)} />\n </>\n )}\n\n {tab === 'all' && (\n <AccountsTable selected={selected} onSelect={setSelected} />\n )}\n </div>\n );\n}\n"],"mappings":";AA2Dc,SAcN,UAdM,KAcN,YAdM;AAzDd,SAAS,SAAS,gBAAgB;AAClC,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,oCAAoC;AAC7C,SAAS,oBAAyC;AAClD,SAAS,yBAAyB;AAClC,SAAS,qBAAqB;AAE9B,MAAM,OAAO;AAAA,EACX,EAAE,IAAI,YAAY,OAAO,WAAW;AAAA,EACpC,EAAE,IAAI,OAAO,OAAO,eAAe;AACrC;AAEA,MAAM,OAAO,KAAK,IAAI,CAAC,MAAM,EAAE,EAAE;AAG1B,SAAS,oBAAoB;AAClC,QAAM,CAAC,KAAK,MAAM,IAAI,YAAY,IAAI;AACtC,QAAM,CAAC,UAAU,WAAW,IAAI,SAAgC,IAAI;AACpE,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,sBAAsB;AAC1B,QAAM,QAAQ;AAAA,IACZ,MACE,aAAa,WACT,YAAY,aAAa,SAAS,IAClC,aAAa,QAAQ;AAAA,IAC3B,CAAC,UAAU,aAAa,SAAS;AAAA,EACnC;AACA,QAAM,YAAY,6BAA6B;AAAA,IAC7C,WAAW,MAAM;AAAA,IACjB,SAAS,MAAM;AAAA,EACjB,CAAC;AACD,QAAM,OAAO,aAAa,KAAK;AAE/B,SACE,qBAAC,SACC;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,aAAa,8BAA8B,UAAU;AAAA,QACrD,SACE,QAAQ,cACN,oBAAC,SAAI,WAAU,eACb;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,YACP,UAAU;AAAA,YACV;AAAA,YACA;AAAA,YACA;AAAA;AAAA,QACF,GACF;AAAA;AAAA,IAGN;AAAA,IACA,oBAAC,YAAS,MAAM,MAAM,QAAQ,KAAK,UAAU,QAAQ;AAAA,IAEpD,QAAQ,cACP,iCACE;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAM,UAAU;AAAA,UAChB,WAAW,UAAU;AAAA,UACrB,SAAS,UAAU;AAAA,UACnB,SAAS,MAAM,KAAK,UAAU,QAAQ;AAAA,UACtC,SAAS,GAAG,KAAK,CAAC,EAAE,YAAY,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC;AAAA,UACjD;AAAA,UACA;AAAA,UACA,iBAAiB;AAAA;AAAA,MACnB;AAAA,MACA,oBAAC,gBAAa,SAAS,UAAU,SAAS,MAAM,YAAY,IAAI,GAAG;AAAA,OACrE;AAAA,IAGD,QAAQ,SACP,oBAAC,iBAAc,UAAoB,UAAU,aAAa;AAAA,KAE9D;AAEJ;","names":[]}
@@ -10,9 +10,15 @@ const SESSION_KEYS = [
10
10
  "tenant",
11
11
  "pb_workspaces_cache"
12
12
  ];
13
+ const encode = (value) => btoa(JSON.stringify(value)).replace(/=+$/, "").replace(/\+/g, "-").replace(/\//g, "_");
13
14
  async function seedOwnerSession() {
14
15
  const owner = await apiFetch("/accounts/me");
16
+ const exp = Math.floor(Date.now() / 1e3) + 60 * 60;
15
17
  clearOwnerSession();
18
+ localStorage.setItem(
19
+ "pb_token",
20
+ `${encode({ alg: "none", typ: "JWT" })}.${encode({ sub: owner.id, email: owner.email, exp, ownerView: true })}.owner-view`
21
+ );
16
22
  localStorage.setItem("pb_account_id", owner.id);
17
23
  localStorage.setItem("pb_user", JSON.stringify(owner));
18
24
  localStorage.setItem("userEmail", owner.email);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/accounts/owner-session.ts"],"sourcesContent":["import { apiFetch } from '@withpotter/lib/api';\n\n/**\n * Hand the merchant dashboard the owner's session after \"View as owner\".\n *\n * The API's impersonation sets the owner's session as an httpOnly cookie on\n * this origin, which every merchant API call already authenticates with. The\n * merchant app, though, decides whether anyone is signed in from the cached\n * account (`pb_user`); finding nothing, it sent the tab to /login and the Admin\n * host bounced it home. So we read the owner through the cookie and seed that.\n */\nconst SESSION_KEYS = [\n 'pb_token',\n 'pb_user',\n 'pb_account_id',\n 'userEmail',\n 'organizationId',\n 'currentWorkspace',\n 'pb_current_tenant',\n 'tenant',\n 'pb_workspaces_cache',\n];\n\ninterface OwnerAccount {\n id: string;\n email: string;\n [key: string]: unknown;\n}\n\nexport async function seedOwnerSession(): Promise<void> {\n const owner = await apiFetch<OwnerAccount>('/accounts/me');\n\n clearOwnerSession();\n localStorage.setItem('pb_account_id', owner.id);\n localStorage.setItem('pb_user', JSON.stringify(owner));\n localStorage.setItem('userEmail', owner.email);\n}\n\n/** Drops any merchant session cached in this browser (and its workspace). */\nexport function clearOwnerSession(): void {\n try {\n SESSION_KEYS.forEach((key) => localStorage.removeItem(key));\n } catch {\n // Storage blocked: nothing cached to clear.\n }\n}\n"],"mappings":"AAAA,SAAS,gBAAgB;AAWzB,MAAM,eAAe;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAQA,eAAsB,mBAAkC;AACtD,QAAM,QAAQ,MAAM,SAAuB,cAAc;AAEzD,oBAAkB;AAClB,eAAa,QAAQ,iBAAiB,MAAM,EAAE;AAC9C,eAAa,QAAQ,WAAW,KAAK,UAAU,KAAK,CAAC;AACrD,eAAa,QAAQ,aAAa,MAAM,KAAK;AAC/C;AAGO,SAAS,oBAA0B;AACxC,MAAI;AACF,iBAAa,QAAQ,CAAC,QAAQ,aAAa,WAAW,GAAG,CAAC;AAAA,EAC5D,QAAQ;AAAA,EAER;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/accounts/owner-session.ts"],"sourcesContent":["import { apiFetch } from '@withpotter/lib/api';\n\n/**\n * Hand the merchant dashboard the owner's session after \"View as owner\".\n *\n * The API's impersonation sets the owner's session as an httpOnly cookie on\n * this origin, which every merchant API call already authenticates with. The\n * merchant app, though, decides whether anyone is signed in from localStorage\n * (`pb_token`, `pb_user`); finding nothing, it sent the tab to /login and the\n * Admin host bounced it home. So we read the owner through the cookie and seed\n * what the app looks for — the token is only a local marker with the cookie's\n * one-hour expiry; the server never trusts it, because the cookie wins.\n */\nconst SESSION_KEYS = [\n 'pb_token',\n 'pb_user',\n 'pb_account_id',\n 'userEmail',\n 'organizationId',\n 'currentWorkspace',\n 'pb_current_tenant',\n 'tenant',\n 'pb_workspaces_cache',\n];\n\nconst encode = (value: object) =>\n btoa(JSON.stringify(value))\n .replace(/=+$/, '')\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_');\n\ninterface OwnerAccount {\n id: string;\n email: string;\n [key: string]: unknown;\n}\n\nexport async function seedOwnerSession(): Promise<void> {\n const owner = await apiFetch<OwnerAccount>('/accounts/me');\n const exp = Math.floor(Date.now() / 1000) + 60 * 60;\n\n clearOwnerSession();\n localStorage.setItem(\n 'pb_token',\n `${encode({ alg: 'none', typ: 'JWT' })}.${encode({ sub: owner.id, email: owner.email, exp, ownerView: true })}.owner-view`,\n );\n localStorage.setItem('pb_account_id', owner.id);\n localStorage.setItem('pb_user', JSON.stringify(owner));\n localStorage.setItem('userEmail', owner.email);\n}\n\n/** Drops any merchant session cached in this browser (and its workspace). */\nexport function clearOwnerSession(): void {\n try {\n SESSION_KEYS.forEach((key) => localStorage.removeItem(key));\n } catch {\n // Storage blocked: nothing cached to clear.\n }\n}\n"],"mappings":"AAAA,SAAS,gBAAgB;AAazB,MAAM,eAAe;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,MAAM,SAAS,CAAC,UACd,KAAK,KAAK,UAAU,KAAK,CAAC,EACvB,QAAQ,OAAO,EAAE,EACjB,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,GAAG;AAQvB,eAAsB,mBAAkC;AACtD,QAAM,QAAQ,MAAM,SAAuB,cAAc;AACzD,QAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,IAAI,KAAK;AAEjD,oBAAkB;AAClB,eAAa;AAAA,IACX;AAAA,IACA,GAAG,OAAO,EAAE,KAAK,QAAQ,KAAK,MAAM,CAAC,CAAC,IAAI,OAAO,EAAE,KAAK,MAAM,IAAI,OAAO,MAAM,OAAO,KAAK,WAAW,KAAK,CAAC,CAAC;AAAA,EAC/G;AACA,eAAa,QAAQ,iBAAiB,MAAM,EAAE;AAC9C,eAAa,QAAQ,WAAW,KAAK,UAAU,KAAK,CAAC;AACrD,eAAa,QAAQ,aAAa,MAAM,KAAK;AAC/C;AAGO,SAAS,oBAA0B;AACxC,MAAI;AACF,iBAAa,QAAQ,CAAC,QAAQ,aAAa,WAAW,GAAG,CAAC;AAAA,EAC5D,QAAQ;AAAA,EAER;AACF;","names":[]}
@@ -65,20 +65,3 @@
65
65
  font-variant-numeric: tabular-nums;
66
66
  cursor: default;
67
67
  }
68
-
69
- .hint {
70
- display: inline-flex;
71
- align-items: center;
72
- margin-left: 4px;
73
- padding: 0;
74
- border: 0;
75
- background: none;
76
- vertical-align: middle;
77
- color: var(--text-tertiary);
78
- cursor: help;
79
- }
80
-
81
- .hint:hover,
82
- .hint:focus-visible {
83
- color: var(--text-primary);
84
- }
@@ -23,12 +23,6 @@ const TONE_OF = {
23
23
  completed: "complete",
24
24
  closed: "complete",
25
25
  draft: "draft",
26
- // Account stages read as progress: grey, blue, amber, teal, green.
27
- stage_signed_up: "neutral",
28
- stage_verified: "processing",
29
- stage_onboarding: "pending",
30
- stage_kyc_approved: "ahead",
31
- stage_active: "success",
32
26
  deleted: "neutral",
33
27
  rejected: "neutral"
34
28
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/audit/status-pill.tsx"],"sourcesContent":["import styles from './status-pill.module.css';\n\ntype StatusTone =\n | 'success'\n | 'pending'\n | 'failed'\n | 'processing'\n | 'complete'\n | 'draft'\n | 'ahead'\n | 'neutral';\n\nconst TONE_OF: Record<string, StatusTone> = {\n active: 'success',\n verified: 'success',\n approved: 'success',\n resolved: 'success',\n pending: 'pending',\n pending_verification: 'pending',\n unverified: 'pending',\n invited: 'pending',\n paused: 'pending',\n open: 'pending',\n non_renewing: 'pending',\n trialing: 'processing',\n processing: 'processing',\n scheduled: 'processing',\n acknowledged: 'processing',\n suspended: 'failed',\n failed: 'failed',\n flagged: 'failed',\n past_due: 'failed',\n completed: 'complete',\n closed: 'complete',\n draft: 'draft',\n // Account stages read as progress: grey, blue, amber, teal, green.\n stage_signed_up: 'neutral',\n stage_verified: 'processing',\n stage_onboarding: 'pending',\n stage_kyc_approved: 'ahead',\n stage_active: 'success',\n deleted: 'neutral',\n rejected: 'neutral',\n};\n\n/** Words for a status key: `pending_verification` → \"Pending verification\". */\nconst words = (s: string) => {\n const w = s.replace(/[_-]+/g, ' ');\n return w.charAt(0).toUpperCase() + w.slice(1);\n};\n\n/**\n * A status as a 6px dot plus text. Each state reads as itself: live ok,\n * waiting amber, in flight brand blue, stopped red, finished solid grey,\n * draft hollow. Static: nothing pulses.\n */\nexport function StatusPill({\n status,\n label,\n}: {\n status: string;\n label?: string;\n}) {\n const tone = TONE_OF[status.toLowerCase()] ?? 'neutral';\n return (\n <span className={styles.pill} data-tone={tone}>\n <span className={`${styles.dot} adm-status-dot`} aria-hidden />\n {label ?? words(status)}\n </span>\n );\n}\n"],"mappings":"AAiEI,SACE,KADF;AAjEJ,OAAO,YAAY;AAYnB,MAAM,UAAsC;AAAA,EAC1C,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,UAAU;AAAA,EACV,UAAU;AAAA,EACV,SAAS;AAAA,EACT,sBAAsB;AAAA,EACtB,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,cAAc;AAAA,EACd,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,UAAU;AAAA,EACV,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,OAAO;AAAA;AAAA,EAEP,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAChB,kBAAkB;AAAA,EAClB,oBAAoB;AAAA,EACpB,cAAc;AAAA,EACd,SAAS;AAAA,EACT,UAAU;AACZ;AAGA,MAAM,QAAQ,CAAC,MAAc;AAC3B,QAAM,IAAI,EAAE,QAAQ,UAAU,GAAG;AACjC,SAAO,EAAE,OAAO,CAAC,EAAE,YAAY,IAAI,EAAE,MAAM,CAAC;AAC9C;AAOO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AACF,GAGG;AACD,QAAM,OAAO,QAAQ,OAAO,YAAY,CAAC,KAAK;AAC9C,SACE,qBAAC,UAAK,WAAW,OAAO,MAAM,aAAW,MACvC;AAAA,wBAAC,UAAK,WAAW,GAAG,OAAO,GAAG,mBAAmB,eAAW,MAAC;AAAA,IAC5D,SAAS,MAAM,MAAM;AAAA,KACxB;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/audit/status-pill.tsx"],"sourcesContent":["import styles from './status-pill.module.css';\n\ntype StatusTone =\n | 'success'\n | 'pending'\n | 'failed'\n | 'processing'\n | 'complete'\n | 'draft'\n | 'neutral';\n\nconst TONE_OF: Record<string, StatusTone> = {\n active: 'success',\n verified: 'success',\n approved: 'success',\n resolved: 'success',\n pending: 'pending',\n pending_verification: 'pending',\n unverified: 'pending',\n invited: 'pending',\n paused: 'pending',\n open: 'pending',\n non_renewing: 'pending',\n trialing: 'processing',\n processing: 'processing',\n scheduled: 'processing',\n acknowledged: 'processing',\n suspended: 'failed',\n failed: 'failed',\n flagged: 'failed',\n past_due: 'failed',\n completed: 'complete',\n closed: 'complete',\n draft: 'draft',\n deleted: 'neutral',\n rejected: 'neutral',\n};\n\n/** Words for a status key: `pending_verification` → \"Pending verification\". */\nconst words = (s: string) => {\n const w = s.replace(/[_-]+/g, ' ');\n return w.charAt(0).toUpperCase() + w.slice(1);\n};\n\n/**\n * A status as a 6px dot plus text. Each state reads as itself: live ok,\n * waiting amber, in flight brand blue, stopped red, finished solid grey,\n * draft hollow. Static: nothing pulses.\n */\nexport function StatusPill({\n status,\n label,\n}: {\n status: string;\n label?: string;\n}) {\n const tone = TONE_OF[status.toLowerCase()] ?? 'neutral';\n return (\n <span className={styles.pill} data-tone={tone}>\n <span className={`${styles.dot} adm-status-dot`} aria-hidden />\n {label ?? words(status)}\n </span>\n );\n}\n"],"mappings":"AA0DI,SACE,KADF;AA1DJ,OAAO,YAAY;AAWnB,MAAM,UAAsC;AAAA,EAC1C,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,UAAU;AAAA,EACV,UAAU;AAAA,EACV,SAAS;AAAA,EACT,sBAAsB;AAAA,EACtB,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,cAAc;AAAA,EACd,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,UAAU;AAAA,EACV,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,SAAS;AAAA,EACT,UAAU;AACZ;AAGA,MAAM,QAAQ,CAAC,MAAc;AAC3B,QAAM,IAAI,EAAE,QAAQ,UAAU,GAAG;AACjC,SAAO,EAAE,OAAO,CAAC,EAAE,YAAY,IAAI,EAAE,MAAM,CAAC;AAC9C;AAOO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AACF,GAGG;AACD,QAAM,OAAO,QAAQ,OAAO,YAAY,CAAC,KAAK;AAC9C,SACE,qBAAC,UAAK,WAAW,OAAO,MAAM,aAAW,MACvC;AAAA,wBAAC,UAAK,WAAW,GAAG,OAAO,GAAG,mBAAmB,eAAW,MAAC;AAAA,IAC5D,SAAS,MAAM,MAAM;AAAA,KACxB;AAEJ;","names":[]}
@@ -27,10 +27,6 @@
27
27
  --tone: var(--brand-500);
28
28
  }
29
29
 
30
- .pill[data-tone='ahead'] {
31
- --tone: color-mix(in srgb, var(--adm-ok) 55%, var(--brand-500));
32
- }
33
-
34
30
  .pill[data-tone='failed'] {
35
31
  --tone: var(--error);
36
32
  }
@@ -70,7 +70,7 @@ function CommerceOverview({
70
70
  previousOrdersTrend: [],
71
71
  compare: false,
72
72
  span,
73
- money: moneyIn(focus ?? "NGN")
73
+ money: moneyIn(focus ?? "USD")
74
74
  }
75
75
  ) : pick }) }) }),
76
76
  /* @__PURE__ */ jsxs("div", { className: "adm-grid", children: [
@@ -79,7 +79,7 @@ function CommerceOverview({
79
79
  {
80
80
  rows: rowFor(data?.topTenantsByCurrency, focus)?.tenants ?? [],
81
81
  loading,
82
- money: moneyIn(focus ?? "NGN"),
82
+ money: moneyIn(focus ?? "USD"),
83
83
  caption,
84
84
  onOpen: onOpenMerchant
85
85
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/commerce/commerce-overview.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo } from 'react';\nimport { Button, ErrorBanner } from '@withpotter/ui-kit';\nimport { usePlatformCockpit } from '../hooks';\nimport {\n GmvOrdersChart,\n PICK_CURRENCY,\n Panel,\n focusCurrency,\n moneyIn,\n periodPhrase,\n rowFor,\n toTrend,\n type DateRange,\n} from '../_ui';\nimport { OverviewKpis } from './overview-kpis';\nimport { SalesByService } from './overview-services';\nimport { TopMerchants } from './overview-top-merchants';\nimport styles from './overview.module.css';\n\n/**\n * Commerce across every merchant, from the one cockpit call: the headline\n * figures, who sells most, where the base sits and who needs a look.\n * Nothing here is fetched per merchant. Money comes back once per currency\n * and is never added across currencies or converted.\n */\nexport function CommerceOverview({\n range,\n currency,\n compare,\n onOpenMerchant,\n}: {\n range: DateRange;\n /** The currency filter's choice; '' shows every currency. */\n currency: string;\n /** False for \"all time\": there is no earlier period to compare with. */\n compare: boolean;\n onOpenMerchant: (id: string) => void;\n}) {\n const cockpit = usePlatformCockpit({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n const data = cockpit.data;\n const currencies = data?.currencies ?? [];\n // A chart or a ranking draws one currency: the filter's, or the only one.\n const focus = focusCurrency(currencies, currency);\n const span = periodPhrase(range);\n const period = `${span[0].toUpperCase()}${span.slice(1)}`;\n const caption = focus ? `${period} · ${focus}` : period;\n\n const series = useMemo(\n () => rowFor(data?.trend.performanceSeriesByCurrency, focus)?.series,\n [data, focus],\n );\n const gmvTrend = useMemo(() => toTrend(series, 'gmv'), [series]);\n const ordersTrend = useMemo(() => toTrend(series, 'orders'), [series]);\n // Sales by service is one mixed figure: only honest with one currency.\n const services =\n data?.salesByService?.length && currencies.length === 1\n ? data.salesByService\n : null;\n\n if (cockpit.isError) {\n return (\n <ErrorBanner>\n The commerce overview could not be loaded.{' '}\n <Button size=\"sm\" variant=\"secondary\" onClick={() => cockpit.refetch()}>\n Try again\n </Button>\n </ErrorBanner>\n );\n }\n\n const loading = cockpit.isLoading || !data;\n const pick = (\n <p className=\"text-sm text-[var(--text-secondary)]\">{PICK_CURRENCY}</p>\n );\n\n return (\n <div className={styles.stack}>\n <OverviewKpis\n data={data}\n loading={loading}\n currency={currency}\n focus={focus}\n compare={compare}\n caption={caption}\n />\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-12\">\n <Panel title=\"GMV & orders\" caption={caption}>\n {focus || loading ? (\n <GmvOrdersChart\n gmvTrend={gmvTrend}\n ordersTrend={ordersTrend}\n previousGmvTrend={[]}\n previousOrdersTrend={[]}\n compare={false}\n span={span}\n money={moneyIn(focus ?? 'NGN')}\n />\n ) : (\n pick\n )}\n </Panel>\n </div>\n </div>\n\n <div className=\"adm-grid\">\n <div className={services ? 'adm-span-7' : 'adm-span-12'}>\n {focus || loading ? (\n <TopMerchants\n rows={rowFor(data?.topTenantsByCurrency, focus)?.tenants ?? []}\n loading={loading}\n money={moneyIn(focus ?? 'NGN')}\n caption={caption}\n onOpen={onOpenMerchant}\n />\n ) : (\n <Panel title=\"Top merchants by sales\" caption={caption}>\n {pick}\n </Panel>\n )}\n </div>\n {services && (\n <div className=\"adm-span-5\">\n <SalesByService\n rows={services}\n loading={loading}\n money={moneyIn(currencies[0])}\n caption={caption}\n />\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":";AAkEM,SAEE,KAFF;AAhEN,SAAS,eAAe;AACxB,SAAS,QAAQ,mBAAmB;AACpC,SAAS,0BAA0B;AACnC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,oBAAoB;AAC7B,SAAS,sBAAsB;AAC/B,SAAS,oBAAoB;AAC7B,OAAO,YAAY;AAQZ,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAOG;AACD,QAAM,UAAU,mBAAmB;AAAA,IACjC,WAAW,MAAM;AAAA,IACjB,SAAS,MAAM;AAAA,EACjB,CAAC;AACD,QAAM,OAAO,QAAQ;AACrB,QAAM,aAAa,MAAM,cAAc,CAAC;AAExC,QAAM,QAAQ,cAAc,YAAY,QAAQ;AAChD,QAAM,OAAO,aAAa,KAAK;AAC/B,QAAM,SAAS,GAAG,KAAK,CAAC,EAAE,YAAY,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC;AACvD,QAAM,UAAU,QAAQ,GAAG,MAAM,SAAM,KAAK,KAAK;AAEjD,QAAM,SAAS;AAAA,IACb,MAAM,OAAO,MAAM,MAAM,6BAA6B,KAAK,GAAG;AAAA,IAC9D,CAAC,MAAM,KAAK;AAAA,EACd;AACA,QAAM,WAAW,QAAQ,MAAM,QAAQ,QAAQ,KAAK,GAAG,CAAC,MAAM,CAAC;AAC/D,QAAM,cAAc,QAAQ,MAAM,QAAQ,QAAQ,QAAQ,GAAG,CAAC,MAAM,CAAC;AAErE,QAAM,WACJ,MAAM,gBAAgB,UAAU,WAAW,WAAW,IAClD,KAAK,iBACL;AAEN,MAAI,QAAQ,SAAS;AACnB,WACE,qBAAC,eAAY;AAAA;AAAA,MACgC;AAAA,MAC3C,oBAAC,UAAO,MAAK,MAAK,SAAQ,aAAY,SAAS,MAAM,QAAQ,QAAQ,GAAG,uBAExE;AAAA,OACF;AAAA,EAEJ;AAEA,QAAM,UAAU,QAAQ,aAAa,CAAC;AACtC,QAAM,OACJ,oBAAC,OAAE,WAAU,wCAAwC,yBAAc;AAGrE,SACE,qBAAC,SAAI,WAAW,OAAO,OACrB;AAAA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,IACF;AAAA,IAEA,oBAAC,SAAI,WAAU,YACb,8BAAC,SAAI,WAAU,eACb,8BAAC,SAAM,OAAM,gBAAe,SACzB,mBAAS,UACR;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA,kBAAkB,CAAC;AAAA,QACnB,qBAAqB,CAAC;AAAA,QACtB,SAAS;AAAA,QACT;AAAA,QACA,OAAO,QAAQ,SAAS,KAAK;AAAA;AAAA,IAC/B,IAEA,MAEJ,GACF,GACF;AAAA,IAEA,qBAAC,SAAI,WAAU,YACb;AAAA,0BAAC,SAAI,WAAW,WAAW,eAAe,eACvC,mBAAS,UACR;AAAA,QAAC;AAAA;AAAA,UACC,MAAM,OAAO,MAAM,sBAAsB,KAAK,GAAG,WAAW,CAAC;AAAA,UAC7D;AAAA,UACA,OAAO,QAAQ,SAAS,KAAK;AAAA,UAC7B;AAAA,UACA,QAAQ;AAAA;AAAA,MACV,IAEA,oBAAC,SAAM,OAAM,0BAAyB,SACnC,gBACH,GAEJ;AAAA,MACC,YACC,oBAAC,SAAI,WAAU,cACb;AAAA,QAAC;AAAA;AAAA,UACC,MAAM;AAAA,UACN;AAAA,UACA,OAAO,QAAQ,WAAW,CAAC,CAAC;AAAA,UAC5B;AAAA;AAAA,MACF,GACF;AAAA,OAEJ;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/commerce/commerce-overview.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo } from 'react';\nimport { Button, ErrorBanner } from '@withpotter/ui-kit';\nimport { usePlatformCockpit } from '../hooks';\nimport {\n GmvOrdersChart,\n PICK_CURRENCY,\n Panel,\n focusCurrency,\n moneyIn,\n periodPhrase,\n rowFor,\n toTrend,\n type DateRange,\n} from '../_ui';\nimport { OverviewKpis } from './overview-kpis';\nimport { SalesByService } from './overview-services';\nimport { TopMerchants } from './overview-top-merchants';\nimport styles from './overview.module.css';\n\n/**\n * Commerce across every merchant, from the one cockpit call: the headline\n * figures, who sells most, where the base sits and who needs a look.\n * Nothing here is fetched per merchant. Money comes back once per currency\n * and is never added across currencies or converted.\n */\nexport function CommerceOverview({\n range,\n currency,\n compare,\n onOpenMerchant,\n}: {\n range: DateRange;\n /** The currency filter's choice; '' shows every currency. */\n currency: string;\n /** False for \"all time\": there is no earlier period to compare with. */\n compare: boolean;\n onOpenMerchant: (id: string) => void;\n}) {\n const cockpit = usePlatformCockpit({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n const data = cockpit.data;\n const currencies = data?.currencies ?? [];\n // A chart or a ranking draws one currency: the filter's, or the only one.\n const focus = focusCurrency(currencies, currency);\n const span = periodPhrase(range);\n const period = `${span[0].toUpperCase()}${span.slice(1)}`;\n const caption = focus ? `${period} · ${focus}` : period;\n\n const series = useMemo(\n () => rowFor(data?.trend.performanceSeriesByCurrency, focus)?.series,\n [data, focus],\n );\n const gmvTrend = useMemo(() => toTrend(series, 'gmv'), [series]);\n const ordersTrend = useMemo(() => toTrend(series, 'orders'), [series]);\n // Sales by service is one mixed figure: only honest with one currency.\n const services =\n data?.salesByService?.length && currencies.length === 1\n ? data.salesByService\n : null;\n\n if (cockpit.isError) {\n return (\n <ErrorBanner>\n The commerce overview could not be loaded.{' '}\n <Button size=\"sm\" variant=\"secondary\" onClick={() => cockpit.refetch()}>\n Try again\n </Button>\n </ErrorBanner>\n );\n }\n\n const loading = cockpit.isLoading || !data;\n const pick = (\n <p className=\"text-sm text-[var(--text-secondary)]\">{PICK_CURRENCY}</p>\n );\n\n return (\n <div className={styles.stack}>\n <OverviewKpis\n data={data}\n loading={loading}\n currency={currency}\n focus={focus}\n compare={compare}\n caption={caption}\n />\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-12\">\n <Panel title=\"GMV & orders\" caption={caption}>\n {focus || loading ? (\n <GmvOrdersChart\n gmvTrend={gmvTrend}\n ordersTrend={ordersTrend}\n previousGmvTrend={[]}\n previousOrdersTrend={[]}\n compare={false}\n span={span}\n money={moneyIn(focus ?? 'USD')}\n />\n ) : (\n pick\n )}\n </Panel>\n </div>\n </div>\n\n <div className=\"adm-grid\">\n <div className={services ? 'adm-span-7' : 'adm-span-12'}>\n {focus || loading ? (\n <TopMerchants\n rows={rowFor(data?.topTenantsByCurrency, focus)?.tenants ?? []}\n loading={loading}\n money={moneyIn(focus ?? 'USD')}\n caption={caption}\n onOpen={onOpenMerchant}\n />\n ) : (\n <Panel title=\"Top merchants by sales\" caption={caption}>\n {pick}\n </Panel>\n )}\n </div>\n {services && (\n <div className=\"adm-span-5\">\n <SalesByService\n rows={services}\n loading={loading}\n money={moneyIn(currencies[0])}\n caption={caption}\n />\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":";AAkEM,SAEE,KAFF;AAhEN,SAAS,eAAe;AACxB,SAAS,QAAQ,mBAAmB;AACpC,SAAS,0BAA0B;AACnC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,oBAAoB;AAC7B,SAAS,sBAAsB;AAC/B,SAAS,oBAAoB;AAC7B,OAAO,YAAY;AAQZ,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAOG;AACD,QAAM,UAAU,mBAAmB;AAAA,IACjC,WAAW,MAAM;AAAA,IACjB,SAAS,MAAM;AAAA,EACjB,CAAC;AACD,QAAM,OAAO,QAAQ;AACrB,QAAM,aAAa,MAAM,cAAc,CAAC;AAExC,QAAM,QAAQ,cAAc,YAAY,QAAQ;AAChD,QAAM,OAAO,aAAa,KAAK;AAC/B,QAAM,SAAS,GAAG,KAAK,CAAC,EAAE,YAAY,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC;AACvD,QAAM,UAAU,QAAQ,GAAG,MAAM,SAAM,KAAK,KAAK;AAEjD,QAAM,SAAS;AAAA,IACb,MAAM,OAAO,MAAM,MAAM,6BAA6B,KAAK,GAAG;AAAA,IAC9D,CAAC,MAAM,KAAK;AAAA,EACd;AACA,QAAM,WAAW,QAAQ,MAAM,QAAQ,QAAQ,KAAK,GAAG,CAAC,MAAM,CAAC;AAC/D,QAAM,cAAc,QAAQ,MAAM,QAAQ,QAAQ,QAAQ,GAAG,CAAC,MAAM,CAAC;AAErE,QAAM,WACJ,MAAM,gBAAgB,UAAU,WAAW,WAAW,IAClD,KAAK,iBACL;AAEN,MAAI,QAAQ,SAAS;AACnB,WACE,qBAAC,eAAY;AAAA;AAAA,MACgC;AAAA,MAC3C,oBAAC,UAAO,MAAK,MAAK,SAAQ,aAAY,SAAS,MAAM,QAAQ,QAAQ,GAAG,uBAExE;AAAA,OACF;AAAA,EAEJ;AAEA,QAAM,UAAU,QAAQ,aAAa,CAAC;AACtC,QAAM,OACJ,oBAAC,OAAE,WAAU,wCAAwC,yBAAc;AAGrE,SACE,qBAAC,SAAI,WAAW,OAAO,OACrB;AAAA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,IACF;AAAA,IAEA,oBAAC,SAAI,WAAU,YACb,8BAAC,SAAI,WAAU,eACb,8BAAC,SAAM,OAAM,gBAAe,SACzB,mBAAS,UACR;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA,kBAAkB,CAAC;AAAA,QACnB,qBAAqB,CAAC;AAAA,QACtB,SAAS;AAAA,QACT;AAAA,QACA,OAAO,QAAQ,SAAS,KAAK;AAAA;AAAA,IAC/B,IAEA,MAEJ,GACF,GACF;AAAA,IAEA,qBAAC,SAAI,WAAU,YACb;AAAA,0BAAC,SAAI,WAAW,WAAW,eAAe,eACvC,mBAAS,UACR;AAAA,QAAC;AAAA;AAAA,UACC,MAAM,OAAO,MAAM,sBAAsB,KAAK,GAAG,WAAW,CAAC;AAAA,UAC7D;AAAA,UACA,OAAO,QAAQ,SAAS,KAAK;AAAA,UAC7B;AAAA,UACA,QAAQ;AAAA;AAAA,MACV,IAEA,oBAAC,SAAM,OAAM,0BAAyB,SACnC,gBACH,GAEJ;AAAA,MACC,YACC,oBAAC,SAAI,WAAU,cACb;AAAA,QAAC;AAAA;AAAA,UACC,MAAM;AAAA,UACN;AAAA,UACA,OAAO,QAAQ,WAAW,CAAC,CAAC;AAAA,UAC5B;AAAA;AAAA,MACF,GACF;AAAA,OAEJ;AAAA,KACF;AAEJ;","names":[]}
@@ -3,7 +3,6 @@ const compactMoney = (n, currency) => {
3
3
  try {
4
4
  return new Intl.NumberFormat("en-US", {
5
5
  style: "currency",
6
- currencyDisplay: "narrowSymbol",
7
6
  currency,
8
7
  notation: "compact",
9
8
  maximumFractionDigits: 1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/commerce/merchant-meta.ts"],"sourcesContent":["import type { TenantRow, TenantStatus } from '@withpotter/lib/admin-api';\nimport type { PillTone } from '../tenants/[id]/commerce/format';\n\n/**\n * GMV is in the merchant's own currency. With the currency known (tenant\n * detail), show it; list rows carry none, so they get a plain number.\n */\nexport const compactMoney = (n: number, currency?: string | null) => {\n if (currency) {\n try {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currencyDisplay: 'narrowSymbol',\n currency,\n notation: 'compact',\n maximumFractionDigits: 1,\n }).format(n ?? 0);\n } catch {\n // An unknown code falls through to the plain number.\n }\n }\n\n return compactNumber(n);\n};\n\n/**\n * Sales per currency, compact and never added together: \"₦1.2M · $3.4K\".\n * Falls back to the plain number when the API sends no per-currency rows.\n */\nexport const compactSales = (\n rows: ReadonlyArray<{ currency: string; amount: number }> | undefined,\n fallback: number,\n) =>\n rows?.length\n ? rows.map((r) => compactMoney(r.amount, r.currency)).join(' · ')\n : compactNumber(fallback);\n\n/** \"12.4K\": sales without a currency, for list rows. */\nexport const compactNumber = (n: number) =>\n new Intl.NumberFormat('en-US', {\n notation: 'compact',\n maximumFractionDigits: 1,\n }).format(n ?? 0);\n\nexport const STATUS_TONE: Record<TenantStatus, PillTone> = {\n active: 'success',\n pending: 'pending',\n suspended: 'failed',\n deleted: 'neutral',\n};\n\n/**\n * A merchant's logo when the API sends one, flat (`logoUrl`) or on the\n * profile (`profile.logoUrl`). Without it EntityAvatar falls back to initials,\n * so every list picks logos up the day the API starts sending them.\n */\nexport const logoOf = (row: object): string | null => {\n const r = row as {\n logoUrl?: string | null;\n profile?: { logoUrl?: string | null } | null;\n };\n\n return r.logoUrl ?? r.profile?.logoUrl ?? null;\n};\n\n/**\n * Ranked by sales within the rows shown only. Each figure is in its own\n * merchant's currency, so this orders a page; it is not a platform league.\n */\nexport const bySales = (rows: TenantRow[]) =>\n [...rows].sort((a, b) => (b.gmv ?? 0) - (a.gmv ?? 0));\n"],"mappings":"AAOO,MAAM,eAAe,CAAC,GAAW,aAA6B;AACnE,MAAI,UAAU;AACZ,QAAI;AACF,aAAO,IAAI,KAAK,aAAa,SAAS;AAAA,QACpC,OAAO;AAAA,QACP,iBAAiB;AAAA,QACjB;AAAA,QACA,UAAU;AAAA,QACV,uBAAuB;AAAA,MACzB,CAAC,EAAE,OAAO,KAAK,CAAC;AAAA,IAClB,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO,cAAc,CAAC;AACxB;AAMO,MAAM,eAAe,CAC1B,MACA,aAEA,MAAM,SACF,KAAK,IAAI,CAAC,MAAM,aAAa,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,KAAK,QAAK,IAC9D,cAAc,QAAQ;AAGrB,MAAM,gBAAgB,CAAC,MAC5B,IAAI,KAAK,aAAa,SAAS;AAAA,EAC7B,UAAU;AAAA,EACV,uBAAuB;AACzB,CAAC,EAAE,OAAO,KAAK,CAAC;AAEX,MAAM,cAA8C;AAAA,EACzD,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,WAAW;AAAA,EACX,SAAS;AACX;AAOO,MAAM,SAAS,CAAC,QAA+B;AACpD,QAAM,IAAI;AAKV,SAAO,EAAE,WAAW,EAAE,SAAS,WAAW;AAC5C;AAMO,MAAM,UAAU,CAAC,SACtB,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,GAAG,OAAO,EAAE,OAAO,MAAM,EAAE,OAAO,EAAE;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/commerce/merchant-meta.ts"],"sourcesContent":["import type { TenantRow, TenantStatus } from '@withpotter/lib/admin-api';\nimport type { PillTone } from '../tenants/[id]/commerce/format';\n\n/**\n * GMV is in the merchant's own currency. With the currency known (tenant\n * detail), show it; list rows carry none, so they get a plain number.\n */\nexport const compactMoney = (n: number, currency?: string | null) => {\n if (currency) {\n try {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency,\n notation: 'compact',\n maximumFractionDigits: 1,\n }).format(n ?? 0);\n } catch {\n // An unknown code falls through to the plain number.\n }\n }\n\n return compactNumber(n);\n};\n\n/**\n * Sales per currency, compact and never added together: \"₦1.2M · $3.4K\".\n * Falls back to the plain number when the API sends no per-currency rows.\n */\nexport const compactSales = (\n rows: ReadonlyArray<{ currency: string; amount: number }> | undefined,\n fallback: number,\n) =>\n rows?.length\n ? rows.map((r) => compactMoney(r.amount, r.currency)).join(' · ')\n : compactNumber(fallback);\n\n/** \"12.4K\": sales without a currency, for list rows. */\nexport const compactNumber = (n: number) =>\n new Intl.NumberFormat('en-US', {\n notation: 'compact',\n maximumFractionDigits: 1,\n }).format(n ?? 0);\n\nexport const STATUS_TONE: Record<TenantStatus, PillTone> = {\n active: 'success',\n pending: 'pending',\n suspended: 'failed',\n deleted: 'neutral',\n};\n\n/**\n * A merchant's logo when the API sends one, flat (`logoUrl`) or on the\n * profile (`profile.logoUrl`). Without it EntityAvatar falls back to initials,\n * so every list picks logos up the day the API starts sending them.\n */\nexport const logoOf = (row: object): string | null => {\n const r = row as {\n logoUrl?: string | null;\n profile?: { logoUrl?: string | null } | null;\n };\n\n return r.logoUrl ?? r.profile?.logoUrl ?? null;\n};\n\n/**\n * Ranked by sales within the rows shown only. Each figure is in its own\n * merchant's currency, so this orders a page; it is not a platform league.\n */\nexport const bySales = (rows: TenantRow[]) =>\n [...rows].sort((a, b) => (b.gmv ?? 0) - (a.gmv ?? 0));\n"],"mappings":"AAOO,MAAM,eAAe,CAAC,GAAW,aAA6B;AACnE,MAAI,UAAU;AACZ,QAAI;AACF,aAAO,IAAI,KAAK,aAAa,SAAS;AAAA,QACpC,OAAO;AAAA,QACP;AAAA,QACA,UAAU;AAAA,QACV,uBAAuB;AAAA,MACzB,CAAC,EAAE,OAAO,KAAK,CAAC;AAAA,IAClB,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO,cAAc,CAAC;AACxB;AAMO,MAAM,eAAe,CAC1B,MACA,aAEA,MAAM,SACF,KAAK,IAAI,CAAC,MAAM,aAAa,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,KAAK,QAAK,IAC9D,cAAc,QAAQ;AAGrB,MAAM,gBAAgB,CAAC,MAC5B,IAAI,KAAK,aAAa,SAAS;AAAA,EAC7B,UAAU;AAAA,EACV,uBAAuB;AACzB,CAAC,EAAE,OAAO,KAAK,CAAC;AAEX,MAAM,cAA8C;AAAA,EACzD,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,WAAW;AAAA,EACX,SAAS;AACX;AAOO,MAAM,SAAS,CAAC,QAA+B;AACpD,QAAM,IAAI;AAKV,SAAO,EAAE,WAAW,EAAE,SAAS,WAAW;AAC5C;AAMO,MAAM,UAAU,CAAC,SACtB,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,GAAG,OAAO,EAAE,OAAO,MAAM,EAAE,OAAO,EAAE;","names":[]}
@@ -78,7 +78,7 @@ function MerchantView({ range }) {
78
78
  ] }),
79
79
  /* @__PURE__ */ jsxs("span", { className: styles.sub, children: [
80
80
  "Shown in ",
81
- t.currency || "NGN",
81
+ t.currency || "USD",
82
82
  ", the merchant\u2019s currency"
83
83
  ] })
84
84
  ] })
@@ -90,7 +90,7 @@ function MerchantView({ range }) {
90
90
  MerchantCommerce,
91
91
  {
92
92
  tenantId: id,
93
- currency: t?.currency ?? "NGN",
93
+ currency: t?.currency ?? "USD",
94
94
  range
95
95
  },
96
96
  id
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/commerce/merchant-view.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport Link from 'next/link';\nimport { usePathname, useRouter, useSearchParams } from 'next/navigation';\nimport { Button, Card, EmptyNote, ErrorBanner } from '@withpotter/ui-kit';\nimport { usePlatformTenant, usePlatformTenants } from '../hooks';\nimport { EntityAvatar, Reveal } from '../_fx';\nimport { Skeleton, type DateRange } from '../_ui';\nimport { MerchantCommerce } from '../tenants/[id]/commerce';\nimport { StatusPill } from '../tenants/[id]/commerce/status-pill';\nimport { bySales, compactSales, STATUS_TONE } from './merchant-meta';\nimport { MerchantPicker } from './merchant-picker';\nimport styles from './console.module.css';\n\n/** \"By merchant\": pick a merchant, then read their commerce. */\nexport function MerchantView({ range }: { range: DateRange }) {\n const router = useRouter();\n const pathname = usePathname();\n const params = useSearchParams();\n const [picking, setPicking] = useState(false);\n\n const top = usePlatformTenants({ pageSize: 50 });\n const fallback = bySales(top.data?.rows ?? [])[0]?.id ?? null;\n const id = params.get('merchant') || fallback;\n const tenant = usePlatformTenant(id ?? '', {\n startDate: range.startDate,\n endDate: range.endDate,\n });\n const t = tenant.data;\n\n const pick = (next: string) => {\n const qs = new URLSearchParams(params.toString());\n qs.set('merchant', next);\n router.replace(`${pathname}?${qs.toString()}`, { scroll: false });\n setPicking(false);\n };\n\n const noMerchants = !top.isLoading && !top.isError && !id;\n\n return (\n <div className={styles.body}>\n {top.isError && !id ? (\n <ErrorBanner>\n Merchants could not be loaded.{' '}\n <Button size=\"sm\" variant=\"secondary\" onClick={() => top.refetch()}>\n Try again\n </Button>\n </ErrorBanner>\n ) : noMerchants ? (\n <EmptyNote\n well\n action={\n <Button variant=\"secondary\" asChild>\n <Link href=\"/admin/tenants\">Go to merchants</Link>\n </Button>\n }\n >\n Once a business signs up, its items, orders and stock appear here.\n </EmptyNote>\n ) : (\n <>\n <Card>\n <Reveal className={styles.current}>\n {t?.profile?.logoUrl && (\n <EntityAvatar\n name={t.businessName}\n src={t.profile.logoUrl}\n size={40}\n />\n )}\n <div className={styles.currentMain}>\n <span className={styles.eyebrow}>Merchant</span>\n <span className={styles.currentName} title={t?.businessName}>\n {t ? t.businessName : <Skeleton width={160} height={20} />}\n </span>\n {t && (\n <span className={styles.currentMeta}>\n <StatusPill\n status={t.status}\n tone={STATUS_TONE[t.status]}\n />\n <span className={styles.sub}>\n {compactSales(\n t.metrics.byCurrency?.map((r) => ({\n currency: r.currency,\n amount: r.gmv,\n })),\n t.metrics.gmv,\n )}{' '}\n lifetime sales\n </span>\n <span className={styles.sub}>\n Shown in {t.currency || 'NGN'}, the merchant’s currency\n </span>\n </span>\n )}\n </div>\n {id && (\n <Button variant=\"ghost\" asChild>\n <Link href={`/admin/tenants/${id}`}>Merchant profile</Link>\n </Button>\n )}\n <Button variant=\"secondary\" onClick={() => setPicking(true)}>\n Change merchant\n </Button>\n </Reveal>\n </Card>\n\n {id && (\n <MerchantCommerce\n key={id}\n tenantId={id}\n currency={t?.currency ?? 'NGN'}\n range={range}\n />\n )}\n </>\n )}\n\n <MerchantPicker\n open={picking}\n onOpenChange={setPicking}\n selectedId={id}\n onPick={(row) => pick(row.id)}\n />\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/commerce/merchant-view.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport Link from 'next/link';\nimport { usePathname, useRouter, useSearchParams } from 'next/navigation';\nimport { Button, Card, EmptyNote, ErrorBanner } from '@withpotter/ui-kit';\nimport { usePlatformTenant, usePlatformTenants } from '../hooks';\nimport { EntityAvatar, Reveal } from '../_fx';\nimport { Skeleton, type DateRange } from '../_ui';\nimport { MerchantCommerce } from '../tenants/[id]/commerce';\nimport { StatusPill } from '../tenants/[id]/commerce/status-pill';\nimport { bySales, compactSales, STATUS_TONE } from './merchant-meta';\nimport { MerchantPicker } from './merchant-picker';\nimport styles from './console.module.css';\n\n/** \"By merchant\": pick a merchant, then read their commerce. */\nexport function MerchantView({ range }: { range: DateRange }) {\n const router = useRouter();\n const pathname = usePathname();\n const params = useSearchParams();\n const [picking, setPicking] = useState(false);\n\n const top = usePlatformTenants({ pageSize: 50 });\n const fallback = bySales(top.data?.rows ?? [])[0]?.id ?? null;\n const id = params.get('merchant') || fallback;\n const tenant = usePlatformTenant(id ?? '', {\n startDate: range.startDate,\n endDate: range.endDate,\n });\n const t = tenant.data;\n\n const pick = (next: string) => {\n const qs = new URLSearchParams(params.toString());\n qs.set('merchant', next);\n router.replace(`${pathname}?${qs.toString()}`, { scroll: false });\n setPicking(false);\n };\n\n const noMerchants = !top.isLoading && !top.isError && !id;\n\n return (\n <div className={styles.body}>\n {top.isError && !id ? (\n <ErrorBanner>\n Merchants could not be loaded.{' '}\n <Button size=\"sm\" variant=\"secondary\" onClick={() => top.refetch()}>\n Try again\n </Button>\n </ErrorBanner>\n ) : noMerchants ? (\n <EmptyNote\n well\n action={\n <Button variant=\"secondary\" asChild>\n <Link href=\"/admin/tenants\">Go to merchants</Link>\n </Button>\n }\n >\n Once a business signs up, its items, orders and stock appear here.\n </EmptyNote>\n ) : (\n <>\n <Card>\n <Reveal className={styles.current}>\n {t?.profile?.logoUrl && (\n <EntityAvatar\n name={t.businessName}\n src={t.profile.logoUrl}\n size={40}\n />\n )}\n <div className={styles.currentMain}>\n <span className={styles.eyebrow}>Merchant</span>\n <span className={styles.currentName} title={t?.businessName}>\n {t ? t.businessName : <Skeleton width={160} height={20} />}\n </span>\n {t && (\n <span className={styles.currentMeta}>\n <StatusPill\n status={t.status}\n tone={STATUS_TONE[t.status]}\n />\n <span className={styles.sub}>\n {compactSales(\n t.metrics.byCurrency?.map((r) => ({\n currency: r.currency,\n amount: r.gmv,\n })),\n t.metrics.gmv,\n )}{' '}\n lifetime sales\n </span>\n <span className={styles.sub}>\n Shown in {t.currency || 'USD'}, the merchant’s currency\n </span>\n </span>\n )}\n </div>\n {id && (\n <Button variant=\"ghost\" asChild>\n <Link href={`/admin/tenants/${id}`}>Merchant profile</Link>\n </Button>\n )}\n <Button variant=\"secondary\" onClick={() => setPicking(true)}>\n Change merchant\n </Button>\n </Reveal>\n </Card>\n\n {id && (\n <MerchantCommerce\n key={id}\n tenantId={id}\n currency={t?.currency ?? 'USD'}\n range={range}\n />\n )}\n </>\n )}\n\n <MerchantPicker\n open={picking}\n onOpenChange={setPicking}\n selectedId={id}\n onPick={(row) => pick(row.id)}\n />\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -109,6 +109,8 @@
109
109
  }
110
110
 
111
111
  .trend thead th {
112
+ position: sticky;
113
+ top: 0;
112
114
  background: var(--bg-base);
113
115
  }
114
116
 
@@ -1,6 +1,8 @@
1
1
  /* The merchant preview: their card on a brand-tinted stage. */
2
2
 
3
3
  .frame {
4
+ position: sticky;
5
+ top: 0;
4
6
  display: grid;
5
7
  gap: 12px;
6
8
  padding: 16px;
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { keepPreviousData, useQuery } from "@tanstack/react-query";
2
+ import { useQuery } from "@tanstack/react-query";
3
3
  import { platformAdminApi } from "@withpotter/lib/admin-api";
4
4
  function useAdminSession(enabled = true) {
5
5
  return useQuery({
@@ -28,49 +28,6 @@ function usePlatformRevenue(range = {}) {
28
28
  queryFn: () => platformAdminApi.revenue(range)
29
29
  });
30
30
  }
31
- function useGatewayChannels(range = {}) {
32
- return useQuery({
33
- queryKey: ["platform-admin", "gateway-channels", range],
34
- queryFn: () => platformAdminApi.gatewayChannels(range),
35
- retry: false,
36
- staleTime: 6e4
37
- });
38
- }
39
- function useMerchantPayments(filters) {
40
- return useQuery({
41
- queryKey: ["platform-admin", "revenue", "merchant-payments", filters],
42
- queryFn: () => platformAdminApi.merchantPayments(filters),
43
- retry: false,
44
- staleTime: 3e4,
45
- placeholderData: keepPreviousData
46
- });
47
- }
48
- function useSubscriptionPayments(filters) {
49
- return useQuery({
50
- queryKey: ["platform-admin", "revenue", "subscription-payments", filters],
51
- queryFn: () => platformAdminApi.subscriptionPayments(filters),
52
- retry: false,
53
- staleTime: 3e4,
54
- placeholderData: keepPreviousData
55
- });
56
- }
57
- function useMerchantRevenue(filters) {
58
- return useQuery({
59
- queryKey: ["platform-admin", "revenue", "merchants", filters],
60
- queryFn: () => platformAdminApi.merchantRevenue(filters),
61
- retry: false,
62
- staleTime: 3e4,
63
- placeholderData: keepPreviousData
64
- });
65
- }
66
- function useMerchantOptions(range) {
67
- return useQuery({
68
- queryKey: ["platform-admin", "revenue", "merchant-options", range],
69
- queryFn: () => platformAdminApi.merchantOptions(range),
70
- retry: false,
71
- staleTime: 6e4
72
- });
73
- }
74
31
  function usePlatformTenantsAnalytics(range = {}) {
75
32
  return useQuery({
76
33
  queryKey: ["platform-admin", "tenants-analytics", range],
@@ -211,13 +168,6 @@ function useAdminRoles() {
211
168
  queryFn: () => platformAdminApi.roles()
212
169
  });
213
170
  }
214
- function useAccountSessions(id) {
215
- return useQuery({
216
- queryKey: ["platform-admin", "account-sessions", id],
217
- queryFn: () => platformAdminApi.accountSessions(id),
218
- enabled: !!id
219
- });
220
- }
221
171
  function useAccountTenants(id) {
222
172
  return useQuery({
223
173
  queryKey: ["platform-admin", "account-tenants", id],
@@ -238,15 +188,10 @@ function usePlatformAuditAnalytics(range = {}) {
238
188
  });
239
189
  }
240
190
  export {
241
- useAccountSessions,
242
191
  useAccountTenants,
243
192
  useAdminPermissions,
244
193
  useAdminRoles,
245
194
  useAdminSession,
246
- useGatewayChannels,
247
- useMerchantOptions,
248
- useMerchantPayments,
249
- useMerchantRevenue,
250
195
  usePlatformAccounts,
251
196
  usePlatformAccountsAnalytics,
252
197
  usePlatformAdmins,
@@ -259,7 +204,6 @@ export {
259
204
  usePlatformTenant,
260
205
  usePlatformTenants,
261
206
  usePlatformTenantsAnalytics,
262
- useSubscriptionPayments,
263
207
  useTenantAnalytics,
264
208
  useTenantCustomerInsights,
265
209
  useTenantCustomers,