@withpotter/pages-admin 0.13.0-next.124 → 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 -308
  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
@@ -2,11 +2,7 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useMemo } from "react";
4
4
  import { ADMIN_NAME } from "../_shell/admin-brand";
5
- import {
6
- useMerchantPayments,
7
- usePlatformCockpit,
8
- usePlatformRevenue
9
- } from "../hooks";
5
+ import { usePlatformCockpit, usePlatformRevenue } from "../hooks";
10
6
  import { LoadFailed } from "../audit/load-failed";
11
7
  import {
12
8
  AdmLoading,
@@ -23,16 +19,10 @@ import {
23
19
  usePersistedDateRange,
24
20
  useTabParam
25
21
  } from "../_ui";
26
- import { MerchantPaymentsView } from "./merchant-payments-view";
27
- import { usePlanList } from "../pricing/use-plans";
28
22
  import { RevenueOverview } from "./revenue-overview";
29
23
  import { RevenueRetention } from "./revenue-retention";
30
- import { SubscriptionsView } from "./subscriptions-view";
31
- import { SummaryDownload } from "./summary-download";
32
24
  const TABS = [
33
- { id: "overview", label: "Money Olive earns" },
34
- { id: "merchants", label: "Merchant payments" },
35
- { id: "subscriptions", label: "Subscriptions" },
25
+ { id: "overview", label: "Overview" },
36
26
  { id: "retention", label: "Retention" }
37
27
  ];
38
28
  const KEYS = TABS.map((t) => t.id);
@@ -54,19 +44,13 @@ function RevenueWorkspace() {
54
44
  startDate: range.startDate,
55
45
  endDate: range.endDate
56
46
  });
57
- const plans = usePlanList();
58
47
  const cockpit = usePlatformCockpit({
59
48
  startDate: range.startDate,
60
49
  endDate: range.endDate
61
50
  });
62
- const ledger = useMerchantPayments({
63
- startDate: range.startDate,
64
- endDate: range.endDate,
65
- limit: 1
66
- });
67
51
  const currencies = useMemo(() => data?.currencies ?? [], [data]);
68
52
  const focus = focusCurrency(currencies, currency);
69
- const recurringCurrency = data?.currency || "NGN";
53
+ const recurringCurrency = data?.currency || "USD";
70
54
  const money = useMemo(() => moneyIn(recurringCurrency), [recurringCurrency]);
