@withpotter/pages-admin 0.13.0-next.123 → 0.13.0-next.58

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 (262) 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/_ui/workspace-tab.js +1 -11
  65. package/dist/admin/_ui/workspace-tab.js.map +1 -1
  66. package/dist/admin/accounts/account-panel.js +6 -49
  67. package/dist/admin/accounts/account-panel.js.map +1 -1
  68. package/dist/admin/accounts/account-panel.module.css +0 -73
  69. package/dist/admin/accounts/account-row.js +2 -11
  70. package/dist/admin/accounts/account-row.js.map +1 -1
  71. package/dist/admin/accounts/account-vitals.js +13 -15
  72. package/dist/admin/accounts/account-vitals.js.map +1 -1
  73. package/dist/admin/accounts/accounts-table.js +8 -39
  74. package/dist/admin/accounts/accounts-table.js.map +1 -1
  75. package/dist/admin/accounts/accounts-workspace.js +2 -2
  76. package/dist/admin/accounts/accounts-workspace.js.map +1 -1
  77. package/dist/admin/accounts/owner-session.js +6 -0
  78. package/dist/admin/accounts/owner-session.js.map +1 -1
  79. package/dist/admin/accounts/rows.module.css +0 -17
  80. package/dist/admin/audit/status-pill.js +0 -6
  81. package/dist/admin/audit/status-pill.js.map +1 -1
  82. package/dist/admin/audit/status-pill.module.css +0 -4
  83. package/dist/admin/commerce/commerce-overview.js +2 -2
  84. package/dist/admin/commerce/commerce-overview.js.map +1 -1
  85. package/dist/admin/commerce/merchant-meta.js +0 -1
  86. package/dist/admin/commerce/merchant-meta.js.map +1 -1
  87. package/dist/admin/commerce/merchant-view.js +2 -2
  88. package/dist/admin/commerce/merchant-view.js.map +1 -1
  89. package/dist/admin/feedback/prompt-operations.module.css +2 -0
  90. package/dist/admin/feedback/prompt-preview.module.css +2 -0
  91. package/dist/admin/hooks.js +1 -57
  92. package/dist/admin/hooks.js.map +1 -1
  93. package/dist/admin/layout.js +2 -8
  94. package/dist/admin/layout.js.map +1 -1
  95. package/dist/admin/page.js +8 -28
  96. package/dist/admin/page.js.map +1 -1
  97. package/dist/admin/payout-tiers/_tier-dialog.js +21 -31
  98. package/dist/admin/payout-tiers/_tier-dialog.js.map +1 -1
  99. package/dist/admin/payout-tiers/tier-preview-result.js +9 -10
  100. package/dist/admin/payout-tiers/tier-preview-result.js.map +1 -1
  101. package/dist/admin/payout-tiers/tiers-table.js +17 -40
  102. package/dist/admin/payout-tiers/tiers-table.js.map +1 -1
  103. package/dist/admin/payout-tiers/tiers-workspace.js +17 -20
  104. package/dist/admin/payout-tiers/tiers-workspace.js.map +1 -1
  105. package/dist/admin/pos/pos-workspace.js +2 -6
  106. package/dist/admin/pos/pos-workspace.js.map +1 -1
  107. package/dist/admin/pos/shifts-tab.js +9 -16
  108. package/dist/admin/pos/shifts-tab.js.map +1 -1
  109. package/dist/admin/revenue/cohort-panel.js +6 -6
  110. package/dist/admin/revenue/cohort-panel.js.map +1 -1
  111. package/dist/admin/revenue/health-panel.js +21 -22
  112. package/dist/admin/revenue/health-panel.js.map +1 -1
  113. package/dist/admin/revenue/plan-mix-panel.js +3 -3
  114. package/dist/admin/revenue/plan-mix-panel.js.map +1 -1
  115. package/dist/admin/revenue/quality-panel.js +19 -19
  116. package/dist/admin/revenue/quality-panel.js.map +1 -1
  117. package/dist/admin/revenue/revenue-kpis.js +50 -111
  118. package/dist/admin/revenue/revenue-kpis.js.map +1 -1
  119. package/dist/admin/revenue/revenue-overview.js +46 -39
  120. package/dist/admin/revenue/revenue-overview.js.map +1 -1
  121. package/dist/admin/revenue/revenue-retention.js +13 -17
  122. package/dist/admin/revenue/revenue-retention.js.map +1 -1
  123. package/dist/admin/revenue/revenue-workspace.js +6 -53
  124. package/dist/admin/revenue/revenue-workspace.js.map +1 -1
  125. package/dist/admin/revenue/subscriptions-panel.js +14 -14
  126. package/dist/admin/revenue/subscriptions-panel.js.map +1 -1
  127. package/dist/admin/revenue/top-tenants-panel.js +4 -4
  128. package/dist/admin/revenue/top-tenants-panel.js.map +1 -1
  129. package/dist/admin/settings/account-tab.js +25 -1
  130. package/dist/admin/settings/account-tab.js.map +1 -1
  131. package/dist/admin/settings/general-tab.js +2 -2
  132. package/dist/admin/settings/general-tab.js.map +1 -1
  133. package/dist/admin/settings/system-status-card.js +30 -0
  134. package/dist/admin/settings/system-status-card.js.map +1 -0
  135. package/dist/admin/support/queue-card.module.css +4 -1
  136. package/dist/admin/tenants/[id]/commerce/format.js +1 -2
  137. package/dist/admin/tenants/[id]/commerce/format.js.map +1 -1
  138. package/dist/admin/tenants/[id]/commerce/recent-orders.js +1 -1
  139. package/dist/admin/tenants/[id]/commerce/recent-orders.js.map +1 -1
  140. package/dist/admin/tenants/[id]/page.js +10 -37
  141. package/dist/admin/tenants/[id]/page.js.map +1 -1
  142. package/dist/admin/tenants/[id]/tenant-layout.module.css +4 -1
  143. package/dist/admin/tenants/[id]/tenant-overview.js +2 -3
  144. package/dist/admin/tenants/[id]/tenant-overview.js.map +1 -1
  145. package/dist/admin/tenants/[id]/tenant-overview.module.css +1 -1
  146. package/dist/admin/tenants/[id]/tenant-pos.js +16 -64
  147. package/dist/admin/tenants/[id]/tenant-pos.js.map +1 -1
  148. package/dist/admin/tenants/[id]/tenant-wallet-card.js +2 -3
  149. package/dist/admin/tenants/[id]/tenant-wallet-card.js.map +1 -1
  150. package/dist/admin/tenants/[id]/verification/document-viewer.js +52 -3
  151. package/dist/admin/tenants/[id]/verification/document-viewer.js.map +1 -1
  152. package/dist/admin/tenants/[id]/verification/document-viewer.module.css +0 -51
  153. package/dist/admin/tenants/[id]/verification/tenant-verification.js +0 -10
  154. package/dist/admin/tenants/[id]/verification/tenant-verification.js.map +1 -1
  155. package/dist/admin/tenants/tenant-list-row.js +1 -2
  156. package/dist/admin/tenants/tenant-list-row.js.map +1 -1
  157. package/dist/admin/tenants/tenants-table.js +2 -3
  158. package/dist/admin/tenants/tenants-table.js.map +1 -1
  159. package/dist/admin/tenants/tenants-workspace.js +2 -2
  160. package/dist/admin/tenants/tenants-workspace.js.map +1 -1
  161. package/dist/admin/verification/verification-page.js +3 -19
  162. package/dist/admin/verification/verification-page.js.map +1 -1
  163. package/dist/admin/verification/verification.module.css +0 -10
  164. package/dist/admin/verification/waiting-case-actions.js +68 -0
  165. package/dist/admin/verification/waiting-case-actions.js.map +1 -0
  166. package/dist/admin/verification/waiting-cases.js +48 -66
  167. package/dist/admin/verification/waiting-cases.js.map +1 -1
  168. package/package.json +5 -5
  169. package/dist/admin/_pos/olive-balance.js +0 -40
  170. package/dist/admin/_pos/olive-balance.js.map +0 -1
  171. package/dist/admin/_pos/olive-state.js +0 -23
  172. package/dist/admin/_pos/olive-state.js.map +0 -1
  173. package/dist/admin/_pos/payments-model.js +0 -55
  174. package/dist/admin/_pos/payments-model.js.map +0 -1
  175. package/dist/admin/_pos/tenant-operators.js +0 -63
  176. package/dist/admin/_pos/tenant-operators.js.map +0 -1
  177. package/dist/admin/_pos/tenant-outlets.js +0 -77
  178. package/dist/admin/_pos/tenant-outlets.js.map +0 -1
  179. package/dist/admin/_pos/tenant-pos-figures.js +0 -82
  180. package/dist/admin/_pos/tenant-pos-figures.js.map +0 -1
  181. package/dist/admin/_pos/tenant-requests.js +0 -64
  182. package/dist/admin/_pos/tenant-requests.js.map +0 -1
  183. package/dist/admin/_pos/tenant-transactions.js +0 -145
  184. package/dist/admin/_pos/tenant-transactions.js.map +0 -1
  185. package/dist/admin/_shell/nav-fold.js +0 -14
  186. package/dist/admin/_shell/nav-fold.js.map +0 -1
  187. package/dist/admin/_shell/nav-group.js +0 -78
  188. package/dist/admin/_shell/nav-group.js.map +0 -1
  189. package/dist/admin/_shell/use-open-groups.js +0 -64
  190. package/dist/admin/_shell/use-open-groups.js.map +0 -1
  191. package/dist/admin/_ui/page-header.module.css +0 -45
  192. package/dist/admin/accounts/account-sessions.js +0 -70
  193. package/dist/admin/accounts/account-sessions.js.map +0 -1
  194. package/dist/admin/accounts/account-sessions.module.css +0 -55
  195. package/dist/admin/accounts/account-stage-filter.js +0 -29
  196. package/dist/admin/accounts/account-stage-filter.js.map +0 -1
  197. package/dist/admin/accounts/account-stage.js +0 -81
  198. package/dist/admin/accounts/account-stage.js.map +0 -1
  199. package/dist/admin/accounts/hint.js +0 -12
  200. package/dist/admin/accounts/hint.js.map +0 -1
  201. package/dist/admin/payout-tiers/requirement-labels.js +0 -70
  202. package/dist/admin/payout-tiers/requirement-labels.js.map +0 -1
  203. package/dist/admin/pos/transactions-tab.js +0 -214
  204. package/dist/admin/pos/transactions-tab.js.map +0 -1
  205. package/dist/admin/revenue/channel-panel.js +0 -49
  206. package/dist/admin/revenue/channel-panel.js.map +0 -1
  207. package/dist/admin/revenue/channel-rows.js +0 -17
  208. package/dist/admin/revenue/channel-rows.js.map +0 -1
  209. package/dist/admin/revenue/download-button.js +0 -40
  210. package/dist/admin/revenue/download-button.js.map +0 -1
  211. package/dist/admin/revenue/earnings/earnings-colors.js +0 -25
  212. package/dist/admin/revenue/earnings/earnings-colors.js.map +0 -1
  213. package/dist/admin/revenue/earnings/earnings-legend.js +0 -75
  214. package/dist/admin/revenue/earnings/earnings-legend.js.map +0 -1
  215. package/dist/admin/revenue/earnings/earnings-summary.js +0 -296
  216. package/dist/admin/revenue/earnings/earnings-summary.js.map +0 -1
  217. package/dist/admin/revenue/earnings/earnings-table.js +0 -143
  218. package/dist/admin/revenue/earnings/earnings-table.js.map +0 -1
  219. package/dist/admin/revenue/earnings/earnings.module.css +0 -201
  220. package/dist/admin/revenue/earnings/use-earnings.js +0 -27
  221. package/dist/admin/revenue/earnings/use-earnings.js.map +0 -1
  222. package/dist/admin/revenue/ledger-model.js +0 -104
  223. package/dist/admin/revenue/ledger-model.js.map +0 -1
  224. package/dist/admin/revenue/ledger.module.css +0 -144
  225. package/dist/admin/revenue/merchant-headline.js +0 -37
  226. package/dist/admin/revenue/merchant-headline.js.map +0 -1
  227. package/dist/admin/revenue/merchant-payments-view.js +0 -234
  228. package/dist/admin/revenue/merchant-payments-view.js.map +0 -1
  229. package/dist/admin/revenue/method-cards.js +0 -40
  230. package/dist/admin/revenue/method-cards.js.map +0 -1
  231. package/dist/admin/revenue/payment-drawer.js +0 -53
  232. package/dist/admin/revenue/payment-drawer.js.map +0 -1
  233. package/dist/admin/revenue/plan-lines.js +0 -36
  234. package/dist/admin/revenue/plan-lines.js.map +0 -1
  235. package/dist/admin/revenue/plan-payment-drawer.js +0 -42
  236. package/dist/admin/revenue/plan-payment-drawer.js.map +0 -1
  237. package/dist/admin/revenue/plans-panel.js +0 -48
  238. package/dist/admin/revenue/plans-panel.js.map +0 -1
  239. package/dist/admin/revenue/platform-fees.js +0 -9
  240. package/dist/admin/revenue/platform-fees.js.map +0 -1
  241. package/dist/admin/revenue/print-summary-button.js +0 -27
  242. package/dist/admin/revenue/print-summary-button.js.map +0 -1
  243. package/dist/admin/revenue/subscriptions-view.js +0 -213
  244. package/dist/admin/revenue/subscriptions-view.js.map +0 -1
  245. package/dist/admin/revenue/summary-download.js +0 -38
  246. package/dist/admin/revenue/summary-download.js.map +0 -1
  247. package/dist/admin/revenue/summary-print.js +0 -82
  248. package/dist/admin/revenue/summary-print.js.map +0 -1
  249. package/dist/admin/revenue/summary-rows.js +0 -44
  250. package/dist/admin/revenue/summary-rows.js.map +0 -1
  251. package/dist/admin/revenue/url-filters.js +0 -36
  252. package/dist/admin/revenue/url-filters.js.map +0 -1
  253. package/dist/admin/revenue/use-url-filters.js +0 -24
  254. package/dist/admin/revenue/use-url-filters.js.map +0 -1
  255. package/dist/admin/tenants/[id]/use-tenant-wallet.js +0 -23
  256. package/dist/admin/tenants/[id]/use-tenant-wallet.js.map +0 -1
  257. package/dist/admin/tenants/[id]/verification/document-file.js +0 -78
  258. package/dist/admin/tenants/[id]/verification/document-file.js.map +0 -1
  259. package/dist/admin/tenants/[id]/verification/sent-files.js +0 -33
  260. package/dist/admin/tenants/[id]/verification/sent-files.js.map +0 -1
  261. package/dist/admin/verification/preview-dialog.js +0 -111
  262. package/dist/admin/verification/preview-dialog.js.map +0 -1
