@withpotter/pages-admin 0.13.0-next.120 → 0.13.0-next.122

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/admin/_ui/admin.css +7 -0
  2. package/dist/admin/_ui/stat-card.js +11 -2
  3. package/dist/admin/_ui/stat-card.js.map +1 -1
  4. package/dist/admin/admins/access-dialog.js +2 -7
  5. package/dist/admin/admins/access-dialog.js.map +1 -1
  6. package/dist/admin/admins/invite-dialog.js +0 -6
  7. package/dist/admin/admins/invite-dialog.js.map +1 -1
  8. package/dist/admin/hooks.js +0 -7
  9. package/dist/admin/hooks.js.map +1 -1
  10. package/dist/admin/revenue/earnings/earnings-colors.js +23 -0
  11. package/dist/admin/revenue/earnings/earnings-colors.js.map +1 -0
  12. package/dist/admin/revenue/earnings/earnings-legend.js +38 -0
  13. package/dist/admin/revenue/earnings/earnings-legend.js.map +1 -0
  14. package/dist/admin/revenue/earnings/earnings-summary.js +194 -0
  15. package/dist/admin/revenue/earnings/earnings-summary.js.map +1 -0
  16. package/dist/admin/revenue/earnings/earnings-table.js +141 -0
  17. package/dist/admin/revenue/earnings/earnings-table.js.map +1 -0
  18. package/dist/admin/revenue/earnings/earnings.module.css +118 -0
  19. package/dist/admin/revenue/earnings/use-earnings.js +27 -0
  20. package/dist/admin/revenue/earnings/use-earnings.js.map +1 -0
  21. package/dist/admin/revenue/health-panel.js +5 -4
  22. package/dist/admin/revenue/health-panel.js.map +1 -1
  23. package/dist/admin/revenue/revenue-kpis.js +1 -1
  24. package/dist/admin/revenue/revenue-kpis.js.map +1 -1
  25. package/dist/admin/revenue/revenue-overview.js +18 -1
  26. package/dist/admin/revenue/revenue-overview.js.map +1 -1
  27. package/dist/admin/revenue/revenue-workspace.js +2 -0
  28. package/dist/admin/revenue/revenue-workspace.js.map +1 -1
  29. package/dist/admin/roles/page.js +4 -14
  30. package/dist/admin/roles/page.js.map +1 -1
  31. package/dist/admin/roles/role-dialog.js +1 -2
  32. package/dist/admin/roles/role-dialog.js.map +1 -1
  33. package/package.json +4 -4
  34. package/dist/admin/roles/role-requests.js +0 -80
  35. package/dist/admin/roles/role-requests.js.map +0 -1
@@ -209,6 +209,13 @@
209
209
  min-width: 0;
210
210
  }
211
211
 
