@withpotter/pages-admin 0.13.0-next.104 → 0.13.0-next.106
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/_overview/activation-panel.js +6 -6
- package/dist/admin/_overview/activation-panel.js.map +1 -1
- package/dist/admin/_overview/attention-groups.js +8 -8
- package/dist/admin/_overview/attention-groups.js.map +1 -1
- package/dist/admin/_overview/attention-panel.js +3 -3
- package/dist/admin/_overview/attention-panel.js.map +1 -1
- package/dist/admin/_overview/control-room.js +9 -9
- package/dist/admin/_overview/control-room.js.map +1 -1
- package/dist/admin/_overview/kpi-row.js +7 -7
- package/dist/admin/_overview/kpi-row.js.map +1 -1
- package/dist/admin/_overview/leaders-panel.js +1 -1
- package/dist/admin/_overview/leaders-panel.js.map +1 -1
- package/dist/admin/_overview/portfolio-panel.js +5 -5
- package/dist/admin/_overview/portfolio-panel.js.map +1 -1
- package/dist/admin/_overview/position-panel.js +6 -6
- package/dist/admin/_overview/position-panel.js.map +1 -1
- package/dist/admin/_ui/data-table.js +1 -1
- package/dist/admin/_ui/data-table.js.map +1 -1
- package/dist/admin/_ui/list-card.js +1 -1
- package/dist/admin/_ui/list-card.js.map +1 -1
- package/dist/admin/_ui/trend.js +2 -2
- package/dist/admin/_ui/trend.js.map +1 -1
- package/dist/admin/page.js +6 -6
- package/dist/admin/page.js.map +1 -1
- package/dist/admin/payout-tiers/_tier-dialog.js +31 -21
- package/dist/admin/payout-tiers/_tier-dialog.js.map +1 -1
- package/dist/admin/payout-tiers/requirement-labels.js +70 -0
- package/dist/admin/payout-tiers/requirement-labels.js.map +1 -0
- package/dist/admin/payout-tiers/tier-preview-result.js +10 -9
- package/dist/admin/payout-tiers/tier-preview-result.js.map +1 -1
- package/dist/admin/payout-tiers/tiers-table.js +6 -7
- package/dist/admin/payout-tiers/tiers-table.js.map +1 -1
- package/dist/admin/payout-tiers/tiers-workspace.js +2 -2
- package/dist/admin/payout-tiers/tiers-workspace.js.map +1 -1
- package/dist/admin/revenue/cohort-panel.js +1 -1
- package/dist/admin/revenue/cohort-panel.js.map +1 -1
- package/dist/admin/revenue/plan-mix-panel.js +2 -2
- package/dist/admin/revenue/plan-mix-panel.js.map +1 -1
- package/dist/admin/revenue/revenue-kpis.js +3 -3
- package/dist/admin/revenue/revenue-kpis.js.map +1 -1
- package/dist/admin/revenue/revenue-overview.js +1 -1
- package/dist/admin/revenue/revenue-overview.js.map +1 -1
- package/dist/admin/revenue/subscriptions-view.js +3 -3
- package/dist/admin/revenue/subscriptions-view.js.map +1 -1
- package/dist/admin/revenue/top-tenants-panel.js +3 -3
- package/dist/admin/revenue/top-tenants-panel.js.map +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/_ui/list-card.tsx"],"sourcesContent":["'use client';\n\nimport type { ComponentType, ReactNode } from 'react';\nimport { Button, TableShell } from '@withpotter/ui-kit';\nimport { EmptyState } from './empty-state';\nimport { Skeleton } from './skeleton';\nimport styles from './list-card.module.css';\n\n/** What an empty cell shows: an en dash, never a blank. */\nexport const EMPTY = '–';\n\n/**\n * A list table in its own card: title, search and filters in the toolbar,\n * rows scrolling under a sticky header, the pager in the footer. Owns the\n * loading, error and empty rows so each list page only declares columns.\n */\nexport function ListCard({\n title,\n count,\n controls,\n head,\n columns,\n minWidth = 760,\n selection,\n isLoading,\n isError,\n onRetry,\n errorTitle,\n isEmpty,\n emptyIcon,\n emptyTitle,\n emptyDescription,\n footer,\n children,\n}: {\n title: string;\n count?: number;\n controls?: ReactNode;\n /** The header <tr> of <Th> cells. */\n head: ReactNode;\n /** Column count, for skeleton and state rows. */\n columns: number;\n minWidth?: number;\n /** Takes the header row's place while rows are ticked. */\n selection?: ReactNode;\n isLoading?: boolean;\n isError?: boolean;\n onRetry?: () => void;\n errorTitle: string;\n isEmpty?: boolean;\n emptyIcon?: ComponentType<{ size?: number; color?: string }>;\n emptyTitle: string;\n emptyDescription?: string;\n footer?: ReactNode;\n children: ReactNode;\n}) {\n const state = (body: ReactNode) => (\n <tr>\n <td colSpan={columns} className=\"!p-0\">\n {body}\n </td>\n </tr>\n );\n\n return (\n <section\n className={`adm-section ${styles.card}`}\n aria-label={title}\n aria-busy={isLoading || undefined}\n >\n <TableShell\n bare\n minWidth={minWidth}\n head={head}\n selection={selection}\n columnCount={columns}\n toolbar={\n <>\n <h2 className={styles.title}>\n {title}\n {count !== undefined && (\n <span className={styles.count}>{count}</span>\n )}\n </h2>\n {controls}\n </>\n }\n footer={footer}\n >\n {isLoading\n ? Array.from({ length: 8 }, (_, row) => (\n <tr key={`sk-${row}`} aria-hidden>\n {Array.from({ length: columns }, (_, col) => (\n <td key={col}>\n <Skeleton width={`${48 + ((row + col) % 4) * 12}%`} />\n </td>\n ))}\n </tr>\n ))\n : isError\n ? state(\n <EmptyState\n tone=\"error\"\n title={errorTitle}\n description=\"Please
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/_ui/list-card.tsx"],"sourcesContent":["'use client';\n\nimport type { ComponentType, ReactNode } from 'react';\nimport { Button, TableShell } from '@withpotter/ui-kit';\nimport { EmptyState } from './empty-state';\nimport { Skeleton } from './skeleton';\nimport styles from './list-card.module.css';\n\n/** What an empty cell shows: an en dash, never a blank. */\nexport const EMPTY = '–';\n\n/**\n * A list table in its own card: title, search and filters in the toolbar,\n * rows scrolling under a sticky header, the pager in the footer. Owns the\n * loading, error and empty rows so each list page only declares columns.\n */\nexport function ListCard({\n title,\n count,\n controls,\n head,\n columns,\n minWidth = 760,\n selection,\n isLoading,\n isError,\n onRetry,\n errorTitle,\n isEmpty,\n emptyIcon,\n emptyTitle,\n emptyDescription,\n footer,\n children,\n}: {\n title: string;\n count?: number;\n controls?: ReactNode;\n /** The header <tr> of <Th> cells. */\n head: ReactNode;\n /** Column count, for skeleton and state rows. */\n columns: number;\n minWidth?: number;\n /** Takes the header row's place while rows are ticked. */\n selection?: ReactNode;\n isLoading?: boolean;\n isError?: boolean;\n onRetry?: () => void;\n errorTitle: string;\n isEmpty?: boolean;\n emptyIcon?: ComponentType<{ size?: number; color?: string }>;\n emptyTitle: string;\n emptyDescription?: string;\n footer?: ReactNode;\n children: ReactNode;\n}) {\n const state = (body: ReactNode) => (\n <tr>\n <td colSpan={columns} className=\"!p-0\">\n {body}\n </td>\n </tr>\n );\n\n return (\n <section\n className={`adm-section ${styles.card}`}\n aria-label={title}\n aria-busy={isLoading || undefined}\n >\n <TableShell\n bare\n minWidth={minWidth}\n head={head}\n selection={selection}\n columnCount={columns}\n toolbar={\n <>\n <h2 className={styles.title}>\n {title}\n {count !== undefined && (\n <span className={styles.count}>{count}</span>\n )}\n </h2>\n {controls}\n </>\n }\n footer={footer}\n >\n {isLoading\n ? Array.from({ length: 8 }, (_, row) => (\n <tr key={`sk-${row}`} aria-hidden>\n {Array.from({ length: columns }, (_, col) => (\n <td key={col}>\n <Skeleton width={`${48 + ((row + col) % 4) * 12}%`} />\n </td>\n ))}\n </tr>\n ))\n : isError\n ? state(\n <EmptyState\n tone=\"error\"\n title={errorTitle}\n description=\"Please try again. If it keeps happening, tell the engineering team.\"\n action={\n onRetry && (\n <Button size=\"sm\" variant=\"secondary\" onClick={onRetry}>\n Try again\n </Button>\n )\n }\n />,\n )\n : isEmpty\n ? state(\n <EmptyState\n icon={emptyIcon}\n title={emptyTitle}\n description={emptyDescription}\n />,\n )\n : children}\n </TableShell>\n </section>\n );\n}\n\n/** Count on the left, Previous · Page n of m · Next on the right. */\nexport function ListPager({\n page,\n totalPages,\n total,\n noun,\n onPage,\n}: {\n page: number;\n totalPages: number;\n total?: number;\n noun: string;\n onPage: (next: number) => void;\n}) {\n return (\n <div className={styles.footer}>\n <span>\n {total != null\n ? `${new Intl.NumberFormat().format(total)} ${noun}`\n : ''}\n </span>\n <div className={styles.pages}>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onClick={() => onPage(Math.max(1, page - 1))}\n disabled={page <= 1}\n >\n Previous\n </Button>\n <span>\n Page {page} of {totalPages}\n </span>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onClick={() => onPage(Math.min(totalPages, page + 1))}\n disabled={page >= totalPages}\n >\n Next\n </Button>\n </div>\n </div>\n );\n}\n"],"mappings":";AA0DM,SAmBI,UAnBJ,KAoBM,YApBN;AAvDN,SAAS,QAAQ,kBAAkB;AACnC,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB;AACzB,OAAO,YAAY;AAGZ,MAAM,QAAQ;AAOd,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAqBG;AACD,QAAM,QAAQ,CAAC,SACb,oBAAC,QACC,8BAAC,QAAG,SAAS,SAAS,WAAU,QAC7B,gBACH,GACF;AAGF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,eAAe,OAAO,IAAI;AAAA,MACrC,cAAY;AAAA,MACZ,aAAW,aAAa;AAAA,MAExB;AAAA,QAAC;AAAA;AAAA,UACC,MAAI;AAAA,UACJ;AAAA,UACA;AAAA,UACA;AAAA,UACA,aAAa;AAAA,UACb,SACE,iCACE;AAAA,iCAAC,QAAG,WAAW,OAAO,OACnB;AAAA;AAAA,cACA,UAAU,UACT,oBAAC,UAAK,WAAW,OAAO,OAAQ,iBAAM;AAAA,eAE1C;AAAA,YACC;AAAA,aACH;AAAA,UAEF;AAAA,UAEC,sBACG,MAAM,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,GAAG,QAC5B,oBAAC,QAAqB,eAAW,MAC9B,gBAAM,KAAK,EAAE,QAAQ,QAAQ,GAAG,CAACA,IAAG,QACnC,oBAAC,QACC,8BAAC,YAAS,OAAO,GAAG,MAAO,MAAM,OAAO,IAAK,EAAE,KAAK,KAD7C,GAET,CACD,KALM,MAAM,GAAG,EAMlB,CACD,IACD,UACE;AAAA,YACE;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,OAAO;AAAA,gBACP,aAAY;AAAA,gBACZ,QACE,WACE,oBAAC,UAAO,MAAK,MAAK,SAAQ,aAAY,SAAS,SAAS,uBAExD;AAAA;AAAA,YAGN;AAAA,UACF,IACA,UACE;AAAA,YACE;AAAA,cAAC;AAAA;AAAA,gBACC,MAAM;AAAA,gBACN,OAAO;AAAA,gBACP,aAAa;AAAA;AAAA,YACf;AAAA,UACF,IACA;AAAA;AAAA,MACV;AAAA;AAAA,EACF;AAEJ;AAGO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,SACE,qBAAC,SAAI,WAAW,OAAO,QACrB;AAAA,wBAAC,UACE,mBAAS,OACN,GAAG,IAAI,KAAK,aAAa,EAAE,OAAO,KAAK,CAAC,IAAI,IAAI,KAChD,IACN;AAAA,IACA,qBAAC,SAAI,WAAW,OAAO,OACrB;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAQ;AAAA,UACR,SAAS,MAAM,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,CAAC;AAAA,UAC3C,UAAU,QAAQ;AAAA,UACnB;AAAA;AAAA,MAED;AAAA,MACA,qBAAC,UAAK;AAAA;AAAA,QACE;AAAA,QAAK;AAAA,QAAK;AAAA,SAClB;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAQ;AAAA,UACR,SAAS,MAAM,OAAO,KAAK,IAAI,YAAY,OAAO,CAAC,CAAC;AAAA,UACpD,UAAU,QAAQ;AAAA,UACnB;AAAA;AAAA,MAED;AAAA,OACF;AAAA,KACF;AAEJ;","names":["_"]}
|
package/dist/admin/_ui/trend.js
CHANGED
|
@@ -20,9 +20,9 @@ function GmvOrdersChart({
|
|
|
20
20
|
data: gmvTrend,
|
|
21
21
|
compare: compare ? previousGmvTrend : null,
|
|
22
22
|
compareLabel: `Previous ${span}`,
|
|
23
|
-
label: "
|
|
23
|
+
label: "Sales",
|
|
24
24
|
formatValue: money,
|
|
25
|
-
emptyMessage: "No
|
|
25
|
+
emptyMessage: "No sales in this period",
|
|
26
26
|
height: 160
|
|
27
27
|
}
|
|
28
28
|
),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/_ui/trend.tsx"],"sourcesContent":["'use client';\n\nimport { TimeSeriesChart } from '@withpotter/components/charts';\n\n/** A `{date, N}[]` series reshaped into the `{date, value}` TimeSeriesChart wants. */\nexport function toTrend<K extends string>(\n series: Array<{ date: string } & Record<K, number>> | undefined,\n key: K,\n): Array<{ date: string; value: number }> {\n return (series ?? []).map((p) => ({ date: p.date, value: p[key] }));\n}\n\n/**\n * GMV over orders: the two measures share a window but not a scale, so\n * they're two stacked charts rather than one dual-axis one. Used wherever a\n * page shows both for the same range (Overview, Merchants, Commerce).\n */\nexport function GmvOrdersChart({\n gmvTrend,\n ordersTrend,\n previousGmvTrend,\n previousOrdersTrend,\n compare,\n span,\n money,\n}: {\n gmvTrend: Array<{ date: string; value: number }>;\n ordersTrend: Array<{ date: string; value: number }>;\n previousGmvTrend: Array<{ date: string; value: number }>;\n previousOrdersTrend: Array<{ date: string; value: number }>;\n /** False for \"all time\": there is no earlier period to compare with. */\n compare: boolean;\n span: string;\n money: (value: number) => string;\n}) {\n return (\n <>\n <TimeSeriesChart\n data={gmvTrend}\n compare={compare ? previousGmvTrend : null}\n compareLabel={`Previous ${span}`}\n label=\"
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/_ui/trend.tsx"],"sourcesContent":["'use client';\n\nimport { TimeSeriesChart } from '@withpotter/components/charts';\n\n/** A `{date, N}[]` series reshaped into the `{date, value}` TimeSeriesChart wants. */\nexport function toTrend<K extends string>(\n series: Array<{ date: string } & Record<K, number>> | undefined,\n key: K,\n): Array<{ date: string; value: number }> {\n return (series ?? []).map((p) => ({ date: p.date, value: p[key] }));\n}\n\n/**\n * GMV over orders: the two measures share a window but not a scale, so\n * they're two stacked charts rather than one dual-axis one. Used wherever a\n * page shows both for the same range (Overview, Merchants, Commerce).\n */\nexport function GmvOrdersChart({\n gmvTrend,\n ordersTrend,\n previousGmvTrend,\n previousOrdersTrend,\n compare,\n span,\n money,\n}: {\n gmvTrend: Array<{ date: string; value: number }>;\n ordersTrend: Array<{ date: string; value: number }>;\n previousGmvTrend: Array<{ date: string; value: number }>;\n previousOrdersTrend: Array<{ date: string; value: number }>;\n /** False for \"all time\": there is no earlier period to compare with. */\n compare: boolean;\n span: string;\n money: (value: number) => string;\n}) {\n return (\n <>\n <TimeSeriesChart\n data={gmvTrend}\n compare={compare ? previousGmvTrend : null}\n compareLabel={`Previous ${span}`}\n label=\"Sales\"\n formatValue={money}\n emptyMessage=\"No sales in this period\"\n height={160}\n />\n <div\n style={{\n margin: '20px 0 8px',\n fontSize: 12,\n fontWeight: 600,\n color: 'var(--text-tertiary)',\n }}\n >\n Orders\n </div>\n <TimeSeriesChart\n data={ordersTrend}\n compare={compare ? previousOrdersTrend : null}\n compareLabel={`Previous ${span}`}\n label=\"Orders\"\n emptyMessage=\"No orders in this period\"\n height={100}\n />\n </>\n );\n}\n"],"mappings":";AAoCI,mBACE,KADF;AAlCJ,SAAS,uBAAuB;AAGzB,SAAS,QACd,QACA,KACwC;AACxC,UAAQ,UAAU,CAAC,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,EAAE,GAAG,EAAE,EAAE;AACpE;AAOO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GASG;AACD,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,SAAS,UAAU,mBAAmB;AAAA,QACtC,cAAc,YAAY,IAAI;AAAA,QAC9B,OAAM;AAAA,QACN,aAAa;AAAA,QACb,cAAa;AAAA,QACb,QAAQ;AAAA;AAAA,IACV;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO;AAAA,UACL,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,YAAY;AAAA,UACZ,OAAO;AAAA,QACT;AAAA,QACD;AAAA;AAAA,IAED;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,SAAS,UAAU,sBAAsB;AAAA,QACzC,cAAc,YAAY,IAAI;AAAA,QAC9B,OAAM;AAAA,QACN,cAAa;AAAA,QACb,QAAQ;AAAA;AAAA,IACV;AAAA,KACF;AAEJ;","names":[]}
|
package/dist/admin/page.js
CHANGED
|
@@ -62,12 +62,12 @@ function AdminIndexPage() {
|
|
|
62
62
|
const gmvTrend = useMemo(() => toTrend(series, "gmv"), [series]);
|
|
63
63
|
const ordersTrend = useMemo(() => toTrend(series, "orders"), [series]);
|
|
64
64
|
if (cockpit.isLoading) {
|
|
65
|
-
return /* @__PURE__ */ jsx(AdmLoading, { label: "Loading
|
|
65
|
+
return /* @__PURE__ */ jsx(AdmLoading, { label: "Loading overview" });
|
|
66
66
|
}
|
|
67
67
|
if (cockpit.isError || !cockpit.data) {
|
|
68
68
|
return /* @__PURE__ */ jsxs("div", { className: "adm-panel", role: "alert", children: [
|
|
69
|
-
/* @__PURE__ */ jsx("strong", { children: "
|
|
70
|
-
/* @__PURE__ */ jsx("p", { className: "mb-0 mt-2 text-sm text-[var(--text-secondary)]", children: "
|
|
69
|
+
/* @__PURE__ */ jsx("strong", { children: "We couldn't load the overview." }),
|
|
70
|
+
/* @__PURE__ */ jsx("p", { className: "mb-0 mt-2 text-sm text-[var(--text-secondary)]", children: "Something went wrong while fetching the numbers. Please try again." }),
|
|
71
71
|
/* @__PURE__ */ jsx(
|
|
72
72
|
"button",
|
|
73
73
|
{
|
|
@@ -87,8 +87,8 @@ function AdminIndexPage() {
|
|
|
87
87
|
/* @__PURE__ */ jsx(
|
|
88
88
|
PageHeader,
|
|
89
89
|
{
|
|
90
|
-
title: "
|
|
91
|
-
description: "
|
|
90
|
+
title: "Overview",
|
|
91
|
+
description: "How merchants are growing, how much money is moving, and what needs your attention.",
|
|
92
92
|
actions: /* @__PURE__ */ jsxs("div", { className: "adm-toolbar", children: [
|
|
93
93
|
/* @__PURE__ */ jsx(
|
|
94
94
|
CurrencyFilter,
|
|
@@ -130,7 +130,7 @@ function AdminIndexPage() {
|
|
|
130
130
|
}
|
|
131
131
|
),
|
|
132
132
|
/* @__PURE__ */ jsxs("div", { className: "adm-grid", children: [
|
|
133
|
-
/* @__PURE__ */ jsx("div", { className: "adm-span-8", children: /* @__PURE__ */ jsx(Panel, { title: "
|
|
133
|
+
/* @__PURE__ */ jsx("div", { className: "adm-span-8", children: /* @__PURE__ */ jsx(Panel, { title: "Sales over time", caption, children: focus ? /* @__PURE__ */ jsx(
|
|
134
134
|
GmvOrdersChart,
|
|
135
135
|
{
|
|
136
136
|
gmvTrend,
|
package/dist/admin/page.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/admin/page.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo } from 'react';\nimport {\n AdmLoading,\n CurrencyFilter,\n GmvOrdersChart,\n PICK_CURRENCY,\n PageHeader,\n Panel,\n PeriodSelector,\n periodPhrase,\n customRange,\n focusCurrency,\n moneyIn,\n rangeFromKey,\n rowFor,\n toTrend,\n useCurrencyParam,\n usePersistedDateRange,\n} from './_ui';\nimport { useGatewayChannels, usePlatformCockpit } from './hooks';\nimport { ActivationPanel } from './_overview/activation-panel';\nimport { AttentionPanel } from './_overview/attention-panel';\nimport { ControlRoom } from './_overview/control-room';\nimport { KpiRow } from './_overview/kpi-row';\nimport { LeadersPanel } from './_overview/leaders-panel';\nimport { PortfolioPanel } from './_overview/portfolio-panel';\nimport { PositionPanel } from './_overview/position-panel';\nimport { ChannelPanel } from './revenue/channel-panel';\n\nexport default function AdminIndexPage() {\n const {\n rangeKey,\n setRangeKey,\n customStart,\n customEnd,\n onCustomChange,\n compare,\n } = usePersistedDateRange();\n // Money comes back once per currency; `?currency=` narrows it to one.\n const [currency, setCurrency] = useCurrencyParam();\n const range = useMemo(\n () =>\n rangeKey === 'custom'\n ? customRange(customStart, customEnd)\n : rangeFromKey(rangeKey),\n [customEnd, customStart, rangeKey],\n );\n const cockpit = usePlatformCockpit({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n const channels = useGatewayChannels({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n const currencies = useMemo(\n () => cockpit.data?.currencies ?? [],\n [cockpit.data],\n );\n // A chart can only draw one currency: the filter's, or the only one.\n const focus = focusCurrency(currencies, currency);\n const series = useMemo(\n () =>\n rowFor(cockpit.data?.trend.performanceSeriesByCurrency, focus)?.series,\n [cockpit.data, focus],\n );\n const gmvTrend = useMemo(() => toTrend(series, 'gmv'), [series]);\n const ordersTrend = useMemo(() => toTrend(series, 'orders'), [series]);\n\n if (cockpit.isLoading) {\n return <AdmLoading label=\"Loading
|
|
1
|
+
{"version":3,"sources":["../../src/admin/page.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo } from 'react';\nimport {\n AdmLoading,\n CurrencyFilter,\n GmvOrdersChart,\n PICK_CURRENCY,\n PageHeader,\n Panel,\n PeriodSelector,\n periodPhrase,\n customRange,\n focusCurrency,\n moneyIn,\n rangeFromKey,\n rowFor,\n toTrend,\n useCurrencyParam,\n usePersistedDateRange,\n} from './_ui';\nimport { useGatewayChannels, usePlatformCockpit } from './hooks';\nimport { ActivationPanel } from './_overview/activation-panel';\nimport { AttentionPanel } from './_overview/attention-panel';\nimport { ControlRoom } from './_overview/control-room';\nimport { KpiRow } from './_overview/kpi-row';\nimport { LeadersPanel } from './_overview/leaders-panel';\nimport { PortfolioPanel } from './_overview/portfolio-panel';\nimport { PositionPanel } from './_overview/position-panel';\nimport { ChannelPanel } from './revenue/channel-panel';\n\nexport default function AdminIndexPage() {\n const {\n rangeKey,\n setRangeKey,\n customStart,\n customEnd,\n onCustomChange,\n compare,\n } = usePersistedDateRange();\n // Money comes back once per currency; `?currency=` narrows it to one.\n const [currency, setCurrency] = useCurrencyParam();\n const range = useMemo(\n () =>\n rangeKey === 'custom'\n ? customRange(customStart, customEnd)\n : rangeFromKey(rangeKey),\n [customEnd, customStart, rangeKey],\n );\n const cockpit = usePlatformCockpit({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n const channels = useGatewayChannels({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n const currencies = useMemo(\n () => cockpit.data?.currencies ?? [],\n [cockpit.data],\n );\n // A chart can only draw one currency: the filter's, or the only one.\n const focus = focusCurrency(currencies, currency);\n const series = useMemo(\n () =>\n rowFor(cockpit.data?.trend.performanceSeriesByCurrency, focus)?.series,\n [cockpit.data, focus],\n );\n const gmvTrend = useMemo(() => toTrend(series, 'gmv'), [series]);\n const ordersTrend = useMemo(() => toTrend(series, 'orders'), [series]);\n\n if (cockpit.isLoading) {\n return <AdmLoading label=\"Loading overview\" />;\n }\n\n if (cockpit.isError || !cockpit.data) {\n return (\n <div className=\"adm-panel\" role=\"alert\">\n <strong>We couldn't load the overview.</strong>\n <p className=\"mb-0 mt-2 text-sm text-[var(--text-secondary)]\">\n Something went wrong while fetching the numbers. Please try again.\n </p>\n <button\n type=\"button\"\n className=\"mt-4 text-sm font-semibold text-[var(--accent-primary)]\"\n onClick={() => void cockpit.refetch()}\n >\n Try again\n </button>\n </div>\n );\n }\n\n const data = cockpit.data;\n const span = periodPhrase(range);\n const period = `${span[0].toUpperCase()}${span.slice(1)}`;\n const caption = focus ? `${period} · ${focus}` : period;\n\n return (\n <div>\n <PageHeader\n title=\"Overview\"\n description=\"How merchants are growing, how much money is moving, and what needs your attention.\"\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\n <ControlRoom\n attention={data.attention}\n currency={currency}\n totals={data.totals}\n />\n\n <KpiRow\n data={data}\n currency={currency}\n focus={focus}\n compare={compare}\n caption={caption}\n />\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-8\">\n <Panel title=\"Sales over time\" caption={caption}>\n {focus ? (\n <GmvOrdersChart\n gmvTrend={gmvTrend}\n ordersTrend={ordersTrend}\n previousGmvTrend={[]}\n previousOrdersTrend={[]}\n compare={false}\n span={span}\n money={moneyIn(focus)}\n />\n ) : (\n <p className=\"text-sm text-[var(--text-secondary)]\">\n {PICK_CURRENCY}\n </p>\n )}\n </Panel>\n </div>\n <div className=\"adm-span-4\">\n <PositionPanel data={data} currency={currency} />\n </div>\n </div>\n\n {channels.data && channels.data.byChannel.length > 0 && (\n <div className=\"adm-grid\">\n <div className=\"adm-span-12\">\n <ChannelPanel\n totals={channels.data.byChannel}\n focus={focus}\n caption={caption}\n />\n </div>\n </div>\n )}\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-12\">\n <ActivationPanel activation={data.activation} />\n </div>\n </div>\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-4\">\n <AttentionPanel attention={data.attention} />\n </div>\n <div className=\"adm-span-4\">\n <LeadersPanel\n tenants={rowFor(data.topTenantsByCurrency, focus)?.tenants ?? []}\n focus={focus}\n caption={focus ? `${span} · ${focus}` : span}\n />\n </div>\n <div className=\"adm-span-4\">\n <PortfolioPanel\n statuses={data.statusBreakdown}\n industries={data.industryBreakdown}\n />\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";AAwEW,cAKL,YALK;AAtEX,SAAS,eAAe;AACxB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,oBAAoB,0BAA0B;AACvD,SAAS,uBAAuB;AAChC,SAAS,sBAAsB;AAC/B,SAAS,mBAAmB;AAC5B,SAAS,cAAc;AACvB,SAAS,oBAAoB;AAC7B,SAAS,sBAAsB;AAC/B,SAAS,qBAAqB;AAC9B,SAAS,oBAAoB;AAEd,SAAR,iBAAkC;AACvC,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,sBAAsB;AAE1B,QAAM,CAAC,UAAU,WAAW,IAAI,iBAAiB;AACjD,QAAM,QAAQ;AAAA,IACZ,MACE,aAAa,WACT,YAAY,aAAa,SAAS,IAClC,aAAa,QAAQ;AAAA,IAC3B,CAAC,WAAW,aAAa,QAAQ;AAAA,EACnC;AACA,QAAM,UAAU,mBAAmB;AAAA,IACjC,WAAW,MAAM;AAAA,IACjB,SAAS,MAAM;AAAA,EACjB,CAAC;AACD,QAAM,WAAW,mBAAmB;AAAA,IAClC,WAAW,MAAM;AAAA,IACjB,SAAS,MAAM;AAAA,EACjB,CAAC;AACD,QAAM,aAAa;AAAA,IACjB,MAAM,QAAQ,MAAM,cAAc,CAAC;AAAA,IACnC,CAAC,QAAQ,IAAI;AAAA,EACf;AAEA,QAAM,QAAQ,cAAc,YAAY,QAAQ;AAChD,QAAM,SAAS;AAAA,IACb,MACE,OAAO,QAAQ,MAAM,MAAM,6BAA6B,KAAK,GAAG;AAAA,IAClE,CAAC,QAAQ,MAAM,KAAK;AAAA,EACtB;AACA,QAAM,WAAW,QAAQ,MAAM,QAAQ,QAAQ,KAAK,GAAG,CAAC,MAAM,CAAC;AAC/D,QAAM,cAAc,QAAQ,MAAM,QAAQ,QAAQ,QAAQ,GAAG,CAAC,MAAM,CAAC;AAErE,MAAI,QAAQ,WAAW;AACrB,WAAO,oBAAC,cAAW,OAAM,oBAAmB;AAAA,EAC9C;AAEA,MAAI,QAAQ,WAAW,CAAC,QAAQ,MAAM;AACpC,WACE,qBAAC,SAAI,WAAU,aAAY,MAAK,SAC9B;AAAA,0BAAC,YAAO,4CAAmC;AAAA,MAC3C,oBAAC,OAAE,WAAU,kDAAiD,gFAE9D;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,WAAU;AAAA,UACV,SAAS,MAAM,KAAK,QAAQ,QAAQ;AAAA,UACrC;AAAA;AAAA,MAED;AAAA,OACF;AAAA,EAEJ;AAEA,QAAM,OAAO,QAAQ;AACrB,QAAM,OAAO,aAAa,KAAK;AAC/B,QAAM,SAAS,GAAG,KAAK,CAAC,EAAE,YAAY,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC;AACvD,QAAM,UAAU,QAAQ,GAAG,MAAM,SAAM,KAAK,KAAK;AAEjD,SACE,qBAAC,SACC;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,aAAY;AAAA,QACZ,SACE,qBAAC,SAAI,WAAU,eACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,OAAO;AAAA,cACP,UAAU;AAAA,cACV;AAAA;AAAA,UACF;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,OAAO;AAAA,cACP,UAAU;AAAA,cACV;AAAA,cACA;AAAA,cACA;AAAA;AAAA,UACF;AAAA,WACF;AAAA;AAAA,IAEJ;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,KAAK;AAAA,QAChB;AAAA,QACA,QAAQ,KAAK;AAAA;AAAA,IACf;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,IACF;AAAA,IAEA,qBAAC,SAAI,WAAU,YACb;AAAA,0BAAC,SAAI,WAAU,cACb,8BAAC,SAAM,OAAM,mBAAkB,SAC5B,kBACC;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA;AAAA,UACA,kBAAkB,CAAC;AAAA,UACnB,qBAAqB,CAAC;AAAA,UACtB,SAAS;AAAA,UACT;AAAA,UACA,OAAO,QAAQ,KAAK;AAAA;AAAA,MACtB,IAEA,oBAAC,OAAE,WAAU,wCACV,yBACH,GAEJ,GACF;AAAA,MACA,oBAAC,SAAI,WAAU,cACb,8BAAC,iBAAc,MAAY,UAAoB,GACjD;AAAA,OACF;AAAA,IAEC,SAAS,QAAQ,SAAS,KAAK,UAAU,SAAS,KACjD,oBAAC,SAAI,WAAU,YACb,8BAAC,SAAI,WAAU,eACb;AAAA,MAAC;AAAA;AAAA,QACC,QAAQ,SAAS,KAAK;AAAA,QACtB;AAAA,QACA;AAAA;AAAA,IACF,GACF,GACF;AAAA,IAGF,oBAAC,SAAI,WAAU,YACb,8BAAC,SAAI,WAAU,eACb,8BAAC,mBAAgB,YAAY,KAAK,YAAY,GAChD,GACF;AAAA,IAEA,qBAAC,SAAI,WAAU,YACb;AAAA,0BAAC,SAAI,WAAU,cACb,8BAAC,kBAAe,WAAW,KAAK,WAAW,GAC7C;AAAA,MACA,oBAAC,SAAI,WAAU,cACb;AAAA,QAAC;AAAA;AAAA,UACC,SAAS,OAAO,KAAK,sBAAsB,KAAK,GAAG,WAAW,CAAC;AAAA,UAC/D;AAAA,UACA,SAAS,QAAQ,GAAG,IAAI,SAAM,KAAK,KAAK;AAAA;AAAA,MAC1C,GACF;AAAA,MACA,oBAAC,SAAI,WAAU,cACb;AAAA,QAAC;AAAA;AAAA,UACC,UAAU,KAAK;AAAA,UACf,YAAY,KAAK;AAAA;AAAA,MACnB,GACF;AAAA,OACF;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -7,6 +7,7 @@ import { Banner } from "@withpotter/ui-kit/banner";
|
|
|
7
7
|
import { Input } from "@withpotter/ui-kit/input";
|
|
8
8
|
import { Checkbox } from "@withpotter/ui-kit/checkbox";
|
|
9
9
|
import { Field } from "@withpotter/ui-kit/field";
|
|
10
|
+
import { requirementGroupLabel, requirementLabel } from "./requirement-labels";
|
|
10
11
|
import { formatMinor, parseMajorToMinor } from "@withpotter/lib/money-minor";
|
|
11
12
|
import {
|
|
12
13
|
TIER_REQUIREMENTS
|
|
@@ -53,15 +54,15 @@ function TierDialog({
|
|
|
53
54
|
const dailyMaximumMinor = parseMajorToMinor(daily, code);
|
|
54
55
|
const monthlyMaximumMinor = monthly ? parseMajorToMinor(monthly, code) : null;
|
|
55
56
|
if (!key.trim() || !label.trim())
|
|
56
|
-
return setFormError("
|
|
57
|
+
return setFormError("Short name and display name are required.");
|
|
57
58
|
if (!minimumMinor || !maximumMinor || !dailyMaximumMinor)
|
|
58
59
|
return setFormError(
|
|
59
|
-
"
|
|
60
|
+
"The smallest transfer, largest transfer and daily limit must be valid amounts."
|
|
60
61
|
);
|
|
61
62
|
if (monthly && !monthlyMaximumMinor)
|
|
62
|
-
return setFormError("The monthly limit
|
|
63
|
+
return setFormError("The monthly limit must be a valid amount.");
|
|
63
64
|
if (!/^\d+$/.test(position))
|
|
64
|
-
return setFormError("
|
|
65
|
+
return setFormError("Priority must be a whole number.");
|
|
65
66
|
onSave({
|
|
66
67
|
key: key.trim(),
|
|
67
68
|
currency: code,
|
|
@@ -79,7 +80,7 @@ function TierDialog({
|
|
|
79
80
|
FormDialog,
|
|
80
81
|
{
|
|
81
82
|
title: tier ? "Edit tier" : "New payout tier",
|
|
82
|
-
description: "
|
|
83
|
+
description: "The sending limits a merchant gets once they have completed every check listed below.",
|
|
83
84
|
size: "lg",
|
|
84
85
|
onClose,
|
|
85
86
|
footer: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -89,15 +90,24 @@ function TierDialog({
|
|
|
89
90
|
children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
90
91
|
(formError || error) && /* @__PURE__ */ jsx(Banner, { tone: "error", children: formError ?? error }),
|
|
91
92
|
/* @__PURE__ */ jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [
|
|
92
|
-
/* @__PURE__ */ jsx(
|
|
93
|
-
|
|
93
|
+
/* @__PURE__ */ jsx(
|
|
94
|
+
Field,
|
|
94
95
|
{
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
label: "Short name",
|
|
97
|
+
htmlFor: "t-key",
|
|
98
|
+
required: true,
|
|
99
|
+
hint: "A unique code for this level, for example starter.",
|
|
100
|
+
children: /* @__PURE__ */ jsx(
|
|
101
|
+
Input,
|
|
102
|
+
{
|
|
103
|
+
id: "t-key",
|
|
104
|
+
value: key,
|
|
105
|
+
onChange: (e) => setKey(e.target.value)
|
|
106
|
+
}
|
|
107
|
+
)
|
|
98
108
|
}
|
|
99
|
-
)
|
|
100
|
-
/* @__PURE__ */ jsx(Field, { label: "
|
|
109
|
+
),
|
|
110
|
+
/* @__PURE__ */ jsx(Field, { label: "Display name", htmlFor: "t-label", required: true, children: /* @__PURE__ */ jsx(
|
|
101
111
|
Input,
|
|
102
112
|
{
|
|
103
113
|
id: "t-label",
|
|
@@ -117,9 +127,9 @@ function TierDialog({
|
|
|
117
127
|
/* @__PURE__ */ jsx(
|
|
118
128
|
Field,
|
|
119
129
|
{
|
|
120
|
-
label: "
|
|
130
|
+
label: "Priority",
|
|
121
131
|
htmlFor: "t-pos",
|
|
122
|
-
hint: "
|
|
132
|
+
hint: "If a merchant qualifies for several levels, the one with the higher number is used.",
|
|
123
133
|
children: /* @__PURE__ */ jsx(
|
|
124
134
|
Input,
|
|
125
135
|
{
|
|
@@ -131,7 +141,7 @@ function TierDialog({
|
|
|
131
141
|
)
|
|
132
142
|
}
|
|
133
143
|
),
|
|
134
|
-
/* @__PURE__ */ jsx(Field, { label: "
|
|
144
|
+
/* @__PURE__ */ jsx(Field, { label: "Smallest single transfer", htmlFor: "t-min", required: true, children: /* @__PURE__ */ jsx(
|
|
135
145
|
Input,
|
|
136
146
|
{
|
|
137
147
|
id: "t-min",
|
|
@@ -140,7 +150,7 @@ function TierDialog({
|
|
|
140
150
|
onChange: (e) => setMin(e.target.value)
|
|
141
151
|
}
|
|
142
152
|
) }),
|
|
143
|
-
/* @__PURE__ */ jsx(Field, { label: "
|
|
153
|
+
/* @__PURE__ */ jsx(Field, { label: "Largest single transfer", htmlFor: "t-max", required: true, children: /* @__PURE__ */ jsx(
|
|
144
154
|
Input,
|
|
145
155
|
{
|
|
146
156
|
id: "t-max",
|
|
@@ -149,7 +159,7 @@ function TierDialog({
|
|
|
149
159
|
onChange: (e) => setMax(e.target.value)
|
|
150
160
|
}
|
|
151
161
|
) }),
|
|
152
|
-
/* @__PURE__ */ jsx(Field, { label: "Daily
|
|
162
|
+
/* @__PURE__ */ jsx(Field, { label: "Daily limit", htmlFor: "t-day", required: true, children: /* @__PURE__ */ jsx(
|
|
153
163
|
Input,
|
|
154
164
|
{
|
|
155
165
|
id: "t-day",
|
|
@@ -158,7 +168,7 @@ function TierDialog({
|
|
|
158
168
|
onChange: (e) => setDaily(e.target.value)
|
|
159
169
|
}
|
|
160
170
|
) }),
|
|
161
|
-
/* @__PURE__ */ jsx(Field, { label: "Monthly
|
|
171
|
+
/* @__PURE__ */ jsx(Field, { label: "Monthly limit", htmlFor: "t-mon", hint: "Optional.", children: /* @__PURE__ */ jsx(
|
|
162
172
|
Input,
|
|
163
173
|
{
|
|
164
174
|
id: "t-mon",
|
|
@@ -170,16 +180,16 @@ function TierDialog({
|
|
|
170
180
|
] }),
|
|
171
181
|
/* @__PURE__ */ jsxs("fieldset", { className: "space-y-2", children: [
|
|
172
182
|
/* @__PURE__ */ jsxs("legend", { className: "text-sm font-medium text-[var(--text-secondary)]", children: [
|
|
173
|
-
"
|
|
183
|
+
"Checks a merchant must complete (",
|
|
174
184
|
requirements.length,
|
|
175
185
|
")"
|
|
176
186
|
] }),
|
|
177
187
|
/* @__PURE__ */ jsx("div", { className: "max-h-56 space-y-3 overflow-y-auto rounded-md border border-[var(--border-default)] p-3", children: Object.entries(TIER_REQUIREMENTS).map(([group, ops]) => /* @__PURE__ */ jsxs("div", { children: [
|
|
178
|
-
/* @__PURE__ */ jsx("p", { className: "mb-1 text-xs font-medium text-[var(--text-tertiary)]", children: group }),
|
|
188
|
+
/* @__PURE__ */ jsx("p", { className: "mb-1 text-xs font-medium text-[var(--text-tertiary)]", children: requirementGroupLabel(group) }),
|
|
179
189
|
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-x-4 gap-y-1", children: ops.map((op) => /* @__PURE__ */ jsx(
|
|
180
190
|
Checkbox,
|
|
181
191
|
{
|
|
182
|
-
label: op,
|
|
192
|
+
label: requirementLabel(op),
|
|
183
193
|
checked: requirements.includes(op),
|
|
184
194
|
onChange: () => toggle(op)
|
|
185
195
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/payout-tiers/_tier-dialog.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { FormDialog } from '@withpotter/ui-kit/form-dialog';\nimport { Button } from '@withpotter/ui-kit/button';\nimport { Banner } from '@withpotter/ui-kit/banner';\nimport { Input } from '@withpotter/ui-kit/input';\nimport { Checkbox } from '@withpotter/ui-kit/checkbox';\nimport { Field } from '@withpotter/ui-kit/field';\nimport { formatMinor, parseMajorToMinor } from '@withpotter/lib/money-minor';\nimport {\n TIER_REQUIREMENTS,\n type PayoutTier,\n type PayoutTierInput,\n} from '@withpotter/lib/admin-money-api';\n\n/** Minor units back to what a person types: \"150000\" NGN is \"1500\". */\nfunction toMajor(minor: string, currency: string): string {\n return formatMinor(minor, currency).replace(/[^\\d.]/g, '');\n}\n\nexport function TierDialog({\n tier,\n saving,\n error,\n onSave,\n onClose,\n}: {\n tier: PayoutTier | null;\n saving: boolean;\n error: string | null;\n onSave: (body: PayoutTierInput) => void;\n onClose: () => void;\n}) {\n const [key, setKey] = useState(tier?.key ?? '');\n const [currency, setCurrency] = useState(tier?.currency ?? 'NGN');\n const [label, setLabel] = useState(tier?.label ?? '');\n const [position, setPosition] = useState(String(tier?.position ?? 0));\n const [requirements, setRequirements] = useState<string[]>(\n tier?.requirements ?? [],\n );\n const [min, setMin] = useState(\n tier ? toMajor(tier.minimumMinor, tier.currency) : '',\n );\n const [max, setMax] = useState(\n tier ? toMajor(tier.maximumMinor, tier.currency) : '',\n );\n const [daily, setDaily] = useState(\n tier ? toMajor(tier.dailyMaximumMinor, tier.currency) : '',\n );\n const [monthly, setMonthly] = useState(\n tier?.monthlyMaximumMinor\n ? toMajor(tier.monthlyMaximumMinor, tier.currency)\n : '',\n );\n const [enabled, setEnabled] = useState(tier?.enabled ?? true);\n const [formError, setFormError] = useState<string | null>(null);\n\n const toggle = (r: string) =>\n setRequirements((c) =>\n c.includes(r) ? c.filter((x) => x !== r) : [...c, r],\n );\n\n const submit = () => {\n setFormError(null);\n const code = currency.trim().toUpperCase();\n const minimumMinor = parseMajorToMinor(min, code);\n const maximumMinor = parseMajorToMinor(max, code);\n const dailyMaximumMinor = parseMajorToMinor(daily, code);\n const monthlyMaximumMinor = monthly\n ? parseMajorToMinor(monthly, code)\n : null;\n if (!key.trim() || !label.trim())\n return setFormError('Key and label are required.');\n if (!minimumMinor || !maximumMinor || !dailyMaximumMinor)\n return setFormError(\n 'Minimum, maximum and daily limit must be valid amounts.',\n );\n if (monthly && !monthlyMaximumMinor)\n return setFormError('The monthly limit is not a valid amount.');\n if (!/^\\d+$/.test(position))\n return setFormError('Position must be a whole number.');\n onSave({\n key: key.trim(),\n currency: code,\n label: label.trim(),\n position: Number(position),\n requirements,\n minimumMinor,\n maximumMinor,\n dailyMaximumMinor,\n monthlyMaximumMinor,\n enabled,\n });\n };\n\n return (\n <FormDialog\n title={tier ? 'Edit tier' : 'New payout tier'}\n description=\"Limits a business gets once it has passed every listed verification.\"\n size=\"lg\"\n onClose={onClose}\n footer={\n <>\n <Button variant=\"secondary\" onClick={onClose}>\n Cancel\n </Button>\n <Button onClick={submit} loading={saving}>\n {tier ? 'Save tier' : 'Create tier'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-4\">\n {(formError || error) && (\n <Banner tone=\"error\">{formError ?? error}</Banner>\n )}\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <Field label=\"Key\" htmlFor=\"t-key\" required>\n <Input\n id=\"t-key\"\n value={key}\n onChange={(e) => setKey(e.target.value)}\n />\n </Field>\n <Field label=\"Label\" htmlFor=\"t-label\" required>\n <Input\n id=\"t-label\"\n value={label}\n onChange={(e) => setLabel(e.target.value)}\n />\n </Field>\n <Field label=\"Currency\" htmlFor=\"t-cur\" required>\n <Input\n id=\"t-cur\"\n value={currency}\n maxLength={3}\n onChange={(e) => setCurrency(e.target.value)}\n />\n </Field>\n <Field\n label=\"Position\"\n htmlFor=\"t-pos\"\n hint=\"Higher tiers are chosen first when a business qualifies for several.\"\n >\n <Input\n id=\"t-pos\"\n inputMode=\"numeric\"\n value={position}\n onChange={(e) => setPosition(e.target.value)}\n />\n </Field>\n <Field label=\"Per-transfer minimum\" htmlFor=\"t-min\" required>\n <Input\n id=\"t-min\"\n inputMode=\"decimal\"\n value={min}\n onChange={(e) => setMin(e.target.value)}\n />\n </Field>\n <Field label=\"Per-transfer maximum\" htmlFor=\"t-max\" required>\n <Input\n id=\"t-max\"\n inputMode=\"decimal\"\n value={max}\n onChange={(e) => setMax(e.target.value)}\n />\n </Field>\n <Field label=\"Daily total\" htmlFor=\"t-day\" required>\n <Input\n id=\"t-day\"\n inputMode=\"decimal\"\n value={daily}\n onChange={(e) => setDaily(e.target.value)}\n />\n </Field>\n <Field label=\"Monthly total\" htmlFor=\"t-mon\" hint=\"Optional.\">\n <Input\n id=\"t-mon\"\n inputMode=\"decimal\"\n value={monthly}\n onChange={(e) => setMonthly(e.target.value)}\n />\n </Field>\n </div>\n\n <fieldset className=\"space-y-2\">\n <legend className=\"text-sm font-medium text-[var(--text-secondary)]\">\n Required verifications ({requirements.length})\n </legend>\n <div className=\"max-h-56 space-y-3 overflow-y-auto rounded-md border border-[var(--border-default)] p-3\">\n {Object.entries(TIER_REQUIREMENTS).map(([group, ops]) => (\n <div key={group}>\n <p className=\"mb-1 text-xs font-medium text-[var(--text-tertiary)]\">\n {group}\n </p>\n <div className=\"flex flex-wrap gap-x-4 gap-y-1\">\n {ops.map((op) => (\n <Checkbox\n key={op}\n label={op}\n checked={requirements.includes(op)}\n onChange={() => toggle(op)}\n />\n ))}\n </div>\n </div>\n ))}\n </div>\n </fieldset>\n\n <Checkbox\n label=\"Enabled\"\n checked={enabled}\n onChange={(e) => setEnabled(e.target.checked)}\n />\n </div>\n </FormDialog>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/payout-tiers/_tier-dialog.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { FormDialog } from '@withpotter/ui-kit/form-dialog';\nimport { Button } from '@withpotter/ui-kit/button';\nimport { Banner } from '@withpotter/ui-kit/banner';\nimport { Input } from '@withpotter/ui-kit/input';\nimport { Checkbox } from '@withpotter/ui-kit/checkbox';\nimport { Field } from '@withpotter/ui-kit/field';\nimport { requirementGroupLabel, requirementLabel } from './requirement-labels';\nimport { formatMinor, parseMajorToMinor } from '@withpotter/lib/money-minor';\nimport {\n TIER_REQUIREMENTS,\n type PayoutTier,\n type PayoutTierInput,\n} from '@withpotter/lib/admin-money-api';\n\n/** Minor units back to what a person types: \"150000\" NGN is \"1500\". */\nfunction toMajor(minor: string, currency: string): string {\n return formatMinor(minor, currency).replace(/[^\\d.]/g, '');\n}\n\nexport function TierDialog({\n tier,\n saving,\n error,\n onSave,\n onClose,\n}: {\n tier: PayoutTier | null;\n saving: boolean;\n error: string | null;\n onSave: (body: PayoutTierInput) => void;\n onClose: () => void;\n}) {\n const [key, setKey] = useState(tier?.key ?? '');\n const [currency, setCurrency] = useState(tier?.currency ?? 'NGN');\n const [label, setLabel] = useState(tier?.label ?? '');\n const [position, setPosition] = useState(String(tier?.position ?? 0));\n const [requirements, setRequirements] = useState<string[]>(\n tier?.requirements ?? [],\n );\n const [min, setMin] = useState(\n tier ? toMajor(tier.minimumMinor, tier.currency) : '',\n );\n const [max, setMax] = useState(\n tier ? toMajor(tier.maximumMinor, tier.currency) : '',\n );\n const [daily, setDaily] = useState(\n tier ? toMajor(tier.dailyMaximumMinor, tier.currency) : '',\n );\n const [monthly, setMonthly] = useState(\n tier?.monthlyMaximumMinor\n ? toMajor(tier.monthlyMaximumMinor, tier.currency)\n : '',\n );\n const [enabled, setEnabled] = useState(tier?.enabled ?? true);\n const [formError, setFormError] = useState<string | null>(null);\n\n const toggle = (r: string) =>\n setRequirements((c) =>\n c.includes(r) ? c.filter((x) => x !== r) : [...c, r],\n );\n\n const submit = () => {\n setFormError(null);\n const code = currency.trim().toUpperCase();\n const minimumMinor = parseMajorToMinor(min, code);\n const maximumMinor = parseMajorToMinor(max, code);\n const dailyMaximumMinor = parseMajorToMinor(daily, code);\n const monthlyMaximumMinor = monthly\n ? parseMajorToMinor(monthly, code)\n : null;\n if (!key.trim() || !label.trim())\n return setFormError('Short name and display name are required.');\n if (!minimumMinor || !maximumMinor || !dailyMaximumMinor)\n return setFormError(\n 'The smallest transfer, largest transfer and daily limit must be valid amounts.',\n );\n if (monthly && !monthlyMaximumMinor)\n return setFormError('The monthly limit must be a valid amount.');\n if (!/^\\d+$/.test(position))\n return setFormError('Priority must be a whole number.');\n onSave({\n key: key.trim(),\n currency: code,\n label: label.trim(),\n position: Number(position),\n requirements,\n minimumMinor,\n maximumMinor,\n dailyMaximumMinor,\n monthlyMaximumMinor,\n enabled,\n });\n };\n\n return (\n <FormDialog\n title={tier ? 'Edit tier' : 'New payout tier'}\n description=\"The sending limits a merchant gets once they have completed every check listed below.\"\n size=\"lg\"\n onClose={onClose}\n footer={\n <>\n <Button variant=\"secondary\" onClick={onClose}>\n Cancel\n </Button>\n <Button onClick={submit} loading={saving}>\n {tier ? 'Save tier' : 'Create tier'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-4\">\n {(formError || error) && (\n <Banner tone=\"error\">{formError ?? error}</Banner>\n )}\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <Field\n label=\"Short name\"\n htmlFor=\"t-key\"\n required\n hint=\"A unique code for this level, for example starter.\"\n >\n <Input\n id=\"t-key\"\n value={key}\n onChange={(e) => setKey(e.target.value)}\n />\n </Field>\n <Field label=\"Display name\" htmlFor=\"t-label\" required>\n <Input\n id=\"t-label\"\n value={label}\n onChange={(e) => setLabel(e.target.value)}\n />\n </Field>\n <Field label=\"Currency\" htmlFor=\"t-cur\" required>\n <Input\n id=\"t-cur\"\n value={currency}\n maxLength={3}\n onChange={(e) => setCurrency(e.target.value)}\n />\n </Field>\n <Field\n label=\"Priority\"\n htmlFor=\"t-pos\"\n hint=\"If a merchant qualifies for several levels, the one with the higher number is used.\"\n >\n <Input\n id=\"t-pos\"\n inputMode=\"numeric\"\n value={position}\n onChange={(e) => setPosition(e.target.value)}\n />\n </Field>\n <Field label=\"Smallest single transfer\" htmlFor=\"t-min\" required>\n <Input\n id=\"t-min\"\n inputMode=\"decimal\"\n value={min}\n onChange={(e) => setMin(e.target.value)}\n />\n </Field>\n <Field label=\"Largest single transfer\" htmlFor=\"t-max\" required>\n <Input\n id=\"t-max\"\n inputMode=\"decimal\"\n value={max}\n onChange={(e) => setMax(e.target.value)}\n />\n </Field>\n <Field label=\"Daily limit\" htmlFor=\"t-day\" required>\n <Input\n id=\"t-day\"\n inputMode=\"decimal\"\n value={daily}\n onChange={(e) => setDaily(e.target.value)}\n />\n </Field>\n <Field label=\"Monthly limit\" htmlFor=\"t-mon\" hint=\"Optional.\">\n <Input\n id=\"t-mon\"\n inputMode=\"decimal\"\n value={monthly}\n onChange={(e) => setMonthly(e.target.value)}\n />\n </Field>\n </div>\n\n <fieldset className=\"space-y-2\">\n <legend className=\"text-sm font-medium text-[var(--text-secondary)]\">\n Checks a merchant must complete ({requirements.length})\n </legend>\n <div className=\"max-h-56 space-y-3 overflow-y-auto rounded-md border border-[var(--border-default)] p-3\">\n {Object.entries(TIER_REQUIREMENTS).map(([group, ops]) => (\n <div key={group}>\n <p className=\"mb-1 text-xs font-medium text-[var(--text-tertiary)]\">\n {requirementGroupLabel(group)}\n </p>\n <div className=\"flex flex-wrap gap-x-4 gap-y-1\">\n {ops.map((op) => (\n <Checkbox\n key={op}\n label={requirementLabel(op)}\n checked={requirements.includes(op)}\n onChange={() => toggle(op)}\n />\n ))}\n </div>\n </div>\n ))}\n </div>\n </fieldset>\n\n <Checkbox\n label=\"Enabled\"\n checked={enabled}\n onChange={(e) => setEnabled(e.target.checked)}\n />\n </div>\n </FormDialog>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const LABELS = {
|
|
2
|
+
nin: "National ID number (Nigeria)",
|
|
3
|
+
"nin-slip": "National ID slip (Nigeria)",
|
|
4
|
+
"advanced-nin": "Detailed national ID check (Nigeria)",
|
|
5
|
+
"premium-nin": "Full national ID check with photo (Nigeria)",
|
|
6
|
+
bvn: "Bank verification number (Nigeria)",
|
|
7
|
+
"validate-bvn": "Bank verification number match (Nigeria)",
|
|
8
|
+
"bvn-nin-selfie": "Bank number, national ID and selfie match (Nigeria)",
|
|
9
|
+
"phone-number": "Phone number",
|
|
10
|
+
"age-identity": "Age and identity",
|
|
11
|
+
passport: "Passport",
|
|
12
|
+
"drivers-license": "Driver's licence",
|
|
13
|
+
"voter-id": "Voter's card",
|
|
14
|
+
"ghana-card": "Ghana Card",
|
|
15
|
+
"ghana-passport": "Ghana passport",
|
|
16
|
+
"ghana-digital-address": "Ghana digital address",
|
|
17
|
+
"kenya-national-id": "Kenya national ID",
|
|
18
|
+
"kenya-passport": "Kenya passport",
|
|
19
|
+
"kenya-kra-pin": "Kenya tax number",
|
|
20
|
+
"uganda-nin": "Uganda national ID",
|
|
21
|
+
"uganda-voter-id": "Uganda voter's card",
|
|
22
|
+
"south-africa-national-id": "South Africa national ID",
|
|
23
|
+
"angola-national-id": "Angola national ID",
|
|
24
|
+
"zambia-nrc": "Zambia national registration card",
|
|
25
|
+
"zimbabwe-national-id": "Zimbabwe national ID",
|
|
26
|
+
"zimbabwe-credit-check": "Zimbabwe credit check",
|
|
27
|
+
"canada-ekyc": "Canada identity check",
|
|
28
|
+
"uk-ekyc": "UK identity check",
|
|
29
|
+
liveness: "Live selfie check",
|
|
30
|
+
"face-compare": "Selfie matches photo",
|
|
31
|
+
"face-doc-compare": "Selfie matches ID document",
|
|
32
|
+
"document-analysis": "ID document check",
|
|
33
|
+
"generic-ocr": "Text read from a document",
|
|
34
|
+
"utility-bill": "Utility bill",
|
|
35
|
+
"individual-address": "Home address",
|
|
36
|
+
"aml-screening": "Anti-money-laundering screening",
|
|
37
|
+
"user-screening": "Person screening (sanctions and watchlists)",
|
|
38
|
+
"email-check": "Email address check",
|
|
39
|
+
"ip-screening": "Device location check",
|
|
40
|
+
"phone-check": "Phone number check",
|
|
41
|
+
cac: "Company registration (Nigeria)",
|
|
42
|
+
tin: "Tax number",
|
|
43
|
+
"global-business-search": "Worldwide business search",
|
|
44
|
+
"kenya-business": "Kenya business registration",
|
|
45
|
+
"zambia-business": "Zambia business registration",
|
|
46
|
+
"business-document": "Business documents",
|
|
47
|
+
"business-address": "Business address",
|
|
48
|
+
"business-aml-screening": "Business screening (sanctions and watchlists)",
|
|
49
|
+
"business-owners": "Owners listed",
|
|
50
|
+
"business-profile": "Business profile completed",
|
|
51
|
+
"screening-clear": "Screening came back clear"
|
|
52
|
+
};
|
|
53
|
+
const GROUP_LABELS = {
|
|
54
|
+
Individual: "Checks on a person",
|
|
55
|
+
Business: "Checks on a business",
|
|
56
|
+
Readiness: "Account readiness (worked out automatically)"
|
|
57
|
+
};
|
|
58
|
+
function requirementLabel(code) {
|
|
59
|
+
if (LABELS[code]) return LABELS[code];
|
|
60
|
+
const spaced = code.replace(/[-_]+/g, " ").trim();
|
|
61
|
+
return spaced.charAt(0).toUpperCase() + spaced.slice(1);
|
|
62
|
+
}
|
|
63
|
+
function requirementGroupLabel(group) {
|
|
64
|
+
return GROUP_LABELS[group] ?? group;
|
|
65
|
+
}
|
|
66
|
+
export {
|
|
67
|
+
requirementGroupLabel,
|
|
68
|
+
requirementLabel
|
|
69
|
+
};
|
|
70
|
+
//# sourceMappingURL=requirement-labels.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/payout-tiers/requirement-labels.ts"],"sourcesContent":["/**\n * Plain-English names for the identity and business checks a payout level can\n * require. The codes are what the API stores; admins should never have to read them.\n */\nconst LABELS: Record<string, string> = {\n nin: 'National ID number (Nigeria)',\n 'nin-slip': 'National ID slip (Nigeria)',\n 'advanced-nin': 'Detailed national ID check (Nigeria)',\n 'premium-nin': 'Full national ID check with photo (Nigeria)',\n bvn: 'Bank verification number (Nigeria)',\n 'validate-bvn': 'Bank verification number match (Nigeria)',\n 'bvn-nin-selfie': 'Bank number, national ID and selfie match (Nigeria)',\n 'phone-number': 'Phone number',\n 'age-identity': 'Age and identity',\n passport: 'Passport',\n 'drivers-license': \"Driver's licence\",\n 'voter-id': \"Voter's card\",\n 'ghana-card': 'Ghana Card',\n 'ghana-passport': 'Ghana passport',\n 'ghana-digital-address': 'Ghana digital address',\n 'kenya-national-id': 'Kenya national ID',\n 'kenya-passport': 'Kenya passport',\n 'kenya-kra-pin': 'Kenya tax number',\n 'uganda-nin': 'Uganda national ID',\n 'uganda-voter-id': \"Uganda voter's card\",\n 'south-africa-national-id': 'South Africa national ID',\n 'angola-national-id': 'Angola national ID',\n 'zambia-nrc': 'Zambia national registration card',\n 'zimbabwe-national-id': 'Zimbabwe national ID',\n 'zimbabwe-credit-check': 'Zimbabwe credit check',\n 'canada-ekyc': 'Canada identity check',\n 'uk-ekyc': 'UK identity check',\n liveness: 'Live selfie check',\n 'face-compare': 'Selfie matches photo',\n 'face-doc-compare': 'Selfie matches ID document',\n 'document-analysis': 'ID document check',\n 'generic-ocr': 'Text read from a document',\n 'utility-bill': 'Utility bill',\n 'individual-address': 'Home address',\n 'aml-screening': 'Anti-money-laundering screening',\n 'user-screening': 'Person screening (sanctions and watchlists)',\n 'email-check': 'Email address check',\n 'ip-screening': 'Device location check',\n 'phone-check': 'Phone number check',\n cac: 'Company registration (Nigeria)',\n tin: 'Tax number',\n 'global-business-search': 'Worldwide business search',\n 'kenya-business': 'Kenya business registration',\n 'zambia-business': 'Zambia business registration',\n 'business-document': 'Business documents',\n 'business-address': 'Business address',\n 'business-aml-screening': 'Business screening (sanctions and watchlists)',\n 'business-owners': 'Owners listed',\n 'business-profile': 'Business profile completed',\n 'screening-clear': 'Screening came back clear',\n};\n\nconst GROUP_LABELS: Record<string, string> = {\n Individual: 'Checks on a person',\n Business: 'Checks on a business',\n Readiness: 'Account readiness (worked out automatically)',\n};\n\n/** \"bvn\" → \"Bank verification number (Nigeria)\". Unknown codes stay readable. */\nexport function requirementLabel(code: string): string {\n if (LABELS[code]) return LABELS[code];\n const spaced = code.replace(/[-_]+/g, ' ').trim();\n return spaced.charAt(0).toUpperCase() + spaced.slice(1);\n}\n\nexport function requirementGroupLabel(group: string): string {\n return GROUP_LABELS[group] ?? group;\n}\n"],"mappings":"AAIA,MAAM,SAAiC;AAAA,EACrC,KAAK;AAAA,EACL,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,KAAK;AAAA,EACL,gBAAgB;AAAA,EAChB,kBAAkB;AAAA,EAClB,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,mBAAmB;AAAA,EACnB,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB,yBAAyB;AAAA,EACzB,qBAAqB;AAAA,EACrB,kBAAkB;AAAA,EAClB,iBAAiB;AAAA,EACjB,cAAc;AAAA,EACd,mBAAmB;AAAA,EACnB,4BAA4B;AAAA,EAC5B,sBAAsB;AAAA,EACtB,cAAc;AAAA,EACd,wBAAwB;AAAA,EACxB,yBAAyB;AAAA,EACzB,eAAe;AAAA,EACf,WAAW;AAAA,EACX,UAAU;AAAA,EACV,gBAAgB;AAAA,EAChB,oBAAoB;AAAA,EACpB,qBAAqB;AAAA,EACrB,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,sBAAsB;AAAA,EACtB,iBAAiB;AAAA,EACjB,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,KAAK;AAAA,EACL,KAAK;AAAA,EACL,0BAA0B;AAAA,EAC1B,kBAAkB;AAAA,EAClB,mBAAmB;AAAA,EACnB,qBAAqB;AAAA,EACrB,oBAAoB;AAAA,EACpB,0BAA0B;AAAA,EAC1B,mBAAmB;AAAA,EACnB,oBAAoB;AAAA,EACpB,mBAAmB;AACrB;AAEA,MAAM,eAAuC;AAAA,EAC3C,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,WAAW;AACb;AAGO,SAAS,iBAAiB,MAAsB;AACrD,MAAI,OAAO,IAAI,EAAG,QAAO,OAAO,IAAI;AACpC,QAAM,SAAS,KAAK,QAAQ,UAAU,GAAG,EAAE,KAAK;AAChD,SAAO,OAAO,OAAO,CAAC,EAAE,YAAY,IAAI,OAAO,MAAM,CAAC;AACxD;AAEO,SAAS,sBAAsB,OAAuB;AAC3D,SAAO,aAAa,KAAK,KAAK;AAChC;","names":[]}
|
|
@@ -11,15 +11,16 @@ import {
|
|
|
11
11
|
import { StatusBadge } from "@withpotter/components/shared/status-badge";
|
|
12
12
|
import { limitLines, requirementRows } from "./tier-preview";
|
|
13
13
|
import styles from "../_money/ledger.module.css";
|
|
14
|
+
import { requirementLabel } from "./requirement-labels";
|
|
14
15
|
function TierPreviewResult({ preview }) {
|
|
15
16
|
const { tier } = preview;
|
|
16
17
|
const rows = requirementRows(preview);
|
|
17
18
|
return /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 16 }, children: [
|
|
18
19
|
/* @__PURE__ */ jsxs(Card, { children: [
|
|
19
|
-
/* @__PURE__ */ jsx("h3", { className: styles.chosenName, children: tier ? `
|
|
20
|
+
/* @__PURE__ */ jsx("h3", { className: styles.chosenName, children: tier ? `Gets the ${tier.label} level in ${preview.currency}` : `No ${preview.currency} level fits yet` }),
|
|
20
21
|
tier ? /* @__PURE__ */ jsxs(FactList, { className: "mt-4", children: [
|
|
21
|
-
/* @__PURE__ */ jsx(Fact, { label: "
|
|
22
|
-
/* @__PURE__ */ jsx(Fact, { label: "
|
|
22
|
+
/* @__PURE__ */ jsx(Fact, { label: "Level", value: tier.label }),
|
|
23
|
+
/* @__PURE__ */ jsx(Fact, { label: "Priority", value: String(tier.position) }),
|
|
23
24
|
limitLines(tier).map(([label, value]) => /* @__PURE__ */ jsx(Fact, { label, value }, label)),
|
|
24
25
|
/* @__PURE__ */ jsx(
|
|
25
26
|
Fact,
|
|
@@ -28,23 +29,23 @@ function TierPreviewResult({ preview }) {
|
|
|
28
29
|
value: tier.enabled ? "Enabled" : "Disabled"
|
|
29
30
|
}
|
|
30
31
|
)
|
|
31
|
-
] }) : /* @__PURE__ */ jsx("p", { className: styles.quiet, children: "
|
|
32
|
+
] }) : /* @__PURE__ */ jsx("p", { className: styles.quiet, children: "This merchant has to verify their identity before they can send money out." })
|
|
32
33
|
] }),
|
|
33
34
|
/* @__PURE__ */ jsx(
|
|
34
35
|
TableShell,
|
|
35
36
|
{
|
|
36
37
|
minWidth: 0,
|
|
37
38
|
head: /* @__PURE__ */ jsxs("tr", { children: [
|
|
38
|
-
/* @__PURE__ */ jsx(Th, { children: "
|
|
39
|
-
/* @__PURE__ */ jsx(Th, { children: "
|
|
39
|
+
/* @__PURE__ */ jsx(Th, { children: "Check" }),
|
|
40
|
+
/* @__PURE__ */ jsx(Th, { children: "Result" })
|
|
40
41
|
] }),
|
|
41
42
|
children: rows.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: 2, className: styles.quiet, children: "Nothing verified and nothing required." }) }) : rows.map((r) => /* @__PURE__ */ jsxs(TableRow, { children: [
|
|
42
|
-
/* @__PURE__ */ jsx("td", { children: r.name }),
|
|
43
|
-
/* @__PURE__ */ jsx("td", { children: r.met ? /* @__PURE__ */ jsx(StatusBadge, { status: "completed", label: "
|
|
43
|
+
/* @__PURE__ */ jsx("td", { children: requirementLabel(r.name) }),
|
|
44
|
+
/* @__PURE__ */ jsx("td", { children: r.met ? /* @__PURE__ */ jsx(StatusBadge, { status: "completed", label: "Done" }) : /* @__PURE__ */ jsx(
|
|
44
45
|
StatusBadge,
|
|
45
46
|
{
|
|
46
47
|
status: "action_required",
|
|
47
|
-
label: "
|
|
48
|
+
label: "Still needed for the next level"
|
|
48
49
|
}
|
|
49
50
|
) })
|
|
50
51
|
] }, r.name))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/payout-tiers/tier-preview-result.tsx"],"sourcesContent":["'use client';\n\nimport {\n Card,\n Fact,\n FactList,\n TableRow,\n TableShell,\n Th,\n} from '@withpotter/ui-kit';\nimport { StatusBadge } from '@withpotter/components/shared/status-badge';\nimport type { PayoutTierPreview } from '@withpotter/lib/admin-money-api';\nimport { limitLines, requirementRows } from './tier-preview';\nimport styles from '../_money/ledger.module.css';\n\n/**\n * What the preview answered: the tier the business lands on with its\n * limits, then every requirement it names, met or still missing.\n */\nexport function TierPreviewResult({ preview }: { preview: PayoutTierPreview }) {\n const { tier } = preview;\n const rows = requirementRows(preview);\n\n return (\n <div style={{ display: 'grid', gap: 16 }}>\n <Card>\n <h3 className={styles.chosenName}>\n {tier\n ? `
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/payout-tiers/tier-preview-result.tsx"],"sourcesContent":["'use client';\n\nimport {\n Card,\n Fact,\n FactList,\n TableRow,\n TableShell,\n Th,\n} from '@withpotter/ui-kit';\nimport { StatusBadge } from '@withpotter/components/shared/status-badge';\nimport type { PayoutTierPreview } from '@withpotter/lib/admin-money-api';\nimport { limitLines, requirementRows } from './tier-preview';\nimport styles from '../_money/ledger.module.css';\nimport { requirementLabel } from './requirement-labels';\n\n/**\n * What the preview answered: the tier the business lands on with its\n * limits, then every requirement it names, met or still missing.\n */\nexport function TierPreviewResult({ preview }: { preview: PayoutTierPreview }) {\n const { tier } = preview;\n const rows = requirementRows(preview);\n\n return (\n <div style={{ display: 'grid', gap: 16 }}>\n <Card>\n <h3 className={styles.chosenName}>\n {tier\n ? `Gets the ${tier.label} level in ${preview.currency}`\n : `No ${preview.currency} level fits yet`}\n </h3>\n {tier ? (\n <FactList className=\"mt-4\">\n <Fact label=\"Level\" value={tier.label} />\n <Fact label=\"Priority\" value={String(tier.position)} />\n {limitLines(tier).map(([label, value]) => (\n <Fact key={label} label={label} value={value} />\n ))}\n <Fact\n label=\"Status\"\n value={tier.enabled ? 'Enabled' : 'Disabled'}\n />\n </FactList>\n ) : (\n <p className={styles.quiet}>\n This merchant has to verify their identity before they can send money out.\n </p>\n )}\n </Card>\n <TableShell\n minWidth={0}\n head={\n <tr>\n <Th>Check</Th>\n <Th>Result</Th>\n </tr>\n }\n >\n {rows.length === 0 ? (\n <tr>\n <td colSpan={2} className={styles.quiet}>\n Nothing verified and nothing required.\n </td>\n </tr>\n ) : (\n rows.map((r) => (\n <TableRow key={r.name}>\n <td>{requirementLabel(r.name)}</td>\n <td>\n {r.met ? (\n <StatusBadge status=\"completed\" label=\"Done\" />\n ) : (\n <StatusBadge\n status=\"action_required\"\n label=\"Still needed for the next level\"\n />\n )}\n </td>\n </TableRow>\n ))\n )}\n </TableShell>\n </div>\n );\n}\n"],"mappings":";AA2BQ,cAME,YANF;AAzBR;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,mBAAmB;AAE5B,SAAS,YAAY,uBAAuB;AAC5C,OAAO,YAAY;AACnB,SAAS,wBAAwB;AAM1B,SAAS,kBAAkB,EAAE,QAAQ,GAAmC;AAC7E,QAAM,EAAE,KAAK,IAAI;AACjB,QAAM,OAAO,gBAAgB,OAAO;AAEpC,SACE,qBAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,KAAK,GAAG,GACrC;AAAA,yBAAC,QACC;AAAA,0BAAC,QAAG,WAAW,OAAO,YACnB,iBACG,YAAY,KAAK,KAAK,aAAa,QAAQ,QAAQ,KACnD,MAAM,QAAQ,QAAQ,mBAC5B;AAAA,MACC,OACC,qBAAC,YAAS,WAAU,QAClB;AAAA,4BAAC,QAAK,OAAM,SAAQ,OAAO,KAAK,OAAO;AAAA,QACvC,oBAAC,QAAK,OAAM,YAAW,OAAO,OAAO,KAAK,QAAQ,GAAG;AAAA,QACpD,WAAW,IAAI,EAAE,IAAI,CAAC,CAAC,OAAO,KAAK,MAClC,oBAAC,QAAiB,OAAc,SAArB,KAAmC,CAC/C;AAAA,QACD;AAAA,UAAC;AAAA;AAAA,YACC,OAAM;AAAA,YACN,OAAO,KAAK,UAAU,YAAY;AAAA;AAAA,QACpC;AAAA,SACF,IAEA,oBAAC,OAAE,WAAW,OAAO,OAAO,wFAE5B;AAAA,OAEJ;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,UAAU;AAAA,QACV,MACE,qBAAC,QACC;AAAA,8BAAC,MAAG,mBAAK;AAAA,UACT,oBAAC,MAAG,oBAAM;AAAA,WACZ;AAAA,QAGD,eAAK,WAAW,IACf,oBAAC,QACC,8BAAC,QAAG,SAAS,GAAG,WAAW,OAAO,OAAO,oDAEzC,GACF,IAEA,KAAK,IAAI,CAAC,MACR,qBAAC,YACC;AAAA,8BAAC,QAAI,2BAAiB,EAAE,IAAI,GAAE;AAAA,UAC9B,oBAAC,QACE,YAAE,MACD,oBAAC,eAAY,QAAO,aAAY,OAAM,QAAO,IAE7C;AAAA,YAAC;AAAA;AAAA,cACC,QAAO;AAAA,cACP,OAAM;AAAA;AAAA,UACR,GAEJ;AAAA,aAXa,EAAE,IAYjB,CACD;AAAA;AAAA,IAEL;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
dangerRowBtn
|
|
15
15
|
} from "../_ui";
|
|
16
16
|
import { TierDialog } from "./_tier-dialog";
|
|
17
|
+
import { requirementLabel } from "./requirement-labels";
|
|
17
18
|
import { TIERS_KEY, useTiers } from "./use-tiers";
|
|
18
19
|
const TAG = {
|
|
19
20
|
padding: "2px 8px",
|
|
@@ -50,7 +51,7 @@ function TiersTable({
|
|
|
50
51
|
const columns = [
|
|
51
52
|
{
|
|
52
53
|
key: "label",
|
|
53
|
-
header: "
|
|
54
|
+
header: "Level",
|
|
54
55
|
render: (t) => /* @__PURE__ */ jsxs("span", { children: [
|
|
55
56
|
/* @__PURE__ */ jsx("span", { style: { fontWeight: 500 }, children: t.label }),
|
|
56
57
|
!t.enabled && /* @__PURE__ */ jsx("span", { style: { ...TAG, marginLeft: 8 }, children: "Disabled" }),
|
|
@@ -64,10 +65,8 @@ function TiersTable({
|
|
|
64
65
|
fontFamily: "var(--font-mono, monospace)"
|
|
65
66
|
},
|
|
66
67
|
children: [
|
|
67
|
-
t.key,
|
|
68
|
-
" \xB7 ",
|
|
69
68
|
t.currency,
|
|
70
|
-
" \xB7
|
|
69
|
+
" \xB7 priority ",
|
|
71
70
|
t.position
|
|
72
71
|
]
|
|
73
72
|
}
|
|
@@ -76,8 +75,8 @@ function TiersTable({
|
|
|
76
75
|
},
|
|
77
76
|
{
|
|
78
77
|
key: "requirements",
|
|
79
|
-
header: "
|
|
80
|
-
render: (t) => t.requirements.length === 0 ? "\u2013" : /* @__PURE__ */ jsx("span", { style: { display: "flex", flexWrap: "wrap", gap: 4 }, children: t.requirements.map((r) => /* @__PURE__ */ jsx("span", { style: TAG, children: r }, r)) })
|
|
78
|
+
header: "Must complete",
|
|
79
|
+
render: (t) => t.requirements.length === 0 ? "\u2013" : /* @__PURE__ */ jsx("span", { style: { display: "flex", flexWrap: "wrap", gap: 4 }, children: t.requirements.map((r) => /* @__PURE__ */ jsx("span", { style: TAG, children: requirementLabel(r) }, r)) })
|
|
81
80
|
},
|
|
82
81
|
{
|
|
83
82
|
key: "transfer",
|
|
@@ -130,7 +129,7 @@ function TiersTable({
|
|
|
130
129
|
isLoading,
|
|
131
130
|
isError,
|
|
132
131
|
emptyTitle: currency && data?.length ? `No ${currency} levels` : "No levels yet",
|
|
133
|
-
emptyDescription: `
|
|
132
|
+
emptyDescription: `Until you add one, every merchant has to verify their identity before they can send money out${currency ? ` in ${currency}` : ""}.`
|
|
134
133
|
}
|
|
135
134
|
),
|
|
136
135
|
editing && /* @__PURE__ */ jsx(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/payout-tiers/tiers-table.tsx"],"sourcesContent":["'use client';\n\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport { Banner } from '@withpotter/ui-kit/banner';\nimport { useConfirm } from '@withpotter/ui-kit/confirm-dialog';\nimport { formatMinor } from '@withpotter/lib/money-minor';\nimport {\n adminMoneyApi,\n type PayoutTier,\n type PayoutTierInput,\n} from '@withpotter/lib/admin-money-api';\nimport {\n DataTable,\n byCurrency,\n rowBtn,\n dangerRowBtn,\n type Column,\n} from '../_ui';\nimport { TierDialog } from './_tier-dialog';\nimport { TIERS_KEY, useTiers } from './use-tiers';\n\nconst TAG = {\n padding: '2px 8px',\n borderRadius: 999,\n border: '1px solid var(--border-default)',\n fontSize: 12,\n color: 'var(--text-secondary)',\n} as const;\n\ntype Editing = PayoutTier | 'new' | null;\n\n/** The tiers, with edit and remove on each row and the tier dialog. */\nexport function TiersTable({\n editing,\n onEdit,\n currency = '',\n}: {\n editing: Editing;\n onEdit: (next: Editing) => void;\n /** The currency filter's choice; '' lists every currency. */\n currency?: string;\n}) {\n const qc = useQueryClient();\n const confirm = useConfirm();\n const { data, isLoading, isError } = useTiers();\n\n const refresh = () => qc.invalidateQueries({ queryKey: TIERS_KEY });\n const save = useMutation({\n mutationFn: (body: PayoutTierInput) =>\n editing && editing !== 'new'\n ? adminMoneyApi.updateTier(editing.id, body)\n : adminMoneyApi.createTier(body),\n onSuccess: () => {\n close();\n refresh();\n },\n });\n const remove = useMutation({\n mutationFn: (id: string) => adminMoneyApi.removeTier(id),\n onSuccess: refresh,\n });\n // Closing the dialog clears both errors, as opening a fresh one should.\n function close() {\n onEdit(null);\n save.reset();\n remove.reset();\n }\n\n const columns: Column<PayoutTier>[] = [\n {\n key: 'label',\n header: '
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/payout-tiers/tiers-table.tsx"],"sourcesContent":["'use client';\n\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport { Banner } from '@withpotter/ui-kit/banner';\nimport { useConfirm } from '@withpotter/ui-kit/confirm-dialog';\nimport { formatMinor } from '@withpotter/lib/money-minor';\nimport {\n adminMoneyApi,\n type PayoutTier,\n type PayoutTierInput,\n} from '@withpotter/lib/admin-money-api';\nimport {\n DataTable,\n byCurrency,\n rowBtn,\n dangerRowBtn,\n type Column,\n} from '../_ui';\nimport { TierDialog } from './_tier-dialog';\nimport { requirementLabel } from './requirement-labels';\nimport { TIERS_KEY, useTiers } from './use-tiers';\n\nconst TAG = {\n padding: '2px 8px',\n borderRadius: 999,\n border: '1px solid var(--border-default)',\n fontSize: 12,\n color: 'var(--text-secondary)',\n} as const;\n\ntype Editing = PayoutTier | 'new' | null;\n\n/** The tiers, with edit and remove on each row and the tier dialog. */\nexport function TiersTable({\n editing,\n onEdit,\n currency = '',\n}: {\n editing: Editing;\n onEdit: (next: Editing) => void;\n /** The currency filter's choice; '' lists every currency. */\n currency?: string;\n}) {\n const qc = useQueryClient();\n const confirm = useConfirm();\n const { data, isLoading, isError } = useTiers();\n\n const refresh = () => qc.invalidateQueries({ queryKey: TIERS_KEY });\n const save = useMutation({\n mutationFn: (body: PayoutTierInput) =>\n editing && editing !== 'new'\n ? adminMoneyApi.updateTier(editing.id, body)\n : adminMoneyApi.createTier(body),\n onSuccess: () => {\n close();\n refresh();\n },\n });\n const remove = useMutation({\n mutationFn: (id: string) => adminMoneyApi.removeTier(id),\n onSuccess: refresh,\n });\n // Closing the dialog clears both errors, as opening a fresh one should.\n function close() {\n onEdit(null);\n save.reset();\n remove.reset();\n }\n\n const columns: Column<PayoutTier>[] = [\n {\n key: 'label',\n header: 'Level',\n render: (t) => (\n <span>\n <span style={{ fontWeight: 500 }}>{t.label}</span>\n {!t.enabled && (\n <span style={{ ...TAG, marginLeft: 8 }}>Disabled</span>\n )}\n <span\n style={{\n display: 'block',\n fontSize: 12,\n color: 'var(--text-tertiary)',\n fontFamily: 'var(--font-mono, monospace)',\n }}\n >\n {t.currency} · priority {t.position}\n </span>\n </span>\n ),\n },\n {\n key: 'requirements',\n header: 'Must complete',\n render: (t) =>\n t.requirements.length === 0 ? (\n '–'\n ) : (\n <span style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>\n {t.requirements.map((r) => (\n <span key={r} style={TAG}>\n {requirementLabel(r)}\n </span>\n ))}\n </span>\n ),\n },\n {\n key: 'transfer',\n header: 'Per transfer',\n render: (t) =>\n `${formatMinor(t.minimumMinor, t.currency)} to ${formatMinor(t.maximumMinor, t.currency)}`,\n },\n {\n key: 'daily',\n header: 'Per day',\n render: (t) => formatMinor(t.dailyMaximumMinor, t.currency),\n },\n {\n key: 'monthly',\n header: 'Per month',\n render: (t) =>\n t.monthlyMaximumMinor\n ? formatMinor(t.monthlyMaximumMinor, t.currency)\n : 'No cap',\n },\n {\n key: 'actions',\n header: '',\n render: (t) => (\n <span style={{ display: 'flex', gap: 6 }}>\n <button type=\"button\" style={rowBtn} onClick={() => onEdit(t)}>\n Edit\n </button>\n <button\n type=\"button\"\n style={dangerRowBtn}\n onClick={async () => {\n const ok = await confirm({\n title: `Remove ${t.label}?`,\n description:\n 'Businesses on this tier fall back to the next tier they qualify for.',\n confirmLabel: 'Remove',\n tone: 'danger',\n });\n if (ok) remove.mutate(t.id);\n }}\n >\n Remove\n </button>\n </span>\n ),\n },\n ];\n\n return (\n <>\n {remove.error && !editing && (\n <Banner tone=\"error\">{remove.error.message}</Banner>\n )}\n <DataTable\n columns={columns}\n rows={data && byCurrency(data, currency, (t) => t.currency)}\n getRowKey={(t) => t.id}\n isLoading={isLoading}\n isError={isError}\n emptyTitle={\n currency && data?.length ? `No ${currency} levels` : 'No levels yet'\n }\n emptyDescription={`Until you add one, every merchant has to verify their identity before they can send money out${currency ? ` in ${currency}` : ''}.`}\n />\n {editing && (\n <TierDialog\n tier={editing === 'new' ? null : editing}\n saving={save.isPending}\n error={save.error?.message ?? null}\n onSave={(b) => save.mutate(b)}\n onClose={close}\n />\n )}\n </>\n );\n}\n"],"mappings":"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","names":[]}
|