@@ -1,33 +1,6 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import * as Popover from "@radix-ui/react-popover";
4
- import { Info } from "lucide-react";
5
- import styles from "./page-header.module.css";
6
3
  import { useInWorkspaceTab } from "./workspace-tab";
7
- function PageInfo({ title, text }) {
8
- return /* @__PURE__ */ jsx("span", { className: `adm-page-desc ${styles.info}`, children: /* @__PURE__ */ jsxs(Popover.Root, { children: [
9
- /* @__PURE__ */ jsx(Popover.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(
10
- "button",
11
- {
12
- type: "button",
13
- className: styles.trigger,
14
- "aria-label": `About ${title}`,
15
- children: /* @__PURE__ */ jsx(Info, { size: 16, "aria-hidden": true })
16
- }
17
- ) }),
18
- /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(
19
- Popover.Content,
20
- {
21
- side: "bottom",
22
- align: "start",
23
- sideOffset: 6,
24
- collisionPadding: 16,
25
- className: styles.popover,
26
- children: text
27
- }
28
- ) })
29
- ] }) });
30
- }
31
4
  function PageHeader({
32
5
  title,
33
6
  description,
@@ -36,15 +9,15 @@ function PageHeader({
36
9
  if (useInWorkspaceTab()) {
37
10
  if (!description && !actions) return null;
38
11
  return /* @__PURE__ */ jsxs("div", { className: "adm-page-header adm-tab-lead", children: [
39
- description && /* @__PURE__ */ jsx(PageInfo, { title, text: description }),
12
+ description && /* @__PURE__ */ jsx("p", { className: "adm-page-desc", children: description }),
40
13
  actions && /* @__PURE__ */ jsx("div", { className: "adm-page-actions", children: actions })
41
14
  ] });
42
15
  }
43
16
  return /* @__PURE__ */ jsxs("div", { className: "adm-page-header", children: [
44
- /* @__PURE__ */ jsxs("div", { className: "adm-page-lead", children: [
17
+ /* @__PURE__ */ jsx("div", { className: "adm-page-lead", children: /* @__PURE__ */ jsxs("div", { style: { minWidth: 0 }, children: [
45
18
  /* @__PURE__ */ jsx("h1", { className: "adm-page-title", children: title }),
46
- description && /* @__PURE__ */ jsx(PageInfo, { title, text: description })
47
- ] }),
19
+ description && /* @__PURE__ */ jsx("p", { className: "adm-page-desc", children: description })
20
+ ] }) }),
48
21
  actions && /* @__PURE__ */ jsx("div", { className: "adm-page-actions", children: actions })
49
22
  ] });
50
23
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/_ui/page-header.tsx"],"sourcesContent":["'use client';\n\nimport * as Popover from '@radix-ui/react-popover';\nimport { Info } from 'lucide-react';\nimport type { ReactNode } from 'react';\nimport type { LucideIcon } from 'lucide-react';\nimport type { Tone } from '../_fx';\nimport styles from './page-header.module.css';\nimport { useInWorkspaceTab } from './workspace-tab';\n\n/**\n * The page's explanation: an info icon with the text in a popover, never a\n * line under the title. It keeps the `adm-page-desc` class as a global hook so\n * an app that draws its own info icon can hide this one with CSS.\n */\nfunction PageInfo({ title, text }: { title: string; text: string }) {\n return (\n <span className={`adm-page-desc ${styles.info}`}>\n <Popover.Root>\n <Popover.Trigger asChild>\n <button\n type=\"button\"\n className={styles.trigger}\n aria-label={`About ${title}`}\n >\n <Info size={16} aria-hidden />\n </button>\n </Popover.Trigger>\n <Popover.Portal>\n <Popover.Content\n side=\"bottom\"\n align=\"start\"\n sideOffset={6}\n collisionPadding={16}\n className={styles.popover}\n >\n {text}\n </Popover.Content>\n </Popover.Portal>\n </Popover.Root>\n </span>\n );\n}\n\n/**\n * Standard page header: title, an info icon for context, actions on the right.\n * No icon: the nav already says where you are. `icon`/`tone` are accepted\n * and ignored so existing callers compile.\n */\nexport function PageHeader({\n title,\n description,\n actions,\n}: {\n title: string;\n description?: string;\n actions?: ReactNode;\n icon?: LucideIcon;\n tone?: Tone;\n}) {\n // Inside a workspace tab the tab is the title: keep the context and actions.\n if (useInWorkspaceTab()) {\n if (!description && !actions) return null;\n return (\n <div className=\"adm-page-header adm-tab-lead\">\n {description && <PageInfo title={title} text={description} />}\n {actions && <div className=\"adm-page-actions\">{actions}</div>}\n </div>\n );\n }\n\n return (\n <div className=\"adm-page-header\">\n <div className=\"adm-page-lead\">\n <h1 className=\"adm-page-title\">{title}</h1>\n {description && <PageInfo title={title} text={description} />}\n </div>\n {actions && <div className=\"adm-page-actions\">{actions}</div>}\n </div>\n );\n}\n"],"mappings":";AAkBM,SAOM,KAPN;AAhBN,YAAY,aAAa;AACzB,SAAS,YAAY;AAIrB,OAAO,YAAY;AACnB,SAAS,yBAAyB;AAOlC,SAAS,SAAS,EAAE,OAAO,KAAK,GAAoC;AAClE,SACE,oBAAC,UAAK,WAAW,iBAAiB,OAAO,IAAI,IAC3C,+BAAC,QAAQ,MAAR,EACC;AAAA,wBAAC,QAAQ,SAAR,EAAgB,SAAO,MACtB;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,WAAW,OAAO;AAAA,QAClB,cAAY,SAAS,KAAK;AAAA,QAE1B,8BAAC,QAAK,MAAM,IAAI,eAAW,MAAC;AAAA;AAAA,IAC9B,GACF;AAAA,IACA,oBAAC,QAAQ,QAAR,EACC;AAAA,MAAC,QAAQ;AAAA,MAAR;AAAA,QACC,MAAK;AAAA,QACL,OAAM;AAAA,QACN,YAAY;AAAA,QACZ,kBAAkB;AAAA,QAClB,WAAW,OAAO;AAAA,QAEjB;AAAA;AAAA,IACH,GACF;AAAA,KACF,GACF;AAEJ;AAOO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AACF,GAMG;AAED,MAAI,kBAAkB,GAAG;AACvB,QAAI,CAAC,eAAe,CAAC,QAAS,QAAO;AACrC,WACE,qBAAC,SAAI,WAAU,gCACZ;AAAA,qBAAe,oBAAC,YAAS,OAAc,MAAM,aAAa;AAAA,MAC1D,WAAW,oBAAC,SAAI,WAAU,oBAAoB,mBAAQ;AAAA,OACzD;AAAA,EAEJ;AAEA,SACE,qBAAC,SAAI,WAAU,mBACb;AAAA,yBAAC,SAAI,WAAU,iBACb;AAAA,0BAAC,QAAG,WAAU,kBAAkB,iBAAM;AAAA,MACrC,eAAe,oBAAC,YAAS,OAAc,MAAM,aAAa;AAAA,OAC7D;AAAA,IACC,WAAW,oBAAC,SAAI,WAAU,oBAAoB,mBAAQ;AAAA,KACzD;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/_ui/page-header.tsx"],"sourcesContent":["'use client';\n\nimport type { ReactNode } from 'react';\nimport type { LucideIcon } from 'lucide-react';\nimport type { Tone } from '../_fx';\nimport { useInWorkspaceTab } from './workspace-tab';\n\n/**\n * Standard page header: title, one line of context, actions on the right.\n * No icon: the nav already says where you are. `icon`/`tone` are accepted\n * and ignored so existing callers compile.\n */\nexport function PageHeader({\n title,\n description,\n actions,\n}: {\n title: string;\n description?: string;\n actions?: ReactNode;\n icon?: LucideIcon;\n tone?: Tone;\n}) {\n // Inside a workspace tab the tab is the title: keep the context and actions.\n if (useInWorkspaceTab()) {\n if (!description && !actions) return null;\n return (\n <div className=\"adm-page-header adm-tab-lead\">\n {description && <p className=\"adm-page-desc\">{description}</p>}\n {actions && <div className=\"adm-page-actions\">{actions}</div>}\n </div>\n );\n }\n\n return (\n <div className=\"adm-page-header\">\n <div className=\"adm-page-lead\">\n <div style={{ minWidth: 0 }}>\n <h1 className=\"adm-page-title\">{title}</h1>\n {description && <p className=\"adm-page-desc\">{description}</p>}\n </div>\n </div>\n {actions && <div className=\"adm-page-actions\">{actions}</div>}\n </div>\n );\n}\n"],"mappings":";AA2BM,SACkB,KADlB;AAtBN,SAAS,yBAAyB;AAO3B,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AACF,GAMG;AAED,MAAI,kBAAkB,GAAG;AACvB,QAAI,CAAC,eAAe,CAAC,QAAS,QAAO;AACrC,WACE,qBAAC,SAAI,WAAU,gCACZ;AAAA,qBAAe,oBAAC,OAAE,WAAU,iBAAiB,uBAAY;AAAA,MACzD,WAAW,oBAAC,SAAI,WAAU,oBAAoB,mBAAQ;AAAA,OACzD;AAAA,EAEJ;AAEA,SACE,qBAAC,SAAI,WAAU,mBACb;AAAA,wBAAC,SAAI,WAAU,iBACb,+BAAC,SAAI,OAAO,EAAE,UAAU,EAAE,GACxB;AAAA,0BAAC,QAAG,WAAU,kBAAkB,iBAAM;AAAA,MACrC,eAAe,oBAAC,OAAE,WAAU,iBAAiB,uBAAY;AAAA,OAC5D,GACF;AAAA,IACC,WAAW,oBAAC,SAAI,WAAU,oBAAoB,mBAAQ;AAAA,KACzD;AAEJ;","names":[]}
@@ -75,7 +75,17 @@
75
75
  padding: 0 0 24px;
76
76
  }
77
77
 
78
+ /* A table's own x-scroll wrapper would capture the sticky header; let the
79
+ panel body be the one scroller so column headers stay pinned. */
80
+ .body div:has(> table) {
81
+ overflow: visible;
82
+ }
83
+
84
+ /* Sticky column headers when a table scrolls inside the card. */
78
85
  .body thead th {
86
+ position: sticky;
87
+ top: 0;
88
+ z-index: 1;
79
89
  background: var(--bg-base);
80
90
  }
81
91
 
@@ -14,8 +14,7 @@ function StatCard({
14
14
  href,
15
15
  compact,
16
16
  note,
17
- alert,
18
- accent
17
+ alert
19
18
  }) {
20
19
  const body = /* @__PURE__ */ jsxs(Fragment, { children: [
21
20
  /* @__PURE__ */ jsxs("div", { className: "adm-stat-head", children: [
@@ -61,15 +60,7 @@ function StatCard({
61
60
  }
62
61
  );
63
62
  }
64
- return /* @__PURE__ */ jsx(
65
- "div",
66
- {
67
- className: `adm-stat${compact ? " adm-stat--compact" : ""}`,
68
- "data-accent": accent ? "" : void 0,
69
- style: accent ? { "--adm-stat-accent": accent } : void 0,
70
- children: body
71
- }
72
- );
63
+ return /* @__PURE__ */ jsx("div", { className: `adm-stat${compact ? " adm-stat--compact" : ""}`, children: body });
73
64
  }
74
65
  const isWord = (value) => typeof value === "string" && !/\d/.test(value) && value.length > 10;
75
66
  function DeltaPill({ delta }) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/_ui/stat-card.tsx"],"sourcesContent":["import type { ComponentType, CSSProperties, ReactNode } from 'react';\nimport Link from 'next/link';\nimport { Skeleton } from './skeleton';\nimport { Sparkline } from './sparkline';\n\nexport interface StatCardDelta {\n /** Period-over-period change. `null` = no prior baseline to compare against. */\n pct: number | null;\n /** When false, an increase is rendered red and a decrease green. Default true. */\n positiveIsGood?: boolean;\n /** Optional caption, e.g. \"vs prev 30d\". */\n caption?: string;\n}\n\n/**\n * KPI stat card: label, value (weight 500), optional sub-line or plain-text\n * delta, and an optional sparkline. No icon chip. Becomes a link with `href`.\n */\nexport function StatCard({\n label,\n value,\n sub,\n hint,\n isLoading,\n delta,\n spark,\n sparkColor,\n href,\n compact,\n note,\n alert,\n accent,\n}: {\n label: string;\n value: ReactNode;\n sub?: string;\n /** Accepted for older callers; KPI cards no longer draw an icon chip. */\n icon?: ComponentType<{ size?: number; color?: string }>;\n hint?: string;\n isLoading?: boolean;\n delta?: StatCardDelta;\n spark?: number[];\n sparkColor?: string;\n href?: string;\n compact?: boolean;\n /** Quiet secondary text under the label. */\n note?: string;\n /** Paint the value red: a queue that needs someone now. */\n alert?: boolean;\n /** A colour key for the card: a bar along its top edge, so it can match a chart slice. */\n accent?: string;\n}) {\n const body = (\n <>\n <div className=\"adm-stat-head\">\n {hint ? (\n <span\n className=\"adm-stat-label adm-hint\"\n tabIndex={0}\n role=\"note\"\n aria-label={hint}\n >\n {label}\n <span className=\"adm-hint-bubble\" role=\"tooltip\">\n {hint}\n </span>\n </span>\n ) : (\n <span className=\"adm-stat-label\" title={label}>\n {label}\n </span>\n )}\n {note && <span className=\"adm-stat-note\">{note}</span>}\n </div>\n {isLoading ? (\n <Skeleton width={96} height={30} />\n ) : (\n <div\n className={`adm-stat-value${isWord(value) ? ' adm-stat-value--text' : ''}`}\n data-alert={alert || undefined}\n title={typeof value === 'string' ? value : undefined}\n >\n {value}\n </div>\n )}\n {!isLoading && (delta || sub || (spark && spark.length > 0)) && (\n <div className=\"adm-stat-foot\">\n <div className=\"adm-stat-foot-meta\">\n {delta ? (\n <DeltaPill delta={delta} />\n ) : sub ? (\n <span className=\"adm-stat-sub\" title={sub}>\n {sub}\n </span>\n ) : null}\n {delta?.caption && (\n <span className=\"adm-stat-caption\">{delta.caption}</span>\n )}\n </div>\n {spark && spark.length > 0 && (\n <Sparkline values={spark} color={sparkColor} />\n )}\n </div>\n )}\n </>\n );\n\n if (href) {\n return (\n <Link\n href={href}\n className={`adm-stat adm-stat-link${\n compact ? ' adm-stat--compact' : ''\n }`}\n >\n {body}\n </Link>\n );\n }\n return (\n <div\n className={`adm-stat${compact ? ' adm-stat--compact' : ''}`}\n data-accent={accent ? '' : undefined}\n style={accent ? ({ '--adm-stat-accent': accent } as CSSProperties) : undefined}\n >\n {body}\n </div>\n );\n}\n\n/** A label rather than a number: set at body size so it never shouts. */\nconst isWord = (value: ReactNode) =>\n typeof value === 'string' && !/\\d/.test(value) && value.length > 10;\n\nfunction DeltaPill({ delta }: { delta: StatCardDelta }) {\n const positiveIsGood = delta.positiveIsGood ?? true;\n if (delta.pct === null) {\n return <span className=\"adm-delta adm-delta--flat\">New</span>;\n }\n const up = delta.pct > 0;\n const flat = delta.pct === 0;\n const good = flat ? false : up === positiveIsGood;\n const tone = flat ? 'flat' : good ? 'good' : 'bad';\n // The sign carries the direction in words, so no arrow glyph is needed.\n const sign = flat ? '' : up ? '+' : '\\u2212';\n return (\n <span className={`adm-delta adm-delta--${tone}`}>\n {sign}\n {Math.abs(delta.pct)}%\n </span>\n );\n}\n"],"mappings":"AAqDI,mBAUQ,KAPF,YAHN;AApDJ,OAAO,UAAU;AACjB,SAAS,gBAAgB;AACzB,SAAS,iBAAiB;AAenB,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAmBG;AACD,QAAM,OACJ,iCACE;AAAA,yBAAC,SAAI,WAAU,iBACZ;AAAA,aACC;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,UAAU;AAAA,UACV,MAAK;AAAA,UACL,cAAY;AAAA,UAEX;AAAA;AAAA,YACD,oBAAC,UAAK,WAAU,mBAAkB,MAAK,WACpC,gBACH;AAAA;AAAA;AAAA,MACF,IAEA,oBAAC,UAAK,WAAU,kBAAiB,OAAO,OACrC,iBACH;AAAA,MAED,QAAQ,oBAAC,UAAK,WAAU,iBAAiB,gBAAK;AAAA,OACjD;AAAA,IACC,YACC,oBAAC,YAAS,OAAO,IAAI,QAAQ,IAAI,IAEjC;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,iBAAiB,OAAO,KAAK,IAAI,0BAA0B,EAAE;AAAA,QACxE,cAAY,SAAS;AAAA,QACrB,OAAO,OAAO,UAAU,WAAW,QAAQ;AAAA,QAE1C;AAAA;AAAA,IACH;AAAA,IAED,CAAC,cAAc,SAAS,OAAQ,SAAS,MAAM,SAAS,MACvD,qBAAC,SAAI,WAAU,iBACb;AAAA,2BAAC,SAAI,WAAU,sBACZ;AAAA,gBACC,oBAAC,aAAU,OAAc,IACvB,MACF,oBAAC,UAAK,WAAU,gBAAe,OAAO,KACnC,eACH,IACE;AAAA,QACH,OAAO,WACN,oBAAC,UAAK,WAAU,oBAAoB,gBAAM,SAAQ;AAAA,SAEtD;AAAA,MACC,SAAS,MAAM,SAAS,KACvB,oBAAC,aAAU,QAAQ,OAAO,OAAO,YAAY;AAAA,OAEjD;AAAA,KAEJ;AAGF,MAAI,MAAM;AACR,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,WAAW,yBACT,UAAU,uBAAuB,EACnC;AAAA,QAEC;AAAA;AAAA,IACH;AAAA,EAEJ;AACA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,WAAW,UAAU,uBAAuB,EAAE;AAAA,MACzD,eAAa,SAAS,KAAK;AAAA,MAC3B,OAAO,SAAU,EAAE,qBAAqB,OAAO,IAAsB;AAAA,MAEpE;AAAA;AAAA,EACH;AAEJ;AAGA,MAAM,SAAS,CAAC,UACd,OAAO,UAAU,YAAY,CAAC,KAAK,KAAK,KAAK,KAAK,MAAM,SAAS;AAEnE,SAAS,UAAU,EAAE,MAAM,GAA6B;AACtD,QAAM,iBAAiB,MAAM,kBAAkB;AAC/C,MAAI,MAAM,QAAQ,MAAM;AACtB,WAAO,oBAAC,UAAK,WAAU,6BAA4B,iBAAG;AAAA,EACxD;AACA,QAAM,KAAK,MAAM,MAAM;AACvB,QAAM,OAAO,MAAM,QAAQ;AAC3B,QAAM,OAAO,OAAO,QAAQ,OAAO;AACnC,QAAM,OAAO,OAAO,SAAS,OAAO,SAAS;AAE7C,QAAM,OAAO,OAAO,KAAK,KAAK,MAAM;AACpC,SACE,qBAAC,UAAK,WAAW,wBAAwB,IAAI,IAC1C;AAAA;AAAA,IACA,KAAK,IAAI,MAAM,GAAG;AAAA,IAAE;AAAA,KACvB;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/_ui/stat-card.tsx"],"sourcesContent":["import type { ComponentType, ReactNode } from 'react';\nimport Link from 'next/link';\nimport { Skeleton } from './skeleton';\nimport { Sparkline } from './sparkline';\n\nexport interface StatCardDelta {\n /** Period-over-period change. `null` = no prior baseline to compare against. */\n pct: number | null;\n /** When false, an increase is rendered red and a decrease green. Default true. */\n positiveIsGood?: boolean;\n /** Optional caption, e.g. \"vs prev 30d\". */\n caption?: string;\n}\n\n/**\n * KPI stat card: label, value (weight 500), optional sub-line or plain-text\n * delta, and an optional sparkline. No icon chip. Becomes a link with `href`.\n */\nexport function StatCard({\n label,\n value,\n sub,\n hint,\n isLoading,\n delta,\n spark,\n sparkColor,\n href,\n compact,\n note,\n alert,\n}: {\n label: string;\n value: ReactNode;\n sub?: string;\n /** Accepted for older callers; KPI cards no longer draw an icon chip. */\n icon?: ComponentType<{ size?: number; color?: string }>;\n hint?: string;\n isLoading?: boolean;\n delta?: StatCardDelta;\n spark?: number[];\n sparkColor?: string;\n href?: string;\n compact?: boolean;\n /** Quiet secondary text under the label. */\n note?: string;\n /** Paint the value red: a queue that needs someone now. */\n alert?: boolean;\n}) {\n const body = (\n <>\n <div className=\"adm-stat-head\">\n {hint ? (\n <span\n className=\"adm-stat-label adm-hint\"\n tabIndex={0}\n role=\"note\"\n aria-label={hint}\n >\n {label}\n <span className=\"adm-hint-bubble\" role=\"tooltip\">\n {hint}\n </span>\n </span>\n ) : (\n <span className=\"adm-stat-label\" title={label}>\n {label}\n </span>\n )}\n {note && <span className=\"adm-stat-note\">{note}</span>}\n </div>\n {isLoading ? (\n <Skeleton width={96} height={30} />\n ) : (\n <div\n className={`adm-stat-value${isWord(value) ? ' adm-stat-value--text' : ''}`}\n data-alert={alert || undefined}\n title={typeof value === 'string' ? value : undefined}\n >\n {value}\n </div>\n )}\n {!isLoading && (delta || sub || (spark && spark.length > 0)) && (\n <div className=\"adm-stat-foot\">\n <div className=\"adm-stat-foot-meta\">\n {delta ? (\n <DeltaPill delta={delta} />\n ) : sub ? (\n <span className=\"adm-stat-sub\" title={sub}>\n {sub}\n </span>\n ) : null}\n {delta?.caption && (\n <span className=\"adm-stat-caption\">{delta.caption}</span>\n )}\n </div>\n {spark && spark.length > 0 && (\n <Sparkline values={spark} color={sparkColor} />\n )}\n </div>\n )}\n </>\n );\n\n if (href) {\n return (\n <Link\n href={href}\n className={`adm-stat adm-stat-link${\n compact ? ' adm-stat--compact' : ''\n }`}\n >\n {body}\n </Link>\n );\n }\n return (\n <div className={`adm-stat${compact ? ' adm-stat--compact' : ''}`}>\n {body}\n </div>\n );\n}\n\n/** A label rather than a number: set at body size so it never shouts. */\nconst isWord = (value: ReactNode) =>\n typeof value === 'string' && !/\\d/.test(value) && value.length > 10;\n\nfunction DeltaPill({ delta }: { delta: StatCardDelta }) {\n const positiveIsGood = delta.positiveIsGood ?? true;\n if (delta.pct === null) {\n return <span className=\"adm-delta adm-delta--flat\">New</span>;\n }\n const up = delta.pct > 0;\n const flat = delta.pct === 0;\n const good = flat ? false : up === positiveIsGood;\n const tone = flat ? 'flat' : good ? 'good' : 'bad';\n // The sign carries the direction in words, so no arrow glyph is needed.\n const sign = flat ? '' : up ? '+' : '\\u2212';\n return (\n <span className={`adm-delta adm-delta--${tone}`}>\n {sign}\n {Math.abs(delta.pct)}%\n </span>\n );\n}\n"],"mappings":"AAkDI,mBAUQ,KAPF,YAHN;AAjDJ,OAAO,UAAU;AACjB,SAAS,gBAAgB;AACzB,SAAS,iBAAiB;AAenB,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAiBG;AACD,QAAM,OACJ,iCACE;AAAA,yBAAC,SAAI,WAAU,iBACZ;AAAA,aACC;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,UAAU;AAAA,UACV,MAAK;AAAA,UACL,cAAY;AAAA,UAEX;AAAA;AAAA,YACD,oBAAC,UAAK,WAAU,mBAAkB,MAAK,WACpC,gBACH;AAAA;AAAA;AAAA,MACF,IAEA,oBAAC,UAAK,WAAU,kBAAiB,OAAO,OACrC,iBACH;AAAA,MAED,QAAQ,oBAAC,UAAK,WAAU,iBAAiB,gBAAK;AAAA,OACjD;AAAA,IACC,YACC,oBAAC,YAAS,OAAO,IAAI,QAAQ,IAAI,IAEjC;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,iBAAiB,OAAO,KAAK,IAAI,0BAA0B,EAAE;AAAA,QACxE,cAAY,SAAS;AAAA,QACrB,OAAO,OAAO,UAAU,WAAW,QAAQ;AAAA,QAE1C;AAAA;AAAA,IACH;AAAA,IAED,CAAC,cAAc,SAAS,OAAQ,SAAS,MAAM,SAAS,MACvD,qBAAC,SAAI,WAAU,iBACb;AAAA,2BAAC,SAAI,WAAU,sBACZ;AAAA,gBACC,oBAAC,aAAU,OAAc,IACvB,MACF,oBAAC,UAAK,WAAU,gBAAe,OAAO,KACnC,eACH,IACE;AAAA,QACH,OAAO,WACN,oBAAC,UAAK,WAAU,oBAAoB,gBAAM,SAAQ;AAAA,SAEtD;AAAA,MACC,SAAS,MAAM,SAAS,KACvB,oBAAC,aAAU,QAAQ,OAAO,OAAO,YAAY;AAAA,OAEjD;AAAA,KAEJ;AAGF,MAAI,MAAM;AACR,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,WAAW,yBACT,UAAU,uBAAuB,EACnC;AAAA,QAEC;AAAA;AAAA,IACH;AAAA,EAEJ;AACA,SACE,oBAAC,SAAI,WAAW,WAAW,UAAU,uBAAuB,EAAE,IAC3D,gBACH;AAEJ;AAGA,MAAM,SAAS,CAAC,UACd,OAAO,UAAU,YAAY,CAAC,KAAK,KAAK,KAAK,KAAK,MAAM,SAAS;AAEnE,SAAS,UAAU,EAAE,MAAM,GAA6B;AACtD,QAAM,iBAAiB,MAAM,kBAAkB;AAC/C,MAAI,MAAM,QAAQ,MAAM;AACtB,WAAO,oBAAC,UAAK,WAAU,6BAA4B,iBAAG;AAAA,EACxD;AACA,QAAM,KAAK,MAAM,MAAM;AACvB,QAAM,OAAO,MAAM,QAAQ;AAC3B,QAAM,OAAO,OAAO,QAAQ,OAAO;AACnC,QAAM,OAAO,OAAO,SAAS,OAAO,SAAS;AAE7C,QAAM,OAAO,OAAO,KAAK,KAAK,MAAM;AACpC,SACE,qBAAC,UAAK,WAAW,wBAAwB,IAAI,IAC1C;AAAA;AAAA,IACA,KAAK,IAAI,MAAM,GAAG;AAAA,IAAE;AAAA,KACvB;AAEJ;","names":[]}
@@ -20,9 +20,9 @@ function GmvOrdersChart({
20
20
  data: gmvTrend,
21
21
  compare: compare ? previousGmvTrend : null,
22
22
  compareLabel: `Previous ${span}`,
23
- label: "Sales",
23
+ label: "GMV",
24
24
  formatValue: money,
25
- emptyMessage: "No sales in this period",
25
+ emptyMessage: "No merchant GMV in this period",
26
26
  height: 160
27
27
  }
28
28
  ),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/_ui/trend.tsx"],"sourcesContent":["'use client';\n\nimport { TimeSeriesChart } from '@withpotter/components/charts';\n\n/** A `{date, N}[]` series reshaped into the `{date, value}` TimeSeriesChart wants. */\nexport function toTrend<K extends string>(\n series: Array<{ date: string } & Record<K, number>> | undefined,\n key: K,\n): Array<{ date: string; value: number }> {\n return (series ?? []).map((p) => ({ date: p.date, value: p[key] }));\n}\n\n/**\n * GMV over orders: the two measures share a window but not a scale, so\n * they're two stacked charts rather than one dual-axis one. Used wherever a\n * page shows both for the same range (Overview, Merchants, Commerce).\n */\nexport function GmvOrdersChart({\n gmvTrend,\n ordersTrend,\n previousGmvTrend,\n previousOrdersTrend,\n compare,\n span,\n money,\n}: {\n gmvTrend: Array<{ date: string; value: number }>;\n ordersTrend: Array<{ date: string; value: number }>;\n previousGmvTrend: Array<{ date: string; value: number }>;\n previousOrdersTrend: Array<{ date: string; value: number }>;\n /** False for \"all time\": there is no earlier period to compare with. */\n compare: boolean;\n span: string;\n money: (value: number) => string;\n}) {\n return (\n <>\n <TimeSeriesChart\n data={gmvTrend}\n compare={compare ? previousGmvTrend : null}\n compareLabel={`Previous ${span}`}\n label=\"Sales\"\n formatValue={money}\n emptyMessage=\"No sales in this period\"\n height={160}\n />\n <div\n style={{\n margin: '20px 0 8px',\n fontSize: 12,\n fontWeight: 600,\n color: 'var(--text-tertiary)',\n }}\n >\n Orders\n </div>\n <TimeSeriesChart\n data={ordersTrend}\n compare={compare ? previousOrdersTrend : null}\n compareLabel={`Previous ${span}`}\n label=\"Orders\"\n emptyMessage=\"No orders in this period\"\n height={100}\n />\n </>\n );\n}\n"],"mappings":";AAoCI,mBACE,KADF;AAlCJ,SAAS,uBAAuB;AAGzB,SAAS,QACd,QACA,KACwC;AACxC,UAAQ,UAAU,CAAC,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,EAAE,GAAG,EAAE,EAAE;AACpE;AAOO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GASG;AACD,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,SAAS,UAAU,mBAAmB;AAAA,QACtC,cAAc,YAAY,IAAI;AAAA,QAC9B,OAAM;AAAA,QACN,aAAa;AAAA,QACb,cAAa;AAAA,QACb,QAAQ;AAAA;AAAA,IACV;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO;AAAA,UACL,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,YAAY;AAAA,UACZ,OAAO;AAAA,QACT;AAAA,QACD;AAAA;AAAA,IAED;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,SAAS,UAAU,sBAAsB;AAAA,QACzC,cAAc,YAAY,IAAI;AAAA,QAC9B,OAAM;AAAA,QACN,cAAa;AAAA,QACb,QAAQ;AAAA;AAAA,IACV;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/_ui/trend.tsx"],"sourcesContent":["'use client';\n\nimport { TimeSeriesChart } from '@withpotter/components/charts';\n\n/** A `{date, N}[]` series reshaped into the `{date, value}` TimeSeriesChart wants. */\nexport function toTrend<K extends string>(\n series: Array<{ date: string } & Record<K, number>> | undefined,\n key: K,\n): Array<{ date: string; value: number }> {\n return (series ?? []).map((p) => ({ date: p.date, value: p[key] }));\n}\n\n/**\n * GMV over orders: the two measures share a window but not a scale, so\n * they're two stacked charts rather than one dual-axis one. Used wherever a\n * page shows both for the same range (Overview, Merchants, Commerce).\n */\nexport function GmvOrdersChart({\n gmvTrend,\n ordersTrend,\n previousGmvTrend,\n previousOrdersTrend,\n compare,\n span,\n money,\n}: {\n gmvTrend: Array<{ date: string; value: number }>;\n ordersTrend: Array<{ date: string; value: number }>;\n previousGmvTrend: Array<{ date: string; value: number }>;\n previousOrdersTrend: Array<{ date: string; value: number }>;\n /** False for \"all time\": there is no earlier period to compare with. */\n compare: boolean;\n span: string;\n money: (value: number) => string;\n}) {\n return (\n <>\n <TimeSeriesChart\n data={gmvTrend}\n compare={compare ? previousGmvTrend : null}\n compareLabel={`Previous ${span}`}\n label=\"GMV\"\n formatValue={money}\n emptyMessage=\"No merchant GMV in this period\"\n height={160}\n />\n <div\n style={{\n margin: '20px 0 8px',\n fontSize: 12,\n fontWeight: 600,\n color: 'var(--text-tertiary)',\n }}\n >\n Orders\n </div>\n <TimeSeriesChart\n data={ordersTrend}\n compare={compare ? previousOrdersTrend : null}\n compareLabel={`Previous ${span}`}\n label=\"Orders\"\n emptyMessage=\"No orders in this period\"\n height={100}\n />\n </>\n );\n}\n"],"mappings":";AAoCI,mBACE,KADF;AAlCJ,SAAS,uBAAuB;AAGzB,SAAS,QACd,QACA,KACwC;AACxC,UAAQ,UAAU,CAAC,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,EAAE,GAAG,EAAE,EAAE;AACpE;AAOO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GASG;AACD,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,SAAS,UAAU,mBAAmB;AAAA,QACtC,cAAc,YAAY,IAAI;AAAA,QAC9B,OAAM;AAAA,QACN,aAAa;AAAA,QACb,cAAa;AAAA,QACb,QAAQ;AAAA;AAAA,IACV;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO;AAAA,UACL,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,YAAY;AAAA,UACZ,OAAO;AAAA,QACT;AAAA,QACD;AAAA;AAAA,IAED;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,SAAS,UAAU,sBAAsB;AAAA,QACzC,cAAc,YAAY,IAAI;AAAA,QAC9B,OAAM;AAAA,QACN,cAAa;AAAA,QACb,QAAQ;AAAA;AAAA,IACV;AAAA,KACF;AAEJ;","names":[]}
@@ -1,20 +1,10 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  import { createContext, useContext } from "react";
4
- import { motion, useReducedMotion } from "motion/react";
5
4
  const NESTED_TAB_PARAM = "view";
6
5
  const InWorkspaceTab = createContext(false);
7
6
  function WorkspaceTab({ children }) {
8
- const calm = useReducedMotion();
9
- return /* @__PURE__ */ jsx(InWorkspaceTab.Provider, { value: true, children: /* @__PURE__ */ jsx(
10
- motion.div,
11
- {
12
- initial: calm ? false : { opacity: 0, y: 8 },
13
- animate: { opacity: 1, y: 0 },
14
- transition: { duration: 0.2, ease: [0, 0, 0.2, 1] },
15
- children
16
- }
17
- ) });
7
+ return /* @__PURE__ */ jsx(InWorkspaceTab.Provider, { value: true, children });
18
8
  }
19
9
  const useInWorkspaceTab = () => useContext(InWorkspaceTab);
20
10
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/_ui/workspace-tab.tsx"],"sourcesContent":["'use client';\n\nimport { createContext, useContext, type ReactNode } from 'react';\nimport { motion, useReducedMotion } from 'motion/react';\n\n/** The address parameter tabs inside a workspace tab use, so they never fight the workspace's `?tab=`. */\nexport const NESTED_TAB_PARAM = 'view';\n\nconst InWorkspaceTab = createContext(false);\n\n/**\n * Wraps a page shown as one tab of a workspace (Money, say). Inside it the\n * page's header drops its title, which the tab already shows, and keeps its\n * one line of context and its actions; tabs within the page move to `?view=`.\n */\nexport function WorkspaceTab({ children }: { children: ReactNode }) {\n const calm = useReducedMotion();\n\n return (\n <InWorkspaceTab.Provider value={true}>\n {/* The first paint of a tab eases up and in (200ms), so opening one reads as arriving, not\n as the page being replaced. Transform and opacity only, and none of it under reduced motion. */}\n <motion.div\n initial={calm ? false : { opacity: 0, y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ duration: 0.2, ease: [0, 0, 0.2, 1] }}\n >\n {children}\n </motion.div>\n </InWorkspaceTab.Provider>\n );\n}\n\nexport const useInWorkspaceTab = () => useContext(InWorkspaceTab);\n"],"mappings":";AAsBM;AApBN,SAAS,eAAe,kBAAkC;AAC1D,SAAS,QAAQ,wBAAwB;AAGlC,MAAM,mBAAmB;AAEhC,MAAM,iBAAiB,cAAc,KAAK;AAOnC,SAAS,aAAa,EAAE,SAAS,GAA4B;AAClE,QAAM,OAAO,iBAAiB;AAE9B,SACE,oBAAC,eAAe,UAAf,EAAwB,OAAO,MAG9B;AAAA,IAAC,OAAO;AAAA,IAAP;AAAA,MACC,SAAS,OAAO,QAAQ,EAAE,SAAS,GAAG,GAAG,EAAE;AAAA,MAC3C,SAAS,EAAE,SAAS,GAAG,GAAG,EAAE;AAAA,MAC5B,YAAY,EAAE,UAAU,KAAK,MAAM,CAAC,GAAG,GAAG,KAAK,CAAC,EAAE;AAAA,MAEjD;AAAA;AAAA,EACH,GACF;AAEJ;AAEO,MAAM,oBAAoB,MAAM,WAAW,cAAc;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/_ui/workspace-tab.tsx"],"sourcesContent":["'use client';\n\nimport { createContext, useContext, type ReactNode } from 'react';\n\n/** The address parameter tabs inside a workspace tab use, so they never fight the workspace's `?tab=`. */\nexport const NESTED_TAB_PARAM = 'view';\n\nconst InWorkspaceTab = createContext(false);\n\n/**\n * Wraps a page shown as one tab of a workspace (Money, say). Inside it the\n * page's header drops its title, which the tab already shows, and keeps its\n * one line of context and its actions; tabs within the page move to `?view=`.\n */\nexport function WorkspaceTab({ children }: { children: ReactNode }) {\n return (\n <InWorkspaceTab.Provider value={true}>{children}</InWorkspaceTab.Provider>\n );\n}\n\nexport const useInWorkspaceTab = () => useContext(InWorkspaceTab);\n"],"mappings":";AAgBI;AAdJ,SAAS,eAAe,kBAAkC;AAGnD,MAAM,mBAAmB;AAEhC,MAAM,iBAAiB,cAAc,KAAK;AAOnC,SAAS,aAAa,EAAE,SAAS,GAA4B;AAClE,SACE,oBAAC,eAAe,UAAf,EAAwB,OAAO,MAAO,UAAS;AAEpD;AAEO,MAAM,oBAAoB,MAAM,WAAW,cAAc;","names":[]}
@@ -7,16 +7,6 @@ import { StatusPill } from "../audit/status-pill";
7
7
  import { useAccountTenants } from "../hooks";
8
8
  import { CopyText } from "../tenants/[id]/copy-text";
9
9
  import { AccountActivity } from "./account-activity";
10
- import {
11
- ACTIVE_HINT,
12
- providerLabel,
13
- STAGE_LABELS,
14
- stageStatus,
15
- stepStates,
16
- stoppedAt
17
- } from "./account-stage";
18
- import { AccountSessions } from "./account-sessions";
19
- import { Hint } from "./hint";
20
10
  import { AccountVitals } from "./account-vitals";
21
11
  import { useAccountActions } from "./use-account-actions";
22
12
  import { useModuleOn } from "../_shell/modules-context";
@@ -27,14 +17,12 @@ function Section({
27
17
  title,
28
18
  index,
29
19
  count,
30
- hint,
31
20
  children
32
21
  }) {
33
22
  return /* @__PURE__ */ jsxs(Reveal, { as: "section", index, className: styles.section, children: [
34
23
  /* @__PURE__ */ jsxs("h3", { className: styles.heading, children: [
35
24
  title,
36
- count !== void 0 && /* @__PURE__ */ jsx("span", { className: styles.count, children: count }),
37
- hint && /* @__PURE__ */ jsx(Hint, { label: hint })
25
+ count !== void 0 && /* @__PURE__ */ jsx("span", { className: styles.count, children: count })
38
26
  ] }),
39
27
  children
40
28
  ] });
@@ -68,14 +56,14 @@ function AccountPanel({
68
56
  label: account.emailVerified ? "Email verified" : "Email unverified"
69
57
  }
70
58
  ),
71
- account.stage && /* @__PURE__ */ jsx(
59
+ account.mfaEnabled !== void 0 && /* @__PURE__ */ jsx(
72
60
  StatusPill,
73
61
  {
74
- status: stageStatus(account.stage),
75
- label: STAGE_LABELS[account.stage]
62
+ status: account.mfaEnabled ? "active" : "deleted",
63
+ label: account.mfaEnabled ? "2FA on" : "2FA off"
76
64
  }
77
65
  ),
78
- account.authProvider && /* @__PURE__ */ jsx("span", { className: styles.muted, children: providerLabel(account.authProvider) })
66
+ account.authProvider && /* @__PURE__ */ jsx("span", { className: styles.muted, children: account.authProvider })
79
67
  ] }),
80
68
  footer: account && /* @__PURE__ */ jsxs(Fragment, { children: [
81
69
  account.status && /* @__PURE__ */ jsx(
@@ -114,27 +102,6 @@ function AccountPanel({
114
102
  )
115
103
  ] }),
116
104
  children: account && /* @__PURE__ */ jsxs("div", { className: styles.panel, children: [
117
- account.stage && /* @__PURE__ */ jsxs("div", { className: styles.stopped, children: [
118
- /* @__PURE__ */ jsxs("span", { className: styles.stoppedLabel, children: [
119
- "Where this account stopped",
120
- /* @__PURE__ */ jsx(Hint, { label: ACTIVE_HINT })
121
- ] }),
122
- /* @__PURE__ */ jsx("ol", { className: styles.steps, children: stepStates(account).map(
123
- (step) => /* @__PURE__ */ jsxs(
124
- "li",
125
- {
126
- className: styles.step,
127
- "data-state": step.state,
128
- children: [
129
- /* @__PURE__ */ jsx("span", { className: styles.stepDot, "aria-hidden": true }),
130
- step.label
131
- ]
132
- },
133
- step.label
134
- )
135
- ) }),
136
- /* @__PURE__ */ jsx("span", { className: styles.stoppedText, children: stoppedAt(account) ?? "Active" })
137
- ] }),
138
105
  [verify, impersonate, setStatus].map(
139
106
  (action, i) => action.isError && /* @__PURE__ */ jsx(Banner, { tone: "error", title: "That didn\u2019t work", children: action.error.message }, i)
140
107
  ),
@@ -156,17 +123,7 @@ function AccountPanel({
156
123
  }
157
124
  ),
158
125
  /* @__PURE__ */ jsx(Section, { title: "Workspaces", index: 1, count: liveWorkspaces, children: /* @__PURE__ */ jsx(Workspaces, { accountId: account.id }) }),
159
- /* @__PURE__ */ jsx(
160
- Section,
161
- {
162
- title: "Sessions",
163
- index: 2,
164
- count: account.activeSessions,
165
- hint: "The number is devices signed in now.",
166
- children: /* @__PURE__ */ jsx(AccountSessions, { accountId: account.id })
167
- }
168
- ),
169
- /* @__PURE__ */ jsx(Section, { title: "Account ID", index: 3, children: /* @__PURE__ */ jsx(CopyText, { value: account.id }) })
126
+ /* @__PURE__ */ jsx(Section, { title: "Account ID", index: 2, children: /* @__PURE__ */ jsx(CopyText, { value: account.id }) })
170
127
  ] })
171
128
  }
172
129
  );
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/accounts/account-panel.tsx"],"sourcesContent":["'use client';\n\nimport { Banner, Button } from '@withpotter/ui-kit';\nimport type { AccountRow } from '@withpotter/lib/admin-api';\nimport { Reveal } from '../_fx';\nimport { Drawer } from '../_ui';\nimport { StatusPill } from '../audit/status-pill';\nimport { useAccountTenants } from '../hooks';\nimport { CopyText } from '../tenants/[id]/copy-text';\nimport { AccountActivity } from './account-activity';\nimport {\n ACTIVE_HINT,\n providerLabel,\n STAGE_LABELS,\n stageStatus,\n stepStates,\n stoppedAt,\n} from './account-stage';\nimport type { StepState } from './account-stage';\nimport { AccountSessions } from './account-sessions';\nimport { Hint } from './hint';\nimport { AccountVitals } from './account-vitals';\nimport { useAccountActions } from './use-account-actions';\nimport { useModuleOn } from '../_shell/modules-context';\nimport { useAccountActivity } from './use-account-activity';\nimport { Workspaces } from './account-workspaces';\nimport styles from './account-panel.module.css';\n\n/** Enough to open the panel from anywhere; the table row adds the rest. */\nexport type AccountSummary = Pick<AccountRow, 'id' | 'email' | 'name'> &\n Partial<AccountRow>;\n\nfunction Section({\n title,\n index,\n count,\n hint,\n children,\n}: {\n title: string;\n index: number;\n count?: number;\n hint?: string;\n children: React.ReactNode;\n}) {\n return (\n <Reveal as=\"section\" index={index} className={styles.section}>\n <h3 className={styles.heading}>\n {title}\n {count !== undefined && <span className={styles.count}>{count}</span>}\n {hint && <Hint label={hint} />}\n </h3>\n {children}\n </Reveal>\n );\n}\n\nexport function AccountPanel({\n account,\n onClose,\n}: {\n account: AccountSummary | null;\n onClose: () => void;\n}) {\n const { setStatus, verify, impersonate } = useAccountActions();\n const ownerView = useModuleOn('ownerView');\n const tenants = useAccountTenants(account?.id ?? null);\n const activity = useAccountActivity(account?.id ?? null, tenants.data);\n const suspended = account?.status === 'suspended';\n const liveWorkspaces = tenants.data?.filter(\n (w) => w.status !== 'deleted' && w.memberStatus !== 'removed',\n ).length;\n\n return (\n <Drawer\n open={Boolean(account)}\n onClose={onClose}\n width={560}\n title={account?.name || account?.email || 'Account'}\n subtitle={account?.name ? account.email : undefined}\n meta={\n account && (\n <>\n {account.status && <StatusPill status={account.status} />}\n {account.emailVerified !== undefined && (\n <StatusPill\n status={account.emailVerified ? 'verified' : 'unverified'}\n label={\n account.emailVerified ? 'Email verified' : 'Email unverified'\n }\n />\n )}\n {account.stage && (\n <StatusPill\n status={stageStatus(account.stage)}\n label={STAGE_LABELS[account.stage]}\n />\n )}\n {account.authProvider && (\n <span className={styles.muted}>\n {providerLabel(account.authProvider)}\n </span>\n )}\n </>\n )\n }\n footer={\n account && (\n <>\n {account.status && (\n <Button\n size=\"sm\"\n variant={suspended ? 'secondary' : 'ghostDanger'}\n loading={setStatus.isPending}\n onClick={() =>\n setStatus.mutate({\n id: account.id,\n status: suspended ? 'active' : 'suspended',\n })\n }\n >\n {suspended ? 'Restore' : 'Suspend'}\n </Button>\n )}\n <span className={styles.spacer} />\n {account.emailVerified === false && (\n <Button\n size=\"sm\"\n variant=\"secondary\"\n loading={verify.isPending}\n onClick={() => verify.mutate(account.id)}\n >\n Verify email\n </Button>\n )}\n {ownerView && (\n <Button\n size=\"sm\"\n variant=\"primary\"\n loading={impersonate.isPending}\n onClick={() => impersonate.mutate(account.id)}\n >\n View as user\n </Button>\n )}\n </>\n )\n }\n >\n {account && (\n <div className={styles.panel}>\n {account.stage && (\n <div className={styles.stopped}>\n <span className={styles.stoppedLabel}>\n Where this account stopped\n <Hint label={ACTIVE_HINT} />\n </span>\n <ol className={styles.steps}>\n {stepStates(account).map(\n (step: { label: string; state: StepState }) => (\n <li\n key={step.label}\n className={styles.step}\n data-state={step.state}\n >\n <span className={styles.stepDot} aria-hidden />\n {step.label}\n </li>\n ),\n )}\n </ol>\n <span className={styles.stoppedText}>\n {stoppedAt(account) ?? 'Active'}\n </span>\n </div>\n )}\n\n {[verify, impersonate, setStatus].map(\n (action, i) =>\n action.isError && (\n <Banner key={i} tone=\"error\" title=\"That didn’t work\">\n {action.error.message}\n </Banner>\n ),\n )}\n\n <AccountVitals account={account} workspaces={liveWorkspaces} />\n\n <Section\n title=\"Recent activity\"\n index={0}\n count={activity.entries.length}\n >\n <AccountActivity\n entries={activity.entries}\n isLoading={activity.isLoading}\n skipped={activity.skipped}\n />\n </Section>\n\n <Section title=\"Workspaces\" index={1} count={liveWorkspaces}>\n <Workspaces accountId={account.id} />\n </Section>\n\n <Section\n title=\"Sessions\"\n index={2}\n count={account.activeSessions}\n hint=\"The number is devices signed in now.\"\n >\n <AccountSessions accountId={account.id} />\n </Section>\n\n <Section title=\"Account ID\" index={3}>\n <CopyText value={account.id} />\n </Section>\n </div>\n )}\n </Drawer>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/accounts/account-panel.tsx"],"sourcesContent":["'use client';\n\nimport { Banner, Button } from '@withpotter/ui-kit';\nimport type { AccountRow } from '@withpotter/lib/admin-api';\nimport { Reveal } from '../_fx';\nimport { Drawer } from '../_ui';\nimport { StatusPill } from '../audit/status-pill';\nimport { useAccountTenants } from '../hooks';\nimport { CopyText } from '../tenants/[id]/copy-text';\nimport { AccountActivity } from './account-activity';\nimport { AccountVitals } from './account-vitals';\nimport { useAccountActions } from './use-account-actions';\nimport { useModuleOn } from '../_shell/modules-context';\nimport { useAccountActivity } from './use-account-activity';\nimport { Workspaces } from './account-workspaces';\nimport styles from './account-panel.module.css';\n\n/** Enough to open the panel from anywhere; the table row adds the rest. */\nexport type AccountSummary = Pick<AccountRow, 'id' | 'email' | 'name'> &\n Partial<AccountRow>;\n\nfunction Section({\n title,\n index,\n count,\n children,\n}: {\n title: string;\n index: number;\n count?: number;\n children: React.ReactNode;\n}) {\n return (\n <Reveal as=\"section\" index={index} className={styles.section}>\n <h3 className={styles.heading}>\n {title}\n {count !== undefined && <span className={styles.count}>{count}</span>}\n </h3>\n {children}\n </Reveal>\n );\n}\n\nexport function AccountPanel({\n account,\n onClose,\n}: {\n account: AccountSummary | null;\n onClose: () => void;\n}) {\n const { setStatus, verify, impersonate } = useAccountActions();\n const ownerView = useModuleOn('ownerView');\n const tenants = useAccountTenants(account?.id ?? null);\n const activity = useAccountActivity(account?.id ?? null, tenants.data);\n const suspended = account?.status === 'suspended';\n const liveWorkspaces = tenants.data?.filter(\n (w) => w.status !== 'deleted' && w.memberStatus !== 'removed',\n ).length;\n\n return (\n <Drawer\n open={Boolean(account)}\n onClose={onClose}\n width={560}\n title={account?.name || account?.email || 'Account'}\n subtitle={account?.name ? account.email : undefined}\n meta={\n account && (\n <>\n {account.status && <StatusPill status={account.status} />}\n {account.emailVerified !== undefined && (\n <StatusPill\n status={account.emailVerified ? 'verified' : 'unverified'}\n label={\n account.emailVerified ? 'Email verified' : 'Email unverified'\n }\n />\n )}\n {account.mfaEnabled !== undefined && (\n <StatusPill\n status={account.mfaEnabled ? 'active' : 'deleted'}\n label={account.mfaEnabled ? '2FA on' : '2FA off'}\n />\n )}\n {account.authProvider && (\n <span className={styles.muted}>{account.authProvider}</span>\n )}\n </>\n )\n }\n footer={\n account && (\n <>\n {account.status && (\n <Button\n size=\"sm\"\n variant={suspended ? 'secondary' : 'ghostDanger'}\n loading={setStatus.isPending}\n onClick={() =>\n setStatus.mutate({\n id: account.id,\n status: suspended ? 'active' : 'suspended',\n })\n }\n >\n {suspended ? 'Restore' : 'Suspend'}\n </Button>\n )}\n <span className={styles.spacer} />\n {account.emailVerified === false && (\n <Button\n size=\"sm\"\n variant=\"secondary\"\n loading={verify.isPending}\n onClick={() => verify.mutate(account.id)}\n >\n Verify email\n </Button>\n )}\n {ownerView && (\n <Button\n size=\"sm\"\n variant=\"primary\"\n loading={impersonate.isPending}\n onClick={() => impersonate.mutate(account.id)}\n >\n View as user\n </Button>\n )}\n </>\n )\n }\n >\n {account && (\n <div className={styles.panel}>\n {[verify, impersonate, setStatus].map(\n (action, i) =>\n action.isError && (\n <Banner key={i} tone=\"error\" title=\"That didn’t work\">\n {action.error.message}\n </Banner>\n ),\n )}\n\n <AccountVitals account={account} workspaces={liveWorkspaces} />\n\n <Section\n title=\"Recent activity\"\n index={0}\n count={activity.entries.length}\n >\n <AccountActivity\n entries={activity.entries}\n isLoading={activity.isLoading}\n skipped={activity.skipped}\n />\n </Section>\n\n <Section title=\"Workspaces\" index={1} count={liveWorkspaces}>\n <Workspaces accountId={account.id} />\n </Section>\n\n <Section title=\"Account ID\" index={2}>\n <CopyText value={account.id} />\n </Section>\n </div>\n )}\n </Drawer>\n );\n}\n"],"mappings":"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","names":[]}
@@ -16,10 +16,6 @@
16
16
  background: var(--bg-base);
17
17
  }
18
18
 
19
- .vitals[data-count='3'] {
20
- grid-template-columns: repeat(3, minmax(0, 1fr));
21
- }
22
-
23
19
  .vital {
24
20
  display: grid;
25
21
  gap: 4px;
@@ -142,72 +138,3 @@
142
138
  line-height: 16px;
143
139
  color: var(--text-tertiary);
144
140
  }
145
-
146
- .stopped {
147
- display: grid;
148
- gap: 12px;
149
- padding: 16px;
150
- border-radius: var(--adm-card-radius);
151
- border: 1px solid var(--adm-line);
152
- background: var(--bg-base);
153
- }
154
-
155
- .stoppedLabel {
156
- display: flex;
157
- align-items: center;
158
- gap: 4px;
159
- font-size: 12px;
160
- font-weight: 400;
161
- line-height: 16px;
162
- color: var(--text-tertiary);
163
- }
164
-
165
- .steps {
166
- display: grid;
167
- grid-template-columns: repeat(4, minmax(0, 1fr));
168
- gap: 8px;
169
- margin: 0;
170
- padding: 0;
171
- list-style: none;
172
- }
173
-
174
- .step {
175
- display: grid;
176
- gap: 8px;
177
- font-size: 12px;
178
- line-height: 16px;
179
- color: var(--text-tertiary);
180
- }
181
-
182
- .stepDot {
183
- width: 8px;
184
- height: 8px;
185
- border-radius: 999px;
186
- box-shadow: inset 0 0 0 1px var(--border-subtle);
187
- }
188
-
189
- .step[data-state='done'] {
190
- color: var(--text-secondary);
191
- }
192
-
193
- .step[data-state='done'] .stepDot {
194
- background: var(--adm-ok);
195
- box-shadow: none;
196
- }
197
-
198
- .step[data-state='current'] {
199
- font-weight: 600;
200
- color: var(--text-primary);
201
- }
202
-
203
- .step[data-state='current'] .stepDot {
204
- background: var(--adm-wait);
205
- box-shadow: none;
206
- }
207
-
208
- .stoppedText {
209
- font-size: 14px;
210
- font-weight: 500;
211
- line-height: 20px;
212
- color: var(--text-primary);
213
- }
@@ -11,7 +11,6 @@ import { EntityAvatar } from "../_fx";
11
11
  import { EMPTY } from "../_ui/list-card";
12
12
  import { StatusPill } from "../audit/status-pill";
13
13
  import { When } from "../audit/when";
14
- import { kycLabel, STAGE_LABELS, stageStatus } from "./account-stage";
15
14
  import { useAccountActions } from "./use-account-actions";
16
15
  import { useModuleOn } from "../_shell/modules-context";
17
16
  import list from "../_ui/list-card.module.css";
@@ -77,19 +76,11 @@ function AccountListRow({
77
76
  ] }) }),
