@withpotter/pages-admin 0.13.0-next.101 → 0.13.0-next.102
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/hooks.js +19 -0
- package/dist/admin/hooks.js.map +1 -1
- package/dist/admin/revenue/cohort-panel.js +5 -5
- package/dist/admin/revenue/cohort-panel.js.map +1 -1
- package/dist/admin/revenue/health-panel.js +17 -17
- package/dist/admin/revenue/health-panel.js.map +1 -1
- package/dist/admin/revenue/ledger-model.js +7 -0
- package/dist/admin/revenue/ledger-model.js.map +1 -1
- package/dist/admin/revenue/ledger.module.css +43 -0
- package/dist/admin/revenue/merchant-headline.js +37 -0
- package/dist/admin/revenue/merchant-headline.js.map +1 -0
- package/dist/admin/revenue/merchant-payments-view.js +46 -22
- package/dist/admin/revenue/merchant-payments-view.js.map +1 -1
- package/dist/admin/revenue/merchants-view.js +99 -0
- package/dist/admin/revenue/merchants-view.js.map +1 -0
- package/dist/admin/revenue/payment-drawer.js +53 -0
- package/dist/admin/revenue/payment-drawer.js.map +1 -0
- package/dist/admin/revenue/plan-mix-panel.js +1 -1
- package/dist/admin/revenue/plan-mix-panel.js.map +1 -1
- package/dist/admin/revenue/plan-payment-drawer.js +42 -0
- package/dist/admin/revenue/plan-payment-drawer.js.map +1 -0
- package/dist/admin/revenue/print-summary-button.js +34 -0
- package/dist/admin/revenue/print-summary-button.js.map +1 -0
- 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 +64 -50
- package/dist/admin/revenue/revenue-kpis.js.map +1 -1
- package/dist/admin/revenue/revenue-overview.js +68 -50
- package/dist/admin/revenue/revenue-overview.js.map +1 -1
- package/dist/admin/revenue/revenue-retention.js +17 -13
- package/dist/admin/revenue/revenue-retention.js.map +1 -1
- package/dist/admin/revenue/revenue-workspace.js +16 -0
- 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/subscriptions-view.js +20 -20
- package/dist/admin/revenue/subscriptions-view.js.map +1 -1
- package/dist/admin/revenue/summary-download.js +34 -0
- package/dist/admin/revenue/summary-download.js.map +1 -0
- package/dist/admin/revenue/summary-print.js +82 -0
- package/dist/admin/revenue/summary-print.js.map +1 -0
- package/dist/admin/revenue/summary-rows.js +44 -0
- package/dist/admin/revenue/summary-rows.js.map +1 -0
- package/dist/admin/revenue/top-tenants-panel.js +1 -1
- package/dist/admin/revenue/top-tenants-panel.js.map +1 -1
- package/dist/admin/revenue/url-filters.js +36 -0
- package/dist/admin/revenue/url-filters.js.map +1 -0
- package/dist/admin/revenue/use-url-filters.js +24 -0
- package/dist/admin/revenue/use-url-filters.js.map +1 -0
- package/package.json +4 -4
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { Fragment, 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,
|
|
7
9
|
data,
|
|
8
10
|
money,
|
|
9
11
|
money2,
|
|
@@ -17,55 +19,67 @@ function RevenueKpis({
|
|
|
17
19
|
const noSource = "The plan payments are not connected yet";
|
|
18
20
|
const gmv = rowFor(data.gmvByCurrency, focus);
|
|
19
21
|
const lifetimeShare = gmv && gmv.lifetime > 0 ? `${Math.round(gmv.inRange / gmv.lifetime * 100)}% of lifetime` : void 0;
|
|
20
|
-
return /* @__PURE__ */ jsxs(
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
22
|
+
return /* @__PURE__ */ jsxs(
|
|
23
|
+
"section",
|
|
24
|
+
{
|
|
25
|
+
"aria-label": part === "olive" ? "What Olive earns" : "What merchants sold",
|
|
26
|
+
className: "adm-grid",
|
|
27
|
+
children: [
|
|
28
|
+
part === "olive" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
29
|
+
/* @__PURE__ */ jsx(Reveal, { index: 0, className: "adm-span-3", children: /* @__PURE__ */ jsx(
|
|
30
|
+
StatCard,
|
|
31
|
+
{
|
|
32
|
+
label: "Plan income each month",
|
|
33
|
+
value: tracked ? /* @__PURE__ */ jsx(CountUp, { value: recurring.mrr, format: money }) : notTracked,
|
|
34
|
+
hint: `What merchants on a paid plan are expected to pay ${ADMIN_NAME} every month, in ${data.currency || "NGN"}. Free plans, trials and pauses are not counted. (MRR)`,
|
|
35
|
+
sub: tracked ? `${integer.format(recurring.payingMerchants ?? recurring.activeSubscriptions)} merchants paying` : noSource
|
|
36
|
+
}
|
|
37
|
+
) }),
|
|
38
|
+
/* @__PURE__ */ jsx(Reveal, { index: 1, className: "adm-span-3", children: /* @__PURE__ */ jsx(
|
|
39
|
+
StatCard,
|
|
40
|
+
{
|
|
41
|
+
label: "Plan income over a year",
|
|
42
|
+
value: tracked ? /* @__PURE__ */ jsx(CountUp, { value: recurring.arr, format: money }) : notTracked,
|
|
43
|
+
hint: "What plan income adds up to over a year if nothing changes: the monthly figure times 12. (ARR)",
|
|
44
|
+
sub: tracked ? "Each month times 12" : noSource
|
|
45
|
+
}
|
|
46
|
+
) }),
|
|
47
|
+
/* @__PURE__ */ jsx(Reveal, { index: 2, className: "adm-span-3", children: /* @__PURE__ */ jsx(
|
|
48
|
+
StatCard,
|
|
49
|
+
{
|
|
50
|
+
label: "Average per paying merchant",
|
|
51
|
+
value: tracked ? /* @__PURE__ */ jsx(CountUp, { value: recurring.arpa, format: money2 }) : notTracked,
|
|
52
|
+
hint: "What one paying merchant pays in a month, on average. (ARPA)",
|
|
53
|
+
sub: tracked ? "Each month" : noSource
|
|
54
|
+
}
|
|
55
|
+
) })
|
|
56
|
+
] }),
|
|
57
|
+
part === "merchant" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
58
|
+
/* @__PURE__ */ jsx(Reveal, { index: 3, className: "adm-span-3", children: /* @__PURE__ */ jsx(
|
|
59
|
+
StatCard,
|
|
60
|
+
{
|
|
61
|
+
label: "Merchant sales",
|
|
62
|
+
value: /* @__PURE__ */ jsx(
|
|
63
|
+
MoneyLines,
|
|
64
|
+
{
|
|
65
|
+
rows: data.gmvByCurrency,
|
|
66
|
+
currency,
|
|
67
|
+
amount: (g) => g.inRange,
|
|
68
|
+
empty: "No sales yet"
|
|
69
|
+
}
|
|
70
|
+
),
|
|
71
|
+
href: "/admin/tenants",
|
|
72
|
+
sub: lifetimeShare,
|
|
73
|
+
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)`,
|
|
74
|
+
spark: focus ? gmvSpark : void 0,
|
|
75
|
+
sparkColor: TONE.money.fg
|
|
76
|
+
}
|
|
77
|
+
) }),
|
|
78
|
+
extra
|
|
79
|
+
] })
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
);
|
|
69
83
|
}
|
|
70
84
|
export {
|
|
71
85
|
RevenueKpis
|
|
@@ -1 +1 @@
|
|
|
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
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/revenue/revenue-kpis.tsx"],"sourcesContent":["import { ADMIN_NAME } from '../_shell/admin-brand';\nimport type { ReactNode } from 'react';\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/**\n * The headline figures, in two groups that are never added together: what\n * merchants sold (`merchant`), and what Olive itself earns from plans (`olive`).\n */\nexport function RevenueKpis({\n part,\n extra,\n data,\n money,\n money2,\n currency,\n focus,\n gmvSpark,\n}: {\n part: 'merchant' | 'olive';\n /** More cards for the merchant group, in the same row. */\n extra?: ReactNode;\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 // Without the plan payments connected the API sends 0s; say so rather than show a zero.\n const tracked = recurring.tracked !== false;\n const notTracked = 'Not set up yet';\n const noSource = 'The plan payments are not connected yet';\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\n aria-label={part === 'olive' ? 'What Olive earns' : 'What merchants sold'}\n className=\"adm-grid\"\n >\n {part === 'olive' && (\n <>\n <Reveal index={0} className=\"adm-span-3\">\n <StatCard\n label=\"Plan income each month\"\n value={\n tracked ? (\n <CountUp value={recurring.mrr} format={money} />\n ) : (\n notTracked\n )\n }\n hint={`What merchants on a paid plan are expected to pay ${ADMIN_NAME} every month, in ${data.currency || 'NGN'}. Free plans, trials and pauses are not counted. (MRR)`}\n sub={\n tracked\n ? `${integer.format(recurring.payingMerchants ?? recurring.activeSubscriptions)} merchants paying`\n : noSource\n }\n />\n </Reveal>\n <Reveal index={1} className=\"adm-span-3\">\n <StatCard\n label=\"Plan income over a year\"\n value={\n tracked ? (\n <CountUp value={recurring.arr} format={money} />\n ) : (\n notTracked\n )\n }\n hint=\"What plan income adds up to over a year if nothing changes: the monthly figure times 12. (ARR)\"\n sub={tracked ? 'Each month times 12' : noSource}\n />\n </Reveal>\n <Reveal index={2} className=\"adm-span-3\">\n <StatCard\n label=\"Average per paying merchant\"\n value={\n tracked ? (\n <CountUp value={recurring.arpa} format={money2} />\n ) : (\n notTracked\n )\n }\n hint=\"What one paying merchant pays in a month, on average. (ARPA)\"\n sub={tracked ? 'Each month' : noSource}\n />\n </Reveal>\n </>\n )}\n {part === 'merchant' && (\n <>\n <Reveal index={3} className=\"adm-span-3\">\n <StatCard\n label=\"Merchant sales\"\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={`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)`}\n spark={focus ? gmvSpark : undefined}\n sparkColor={TONE.money.fg}\n />\n </Reveal>\n {extra}\n </>\n )}\n </section>\n );\n}\n"],"mappings":"AAoDQ,mBAMM,KANN;AApDR,SAAS,kBAAkB;AAG3B,SAAS,SAAS,QAAQ,YAAY;AACtC,SAAS,YAAY,UAAU,cAAc;AAE7C,MAAM,UAAU,IAAI,KAAK,aAAa,OAAO;AAMtC,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAaG;AACD,QAAM,EAAE,UAAU,IAAI;AAEtB,QAAM,UAAU,UAAU,YAAY;AACtC,QAAM,aAAa;AACnB,QAAM,WAAW;AACjB,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;AAAA,IAAC;AAAA;AAAA,MACC,cAAY,SAAS,UAAU,qBAAqB;AAAA,MACpD,WAAU;AAAA,MAET;AAAA,iBAAS,WACR,iCACF;AAAA,8BAAC,UAAO,OAAO,GAAG,WAAU,cAC1B;AAAA,YAAC;AAAA;AAAA,cACC,OAAM;AAAA,cACN,OACE,UACE,oBAAC,WAAQ,OAAO,UAAU,KAAK,QAAQ,OAAO,IAE9C;AAAA,cAGJ,MAAM,qDAAqD,UAAU,oBAAoB,KAAK,YAAY,KAAK;AAAA,cAC/G,KACE,UACI,GAAG,QAAQ,OAAO,UAAU,mBAAmB,UAAU,mBAAmB,CAAC,sBAC7E;AAAA;AAAA,UAER,GACF;AAAA,UACA,oBAAC,UAAO,OAAO,GAAG,WAAU,cAC1B;AAAA,YAAC;AAAA;AAAA,cACC,OAAM;AAAA,cACN,OACE,UACE,oBAAC,WAAQ,OAAO,UAAU,KAAK,QAAQ,OAAO,IAE9C;AAAA,cAGJ,MAAK;AAAA,cACL,KAAK,UAAU,wBAAwB;AAAA;AAAA,UACzC,GACF;AAAA,UACA,oBAAC,UAAO,OAAO,GAAG,WAAU,cAC1B;AAAA,YAAC;AAAA;AAAA,cACC,OAAM;AAAA,cACN,OACE,UACE,oBAAC,WAAQ,OAAO,UAAU,MAAM,QAAQ,QAAQ,IAEhD;AAAA,cAGJ,MAAK;AAAA,cACL,KAAK,UAAU,eAAe;AAAA;AAAA,UAChC,GACF;AAAA,WACE;AAAA,QAED,SAAS,cACR,iCACF;AAAA,8BAAC,UAAO,OAAO,GAAG,WAAU,cAC1B;AAAA,YAAC;AAAA;AAAA,cACC,OAAM;AAAA,cACN,OACE;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAM,KAAK;AAAA,kBACX;AAAA,kBACA,QAAQ,CAAC,MAAM,EAAE;AAAA,kBACjB,OAAM;AAAA;AAAA,cACR;AAAA,cAEF,MAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM,qCAAqC,UAAU,uCAAuC,UAAU;AAAA,cACtG,OAAO,QAAQ,WAAW;AAAA,cAC1B,YAAY,KAAK,MAAM;AAAA;AAAA,UACzB,GACF;AAAA,UACK;AAAA,WACH;AAAA;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
|
|
@@ -6,6 +6,7 @@ import { MoneyLines, PICK_CURRENCY, Panel, StatCard, moneyIn, rowFor, toTrend }
|
|
|
6
6
|
import { Reveal } from "../_fx";
|
|
7
7
|
import { Tinted } from "../audit/tinted";
|
|
8
8
|
import { ChannelPanel } from "./channel-panel";
|
|
9
|
+
import { MerchantHeadline } from "./merchant-headline";
|
|
9
10
|
import { MethodCards } from "./method-cards";
|
|
10
11
|
import { HealthPanel } from "./health-panel";
|
|
11
12
|
import { PlanMixPanel } from "./plan-mix-panel";
|
|
@@ -34,37 +35,25 @@ function RevenueOverview({
|
|
|
34
35
|
const recurringTracked = data.recurring.tracked !== false;
|
|
35
36
|
const platformFees = platformFeeRows(data.platformFeesEarnedByCurrency, currency);
|
|
36
37
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
38
|
+
/* @__PURE__ */ jsx("h2", { className: ledgerStyles.sectionTitle, children: "Money merchants earned" }),
|
|
39
|
+
/* @__PURE__ */ jsx("p", { className: ledgerStyles.sectionNote, children: "What customers paid to merchants. This is the merchants' money, not Olive's own income." }),
|
|
37
40
|
/* @__PURE__ */ jsx(
|
|
38
41
|
RevenueKpis,
|
|
39
42
|
{
|
|
43
|
+
part: "merchant",
|
|
40
44
|
data,
|
|
41
45
|
money,
|
|
42
46
|
money2,
|
|
43
47
|
currency,
|
|
44
48
|
focus,
|
|
45
|
-
gmvSpark: gmvSeries.map((p) => p.value)
|
|
49
|
+
gmvSpark: gmvSeries.map((p) => p.value),
|
|
50
|
+
extra: ledger && focus ? /* @__PURE__ */ jsx(MerchantHeadline, { ledger, focus }) : null
|
|
46
51
|
}
|
|
47
52
|
),
|
|
48
|
-
platformFees && /* @__PURE__ */ jsx("section", { "aria-label": "Platform fees", className: "adm-grid", children: /* @__PURE__ */ jsx(Reveal, { index: 0, className: "adm-span-3", children: /* @__PURE__ */ jsx(
|
|
49
|
-
StatCard,
|
|
50
|
-
{
|
|
51
|
-
label: "Platform fees earned",
|
|
52
|
-
value: /* @__PURE__ */ jsx(
|
|
53
|
-
MoneyLines,
|
|
54
|
-
{
|
|
55
|
-
rows: platformFees,
|
|
56
|
-
currency: "",
|
|
57
|
-
amount: (r) => r.amount,
|
|
58
|
-
decimals: 2
|
|
59
|
-
}
|
|
60
|
-
),
|
|
61
|
-
hint: "What Olive keeps from payouts, settlements and subscriptions."
|
|
62
|
-
}
|
|
63
|
-
) }) }),
|
|
64
53
|
/* @__PURE__ */ jsx("div", { className: "adm-grid", children: /* @__PURE__ */ jsx("div", { className: "adm-span-12", children: /* @__PURE__ */ jsx(
|
|
65
54
|
Panel,
|
|
66
55
|
{
|
|
67
|
-
title: "
|
|
56
|
+
title: "Merchant sales over time",
|
|
68
57
|
caption: gmvCaption,
|
|
69
58
|
height: "calc(var(--adm-panel-h) + 32px)",
|
|
70
59
|
children: focus ? /* @__PURE__ */ jsx("div", { className: styles.flatChart, children: /* @__PURE__ */ jsx(Tinted, { tone: "money", children: /* @__PURE__ */ jsx(
|
|
@@ -81,11 +70,7 @@ function RevenueOverview({
|
|
|
81
70
|
}
|
|
82
71
|
) }) }),
|
|
83
72
|
ledger && focus ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
84
|
-
/* @__PURE__ */ jsx("
|
|
85
|
-
/* @__PURE__ */ jsxs("p", { className: ledgerStyles.sectionNote, children: [
|
|
86
|
-
"Money customers paid to merchants, by way of paying. ",
|
|
87
|
-
gmvCaption
|
|
88
|
-
] }),
|
|
73
|
+
/* @__PURE__ */ jsx("h3", { className: ledgerStyles.subTitle, children: "How customers paid" }),
|
|
89
74
|
/* @__PURE__ */ jsx(
|
|
90
75
|
MethodCards,
|
|
91
76
|
{
|
|
@@ -95,34 +80,67 @@ function RevenueOverview({
|
|
|
95
80
|
}
|
|
96
81
|
)
|
|
97
82
|
] }) : channels && channels.length > 0 && /* @__PURE__ */ jsx("div", { className: "adm-grid", children: /* @__PURE__ */ jsx("div", { className: "adm-span-12", children: /* @__PURE__ */ jsx(ChannelPanel, { totals: channels, focus, caption: gmvCaption }) }) }),
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
planBreakdown: data.recurring.planBreakdown,
|
|
111
|
-
money,
|
|
112
|
-
caption: recurringCaption
|
|
113
|
-
}
|
|
114
|
-
) })
|
|
83
|
+
/* @__PURE__ */ jsx("div", { className: "adm-grid", children: /* @__PURE__ */ jsx("div", { className: "adm-span-12", children: /* @__PURE__ */ jsx(
|
|
84
|
+
TopTenantsPanel,
|
|
85
|
+
{
|
|
86
|
+
tenants: gmv?.topTenants ?? [],
|
|
87
|
+
lifetime: gmv?.lifetime ?? 0,
|
|
88
|
+
focus
|
|
89
|
+
}
|
|
90
|
+
) }) }),
|
|
91
|
+
/* @__PURE__ */ jsx("h2", { className: ledgerStyles.sectionTitle, children: "Money Olive earns" }),
|
|
92
|
+
/* @__PURE__ */ jsxs("p", { className: ledgerStyles.sectionNote, children: [
|
|
93
|
+
"What merchants pay Olive: their plan payments, and the fees Olive keeps.",
|
|
94
|
+
recurringTracked ? "" : " Plan income is not set up on this box yet."
|
|
115
95
|
] }),
|
|
116
|
-
/* @__PURE__ */
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
96
|
+
/* @__PURE__ */ jsx(
|
|
97
|
+
RevenueKpis,
|
|
98
|
+
{
|
|
99
|
+
part: "olive",
|
|
100
|
+
data,
|
|
101
|
+
money,
|
|
102
|
+
money2,
|
|
103
|
+
currency,
|
|
104
|
+
focus,
|
|
105
|
+
gmvSpark: []
|
|
106
|
+
}
|
|
107
|
+
),
|
|
108
|
+
platformFees && /* @__PURE__ */ jsx("section", { "aria-label": "Platform fees", className: "adm-grid", children: /* @__PURE__ */ jsx(Reveal, { index: 0, className: "adm-span-3", children: /* @__PURE__ */ jsx(
|
|
109
|
+
StatCard,
|
|
110
|
+
{
|
|
111
|
+
label: "Fees Olive keeps",
|
|
112
|
+
value: /* @__PURE__ */ jsx(
|
|
113
|
+
MoneyLines,
|
|
114
|
+
{
|
|
115
|
+
rows: platformFees,
|
|
116
|
+
currency: "",
|
|
117
|
+
amount: (r) => r.amount,
|
|
118
|
+
decimals: 2
|
|
119
|
+
}
|
|
120
|
+
),
|
|
121
|
+
hint: "What Olive keeps from payouts, settlements and plan payments, after costs."
|
|
122
|
+
}
|
|
123
|
+
) }) }),
|
|
124
|
+
recurringTracked && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
125
|
+
/* @__PURE__ */ jsxs("div", { className: "adm-grid", children: [
|
|
126
|
+
/* @__PURE__ */ jsx("div", { className: "adm-span-7", children: /* @__PURE__ */ jsx(
|
|
127
|
+
HealthPanel,
|
|
128
|
+
{
|
|
129
|
+
health,
|
|
130
|
+
money,
|
|
131
|
+
caption: recurringCaption
|
|
132
|
+
}
|
|
133
|
+
) }),
|
|
134
|
+
/* @__PURE__ */ jsx("div", { className: "adm-span-5", children: /* @__PURE__ */ jsx(
|
|
135
|
+
PlanMixPanel,
|
|
136
|
+
{
|
|
137
|
+
planBreakdown: data.recurring.planBreakdown,
|
|
138
|
+
money,
|
|
139
|
+
caption: recurringCaption
|
|
140
|
+
}
|
|
141
|
+
) })
|
|
142
|
+
] }),
|
|
143
|
+
/* @__PURE__ */ jsx("div", { className: "adm-grid", children: /* @__PURE__ */ jsx("div", { className: "adm-span-12", children: /* @__PURE__ */ jsx(SubscriptionsPanel, { recurring: data.recurring, span }) }) })
|
|
126
144
|
] })
|
|
127
145
|
] });
|
|
128
146
|
}
|
|
@@ -1 +1 @@
|
|
|
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 {\n GatewayChannelTotal,\n MerchantPaymentsPage,\n PlatformCockpit,\n PlatformRevenue,\n} from '@withpotter/lib/admin-api';\nimport { MoneyLines, PICK_CURRENCY, Panel, StatCard, moneyIn, rowFor, toTrend } from '../_ui';\nimport { Reveal } from '../_fx';\nimport { Tinted } from '../audit/tinted';\nimport { ChannelPanel } from './channel-panel';\nimport { MethodCards } from './method-cards';\nimport { HealthPanel } from './health-panel';\nimport { PlanMixPanel } from './plan-mix-panel';\nimport { platformFeeRows } from './platform-fees';\nimport { RevenueKpis } from './revenue-kpis';\nimport { SubscriptionsPanel } from './subscriptions-panel';\nimport { TopTenantsPanel } from './top-tenants-panel';\nimport styles from './revenue.module.css';\nimport ledgerStyles from './ledger.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 channels,\n ledger,\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 /** How customers paid, per channel and currency. Absent when the box cannot say. */\n channels?: readonly GatewayChannelTotal[];\n /** The merchant payments view's totals, with the period before; absent on a box without it. */\n ledger?: MerchantPaymentsPage;\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 const recurringTracked = data.recurring.tracked !== false;\n // Hidden until the api sends the field; never an invented zero.\n const platformFees = platformFeeRows(data.platformFeesEarnedByCurrency, currency);\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
|
|
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 {\n GatewayChannelTotal,\n MerchantPaymentsPage,\n PlatformCockpit,\n PlatformRevenue,\n} from '@withpotter/lib/admin-api';\nimport { MoneyLines, PICK_CURRENCY, Panel, StatCard, moneyIn, rowFor, toTrend } from '../_ui';\nimport { Reveal } from '../_fx';\nimport { Tinted } from '../audit/tinted';\nimport { ChannelPanel } from './channel-panel';\nimport { MerchantHeadline } from './merchant-headline';\nimport { MethodCards } from './method-cards';\nimport { HealthPanel } from './health-panel';\nimport { PlanMixPanel } from './plan-mix-panel';\nimport { platformFeeRows } from './platform-fees';\nimport { RevenueKpis } from './revenue-kpis';\nimport { SubscriptionsPanel } from './subscriptions-panel';\nimport { TopTenantsPanel } from './top-tenants-panel';\nimport styles from './revenue.module.css';\nimport ledgerStyles from './ledger.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 channels,\n ledger,\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 /** How customers paid, per channel and currency. Absent when the box cannot say. */\n channels?: readonly GatewayChannelTotal[];\n /** The merchant payments view's totals, with the period before; absent on a box without it. */\n ledger?: MerchantPaymentsPage;\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 const recurringTracked = data.recurring.tracked !== false;\n // Hidden until the api sends the field; never an invented zero.\n const platformFees = platformFeeRows(data.platformFeesEarnedByCurrency, currency);\n\n return (\n <>\n <h2 className={ledgerStyles.sectionTitle}>Money merchants earned</h2>\n <p className={ledgerStyles.sectionNote}>\n What customers paid to merchants. This is the merchants' money, not Olive's own income.\n </p>\n <RevenueKpis\n part=\"merchant\"\n data={data}\n money={money}\n money2={money2}\n currency={currency}\n focus={focus}\n gmvSpark={gmvSeries.map((p) => p.value)}\n extra={ledger && focus ? <MerchantHeadline ledger={ledger} focus={focus} /> : null}\n />\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-12\">\n <Panel\n title=\"Merchant sales over time\"\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 {ledger && focus ? (\n <>\n <h3 className={ledgerStyles.subTitle}>How customers paid</h3>\n <MethodCards\n now={ledger.byChannel.current}\n before={ledger.byChannel.previous}\n currency={focus}\n />\n </>\n ) : (\n channels &&\n channels.length > 0 && (\n <div className=\"adm-grid\">\n <div className=\"adm-span-12\">\n <ChannelPanel totals={channels} focus={focus} caption={gmvCaption} />\n </div>\n </div>\n )\n )}\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-12\">\n <TopTenantsPanel\n tenants={gmv?.topTenants ?? []}\n lifetime={gmv?.lifetime ?? 0}\n focus={focus}\n />\n </div>\n </div>\n\n <h2 className={ledgerStyles.sectionTitle}>Money Olive earns</h2>\n <p className={ledgerStyles.sectionNote}>\n What merchants pay Olive: their plan payments, and the fees Olive keeps.\n {recurringTracked ? '' : ' Plan income is not set up on this box yet.'}\n </p>\n <RevenueKpis\n part=\"olive\"\n data={data}\n money={money}\n money2={money2}\n currency={currency}\n focus={focus}\n gmvSpark={[]}\n />\n\n {platformFees && (\n <section aria-label=\"Platform fees\" className=\"adm-grid\">\n <Reveal index={0} className=\"adm-span-3\">\n <StatCard\n label=\"Fees Olive keeps\"\n value={\n <MoneyLines\n rows={platformFees}\n currency=\"\"\n amount={(r) => r.amount}\n decimals={2}\n />\n }\n hint=\"What Olive keeps from payouts, settlements and plan payments, after costs.\"\n />\n </Reveal>\n </section>\n )}\n\n {recurringTracked && (\n <>\n <div className=\"adm-grid\">\n <div className=\"adm-span-7\">\n <HealthPanel\n health={health}\n money={money}\n caption={recurringCaption}\n />\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 <div className=\"adm-grid\">\n <div className=\"adm-span-12\">\n <SubscriptionsPanel recurring={data.recurring} span={span} />\n </div>\n </div>\n </>\n )}\n </>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { EmptyBlock } from "../audit/empty-block";
|
|
4
|
+
import ledgerStyles from "./ledger.module.css";
|
|
4
5
|
import { CohortPanel } from "./cohort-panel";
|
|
5
6
|
import { QualityPanel } from "./quality-panel";
|
|
6
7
|
function RevenueRetention({
|
|
@@ -10,19 +11,22 @@ function RevenueRetention({
|
|
|
10
11
|
caption
|
|
11
12
|
}) {
|
|
12
13
|
if (!quality) {
|
|
13
|
-
return /* @__PURE__ */ jsx(EmptyBlock, { message: "Retention
|
|
14
|
+
return /* @__PURE__ */ jsx(EmptyBlock, { message: "Retention shows here once there is enough history to compare." });
|
|
14
15
|
}
|
|
15
|
-
return /* @__PURE__ */ jsxs(
|
|
16
|
-
/* @__PURE__ */ jsx("
|
|
17
|
-
|
|
18
|
-
{
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
17
|
+
/* @__PURE__ */ jsx("p", { className: ledgerStyles.lede, children: "This shows how well Olive keeps the income it earns, and whether the merchants it signs up carry on selling." }),
|
|
18
|
+
/* @__PURE__ */ jsxs("div", { className: "adm-grid", children: [
|
|
19
|
+
/* @__PURE__ */ jsx("div", { className: "adm-span-5", children: /* @__PURE__ */ jsx(
|
|
20
|
+
QualityPanel,
|
|
21
|
+
{
|
|
22
|
+
quality,
|
|
23
|
+
money,
|
|
24
|
+
currency,
|
|
25
|
+
caption
|
|
26
|
+
}
|
|
27
|
+
) }),
|
|
28
|
+
/* @__PURE__ */ jsx("div", { className: "adm-span-7", children: /* @__PURE__ */ jsx(CohortPanel, { cohorts: quality.cohorts }) })
|
|
29
|
+
] })
|
|
26
30
|
] });
|
|
27
31
|
}
|
|
28
32
|
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 { 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
|
|
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 ledgerStyles from './ledger.module.css';\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 shows here once there is enough history to compare.\" />\n );\n }\n\n return (\n <>\n <p className={ledgerStyles.lede}>\n This shows how well Olive keeps the income it earns, and whether the merchants it signs up carry on selling.\n </p>\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}\n"],"mappings":";AAuBM,SAKF,UALE,KASA,YATA;AApBN,SAAS,kBAAkB;AAC3B,OAAO,kBAAkB;AACzB,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,iEAAgE;AAAA,EAExF;AAEA,SACE,iCACE;AAAA,wBAAC,OAAE,WAAW,aAAa,MAAM,0HAEjC;AAAA,IACA,qBAAC,SAAI,WAAU,YACf;AAAA,0BAAC,SAAI,WAAU,cACb;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA;AAAA,MACF,GACF;AAAA,MACA,oBAAC,SAAI,WAAU,cACb,8BAAC,eAAY,SAAS,QAAQ,SAAS,GACzC;AAAA,OACA;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -25,12 +25,15 @@ import {
|
|
|
25
25
|
useTabParam
|
|
26
26
|
} from "../_ui";
|
|
27
27
|
import { MerchantPaymentsView } from "./merchant-payments-view";
|
|
28
|
+
import { MerchantsView } from "./merchants-view";
|
|
28
29
|
import { RevenueOverview } from "./revenue-overview";
|
|
29
30
|
import { RevenueRetention } from "./revenue-retention";
|
|
30
31
|
import { SubscriptionsView } from "./subscriptions-view";
|
|
32
|
+
import { SummaryDownload } from "./summary-download";
|
|
31
33
|
const TABS = [
|
|
32
34
|
{ id: "overview", label: "Overview" },
|
|
33
35
|
{ id: "merchants", label: "Merchant payments" },
|
|
36
|
+
{ id: "by-merchant", label: "Merchants" },
|
|
34
37
|
{ id: "subscriptions", label: "Subscriptions" },
|
|
35
38
|
{ id: "retention", label: "Retention" }
|
|
36
39
|
];
|
|
@@ -96,6 +99,8 @@ function RevenueWorkspace() {
|
|
|
96
99
|
currency
|
|
97
100
|
}
|
|
98
101
|
);
|
|
102
|
+
} else if (tab === "by-merchant") {
|
|
103
|
+
body = /* @__PURE__ */ jsx(MerchantsView, { startDate: range.startDate, endDate: range.endDate });
|
|
99
104
|
} else if (tab === "subscriptions") {
|
|
100
105
|
body = /* @__PURE__ */ jsx(
|
|
101
106
|
SubscriptionsView,
|
|
@@ -140,6 +145,17 @@ function RevenueWorkspace() {
|
|
|
140
145
|
title: "Revenue",
|
|
141
146
|
description: `All the money moving through ${ADMIN_NAME}: what customers pay merchants, and what merchants pay ${ADMIN_NAME} for their plan.`,
|
|
142
147
|
actions: /* @__PURE__ */ jsxs("div", { className: "adm-toolbar", children: [
|
|
148
|
+
tab === "overview" ? /* @__PURE__ */ jsx(
|
|
149
|
+
SummaryDownload,
|
|
150
|
+
{
|
|
151
|
+
ledger: ledger.data,
|
|
152
|
+
revenue: data,
|
|
153
|
+
currency: focus ?? "NGN",
|
|
154
|
+
startDate: range.startDate,
|
|
155
|
+
endDate: range.endDate,
|
|
156
|
+
period
|
|
157
|
+
}
|
|
158
|
+
) : null,
|
|
143
159
|
/* @__PURE__ */ jsx(
|
|
144
160
|
CurrencyFilter,
|
|
145
161
|
{
|
|
@@ -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 useGatewayChannels,\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 { RevenueOverview } from './revenue-overview';\nimport { RevenueRetention } from './revenue-retention';\nimport { SubscriptionsView } from './subscriptions-view';\n\nconst TABS = [\n { id: 'overview', label: 'Overview' },\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 cockpit = usePlatformCockpit({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n\n const channels = useGatewayChannels({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n\n // The same totals the Merchant payments tab reads, one row only, for the method cards.\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 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 channels={channels.data?.byChannel}\n ledger={ledger.data}\n />\n );\n }\n\n return (\n <div>\n <PageHeader\n title=\"Revenue\"\n description={`All the money moving through ${ADMIN_NAME}: what customers pay merchants, and what merchants pay ${ADMIN_NAME} for their plan.`}\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":";
|
|
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 useGatewayChannels,\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 { MerchantsView } from './merchants-view';\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: 'Overview' },\n { id: 'merchants', label: 'Merchant payments' },\n { id: 'by-merchant', label: 'Merchants' },\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 cockpit = usePlatformCockpit({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n\n const channels = useGatewayChannels({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n\n // The same totals the Merchant payments tab reads, one row only, for the method cards.\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 === 'by-merchant') {\n body = <MerchantsView startDate={range.startDate} endDate={range.endDate} />;\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 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 channels={channels.data?.byChannel}\n ledger={ledger.data}\n />\n );\n }\n\n return (\n <div>\n <PageHeader\n title=\"Revenue\"\n description={`All the money moving through ${ADMIN_NAME}: what customers pay merchants, and what merchants pay ${ADMIN_NAME} for their plan.`}\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":[]}
|
|
@@ -8,49 +8,49 @@ function SubscriptionsPanel({
|
|
|
8
8
|
}) {
|
|
9
9
|
const rows = [
|
|
10
10
|
{
|
|
11
|
-
label: "
|
|
11
|
+
label: "Payment missed",
|
|
12
12
|
status: "past_due",
|
|
13
|
-
note: "
|
|
13
|
+
note: "Their last payment failed, so they may leave",
|
|
14
14
|
value: integer.format(recurring.pastDueSubscriptions),
|
|
15
15
|
tone: recurring.pastDueSubscriptions > 0 ? "danger" : "default"
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
|
-
label: "
|
|
18
|
+
label: "Set to end",
|
|
19
19
|
status: "non_renewing",
|
|
20
|
-
note: "
|
|
20
|
+
note: "Will not renew when the paid period ends",
|
|
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
|
|
27
|
+
note: "Paying for their plan",
|
|
28
28
|
value: integer.format(recurring.activeSubscriptions)
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
|
-
label: "
|
|
31
|
+
label: "On a trial",
|
|
32
32
|
status: "trialing",
|
|
33
|
-
note: "
|
|
33
|
+
note: "Trying a plan, not yet paying",
|
|
34
34
|
value: integer.format(recurring.trialingSubscriptions)
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
|
-
label: "
|
|
38
|
-
note: `Started in ${span}`,
|
|
37
|
+
label: "Joined this period",
|
|
38
|
+
note: `Started paying in ${span}`,
|
|
39
39
|
value: integer.format(recurring.newInRange)
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
|
-
label: "
|
|
43
|
-
note: `
|
|
42
|
+
label: "Left this period",
|
|
43
|
+
note: `Stopped paying in ${span}`,
|
|
44
44
|
value: integer.format(recurring.churnedInRange),
|
|
45
45
|
tone: recurring.churnedInRange > 0 ? "danger" : "default"
|
|
46
46
|
},
|
|
47
47
|
{
|
|
48
|
-
label: "Cancelled",
|
|
49
|
-
note: "
|
|
48
|
+
label: "Cancelled in total",
|
|
49
|
+
note: "Since the start",
|
|
50
50
|
value: integer.format(recurring.cancelledSubscriptions)
|
|
51
51
|
}
|
|
52
52
|
];
|
|
53
|
-
return /* @__PURE__ */ jsx(Panel, { title: "
|
|
53
|
+
return /* @__PURE__ */ jsx(Panel, { title: "Plan subscriptions today", caption: "Right now", 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: '
|
|
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 today\" 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,4BAA2B,SAAQ,aAAY,QAAO,sBACjE,8BAAC,cAAW,MAAY,GAC1B;AAEJ;","names":[]}
|