71
55
  const money2 = useMemo(
72
56
  () => moneyIn(recurringCurrency, 2),
@@ -85,24 +69,6 @@ function RevenueWorkspace() {
85
69
  );
86
70
  } else if (isLoading || !data) {
87
71
  body = /* @__PURE__ */ jsx(AdmLoading, { label: "Loading revenue metrics\u2026" });
88
- } else if (tab === "merchants") {
89
- body = /* @__PURE__ */ jsx(
90
- MerchantPaymentsView,
91
- {
92
- startDate: range.startDate,
93
- endDate: range.endDate,
94
- currency
95
- }
96
- );
97
- } else if (tab === "subscriptions") {
98
- body = /* @__PURE__ */ jsx(
99
- SubscriptionsView,
100
- {
101
- startDate: range.startDate,
102
- endDate: range.endDate,
103
- revenue: data
104
- }
105
- );
106
72
  } else if (tab === "retention") {
107
73
  body = /* @__PURE__ */ jsx(
108
74
  RevenueRetention,
@@ -117,17 +83,15 @@ function RevenueWorkspace() {
117
83
  body = /* @__PURE__ */ jsx(
118
84
  RevenueOverview,
119
85
  {
120
- startDate: range.startDate,
121
- endDate: range.endDate,
122
86
  data,
123
87
  health: cockpit.data?.health,
124
88
  money,
125
89
  money2,
126
90
  currency,
127
91
  focus,
92
+ period,
128
93
  span,
129
- recurringCaption: `${period} \xB7 ${recurringCurrency}`,
130
- plans: plans.data
94
+ recurringCaption: `${period} \xB7 ${recurringCurrency}`
131
95
  }
132
96
  );
133
97
  }
@@ -136,19 +100,8 @@ function RevenueWorkspace() {
136
100
  PageHeader,
137
101
  {
138
102
  title: "Revenue",
139
- description: `How money flows through ${ADMIN_NAME}: what it earns, where it comes from, what it costs and what is left.`,
103
+ description: `${ADMIN_NAME}'s subscription income each month and year, and the merchant sales (GMV) it is based on.`,
140
104
  actions: /* @__PURE__ */ jsxs("div", { className: "adm-toolbar", children: [
141
- tab === "overview" ? /* @__PURE__ */ jsx(
142
- SummaryDownload,
143
- {
144
- ledger: ledger.data,
145
- revenue: data,
146
- currency: focus ?? "NGN",
147
- startDate: range.startDate,
148
- endDate: range.endDate,
149
- period
150
- }
151
- ) : null,
152
105
  /* @__PURE__ */ jsx(
153
106
  CurrencyFilter,
154
107
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/revenue/revenue-workspace.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo } from 'react';\nimport { ADMIN_NAME } from '../_shell/admin-brand';\nimport {\n useMerchantPayments,\n usePlatformCockpit,\n usePlatformRevenue,\n} from '../hooks';\nimport { LoadFailed } from '../audit/load-failed';\nimport {\n AdmLoading,\n CurrencyFilter,\n PageHeader,\n PageTabs,\n PeriodSelector,\n customRange,\n focusCurrency,\n moneyIn,\n periodPhrase,\n rangeFromKey,\n useCurrencyParam,\n usePersistedDateRange,\n useTabParam,\n} from '../_ui';\nimport { MerchantPaymentsView } from './merchant-payments-view';\nimport { usePlanList } from '../pricing/use-plans';\nimport { RevenueOverview } from './revenue-overview';\nimport { RevenueRetention } from './revenue-retention';\nimport { SubscriptionsView } from './subscriptions-view';\nimport { SummaryDownload } from './summary-download';\n\nconst TABS = [\n { id: 'overview', label: 'Money Olive earns' },\n { id: 'merchants', label: 'Merchant payments' },\n { id: 'subscriptions', label: 'Subscriptions' },\n { id: 'retention', label: 'Retention' },\n] as const;\n\nconst KEYS = TABS.map((t) => t.id);\n\n/**\n * Revenue: the whole picture on Overview, what customers paid merchants on\n * Merchant payments, what merchants pay Olive on Subscriptions, and revenue\n * quality and cohorts on Retention.\n */\nexport function RevenueWorkspace() {\n const [tab, setTab] = useTabParam(KEYS);\n const {\n rangeKey,\n setRangeKey,\n customStart,\n customEnd,\n onCustomChange,\n } = usePersistedDateRange();\n // Merchant GMV comes back once per currency; `?currency=` narrows it.\n const [currency, setCurrency] = useCurrencyParam();\n\n const range = useMemo(\n () =>\n rangeKey === 'custom'\n ? customRange(customStart, customEnd)\n : rangeFromKey(rangeKey),\n [rangeKey, customStart, customEnd],\n );\n\n const { data, isLoading, isError, refetch } = usePlatformRevenue({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n // Revenue health (MRR movement, logo churn, concentration) is computed as\n // part of the cockpit, not the revenue endpoint — fetched here too rather\n // than duplicating that aggregation.\n const plans = usePlanList();\n const cockpit = usePlatformCockpit({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n\n // The same totals the Merchant payments tab reads, one row only, for the summary download.\n const ledger = useMerchantPayments({\n startDate: range.startDate,\n endDate: range.endDate,\n limit: 1,\n });\n\n const currencies = useMemo(() => data?.currencies ?? [], [data]);\n const focus = focusCurrency(currencies, currency);\n // Recurring revenue is the platform's own billing, in the currency the API\n // names for it (`currency`), shown as it comes, never converted.\n const recurringCurrency = data?.currency || 'NGN';\n const money = useMemo(() => moneyIn(recurringCurrency), [recurringCurrency]);\n const money2 = useMemo(\n () => moneyIn(recurringCurrency, 2),\n [recurringCurrency],\n );\n\n const span = periodPhrase(range);\n const period = `${span[0].toUpperCase()}${span.slice(1)}`;\n\n let body;\n if (isError) {\n body = (\n <LoadFailed\n message=\"Revenue for this period could not be loaded.\"\n onRetry={() => void refetch()}\n />\n );\n } else if (isLoading || !data) {\n body = <AdmLoading label=\"Loading revenue metrics…\" />;\n } else if (tab === 'merchants') {\n body = (\n <MerchantPaymentsView\n startDate={range.startDate}\n endDate={range.endDate}\n currency={currency}\n />\n );\n } else if (tab === 'subscriptions') {\n body = (\n <SubscriptionsView\n startDate={range.startDate}\n endDate={range.endDate}\n revenue={data}\n />\n );\n } else if (tab === 'retention') {\n body = (\n <RevenueRetention\n quality={data.quality}\n money={money}\n currency={currency}\n caption={currency ? `${period} · ${currency}` : period}\n />\n );\n } else {\n body = (\n <RevenueOverview\n startDate={range.startDate}\n endDate={range.endDate}\n data={data}\n health={cockpit.data?.health}\n money={money}\n money2={money2}\n currency={currency}\n focus={focus}\n span={span}\n recurringCaption={`${period} · ${recurringCurrency}`}\n plans={plans.data}\n />\n );\n }\n\n return (\n <div>\n <PageHeader\n title=\"Revenue\"\n description={`How money flows through ${ADMIN_NAME}: what it earns, where it comes from, what it costs and what is left.`}\n actions={\n <div className=\"adm-toolbar\">\n {tab === 'overview' ? (\n <SummaryDownload\n ledger={ledger.data}\n revenue={data}\n currency={focus ?? 'NGN'}\n startDate={range.startDate}\n endDate={range.endDate}\n period={period}\n />\n ) : null}\n <CurrencyFilter\n value={currency}\n onChange={setCurrency}\n currencies={currencies}\n />\n <PeriodSelector\n value={rangeKey}\n onChange={setRangeKey}\n customStart={customStart}\n customEnd={customEnd}\n onCustomChange={onCustomChange}\n />\n </div>\n }\n />\n <PageTabs tabs={TABS} active={tab} onChange={setTab} />\n {body}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/revenue/revenue-workspace.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo } from 'react';\nimport { ADMIN_NAME } from '../_shell/admin-brand';\nimport { usePlatformCockpit, usePlatformRevenue } from '../hooks';\nimport { LoadFailed } from '../audit/load-failed';\nimport {\n AdmLoading,\n CurrencyFilter,\n PageHeader,\n PageTabs,\n PeriodSelector,\n customRange,\n focusCurrency,\n moneyIn,\n periodPhrase,\n rangeFromKey,\n useCurrencyParam,\n usePersistedDateRange,\n useTabParam,\n} from '../_ui';\nimport { RevenueOverview } from './revenue-overview';\nimport { RevenueRetention } from './revenue-retention';\n\nconst TABS = [\n { id: 'overview', label: 'Overview' },\n { id: 'retention', label: 'Retention' },\n] as const;\n\nconst KEYS = TABS.map((t) => t.id);\n\n/** Revenue: headline money on Overview, revenue quality and cohorts on Retention. */\nexport function RevenueWorkspace() {\n const [tab, setTab] = useTabParam(KEYS);\n const {\n rangeKey,\n setRangeKey,\n customStart,\n customEnd,\n onCustomChange,\n } = usePersistedDateRange();\n // Merchant GMV comes back once per currency; `?currency=` narrows it.\n const [currency, setCurrency] = useCurrencyParam();\n\n const range = useMemo(\n () =>\n rangeKey === 'custom'\n ? customRange(customStart, customEnd)\n : rangeFromKey(rangeKey),\n [rangeKey, customStart, customEnd],\n );\n\n const { data, isLoading, isError, refetch } = usePlatformRevenue({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n // Revenue health (MRR movement, logo churn, concentration) is computed as\n // part of the cockpit, not the revenue endpoint — fetched here too rather\n // than duplicating that aggregation.\n const cockpit = usePlatformCockpit({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n\n const currencies = useMemo(() => data?.currencies ?? [], [data]);\n const focus = focusCurrency(currencies, currency);\n // Recurring revenue is the platform's own billing, in the currency the API\n // names for it (`currency`), shown as it comes, never converted.\n const recurringCurrency = data?.currency || 'USD';\n const money = useMemo(() => moneyIn(recurringCurrency), [recurringCurrency]);\n const money2 = useMemo(\n () => moneyIn(recurringCurrency, 2),\n [recurringCurrency],\n );\n\n const span = periodPhrase(range);\n const period = `${span[0].toUpperCase()}${span.slice(1)}`;\n\n let body;\n if (isError) {\n body = (\n <LoadFailed\n message=\"Revenue for this period could not be loaded.\"\n onRetry={() => void refetch()}\n />\n );\n } else if (isLoading || !data) {\n body = <AdmLoading label=\"Loading revenue metrics…\" />;\n } else if (tab === 'retention') {\n body = (\n <RevenueRetention\n quality={data.quality}\n money={money}\n currency={currency}\n caption={currency ? `${period} · ${currency}` : period}\n />\n );\n } else {\n body = (\n <RevenueOverview\n data={data}\n health={cockpit.data?.health}\n money={money}\n money2={money2}\n currency={currency}\n focus={focus}\n period={period}\n span={span}\n recurringCaption={`${period} · ${recurringCurrency}`}\n />\n );\n }\n\n return (\n <div>\n <PageHeader\n title=\"Revenue\"\n description={`${ADMIN_NAME}'s subscription income each month and year, and the merchant sales (GMV) it is based on.`}\n actions={\n <div className=\"adm-toolbar\">\n <CurrencyFilter\n value={currency}\n onChange={setCurrency}\n currencies={currencies}\n />\n <PeriodSelector\n value={rangeKey}\n onChange={setRangeKey}\n customStart={customStart}\n customEnd={customEnd}\n onCustomChange={onCustomChange}\n />\n </div>\n }\n />\n <PageTabs tabs={TABS} active={tab} onChange={setTab} />\n {body}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -8,49 +8,49 @@ function SubscriptionsPanel({
8
8
  }) {
9
9
  const rows = [
10
10
  {
11
- label: "Payment missed",
11
+ label: "Past due",
12
12
  status: "past_due",
13
- note: "Their last payment failed, so they may leave",
13
+ note: "Payment failed, at risk of churning",
14
14
  value: integer.format(recurring.pastDueSubscriptions),
15
15
  tone: recurring.pastDueSubscriptions > 0 ? "danger" : "default"
16
16
  },
17
17
  {
18
- label: "Set to end",
18
+ label: "Non-renewing",
19
19
  status: "non_renewing",
20
- note: "Will not renew when the paid period ends",
20
+ note: "Set to cancel at period end",
21
21
  value: integer.format(recurring.nonRenewingSubscriptions),
22
22
  tone: recurring.nonRenewingSubscriptions > 0 ? "danger" : "default"
23
23
  },
24
24
  {
25
25
  label: "Active",
26
26
  status: "active",
27
- note: "Paying for their plan",
27
+ note: "Paying on a live billing cycle",
28
28
  value: integer.format(recurring.activeSubscriptions)
29
29
  },
30
30
  {
31
- label: "On a trial",
31
+ label: "Trialing",
32
32
  status: "trialing",
33
- note: "Trying a plan, not yet paying",
33
+ note: "In trial, not yet billed",
34
34
  value: integer.format(recurring.trialingSubscriptions)
35
35
  },
36
36
  {
37
- label: "Joined this period",
38
- note: `Started paying in ${span}`,
37
+ label: "New this period",
38
+ note: `Started in ${span}`,
39
39
  value: integer.format(recurring.newInRange)
40
40
  },
41
41
  {
42
- label: "Left this period",
43
- note: `Stopped paying in ${span}`,
42
+ label: "Churned this period",
43
+ note: `Cancelled in ${span}`,
44
44
  value: integer.format(recurring.churnedInRange),
45
45
  tone: recurring.churnedInRange > 0 ? "danger" : "default"
46
46
  },
47
47
  {
48
- label: "Cancelled in total",
49
- note: "Since the start",
48
+ label: "Cancelled",
49
+ note: "Lifetime total",
50
50
  value: integer.format(recurring.cancelledSubscriptions)
51
51
  }
52
52
  ];
53
- return /* @__PURE__ */ jsx(Panel, { title: "Plan subscriptions", caption: "Right now", height: "var(--adm-panel-h)", children: /* @__PURE__ */ jsx(FigureList, { rows }) });
53
+ return /* @__PURE__ */ jsx(Panel, { title: "Subscriptions", caption: "Today", height: "var(--adm-panel-h)", children: /* @__PURE__ */ jsx(FigureList, { rows }) });
54
54
  }
55
55
  export {
56
56
  SubscriptionsPanel
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/revenue/subscriptions-panel.tsx"],"sourcesContent":["import type { PlatformRevenue } from '@withpotter/lib/admin-api';\nimport { Panel } from '../_ui';\nimport { FigureList, type Figure } from './figure-list';\n\nconst integer = new Intl.NumberFormat('en-US');\n\n/** Where the subscription base stands, and what started in the window. */\nexport function SubscriptionsPanel({\n recurring,\n span,\n}: {\n recurring: PlatformRevenue['recurring'];\n span: string;\n}) {\n const rows: Figure[] = [\n {\n label: 'Payment missed',\n status: 'past_due',\n note: 'Their last payment failed, so they may leave',\n value: integer.format(recurring.pastDueSubscriptions),\n tone: recurring.pastDueSubscriptions > 0 ? 'danger' : 'default',\n },\n {\n label: 'Set to end',\n status: 'non_renewing',\n note: 'Will not renew when the paid period ends',\n value: integer.format(recurring.nonRenewingSubscriptions),\n tone: recurring.nonRenewingSubscriptions > 0 ? 'danger' : 'default',\n },\n {\n label: 'Active',\n status: 'active',\n note: 'Paying for their plan',\n value: integer.format(recurring.activeSubscriptions),\n },\n {\n label: 'On a trial',\n status: 'trialing',\n note: 'Trying a plan, not yet paying',\n value: integer.format(recurring.trialingSubscriptions),\n },\n {\n label: 'Joined this period',\n note: `Started paying in ${span}`,\n value: integer.format(recurring.newInRange),\n },\n {\n label: 'Left this period',\n note: `Stopped paying in ${span}`,\n value: integer.format(recurring.churnedInRange),\n tone: recurring.churnedInRange > 0 ? 'danger' : 'default',\n },\n {\n label: 'Cancelled in total',\n note: 'Since the start',\n value: integer.format(recurring.cancelledSubscriptions),\n },\n ];\n\n return (\n <Panel title=\"Plan subscriptions\" caption=\"Right now\" height=\"var(--adm-panel-h)\">\n <FigureList rows={rows} />\n </Panel>\n );\n}\n"],"mappings":"AA6DM;AA5DN,SAAS,aAAa;AACtB,SAAS,kBAA+B;AAExC,MAAM,UAAU,IAAI,KAAK,aAAa,OAAO;AAGtC,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AACF,GAGG;AACD,QAAM,OAAiB;AAAA,IACrB;AAAA,MACE,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,OAAO,QAAQ,OAAO,UAAU,oBAAoB;AAAA,MACpD,MAAM,UAAU,uBAAuB,IAAI,WAAW;AAAA,IACxD;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,OAAO,QAAQ,OAAO,UAAU,wBAAwB;AAAA,MACxD,MAAM,UAAU,2BAA2B,IAAI,WAAW;AAAA,IAC5D;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,OAAO,QAAQ,OAAO,UAAU,mBAAmB;AAAA,IACrD;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,OAAO,QAAQ,OAAO,UAAU,qBAAqB;AAAA,IACvD;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM,qBAAqB,IAAI;AAAA,MAC/B,OAAO,QAAQ,OAAO,UAAU,UAAU;AAAA,IAC5C;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM,qBAAqB,IAAI;AAAA,MAC/B,OAAO,QAAQ,OAAO,UAAU,cAAc;AAAA,MAC9C,MAAM,UAAU,iBAAiB,IAAI,WAAW;AAAA,IAClD;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO,QAAQ,OAAO,UAAU,sBAAsB;AAAA,IACxD;AAAA,EACF;AAEA,SACE,oBAAC,SAAM,OAAM,sBAAqB,SAAQ,aAAY,QAAO,sBAC3D,8BAAC,cAAW,MAAY,GAC1B;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/revenue/subscriptions-panel.tsx"],"sourcesContent":["import type { PlatformRevenue } from '@withpotter/lib/admin-api';\nimport { Panel } from '../_ui';\nimport { FigureList, type Figure } from './figure-list';\n\nconst integer = new Intl.NumberFormat('en-US');\n\n/** Where the subscription base stands, and what started in the window. */\nexport function SubscriptionsPanel({\n recurring,\n span,\n}: {\n recurring: PlatformRevenue['recurring'];\n span: string;\n}) {\n const rows: Figure[] = [\n {\n label: 'Past due',\n status: 'past_due',\n note: 'Payment failed, at risk of churning',\n value: integer.format(recurring.pastDueSubscriptions),\n tone: recurring.pastDueSubscriptions > 0 ? 'danger' : 'default',\n },\n {\n label: 'Non-renewing',\n status: 'non_renewing',\n note: 'Set to cancel at period end',\n value: integer.format(recurring.nonRenewingSubscriptions),\n tone: recurring.nonRenewingSubscriptions > 0 ? 'danger' : 'default',\n },\n {\n label: 'Active',\n status: 'active',\n note: 'Paying on a live billing cycle',\n value: integer.format(recurring.activeSubscriptions),\n },\n {\n label: 'Trialing',\n status: 'trialing',\n note: 'In trial, not yet billed',\n value: integer.format(recurring.trialingSubscriptions),\n },\n {\n label: 'New this period',\n note: `Started in ${span}`,\n value: integer.format(recurring.newInRange),\n },\n {\n label: 'Churned this period',\n note: `Cancelled in ${span}`,\n value: integer.format(recurring.churnedInRange),\n tone: recurring.churnedInRange > 0 ? 'danger' : 'default',\n },\n {\n label: 'Cancelled',\n note: 'Lifetime total',\n value: integer.format(recurring.cancelledSubscriptions),\n },\n ];\n\n return (\n <Panel title=\"Subscriptions\" caption=\"Today\" height=\"var(--adm-panel-h)\">\n <FigureList rows={rows} />\n </Panel>\n );\n}\n"],"mappings":"AA6DM;AA5DN,SAAS,aAAa;AACtB,SAAS,kBAA+B;AAExC,MAAM,UAAU,IAAI,KAAK,aAAa,OAAO;AAGtC,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AACF,GAGG;AACD,QAAM,OAAiB;AAAA,IACrB;AAAA,MACE,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,OAAO,QAAQ,OAAO,UAAU,oBAAoB;AAAA,MACpD,MAAM,UAAU,uBAAuB,IAAI,WAAW;AAAA,IACxD;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,OAAO,QAAQ,OAAO,UAAU,wBAAwB;AAAA,MACxD,MAAM,UAAU,2BAA2B,IAAI,WAAW;AAAA,IAC5D;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,OAAO,QAAQ,OAAO,UAAU,mBAAmB;AAAA,IACrD;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,OAAO,QAAQ,OAAO,UAAU,qBAAqB;AAAA,IACvD;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM,cAAc,IAAI;AAAA,MACxB,OAAO,QAAQ,OAAO,UAAU,UAAU;AAAA,IAC5C;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM,gBAAgB,IAAI;AAAA,MAC1B,OAAO,QAAQ,OAAO,UAAU,cAAc;AAAA,MAC9C,MAAM,UAAU,iBAAiB,IAAI,WAAW;AAAA,IAClD;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO,QAAQ,OAAO,UAAU,sBAAsB;AAAA,IACxD;AAAA,EACF;AAEA,SACE,oBAAC,SAAM,OAAM,iBAAgB,SAAQ,SAAQ,QAAO,sBAClD,8BAAC,cAAW,MAAY,GAC1B;AAEJ;","names":[]}
@@ -17,7 +17,7 @@ function TopTenantsPanel({
17
17
  return /* @__PURE__ */ jsx(
18
18
  Panel,
19
19
  {
20
- title: "Biggest merchants by sales",
20
+ title: "Top merchants by GMV",
21
21
  count: tenants.length || void 0,
22
22
  caption: focus ?? void 0,
23
23
  action: /* @__PURE__ */ jsx(Link, { href: "/admin/tenants", children: "View all" }),
@@ -31,9 +31,9 @@ function TopTenantsPanel({
31
31
  head: /* @__PURE__ */ jsxs("tr", { children: [
32
32
  /* @__PURE__ */ jsx(Th, { children: "Merchant" }),
33
33
  /* @__PURE__ */ jsx(Th, { align: "right", children: "Orders" }),
34
- /* @__PURE__ */ jsx(Th, { align: "right", children: "Average order" }),
35
- /* @__PURE__ */ jsx(Th, { align: "right", children: "Sales" }),
36
- /* @__PURE__ */ jsx(Th, { align: "right", children: "Share of sales" })
34
+ /* @__PURE__ */ jsx(Th, { align: "right", children: "AOV" }),
35
+ /* @__PURE__ */ jsx(Th, { align: "right", children: "GMV" }),
36
+ /* @__PURE__ */ jsx(Th, { align: "right", children: "% of GMV" })
37
37
  ] }),
38
38
  children: tenants.map((t) => {
39
39
  const aov = t.orders > 0 ? t.gmv / t.orders : 0;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/revenue/top-tenants-panel.tsx"],"sourcesContent":["import Link from 'next/link';\nimport { useRouter } from 'next/navigation';\nimport { EmptyNote, TableRow, TableShell, Th } from '@withpotter/ui-kit';\nimport type { PlatformRevenue } from '@withpotter/lib/admin-api';\nimport { EntityAvatar } from '../_fx';\nimport { PICK_CURRENCY, Panel, moneyIn } from '../_ui';\nimport { logoOf } from '../tenants/tenants-logo';\nimport styles from './revenue.module.css';\n\nconst integer = new Intl.NumberFormat('en-US');\n\n/**\n * Merchants ranked by GMV within one currency, with order count, AOV and\n * share of that currency's lifetime GMV.\n */\nexport function TopTenantsPanel({\n tenants,\n lifetime,\n focus,\n}: {\n tenants: PlatformRevenue['gmv']['topTenants'];\n /** Lifetime GMV in `focus`, the base for each share. */\n lifetime: number;\n /** The one currency ranked, or null when there are several and none is chosen. */\n focus: string | null;\n}) {\n const router = useRouter();\n const money = moneyIn(focus ?? '');\n\n return (\n <Panel\n title=\"Biggest merchants by sales\"\n count={tenants.length || undefined}\n caption={focus ?? undefined}\n action={<Link href=\"/admin/tenants\">View all</Link>}\n flush\n >\n {tenants.length === 0 ? (\n <EmptyNote className={styles.empty}>\n {focus ? `No ${focus} merchant revenue recorded yet.` : PICK_CURRENCY}\n </EmptyNote>\n ) : (\n <TableShell\n bare\n minWidth={520}\n density=\"compact\"\n head={\n <tr>\n <Th>Merchant</Th>\n <Th align=\"right\">Orders</Th>\n <Th align=\"right\">Average order</Th>\n <Th align=\"right\">Sales</Th>\n <Th align=\"right\">Share of sales</Th>\n </tr>\n }\n >\n {tenants.map((t) => {\n const aov = t.orders > 0 ? t.gmv / t.orders : 0;\n const share = lifetime > 0 ? (t.gmv / lifetime) * 100 : 0;\n const logo = logoOf(t);\n return (\n <TableRow\n key={t.id}\n onClick={() => router.push(`/admin/tenants/${t.id}`)}\n >\n <td className={styles.name}>\n <span className={styles.tenant}>\n {logo && (\n <EntityAvatar\n name={t.businessName}\n src={logo}\n size={28}\n />\n )}\n <span className={styles.tenantText}>\n <Link\n href={`/admin/tenants/${t.id}`}\n title={t.businessName}\n onClick={(e) => e.stopPropagation()}\n >\n {t.businessName}\n </Link>\n {t.slug && (\n <span className={styles.slug} title={t.slug}>\n {t.slug}\n </span>\n )}\n </span>\n </span>\n </td>\n <td className={styles.num}>{integer.format(t.orders)}</td>\n <td className={styles.num}>{money(aov)}</td>\n <td className={styles.numStrong}>{money(t.gmv)}</td>\n <td className={styles.num}>\n <span className={styles.share}>\n <span className={styles.shareTrack} aria-hidden>\n <span\n className={styles.shareFill}\n style={{ width: `${Math.min(share, 100)}%` }}\n />\n </span>\n {share.toFixed(1)}%\n </span>\n </td>\n </TableRow>\n );\n })}\n </TableShell>\n )}\n </Panel>\n );\n}\n"],"mappings":"AAkCc,cAaF,YAbE;AAlCd,OAAO,UAAU;AACjB,SAAS,iBAAiB;AAC1B,SAAS,WAAW,UAAU,YAAY,UAAU;AAEpD,SAAS,oBAAoB;AAC7B,SAAS,eAAe,OAAO,eAAe;AAC9C,SAAS,cAAc;AACvB,OAAO,YAAY;AAEnB,MAAM,UAAU,IAAI,KAAK,aAAa,OAAO;AAMtC,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,QAAM,SAAS,UAAU;AACzB,QAAM,QAAQ,QAAQ,SAAS,EAAE;AAEjC,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAM;AAAA,MACN,OAAO,QAAQ,UAAU;AAAA,MACzB,SAAS,SAAS;AAAA,MAClB,QAAQ,oBAAC,QAAK,MAAK,kBAAiB,sBAAQ;AAAA,MAC5C,OAAK;AAAA,MAEJ,kBAAQ,WAAW,IAClB,oBAAC,aAAU,WAAW,OAAO,OAC1B,kBAAQ,MAAM,KAAK,oCAAoC,eAC1D,IAEA;AAAA,QAAC;AAAA;AAAA,UACC,MAAI;AAAA,UACJ,UAAU;AAAA,UACV,SAAQ;AAAA,UACR,MACE,qBAAC,QACC;AAAA,gCAAC,MAAG,sBAAQ;AAAA,YACZ,oBAAC,MAAG,OAAM,SAAQ,oBAAM;AAAA,YACxB,oBAAC,MAAG,OAAM,SAAQ,2BAAa;AAAA,YAC/B,oBAAC,MAAG,OAAM,SAAQ,mBAAK;AAAA,YACvB,oBAAC,MAAG,OAAM,SAAQ,4BAAc;AAAA,aAClC;AAAA,UAGD,kBAAQ,IAAI,CAAC,MAAM;AAClB,kBAAM,MAAM,EAAE,SAAS,IAAI,EAAE,MAAM,EAAE,SAAS;AAC9C,kBAAM,QAAQ,WAAW,IAAK,EAAE,MAAM,WAAY,MAAM;AACxD,kBAAM,OAAO,OAAO,CAAC;AACrB,mBACE;AAAA,cAAC;AAAA;AAAA,gBAEC,SAAS,MAAM,OAAO,KAAK,kBAAkB,EAAE,EAAE,EAAE;AAAA,gBAEnD;AAAA,sCAAC,QAAG,WAAW,OAAO,MACpB,+BAAC,UAAK,WAAW,OAAO,QACrB;AAAA,4BACC;AAAA,sBAAC;AAAA;AAAA,wBACC,MAAM,EAAE;AAAA,wBACR,KAAK;AAAA,wBACL,MAAM;AAAA;AAAA,oBACR;AAAA,oBAEF,qBAAC,UAAK,WAAW,OAAO,YACtB;AAAA;AAAA,wBAAC;AAAA;AAAA,0BACC,MAAM,kBAAkB,EAAE,EAAE;AAAA,0BAC5B,OAAO,EAAE;AAAA,0BACT,SAAS,CAAC,MAAM,EAAE,gBAAgB;AAAA,0BAEjC,YAAE;AAAA;AAAA,sBACL;AAAA,sBACC,EAAE,QACD,oBAAC,UAAK,WAAW,OAAO,MAAM,OAAO,EAAE,MACpC,YAAE,MACL;AAAA,uBAEJ;AAAA,qBACF,GACF;AAAA,kBACA,oBAAC,QAAG,WAAW,OAAO,KAAM,kBAAQ,OAAO,EAAE,MAAM,GAAE;AAAA,kBACrD,oBAAC,QAAG,WAAW,OAAO,KAAM,gBAAM,GAAG,GAAE;AAAA,kBACvC,oBAAC,QAAG,WAAW,OAAO,WAAY,gBAAM,EAAE,GAAG,GAAE;AAAA,kBAC/C,oBAAC,QAAG,WAAW,OAAO,KACpB,+BAAC,UAAK,WAAW,OAAO,OACtB;AAAA,wCAAC,UAAK,WAAW,OAAO,YAAY,eAAW,MAC7C;AAAA,sBAAC;AAAA;AAAA,wBACC,WAAW,OAAO;AAAA,wBAClB,OAAO,EAAE,OAAO,GAAG,KAAK,IAAI,OAAO,GAAG,CAAC,IAAI;AAAA;AAAA,oBAC7C,GACF;AAAA,oBACC,MAAM,QAAQ,CAAC;AAAA,oBAAE;AAAA,qBACpB,GACF;AAAA;AAAA;AAAA,cAzCK,EAAE;AAAA,YA0CT;AAAA,UAEJ,CAAC;AAAA;AAAA,MACH;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/revenue/top-tenants-panel.tsx"],"sourcesContent":["import Link from 'next/link';\nimport { useRouter } from 'next/navigation';\nimport { EmptyNote, TableRow, TableShell, Th } from '@withpotter/ui-kit';\nimport type { PlatformRevenue } from '@withpotter/lib/admin-api';\nimport { EntityAvatar } from '../_fx';\nimport { PICK_CURRENCY, Panel, moneyIn } from '../_ui';\nimport { logoOf } from '../tenants/tenants-logo';\nimport styles from './revenue.module.css';\n\nconst integer = new Intl.NumberFormat('en-US');\n\n/**\n * Merchants ranked by GMV within one currency, with order count, AOV and\n * share of that currency's lifetime GMV.\n */\nexport function TopTenantsPanel({\n tenants,\n lifetime,\n focus,\n}: {\n tenants: PlatformRevenue['gmv']['topTenants'];\n /** Lifetime GMV in `focus`, the base for each share. */\n lifetime: number;\n /** The one currency ranked, or null when there are several and none is chosen. */\n focus: string | null;\n}) {\n const router = useRouter();\n const money = moneyIn(focus ?? '');\n\n return (\n <Panel\n title=\"Top merchants by GMV\"\n count={tenants.length || undefined}\n caption={focus ?? undefined}\n action={<Link href=\"/admin/tenants\">View all</Link>}\n flush\n >\n {tenants.length === 0 ? (\n <EmptyNote className={styles.empty}>\n {focus ? `No ${focus} merchant revenue recorded yet.` : PICK_CURRENCY}\n </EmptyNote>\n ) : (\n <TableShell\n bare\n minWidth={520}\n density=\"compact\"\n head={\n <tr>\n <Th>Merchant</Th>\n <Th align=\"right\">Orders</Th>\n <Th align=\"right\">AOV</Th>\n <Th align=\"right\">GMV</Th>\n <Th align=\"right\">% of GMV</Th>\n </tr>\n }\n >\n {tenants.map((t) => {\n const aov = t.orders > 0 ? t.gmv / t.orders : 0;\n const share = lifetime > 0 ? (t.gmv / lifetime) * 100 : 0;\n const logo = logoOf(t);\n return (\n <TableRow\n key={t.id}\n onClick={() => router.push(`/admin/tenants/${t.id}`)}\n >\n <td className={styles.name}>\n <span className={styles.tenant}>\n {logo && (\n <EntityAvatar\n name={t.businessName}\n src={logo}\n size={28}\n />\n )}\n <span className={styles.tenantText}>\n <Link\n href={`/admin/tenants/${t.id}`}\n title={t.businessName}\n onClick={(e) => e.stopPropagation()}\n >\n {t.businessName}\n </Link>\n {t.slug && (\n <span className={styles.slug} title={t.slug}>\n {t.slug}\n </span>\n )}\n </span>\n </span>\n </td>\n <td className={styles.num}>{integer.format(t.orders)}</td>\n <td className={styles.num}>{money(aov)}</td>\n <td className={styles.numStrong}>{money(t.gmv)}</td>\n <td className={styles.num}>\n <span className={styles.share}>\n <span className={styles.shareTrack} aria-hidden>\n <span\n className={styles.shareFill}\n style={{ width: `${Math.min(share, 100)}%` }}\n />\n </span>\n {share.toFixed(1)}%\n </span>\n </td>\n </TableRow>\n );\n })}\n </TableShell>\n )}\n </Panel>\n );\n}\n"],"mappings":"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","names":[]}
@@ -2,17 +2,21 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
4
  import { useRouter } from "next/navigation";
5
- import { Button } from "@withpotter/ui-kit";
5
+ import { Button, useConfirm, useToast } from "@withpotter/ui-kit";
6
6
  import { SUPPORT_INBOX_EMAIL } from "@withpotter/lib/support-inbox";
7
7
  import { useAdminSession } from "../hooks";
8
+ import { settingsStore } from "../_settings";
8
9
  import { useModuleOn } from "../_shell/modules-context";
9
10
  import { signOutAdmin } from "../_shell/sign-out";
10
11
  import { ChangePasswordDialog } from "./change-password-dialog";
11
12
  import { ProfileCard } from "./profile-card";
12
13
  import { SettingRow, SettingsCard } from "./setting-row";
14
+ import { SystemStatusCard } from "./system-status-card";
13
15
  import styles from "./settings.module.css";
14
16
  function AccountTab() {
15
17
  const router = useRouter();
18
+ const confirm = useConfirm();
19
+ const { toast } = useToast();
16
20
  const { data: session } = useAdminSession();
17
21
  const [changing, setChanging] = useState(false);
18
22
  const [leaving, setLeaving] = useState(false);
@@ -22,6 +26,17 @@ function AccountTab() {
22
26
  await signOutAdmin();
23
27
  router.replace("/admin/login");
24
28
  };
29
+ const reset = async () => {
30
+ const ok = await confirm({
31
+ title: "Reset settings on this device?",
32
+ description: "Density, accessibility, notification and shortcut choices go back to their defaults. Your account is not affected.",
33
+ confirmLabel: "Reset settings",
34
+ tone: "danger"
35
+ });
36
+ if (!ok) return;
37
+ settingsStore.reset();
38
+ toast({ title: "Settings reset on this device" });
39
+ };
25
40
  return /* @__PURE__ */ jsxs("div", { className: styles.stack, children: [
26
41
  session && /* @__PURE__ */ jsx(ProfileCard, { session }),
27
42
  /* @__PURE__ */ jsxs(SettingsCard, { title: "Security", children: [
@@ -42,6 +57,14 @@ function AccountTab() {
42
57
  }
43
58
  )
44
59
  ] }),
60
+ /* @__PURE__ */ jsx(SettingsCard, { title: "This device", children: /* @__PURE__ */ jsx(
61
+ SettingRow,
62
+ {
63
+ label: "Reset all settings on this device",
64
+ description: "Every preference on this page returns to its default.",
65
+ children: /* @__PURE__ */ jsx(Button, { variant: "danger", onClick: reset, children: "Reset" })
66
+ }
67
+ ) }),
45
68
  supportOn && /* @__PURE__ */ jsx(SettingsCard, { title: "Support inbox", children: /* @__PURE__ */ jsx(
46
69
  SettingRow,
47
70
  {
@@ -50,6 +73,7 @@ function AccountTab() {
50
73
  children: /* @__PURE__ */ jsx("span", { className: styles.value, title: SUPPORT_INBOX_EMAIL, children: SUPPORT_INBOX_EMAIL })
51
74
  }
52
75
  ) }),
76
+ /* @__PURE__ */ jsx(SystemStatusCard, {}),
53
77
  changing && /* @__PURE__ */ jsx(ChangePasswordDialog, { onClose: () => setChanging(false) })
54
78
  ] });
55
79
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/settings/account-tab.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { useRouter } from 'next/navigation';\nimport { Button } from '@withpotter/ui-kit';\nimport { SUPPORT_INBOX_EMAIL } from '@withpotter/lib/support-inbox';\nimport { useAdminSession } from '../hooks';\nimport { useModuleOn } from '../_shell/modules-context';\nimport { signOutAdmin } from '../_shell/sign-out';\nimport { ChangePasswordDialog } from './change-password-dialog';\nimport { ProfileCard } from './profile-card';\nimport { SettingRow, SettingsCard } from './setting-row';\nimport styles from './settings.module.css';\n\n/**\n * Who you are and how you sign in. The support inbox\n * address shows only where this box serves Support.\n */\nexport function AccountTab() {\n const router = useRouter();\n const { data: session } = useAdminSession();\n const [changing, setChanging] = useState(false);\n const [leaving, setLeaving] = useState(false);\n const supportOn = useModuleOn('support');\n\n const signOut = async () => {\n setLeaving(true);\n await signOutAdmin();\n router.replace('/admin/login');\n };\n\n return (\n <div className={styles.stack}>\n {session && <ProfileCard session={session} />}\n <SettingsCard title=\"Security\">\n <SettingRow\n label=\"Password\"\n description=\"At least 12 characters. You stay signed in here.\"\n >\n <Button variant=\"secondary\" onClick={() => setChanging(true)}>\n Change password\n </Button>\n </SettingRow>\n <SettingRow\n label=\"Sign out\"\n description=\"Also ends any View as owner session.\"\n >\n <Button variant=\"secondary\" loading={leaving} onClick={signOut}>\n Sign out\n </Button>\n </SettingRow>\n </SettingsCard>\n {supportOn && (\n <SettingsCard title=\"Support inbox\">\n <SettingRow\n label=\"Inbound address\"\n description=\"Merchants write here; each email opens a case in Support.\"\n >\n <span className={styles.value} title={SUPPORT_INBOX_EMAIL}>\n {SUPPORT_INBOX_EMAIL}\n </span>\n </SettingRow>\n </SettingsCard>\n )}\n {changing && <ChangePasswordDialog onClose={() => setChanging(false)} />}\n </div>\n );\n}\n"],"mappings":";AAiCkB,cACZ,YADY;AA/BlB,SAAS,gBAAgB;AACzB,SAAS,iBAAiB;AAC1B,SAAS,cAAc;AACvB,SAAS,2BAA2B;AACpC,SAAS,uBAAuB;AAChC,SAAS,mBAAmB;AAC5B,SAAS,oBAAoB;AAC7B,SAAS,4BAA4B;AACrC,SAAS,mBAAmB;AAC5B,SAAS,YAAY,oBAAoB;AACzC,OAAO,YAAY;AAMZ,SAAS,aAAa;AAC3B,QAAM,SAAS,UAAU;AACzB,QAAM,EAAE,MAAM,QAAQ,IAAI,gBAAgB;AAC1C,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,YAAY,YAAY,SAAS;AAEvC,QAAM,UAAU,YAAY;AAC1B,eAAW,IAAI;AACf,UAAM,aAAa;AACnB,WAAO,QAAQ,cAAc;AAAA,EAC/B;AAEA,SACE,qBAAC,SAAI,WAAW,OAAO,OACpB;AAAA,eAAW,oBAAC,eAAY,SAAkB;AAAA,IAC3C,qBAAC,gBAAa,OAAM,YAClB;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,aAAY;AAAA,UAEZ,8BAAC,UAAO,SAAQ,aAAY,SAAS,MAAM,YAAY,IAAI,GAAG,6BAE9D;AAAA;AAAA,MACF;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,aAAY;AAAA,UAEZ,8BAAC,UAAO,SAAQ,aAAY,SAAS,SAAS,SAAS,SAAS,sBAEhE;AAAA;AAAA,MACF;AAAA,OACF;AAAA,IACC,aACC,oBAAC,gBAAa,OAAM,iBAClB;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,aAAY;AAAA,QAEZ,8BAAC,UAAK,WAAW,OAAO,OAAO,OAAO,qBACnC,+BACH;AAAA;AAAA,IACF,GACF;AAAA,IAED,YAAY,oBAAC,wBAAqB,SAAS,MAAM,YAAY,KAAK,GAAG;AAAA,KACxE;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/settings/account-tab.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { useRouter } from 'next/navigation';\nimport { Button, useConfirm, useToast } from '@withpotter/ui-kit';\nimport { SUPPORT_INBOX_EMAIL } from '@withpotter/lib/support-inbox';\nimport { useAdminSession } from '../hooks';\nimport { settingsStore } from '../_settings';\nimport { useModuleOn } from '../_shell/modules-context';\nimport { signOutAdmin } from '../_shell/sign-out';\nimport { ChangePasswordDialog } from './change-password-dialog';\nimport { ProfileCard } from './profile-card';\nimport { SettingRow, SettingsCard } from './setting-row';\nimport { SystemStatusCard } from './system-status-card';\nimport styles from './settings.module.css';\n\n/**\n * Who you are, how you sign in, and the box's own health. The support inbox\n * address shows only where this box serves Support.\n */\nexport function AccountTab() {\n const router = useRouter();\n const confirm = useConfirm();\n const { toast } = useToast();\n const { data: session } = useAdminSession();\n const [changing, setChanging] = useState(false);\n const [leaving, setLeaving] = useState(false);\n const supportOn = useModuleOn('support');\n\n const signOut = async () => {\n setLeaving(true);\n await signOutAdmin();\n router.replace('/admin/login');\n };\n\n const reset = async () => {\n const ok = await confirm({\n title: 'Reset settings on this device?',\n description:\n 'Density, accessibility, notification and shortcut choices go back to their defaults. Your account is not affected.',\n confirmLabel: 'Reset settings',\n tone: 'danger',\n });\n if (!ok) return;\n settingsStore.reset();\n toast({ title: 'Settings reset on this device' });\n };\n\n return (\n <div className={styles.stack}>\n {session && <ProfileCard session={session} />}\n <SettingsCard title=\"Security\">\n <SettingRow\n label=\"Password\"\n description=\"At least 12 characters. You stay signed in here.\"\n >\n <Button variant=\"secondary\" onClick={() => setChanging(true)}>\n Change password\n </Button>\n </SettingRow>\n <SettingRow\n label=\"Sign out\"\n description=\"Also ends any View as owner session.\"\n >\n <Button variant=\"secondary\" loading={leaving} onClick={signOut}>\n Sign out\n </Button>\n </SettingRow>\n </SettingsCard>\n <SettingsCard title=\"This device\">\n <SettingRow\n label=\"Reset all settings on this device\"\n description=\"Every preference on this page returns to its default.\"\n >\n <Button variant=\"danger\" onClick={reset}>\n Reset\n </Button>\n </SettingRow>\n </SettingsCard>\n {supportOn && (\n <SettingsCard title=\"Support inbox\">\n <SettingRow\n label=\"Inbound address\"\n description=\"Merchants write here; each email opens a case in Support.\"\n >\n <span className={styles.value} title={SUPPORT_INBOX_EMAIL}>\n {SUPPORT_INBOX_EMAIL}\n </span>\n </SettingRow>\n </SettingsCard>\n )}\n <SystemStatusCard />\n {changing && <ChangePasswordDialog onClose={() => setChanging(false)} />}\n </div>\n );\n}\n"],"mappings":";AAkDkB,cACZ,YADY;AAhDlB,SAAS,gBAAgB;AACzB,SAAS,iBAAiB;AAC1B,SAAS,QAAQ,YAAY,gBAAgB;AAC7C,SAAS,2BAA2B;AACpC,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAC9B,SAAS,mBAAmB;AAC5B,SAAS,oBAAoB;AAC7B,SAAS,4BAA4B;AACrC,SAAS,mBAAmB;AAC5B,SAAS,YAAY,oBAAoB;AACzC,SAAS,wBAAwB;AACjC,OAAO,YAAY;AAMZ,SAAS,aAAa;AAC3B,QAAM,SAAS,UAAU;AACzB,QAAM,UAAU,WAAW;AAC3B,QAAM,EAAE,MAAM,IAAI,SAAS;AAC3B,QAAM,EAAE,MAAM,QAAQ,IAAI,gBAAgB;AAC1C,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,YAAY,YAAY,SAAS;AAEvC,QAAM,UAAU,YAAY;AAC1B,eAAW,IAAI;AACf,UAAM,aAAa;AACnB,WAAO,QAAQ,cAAc;AAAA,EAC/B;AAEA,QAAM,QAAQ,YAAY;AACxB,UAAM,KAAK,MAAM,QAAQ;AAAA,MACvB,OAAO;AAAA,MACP,aACE;AAAA,MACF,cAAc;AAAA,MACd,MAAM;AAAA,IACR,CAAC;AACD,QAAI,CAAC,GAAI;AACT,kBAAc,MAAM;AACpB,UAAM,EAAE,OAAO,gCAAgC,CAAC;AAAA,EAClD;AAEA,SACE,qBAAC,SAAI,WAAW,OAAO,OACpB;AAAA,eAAW,oBAAC,eAAY,SAAkB;AAAA,IAC3C,qBAAC,gBAAa,OAAM,YAClB;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,aAAY;AAAA,UAEZ,8BAAC,UAAO,SAAQ,aAAY,SAAS,MAAM,YAAY,IAAI,GAAG,6BAE9D;AAAA;AAAA,MACF;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,aAAY;AAAA,UAEZ,8BAAC,UAAO,SAAQ,aAAY,SAAS,SAAS,SAAS,SAAS,sBAEhE;AAAA;AAAA,MACF;AAAA,OACF;AAAA,IACA,oBAAC,gBAAa,OAAM,eAClB;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,aAAY;AAAA,QAEZ,8BAAC,UAAO,SAAQ,UAAS,SAAS,OAAO,mBAEzC;AAAA;AAAA,IACF,GACF;AAAA,IACC,aACC,oBAAC,gBAAa,OAAM,iBAClB;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,aAAY;AAAA,QAEZ,8BAAC,UAAK,WAAW,OAAO,OAAO,OAAO,qBACnC,+BACH;AAAA;AAAA,IACF,GACF;AAAA,IAEF,oBAAC,oBAAiB;AAAA,IACjB,YAAY,oBAAC,wBAAqB,SAAS,MAAM,YAAY,KAAK,GAAG;AAAA,KACxE;AAEJ;","names":[]}
@@ -17,8 +17,8 @@ const THEMES = [
17
17
  const START_OPTIONS = [
18
18
  { value: "overview", label: "Overview" },
19
19
  { value: "support", label: "Support" },
20
- { value: "merchants", label: "Workspaces" },
21
- { value: "people", label: "User accounts" }
20
+ { value: "merchants", label: "Merchants" },
21
+ { value: "people", label: "People" }
22
22
  ];
23
23
  function GeneralTab() {
24
24
  const settings = useSettings();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/settings/general-tab.tsx"],"sourcesContent":["'use client';\n\nimport { Select } from '@withpotter/ui-kit';\nimport { useTheme } from '@withpotter/lib/theme-context';\nimport { RANGE_OPTIONS, usePersistedDateRange, type RangeKey } from '../_ui';\nimport { useSettings, type AdminSettings } from '../_settings';\nimport { useModuleOn } from '../_shell/modules-context';\nimport { useSidebarCollapsed, sidebarStore } from '../_shell/use-shell-prefs';\nimport { Segmented, SettingRow, SettingsCard } from './setting-row';\nimport { useSave } from './use-save';\nimport styles from './settings.module.css';\n\ntype Theme = 'system' | 'light' | 'dark';\n\nconst THEMES = [\n { value: 'system' as Theme, label: 'System' },\n { value: 'light' as Theme, label: 'Light' },\n { value: 'dark' as Theme, label: 'Dark' },\n];\n\nconst START_OPTIONS: Array<{\n value: AdminSettings['startPage'];\n label: string;\n}> = [\n { value: 'overview', label: 'Overview' },\n { value: 'support', label: 'Support' },\n { value: 'merchants', label: 'Workspaces' },\n { value: 'people', label: 'User accounts' },\n];\n\n/**\n * Look, layout and the views every page opens with. No display currency on a\n * box: money is shown per currency and never converted, so there is nothing\n * to pick. Support is a start page only where this box serves Support.\n */\nexport function GeneralTab() {\n const settings = useSettings();\n const { update, saved } = useSave();\n const { theme, setTheme } = useTheme();\n const { rangeKey, setRangeKey } = usePersistedDateRange();\n const collapsed = useSidebarCollapsed();\n const supportOn = useModuleOn('support');\n const startOptions = START_OPTIONS.filter(\n (o) => supportOn || o.value !== 'support'\n );\n\n return (\n <div className={styles.stack}>\n <SettingsCard title=\"Appearance\">\n <SettingRow\n label=\"Theme\"\n description=\"System follows your computer's light or dark setting.\"\n >\n <Segmented\n label=\"Theme\"\n value={theme}\n options={THEMES}\n onChange={(next) => {\n setTheme(next);\n saved();\n }}\n />\n </SettingRow>\n <SettingRow\n label=\"Density\"\n description=\"Compact fits more rows and cards on screen.\"\n >\n <Segmented\n label=\"Density\"\n value={settings.density}\n options={[\n { value: 'comfortable', label: 'Comfortable' },\n { value: 'compact', label: 'Compact' },\n ]}\n onChange={(density) => update({ density })}\n />\n </SettingRow>\n <SettingRow\n label=\"Sidebar\"\n description=\"Collapsed shows icons only. You can also toggle it from the sidebar.\"\n >\n <Segmented\n label=\"Sidebar\"\n value={collapsed ? 'collapsed' : 'expanded'}\n options={[\n { value: 'expanded', label: 'Expanded' },\n { value: 'collapsed', label: 'Collapsed' },\n ]}\n onChange={(next) => {\n sidebarStore.set(next === 'collapsed');\n saved();\n }}\n />\n </SettingRow>\n </SettingsCard>\n\n <SettingsCard title=\"Defaults\" description=\"What pages open with.\">\n <SettingRow\n label=\"Default period\"\n description=\"The date range reports and charts start on.\"\n >\n <Select\n className={styles.select}\n ariaLabel=\"Default period\"\n value={rangeKey}\n options={RANGE_OPTIONS.map((o) => ({\n value: o.key,\n label: o.label,\n }))}\n onChange={(key) => {\n setRangeKey(key as RangeKey);\n saved();\n }}\n />\n </SettingRow>\n <SettingRow\n label=\"Start page\"\n description=\"Where the console opens when you sign in or reload on Overview.\"\n >\n <Select\n className={styles.select}\n ariaLabel=\"Start page\"\n value={settings.startPage}\n options={startOptions}\n onChange={(value) =>\n update({ startPage: value as AdminSettings['startPage'] })\n }\n />\n </SettingRow>\n </SettingsCard>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/settings/general-tab.tsx"],"sourcesContent":["'use client';\n\nimport { Select } from '@withpotter/ui-kit';\nimport { useTheme } from '@withpotter/lib/theme-context';\nimport { RANGE_OPTIONS, usePersistedDateRange, type RangeKey } from '../_ui';\nimport { useSettings, type AdminSettings } from '../_settings';\nimport { useModuleOn } from '../_shell/modules-context';\nimport { useSidebarCollapsed, sidebarStore } from '../_shell/use-shell-prefs';\nimport { Segmented, SettingRow, SettingsCard } from './setting-row';\nimport { useSave } from './use-save';\nimport styles from './settings.module.css';\n\ntype Theme = 'system' | 'light' | 'dark';\n\nconst THEMES = [\n { value: 'system' as Theme, label: 'System' },\n { value: 'light' as Theme, label: 'Light' },\n { value: 'dark' as Theme, label: 'Dark' },\n];\n\nconst START_OPTIONS: Array<{\n value: AdminSettings['startPage'];\n label: string;\n}> = [\n { value: 'overview', label: 'Overview' },\n { value: 'support', label: 'Support' },\n { value: 'merchants', label: 'Merchants' },\n { value: 'people', label: 'People' },\n];\n\n/**\n * Look, layout and the views every page opens with. No display currency on a\n * box: money is shown per currency and never converted, so there is nothing\n * to pick. Support is a start page only where this box serves Support.\n */\nexport function GeneralTab() {\n const settings = useSettings();\n const { update, saved } = useSave();\n const { theme, setTheme } = useTheme();\n const { rangeKey, setRangeKey } = usePersistedDateRange();\n const collapsed = useSidebarCollapsed();\n const supportOn = useModuleOn('support');\n const startOptions = START_OPTIONS.filter(\n (o) => supportOn || o.value !== 'support'\n );\n\n return (\n <div className={styles.stack}>\n <SettingsCard title=\"Appearance\">\n <SettingRow\n label=\"Theme\"\n description=\"System follows your computer's light or dark setting.\"\n >\n <Segmented\n label=\"Theme\"\n value={theme}\n options={THEMES}\n onChange={(next) => {\n setTheme(next);\n saved();\n }}\n />\n </SettingRow>\n <SettingRow\n label=\"Density\"\n description=\"Compact fits more rows and cards on screen.\"\n >\n <Segmented\n label=\"Density\"\n value={settings.density}\n options={[\n { value: 'comfortable', label: 'Comfortable' },\n { value: 'compact', label: 'Compact' },\n ]}\n onChange={(density) => update({ density })}\n />\n </SettingRow>\n <SettingRow\n label=\"Sidebar\"\n description=\"Collapsed shows icons only. You can also toggle it from the sidebar.\"\n >\n <Segmented\n label=\"Sidebar\"\n value={collapsed ? 'collapsed' : 'expanded'}\n options={[\n { value: 'expanded', label: 'Expanded' },\n { value: 'collapsed', label: 'Collapsed' },\n ]}\n onChange={(next) => {\n sidebarStore.set(next === 'collapsed');\n saved();\n }}\n />\n </SettingRow>\n </SettingsCard>\n\n <SettingsCard title=\"Defaults\" description=\"What pages open with.\">\n <SettingRow\n label=\"Default period\"\n description=\"The date range reports and charts start on.\"\n >\n <Select\n className={styles.select}\n ariaLabel=\"Default period\"\n value={rangeKey}\n options={RANGE_OPTIONS.map((o) => ({\n value: o.key,\n label: o.label,\n }))}\n onChange={(key) => {\n setRangeKey(key as RangeKey);\n saved();\n }}\n />\n </SettingRow>\n <SettingRow\n label=\"Start page\"\n description=\"Where the console opens when you sign in or reload on Overview.\"\n >\n <Select\n className={styles.select}\n ariaLabel=\"Start page\"\n value={settings.startPage}\n options={startOptions}\n onChange={(value) =>\n update({ startPage: value as AdminSettings['startPage'] })\n }\n />\n </SettingRow>\n </SettingsCard>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,30 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { useQuery } from "@tanstack/react-query";
4
+ import { adminReadinessApi } from "@withpotter/lib/admin-readiness-api";
5
+ import { overallOf, SystemStatusPanel } from "../_shell/system-status-panel";
6
+ import { SettingsCard } from "./setting-row";
7
+ function SystemStatusCard() {
8
+ const query = useQuery({
9
+ queryKey: ["platform-admin", "readiness"],
10
+ queryFn: adminReadinessApi.readiness,
11
+ staleTime: 6e4,
12
+ retry: 1
13
+ });
14
+ return /* @__PURE__ */ jsx(SettingsCard, { title: "System status", children: /* @__PURE__ */ jsx(
15
+ SystemStatusPanel,
16
+ {
17
+ data: query.data,
18
+ overall: overallOf(query.data, query.isError),
19
+ isLoading: query.isLoading,
20
+ isError: query.isError,
21
+ isFetching: query.isFetching,
22
+ checkedAt: query.dataUpdatedAt,
23
+ onRefresh: () => void query.refetch()
24
+ }
25
+ ) });
26
+ }
27
+ export {
28
+ SystemStatusCard
29
+ };
30
+ //# sourceMappingURL=system-status-card.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/admin/settings/system-status-card.tsx"],"sourcesContent":["'use client';\n\nimport { useQuery } from '@tanstack/react-query';\nimport { adminReadinessApi } from '@withpotter/lib/admin-readiness-api';\nimport { overallOf, SystemStatusPanel } from '../_shell/system-status-panel';\nimport { SettingsCard } from './setting-row';\n\n/** The deployment's integration checks, each with its state. */\nexport function SystemStatusCard() {\n const query = useQuery({\n queryKey: ['platform-admin', 'readiness'],\n queryFn: adminReadinessApi.readiness,\n staleTime: 60_000,\n retry: 1,\n });\n\n return (\n <SettingsCard title=\"System status\">\n <SystemStatusPanel\n data={query.data}\n overall={overallOf(query.data, query.isError)}\n isLoading={query.isLoading}\n isError={query.isError}\n isFetching={query.isFetching}\n checkedAt={query.dataUpdatedAt}\n onRefresh={() => void query.refetch()}\n />\n </SettingsCard>\n );\n}\n"],"mappings":";AAkBM;AAhBN,SAAS,gBAAgB;AACzB,SAAS,yBAAyB;AAClC,SAAS,WAAW,yBAAyB;AAC7C,SAAS,oBAAoB;AAGtB,SAAS,mBAAmB;AACjC,QAAM,QAAQ,SAAS;AAAA,IACrB,UAAU,CAAC,kBAAkB,WAAW;AAAA,IACxC,SAAS,kBAAkB;AAAA,IAC3B,WAAW;AAAA,IACX,OAAO;AAAA,EACT,CAAC;AAED,SACE,oBAAC,gBAAa,OAAM,iBAClB;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,MAAM;AAAA,MACZ,SAAS,UAAU,MAAM,MAAM,MAAM,OAAO;AAAA,MAC5C,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,MACf,YAAY,MAAM;AAAA,MAClB,WAAW,MAAM;AAAA,MACjB,WAAW,MAAM,KAAK,MAAM,QAAQ;AAAA;AAAA,EACtC,GACF;AAEJ;","names":[]}
@@ -75,8 +75,11 @@
75
75
  outline-offset: -2px;
76
76
  }
77
77
 
78
- /* Column header with a hairline, not a shadow. */
78
+ /* Sticky column header with a hairline, not a shadow. */
79
79
  .scroll thead th {
80
+ position: sticky;
81
+ top: 0;
82
+ z-index: 1;
80
83
  background: var(--bg-base);
81
84
  box-shadow: inset 0 -1px 0 var(--border-subtle);
82
85
  white-space: nowrap;
@@ -3,8 +3,7 @@ function moneyIn(currency, decimals = 0) {
3
3
  try {
4
4
  return new Intl.NumberFormat("en-US", {
5
5
  style: "currency",
6
- currencyDisplay: "narrowSymbol",
7
- currency: currency || "NGN",
6
+ currency: currency || "USD",
8
7
  maximumFractionDigits: decimals
9
8
  }).format(n);
10
9
  } catch {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/admin/tenants/%5Bid%5D/commerce/format.ts"],"sourcesContent":["import type { TenantProduct } from '@withpotter/lib/admin-api';\n\n/** Amounts in the merchant's own currency, whole units for headlines. */\nexport function moneyIn(currency: string, decimals = 0) {\n return (n: number) => {\n try {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currencyDisplay: 'narrowSymbol',\n currency: currency || 'NGN',\n maximumFractionDigits: decimals,\n }).format(n);\n } catch {\n return `${currency} ${Math.round(n).toLocaleString()}`;\n }\n };\n}\n\nexport type Money = (n: number) => string;\n\n/** 1.2k / 3.4m, for figures in tight tiles. */\nexport const compact = (n: number) =>\n new Intl.NumberFormat('en-US', {\n notation: 'compact',\n maximumFractionDigits: 1,\n }).format(n);\n\nexport const shortDate = (iso: string) =>\n new Intl.DateTimeFormat('en', { month: 'short', day: 'numeric' }).format(\n new Date(iso),\n );\n\n/** Sentence-case a machine word: \"checked_in\" → \"Checked in\". */\nexport const humanize = (s: string) => {\n const t = s.replace(/[_-]+/g, ' ').trim();\n return t ? t[0].toUpperCase() + t.slice(1) : '–';\n};\n\nexport type PillTone =\n 'success' | 'pending' | 'failed' | 'processing' | 'neutral';\n\nconst TONE_OF: Record<string, PillTone> = {\n completed: 'success',\n paid: 'success',\n success: 'success',\n settled: 'success',\n fulfilled: 'success',\n delivered: 'success',\n collected: 'success',\n pending: 'pending',\n unpaid: 'pending',\n awaiting_confirmation: 'pending',\n authorized: 'pending',\n unfulfilled: 'pending',\n partial: 'pending',\n confirmed: 'processing',\n processing: 'processing',\n checked_in: 'processing',\n ready: 'processing',\n failed: 'failed',\n reversed: 'failed',\n expired: 'failed',\n cancelled: 'neutral',\n refunded: 'neutral',\n partially_refunded: 'neutral',\n};\n\nexport const toneOf = (status: string): PillTone =>\n TONE_OF[status?.toLowerCase()] ?? 'neutral';\n\nexport type StockState = {\n label: string;\n tone: PillTone;\n available: number | null;\n};\n\n/** What a shelf looks like for one item, from the counts the API sends. */\nexport function stockOf(p: TenantProduct): StockState {\n if (!p.trackInventory || p.quantity == null)\n return { label: 'Not tracked', tone: 'neutral', available: null };\n\n const available = p.quantity - (p.reserved ?? 0);\n\n if (available <= 0)\n return { label: 'Out of stock', tone: 'failed', available };\n\n if (p.lowStockThreshold != null && available <= p.lowStockThreshold)\n return { label: `Low · ${available}`, tone: 'pending', available };\n\n return {\n label: `In stock · ${compact(available)}`,\n tone: 'success',\n available,\n };\n}\n"],"mappings":"AAGO,SAAS,QAAQ,UAAkB,WAAW,GAAG;AACtD,SAAO,CAAC,MAAc;AACpB,QAAI;AACF,aAAO,IAAI,KAAK,aAAa,SAAS;AAAA,QACpC,OAAO;AAAA,QACP,iBAAiB;AAAA,QACjB,UAAU,YAAY;AAAA,QACtB,uBAAuB;AAAA,MACzB,CAAC,EAAE,OAAO,CAAC;AAAA,IACb,QAAQ;AACN,aAAO,GAAG,QAAQ,IAAI,KAAK,MAAM,CAAC,EAAE,eAAe,CAAC;AAAA,IACtD;AAAA,EACF;AACF;AAKO,MAAM,UAAU,CAAC,MACtB,IAAI,KAAK,aAAa,SAAS;AAAA,EAC7B,UAAU;AAAA,EACV,uBAAuB;AACzB,CAAC,EAAE,OAAO,CAAC;AAEN,MAAM,YAAY,CAAC,QACxB,IAAI,KAAK,eAAe,MAAM,EAAE,OAAO,SAAS,KAAK,UAAU,CAAC,EAAE;AAAA,EAChE,IAAI,KAAK,GAAG;AACd;AAGK,MAAM,WAAW,CAAC,MAAc;AACrC,QAAM,IAAI,EAAE,QAAQ,UAAU,GAAG,EAAE,KAAK;AACxC,SAAO,IAAI,EAAE,CAAC,EAAE,YAAY,IAAI,EAAE,MAAM,CAAC,IAAI;AAC/C;AAKA,MAAM,UAAoC;AAAA,EACxC,WAAW;AAAA,EACX,MAAM;AAAA,EACN,SAAS;AAAA,EACT,SAAS;AAAA,EACT,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,uBAAuB;AAAA,EACvB,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,SAAS;AAAA,EACT,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,SAAS;AAAA,EACT,WAAW;AAAA,EACX,UAAU;AAAA,EACV,oBAAoB;AACtB;AAEO,MAAM,SAAS,CAAC,WACrB,QAAQ,QAAQ,YAAY,CAAC,KAAK;AAS7B,SAAS,QAAQ,GAA8B;AACpD,MAAI,CAAC,EAAE,kBAAkB,EAAE,YAAY;AACrC,WAAO,EAAE,OAAO,eAAe,MAAM,WAAW,WAAW,KAAK;AAElE,QAAM,YAAY,EAAE,YAAY,EAAE,YAAY;AAE9C,MAAI,aAAa;AACf,WAAO,EAAE,OAAO,gBAAgB,MAAM,UAAU,UAAU;AAE5D,MAAI,EAAE,qBAAqB,QAAQ,aAAa,EAAE;AAChD,WAAO,EAAE,OAAO,YAAS,SAAS,IAAI,MAAM,WAAW,UAAU;AAEnE,SAAO;AAAA,IACL,OAAO,iBAAc,QAAQ,SAAS,CAAC;AAAA,IACvC,MAAM;AAAA,IACN;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../../../src/admin/tenants/%5Bid%5D/commerce/format.ts"],"sourcesContent":["import type { TenantProduct } from '@withpotter/lib/admin-api';\n\n/** Amounts in the merchant's own currency, whole units for headlines. */\nexport function moneyIn(currency: string, decimals = 0) {\n return (n: number) => {\n try {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: currency || 'USD',\n maximumFractionDigits: decimals,\n }).format(n);\n } catch {\n return `${currency} ${Math.round(n).toLocaleString()}`;\n }\n };\n}\n\nexport type Money = (n: number) => string;\n\n/** 1.2k / 3.4m, for figures in tight tiles. */\nexport const compact = (n: number) =>\n new Intl.NumberFormat('en-US', {\n notation: 'compact',\n maximumFractionDigits: 1,\n }).format(n);\n\nexport const shortDate = (iso: string) =>\n new Intl.DateTimeFormat('en', { month: 'short', day: 'numeric' }).format(\n new Date(iso),\n );\n\n/** Sentence-case a machine word: \"checked_in\" → \"Checked in\". */\nexport const humanize = (s: string) => {\n const t = s.replace(/[_-]+/g, ' ').trim();\n return t ? t[0].toUpperCase() + t.slice(1) : '–';\n};\n\nexport type PillTone =\n 'success' | 'pending' | 'failed' | 'processing' | 'neutral';\n\nconst TONE_OF: Record<string, PillTone> = {\n completed: 'success',\n paid: 'success',\n success: 'success',\n settled: 'success',\n fulfilled: 'success',\n delivered: 'success',\n collected: 'success',\n pending: 'pending',\n unpaid: 'pending',\n awaiting_confirmation: 'pending',\n authorized: 'pending',\n unfulfilled: 'pending',\n partial: 'pending',\n confirmed: 'processing',\n processing: 'processing',\n checked_in: 'processing',\n ready: 'processing',\n failed: 'failed',\n reversed: 'failed',\n expired: 'failed',\n cancelled: 'neutral',\n refunded: 'neutral',\n partially_refunded: 'neutral',\n};\n\nexport const toneOf = (status: string): PillTone =>\n TONE_OF[status?.toLowerCase()] ?? 'neutral';\n\nexport type StockState = {\n label: string;\n tone: PillTone;\n available: number | null;\n};\n\n/** What a shelf looks like for one item, from the counts the API sends. */\nexport function stockOf(p: TenantProduct): StockState {\n if (!p.trackInventory || p.quantity == null)\n return { label: 'Not tracked', tone: 'neutral', available: null };\n\n const available = p.quantity - (p.reserved ?? 0);\n\n if (available <= 0)\n return { label: 'Out of stock', tone: 'failed', available };\n\n if (p.lowStockThreshold != null && available <= p.lowStockThreshold)\n return { label: `Low · ${available}`, tone: 'pending', available };\n\n return {\n label: `In stock · ${compact(available)}`,\n tone: 'success',\n available,\n };\n}\n"],"mappings":"AAGO,SAAS,QAAQ,UAAkB,WAAW,GAAG;AACtD,SAAO,CAAC,MAAc;AACpB,QAAI;AACF,aAAO,IAAI,KAAK,aAAa,SAAS;AAAA,QACpC,OAAO;AAAA,QACP,UAAU,YAAY;AAAA,QACtB,uBAAuB;AAAA,MACzB,CAAC,EAAE,OAAO,CAAC;AAAA,IACb,QAAQ;AACN,aAAO,GAAG,QAAQ,IAAI,KAAK,MAAM,CAAC,EAAE,eAAe,CAAC;AAAA,IACtD;AAAA,EACF;AACF;AAKO,MAAM,UAAU,CAAC,MACtB,IAAI,KAAK,aAAa,SAAS;AAAA,EAC7B,UAAU;AAAA,EACV,uBAAuB;AACzB,CAAC,EAAE,OAAO,CAAC;AAEN,MAAM,YAAY,CAAC,QACxB,IAAI,KAAK,eAAe,MAAM,EAAE,OAAO,SAAS,KAAK,UAAU,CAAC,EAAE;AAAA,EAChE,IAAI,KAAK,GAAG;AACd;AAGK,MAAM,WAAW,CAAC,MAAc;AACrC,QAAM,IAAI,EAAE,QAAQ,UAAU,GAAG,EAAE,KAAK;AACxC,SAAO,IAAI,EAAE,CAAC,EAAE,YAAY,IAAI,EAAE,MAAM,CAAC,IAAI;AAC/C;AAKA,MAAM,UAAoC;AAAA,EACxC,WAAW;AAAA,EACX,MAAM;AAAA,EACN,SAAS;AAAA,EACT,SAAS;AAAA,EACT,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,uBAAuB;AAAA,EACvB,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,SAAS;AAAA,EACT,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,SAAS;AAAA,EACT,WAAW;AAAA,EACX,UAAU;AAAA,EACV,oBAAoB;AACtB;AAEO,MAAM,SAAS,CAAC,WACrB,QAAQ,QAAQ,YAAY,CAAC,KAAK;AAS7B,SAAS,QAAQ,GAA8B;AACpD,MAAI,CAAC,EAAE,kBAAkB,EAAE,YAAY;AACrC,WAAO,EAAE,OAAO,eAAe,MAAM,WAAW,WAAW,KAAK;AAElE,QAAM,YAAY,EAAE,YAAY,EAAE,YAAY;AAE9C,MAAI,aAAa;AACf,WAAO,EAAE,OAAO,gBAAgB,MAAM,UAAU,UAAU;AAE5D,MAAI,EAAE,qBAAqB,QAAQ,aAAa,EAAE;AAChD,WAAO,EAAE,OAAO,YAAS,SAAS,IAAI,MAAM,WAAW,UAAU;AAEnE,SAAO;AAAA,IACL,OAAO,iBAAc,QAAQ,SAAS,CAAC;AAAA,IACvC,MAAM;AAAA,IACN;AAAA,EACF;AACF;","names":[]}
@@ -39,7 +39,7 @@ function RecentOrders({
39
39
  /* @__PURE__ */ jsx(Th, { children: "Customer" }),
40
40
  /* @__PURE__ */ jsx(Th, { align: "right", children: "Total" }),
41
41
  /* @__PURE__ */ jsx(Th, { children: "Payment" }),
42
- /* @__PURE__ */ jsx(Th, { children: "Fulfilment" }),
42
+ /* @__PURE__ */ jsx(Th, { children: "Status" }),
43
43
  /* @__PURE__ */ jsx(Th, { align: "right", children: "Placed" })
44
44
  ] }),
45
45
  children: loading ? Array.from({ length: 5 }, (_, i) => /* @__PURE__ */ jsx("tr", { "aria-hidden": true, children: /* @__PURE__ */ jsx("td", { colSpan: 6, children: /* @__PURE__ */ jsx(Skeleton, { height: 20 }) }) }, i)) : orders.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: 6, className: styles.emptyCell, children: "No orders yet. They show here as soon as a customer checks out." }) }) : orders.map((o) => /* @__PURE__ */ jsxs(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/admin/tenants/%5Bid%5D/commerce/recent-orders.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport type {\n DashboardStats,\n TenantActivityOrder,\n Transaction,\n} from '@withpotter/lib/admin-api';\nimport { TableRow, TableShell, Th } from '@withpotter/ui-kit';\nimport { Panel, Skeleton } from '../../../_ui';\nimport { moneyIn, shortDate } from './format';\nimport { OrderDrawer } from './order-drawer';\nimport { OrderPipeline } from './order-pipeline';\nimport { StatusPill } from './status-pill';\nimport styles from './orders.module.css';\n\n/** The order pipeline over the latest orders; a row opens the order. */\nexport function RecentOrders({\n orders,\n stats,\n transactions,\n loading,\n statsLoading,\n currency,\n}: {\n orders: TenantActivityOrder[];\n stats: DashboardStats['orderStats'] | undefined;\n transactions: Transaction[];\n loading: boolean;\n statsLoading: boolean;\n currency: string;\n}) {\n const [open, setOpen] = useState<TenantActivityOrder | null>(null);\n\n return (\n <>\n <Panel\n title=\"Orders\"\n count={stats?.total}\n caption={stats ? `${stats.today.toLocaleString()} today` : undefined}\n height=\"calc(var(--adm-panel-h) + 120px)\"\n flush\n >\n <OrderPipeline stats={stats} loading={statsLoading} />\n <TableShell\n bare\n minWidth={640}\n density=\"compact\"\n head={\n <tr>\n <Th>Order</Th>\n <Th>Customer</Th>\n <Th align=\"right\">Total</Th>\n <Th>Payment</Th>\n <Th>Fulfilment</Th>\n <Th align=\"right\">Placed</Th>\n </tr>\n }\n >\n {loading ? (\n Array.from({ length: 5 }, (_, i) => (\n <tr key={i} aria-hidden>\n <td colSpan={6}>\n <Skeleton height={20} />\n </td>\n </tr>\n ))\n ) : orders.length === 0 ? (\n <tr>\n <td colSpan={6} className={styles.emptyCell}>\n No orders yet. They show here as soon as a customer checks out.\n </td>\n </tr>\n ) : (\n orders.map((o) => (\n <TableRow\n key={o.id}\n active={open?.id === o.id}\n onClick={() => setOpen(o)}\n >\n <td className={styles.mono} title={o.orderNumber}>\n {/* The row opens on click; this is its keyboard way in. */}\n <button\n type=\"button\"\n className={styles.orderLink}\n onClick={(e) => {\n e.stopPropagation();\n setOpen(o);\n }}\n >\n {o.orderNumber}\n </button>\n </td>\n <td>\n <span\n className={styles.truncate}\n title={o.customerName || o.customerEmail}\n >\n {o.customerName || o.customerEmail}\n </span>\n </td>\n <td className={styles.num}>\n {moneyIn(o.currency || currency, 2)(o.total)}\n </td>\n <td>\n <StatusPill status={o.paymentStatus} />\n </td>\n <td>\n <StatusPill status={o.status} />\n </td>\n <td className={styles.num}>{shortDate(o.createdAt)}</td>\n </TableRow>\n ))\n )}\n </TableShell>\n </Panel>\n <OrderDrawer\n order={open}\n transactions={transactions}\n currency={currency}\n onClose={() => setOpen(null)}\n />\n </>\n );\n}\n"],"mappings":";AAmCI,mBAQI,KAMI,YAdR;AAjCJ,SAAS,gBAAgB;AAMzB,SAAS,UAAU,YAAY,UAAU;AACzC,SAAS,OAAO,gBAAgB;AAChC,SAAS,SAAS,iBAAiB;AACnC,SAAS,mBAAmB;AAC5B,SAAS,qBAAqB;AAC9B,SAAS,kBAAkB;AAC3B,OAAO,YAAY;AAGZ,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAOG;AACD,QAAM,CAAC,MAAM,OAAO,IAAI,SAAqC,IAAI;AAEjE,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,OAAO,OAAO;AAAA,QACd,SAAS,QAAQ,GAAG,MAAM,MAAM,eAAe,CAAC,WAAW;AAAA,QAC3D,QAAO;AAAA,QACP,OAAK;AAAA,QAEL;AAAA,8BAAC,iBAAc,OAAc,SAAS,cAAc;AAAA,UACpD;AAAA,YAAC;AAAA;AAAA,cACC,MAAI;AAAA,cACJ,UAAU;AAAA,cACV,SAAQ;AAAA,cACR,MACE,qBAAC,QACC;AAAA,oCAAC,MAAG,mBAAK;AAAA,gBACT,oBAAC,MAAG,sBAAQ;AAAA,gBACZ,oBAAC,MAAG,OAAM,SAAQ,mBAAK;AAAA,gBACvB,oBAAC,MAAG,qBAAO;AAAA,gBACX,oBAAC,MAAG,wBAAU;AAAA,gBACd,oBAAC,MAAG,OAAM,SAAQ,oBAAM;AAAA,iBAC1B;AAAA,cAGD,oBACC,MAAM,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,GAAG,MAC5B,oBAAC,QAAW,eAAW,MACrB,8BAAC,QAAG,SAAS,GACX,8BAAC,YAAS,QAAQ,IAAI,GACxB,KAHO,CAIT,CACD,IACC,OAAO,WAAW,IACpB,oBAAC,QACC,8BAAC,QAAG,SAAS,GAAG,WAAW,OAAO,WAAW,6EAE7C,GACF,IAEA,OAAO,IAAI,CAAC,MACV;AAAA,gBAAC;AAAA;AAAA,kBAEC,QAAQ,MAAM,OAAO,EAAE;AAAA,kBACvB,SAAS,MAAM,QAAQ,CAAC;AAAA,kBAExB;AAAA,wCAAC,QAAG,WAAW,OAAO,MAAM,OAAO,EAAE,aAEnC;AAAA,sBAAC;AAAA;AAAA,wBACC,MAAK;AAAA,wBACL,WAAW,OAAO;AAAA,wBAClB,SAAS,CAAC,MAAM;AACd,4BAAE,gBAAgB;AAClB,kCAAQ,CAAC;AAAA,wBACX;AAAA,wBAEC,YAAE;AAAA;AAAA,oBACL,GACF;AAAA,oBACA,oBAAC,QACC;AAAA,sBAAC;AAAA;AAAA,wBACC,WAAW,OAAO;AAAA,wBAClB,OAAO,EAAE,gBAAgB,EAAE;AAAA,wBAE1B,YAAE,gBAAgB,EAAE;AAAA;AAAA,oBACvB,GACF;AAAA,oBACA,oBAAC,QAAG,WAAW,OAAO,KACnB,kBAAQ,EAAE,YAAY,UAAU,CAAC,EAAE,EAAE,KAAK,GAC7C;AAAA,oBACA,oBAAC,QACC,8BAAC,cAAW,QAAQ,EAAE,eAAe,GACvC;AAAA,oBACA,oBAAC,QACC,8BAAC,cAAW,QAAQ,EAAE,QAAQ,GAChC;AAAA,oBACA,oBAAC,QAAG,WAAW,OAAO,KAAM,oBAAU,EAAE,SAAS,GAAE;AAAA;AAAA;AAAA,gBAlC9C,EAAE;AAAA,cAmCT,CACD;AAAA;AAAA,UAEL;AAAA;AAAA;AAAA,IACF;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO;AAAA,QACP;AAAA,QACA;AAAA,QACA,SAAS,MAAM,QAAQ,IAAI;AAAA;AAAA,IAC7B;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../../../src/admin/tenants/%5Bid%5D/commerce/recent-orders.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport type {\n DashboardStats,\n TenantActivityOrder,\n Transaction,\n} from '@withpotter/lib/admin-api';\nimport { TableRow, TableShell, Th } from '@withpotter/ui-kit';\nimport { Panel, Skeleton } from '../../../_ui';\nimport { moneyIn, shortDate } from './format';\nimport { OrderDrawer } from './order-drawer';\nimport { OrderPipeline } from './order-pipeline';\nimport { StatusPill } from './status-pill';\nimport styles from './orders.module.css';\n\n/** The order pipeline over the latest orders; a row opens the order. */\nexport function RecentOrders({\n orders,\n stats,\n transactions,\n loading,\n statsLoading,\n currency,\n}: {\n orders: TenantActivityOrder[];\n stats: DashboardStats['orderStats'] | undefined;\n transactions: Transaction[];\n loading: boolean;\n statsLoading: boolean;\n currency: string;\n}) {\n const [open, setOpen] = useState<TenantActivityOrder | null>(null);\n\n return (\n <>\n <Panel\n title=\"Orders\"\n count={stats?.total}\n caption={stats ? `${stats.today.toLocaleString()} today` : undefined}\n height=\"calc(var(--adm-panel-h) + 120px)\"\n flush\n >\n <OrderPipeline stats={stats} loading={statsLoading} />\n <TableShell\n bare\n minWidth={640}\n density=\"compact\"\n head={\n <tr>\n <Th>Order</Th>\n <Th>Customer</Th>\n <Th align=\"right\">Total</Th>\n <Th>Payment</Th>\n <Th>Status</Th>\n <Th align=\"right\">Placed</Th>\n </tr>\n }\n >\n {loading ? (\n Array.from({ length: 5 }, (_, i) => (\n <tr key={i} aria-hidden>\n <td colSpan={6}>\n <Skeleton height={20} />\n </td>\n </tr>\n ))\n ) : orders.length === 0 ? (\n <tr>\n <td colSpan={6} className={styles.emptyCell}>\n No orders yet. They show here as soon as a customer checks out.\n </td>\n </tr>\n ) : (\n orders.map((o) => (\n <TableRow\n key={o.id}\n active={open?.id === o.id}\n onClick={() => setOpen(o)}\n >\n <td className={styles.mono} title={o.orderNumber}>\n {/* The row opens on click; this is its keyboard way in. */}\n <button\n type=\"button\"\n className={styles.orderLink}\n onClick={(e) => {\n e.stopPropagation();\n setOpen(o);\n }}\n >\n {o.orderNumber}\n </button>\n </td>\n <td>\n <span\n className={styles.truncate}\n title={o.customerName || o.customerEmail}\n >\n {o.customerName || o.customerEmail}\n </span>\n </td>\n <td className={styles.num}>\n {moneyIn(o.currency || currency, 2)(o.total)}\n </td>\n <td>\n <StatusPill status={o.paymentStatus} />\n </td>\n <td>\n <StatusPill status={o.status} />\n </td>\n <td className={styles.num}>{shortDate(o.createdAt)}</td>\n </TableRow>\n ))\n )}\n </TableShell>\n </Panel>\n <OrderDrawer\n order={open}\n transactions={transactions}\n currency={currency}\n onClose={() => setOpen(null)}\n />\n </>\n );\n}\n"],"mappings":";AAmCI,mBAQI,KAMI,YAdR;AAjCJ,SAAS,gBAAgB;AAMzB,SAAS,UAAU,YAAY,UAAU;AACzC,SAAS,OAAO,gBAAgB;AAChC,SAAS,SAAS,iBAAiB;AACnC,SAAS,mBAAmB;AAC5B,SAAS,qBAAqB;AAC9B,SAAS,kBAAkB;AAC3B,OAAO,YAAY;AAGZ,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAOG;AACD,QAAM,CAAC,MAAM,OAAO,IAAI,SAAqC,IAAI;AAEjE,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,OAAO,OAAO;AAAA,QACd,SAAS,QAAQ,GAAG,MAAM,MAAM,eAAe,CAAC,WAAW;AAAA,QAC3D,QAAO;AAAA,QACP,OAAK;AAAA,QAEL;AAAA,8BAAC,iBAAc,OAAc,SAAS,cAAc;AAAA,UACpD;AAAA,YAAC;AAAA;AAAA,cACC,MAAI;AAAA,cACJ,UAAU;AAAA,cACV,SAAQ;AAAA,cACR,MACE,qBAAC,QACC;AAAA,oCAAC,MAAG,mBAAK;AAAA,gBACT,oBAAC,MAAG,sBAAQ;AAAA,gBACZ,oBAAC,MAAG,OAAM,SAAQ,mBAAK;AAAA,gBACvB,oBAAC,MAAG,qBAAO;AAAA,gBACX,oBAAC,MAAG,oBAAM;AAAA,gBACV,oBAAC,MAAG,OAAM,SAAQ,oBAAM;AAAA,iBAC1B;AAAA,cAGD,oBACC,MAAM,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,GAAG,MAC5B,oBAAC,QAAW,eAAW,MACrB,8BAAC,QAAG,SAAS,GACX,8BAAC,YAAS,QAAQ,IAAI,GACxB,KAHO,CAIT,CACD,IACC,OAAO,WAAW,IACpB,oBAAC,QACC,8BAAC,QAAG,SAAS,GAAG,WAAW,OAAO,WAAW,6EAE7C,GACF,IAEA,OAAO,IAAI,CAAC,MACV;AAAA,gBAAC;AAAA;AAAA,kBAEC,QAAQ,MAAM,OAAO,EAAE;AAAA,kBACvB,SAAS,MAAM,QAAQ,CAAC;AAAA,kBAExB;AAAA,wCAAC,QAAG,WAAW,OAAO,MAAM,OAAO,EAAE,aAEnC;AAAA,sBAAC;AAAA;AAAA,wBACC,MAAK;AAAA,wBACL,WAAW,OAAO;AAAA,wBAClB,SAAS,CAAC,MAAM;AACd,4BAAE,gBAAgB;AAClB,kCAAQ,CAAC;AAAA,wBACX;AAAA,wBAEC,YAAE;AAAA;AAAA,oBACL,GACF;AAAA,oBACA,oBAAC,QACC;AAAA,sBAAC;AAAA;AAAA,wBACC,WAAW,OAAO;AAAA,wBAClB,OAAO,EAAE,gBAAgB,EAAE;AAAA,wBAE1B,YAAE,gBAAgB,EAAE;AAAA;AAAA,oBACvB,GACF;AAAA,oBACA,oBAAC,QAAG,WAAW,OAAO,KACnB,kBAAQ,EAAE,YAAY,UAAU,CAAC,EAAE,EAAE,KAAK,GAC7C;AAAA,oBACA,oBAAC,QACC,8BAAC,cAAW,QAAQ,EAAE,eAAe,GACvC;AAAA,oBACA,oBAAC,QACC,8BAAC,cAAW,QAAQ,EAAE,QAAQ,GAChC;AAAA,oBACA,oBAAC,QAAG,WAAW,OAAO,KAAM,oBAAU,EAAE,SAAS,GAAE;AAAA;AAAA;AAAA,gBAlC9C,EAAE;AAAA,cAmCT,CACD;AAAA;AAAA,UAEL;AAAA;AAAA;AAAA,IACF;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO;AAAA,QACP;AAAA,QACA;AAAA,QACA,SAAS,MAAM,QAAQ,IAAI;AAAA;AAAA,IAC7B;AAAA,KACF;AAEJ;","names":[]}