78
77
  visible.has("provider") && /* @__PURE__ */ jsx("td", { className: `${list.nowrap} ${list.muted}`, children: PROVIDER_LABELS[a.authProvider] ?? (a.authProvider || EMPTY) }),
79
78
  /* @__PURE__ */ jsx("td", { className: list.nowrap, children: /* @__PURE__ */ jsx(StatusPill, { status: a.status }) }),
80
- visible.has("kyc") && /* @__PURE__ */ jsx("td", { className: list.nowrap, children: kycLabel(a.kycStatus) ?? /* @__PURE__ */ jsx("span", { className: list.muted, children: EMPTY }) }),
81
- visible.has("stage") && /* @__PURE__ */ jsx("td", { className: list.nowrap, children: a.stage ? /* @__PURE__ */ jsx(
82
- StatusPill,
83
- {
84
- status: stageStatus(a.stage),
85
- label: STAGE_LABELS[a.stage]
86
- }
87
- ) : /* @__PURE__ */ jsx("span", { className: list.muted, children: EMPTY }) }),
88
79
  visible.has("emailVerified") && /* @__PURE__ */ jsx("td", { className: list.nowrap, children: /* @__PURE__ */ jsx(StatusPill, { status: a.emailVerified ? "verified" : "unverified" }) }),