212
+ /* A colour key along the top edge, matching the chart slice the card stands for. */
213
+ .adm-stat[data-accent] {
214
+ box-shadow:
215
+ inset 0 3px 0 var(--adm-stat-accent),
216
+ var(--adm-card-shadow);
217
+ }
218
+
212
219
  .adm-stat-head {
213
220
  display: grid;
214
221
  gap: 2px;
@@ -14,7 +14,8 @@ function StatCard({
14
14
  href,
15
15
  compact,
16
16
  note,
17
- alert
17
+ alert,
18
+ accent
18
19
  }) {
19
20
  const body = /* @__PURE__ */ jsxs(Fragment, { children: [
20
21
  /* @__PURE__ */ jsxs("div", { className: "adm-stat-head", children: [
@@ -60,7 +61,15 @@ function StatCard({
60
61
  }
61
62
  );
62
63
  }
63
- return /* @__PURE__ */ jsx("div", { className: `adm-stat${compact ? " adm-stat--compact" : ""}`, children: body });
64
+ return /* @__PURE__ */ jsx(
65
+ "div",
66
+ {
67
+ className: `adm-stat${compact ? " adm-stat--compact" : ""}`,
68
+ "data-accent": accent ? "" : void 0,
69
+ style: accent ? { "--adm-stat-accent": accent } : void 0,
70
+ children: body
71
+ }
72
+ );
64
73
  }
65
74
  const isWord = (value) => typeof value === "string" && !/\d/.test(value) && value.length > 10;
66
75
  function DeltaPill({ delta }) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/_ui/stat-card.tsx"],"sourcesContent":["import type { ComponentType, ReactNode } from 'react';\nimport Link from 'next/link';\nimport { Skeleton } from './skeleton';\nimport { Sparkline } from './sparkline';\n\nexport interface StatCardDelta {\n /** Period-over-period change. `null` = no prior baseline to compare against. */\n pct: number | null;\n /** When false, an increase is rendered red and a decrease green. Default true. */\n positiveIsGood?: boolean;\n /** Optional caption, e.g. \"vs prev 30d\". */\n caption?: string;\n}\n\n/**\n * KPI stat card: label, value (weight 500), optional sub-line or plain-text\n * delta, and an optional sparkline. No icon chip. Becomes a link with `href`.\n */\nexport function StatCard({\n label,\n value,\n sub,\n hint,\n isLoading,\n delta,\n spark,\n sparkColor,\n href,\n compact,\n note,\n alert,\n}: {\n label: string;\n value: ReactNode;\n sub?: string;\n /** Accepted for older callers; KPI cards no longer draw an icon chip. */\n icon?: ComponentType<{ size?: number; color?: string }>;\n hint?: string;\n isLoading?: boolean;\n delta?: StatCardDelta;\n spark?: number[];\n sparkColor?: string;\n href?: string;\n compact?: boolean;\n /** Quiet secondary text under the label. */\n note?: string;\n /** Paint the value red: a queue that needs someone now. */\n alert?: boolean;\n}) {\n const body = (\n <>\n <div className=\"adm-stat-head\">\n {hint ? (\n <span\n className=\"adm-stat-label adm-hint\"\n tabIndex={0}\n role=\"note\"\n aria-label={hint}\n >\n {label}\n <span className=\"adm-hint-bubble\" role=\"tooltip\">\n {hint}\n </span>\n </span>\n ) : (\n <span className=\"adm-stat-label\" title={label}>\n {label}\n </span>\n )}\n {note && <span className=\"adm-stat-note\">{note}</span>}\n </div>\n {isLoading ? (\n <Skeleton width={96} height={30} />\n ) : (\n <div\n className={`adm-stat-value${isWord(value) ? ' adm-stat-value--text' : ''}`}\n data-alert={alert || undefined}\n title={typeof value === 'string' ? value : undefined}\n >\n {value}\n </div>\n )}\n {!isLoading && (delta || sub || (spark && spark.length > 0)) && (\n <div className=\"adm-stat-foot\">\n <div className=\"adm-stat-foot-meta\">\n {delta ? (\n <DeltaPill delta={delta} />\n ) : sub ? (\n <span className=\"adm-stat-sub\" title={sub}>\n {sub}\n </span>\n ) : null}\n {delta?.caption && (\n <span className=\"adm-stat-caption\">{delta.caption}</span>\n )}\n </div>\n {spark && spark.length > 0 && (\n <Sparkline values={spark} color={sparkColor} />\n )}\n </div>\n )}\n </>\n );\n\n if (href) {\n return (\n <Link\n href={href}\n className={`adm-stat adm-stat-link${\n compact ? ' adm-stat--compact' : ''\n }`}\n >\n {body}\n </Link>\n );\n }\n return (\n <div className={`adm-stat${compact ? ' adm-stat--compact' : ''}`}>\n {body}\n </div>\n );\n}\n\n/** A label rather than a number: set at body size so it never shouts. */\nconst isWord = (value: ReactNode) =>\n typeof value === 'string' && !/\\d/.test(value) && value.length > 10;\n\nfunction DeltaPill({ delta }: { delta: StatCardDelta }) {\n const positiveIsGood = delta.positiveIsGood ?? true;\n if (delta.pct === null) {\n return <span className=\"adm-delta adm-delta--flat\">New</span>;\n }\n const up = delta.pct > 0;\n const flat = delta.pct === 0;\n const good = flat ? false : up === positiveIsGood;\n const tone = flat ? 'flat' : good ? 'good' : 'bad';\n // The sign carries the direction in words, so no arrow glyph is needed.\n const sign = flat ? '' : up ? '+' : '\\u2212';\n return (\n <span className={`adm-delta adm-delta--${tone}`}>\n {sign}\n {Math.abs(delta.pct)}%\n </span>\n );\n}\n"],"mappings":"AAkDI,mBAUQ,KAPF,YAHN;AAjDJ,OAAO,UAAU;AACjB,SAAS,gBAAgB;AACzB,SAAS,iBAAiB;AAenB,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAiBG;AACD,QAAM,OACJ,iCACE;AAAA,yBAAC,SAAI,WAAU,iBACZ;AAAA,aACC;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,UAAU;AAAA,UACV,MAAK;AAAA,UACL,cAAY;AAAA,UAEX;AAAA;AAAA,YACD,oBAAC,UAAK,WAAU,mBAAkB,MAAK,WACpC,gBACH;AAAA;AAAA;AAAA,MACF,IAEA,oBAAC,UAAK,WAAU,kBAAiB,OAAO,OACrC,iBACH;AAAA,MAED,QAAQ,oBAAC,UAAK,WAAU,iBAAiB,gBAAK;AAAA,OACjD;AAAA,IACC,YACC,oBAAC,YAAS,OAAO,IAAI,QAAQ,IAAI,IAEjC;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,iBAAiB,OAAO,KAAK,IAAI,0BAA0B,EAAE;AAAA,QACxE,cAAY,SAAS;AAAA,QACrB,OAAO,OAAO,UAAU,WAAW,QAAQ;AAAA,QAE1C;AAAA;AAAA,IACH;AAAA,IAED,CAAC,cAAc,SAAS,OAAQ,SAAS,MAAM,SAAS,MACvD,qBAAC,SAAI,WAAU,iBACb;AAAA,2BAAC,SAAI,WAAU,sBACZ;AAAA,gBACC,oBAAC,aAAU,OAAc,IACvB,MACF,oBAAC,UAAK,WAAU,gBAAe,OAAO,KACnC,eACH,IACE;AAAA,QACH,OAAO,WACN,oBAAC,UAAK,WAAU,oBAAoB,gBAAM,SAAQ;AAAA,SAEtD;AAAA,MACC,SAAS,MAAM,SAAS,KACvB,oBAAC,aAAU,QAAQ,OAAO,OAAO,YAAY;AAAA,OAEjD;AAAA,KAEJ;AAGF,MAAI,MAAM;AACR,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,WAAW,yBACT,UAAU,uBAAuB,EACnC;AAAA,QAEC;AAAA;AAAA,IACH;AAAA,EAEJ;AACA,SACE,oBAAC,SAAI,WAAW,WAAW,UAAU,uBAAuB,EAAE,IAC3D,gBACH;AAEJ;AAGA,MAAM,SAAS,CAAC,UACd,OAAO,UAAU,YAAY,CAAC,KAAK,KAAK,KAAK,KAAK,MAAM,SAAS;AAEnE,SAAS,UAAU,EAAE,MAAM,GAA6B;AACtD,QAAM,iBAAiB,MAAM,kBAAkB;AAC/C,MAAI,MAAM,QAAQ,MAAM;AACtB,WAAO,oBAAC,UAAK,WAAU,6BAA4B,iBAAG;AAAA,EACxD;AACA,QAAM,KAAK,MAAM,MAAM;AACvB,QAAM,OAAO,MAAM,QAAQ;AAC3B,QAAM,OAAO,OAAO,QAAQ,OAAO;AACnC,QAAM,OAAO,OAAO,SAAS,OAAO,SAAS;AAE7C,QAAM,OAAO,OAAO,KAAK,KAAK,MAAM;AACpC,SACE,qBAAC,UAAK,WAAW,wBAAwB,IAAI,IAC1C;AAAA;AAAA,IACA,KAAK,IAAI,MAAM,GAAG;AAAA,IAAE;AAAA,KACvB;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/_ui/stat-card.tsx"],"sourcesContent":["import type { ComponentType, CSSProperties, ReactNode } from 'react';\nimport Link from 'next/link';\nimport { Skeleton } from './skeleton';\nimport { Sparkline } from './sparkline';\n\nexport interface StatCardDelta {\n /** Period-over-period change. `null` = no prior baseline to compare against. */\n pct: number | null;\n /** When false, an increase is rendered red and a decrease green. Default true. */\n positiveIsGood?: boolean;\n /** Optional caption, e.g. \"vs prev 30d\". */\n caption?: string;\n}\n\n/**\n * KPI stat card: label, value (weight 500), optional sub-line or plain-text\n * delta, and an optional sparkline. No icon chip. Becomes a link with `href`.\n */\nexport function StatCard({\n label,\n value,\n sub,\n hint,\n isLoading,\n delta,\n spark,\n sparkColor,\n href,\n compact,\n note,\n alert,\n accent,\n}: {\n label: string;\n value: ReactNode;\n sub?: string;\n /** Accepted for older callers; KPI cards no longer draw an icon chip. */\n icon?: ComponentType<{ size?: number; color?: string }>;\n hint?: string;\n isLoading?: boolean;\n delta?: StatCardDelta;\n spark?: number[];\n sparkColor?: string;\n href?: string;\n compact?: boolean;\n /** Quiet secondary text under the label. */\n note?: string;\n /** Paint the value red: a queue that needs someone now. */\n alert?: boolean;\n /** A colour key for the card: a bar along its top edge, so it can match a chart slice. */\n accent?: string;\n}) {\n const body = (\n <>\n <div className=\"adm-stat-head\">\n {hint ? (\n <span\n className=\"adm-stat-label adm-hint\"\n tabIndex={0}\n role=\"note\"\n aria-label={hint}\n >\n {label}\n <span className=\"adm-hint-bubble\" role=\"tooltip\">\n {hint}\n </span>\n </span>\n ) : (\n <span className=\"adm-stat-label\" title={label}>\n {label}\n </span>\n )}\n {note && <span className=\"adm-stat-note\">{note}</span>}\n </div>\n {isLoading ? (\n <Skeleton width={96} height={30} />\n ) : (\n <div\n className={`adm-stat-value${isWord(value) ? ' adm-stat-value--text' : ''}`}\n data-alert={alert || undefined}\n title={typeof value === 'string' ? value : undefined}\n >\n {value}\n </div>\n )}\n {!isLoading && (delta || sub || (spark && spark.length > 0)) && (\n <div className=\"adm-stat-foot\">\n <div className=\"adm-stat-foot-meta\">\n {delta ? (\n <DeltaPill delta={delta} />\n ) : sub ? (\n <span className=\"adm-stat-sub\" title={sub}>\n {sub}\n </span>\n ) : null}\n {delta?.caption && (\n <span className=\"adm-stat-caption\">{delta.caption}</span>\n )}\n </div>\n {spark && spark.length > 0 && (\n <Sparkline values={spark} color={sparkColor} />\n )}\n </div>\n )}\n </>\n );\n\n if (href) {\n return (\n <Link\n href={href}\n className={`adm-stat adm-stat-link${\n compact ? ' adm-stat--compact' : ''\n }`}\n >\n {body}\n </Link>\n );\n }\n return (\n <div\n className={`adm-stat${compact ? ' adm-stat--compact' : ''}`}\n data-accent={accent ? '' : undefined}\n style={accent ? ({ '--adm-stat-accent': accent } as CSSProperties) : undefined}\n >\n {body}\n </div>\n );\n}\n\n/** A label rather than a number: set at body size so it never shouts. */\nconst isWord = (value: ReactNode) =>\n typeof value === 'string' && !/\\d/.test(value) && value.length > 10;\n\nfunction DeltaPill({ delta }: { delta: StatCardDelta }) {\n const positiveIsGood = delta.positiveIsGood ?? true;\n if (delta.pct === null) {\n return <span className=\"adm-delta adm-delta--flat\">New</span>;\n }\n const up = delta.pct > 0;\n const flat = delta.pct === 0;\n const good = flat ? false : up === positiveIsGood;\n const tone = flat ? 'flat' : good ? 'good' : 'bad';\n // The sign carries the direction in words, so no arrow glyph is needed.\n const sign = flat ? '' : up ? '+' : '\\u2212';\n return (\n <span className={`adm-delta adm-delta--${tone}`}>\n {sign}\n {Math.abs(delta.pct)}%\n </span>\n );\n}\n"],"mappings":"AAqDI,mBAUQ,KAPF,YAHN;AApDJ,OAAO,UAAU;AACjB,SAAS,gBAAgB;AACzB,SAAS,iBAAiB;AAenB,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAmBG;AACD,QAAM,OACJ,iCACE;AAAA,yBAAC,SAAI,WAAU,iBACZ;AAAA,aACC;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,UAAU;AAAA,UACV,MAAK;AAAA,UACL,cAAY;AAAA,UAEX;AAAA;AAAA,YACD,oBAAC,UAAK,WAAU,mBAAkB,MAAK,WACpC,gBACH;AAAA;AAAA;AAAA,MACF,IAEA,oBAAC,UAAK,WAAU,kBAAiB,OAAO,OACrC,iBACH;AAAA,MAED,QAAQ,oBAAC,UAAK,WAAU,iBAAiB,gBAAK;AAAA,OACjD;AAAA,IACC,YACC,oBAAC,YAAS,OAAO,IAAI,QAAQ,IAAI,IAEjC;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,iBAAiB,OAAO,KAAK,IAAI,0BAA0B,EAAE;AAAA,QACxE,cAAY,SAAS;AAAA,QACrB,OAAO,OAAO,UAAU,WAAW,QAAQ;AAAA,QAE1C;AAAA;AAAA,IACH;AAAA,IAED,CAAC,cAAc,SAAS,OAAQ,SAAS,MAAM,SAAS,MACvD,qBAAC,SAAI,WAAU,iBACb;AAAA,2BAAC,SAAI,WAAU,sBACZ;AAAA,gBACC,oBAAC,aAAU,OAAc,IACvB,MACF,oBAAC,UAAK,WAAU,gBAAe,OAAO,KACnC,eACH,IACE;AAAA,QACH,OAAO,WACN,oBAAC,UAAK,WAAU,oBAAoB,gBAAM,SAAQ;AAAA,SAEtD;AAAA,MACC,SAAS,MAAM,SAAS,KACvB,oBAAC,aAAU,QAAQ,OAAO,OAAO,YAAY;AAAA,OAEjD;AAAA,KAEJ;AAGF,MAAI,MAAM;AACR,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,WAAW,yBACT,UAAU,uBAAuB,EACnC;AAAA,QAEC;AAAA;AAAA,IACH;AAAA,EAEJ;AACA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,WAAW,UAAU,uBAAuB,EAAE;AAAA,MACzD,eAAa,SAAS,KAAK;AAAA,MAC3B,OAAO,SAAU,EAAE,qBAAqB,OAAO,IAAsB;AAAA,MAEpE;AAAA;AAAA,EACH;AAEJ;AAGA,MAAM,SAAS,CAAC,UACd,OAAO,UAAU,YAAY,CAAC,KAAK,KAAK,KAAK,KAAK,MAAM,SAAS;AAEnE,SAAS,UAAU,EAAE,MAAM,GAA6B;AACtD,QAAM,iBAAiB,MAAM,kBAAkB;AAC/C,MAAI,MAAM,QAAQ,MAAM;AACtB,WAAO,oBAAC,UAAK,WAAU,6BAA4B,iBAAG;AAAA,EACxD;AACA,QAAM,KAAK,MAAM,MAAM;AACvB,QAAM,OAAO,MAAM,QAAQ;AAC3B,QAAM,OAAO,OAAO,QAAQ,OAAO;AACnC,QAAM,OAAO,OAAO,SAAS,OAAO,SAAS;AAE7C,QAAM,OAAO,OAAO,KAAK,KAAK,MAAM;AACpC,SACE,qBAAC,UAAK,WAAW,wBAAwB,IAAI,IAC1C;AAAA;AAAA,IACA,KAAK,IAAI,MAAM,GAAG;AAAA,IAAE;AAAA,KACvB;AAEJ;","names":[]}
@@ -53,14 +53,9 @@ function AccessForm({
53
53
  () => kind === "roles" ? (a.roleIds ?? []).filter((id) => roles.some((r) => r.id === id)) : a.permissions ?? []
54
54
  );
55
55
  const save = useMutation({
56
- mutationFn: async () => kind === "roles" ? platformAdminApi.setAdminRoles(a.id, draft) : platformAdminApi.setAdminPermissions(a.id, draft),
56
+ mutationFn: () => kind === "roles" ? platformAdminApi.setAdminRoles(a.id, draft) : platformAdminApi.setAdminPermissions(a.id, draft),
57
57
  onSuccess: () => {
58
- toast(
59
- kind === "roles" ? {
60
- title: "Sent for approval",
61
- description: `Another super admin has to approve the roles for ${a.name ?? a.email}.`
62
- } : { title: `Access updated for ${a.name ?? a.email}` }
63
- );
58
+ toast({ title: `Access updated for ${a.name ?? a.email}` });
64
59
  onSaved();
65
60
  onClose();
66
61
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/admins/access-dialog.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { useMutation } from '@tanstack/react-query';\nimport { Button, FormDialog, useToast } from '@withpotter/ui-kit';\nimport {\n platformAdminApi,\n type AdminRow as Admin,\n type RoleRow,\n} from '@withpotter/lib/admin-api';\nimport { PermissionPicker, toggleIn } from '../roles/permission-picker';\nimport { RoleChoices } from './role-choices';\nimport styles from './admins.module.css';\n\nexport type AccessEdit = { admin: Admin; kind: 'roles' | 'permissions' };\nconst FORM_ID = 'admin-access-form';\n\n/** Change one admin's roles or direct permissions. */\nexport function AccessDialog({\n edit,\n catalog,\n roles,\n onClose,\n onSaved,\n}: {\n edit: AccessEdit | null;\n catalog: string[];\n roles: RoleRow[];\n onClose: () => void;\n onSaved: () => void;\n}) {\n // Count each opening so a cancelled edit never reappears on the next open.\n const [last, setLast] = useState(edit);\n const [opens, setOpens] = useState(0);\n const [wasOpen, setWasOpen] = useState(false);\n if (edit && edit !== last) setLast(edit);\n if (Boolean(edit) !== wasOpen) {\n setWasOpen(Boolean(edit));\n if (edit) setOpens((n) => n + 1);\n }\n const shown = edit ?? last;\n if (!shown) return null;\n return (\n <AccessForm\n key={`${opens}-${shown.admin.id}-${shown.kind}`}\n open={edit !== null}\n edit={shown}\n catalog={catalog}\n roles={roles}\n onClose={onClose}\n onSaved={onSaved}\n />\n );\n}\n\nfunction AccessForm({\n open,\n edit: { admin: a, kind },\n catalog,\n roles,\n onClose,\n onSaved,\n}: {\n open: boolean;\n edit: AccessEdit;\n catalog: string[];\n roles: RoleRow[];\n onClose: () => void;\n onSaved: () => void;\n}) {\n const { toast } = useToast();\n // Deleted roles linger in an admin's roleIds (the API keeps them); drop\n // them so the count is honest and Save does not send dead ids back.\n const [draft, setDraft] = useState<string[]>(() =>\n kind === 'roles'\n ? (a.roleIds ?? []).filter((id) => roles.some((r) => r.id === id))\n : (a.permissions ?? []),\n );\n const save = useMutation({\n mutationFn: async (): Promise<unknown> =>\n kind === 'roles'\n ? platformAdminApi.setAdminRoles(a.id, draft)\n : platformAdminApi.setAdminPermissions(a.id, draft),\n onSuccess: () => {\n toast(\n kind === 'roles'\n ? {\n title: 'Sent for approval',\n description: `Another super admin has to approve the roles for ${a.name ?? a.email}.`,\n }\n : { title: `Access updated for ${a.name ?? a.email}` },\n );\n onSaved();\n onClose();\n },\n onError: (error) =>\n toast({\n title: 'Access was not updated',\n description: error.message,\n variant: 'destructive',\n }),\n });\n\n return (\n <FormDialog\n open={open}\n size={kind === 'roles' ? 'md' : 'lg'}\n title={kind === 'roles' ? 'Roles' : 'Direct permissions'}\n description={a.email}\n onClose={onClose}\n footer={\n <>\n <span className={styles.footNote}>{draft.length} selected</span>\n <div className={styles.footActions}>\n <Button type=\"button\" variant=\"secondary\" onClick={onClose}>\n Cancel\n </Button>\n <Button\n type=\"submit\"\n form={FORM_ID}\n disabled={save.isPending}\n loading={save.isPending}\n >\n Save\n </Button>\n </div>\n </>\n }\n >\n <form\n id={FORM_ID}\n onSubmit={(e) => {\n e.preventDefault();\n if (!save.isPending) save.mutate();\n }}\n >\n {kind === 'roles' ? (\n <RoleChoices\n legend=\"Their access is the union of every role ticked here.\"\n roles={roles}\n value={draft}\n onToggle={(id) => setDraft(toggleIn(id))}\n emptyLabel=\"No roles available.\"\n />\n ) : (\n <PermissionPicker\n legend=\"Granted on top of their roles\"\n keys={catalog}\n value={draft}\n onToggle={(key) => setDraft(toggleIn(key))}\n onSet={setDraft}\n />\n )}\n </form>\n </FormDialog>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/admins/access-dialog.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { useMutation } from '@tanstack/react-query';\nimport { Button, FormDialog, useToast } from '@withpotter/ui-kit';\nimport {\n platformAdminApi,\n type AdminRow as Admin,\n type RoleRow,\n} from '@withpotter/lib/admin-api';\nimport { PermissionPicker, toggleIn } from '../roles/permission-picker';\nimport { RoleChoices } from './role-choices';\nimport styles from './admins.module.css';\n\nexport type AccessEdit = { admin: Admin; kind: 'roles' | 'permissions' };\nconst FORM_ID = 'admin-access-form';\n\n/** Change one admin's roles or direct permissions. */\nexport function AccessDialog({\n edit,\n catalog,\n roles,\n onClose,\n onSaved,\n}: {\n edit: AccessEdit | null;\n catalog: string[];\n roles: RoleRow[];\n onClose: () => void;\n onSaved: () => void;\n}) {\n // Count each opening so a cancelled edit never reappears on the next open.\n const [last, setLast] = useState(edit);\n const [opens, setOpens] = useState(0);\n const [wasOpen, setWasOpen] = useState(false);\n if (edit && edit !== last) setLast(edit);\n if (Boolean(edit) !== wasOpen) {\n setWasOpen(Boolean(edit));\n if (edit) setOpens((n) => n + 1);\n }\n const shown = edit ?? last;\n if (!shown) return null;\n return (\n <AccessForm\n key={`${opens}-${shown.admin.id}-${shown.kind}`}\n open={edit !== null}\n edit={shown}\n catalog={catalog}\n roles={roles}\n onClose={onClose}\n onSaved={onSaved}\n />\n );\n}\n\nfunction AccessForm({\n open,\n edit: { admin: a, kind },\n catalog,\n roles,\n onClose,\n onSaved,\n}: {\n open: boolean;\n edit: AccessEdit;\n catalog: string[];\n roles: RoleRow[];\n onClose: () => void;\n onSaved: () => void;\n}) {\n const { toast } = useToast();\n // Deleted roles linger in an admin's roleIds (the API keeps them); drop\n // them so the count is honest and Save does not send dead ids back.\n const [draft, setDraft] = useState<string[]>(() =>\n kind === 'roles'\n ? (a.roleIds ?? []).filter((id) => roles.some((r) => r.id === id))\n : (a.permissions ?? []),\n );\n const save = useMutation({\n mutationFn: () =>\n kind === 'roles'\n ? platformAdminApi.setAdminRoles(a.id, draft)\n : platformAdminApi.setAdminPermissions(a.id, draft),\n onSuccess: () => {\n toast({ title: `Access updated for ${a.name ?? a.email}` });\n onSaved();\n onClose();\n },\n onError: (error) =>\n toast({\n title: 'Access was not updated',\n description: error.message,\n variant: 'destructive',\n }),\n });\n\n return (\n <FormDialog\n open={open}\n size={kind === 'roles' ? 'md' : 'lg'}\n title={kind === 'roles' ? 'Roles' : 'Direct permissions'}\n description={a.email}\n onClose={onClose}\n footer={\n <>\n <span className={styles.footNote}>{draft.length} selected</span>\n <div className={styles.footActions}>\n <Button type=\"button\" variant=\"secondary\" onClick={onClose}>\n Cancel\n </Button>\n <Button\n type=\"submit\"\n form={FORM_ID}\n disabled={save.isPending}\n loading={save.isPending}\n >\n Save\n </Button>\n </div>\n </>\n }\n >\n <form\n id={FORM_ID}\n onSubmit={(e) => {\n e.preventDefault();\n if (!save.isPending) save.mutate();\n }}\n >\n {kind === 'roles' ? (\n <RoleChoices\n legend=\"Their access is the union of every role ticked here.\"\n roles={roles}\n value={draft}\n onToggle={(id) => setDraft(toggleIn(id))}\n emptyLabel=\"No roles available.\"\n />\n ) : (\n <PermissionPicker\n legend=\"Granted on top of their roles\"\n keys={catalog}\n value={draft}\n onToggle={(key) => setDraft(toggleIn(key))}\n onSet={setDraft}\n />\n )}\n </form>\n </FormDialog>\n );\n}\n"],"mappings":"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","names":[]}
@@ -25,12 +25,6 @@ function InviteDialog({
25
25
  const invite = useMutation({
26
26
  mutationFn: () => platformAdminApi.inviteAdmin(email, role, perms, roleIds),
27
27
  onSuccess: (r) => {
28
- if (r.pendingRoles) {
29
- toast({
30
- title: "Roles sent for approval",
31
- description: "Another super admin has to approve them before this admin holds them."
32
- });
33
- }
34
28
  onInvited(r);
35
29
  setEmail("");
36
30
  setRole("admin");
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/admins/invite-dialog.tsx"],"sourcesContent":["'use client';\n\nimport { useId, useState } from 'react';\nimport { useMutation } from '@tanstack/react-query';\nimport { Button, FormDialog, Input, useToast } from '@withpotter/ui-kit';\nimport { Select } from '@withpotter/ui-kit/select';\nimport { platformAdminApi, type RoleRow } from '@withpotter/lib/admin-api';\nimport { PermissionPicker, toggleIn } from '../roles/permission-picker';\nimport { RoleChoices } from './role-choices';\nimport styles from './admins.module.css';\n\ntype AdminRole = 'admin' | 'superadmin';\nconst FORM_ID = 'invite-admin-form';\n\n/** Email and access level, then roles and any direct grants. */\nexport function InviteDialog({\n open,\n catalog,\n roles,\n onClose,\n onInvited,\n}: {\n open: boolean;\n catalog: string[];\n roles: RoleRow[];\n onClose: () => void;\n onInvited: (temp: { email: string; tempPassword: string }) => void;\n}) {\n const { toast } = useToast();\n const roleFieldId = useId();\n const [email, setEmail] = useState('');\n const [role, setRole] = useState<AdminRole>('admin');\n const [perms, setPerms] = useState<string[]>([]);\n const [roleIds, setRoleIds] = useState<string[]>([]);\n\n const invite = useMutation({\n mutationFn: () => platformAdminApi.inviteAdmin(email, role, perms, roleIds),\n onSuccess: (r) => {\n if (r.pendingRoles) {\n toast({\n title: 'Roles sent for approval',\n description:\n 'Another super admin has to approve them before this admin holds them.',\n });\n }\n onInvited(r);\n setEmail('');\n setRole('admin');\n setPerms([]);\n setRoleIds([]);\n },\n onError: (error) =>\n toast({\n title: 'The invite was not sent',\n description: error.message,\n variant: 'destructive',\n }),\n });\n const canSend = email.trim().length > 0 && !invite.isPending;\n\n return (\n <FormDialog\n open={open}\n size=\"lg\"\n title=\"Invite admin\"\n description=\"They get a temporary password and choose their own on first sign-in.\"\n onClose={onClose}\n footer={\n <>\n <span className={styles.footNote}>\n {role === 'superadmin'\n ? 'Full access'\n : `${roleIds.length} role${roleIds.length === 1 ? '' : 's'} · ${perms.length} direct`}\n </span>\n <div className={styles.footActions}>\n <Button type=\"button\" variant=\"secondary\" onClick={onClose}>\n Cancel\n </Button>\n <Button\n type=\"submit\"\n form={FORM_ID}\n disabled={!canSend}\n loading={invite.isPending}\n >\n Send invite\n </Button>\n </div>\n </>\n }\n >\n <form\n id={FORM_ID}\n className={styles.form}\n onSubmit={(e) => {\n e.preventDefault();\n if (canSend) invite.mutate();\n }}\n >\n <div className={styles.formRow}>\n <Input\n label=\"Email\"\n type=\"email\"\n placeholder=\"name@company.com\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n autoFocus\n />\n <div className={styles.field}>\n <label htmlFor={roleFieldId} className={styles.fieldLabel}>\n Access level\n </label>\n <Select\n id={roleFieldId}\n value={role}\n onChange={(v) => setRole(v as AdminRole)}\n options={[\n { value: 'admin', label: 'Admin' },\n { value: 'superadmin', label: 'Superadmin' },\n ]}\n />\n </div>\n </div>\n\n {role === 'admin' ? (\n <>\n <RoleChoices\n legend=\"Roles (permissions stack as a union)\"\n roles={roles}\n value={roleIds}\n onToggle={(id) => setRoleIds(toggleIn(id))}\n emptyLabel=\"No roles yet — create one on the Roles page.\"\n />\n <PermissionPicker\n legend=\"Extra permissions (optional)\"\n keys={catalog}\n value={perms}\n onToggle={(key) => setPerms(toggleIn(key))}\n onSet={setPerms}\n />\n </>\n ) : (\n <p className={styles.note}>\n Superadmins have full access, so no roles or permissions are needed.\n </p>\n )}\n </form>\n </FormDialog>\n );\n}\n"],"mappings":";AAoEQ,mBACE,KAKA,YANF;AAlER,SAAS,OAAO,gBAAgB;AAChC,SAAS,mBAAmB;AAC5B,SAAS,QAAQ,YAAY,OAAO,gBAAgB;AACpD,SAAS,cAAc;AACvB,SAAS,wBAAsC;AAC/C,SAAS,kBAAkB,gBAAgB;AAC3C,SAAS,mBAAmB;AAC5B,OAAO,YAAY;AAGnB,MAAM,UAAU;AAGT,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,QAAM,EAAE,MAAM,IAAI,SAAS;AAC3B,QAAM,cAAc,MAAM;AAC1B,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,EAAE;AACrC,QAAM,CAAC,MAAM,OAAO,IAAI,SAAoB,OAAO;AACnD,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAmB,CAAC,CAAC;AAC/C,QAAM,CAAC,SAAS,UAAU,IAAI,SAAmB,CAAC,CAAC;AAEnD,QAAM,SAAS,YAAY;AAAA,IACzB,YAAY,MAAM,iBAAiB,YAAY,OAAO,MAAM,OAAO,OAAO;AAAA,IAC1E,WAAW,CAAC,MAAM;AAChB,UAAI,EAAE,cAAc;AAClB,cAAM;AAAA,UACJ,OAAO;AAAA,UACP,aACE;AAAA,QACJ,CAAC;AAAA,MACH;AACA,gBAAU,CAAC;AACX,eAAS,EAAE;AACX,cAAQ,OAAO;AACf,eAAS,CAAC,CAAC;AACX,iBAAW,CAAC,CAAC;AAAA,IACf;AAAA,IACA,SAAS,CAAC,UACR,MAAM;AAAA,MACJ,OAAO;AAAA,MACP,aAAa,MAAM;AAAA,MACnB,SAAS;AAAA,IACX,CAAC;AAAA,EACL,CAAC;AACD,QAAM,UAAU,MAAM,KAAK,EAAE,SAAS,KAAK,CAAC,OAAO;AAEnD,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,MAAK;AAAA,MACL,OAAM;AAAA,MACN,aAAY;AAAA,MACZ;AAAA,MACA,QACE,iCACE;AAAA,4BAAC,UAAK,WAAW,OAAO,UACrB,mBAAS,eACN,gBACA,GAAG,QAAQ,MAAM,QAAQ,QAAQ,WAAW,IAAI,KAAK,GAAG,SAAM,MAAM,MAAM,WAChF;AAAA,QACA,qBAAC,SAAI,WAAW,OAAO,aACrB;AAAA,8BAAC,UAAO,MAAK,UAAS,SAAQ,aAAY,SAAS,SAAS,oBAE5D;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,MAAM;AAAA,cACN,UAAU,CAAC;AAAA,cACX,SAAS,OAAO;AAAA,cACjB;AAAA;AAAA,UAED;AAAA,WACF;AAAA,SACF;AAAA,MAGF;AAAA,QAAC;AAAA;AAAA,UACC,IAAI;AAAA,UACJ,WAAW,OAAO;AAAA,UAClB,UAAU,CAAC,MAAM;AACf,cAAE,eAAe;AACjB,gBAAI,QAAS,QAAO,OAAO;AAAA,UAC7B;AAAA,UAEA;AAAA,iCAAC,SAAI,WAAW,OAAO,SACrB;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,OAAM;AAAA,kBACN,MAAK;AAAA,kBACL,aAAY;AAAA,kBACZ,OAAO;AAAA,kBACP,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,kBACxC,WAAS;AAAA;AAAA,cACX;AAAA,cACA,qBAAC,SAAI,WAAW,OAAO,OACrB;AAAA,oCAAC,WAAM,SAAS,aAAa,WAAW,OAAO,YAAY,0BAE3D;AAAA,gBACA;AAAA,kBAAC;AAAA;AAAA,oBACC,IAAI;AAAA,oBACJ,OAAO;AAAA,oBACP,UAAU,CAAC,MAAM,QAAQ,CAAc;AAAA,oBACvC,SAAS;AAAA,sBACP,EAAE,OAAO,SAAS,OAAO,QAAQ;AAAA,sBACjC,EAAE,OAAO,cAAc,OAAO,aAAa;AAAA,oBAC7C;AAAA;AAAA,gBACF;AAAA,iBACF;AAAA,eACF;AAAA,YAEC,SAAS,UACR,iCACE;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,QAAO;AAAA,kBACP;AAAA,kBACA,OAAO;AAAA,kBACP,UAAU,CAAC,OAAO,WAAW,SAAS,EAAE,CAAC;AAAA,kBACzC,YAAW;AAAA;AAAA,cACb;AAAA,cACA;AAAA,gBAAC;AAAA;AAAA,kBACC,QAAO;AAAA,kBACP,MAAM;AAAA,kBACN,OAAO;AAAA,kBACP,UAAU,CAAC,QAAQ,SAAS,SAAS,GAAG,CAAC;AAAA,kBACzC,OAAO;AAAA;AAAA,cACT;AAAA,eACF,IAEA,oBAAC,OAAE,WAAW,OAAO,MAAM,kFAE3B;AAAA;AAAA;AAAA,MAEJ;AAAA;AAAA,EACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/admins/invite-dialog.tsx"],"sourcesContent":["'use client';\n\nimport { useId, useState } from 'react';\nimport { useMutation } from '@tanstack/react-query';\nimport { Button, FormDialog, Input, useToast } from '@withpotter/ui-kit';\nimport { Select } from '@withpotter/ui-kit/select';\nimport { platformAdminApi, type RoleRow } from '@withpotter/lib/admin-api';\nimport { PermissionPicker, toggleIn } from '../roles/permission-picker';\nimport { RoleChoices } from './role-choices';\nimport styles from './admins.module.css';\n\ntype AdminRole = 'admin' | 'superadmin';\nconst FORM_ID = 'invite-admin-form';\n\n/** Email and access level, then roles and any direct grants. */\nexport function InviteDialog({\n open,\n catalog,\n roles,\n onClose,\n onInvited,\n}: {\n open: boolean;\n catalog: string[];\n roles: RoleRow[];\n onClose: () => void;\n onInvited: (temp: { email: string; tempPassword: string }) => void;\n}) {\n const { toast } = useToast();\n const roleFieldId = useId();\n const [email, setEmail] = useState('');\n const [role, setRole] = useState<AdminRole>('admin');\n const [perms, setPerms] = useState<string[]>([]);\n const [roleIds, setRoleIds] = useState<string[]>([]);\n\n const invite = useMutation({\n mutationFn: () => platformAdminApi.inviteAdmin(email, role, perms, roleIds),\n onSuccess: (r) => {\n onInvited(r);\n setEmail('');\n setRole('admin');\n setPerms([]);\n setRoleIds([]);\n },\n onError: (error) =>\n toast({\n title: 'The invite was not sent',\n description: error.message,\n variant: 'destructive',\n }),\n });\n const canSend = email.trim().length > 0 && !invite.isPending;\n\n return (\n <FormDialog\n open={open}\n size=\"lg\"\n title=\"Invite admin\"\n description=\"They get a temporary password and choose their own on first sign-in.\"\n onClose={onClose}\n footer={\n <>\n <span className={styles.footNote}>\n {role === 'superadmin'\n ? 'Full access'\n : `${roleIds.length} role${roleIds.length === 1 ? '' : 's'} · ${perms.length} direct`}\n </span>\n <div className={styles.footActions}>\n <Button type=\"button\" variant=\"secondary\" onClick={onClose}>\n Cancel\n </Button>\n <Button\n type=\"submit\"\n form={FORM_ID}\n disabled={!canSend}\n loading={invite.isPending}\n >\n Send invite\n </Button>\n </div>\n </>\n }\n >\n <form\n id={FORM_ID}\n className={styles.form}\n onSubmit={(e) => {\n e.preventDefault();\n if (canSend) invite.mutate();\n }}\n >\n <div className={styles.formRow}>\n <Input\n label=\"Email\"\n type=\"email\"\n placeholder=\"name@company.com\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n autoFocus\n />\n <div className={styles.field}>\n <label htmlFor={roleFieldId} className={styles.fieldLabel}>\n Access level\n </label>\n <Select\n id={roleFieldId}\n value={role}\n onChange={(v) => setRole(v as AdminRole)}\n options={[\n { value: 'admin', label: 'Admin' },\n { value: 'superadmin', label: 'Superadmin' },\n ]}\n />\n </div>\n </div>\n\n {role === 'admin' ? (\n <>\n <RoleChoices\n legend=\"Roles (permissions stack as a union)\"\n roles={roles}\n value={roleIds}\n onToggle={(id) => setRoleIds(toggleIn(id))}\n emptyLabel=\"No roles yet — create one on the Roles page.\"\n />\n <PermissionPicker\n legend=\"Extra permissions (optional)\"\n keys={catalog}\n value={perms}\n onToggle={(key) => setPerms(toggleIn(key))}\n onSet={setPerms}\n />\n </>\n ) : (\n <p className={styles.note}>\n Superadmins have full access, so no roles or permissions are needed.\n </p>\n )}\n </form>\n </FormDialog>\n );\n}\n"],"mappings":"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","names":[]}
@@ -211,12 +211,6 @@ function useAdminRoles() {
211
211
  queryFn: () => platformAdminApi.roles()
212
212
  });
213
213
  }
214
- function useRoleChangeRequests() {
215
- return useQuery({
216
- queryKey: ["platform-admin", "roles", "requests"],
217
- queryFn: () => platformAdminApi.roleRequests()
218
- });
219
- }
220
214
  function useAccountSessions(id) {
221
215
  return useQuery({
222
216
  queryKey: ["platform-admin", "account-sessions", id],
@@ -265,7 +259,6 @@ export {
265
259
  usePlatformTenant,
266
260
  usePlatformTenants,
267
261
  usePlatformTenantsAnalytics,
268
- useRoleChangeRequests,
269
262
  useSubscriptionPayments,
270
263
  useTenantAnalytics,
271
264
  useTenantCustomerInsights,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/admin/hooks.ts"],"sourcesContent":["'use client';\n\nimport { keepPreviousData, useQuery } from '@tanstack/react-query';\nimport { platformAdminApi, type LedgerFilters } from '@withpotter/lib/admin-api';\nimport type { TenantListParams } from '@withpotter/lib/admin-tenants-query';\nimport type { AccountStage } from '@withpotter/lib/admin-account-types';\n\nexport function useAdminSession(enabled = true) {\n return useQuery({\n queryKey: ['platform-admin', 'session'],\n queryFn: () => platformAdminApi.me(),\n enabled,\n retry: false,\n staleTime: 60_000,\n });\n}\n\nexport function usePlatformOverview() {\n return useQuery({\n queryKey: ['platform-admin', 'overview'],\n queryFn: () => platformAdminApi.overview(),\n });\n}\n\nexport function usePlatformCockpit(\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'cockpit', range],\n queryFn: () => platformAdminApi.cockpit(range),\n });\n}\n\nexport function usePlatformRevenue(\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'revenue', range],\n queryFn: () => platformAdminApi.revenue(range),\n });\n}\n\n/**\n * How customers paid, per channel. Not every box can say: where the route is\n * absent the query fails once (no retry) and `data` stays undefined, which the\n * panels read as \"nothing to show\".\n */\nexport function useGatewayChannels(\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'gateway-channels', range],\n queryFn: () => platformAdminApi.gatewayChannels(range),\n retry: false,\n staleTime: 60_000,\n });\n}\n\n/**\n * Revenue's Merchant payments view. A box without the route answers 404; the\n * view reads that as \"not available here\", never as a failure to retry.\n */\nexport function useMerchantPayments(filters: LedgerFilters) {\n return useQuery({\n queryKey: ['platform-admin', 'revenue', 'merchant-payments', filters],\n queryFn: () => platformAdminApi.merchantPayments(filters),\n retry: false,\n staleTime: 30_000,\n placeholderData: keepPreviousData,\n });\n}\n\n/** Revenue's Subscriptions view: every plan payment merchants made to Olive. */\nexport function useSubscriptionPayments(filters: LedgerFilters) {\n return useQuery({\n queryKey: ['platform-admin', 'revenue', 'subscription-payments', filters],\n queryFn: () => platformAdminApi.subscriptionPayments(filters),\n retry: false,\n staleTime: 30_000,\n placeholderData: keepPreviousData,\n });\n}\n\n/** Revenue's Merchants view: what each merchant received and how customers paid them. */\nexport function useMerchantRevenue(filters: LedgerFilters) {\n return useQuery({\n queryKey: ['platform-admin', 'revenue', 'merchants', filters],\n queryFn: () => platformAdminApi.merchantRevenue(filters),\n retry: false,\n staleTime: 30_000,\n placeholderData: keepPreviousData,\n });\n}\n\n/** The merchants that took payments in the window, for the merchant picker. */\nexport function useMerchantOptions(range: { startDate?: string; endDate?: string }) {\n return useQuery({\n queryKey: ['platform-admin', 'revenue', 'merchant-options', range],\n queryFn: () => platformAdminApi.merchantOptions(range),\n retry: false,\n staleTime: 60_000,\n });\n}\n\nexport function usePlatformTenantsAnalytics(\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenants-analytics', range],\n queryFn: () => platformAdminApi.tenantsAnalytics(range),\n });\n}\n\nexport function usePlatformTenants(params: TenantListParams) {\n return useQuery({\n queryKey: ['platform-admin', 'tenants', params],\n queryFn: () => platformAdminApi.tenants(params),\n });\n}\n\nexport function usePlatformTenant(\n id: string,\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant', id, range],\n queryFn: () => platformAdminApi.tenant(id, range),\n enabled: !!id,\n });\n}\n\nexport function useTenantAnalytics(\n id: string,\n range: { startDate?: string; endDate?: string } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-analytics', id, range],\n queryFn: () => platformAdminApi.tenantAnalytics(id, range),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantFinance(\n id: string,\n range: { startDate?: string; endDate?: string } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-finance', id, range],\n queryFn: () => platformAdminApi.tenantFinance(id, range),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantTransactions(\n id: string,\n params: {\n status?: string;\n type?: string;\n search?: string;\n page?: number;\n limit?: number;\n } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-transactions', id, params],\n queryFn: () => platformAdminApi.tenantTransactions(id, params),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantCustomers(\n id: string,\n params: {\n search?: string;\n type?: string;\n status?: string;\n page?: number;\n limit?: number;\n } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-customers', id, params],\n queryFn: () => platformAdminApi.tenantCustomers(id, params),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantProducts(\n id: string,\n params: { type?: string; search?: string } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-products', id, params],\n queryFn: () => platformAdminApi.tenantProducts(id, params),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantProductInsights(\n id: string,\n range: { startDate?: string } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-product-insights', id, range],\n queryFn: () => platformAdminApi.tenantProductInsights(id, range),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantCustomerInsights(\n id: string,\n range: { startDate?: string } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-customer-insights', id, range],\n queryFn: () => platformAdminApi.tenantCustomerInsights(id, range),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantInvoices(\n id: string,\n params: {\n status?: string;\n search?: string;\n page?: number;\n limit?: number;\n } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-invoices', id, params],\n queryFn: () => platformAdminApi.tenantInvoices(id, params),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantLoyalty(id: string, enabled = true) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-loyalty', id],\n queryFn: () => platformAdminApi.tenantLoyalty(id),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantDiscounts(\n id: string,\n params: {\n status?: string;\n discountType?: string;\n search?: string;\n page?: number;\n limit?: number;\n } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-discounts', id, params],\n queryFn: () => platformAdminApi.tenantDiscounts(id, params),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantSettlements(\n id: string,\n params: {\n page?: number;\n limit?: number;\n startDate?: string;\n endDate?: string;\n } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-settlements', id, params],\n queryFn: () => platformAdminApi.tenantSettlements(id, params),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantSubscriptions(\n id: string,\n params: {\n status?: string;\n planId?: string;\n page?: number;\n limit?: number;\n } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-subscriptions', id, params],\n queryFn: () => platformAdminApi.tenantSubscriptions(id, params),\n enabled: !!id && enabled,\n });\n}\n\nexport function usePlatformAccounts(params: {\n page?: number;\n pageSize?: number;\n search?: string;\n joinedFrom?: string;\n joinedTo?: string;\n stage?: AccountStage;\n}) {\n return useQuery({\n queryKey: ['platform-admin', 'accounts', params],\n queryFn: () => platformAdminApi.accounts(params),\n });\n}\n\nexport function usePlatformAccountsAnalytics(\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'accounts-analytics', range],\n queryFn: () => platformAdminApi.accountsAnalytics(range),\n });\n}\n\nexport function usePlatformAdmins() {\n return useQuery({\n queryKey: ['platform-admin', 'admins'],\n queryFn: () => platformAdminApi.admins(),\n });\n}\n\nexport function usePlatformAdminsAnalytics(\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'admins-analytics', range],\n queryFn: () => platformAdminApi.adminsAnalytics(range),\n });\n}\n\nexport function useAdminPermissions() {\n return useQuery({\n queryKey: ['platform-admin', 'admin-permissions'],\n queryFn: () => platformAdminApi.adminPermissions(),\n staleTime: 5 * 60_000,\n });\n}\n\nexport function useAdminRoles() {\n return useQuery({\n queryKey: ['platform-admin', 'roles'],\n queryFn: () => platformAdminApi.roles(),\n });\n}\n\nexport function useRoleChangeRequests() {\n return useQuery({\n queryKey: ['platform-admin', 'roles', 'requests'],\n queryFn: () => platformAdminApi.roleRequests(),\n });\n}\n\nexport function useAccountSessions(id: string | null) {\n return useQuery({\n queryKey: ['platform-admin', 'account-sessions', id],\n queryFn: () => platformAdminApi.accountSessions(id!),\n enabled: !!id,\n });\n}\n\nexport function useAccountTenants(id: string | null) {\n return useQuery({\n queryKey: ['platform-admin', 'account-tenants', id],\n queryFn: () => platformAdminApi.accountTenants(id!),\n enabled: !!id,\n });\n}\n\nexport function usePlatformAudit(\n params: {\n page?: number;\n pageSize?: number;\n search?: string;\n action?: string;\n adminEmail?: string;\n from?: string;\n to?: string;\n } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'audit', params],\n queryFn: () => platformAdminApi.audit(params),\n });\n}\n\nexport function usePlatformAuditAnalytics(\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'audit-analytics', range],\n queryFn: () => platformAdminApi.auditAnalytics(range),\n });\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/admin/hooks.ts"],"sourcesContent":["'use client';\n\nimport { keepPreviousData, useQuery } from '@tanstack/react-query';\nimport { platformAdminApi, type LedgerFilters } from '@withpotter/lib/admin-api';\nimport type { TenantListParams } from '@withpotter/lib/admin-tenants-query';\nimport type { AccountStage } from '@withpotter/lib/admin-account-types';\n\nexport function useAdminSession(enabled = true) {\n return useQuery({\n queryKey: ['platform-admin', 'session'],\n queryFn: () => platformAdminApi.me(),\n enabled,\n retry: false,\n staleTime: 60_000,\n });\n}\n\nexport function usePlatformOverview() {\n return useQuery({\n queryKey: ['platform-admin', 'overview'],\n queryFn: () => platformAdminApi.overview(),\n });\n}\n\nexport function usePlatformCockpit(\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'cockpit', range],\n queryFn: () => platformAdminApi.cockpit(range),\n });\n}\n\nexport function usePlatformRevenue(\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'revenue', range],\n queryFn: () => platformAdminApi.revenue(range),\n });\n}\n\n/**\n * How customers paid, per channel. Not every box can say: where the route is\n * absent the query fails once (no retry) and `data` stays undefined, which the\n * panels read as \"nothing to show\".\n */\nexport function useGatewayChannels(\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'gateway-channels', range],\n queryFn: () => platformAdminApi.gatewayChannels(range),\n retry: false,\n staleTime: 60_000,\n });\n}\n\n/**\n * Revenue's Merchant payments view. A box without the route answers 404; the\n * view reads that as \"not available here\", never as a failure to retry.\n */\nexport function useMerchantPayments(filters: LedgerFilters) {\n return useQuery({\n queryKey: ['platform-admin', 'revenue', 'merchant-payments', filters],\n queryFn: () => platformAdminApi.merchantPayments(filters),\n retry: false,\n staleTime: 30_000,\n placeholderData: keepPreviousData,\n });\n}\n\n/** Revenue's Subscriptions view: every plan payment merchants made to Olive. */\nexport function useSubscriptionPayments(filters: LedgerFilters) {\n return useQuery({\n queryKey: ['platform-admin', 'revenue', 'subscription-payments', filters],\n queryFn: () => platformAdminApi.subscriptionPayments(filters),\n retry: false,\n staleTime: 30_000,\n placeholderData: keepPreviousData,\n });\n}\n\n/** Revenue's Merchants view: what each merchant received and how customers paid them. */\nexport function useMerchantRevenue(filters: LedgerFilters) {\n return useQuery({\n queryKey: ['platform-admin', 'revenue', 'merchants', filters],\n queryFn: () => platformAdminApi.merchantRevenue(filters),\n retry: false,\n staleTime: 30_000,\n placeholderData: keepPreviousData,\n });\n}\n\n/** The merchants that took payments in the window, for the merchant picker. */\nexport function useMerchantOptions(range: { startDate?: string; endDate?: string }) {\n return useQuery({\n queryKey: ['platform-admin', 'revenue', 'merchant-options', range],\n queryFn: () => platformAdminApi.merchantOptions(range),\n retry: false,\n staleTime: 60_000,\n });\n}\n\nexport function usePlatformTenantsAnalytics(\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenants-analytics', range],\n queryFn: () => platformAdminApi.tenantsAnalytics(range),\n });\n}\n\nexport function usePlatformTenants(params: TenantListParams) {\n return useQuery({\n queryKey: ['platform-admin', 'tenants', params],\n queryFn: () => platformAdminApi.tenants(params),\n });\n}\n\nexport function usePlatformTenant(\n id: string,\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant', id, range],\n queryFn: () => platformAdminApi.tenant(id, range),\n enabled: !!id,\n });\n}\n\nexport function useTenantAnalytics(\n id: string,\n range: { startDate?: string; endDate?: string } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-analytics', id, range],\n queryFn: () => platformAdminApi.tenantAnalytics(id, range),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantFinance(\n id: string,\n range: { startDate?: string; endDate?: string } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-finance', id, range],\n queryFn: () => platformAdminApi.tenantFinance(id, range),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantTransactions(\n id: string,\n params: {\n status?: string;\n type?: string;\n search?: string;\n page?: number;\n limit?: number;\n } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-transactions', id, params],\n queryFn: () => platformAdminApi.tenantTransactions(id, params),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantCustomers(\n id: string,\n params: {\n search?: string;\n type?: string;\n status?: string;\n page?: number;\n limit?: number;\n } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-customers', id, params],\n queryFn: () => platformAdminApi.tenantCustomers(id, params),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantProducts(\n id: string,\n params: { type?: string; search?: string } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-products', id, params],\n queryFn: () => platformAdminApi.tenantProducts(id, params),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantProductInsights(\n id: string,\n range: { startDate?: string } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-product-insights', id, range],\n queryFn: () => platformAdminApi.tenantProductInsights(id, range),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantCustomerInsights(\n id: string,\n range: { startDate?: string } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-customer-insights', id, range],\n queryFn: () => platformAdminApi.tenantCustomerInsights(id, range),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantInvoices(\n id: string,\n params: {\n status?: string;\n search?: string;\n page?: number;\n limit?: number;\n } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-invoices', id, params],\n queryFn: () => platformAdminApi.tenantInvoices(id, params),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantLoyalty(id: string, enabled = true) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-loyalty', id],\n queryFn: () => platformAdminApi.tenantLoyalty(id),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantDiscounts(\n id: string,\n params: {\n status?: string;\n discountType?: string;\n search?: string;\n page?: number;\n limit?: number;\n } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-discounts', id, params],\n queryFn: () => platformAdminApi.tenantDiscounts(id, params),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantSettlements(\n id: string,\n params: {\n page?: number;\n limit?: number;\n startDate?: string;\n endDate?: string;\n } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-settlements', id, params],\n queryFn: () => platformAdminApi.tenantSettlements(id, params),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantSubscriptions(\n id: string,\n params: {\n status?: string;\n planId?: string;\n page?: number;\n limit?: number;\n } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-subscriptions', id, params],\n queryFn: () => platformAdminApi.tenantSubscriptions(id, params),\n enabled: !!id && enabled,\n });\n}\n\nexport function usePlatformAccounts(params: {\n page?: number;\n pageSize?: number;\n search?: string;\n joinedFrom?: string;\n joinedTo?: string;\n stage?: AccountStage;\n}) {\n return useQuery({\n queryKey: ['platform-admin', 'accounts', params],\n queryFn: () => platformAdminApi.accounts(params),\n });\n}\n\nexport function usePlatformAccountsAnalytics(\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'accounts-analytics', range],\n queryFn: () => platformAdminApi.accountsAnalytics(range),\n });\n}\n\nexport function usePlatformAdmins() {\n return useQuery({\n queryKey: ['platform-admin', 'admins'],\n queryFn: () => platformAdminApi.admins(),\n });\n}\n\nexport function usePlatformAdminsAnalytics(\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'admins-analytics', range],\n queryFn: () => platformAdminApi.adminsAnalytics(range),\n });\n}\n\nexport function useAdminPermissions() {\n return useQuery({\n queryKey: ['platform-admin', 'admin-permissions'],\n queryFn: () => platformAdminApi.adminPermissions(),\n staleTime: 5 * 60_000,\n });\n}\n\nexport function useAdminRoles() {\n return useQuery({\n queryKey: ['platform-admin', 'roles'],\n queryFn: () => platformAdminApi.roles(),\n });\n}\n\nexport function useAccountSessions(id: string | null) {\n return useQuery({\n queryKey: ['platform-admin', 'account-sessions', id],\n queryFn: () => platformAdminApi.accountSessions(id!),\n enabled: !!id,\n });\n}\n\nexport function useAccountTenants(id: string | null) {\n return useQuery({\n queryKey: ['platform-admin', 'account-tenants', id],\n queryFn: () => platformAdminApi.accountTenants(id!),\n enabled: !!id,\n });\n}\n\nexport function usePlatformAudit(\n params: {\n page?: number;\n pageSize?: number;\n search?: string;\n action?: string;\n adminEmail?: string;\n from?: string;\n to?: string;\n } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'audit', params],\n queryFn: () => platformAdminApi.audit(params),\n });\n}\n\nexport function usePlatformAuditAnalytics(\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'audit-analytics', range],\n queryFn: () => platformAdminApi.auditAnalytics(range),\n });\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,23 @@
