@withpotter/pages-admin 0.13.0-next.124 → 0.13.0-next.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/_money/cash-postings.js +3 -20
- package/dist/admin/_money/cash-postings.js.map +1 -1
- package/dist/admin/_money/cash.module.css +0 -10
- package/dist/admin/_money/finder-format.js +0 -1
- package/dist/admin/_money/finder-format.js.map +1 -1
- package/dist/admin/_money/format.js +0 -1
- package/dist/admin/_money/format.js.map +1 -1
- package/dist/admin/_overview/activation-panel.js +6 -6
- package/dist/admin/_overview/activation-panel.js.map +1 -1
- package/dist/admin/_overview/attention-groups.js +8 -8
- package/dist/admin/_overview/attention-groups.js.map +1 -1
- package/dist/admin/_overview/attention-panel.js +33 -37
- package/dist/admin/_overview/attention-panel.js.map +1 -1
- package/dist/admin/_overview/attention.module.css +3 -0
- package/dist/admin/_overview/control-room.js +13 -41
- package/dist/admin/_overview/control-room.js.map +1 -1
- package/dist/admin/_overview/kpi-row.js +7 -7
- package/dist/admin/_overview/kpi-row.js.map +1 -1
- package/dist/admin/_overview/leaders-panel.js +1 -1
- package/dist/admin/_overview/leaders-panel.js.map +1 -1
- package/dist/admin/_overview/overdue-outstanding.js +1 -2
- package/dist/admin/_overview/overdue-outstanding.js.map +1 -1
- package/dist/admin/_overview/portfolio-panel.js +5 -5
- package/dist/admin/_overview/portfolio-panel.js.map +1 -1
- package/dist/admin/_overview/position-panel.js +12 -8
- package/dist/admin/_overview/position-panel.js.map +1 -1
- package/dist/admin/_pos/pos-figures.js +5 -18
- package/dist/admin/_pos/pos-figures.js.map +1 -1
- package/dist/admin/_pos/pos-rules.js +0 -2
- package/dist/admin/_pos/pos-rules.js.map +1 -1
- package/dist/admin/_pos/pos-words.js +1 -6
- package/dist/admin/_pos/pos-words.js.map +1 -1
- package/dist/admin/_pos/use-pos.js +1 -70
- package/dist/admin/_pos/use-pos.js.map +1 -1
- package/dist/admin/_shell/nav-config.js +3 -3
- package/dist/admin/_shell/nav-config.js.map +1 -1
- package/dist/admin/_shell/nav-filter.js +2 -4
- package/dist/admin/_shell/nav-filter.js.map +1 -1
- package/dist/admin/_shell/nav-row.js +2 -5
- package/dist/admin/_shell/nav-row.js.map +1 -1
- package/dist/admin/_shell/sidebar-footer.js +0 -20
- package/dist/admin/_shell/sidebar-footer.js.map +1 -1
- package/dist/admin/_shell/sidebar-footer.module.css +0 -17
- package/dist/admin/_shell/sidebar.js +15 -25
- package/dist/admin/_shell/sidebar.js.map +1 -1
- package/dist/admin/_shell/sidebar.module.css +0 -69
- package/dist/admin/_shell/use-fluid-hover.js +6 -37
- package/dist/admin/_shell/use-fluid-hover.js.map +1 -1
- package/dist/admin/_ui/admin.css +0 -7
- package/dist/admin/_ui/data-table.js +1 -1
- package/dist/admin/_ui/data-table.js.map +1 -1
- package/dist/admin/_ui/list-card.js +1 -1
- package/dist/admin/_ui/list-card.js.map +1 -1
- package/dist/admin/_ui/list-card.module.css +16 -2
- package/dist/admin/_ui/money-view.js +0 -1
- package/dist/admin/_ui/money-view.js.map +1 -1
- package/dist/admin/_ui/page-header.js +4 -31
- package/dist/admin/_ui/page-header.js.map +1 -1
- package/dist/admin/_ui/panel.module.css +10 -0
- package/dist/admin/_ui/stat-card.js +2 -11
- package/dist/admin/_ui/stat-card.js.map +1 -1
- package/dist/admin/_ui/trend.js +2 -2
- package/dist/admin/_ui/trend.js.map +1 -1
- package/dist/admin/accounts/account-panel.js +6 -49
- package/dist/admin/accounts/account-panel.js.map +1 -1
- package/dist/admin/accounts/account-panel.module.css +0 -73
- package/dist/admin/accounts/account-row.js +2 -11
- package/dist/admin/accounts/account-row.js.map +1 -1
- package/dist/admin/accounts/account-vitals.js +13 -15
- package/dist/admin/accounts/account-vitals.js.map +1 -1
- package/dist/admin/accounts/accounts-table.js +8 -39
- package/dist/admin/accounts/accounts-table.js.map +1 -1
- package/dist/admin/accounts/accounts-workspace.js +2 -2
- package/dist/admin/accounts/accounts-workspace.js.map +1 -1
- package/dist/admin/accounts/owner-session.js +6 -0
- package/dist/admin/accounts/owner-session.js.map +1 -1
- package/dist/admin/accounts/rows.module.css +0 -17
- package/dist/admin/audit/status-pill.js +0 -6
- package/dist/admin/audit/status-pill.js.map +1 -1
- package/dist/admin/audit/status-pill.module.css +0 -4
- package/dist/admin/commerce/commerce-overview.js +2 -2
- package/dist/admin/commerce/commerce-overview.js.map +1 -1
- package/dist/admin/commerce/merchant-meta.js +0 -1
- package/dist/admin/commerce/merchant-meta.js.map +1 -1
- package/dist/admin/commerce/merchant-view.js +2 -2
- package/dist/admin/commerce/merchant-view.js.map +1 -1
- package/dist/admin/feedback/prompt-operations.module.css +2 -0
- package/dist/admin/feedback/prompt-preview.module.css +2 -0
- package/dist/admin/hooks.js +1 -57
- package/dist/admin/hooks.js.map +1 -1
- package/dist/admin/layout.js +2 -8
- package/dist/admin/layout.js.map +1 -1
- package/dist/admin/page.js +8 -28
- package/dist/admin/page.js.map +1 -1
- package/dist/admin/payout-tiers/_tier-dialog.js +21 -31
- package/dist/admin/payout-tiers/_tier-dialog.js.map +1 -1
- package/dist/admin/payout-tiers/tier-preview-result.js +9 -10
- package/dist/admin/payout-tiers/tier-preview-result.js.map +1 -1
- package/dist/admin/payout-tiers/tiers-table.js +7 -6
- package/dist/admin/payout-tiers/tiers-table.js.map +1 -1
- package/dist/admin/payout-tiers/tiers-workspace.js +2 -2
- package/dist/admin/payout-tiers/tiers-workspace.js.map +1 -1
- package/dist/admin/pos/pos-workspace.js +2 -6
- package/dist/admin/pos/pos-workspace.js.map +1 -1
- package/dist/admin/pos/shifts-tab.js +9 -16
- package/dist/admin/pos/shifts-tab.js.map +1 -1
- package/dist/admin/revenue/cohort-panel.js +6 -6
- package/dist/admin/revenue/cohort-panel.js.map +1 -1
- package/dist/admin/revenue/health-panel.js +21 -22
- package/dist/admin/revenue/health-panel.js.map +1 -1
- package/dist/admin/revenue/plan-mix-panel.js +3 -3
- package/dist/admin/revenue/plan-mix-panel.js.map +1 -1
- package/dist/admin/revenue/quality-panel.js +19 -19
- package/dist/admin/revenue/quality-panel.js.map +1 -1
- package/dist/admin/revenue/revenue-kpis.js +50 -111
- package/dist/admin/revenue/revenue-kpis.js.map +1 -1
- package/dist/admin/revenue/revenue-overview.js +46 -39
- package/dist/admin/revenue/revenue-overview.js.map +1 -1
- package/dist/admin/revenue/revenue-retention.js +13 -17
- package/dist/admin/revenue/revenue-retention.js.map +1 -1
- package/dist/admin/revenue/revenue-workspace.js +6 -53
- package/dist/admin/revenue/revenue-workspace.js.map +1 -1
- package/dist/admin/revenue/subscriptions-panel.js +14 -14
- package/dist/admin/revenue/subscriptions-panel.js.map +1 -1
- package/dist/admin/revenue/top-tenants-panel.js +4 -4
- package/dist/admin/revenue/top-tenants-panel.js.map +1 -1
- package/dist/admin/settings/account-tab.js +25 -1
- package/dist/admin/settings/account-tab.js.map +1 -1
- package/dist/admin/settings/general-tab.js +2 -2
- package/dist/admin/settings/general-tab.js.map +1 -1
- package/dist/admin/settings/system-status-card.js +30 -0
- package/dist/admin/settings/system-status-card.js.map +1 -0
- package/dist/admin/support/queue-card.module.css +4 -1
- package/dist/admin/tenants/[id]/commerce/format.js +1 -2
- package/dist/admin/tenants/[id]/commerce/format.js.map +1 -1
- package/dist/admin/tenants/[id]/commerce/recent-orders.js +1 -1
- package/dist/admin/tenants/[id]/commerce/recent-orders.js.map +1 -1
- package/dist/admin/tenants/[id]/page.js +10 -37
- package/dist/admin/tenants/[id]/page.js.map +1 -1
- package/dist/admin/tenants/[id]/tenant-layout.module.css +4 -1
- package/dist/admin/tenants/[id]/tenant-overview.js +2 -3
- package/dist/admin/tenants/[id]/tenant-overview.js.map +1 -1
- package/dist/admin/tenants/[id]/tenant-overview.module.css +1 -1
- package/dist/admin/tenants/[id]/tenant-pos.js +16 -64
- package/dist/admin/tenants/[id]/tenant-pos.js.map +1 -1
- package/dist/admin/tenants/[id]/tenant-wallet-card.js +2 -3
- package/dist/admin/tenants/[id]/tenant-wallet-card.js.map +1 -1
- package/dist/admin/tenants/[id]/verification/document-viewer.js +52 -3
- package/dist/admin/tenants/[id]/verification/document-viewer.js.map +1 -1
- package/dist/admin/tenants/[id]/verification/document-viewer.module.css +0 -51
- package/dist/admin/tenants/[id]/verification/tenant-verification.js +0 -10
- package/dist/admin/tenants/[id]/verification/tenant-verification.js.map +1 -1
- package/dist/admin/tenants/tenant-list-row.js +1 -2
- package/dist/admin/tenants/tenant-list-row.js.map +1 -1
- package/dist/admin/tenants/tenants-table.js +2 -3
- package/dist/admin/tenants/tenants-table.js.map +1 -1
- package/dist/admin/tenants/tenants-workspace.js +2 -2
- package/dist/admin/tenants/tenants-workspace.js.map +1 -1
- package/dist/admin/verification/verification-page.js +3 -19
- package/dist/admin/verification/verification-page.js.map +1 -1
- package/dist/admin/verification/verification.module.css +0 -10
- package/dist/admin/verification/waiting-case-actions.js +68 -0
- package/dist/admin/verification/waiting-case-actions.js.map +1 -0
- package/dist/admin/verification/waiting-cases.js +48 -66
- package/dist/admin/verification/waiting-cases.js.map +1 -1
- package/package.json +5 -5
- package/dist/admin/_pos/olive-balance.js +0 -40
- package/dist/admin/_pos/olive-balance.js.map +0 -1
- package/dist/admin/_pos/olive-state.js +0 -23
- package/dist/admin/_pos/olive-state.js.map +0 -1
- package/dist/admin/_pos/payments-model.js +0 -55
- package/dist/admin/_pos/payments-model.js.map +0 -1
- package/dist/admin/_pos/tenant-operators.js +0 -63
- package/dist/admin/_pos/tenant-operators.js.map +0 -1
- package/dist/admin/_pos/tenant-outlets.js +0 -77
- package/dist/admin/_pos/tenant-outlets.js.map +0 -1
- package/dist/admin/_pos/tenant-pos-figures.js +0 -82
- package/dist/admin/_pos/tenant-pos-figures.js.map +0 -1
- package/dist/admin/_pos/tenant-requests.js +0 -64
- package/dist/admin/_pos/tenant-requests.js.map +0 -1
- package/dist/admin/_pos/tenant-transactions.js +0 -145
- package/dist/admin/_pos/tenant-transactions.js.map +0 -1
- package/dist/admin/_shell/nav-fold.js +0 -14
- package/dist/admin/_shell/nav-fold.js.map +0 -1
- package/dist/admin/_shell/nav-group.js +0 -78
- package/dist/admin/_shell/nav-group.js.map +0 -1
- package/dist/admin/_shell/use-open-groups.js +0 -64
- package/dist/admin/_shell/use-open-groups.js.map +0 -1
- package/dist/admin/_ui/page-header.module.css +0 -45
- package/dist/admin/accounts/account-sessions.js +0 -70
- package/dist/admin/accounts/account-sessions.js.map +0 -1
- package/dist/admin/accounts/account-sessions.module.css +0 -55
- package/dist/admin/accounts/account-stage-filter.js +0 -29
- package/dist/admin/accounts/account-stage-filter.js.map +0 -1
- package/dist/admin/accounts/account-stage.js +0 -81
- package/dist/admin/accounts/account-stage.js.map +0 -1
- package/dist/admin/accounts/hint.js +0 -12
- package/dist/admin/accounts/hint.js.map +0 -1
- package/dist/admin/payout-tiers/requirement-labels.js +0 -70
- package/dist/admin/payout-tiers/requirement-labels.js.map +0 -1
- package/dist/admin/pos/transactions-tab.js +0 -214
- package/dist/admin/pos/transactions-tab.js.map +0 -1
- package/dist/admin/revenue/channel-panel.js +0 -49
- package/dist/admin/revenue/channel-panel.js.map +0 -1
- package/dist/admin/revenue/channel-rows.js +0 -17
- package/dist/admin/revenue/channel-rows.js.map +0 -1
- package/dist/admin/revenue/download-button.js +0 -40
- package/dist/admin/revenue/download-button.js.map +0 -1
- package/dist/admin/revenue/earnings/earnings-colors.js +0 -25
- package/dist/admin/revenue/earnings/earnings-colors.js.map +0 -1
- package/dist/admin/revenue/earnings/earnings-legend.js +0 -75
- package/dist/admin/revenue/earnings/earnings-legend.js.map +0 -1
- package/dist/admin/revenue/earnings/earnings-summary.js +0 -308
- package/dist/admin/revenue/earnings/earnings-summary.js.map +0 -1
- package/dist/admin/revenue/earnings/earnings-table.js +0 -143
- package/dist/admin/revenue/earnings/earnings-table.js.map +0 -1
- package/dist/admin/revenue/earnings/earnings.module.css +0 -201
- package/dist/admin/revenue/earnings/use-earnings.js +0 -27
- package/dist/admin/revenue/earnings/use-earnings.js.map +0 -1
- package/dist/admin/revenue/ledger-model.js +0 -104
- package/dist/admin/revenue/ledger-model.js.map +0 -1
- package/dist/admin/revenue/ledger.module.css +0 -144
- package/dist/admin/revenue/merchant-headline.js +0 -37
- package/dist/admin/revenue/merchant-headline.js.map +0 -1
- package/dist/admin/revenue/merchant-payments-view.js +0 -234
- package/dist/admin/revenue/merchant-payments-view.js.map +0 -1
- package/dist/admin/revenue/method-cards.js +0 -40
- package/dist/admin/revenue/method-cards.js.map +0 -1
- package/dist/admin/revenue/payment-drawer.js +0 -53
- package/dist/admin/revenue/payment-drawer.js.map +0 -1
- package/dist/admin/revenue/plan-lines.js +0 -36
- package/dist/admin/revenue/plan-lines.js.map +0 -1
- package/dist/admin/revenue/plan-payment-drawer.js +0 -42
- package/dist/admin/revenue/plan-payment-drawer.js.map +0 -1
- package/dist/admin/revenue/plans-panel.js +0 -48
- package/dist/admin/revenue/plans-panel.js.map +0 -1
- package/dist/admin/revenue/platform-fees.js +0 -9
- package/dist/admin/revenue/platform-fees.js.map +0 -1
- package/dist/admin/revenue/print-summary-button.js +0 -27
- package/dist/admin/revenue/print-summary-button.js.map +0 -1
- package/dist/admin/revenue/subscriptions-view.js +0 -213
- package/dist/admin/revenue/subscriptions-view.js.map +0 -1
- package/dist/admin/revenue/summary-download.js +0 -38
- package/dist/admin/revenue/summary-download.js.map +0 -1
- package/dist/admin/revenue/summary-print.js +0 -82
- package/dist/admin/revenue/summary-print.js.map +0 -1
- package/dist/admin/revenue/summary-rows.js +0 -44
- package/dist/admin/revenue/summary-rows.js.map +0 -1
- package/dist/admin/revenue/url-filters.js +0 -36
- package/dist/admin/revenue/url-filters.js.map +0 -1
- package/dist/admin/revenue/use-url-filters.js +0 -24
- package/dist/admin/revenue/use-url-filters.js.map +0 -1
- package/dist/admin/tenants/[id]/use-tenant-wallet.js +0 -23
- package/dist/admin/tenants/[id]/use-tenant-wallet.js.map +0 -1
- package/dist/admin/tenants/[id]/verification/document-file.js +0 -78
- package/dist/admin/tenants/[id]/verification/document-file.js.map +0 -1
- package/dist/admin/tenants/[id]/verification/sent-files.js +0 -33
- package/dist/admin/tenants/[id]/verification/sent-files.js.map +0 -1
- package/dist/admin/verification/preview-dialog.js +0 -111
- package/dist/admin/verification/preview-dialog.js.map +0 -1
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { ADMIN_NAME } from "../_shell/admin-brand";
|
|
3
3
|
import { CountUp, Reveal, TONE } from "../_fx";
|
|
4
4
|
import { MoneyLines, StatCard, rowFor } from "../_ui";
|
|
5
5
|
const integer = new Intl.NumberFormat("en-US");
|
|
6
6
|
function RevenueKpis({
|
|
7
|
-
part,
|
|
8
|
-
extra,
|
|
9
7
|
data,
|
|
10
8
|
money,
|
|
11
9
|
money2,
|
|
@@ -14,116 +12,57 @@ function RevenueKpis({
|
|
|
14
12
|
gmvSpark
|
|
15
13
|
}) {
|
|
16
14
|
const { recurring } = data;
|
|
17
|
-
const tracked = recurring.tracked !== false;
|
|
18
|
-
const notTracked = "Not set up yet";
|
|
19
|
-
const noSource = "The plan payments are not connected yet";
|
|
20
15
|
const gmv = rowFor(data.gmvByCurrency, focus);
|
|
21
16
|
const lifetimeShare = gmv && gmv.lifetime > 0 ? `${Math.round(gmv.inRange / gmv.lifetime * 100)}% of lifetime` : void 0;
|
|
22
|
-
return /* @__PURE__ */ jsxs(
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
value: /* @__PURE__ */ jsx(
|
|
72
|
-
MoneyLines,
|
|
73
|
-
{
|
|
74
|
-
rows: data.gmvByCurrency,
|
|
75
|
-
currency,
|
|
76
|
-
amount: (g) => g.inRange,
|
|
77
|
-
empty: "No sales yet"
|
|
78
|
-
}
|
|
79
|
-
),
|
|
80
|
-
href: "/admin/tenants",
|
|
81
|
-
sub: lifetimeShare,
|
|
82
|
-
hint: `Everything merchants sold through ${ADMIN_NAME}. This is the merchants' money, not ${ADMIN_NAME}'s own income. One line per currency; currencies are never added together. (GMV)`,
|
|
83
|
-
spark: focus ? gmvSpark : void 0,
|
|
84
|
-
sparkColor: TONE.money.fg
|
|
85
|
-
}
|
|
86
|
-
) }),
|
|
87
|
-
extra,
|
|
88
|
-
data.merchantWalletsByCurrency && /* @__PURE__ */ jsx(Reveal, { index: 4, className: "adm-span-3", children: /* @__PURE__ */ jsx(
|
|
89
|
-
StatCard,
|
|
90
|
-
{
|
|
91
|
-
label: "In merchants' wallets",
|
|
92
|
-
value: /* @__PURE__ */ jsx(
|
|
93
|
-
MoneyLines,
|
|
94
|
-
{
|
|
95
|
-
rows: data.merchantWalletsByCurrency,
|
|
96
|
-
currency,
|
|
97
|
-
amount: (r) => r.amount,
|
|
98
|
-
decimals: 2,
|
|
99
|
-
empty: "Nothing held"
|
|
100
|
-
}
|
|
101
|
-
),
|
|
102
|
-
hint: "What merchants hold in their wallets right now: pending, available, held back and on its way to a bank, added together. A balance today, so it does not follow the dates above.",
|
|
103
|
-
sub: "Right now"
|
|
104
|
-
}
|
|
105
|
-
) }),
|
|
106
|
-
data.merchantPayoutsByCurrency && /* @__PURE__ */ jsx(Reveal, { index: 5, className: "adm-span-3", children: /* @__PURE__ */ jsx(
|
|
107
|
-
StatCard,
|
|
108
|
-
{
|
|
109
|
-
label: "Merchants sent out",
|
|
110
|
-
value: /* @__PURE__ */ jsx(
|
|
111
|
-
MoneyLines,
|
|
112
|
-
{
|
|
113
|
-
rows: data.merchantPayoutsByCurrency,
|
|
114
|
-
currency,
|
|
115
|
-
amount: (r) => r.amount,
|
|
116
|
-
decimals: 2,
|
|
117
|
-
empty: "Nothing sent out"
|
|
118
|
-
}
|
|
119
|
-
),
|
|
120
|
-
hint: "What merchants withdrew to their bank accounts in this window. Payouts the bank sent back are taken off."
|
|
121
|
-
}
|
|
122
|
-
) })
|
|
123
|
-
] })
|
|
124
|
-
]
|
|
125
|
-
}
|
|
126
|
-
);
|
|
17
|
+
return /* @__PURE__ */ jsxs("section", { "aria-label": "Revenue indicators", className: "adm-grid", children: [
|
|
18
|
+
/* @__PURE__ */ jsx(Reveal, { index: 0, className: "adm-span-3", children: /* @__PURE__ */ jsx(
|
|
19
|
+
StatCard,
|
|
20
|
+
{
|
|
21
|
+
label: "Monthly recurring revenue (MRR)",
|
|
22
|
+
value: /* @__PURE__ */ jsx(CountUp, { value: recurring.mrr, format: money }),
|
|
23
|
+
hint: `Monthly recurring revenue from active merchant billing subscriptions, in ${data.currency || "USD"}. Yearly plans are divided by 12.`,
|
|
24
|
+
sub: `${integer.format(recurring.activeSubscriptions)} paying`
|
|
25
|
+
}
|
|
26
|
+
) }),
|
|
27
|
+
/* @__PURE__ */ jsx(Reveal, { index: 1, className: "adm-span-3", children: /* @__PURE__ */ jsx(
|
|
28
|
+
StatCard,
|
|
29
|
+
{
|
|
30
|
+
label: "Annual recurring revenue (ARR)",
|
|
31
|
+
value: /* @__PURE__ */ jsx(CountUp, { value: recurring.arr, format: money }),
|
|
32
|
+
hint: "Annual recurring revenue: what the monthly recurring revenue adds up to over a year if nothing changes (MRR \xD7 12).",
|
|
33
|
+
sub: "MRR \xD7 12"
|
|
34
|
+
}
|
|
35
|
+
) }),
|
|
36
|
+
/* @__PURE__ */ jsx(Reveal, { index: 2, className: "adm-span-3", children: /* @__PURE__ */ jsx(
|
|
37
|
+
StatCard,
|
|
38
|
+
{
|
|
39
|
+
label: "Average revenue per account (ARPA)",
|
|
40
|
+
value: /* @__PURE__ */ jsx(CountUp, { value: recurring.arpa, format: money2 }),
|
|
41
|
+
hint: "What the average paying merchant pays each month: monthly recurring revenue divided by the number of paying merchants.",
|
|
42
|
+
sub: "Per paying account"
|
|
43
|
+
}
|
|
44
|
+
) }),
|
|
45
|
+
/* @__PURE__ */ jsx(Reveal, { index: 3, className: "adm-span-3", children: /* @__PURE__ */ jsx(
|
|
46
|
+
StatCard,
|
|
47
|
+
{
|
|
48
|
+
label: "Merchant sales (GMV)",
|
|
49
|
+
value: /* @__PURE__ */ jsx(
|
|
50
|
+
MoneyLines,
|
|
51
|
+
{
|
|
52
|
+
rows: data.gmvByCurrency,
|
|
53
|
+
currency,
|
|
54
|
+
amount: (g) => g.inRange,
|
|
55
|
+
empty: "No sales yet"
|
|
56
|
+
}
|
|
57
|
+
),
|
|
58
|
+
href: "/admin/tenants",
|
|
59
|
+
sub: lifetimeShare,
|
|
60
|
+
hint: `The merchant sales ${ADMIN_NAME}'s recurring revenue sits on, not ${ADMIN_NAME}'s own income (that's MRR). One line per currency; currencies are never added together or converted.`,
|
|
61
|
+
spark: focus ? gmvSpark : void 0,
|
|
62
|
+
sparkColor: TONE.money.fg
|
|
63
|
+
}
|
|
64
|
+
) })
|
|
65
|
+
] });
|
|
127
66
|
}
|
|
128
67
|
export {
|
|
129
68
|
RevenueKpis
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/revenue/revenue-kpis.tsx"],"sourcesContent":["import { ADMIN_NAME } from '../_shell/admin-brand';\nimport type {
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/revenue/revenue-kpis.tsx"],"sourcesContent":["import { ADMIN_NAME } from '../_shell/admin-brand';\nimport type { PlatformRevenue } from '@withpotter/lib/admin-api';\nimport { CountUp, Reveal, TONE } from '../_fx';\nimport { MoneyLines, StatCard, rowFor } from '../_ui';\n\nconst integer = new Intl.NumberFormat('en-US');\n\n/** The four headline figures: recurring revenue first, then merchant GMV. */\nexport function RevenueKpis({\n data,\n money,\n money2,\n currency,\n focus,\n gmvSpark,\n}: {\n data: PlatformRevenue;\n /** Formats recurring revenue, in the currency the API names for it. */\n money: (value: number) => string;\n money2: (value: number) => string;\n /** The currency filter's choice; empty shows a line per currency. */\n currency: string;\n /** The one currency the share and sparkline follow, or null. */\n focus: string | null;\n gmvSpark: number[];\n}) {\n const { recurring } = data;\n const gmv = rowFor(data.gmvByCurrency, focus);\n const lifetimeShare =\n gmv && gmv.lifetime > 0\n ? `${Math.round((gmv.inRange / gmv.lifetime) * 100)}% of lifetime`\n : undefined;\n\n return (\n <section aria-label=\"Revenue indicators\" className=\"adm-grid\">\n <Reveal index={0} className=\"adm-span-3\">\n <StatCard\n label=\"Monthly recurring revenue (MRR)\"\n value={<CountUp value={recurring.mrr} format={money} />}\n hint={`Monthly recurring revenue from active merchant billing subscriptions, in ${data.currency || 'USD'}. Yearly plans are divided by 12.`}\n sub={`${integer.format(recurring.activeSubscriptions)} paying`}\n />\n </Reveal>\n <Reveal index={1} className=\"adm-span-3\">\n <StatCard\n label=\"Annual recurring revenue (ARR)\"\n value={<CountUp value={recurring.arr} format={money} />}\n hint=\"Annual recurring revenue: what the monthly recurring revenue adds up to over a year if nothing changes (MRR × 12).\"\n sub=\"MRR × 12\"\n />\n </Reveal>\n <Reveal index={2} className=\"adm-span-3\">\n <StatCard\n label=\"Average revenue per account (ARPA)\"\n value={<CountUp value={recurring.arpa} format={money2} />}\n hint=\"What the average paying merchant pays each month: monthly recurring revenue divided by the number of paying merchants.\"\n sub=\"Per paying account\"\n />\n </Reveal>\n <Reveal index={3} className=\"adm-span-3\">\n <StatCard\n label=\"Merchant sales (GMV)\"\n value={\n <MoneyLines\n rows={data.gmvByCurrency}\n currency={currency}\n amount={(g) => g.inRange}\n empty=\"No sales yet\"\n />\n }\n href=\"/admin/tenants\"\n sub={lifetimeShare}\n hint={`The merchant sales ${ADMIN_NAME}'s recurring revenue sits on, not ${ADMIN_NAME}'s own income (that's MRR). One line per currency; currencies are never added together or converted.`}\n spark={focus ? gmvSpark : undefined}\n sparkColor={TONE.money.fg}\n />\n </Reveal>\n </section>\n );\n}\n"],"mappings":"AAkCI,SAIa,KAJb;AAlCJ,SAAS,kBAAkB;AAE3B,SAAS,SAAS,QAAQ,YAAY;AACtC,SAAS,YAAY,UAAU,cAAc;AAE7C,MAAM,UAAU,IAAI,KAAK,aAAa,OAAO;AAGtC,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAUG;AACD,QAAM,EAAE,UAAU,IAAI;AACtB,QAAM,MAAM,OAAO,KAAK,eAAe,KAAK;AAC5C,QAAM,gBACJ,OAAO,IAAI,WAAW,IAClB,GAAG,KAAK,MAAO,IAAI,UAAU,IAAI,WAAY,GAAG,CAAC,kBACjD;AAEN,SACE,qBAAC,aAAQ,cAAW,sBAAqB,WAAU,YACjD;AAAA,wBAAC,UAAO,OAAO,GAAG,WAAU,cAC1B;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,OAAO,oBAAC,WAAQ,OAAO,UAAU,KAAK,QAAQ,OAAO;AAAA,QACrD,MAAM,4EAA4E,KAAK,YAAY,KAAK;AAAA,QACxG,KAAK,GAAG,QAAQ,OAAO,UAAU,mBAAmB,CAAC;AAAA;AAAA,IACvD,GACF;AAAA,IACA,oBAAC,UAAO,OAAO,GAAG,WAAU,cAC1B;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,OAAO,oBAAC,WAAQ,OAAO,UAAU,KAAK,QAAQ,OAAO;AAAA,QACrD,MAAK;AAAA,QACL,KAAI;AAAA;AAAA,IACN,GACF;AAAA,IACA,oBAAC,UAAO,OAAO,GAAG,WAAU,cAC1B;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,OAAO,oBAAC,WAAQ,OAAO,UAAU,MAAM,QAAQ,QAAQ;AAAA,QACvD,MAAK;AAAA,QACL,KAAI;AAAA;AAAA,IACN,GACF;AAAA,IACA,oBAAC,UAAO,OAAO,GAAG,WAAU,cAC1B;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,OACE;AAAA,UAAC;AAAA;AAAA,YACC,MAAM,KAAK;AAAA,YACX;AAAA,YACA,QAAQ,CAAC,MAAM,EAAE;AAAA,YACjB,OAAM;AAAA;AAAA,QACR;AAAA,QAEF,MAAK;AAAA,QACL,KAAK;AAAA,QACL,MAAM,sBAAsB,UAAU,qCAAqC,UAAU;AAAA,QACrF,OAAO,QAAQ,WAAW;AAAA,QAC1B,YAAY,KAAK,MAAM;AAAA;AAAA,IACzB,GACF;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -1,65 +1,63 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { TimeSeriesChart } from "@withpotter/components/charts";
|
|
5
|
+
import { PICK_CURRENCY, Panel, moneyIn, rowFor, toTrend } from "../_ui";
|
|
6
|
+
import { Tinted } from "../audit/tinted";
|
|
5
7
|
import { HealthPanel } from "./health-panel";
|
|
6
8
|
import { PlanMixPanel } from "./plan-mix-panel";
|
|
7
|
-
import { planLines } from "./plan-lines";
|
|
8
|
-
import { PlansPanel } from "./plans-panel";
|
|
9
9
|
import { RevenueKpis } from "./revenue-kpis";
|
|
10
|
-
import
|
|
10
|
+
import { SubscriptionsPanel } from "./subscriptions-panel";
|
|
11
|
+
import { TopTenantsPanel } from "./top-tenants-panel";
|
|
12
|
+
import styles from "./revenue.module.css";
|
|
11
13
|
function RevenueOverview({
|
|
12
|
-
startDate,
|
|
13
|
-
endDate,
|
|
14
14
|
data,
|
|
15
15
|
health,
|
|
16
16
|
money,
|
|
17
17
|
money2,
|
|
18
18
|
currency,
|
|
19
19
|
focus,
|
|
20
|
+
period,
|
|
20
21
|
span,
|
|
21
|
-
recurringCaption
|
|
22
|
-
plans
|
|
22
|
+
recurringCaption
|
|
23
23
|
}) {
|
|
24
|
-
const
|
|
24
|
+
const gmv = rowFor(data.gmvByCurrency, focus);
|
|
25
|
+
const gmvSeries = useMemo(() => toTrend(gmv?.series, "gmv"), [gmv]);
|
|
26
|
+
const gmvCaption = focus ? `${period} \xB7 ${focus}` : period;
|
|
25
27
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
26
|
-
/* @__PURE__ */ jsx(
|
|
27
|
-
EarningsSummary,
|
|
28
|
-
{
|
|
29
|
-
startDate,
|
|
30
|
-
endDate,
|
|
31
|
-
currency,
|
|
32
|
-
recurring: data.recurring,
|
|
33
|
-
span
|
|
34
|
-
}
|
|
35
|
-
),
|
|
36
|
-
/* @__PURE__ */ jsx("h2", { className: ledgerStyles.sectionTitle, children: "Plan income" }),
|
|
37
|
-
/* @__PURE__ */ jsxs("p", { className: ledgerStyles.sectionNote, children: [
|
|
38
|
-
"What merchants pay Olive for their plans, each month and over a year.",
|
|
39
|
-
recurringTracked ? "" : " Plan income is not set up on this box yet."
|
|
40
|
-
] }),
|
|
41
28
|
/* @__PURE__ */ jsx(
|
|
42
29
|
RevenueKpis,
|
|
43
30
|
{
|
|
44
|
-
part: "olive",
|
|
45
31
|
data,
|
|
46
32
|
money,
|
|
47
33
|
money2,
|
|
48
34
|
currency,
|
|
49
35
|
focus,
|
|
50
|
-
gmvSpark:
|
|
36
|
+
gmvSpark: gmvSeries.map((p) => p.value)
|
|
51
37
|
}
|
|
52
38
|
),
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
39
|
+
/* @__PURE__ */ jsx("div", { className: "adm-grid", children: /* @__PURE__ */ jsx("div", { className: "adm-span-12", children: /* @__PURE__ */ jsx(
|
|
40
|
+
Panel,
|
|
41
|
+
{
|
|
42
|
+
title: "GMV trend",
|
|
43
|
+
caption: gmvCaption,
|
|
44
|
+
height: "calc(var(--adm-panel-h) + 32px)",
|
|
45
|
+
children: focus ? /* @__PURE__ */ jsx("div", { className: styles.flatChart, children: /* @__PURE__ */ jsx(Tinted, { tone: "money", children: /* @__PURE__ */ jsx(
|
|
46
|
+
TimeSeriesChart,
|
|
47
|
+
{
|
|
48
|
+
data: gmvSeries,
|
|
49
|
+
compare: null,
|
|
50
|
+
compareLabel: `Previous ${span}`,
|
|
51
|
+
formatValue: moneyIn(focus),
|
|
52
|
+
emptyMessage: `No paid ${focus} orders in this window yet`,
|
|
53
|
+
height: 240
|
|
54
|
+
}
|
|
55
|
+
) }) }) : /* @__PURE__ */ jsx("p", { className: styles.empty, children: PICK_CURRENCY })
|
|
56
|
+
}
|
|
57
|
+
) }) }),
|
|
58
|
+
/* @__PURE__ */ jsxs("div", { className: "adm-grid", children: [
|
|
59
|
+
/* @__PURE__ */ jsx("div", { className: "adm-span-7", children: /* @__PURE__ */ jsx(HealthPanel, { health, money, caption: recurringCaption }) }),
|
|
60
|
+
/* @__PURE__ */ jsx("div", { className: "adm-span-5", children: /* @__PURE__ */ jsx(
|
|
63
61
|
PlanMixPanel,
|
|
64
62
|
{
|
|
65
63
|
planBreakdown: data.recurring.planBreakdown,
|
|
@@ -68,8 +66,17 @@ function RevenueOverview({
|
|
|
68
66
|
}
|
|
69
67
|
) })
|
|
70
68
|
] }),
|
|
71
|
-
/* @__PURE__ */
|
|
72
|
-
|
|
69
|
+
/* @__PURE__ */ jsxs("div", { className: "adm-grid", children: [
|
|
70
|
+
/* @__PURE__ */ jsx("div", { className: "adm-span-8", children: /* @__PURE__ */ jsx(
|
|
71
|
+
TopTenantsPanel,
|
|
72
|
+
{
|
|
73
|
+
tenants: gmv?.topTenants ?? [],
|
|
74
|
+
lifetime: gmv?.lifetime ?? 0,
|
|
75
|
+
focus
|
|
76
|
+
}
|
|
77
|
+
) }),
|
|
78
|
+
/* @__PURE__ */ jsx("div", { className: "adm-span-4", children: /* @__PURE__ */ jsx(SubscriptionsPanel, { recurring: data.recurring, span }) })
|
|
79
|
+
] })
|
|
73
80
|
] });
|
|
74
81
|
}
|
|
75
82
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/revenue/revenue-overview.tsx"],"sourcesContent":["'use client';\n\nimport
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/revenue/revenue-overview.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo } from 'react';\nimport { TimeSeriesChart } from '@withpotter/components/charts';\nimport type { PlatformCockpit, PlatformRevenue } from '@withpotter/lib/admin-api';\nimport { PICK_CURRENCY, Panel, moneyIn, rowFor, toTrend } from '../_ui';\nimport { Tinted } from '../audit/tinted';\nimport { HealthPanel } from './health-panel';\nimport { PlanMixPanel } from './plan-mix-panel';\nimport { RevenueKpis } from './revenue-kpis';\nimport { SubscriptionsPanel } from './subscriptions-panel';\nimport { TopTenantsPanel } from './top-tenants-panel';\nimport styles from './revenue.module.css';\n\n/**\n * Overview tab: the four headline figures, then the GMV trend as the one\n * full-width primary chart, then revenue health, plan mix, top merchants\n * and subscriptions beneath it. Merchant GMV is per currency: the chart and\n * the leaderboard follow one currency (`focus`), never a mix.\n */\nexport function RevenueOverview({\n data,\n health,\n money,\n money2,\n currency,\n focus,\n period,\n span,\n recurringCaption,\n}: {\n data: PlatformRevenue;\n health: PlatformCockpit['health'] | undefined;\n /** Formats recurring revenue, in the currency the API names for it. */\n money: (value: number) => string;\n money2: (value: number) => string;\n /** The currency filter's choice; empty shows a line per currency. */\n currency: string;\n /** The one currency the chart and leaderboard show, or null. */\n focus: string | null;\n period: string;\n span: string;\n recurringCaption: string;\n}) {\n const gmv = rowFor(data.gmvByCurrency, focus);\n const gmvSeries = useMemo(() => toTrend(gmv?.series, 'gmv'), [gmv]);\n const gmvCaption = focus ? `${period} · ${focus}` : period;\n\n return (\n <>\n <RevenueKpis\n data={data}\n money={money}\n money2={money2}\n currency={currency}\n focus={focus}\n gmvSpark={gmvSeries.map((p) => p.value)}\n />\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-12\">\n <Panel\n title=\"GMV trend\"\n caption={gmvCaption}\n height=\"calc(var(--adm-panel-h) + 32px)\"\n >\n {focus ? (\n <div className={styles.flatChart}>\n <Tinted tone=\"money\">\n <TimeSeriesChart\n data={gmvSeries}\n compare={null}\n compareLabel={`Previous ${span}`}\n formatValue={moneyIn(focus)}\n emptyMessage={`No paid ${focus} orders in this window yet`}\n height={240}\n />\n </Tinted>\n </div>\n ) : (\n <p className={styles.empty}>{PICK_CURRENCY}</p>\n )}\n </Panel>\n </div>\n </div>\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-7\">\n <HealthPanel health={health} money={money} caption={recurringCaption} />\n </div>\n <div className=\"adm-span-5\">\n <PlanMixPanel\n planBreakdown={data.recurring.planBreakdown}\n money={money}\n caption={recurringCaption}\n />\n </div>\n </div>\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-8\">\n <TopTenantsPanel\n tenants={gmv?.topTenants ?? []}\n lifetime={gmv?.lifetime ?? 0}\n focus={focus}\n />\n </div>\n <div className=\"adm-span-4\">\n <SubscriptionsPanel recurring={data.recurring} span={span} />\n </div>\n </div>\n </>\n );\n}\n"],"mappings":";AAiDI,mBACE,KAoCA,YArCF;AA/CJ,SAAS,eAAe;AACxB,SAAS,uBAAuB;AAEhC,SAAS,eAAe,OAAO,SAAS,QAAQ,eAAe;AAC/D,SAAS,cAAc;AACvB,SAAS,mBAAmB;AAC5B,SAAS,oBAAoB;AAC7B,SAAS,mBAAmB;AAC5B,SAAS,0BAA0B;AACnC,SAAS,uBAAuB;AAChC,OAAO,YAAY;AAQZ,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAaG;AACD,QAAM,MAAM,OAAO,KAAK,eAAe,KAAK;AAC5C,QAAM,YAAY,QAAQ,MAAM,QAAQ,KAAK,QAAQ,KAAK,GAAG,CAAC,GAAG,CAAC;AAClE,QAAM,aAAa,QAAQ,GAAG,MAAM,SAAM,KAAK,KAAK;AAEpD,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,UAAU,UAAU,IAAI,CAAC,MAAM,EAAE,KAAK;AAAA;AAAA,IACxC;AAAA,IAEA,oBAAC,SAAI,WAAU,YACb,8BAAC,SAAI,WAAU,eACb;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,SAAS;AAAA,QACT,QAAO;AAAA,QAEN,kBACC,oBAAC,SAAI,WAAW,OAAO,WACrB,8BAAC,UAAO,MAAK,SACX;AAAA,UAAC;AAAA;AAAA,YACC,MAAM;AAAA,YACN,SAAS;AAAA,YACT,cAAc,YAAY,IAAI;AAAA,YAC9B,aAAa,QAAQ,KAAK;AAAA,YAC1B,cAAc,WAAW,KAAK;AAAA,YAC9B,QAAQ;AAAA;AAAA,QACV,GACF,GACF,IAEA,oBAAC,OAAE,WAAW,OAAO,OAAQ,yBAAc;AAAA;AAAA,IAE/C,GACF,GACF;AAAA,IAEA,qBAAC,SAAI,WAAU,YACb;AAAA,0BAAC,SAAI,WAAU,cACb,8BAAC,eAAY,QAAgB,OAAc,SAAS,kBAAkB,GACxE;AAAA,MACA,oBAAC,SAAI,WAAU,cACb;AAAA,QAAC;AAAA;AAAA,UACC,eAAe,KAAK,UAAU;AAAA,UAC9B;AAAA,UACA,SAAS;AAAA;AAAA,MACX,GACF;AAAA,OACF;AAAA,IAEA,qBAAC,SAAI,WAAU,YACb;AAAA,0BAAC,SAAI,WAAU,cACb;AAAA,QAAC;AAAA;AAAA,UACC,SAAS,KAAK,cAAc,CAAC;AAAA,UAC7B,UAAU,KAAK,YAAY;AAAA,UAC3B;AAAA;AAAA,MACF,GACF;AAAA,MACA,oBAAC,SAAI,WAAU,cACb,8BAAC,sBAAmB,WAAW,KAAK,WAAW,MAAY,GAC7D;AAAA,OACF;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { EmptyBlock } from "../audit/empty-block";
|
|
4
|
-
import ledgerStyles from "./ledger.module.css";
|
|
5
4
|
import { CohortPanel } from "./cohort-panel";
|
|
6
5
|
import { QualityPanel } from "./quality-panel";
|
|
7
6
|
function RevenueRetention({
|
|
@@ -11,22 +10,19 @@ function RevenueRetention({
|
|
|
11
10
|
caption
|
|
12
11
|
}) {
|
|
13
12
|
if (!quality) {
|
|
14
|
-
return /* @__PURE__ */ jsx(EmptyBlock, { message: "Retention
|
|
13
|
+
return /* @__PURE__ */ jsx(EmptyBlock, { message: "Retention appears once there is enough billing history." });
|
|
15
14
|
}
|
|
16
|
-
return /* @__PURE__ */ jsxs(
|
|
17
|
-
/* @__PURE__ */ jsx("
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
) }),
|
|
28
|
-
/* @__PURE__ */ jsx("div", { className: "adm-span-7", children: /* @__PURE__ */ jsx(CohortPanel, { cohorts: quality.cohorts }) })
|
|
29
|
-
] })
|
|
15
|
+
return /* @__PURE__ */ jsxs("div", { className: "adm-grid", children: [
|
|
16
|
+
/* @__PURE__ */ jsx("div", { className: "adm-span-5", children: /* @__PURE__ */ jsx(
|
|
17
|
+
QualityPanel,
|
|
18
|
+
{
|
|
19
|
+
quality,
|
|
20
|
+
money,
|
|
21
|
+
currency,
|
|
22
|
+
caption
|
|
23
|
+
}
|
|
24
|
+
) }),
|
|
25
|
+
/* @__PURE__ */ jsx("div", { className: "adm-span-7", children: /* @__PURE__ */ jsx(CohortPanel, { cohorts: quality.cohorts }) })
|
|
30
26
|
] });
|
|
31
27
|
}
|
|
32
28
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/revenue/revenue-retention.tsx"],"sourcesContent":["'use client';\n\nimport type { PlatformRevenue } from '@withpotter/lib/admin-api';\nimport { EmptyBlock } from '../audit/empty-block';\nimport
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/revenue/revenue-retention.tsx"],"sourcesContent":["'use client';\n\nimport type { PlatformRevenue } from '@withpotter/lib/admin-api';\nimport { EmptyBlock } from '../audit/empty-block';\nimport { CohortPanel } from './cohort-panel';\nimport { QualityPanel } from './quality-panel';\n\n/** Retention tab: revenue quality beside logo cohort retention. */\nexport function RevenueRetention({\n quality,\n money,\n currency,\n caption,\n}: {\n quality: PlatformRevenue['quality'];\n money: (value: number) => string;\n /** The currency filter's choice; empty shows a line per currency. */\n currency: string;\n caption: string;\n}) {\n if (!quality) {\n return (\n <EmptyBlock message=\"Retention appears once there is enough billing history.\" />\n );\n }\n\n return (\n <div className=\"adm-grid\">\n <div className=\"adm-span-5\">\n <QualityPanel\n quality={quality}\n money={money}\n currency={currency}\n caption={caption}\n />\n </div>\n <div className=\"adm-span-7\">\n <CohortPanel cohorts={quality.cohorts} />\n </div>\n </div>\n );\n}\n"],"mappings":";AAsBM,cAKF,YALE;AAnBN,SAAS,kBAAkB;AAC3B,SAAS,mBAAmB;AAC5B,SAAS,oBAAoB;AAGtB,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,MAAI,CAAC,SAAS;AACZ,WACE,oBAAC,cAAW,SAAQ,2DAA0D;AAAA,EAElF;AAEA,SACE,qBAAC,SAAI,WAAU,YACb;AAAA,wBAAC,SAAI,WAAU,cACb;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,IACF,GACF;AAAA,IACA,oBAAC,SAAI,WAAU,cACb,8BAAC,eAAY,SAAS,QAAQ,SAAS,GACzC;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -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: "
|
|
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 || "
|
|
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: `
|
|
103
|
+
description: `Every revenue on ${ADMIN_NAME} is reported here: its 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 {
|
|
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={`Every revenue on ${ADMIN_NAME} is reported here: its 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":[]}
|