89
80
  visible.has("workspaces") && /* @__PURE__ */ jsx("td", { className: list.num, children: a.workspaceCount ?? EMPTY }),
90
- visible.has("lastLogin") && /* @__PURE__ */ jsx("td", { className: `${list.nowrap} ${list.muted}`, children: day(a.lastLoginAt) ?? "Never" }),
81
+ visible.has("lastLogin") && /* @__PURE__ */ jsx("td", { className: `${list.nowrap} ${list.muted}`, children: a.lastLoginAt ? /* @__PURE__ */ jsx(When, { iso: a.lastLoginAt, className: styles.when }) : "Never" }),
91
82
  visible.has("joined") && /* @__PURE__ */ jsx("td", { className: `${list.nowrap} ${list.muted}`, children: day(a.createdAt) }),
92
- visible.has("firstSale") && /* @__PURE__ */ jsx("td", { className: `${list.nowrap} ${list.muted}`, children: day(a.firstSaleAt) ?? "None yet" }),
83
+ visible.has("mfa") && /* @__PURE__ */ jsx("td", { className: list.nowrap, children: a.mfaEnabled ? "Yes" : "No" }),
93
84
  visible.has("activeSessions") && /* @__PURE__ */ jsx("td", { className: list.num, children: a.activeSessions ?? EMPTY }),