1
+ "use client";
2
+ import { useChartTheme } from "@withpotter/components/charts";
3
+ const ORDER = [
4
+ "payments",
5
+ "payouts",
6
+ "plans",
7
+ "settlements",
8
+ "cash",
9
+ "transfers",
10
+ "fx",
11
+ "lending"
12
+ ];
13
+ function useSourceColors() {
14
+ const { series } = useChartTheme();
15
+ const colors = Object.fromEntries(
16
+ ORDER.map((source, i) => [source, series[i % series.length]])
17
+ );
18
+ return { ...colors, other: "var(--text-tertiary)", series };
19
+ }
20
+ export {
21
+ useSourceColors
22
+ };
23
+ //# sourceMappingURL=earnings-colors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/admin/revenue/earnings/earnings-colors.ts"],"sourcesContent":["'use client';\n\nimport { useChartTheme } from '@withpotter/components/charts';\nimport type { EarningsSource } from '@withpotter/lib/admin-earnings-api';\n\n/**\n * One colour per source, the same on its card, its pie slice and its legend row. They come from the chart system's\n * validated palette (fixed order, checked in light and dark), never picked here; the palette has eight slots, so\n * \"other\" takes a neutral rather than a ninth hue. The most important sources take the first slots.\n */\nconst ORDER: readonly EarningsSource[] = [\n 'payments',\n 'payouts',\n 'plans',\n 'settlements',\n 'cash',\n 'transfers',\n 'fx',\n 'lending',\n];\n\nexport function useSourceColors(): Record<EarningsSource, string> & {\n /** Colours for things that are not sources (payment methods, kept / cost), in palette order. */\n series: readonly string[];\n} {\n const { series } = useChartTheme();\n const colors = Object.fromEntries(\n ORDER.map((source, i) => [source, series[i % series.length]]),\n ) as Record<EarningsSource, string>;\n\n return { ...colors, other: 'var(--text-tertiary)', series };\n}\n"],"mappings":";AAEA,SAAS,qBAAqB;AAQ9B,MAAM,QAAmC;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,SAAS,kBAGd;AACA,QAAM,EAAE,OAAO,IAAI,cAAc;AACjC,QAAM,SAAS,OAAO;AAAA,IACpB,MAAM,IAAI,CAAC,QAAQ,MAAM,CAAC,QAAQ,OAAO,IAAI,OAAO,MAAM,CAAC,CAAC;AAAA,EAC9D;AAEA,SAAO,EAAE,GAAG,QAAQ,OAAO,wBAAwB,OAAO;AAC5D;","names":[]}
@@ -0,0 +1,38 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { DonutChart } from "@withpotter/components/charts";
4
+ import styles from "./earnings.module.css";
5
+ function PieWithDetails({
6
+ slices,
7
+ money,
8
+ centerLabel
9
+ }) {
10
+ const shown = slices.filter((s) => s.value > 0);
11
+ const total = shown.reduce((a, s) => a + s.value, 0);
12
+ return /* @__PURE__ */ jsxs("div", { className: styles.pie, children: [
13
+ /* @__PURE__ */ jsx(
14
+ DonutChart,
15
+ {
16
+ data: shown.map((s) => ({ label: s.label, value: s.value, color: s.color })),
17
+ format: money,
18
+ centerLabel,
19
+ size: 170
20
+ }
21
+ ),
22
+ /* @__PURE__ */ jsx("ul", { className: styles.legend, children: shown.map((s) => /* @__PURE__ */ jsxs("li", { className: styles.legendRow, children: [
23
+ /* @__PURE__ */ jsx("span", { className: styles.swatch, style: { background: s.color }, "aria-hidden": true }),
24
+ /* @__PURE__ */ jsxs("span", { className: styles.legendText, children: [
25
+ /* @__PURE__ */ jsx("span", { className: styles.legendLabel, children: s.label }),
26
+ s.detail ? /* @__PURE__ */ jsx("span", { className: styles.legendDetail, children: s.detail }) : null
27
+ ] }),
28
+ /* @__PURE__ */ jsxs("span", { className: styles.legendNumbers, children: [
29
+ /* @__PURE__ */ jsx("strong", { children: money(s.value) }),
30
+ /* @__PURE__ */ jsx("span", { children: total > 0 ? `${Math.round(s.value / total * 100)}%` : "\u2013" })
31
+ ] })
32
+ ] }, s.key)) })
33
+ ] });
34
+ }
35
+ export {
36
+ PieWithDetails
37
+ };
38
+ //# sourceMappingURL=earnings-legend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/admin/revenue/earnings/earnings-legend.tsx"],"sourcesContent":["'use client';\n\nimport { DonutChart } from '@withpotter/components/charts';\nimport styles from './earnings.module.css';\n\nexport interface LegendSlice {\n key: string;\n label: string;\n value: number;\n color: string;\n /** A second line: \"12 transactions\". */\n detail?: string;\n}\n\n/**\n * A pie with its key beside it: every slice named in its own colour with its amount and share, so the chart can be\n * read without hovering. Slices of nothing are left out.\n */\nexport function PieWithDetails({\n slices,\n money,\n centerLabel,\n}: {\n slices: LegendSlice[];\n money: (value: number) => string;\n centerLabel: string;\n}) {\n const shown = slices.filter((s) => s.value > 0);\n const total = shown.reduce((a, s) => a + s.value, 0);\n\n return (\n <div className={styles.pie}>\n <DonutChart\n data={shown.map((s) => ({ label: s.label, value: s.value, color: s.color }))}\n format={money}\n centerLabel={centerLabel}\n size={170}\n />\n <ul className={styles.legend}>\n {shown.map((s) => (\n <li key={s.key} className={styles.legendRow}>\n <span className={styles.swatch} style={{ background: s.color }} aria-hidden />\n <span className={styles.legendText}>\n <span className={styles.legendLabel}>{s.label}</span>\n {s.detail ? <span className={styles.legendDetail}>{s.detail}</span> : null}\n </span>\n <span className={styles.legendNumbers}>\n <strong>{money(s.value)}</strong>\n <span>{total > 0 ? `${Math.round((s.value / total) * 100)}%` : '–'}</span>\n </span>\n </li>\n ))}\n </ul>\n </div>\n );\n}\n"],"mappings":";AAgCM,cAUM,YAVN;AA9BN,SAAS,kBAAkB;AAC3B,OAAO,YAAY;AAeZ,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,QAAQ,OAAO,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC;AAC9C,QAAM,QAAQ,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,OAAO,CAAC;AAEnD,SACE,qBAAC,SAAI,WAAW,OAAO,KACrB;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,MAAM,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,OAAO,EAAE,OAAO,OAAO,EAAE,MAAM,EAAE;AAAA,QAC3E,QAAQ;AAAA,QACR;AAAA,QACA,MAAM;AAAA;AAAA,IACR;AAAA,IACA,oBAAC,QAAG,WAAW,OAAO,QACnB,gBAAM,IAAI,CAAC,MACV,qBAAC,QAAe,WAAW,OAAO,WAChC;AAAA,0BAAC,UAAK,WAAW,OAAO,QAAQ,OAAO,EAAE,YAAY,EAAE,MAAM,GAAG,eAAW,MAAC;AAAA,MAC5E,qBAAC,UAAK,WAAW,OAAO,YACtB;AAAA,4BAAC,UAAK,WAAW,OAAO,aAAc,YAAE,OAAM;AAAA,QAC7C,EAAE,SAAS,oBAAC,UAAK,WAAW,OAAO,cAAe,YAAE,QAAO,IAAU;AAAA,SACxE;AAAA,MACA,qBAAC,UAAK,WAAW,OAAO,eACtB;AAAA,4BAAC,YAAQ,gBAAM,EAAE,KAAK,GAAE;AAAA,QACxB,oBAAC,UAAM,kBAAQ,IAAI,GAAG,KAAK,MAAO,EAAE,QAAQ,QAAS,GAAG,CAAC,MAAM,UAAI;AAAA,SACrE;AAAA,SATO,EAAE,GAUX,CACD,GACH;AAAA,KACF;AAEJ;","names":[]}
@@ -0,0 +1,194 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import Link from "next/link";
4
+ import { EmptyNote } from "@withpotter/ui-kit";
5
+ import { TimeSeriesChart } from "@withpotter/components/charts";
6
+ import { toMajor, earningsFigures, pctChange } from "@withpotter/lib/admin-earnings-model";
7
+ import { LoadFailed } from "../../audit/load-failed";
8
+ import { AdmLoading, Panel, StatCard, moneyIn, toTrend } from "../../_ui";
9
+ import { useSourceColors } from "./earnings-colors";
10
+ import { PieWithDetails } from "./earnings-legend";
11
+ import { useEarnings } from "./use-earnings";
12
+ import styles from "../revenue.module.css";
13
+ import ledger from "../ledger.module.css";
14
+ import earn from "./earnings.module.css";
15
+ const integer = new Intl.NumberFormat("en-US");
16
+ const CARD_TITLES = {
17
+ payments: "Customer payments",
18
+ cash: "Cash received",
19
+ settlements: "Settlements to bank",
20
+ payouts: "Payouts",
21
+ transfers: "Wallet transfers",
22
+ fx: "Currency conversion",
23
+ lending: "Lending",
24
+ other: "Other fees"
25
+ };
26
+ const CARD_NOTES = {
27
+ payments: "Fees on what customers paid merchants",
28
+ cash: "Fees on cash received for merchants",
29
+ settlements: "Charges when merchants move money to their bank",
30
+ payouts: "Fees when merchants send money out of their wallet"
31
+ };
32
+ function EarningsSummary({
33
+ startDate,
34
+ endDate,
35
+ currency,
36
+ recurring,
37
+ span
38
+ }) {
39
+ const { data, isLoading, isError, refetch } = useEarnings({ startDate, endDate });
40
+ const colors = useSourceColors();
41
+ if (isError)
42
+ return /* @__PURE__ */ jsx(
43
+ LoadFailed,
44
+ {
45
+ message: "Earnings could not be loaded. This box may not have them yet.",
46
+ onRetry: () => void refetch()
47
+ }
48
+ );
49
+ if (isLoading || !data) return /* @__PURE__ */ jsx(AdmLoading, { label: "Loading what Olive earns\u2026" });
50
+ const wanted = currency || recurring.currency || "NGN";
51
+ const cur = data.currencies.find((c) => c.currency === wanted) ?? data.currencies[0];
52
+ if (!cur)
53
+ return /* @__PURE__ */ jsx(EmptyNote, { className: styles.empty, children: "No fees have been earned in this period yet." });
54
+ const plan = recurring.currency === cur.currency ? recurring.collected ?? null : null;
55
+ const f = earningsFigures(cur, plan);
56
+ const money = moneyIn(cur.currency, 2);
57
+ const caption = `${span[0].toUpperCase()}${span.slice(1)} \xB7 ${cur.currency}`;
58
+ const byMethod = (recurring.collectedByMethod ?? []).filter((m) => m.amount > 0);
59
+ const trend = toTrend(
60
+ cur.series.map((p) => ({ date: p.date, value: toMajor(p.feeMinor, cur.exponent) })),
61
+ "value"
62
+ );
63
+ const topTotal = cur.topMerchants.reduce((a, m) => a + toMajor(m.feeMinor, cur.exponent), 0);
64
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
65
+ /* @__PURE__ */ jsxs("p", { className: ledger.sectionNote, children: [
66
+ "Everything Olive makes in this period: fees on what customers pay, on money merchants send out and move to their bank, and what merchants pay for their plan. Showing ",
67
+ cur.currency,
68
+ data.currencies.length > 1 ? "; use the currency filter for the others." : "."
69
+ ] }),
70
+ /* @__PURE__ */ jsxs("section", { "aria-label": "What Olive earns", className: "adm-grid", children: [
71
+ /* @__PURE__ */ jsx("div", { className: "adm-span-3", children: /* @__PURE__ */ jsx(
72
+ StatCard,
73
+ {
74
+ label: "Total earned",
75
+ accent: colors.series[0],
76
+ spark: trend.map((p) => p.value),
77
+ sparkColor: colors.series[0],
78
+ value: money(f.net),
79
+ sub: `${money(f.gross)} before processor costs`,
80
+ hint: "Fees Olive earned plus plan income, less what the card processor charged."
81
+ }
82
+ ) }),
83
+ /* @__PURE__ */ jsx("div", { className: "adm-span-3", children: /* @__PURE__ */ jsx(
84
+ StatCard,
85
+ {
86
+ label: "Processor costs",
87
+ accent: colors.series[7],
88
+ value: cur.providerCostsRecorded ? money(f.provider) : "\u2013",
89
+ sub: cur.providerCostsRecorded ? "Charged by the card processor" : "None recorded in the ledger",
90
+ hint: "What the payment processor kept from the same payments. When none is recorded, the total earned is before any such cost."
91
+ }
92
+ ) }),
93
+ /* @__PURE__ */ jsx("div", { className: "adm-span-3", children: /* @__PURE__ */ jsx(
94
+ StatCard,
95
+ {
96
+ label: "Plan subscriptions",
97
+ accent: colors.plans,
98
+ value: money(f.plan),
99
+ sub: recurring.payingMerchants ? `${integer.format(recurring.payingMerchants)} ${recurring.payingMerchants === 1 ? "merchant" : "merchants"} paying` : "Plan payments collected",
100
+ hint: "What merchants paid Olive for their plans in this period, however they paid."
101
+ }
102
+ ) }),
103
+ f.parts.filter((p) => p.source !== "plans").map((p) => /* @__PURE__ */ jsx("div", { className: "adm-span-3", children: /* @__PURE__ */ jsx(
104
+ StatCard,
105
+ {
106
+ label: CARD_TITLES[p.source] ?? p.label,
107
+ accent: colors[p.source],
108
+ value: money(p.value),
109
+ sub: `${integer.format(p.count)} ${p.count === 1 ? "transaction" : "transactions"}`,
110
+ hint: CARD_NOTES[p.source],
111
+ delta: { pct: pctChange(p.value, p.previous), caption: "vs the period before" }
112
+ }
113
+ ) }, p.source))
114
+ ] }),
115
+ /* @__PURE__ */ jsxs("div", { className: "adm-grid", children: [
116
+ /* @__PURE__ */ jsx("div", { className: "adm-span-4", children: /* @__PURE__ */ jsx(Panel, { title: "Where it comes from", caption, height: "auto", children: f.parts.some((p) => p.value > 0) ? /* @__PURE__ */ jsx(
117
+ PieWithDetails,
118
+ {
119
+ money,
120
+ centerLabel: "Earned",
121
+ slices: f.parts.map((p) => ({
122
+ key: p.source,
123
+ label: CARD_TITLES[p.source] ?? p.label,
124
+ value: p.value,
125
+ color: colors[p.source],
126
+ detail: p.count > 0 ? `${integer.format(p.count)} ${p.count === 1 ? "transaction" : "transactions"}` : void 0
127
+ }))
128
+ }
129
+ ) : /* @__PURE__ */ jsx("p", { className: styles.empty, children: "Nothing earned in this period." }) }) }),
130
+ /* @__PURE__ */ jsx("div", { className: "adm-span-4", children: /* @__PURE__ */ jsx(Panel, { title: "What Olive keeps", caption, height: "auto", children: cur.providerCostsRecorded && f.gross > 0 ? /* @__PURE__ */ jsx(
131
+ PieWithDetails,
132
+ {
133
+ money,
134
+ centerLabel: "Before costs",
135
+ slices: [
136
+ { key: "kept", label: "Kept by Olive", value: Math.max(0, f.net), color: colors.series[2], detail: "After processor costs" },
137
+ { key: "cost", label: "Processor costs", value: Math.max(0, f.provider), color: colors.series[7], detail: "Charged by the card processor" }
138
+ ]
139
+ }
140
+ ) : /* @__PURE__ */ jsx("p", { className: styles.empty, children: "No processor cost is recorded for these fees, so everything shown is before costs." }) }) }),
141
+ /* @__PURE__ */ jsx("div", { className: "adm-span-4", children: /* @__PURE__ */ jsx(Panel, { title: "How plans were paid", caption, height: "auto", children: byMethod.length > 0 && recurring.currency === cur.currency ? /* @__PURE__ */ jsx(
142
+ PieWithDetails,
143
+ {
144
+ money,
145
+ centerLabel: "Plans",
146
+ slices: byMethod.map((m, i) => ({
147
+ key: m.method,
148
+ label: m.method,
149
+ value: m.amount,
150
+ color: colors.series[i % colors.series.length],
151
+ detail: `${integer.format(m.payments)} ${m.payments === 1 ? "payment" : "payments"}`
152
+ }))
153
+ }
154
+ ) : /* @__PURE__ */ jsx("p", { className: styles.empty, children: "No plan payments in this period." }) }) })
155
+ ] }),
156
+ /* @__PURE__ */ jsx("div", { className: "adm-grid", children: /* @__PURE__ */ jsx("div", { className: "adm-span-12", children: /* @__PURE__ */ jsx(Panel, { title: "Fees earned each day", caption, height: "calc(var(--adm-panel-h) + 32px)", children: /* @__PURE__ */ jsx("div", { className: styles.flatChart, children: /* @__PURE__ */ jsx(
157
+ TimeSeriesChart,
158
+ {
159
+ data: trend,
160
+ color: colors.series[0],
161
+ formatValue: money,
162
+ compare: null,
163
+ emptyMessage: "No fees were earned on any day in this period",
164
+ height: 240
165
+ }
166
+ ) }) }) }) }),
167
+ /* @__PURE__ */ jsx("div", { className: "adm-grid", children: /* @__PURE__ */ jsx("div", { className: "adm-span-12", children: /* @__PURE__ */ jsx(Panel, { title: "Where the fees come from", caption: `Merchants, ${caption}`, height: "auto", children: cur.topMerchants.length === 0 ? /* @__PURE__ */ jsx("p", { className: styles.empty, children: "No fees to show in this period." }) : /* @__PURE__ */ jsx("ol", { className: earn.rankList, children: cur.topMerchants.map((m, i) => {
168
+ const value = toMajor(m.feeMinor, cur.exponent);
169
+ const widest = toMajor(cur.topMerchants[0].feeMinor, cur.exponent);
170
+ return /* @__PURE__ */ jsxs("li", { className: earn.rankRow, children: [
171
+ /* @__PURE__ */ jsx("span", { className: earn.rankName, children: m.tenantId && m.name ? /* @__PURE__ */ jsx(Link, { href: `/admin/tenants/${m.tenantId}`, children: m.name }) : "No merchant recorded" }),
172
+ /* @__PURE__ */ jsx("strong", { className: earn.rankAmount, children: money(value) }),
173
+ /* @__PURE__ */ jsxs("span", { className: earn.rankSub, children: [
174
+ `${integer.format(m.count)} ${m.count === 1 ? "transaction" : "transactions"}`,
175
+ topTotal > 0 ? ` \xB7 ${Math.round(value / topTotal * 100)}% of these` : ""
176
+ ] }),
177
+ /* @__PURE__ */ jsx("span", { className: earn.rankBar, "aria-hidden": true, children: /* @__PURE__ */ jsx(
178
+ "span",
179
+ {
180
+ className: earn.rankFill,
181
+ style: {
182
+ width: `${widest > 0 ? Math.max(2, value / widest * 100) : 0}%`,
183
+ background: colors.series[0]
184
+ }
185
+ }
186
+ ) })
187
+ ] }, `${m.tenantId ?? "none"}-${i}`);
188
+ }) }) }) }) })
189
+ ] });
190
+ }
191
+ export {
192
+ EarningsSummary
193
+ };
194
+ //# sourceMappingURL=earnings-summary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/admin/revenue/earnings/earnings-summary.tsx"],"sourcesContent":["'use client';\n\nimport Link from 'next/link';\nimport { EmptyNote } from '@withpotter/ui-kit';\nimport { TimeSeriesChart } from '@withpotter/components/charts';\nimport type { PlatformRevenue } from '@withpotter/lib/admin-api';\nimport { toMajor, earningsFigures, pctChange } from '@withpotter/lib/admin-earnings-model';\nimport { LoadFailed } from '../../audit/load-failed';\nimport { AdmLoading, Panel, StatCard, moneyIn, toTrend } from '../../_ui';\nimport { useSourceColors } from './earnings-colors';\nimport { PieWithDetails } from './earnings-legend';\nimport { useEarnings } from './use-earnings';\nimport styles from '../revenue.module.css';\nimport ledger from '../ledger.module.css';\nimport earn from './earnings.module.css';\n\nconst integer = new Intl.NumberFormat('en-US');\n\n/** The ledger's words for what a merchant was doing, as the card says them. */\nconst CARD_TITLES: Record<string, string> = {\n payments: 'Customer payments',\n cash: 'Cash received',\n settlements: 'Settlements to bank',\n payouts: 'Payouts',\n transfers: 'Wallet transfers',\n fx: 'Currency conversion',\n lending: 'Lending',\n other: 'Other fees',\n};\n\nconst CARD_NOTES: Record<string, string> = {\n payments: 'Fees on what customers paid merchants',\n cash: 'Fees on cash received for merchants',\n settlements: 'Charges when merchants move money to their bank',\n payouts: 'Fees when merchants send money out of their wallet',\n};\n\n/**\n * What Olive earns, in one view: the total, where it comes from, what is left after the processor, how it moves day to\n * day and which merchants it comes from. One currency at a time, never a mix.\n */\nexport function EarningsSummary({\n startDate,\n endDate,\n currency,\n recurring,\n span,\n}: {\n startDate?: string;\n endDate?: string;\n /** The currency filter's choice; empty follows the billing currency. */\n currency: string;\n recurring: PlatformRevenue['recurring'];\n span: string;\n}) {\n const { data, isLoading, isError, refetch } = useEarnings({ startDate, endDate });\n const colors = useSourceColors();\n\n if (isError)\n return (\n <LoadFailed\n message=\"Earnings could not be loaded. This box may not have them yet.\"\n onRetry={() => void refetch()}\n />\n );\n\n if (isLoading || !data) return <AdmLoading label=\"Loading what Olive earns…\" />;\n\n const wanted = currency || recurring.currency || 'NGN';\n const cur = data.currencies.find((c) => c.currency === wanted) ?? data.currencies[0];\n\n if (!cur)\n return (\n <EmptyNote className={styles.empty}>\n No fees have been earned in this period yet.\n </EmptyNote>\n );\n\n const plan = recurring.currency === cur.currency ? (recurring.collected ?? null) : null;\n const f = earningsFigures(cur, plan);\n const money = moneyIn(cur.currency, 2);\n const caption = `${span[0].toUpperCase()}${span.slice(1)} · ${cur.currency}`;\n const byMethod = (recurring.collectedByMethod ?? []).filter((m) => m.amount > 0);\n const trend = toTrend(\n cur.series.map((p) => ({ date: p.date, value: toMajor(p.feeMinor, cur.exponent) })),\n 'value',\n );\n const topTotal = cur.topMerchants.reduce((a, m) => a + toMajor(m.feeMinor, cur.exponent), 0);\n\n return (\n <>\n <p className={ledger.sectionNote}>\n Everything Olive makes in this period: fees on what customers pay, on money merchants send out and move to\n their bank, and what merchants pay for their plan. Showing {cur.currency}\n {data.currencies.length > 1 ? '; use the currency filter for the others.' : '.'}\n </p>\n\n <section aria-label=\"What Olive earns\" className=\"adm-grid\">\n <div className=\"adm-span-3\">\n <StatCard\n label=\"Total earned\"\n accent={colors.series[0]}\n spark={trend.map((p) => p.value)}\n sparkColor={colors.series[0]}\n value={money(f.net)}\n sub={`${money(f.gross)} before processor costs`}\n hint=\"Fees Olive earned plus plan income, less what the card processor charged.\"\n />\n </div>\n <div className=\"adm-span-3\">\n <StatCard\n label=\"Processor costs\"\n accent={colors.series[7]}\n value={cur.providerCostsRecorded ? money(f.provider) : '–'}\n sub={cur.providerCostsRecorded ? 'Charged by the card processor' : 'None recorded in the ledger'}\n hint=\"What the payment processor kept from the same payments. When none is recorded, the total earned is before any such cost.\"\n />\n </div>\n <div className=\"adm-span-3\">\n <StatCard\n label=\"Plan subscriptions\"\n accent={colors.plans}\n value={money(f.plan)}\n sub={recurring.payingMerchants ? `${integer.format(recurring.payingMerchants)} ${recurring.payingMerchants === 1 ? 'merchant' : 'merchants'} paying` : 'Plan payments collected'}\n hint=\"What merchants paid Olive for their plans in this period, however they paid.\"\n />\n </div>\n {f.parts\n .filter((p) => p.source !== 'plans')\n .map((p) => (\n <div key={p.source} className=\"adm-span-3\">\n <StatCard\n label={CARD_TITLES[p.source] ?? p.label}\n accent={colors[p.source]}\n value={money(p.value)}\n sub={`${integer.format(p.count)} ${p.count === 1 ? 'transaction' : 'transactions'}`}\n hint={CARD_NOTES[p.source]}\n delta={{ pct: pctChange(p.value, p.previous), caption: 'vs the period before' }}\n />\n </div>\n ))}\n </section>\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-4\">\n <Panel title=\"Where it comes from\" caption={caption} height=\"auto\">\n {f.parts.some((p) => p.value > 0) ? (\n <PieWithDetails\n money={money}\n centerLabel=\"Earned\"\n slices={f.parts.map((p) => ({\n key: p.source,\n label: CARD_TITLES[p.source] ?? p.label,\n value: p.value,\n color: colors[p.source],\n detail:\n p.count > 0\n ? `${integer.format(p.count)} ${p.count === 1 ? 'transaction' : 'transactions'}`\n : undefined,\n }))}\n />\n ) : (\n <p className={styles.empty}>Nothing earned in this period.</p>\n )}\n </Panel>\n </div>\n <div className=\"adm-span-4\">\n <Panel title=\"What Olive keeps\" caption={caption} height=\"auto\">\n {cur.providerCostsRecorded && f.gross > 0 ? (\n <PieWithDetails\n money={money}\n centerLabel=\"Before costs\"\n slices={[\n { key: 'kept', label: 'Kept by Olive', value: Math.max(0, f.net), color: colors.series[2], detail: 'After processor costs' },\n { key: 'cost', label: 'Processor costs', value: Math.max(0, f.provider), color: colors.series[7], detail: 'Charged by the card processor' },\n ]}\n />\n ) : (\n <p className={styles.empty}>No processor cost is recorded for these fees, so everything shown is before costs.</p>\n )}\n </Panel>\n </div>\n <div className=\"adm-span-4\">\n <Panel title=\"How plans were paid\" caption={caption} height=\"auto\">\n {byMethod.length > 0 && recurring.currency === cur.currency ? (\n <PieWithDetails\n money={money}\n centerLabel=\"Plans\"\n slices={byMethod.map((m, i) => ({\n key: m.method,\n label: m.method,\n value: m.amount,\n color: colors.series[i % colors.series.length],\n detail: `${integer.format(m.payments)} ${m.payments === 1 ? 'payment' : 'payments'}`,\n }))}\n />\n ) : (\n <p className={styles.empty}>No plan payments in this period.</p>\n )}\n </Panel>\n </div>\n </div>\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-12\">\n <Panel title=\"Fees earned each day\" caption={caption} height=\"calc(var(--adm-panel-h) + 32px)\">\n <div className={styles.flatChart}>\n <TimeSeriesChart\n data={trend}\n color={colors.series[0]}\n formatValue={money}\n compare={null}\n emptyMessage=\"No fees were earned on any day in this period\"\n height={240}\n />\n </div>\n </Panel>\n </div>\n </div>\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-12\">\n <Panel title=\"Where the fees come from\" caption={`Merchants, ${caption}`} height=\"auto\">\n {cur.topMerchants.length === 0 ? (\n <p className={styles.empty}>No fees to show in this period.</p>\n ) : (\n <ol className={earn.rankList}>\n {cur.topMerchants.map((m, i) => {\n const value = toMajor(m.feeMinor, cur.exponent);\n const widest = toMajor(cur.topMerchants[0].feeMinor, cur.exponent);\n\n return (\n <li key={`${m.tenantId ?? 'none'}-${i}`} className={earn.rankRow}>\n <span className={earn.rankName}>\n {m.tenantId && m.name ? (\n <Link href={`/admin/tenants/${m.tenantId}`}>{m.name}</Link>\n ) : (\n 'No merchant recorded'\n )}\n </span>\n <strong className={earn.rankAmount}>{money(value)}</strong>\n <span className={earn.rankSub}>\n {`${integer.format(m.count)} ${m.count === 1 ? 'transaction' : 'transactions'}`}\n {topTotal > 0 ? ` · ${Math.round((value / topTotal) * 100)}% of these` : ''}\n </span>\n <span className={earn.rankBar} aria-hidden>\n <span\n className={earn.rankFill}\n style={{\n width: `${widest > 0 ? Math.max(2, (value / widest) * 100) : 0}%`,\n background: colors.series[0],\n }}\n />\n </span>\n </li>\n );\n })}\n </ol>\n )}\n </Panel>\n </div>\n </div>\n </>\n );\n}\n"],"mappings":"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","names":[]}