94
85
  visible.has("lastSeen") && /* @__PURE__ */ jsx("td", { className: `${list.nowrap} ${list.muted}`, children: a.lastSeenAt ? /* @__PURE__ */ jsx(When, { iso: a.lastSeenAt, className: styles.when }) : "Never" }),
95
86
  /* @__PURE__ */ jsx("td", { className: styles.menuCell, onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxs(OverflowMenu, { label: `Actions for ${label}`, children: [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/accounts/account-row.tsx"],"sourcesContent":["'use client';\n\nimport {\n OverflowItem,\n OverflowMenu,\n TableRow,\n useConfirm,\n useToast,\n} from '@withpotter/ui-kit';\nimport type { AccountRow as Account } from '@withpotter/lib/admin-api';\nimport { EntityAvatar } from '../_fx';\nimport { EMPTY } from '../_ui/list-card';\nimport { StatusPill } from '../audit/status-pill';\nimport { When } from '../audit/when';\nimport { kycLabel, STAGE_LABELS, stageStatus } from './account-stage';\nimport { useAccountActions } from './use-account-actions';\nimport { useModuleOn } from '../_shell/modules-context';\nimport list from '../_ui/list-card.module.css';\nimport styles from './rows.module.css';\n\nexport const PROVIDER_LABELS: Record<string, string> = {\n local: 'Email',\n google: 'Google',\n github: 'GitHub',\n};\n\nconst day = (iso?: string | null) =>\n iso ? new Date(iso).toLocaleDateString() : null;\n\n/** One account: who, how they sign in, status, activity and a ••• menu. */\nexport function AccountListRow({\n a,\n active,\n onOpen,\n visible,\n}: {\n a: Account;\n active: boolean;\n onOpen: () => void;\n /** Which optional columns are showing, in the order the header renders them. */\n visible: Set<string>;\n}) {\n const confirm = useConfirm();\n const { toast } = useToast();\n const { setStatus, verify, impersonate } = useAccountActions();\n const ownerView = useModuleOn('ownerView');\n const suspended = a.status === 'suspended';\n const label = a.name ?? a.email;\n\n const toggleStatus = async () => {\n const ok = await confirm({\n title: suspended ? `Restore ${label}?` : `Suspend ${label}?`,\n description: suspended\n ? 'They can sign in and use their workspaces again.'\n : 'They are signed out everywhere and cannot sign in until restored.',\n confirmLabel: suspended ? 'Restore' : 'Suspend',\n tone: suspended ? 'default' : 'danger',\n });\n if (!ok) return;\n setStatus.mutate(\n { id: a.id, status: suspended ? 'active' : 'suspended' },\n {\n onSuccess: () =>\n toast({ title: `${label} ${suspended ? 'restored' : 'suspended'}` }),\n onError: (e) =>\n toast({\n title: 'The status was not changed',\n description: e.message,\n variant: 'destructive',\n }),\n },\n );\n };\n\n return (\n <TableRow active={active} onClick={onOpen}>\n <td>\n <span className={styles.identity}>\n <EntityAvatar name={label} size={32} round />\n <span className={styles.identityText}>\n <button\n type=\"button\"\n className={styles.nameButton}\n title={label}\n aria-label={`Open ${label}`}\n onClick={(e) => {\n e.stopPropagation();\n onOpen();\n }}\n >\n {a.name ?? EMPTY}\n </button>\n <span className={styles.secondary} title={a.email}>\n {a.email}\n </span>\n </span>\n </span>\n </td>\n {visible.has('provider') && (\n <td className={`${list.nowrap} ${list.muted}`}>\n {PROVIDER_LABELS[a.authProvider] ?? (a.authProvider || EMPTY)}\n </td>\n )}\n <td className={list.nowrap}>\n <StatusPill status={a.status} />\n </td>\n {visible.has('kyc') && (\n <td className={list.nowrap}>\n {kycLabel(a.kycStatus) ?? <span className={list.muted}>{EMPTY}</span>}\n </td>\n )}\n {visible.has('stage') && (\n <td className={list.nowrap}>\n {a.stage ? (\n <StatusPill\n status={stageStatus(a.stage)}\n label={STAGE_LABELS[a.stage]}\n />\n ) : (\n <span className={list.muted}>{EMPTY}</span>\n )}\n </td>\n )}\n {visible.has('emailVerified') && (\n <td className={list.nowrap}>\n <StatusPill status={a.emailVerified ? 'verified' : 'unverified'} />\n </td>\n )}\n {visible.has('workspaces') && (\n <td className={list.num}>{a.workspaceCount ?? EMPTY}</td>\n )}\n {visible.has('lastLogin') && (\n <td className={`${list.nowrap} ${list.muted}`}>\n {day(a.lastLoginAt) ?? 'Never'}\n </td>\n )}\n {visible.has('joined') && (\n <td className={`${list.nowrap} ${list.muted}`}>{day(a.createdAt)}</td>\n )}\n {visible.has('firstSale') && (\n <td className={`${list.nowrap} ${list.muted}`}>\n {day(a.firstSaleAt) ?? 'None yet'}\n </td>\n )}\n {visible.has('activeSessions') && (\n <td className={list.num}>{a.activeSessions ?? EMPTY}</td>\n )}\n {visible.has('lastSeen') && (\n <td className={`${list.nowrap} ${list.muted}`}>\n {a.lastSeenAt ? (\n <When iso={a.lastSeenAt} className={styles.when} />\n ) : (\n 'Never'\n )}\n </td>\n )}\n <td className={styles.menuCell} onClick={(e) => e.stopPropagation()}>\n <OverflowMenu label={`Actions for ${label}`}>\n <OverflowItem onClick={onOpen}>View account</OverflowItem>\n {ownerView && (\n <OverflowItem onClick={() => impersonate.mutate(a.id)}>\n View as user\n </OverflowItem>\n )}\n {!a.emailVerified && (\n <OverflowItem\n onClick={() =>\n verify.mutate(a.id, {\n onSuccess: () => toast({ title: `${a.email} verified` }),\n })\n }\n >\n Verify email\n </OverflowItem>\n )}\n <OverflowItem\n onClick={() => void toggleStatus()}\n destructive={!suspended}\n >\n {suspended ? 'Restore' : 'Suspend'}\n </OverflowItem>\n </OverflowMenu>\n </td>\n </TableRow>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/accounts/account-row.tsx"],"sourcesContent":["'use client';\n\nimport {\n OverflowItem,\n OverflowMenu,\n TableRow,\n useConfirm,\n useToast,\n} from '@withpotter/ui-kit';\nimport type { AccountRow as Account } from '@withpotter/lib/admin-api';\nimport { EntityAvatar } from '../_fx';\nimport { EMPTY } from '../_ui/list-card';\nimport { StatusPill } from '../audit/status-pill';\nimport { When } from '../audit/when';\nimport { useAccountActions } from './use-account-actions';\nimport { useModuleOn } from '../_shell/modules-context';\nimport list from '../_ui/list-card.module.css';\nimport styles from './rows.module.css';\n\nexport const PROVIDER_LABELS: Record<string, string> = {\n local: 'Email',\n google: 'Google',\n github: 'GitHub',\n};\n\nconst day = (iso?: string | null) =>\n iso ? new Date(iso).toLocaleDateString() : null;\n\n/** One account: who, how they sign in, status, activity and a ••• menu. */\nexport function AccountListRow({\n a,\n active,\n onOpen,\n visible,\n}: {\n a: Account;\n active: boolean;\n onOpen: () => void;\n /** Which optional columns are showing, in the order the header renders them. */\n visible: Set<string>;\n}) {\n const confirm = useConfirm();\n const { toast } = useToast();\n const { setStatus, verify, impersonate } = useAccountActions();\n const ownerView = useModuleOn('ownerView');\n const suspended = a.status === 'suspended';\n const label = a.name ?? a.email;\n\n const toggleStatus = async () => {\n const ok = await confirm({\n title: suspended ? `Restore ${label}?` : `Suspend ${label}?`,\n description: suspended\n ? 'They can sign in and use their workspaces again.'\n : 'They are signed out everywhere and cannot sign in until restored.',\n confirmLabel: suspended ? 'Restore' : 'Suspend',\n tone: suspended ? 'default' : 'danger',\n });\n if (!ok) return;\n setStatus.mutate(\n { id: a.id, status: suspended ? 'active' : 'suspended' },\n {\n onSuccess: () =>\n toast({ title: `${label} ${suspended ? 'restored' : 'suspended'}` }),\n onError: (e) =>\n toast({\n title: 'The status was not changed',\n description: e.message,\n variant: 'destructive',\n }),\n },\n );\n };\n\n return (\n <TableRow active={active} onClick={onOpen}>\n <td>\n <span className={styles.identity}>\n <EntityAvatar name={label} size={32} round />\n <span className={styles.identityText}>\n <button\n type=\"button\"\n className={styles.nameButton}\n title={label}\n aria-label={`Open ${label}`}\n onClick={(e) => {\n e.stopPropagation();\n onOpen();\n }}\n >\n {a.name ?? EMPTY}\n </button>\n <span className={styles.secondary} title={a.email}>\n {a.email}\n </span>\n </span>\n </span>\n </td>\n {visible.has('provider') && (\n <td className={`${list.nowrap} ${list.muted}`}>\n {PROVIDER_LABELS[a.authProvider] ?? (a.authProvider || EMPTY)}\n </td>\n )}\n <td className={list.nowrap}>\n <StatusPill status={a.status} />\n </td>\n {visible.has('emailVerified') && (\n <td className={list.nowrap}>\n <StatusPill status={a.emailVerified ? 'verified' : 'unverified'} />\n </td>\n )}\n {visible.has('workspaces') && (\n <td className={list.num}>{a.workspaceCount ?? EMPTY}</td>\n )}\n {visible.has('lastLogin') && (\n <td className={`${list.nowrap} ${list.muted}`}>\n {a.lastLoginAt ? (\n <When iso={a.lastLoginAt} className={styles.when} />\n ) : (\n 'Never'\n )}\n </td>\n )}\n {visible.has('joined') && (\n <td className={`${list.nowrap} ${list.muted}`}>{day(a.createdAt)}</td>\n )}\n {visible.has('mfa') && (\n <td className={list.nowrap}>{a.mfaEnabled ? 'Yes' : 'No'}</td>\n )}\n {visible.has('activeSessions') && (\n <td className={list.num}>{a.activeSessions ?? EMPTY}</td>\n )}\n {visible.has('lastSeen') && (\n <td className={`${list.nowrap} ${list.muted}`}>\n {a.lastSeenAt ? (\n <When iso={a.lastSeenAt} className={styles.when} />\n ) : (\n 'Never'\n )}\n </td>\n )}\n <td className={styles.menuCell} onClick={(e) => e.stopPropagation()}>\n <OverflowMenu label={`Actions for ${label}`}>\n <OverflowItem onClick={onOpen}>View account</OverflowItem>\n {ownerView && (\n <OverflowItem onClick={() => impersonate.mutate(a.id)}>\n View as user\n </OverflowItem>\n )}\n {!a.emailVerified && (\n <OverflowItem\n onClick={() =>\n verify.mutate(a.id, {\n onSuccess: () => toast({ title: `${a.email} verified` }),\n })\n }\n >\n Verify email\n </OverflowItem>\n )}\n <OverflowItem\n onClick={() => void toggleStatus()}\n destructive={!suspended}\n >\n {suspended ? 'Restore' : 'Suspend'}\n </OverflowItem>\n </OverflowMenu>\n </td>\n </TableRow>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1,37 +1,35 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { relativeTime, seenWithin } from "./relative-time";
3
3
  import styles from "./account-panel.module.css";
4
- const SEEN_GAP_MS = 5 * 60 * 1e3;
5
4
  function AccountVitals({
6
5
  account,
7
6
  workspaces
8
7
  }) {
9
- const online = Boolean(seenWithin(account.lastSeenAt, 15 * 60));
10
- const laterSeen = account.lastSeenAt && (!account.lastLoginAt || new Date(account.lastSeenAt).getTime() - new Date(account.lastLoginAt).getTime() > SEEN_GAP_MS);
8
+ const online = seenWithin(account.lastSeenAt, 15 * 60);
11
9
  const tiles = [
10
+ {
11
+ label: "Last seen",
12
+ value: relativeTime(account.lastSeenAt ?? account.lastLoginAt),
13
+ hint: account.activeSessions !== void 0 ? `${account.activeSessions} signed-in device${account.activeSessions === 1 ? "" : "s"}` : void 0,
14
+ live: Boolean(online)
15
+ },
12
16
  {
13
17
  label: "Last login",
14
18
  value: relativeTime(account.lastLoginAt),
15
- hint: account.lastLoginAt ? new Date(account.lastLoginAt).toLocaleDateString() : void 0,
16
- live: online && !laterSeen
19
+ hint: account.lastLoginAt ? new Date(account.lastLoginAt).toLocaleDateString() : void 0
17
20
  },
18
- ...laterSeen ? [
19
- {
20
- label: "Last seen",
21
- value: relativeTime(account.lastSeenAt),
22
- live: online
23
- }
24
- ] : [],
25
21
  {
26
22
  label: "Workspaces",
27
- value: workspaces === void 0 ? "\u2026" : String(workspaces)
23
+ value: workspaces === void 0 ? "\u2026" : String(workspaces),
24
+ hint: "Active memberships"
28
25
  },
29
26
  {
30
27
  label: "Joined",
31
- value: account.createdAt ? new Date(account.createdAt).toLocaleDateString() : "\u2014"
28
+ value: account.createdAt ? relativeTime(account.createdAt) : "\u2014",
29
+ hint: account.createdAt ? new Date(account.createdAt).toLocaleDateString() : void 0
32
30
  }
33
31
  ];
34
- return /* @__PURE__ */ jsx("div", { className: styles.vitals, "data-count": tiles.length, children: tiles.map((tile) => /* @__PURE__ */ jsxs("div", { className: styles.vital, children: [
32
+ return /* @__PURE__ */ jsx("div", { className: styles.vitals, children: tiles.map((tile) => /* @__PURE__ */ jsxs("div", { className: styles.vital, children: [
35
33
  /* @__PURE__ */ jsxs("span", { className: styles.vitalLabel, children: [
36
34
  tile.live && /* @__PURE__ */ jsx("span", { className: styles.live, "aria-label": "Online" }),
37
35
  tile.label
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/accounts/account-vitals.tsx"],"sourcesContent":["import type { AccountRow } from '@withpotter/lib/admin-api';\nimport { relativeTime, seenWithin } from './relative-time';\nimport styles from './account-panel.module.css';\n\n/** Last seen only earns a tile when it is meaningfully after the last login. */\nconst SEEN_GAP_MS = 5 * 60 * 1000;\n\ntype Tile = { label: string; value: string; hint?: string; live?: boolean };\n\n/** Three numbers (a fourth when last seen differs) that say how deeply this person is here. */\nexport function AccountVitals({\n account,\n workspaces,\n}: {\n account: Pick<AccountRow, 'id'> & Partial<AccountRow>;\n workspaces: number | undefined;\n}) {\n const online = Boolean(seenWithin(account.lastSeenAt, 15 * 60));\n const laterSeen =\n account.lastSeenAt &&\n (!account.lastLoginAt ||\n new Date(account.lastSeenAt).getTime() -\n new Date(account.lastLoginAt).getTime() >\n SEEN_GAP_MS);\n const tiles: Tile[] = [\n {\n label: 'Last login',\n value: relativeTime(account.lastLoginAt),\n hint: account.lastLoginAt\n ? new Date(account.lastLoginAt).toLocaleDateString()\n : undefined,\n live: online && !laterSeen,\n },\n ...(laterSeen\n ? [\n {\n label: 'Last seen',\n value: relativeTime(account.lastSeenAt),\n live: online,\n },\n ]\n : []),\n {\n label: 'Workspaces',\n value: workspaces === undefined ? '…' : String(workspaces),\n },\n {\n label: 'Joined',\n value: account.createdAt\n ? new Date(account.createdAt).toLocaleDateString()\n : '—',\n },\n ];\n\n return (\n <div className={styles.vitals} data-count={tiles.length}>\n {tiles.map((tile: Tile) => (\n <div key={tile.label} className={styles.vital}>\n <span className={styles.vitalLabel}>\n {tile.live && <span className={styles.live} aria-label=\"Online\" />}\n {tile.label}\n </span>\n <span className={styles.vitalValue}>{tile.value}</span>\n {tile.hint && <span className={styles.vitalHint}>{tile.hint}</span>}\n </div>\n ))}\n </div>\n );\n}\n"],"mappings":"AA0DU,SACgB,KADhB;AAzDV,SAAS,cAAc,kBAAkB;AACzC,OAAO,YAAY;AAGnB,MAAM,cAAc,IAAI,KAAK;AAKtB,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AACF,GAGG;AACD,QAAM,SAAS,QAAQ,WAAW,QAAQ,YAAY,KAAK,EAAE,CAAC;AAC9D,QAAM,YACJ,QAAQ,eACP,CAAC,QAAQ,eACR,IAAI,KAAK,QAAQ,UAAU,EAAE,QAAQ,IACnC,IAAI,KAAK,QAAQ,WAAW,EAAE,QAAQ,IACtC;AACN,QAAM,QAAgB;AAAA,IACpB;AAAA,MACE,OAAO;AAAA,MACP,OAAO,aAAa,QAAQ,WAAW;AAAA,MACvC,MAAM,QAAQ,cACV,IAAI,KAAK,QAAQ,WAAW,EAAE,mBAAmB,IACjD;AAAA,MACJ,MAAM,UAAU,CAAC;AAAA,IACnB;AAAA,IACA,GAAI,YACA;AAAA,MACE;AAAA,QACE,OAAO;AAAA,QACP,OAAO,aAAa,QAAQ,UAAU;AAAA,QACtC,MAAM;AAAA,MACR;AAAA,IACF,IACA,CAAC;AAAA,IACL;AAAA,MACE,OAAO;AAAA,MACP,OAAO,eAAe,SAAY,WAAM,OAAO,UAAU;AAAA,IAC3D;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,OAAO,QAAQ,YACX,IAAI,KAAK,QAAQ,SAAS,EAAE,mBAAmB,IAC/C;AAAA,IACN;AAAA,EACF;AAEA,SACE,oBAAC,SAAI,WAAW,OAAO,QAAQ,cAAY,MAAM,QAC9C,gBAAM,IAAI,CAAC,SACV,qBAAC,SAAqB,WAAW,OAAO,OACtC;AAAA,yBAAC,UAAK,WAAW,OAAO,YACrB;AAAA,WAAK,QAAQ,oBAAC,UAAK,WAAW,OAAO,MAAM,cAAW,UAAS;AAAA,MAC/D,KAAK;AAAA,OACR;AAAA,IACA,oBAAC,UAAK,WAAW,OAAO,YAAa,eAAK,OAAM;AAAA,IAC/C,KAAK,QAAQ,oBAAC,UAAK,WAAW,OAAO,WAAY,eAAK,MAAK;AAAA,OANpD,KAAK,KAOf,CACD,GACH;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/accounts/account-vitals.tsx"],"sourcesContent":["import type { AccountRow } from '@withpotter/lib/admin-api';\nimport { relativeTime, seenWithin } from './relative-time';\nimport styles from './account-panel.module.css';\n\n/** Four numbers that say whether this person is here and how deeply. */\nexport function AccountVitals({\n account,\n workspaces,\n}: {\n account: Pick<AccountRow, 'id'> & Partial<AccountRow>;\n workspaces: number | undefined;\n}) {\n const online = seenWithin(account.lastSeenAt, 15 * 60);\n const tiles = [\n {\n label: 'Last seen',\n value: relativeTime(account.lastSeenAt ?? account.lastLoginAt),\n hint:\n account.activeSessions !== undefined\n ? `${account.activeSessions} signed-in device${account.activeSessions === 1 ? '' : 's'}`\n : undefined,\n live: Boolean(online),\n },\n {\n label: 'Last login',\n value: relativeTime(account.lastLoginAt),\n hint: account.lastLoginAt\n ? new Date(account.lastLoginAt).toLocaleDateString()\n : undefined,\n },\n {\n label: 'Workspaces',\n value: workspaces === undefined ? '…' : String(workspaces),\n hint: 'Active memberships',\n },\n {\n label: 'Joined',\n value: account.createdAt ? relativeTime(account.createdAt) : '—',\n hint: account.createdAt\n ? new Date(account.createdAt).toLocaleDateString()\n : undefined,\n },\n ];\n\n return (\n <div className={styles.vitals}>\n {tiles.map((tile) => (\n <div key={tile.label} className={styles.vital}>\n <span className={styles.vitalLabel}>\n {tile.live && <span className={styles.live} aria-label=\"Online\" />}\n {tile.label}\n </span>\n <span className={styles.vitalValue}>{tile.value}</span>\n {tile.hint && <span className={styles.vitalHint}>{tile.hint}</span>}\n </div>\n ))}\n </div>\n );\n}\n"],"mappings":"AAgDU,SACgB,KADhB;AA/CV,SAAS,cAAc,kBAAkB;AACzC,OAAO,YAAY;AAGZ,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AACF,GAGG;AACD,QAAM,SAAS,WAAW,QAAQ,YAAY,KAAK,EAAE;AACrD,QAAM,QAAQ;AAAA,IACZ;AAAA,MACE,OAAO;AAAA,MACP,OAAO,aAAa,QAAQ,cAAc,QAAQ,WAAW;AAAA,MAC7D,MACE,QAAQ,mBAAmB,SACvB,GAAG,QAAQ,cAAc,oBAAoB,QAAQ,mBAAmB,IAAI,KAAK,GAAG,KACpF;AAAA,MACN,MAAM,QAAQ,MAAM;AAAA,IACtB;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,OAAO,aAAa,QAAQ,WAAW;AAAA,MACvC,MAAM,QAAQ,cACV,IAAI,KAAK,QAAQ,WAAW,EAAE,mBAAmB,IACjD;AAAA,IACN;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,OAAO,eAAe,SAAY,WAAM,OAAO,UAAU;AAAA,MACzD,MAAM;AAAA,IACR;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,OAAO,QAAQ,YAAY,aAAa,QAAQ,SAAS,IAAI;AAAA,MAC7D,MAAM,QAAQ,YACV,IAAI,KAAK,QAAQ,SAAS,EAAE,mBAAmB,IAC/C;AAAA,IACN;AAAA,EACF;AAEA,SACE,oBAAC,SAAI,WAAW,OAAO,QACpB,gBAAM,IAAI,CAAC,SACV,qBAAC,SAAqB,WAAW,OAAO,OACtC;AAAA,yBAAC,UAAK,WAAW,OAAO,YACrB;AAAA,WAAK,QAAQ,oBAAC,UAAK,WAAW,OAAO,MAAM,cAAW,UAAS;AAAA,MAC/D,KAAK;AAAA,OACR;AAAA,IACA,oBAAC,UAAK,WAAW,OAAO,YAAa,eAAK,OAAM;AAAA,IAC/C,KAAK,QAAQ,oBAAC,UAAK,WAAW,OAAO,WAAY,eAAK,MAAK;AAAA,OANpD,KAAK,KAOf,CACD,GACH;AAEJ;","names":[]}