@withpotter/pages-admin 0.13.0-next.58 → 0.13.0-next.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,20 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  import { createContext, useContext } from "react";
4
+ import { motion, useReducedMotion } from "motion/react";
4
5
  const NESTED_TAB_PARAM = "view";
5
6
  const InWorkspaceTab = createContext(false);
6
7
  function WorkspaceTab({ children }) {
7
- return /* @__PURE__ */ jsx(InWorkspaceTab.Provider, { value: true, children });
8
+ const calm = useReducedMotion();
9
+ return /* @__PURE__ */ jsx(InWorkspaceTab.Provider, { value: true, children: /* @__PURE__ */ jsx(
10
+ motion.div,
11
+ {
12
+ initial: calm ? false : { opacity: 0, y: 8 },
13
+ animate: { opacity: 1, y: 0 },
14
+ transition: { duration: 0.2, ease: [0, 0, 0.2, 1] },
15
+ children
16
+ }
17
+ ) });
8
18
  }
9
19
  const useInWorkspaceTab = () => useContext(InWorkspaceTab);
10
20
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/_ui/workspace-tab.tsx"],"sourcesContent":["'use client';\n\nimport { createContext, useContext, type ReactNode } from 'react';\n\n/** The address parameter tabs inside a workspace tab use, so they never fight the workspace's `?tab=`. */\nexport const NESTED_TAB_PARAM = 'view';\n\nconst InWorkspaceTab = createContext(false);\n\n/**\n * Wraps a page shown as one tab of a workspace (Money, say). Inside it the\n * page's header drops its title, which the tab already shows, and keeps its\n * one line of context and its actions; tabs within the page move to `?view=`.\n */\nexport function WorkspaceTab({ children }: { children: ReactNode }) {\n return (\n <InWorkspaceTab.Provider value={true}>{children}</InWorkspaceTab.Provider>\n );\n}\n\nexport const useInWorkspaceTab = () => useContext(InWorkspaceTab);\n"],"mappings":";AAgBI;AAdJ,SAAS,eAAe,kBAAkC;AAGnD,MAAM,mBAAmB;AAEhC,MAAM,iBAAiB,cAAc,KAAK;AAOnC,SAAS,aAAa,EAAE,SAAS,GAA4B;AAClE,SACE,oBAAC,eAAe,UAAf,EAAwB,OAAO,MAAO,UAAS;AAEpD;AAEO,MAAM,oBAAoB,MAAM,WAAW,cAAc;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/_ui/workspace-tab.tsx"],"sourcesContent":["'use client';\n\nimport { createContext, useContext, type ReactNode } from 'react';\nimport { motion, useReducedMotion } from 'motion/react';\n\n/** The address parameter tabs inside a workspace tab use, so they never fight the workspace's `?tab=`. */\nexport const NESTED_TAB_PARAM = 'view';\n\nconst InWorkspaceTab = createContext(false);\n\n/**\n * Wraps a page shown as one tab of a workspace (Money, say). Inside it the\n * page's header drops its title, which the tab already shows, and keeps its\n * one line of context and its actions; tabs within the page move to `?view=`.\n */\nexport function WorkspaceTab({ children }: { children: ReactNode }) {\n const calm = useReducedMotion();\n\n return (\n <InWorkspaceTab.Provider value={true}>\n {/* The first paint of a tab eases up and in (200ms), so opening one reads as arriving, not\n as the page being replaced. Transform and opacity only, and none of it under reduced motion. */}\n <motion.div\n initial={calm ? false : { opacity: 0, y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ duration: 0.2, ease: [0, 0, 0.2, 1] }}\n >\n {children}\n </motion.div>\n </InWorkspaceTab.Provider>\n );\n}\n\nexport const useInWorkspaceTab = () => useContext(InWorkspaceTab);\n"],"mappings":";AAsBM;AApBN,SAAS,eAAe,kBAAkC;AAC1D,SAAS,QAAQ,wBAAwB;AAGlC,MAAM,mBAAmB;AAEhC,MAAM,iBAAiB,cAAc,KAAK;AAOnC,SAAS,aAAa,EAAE,SAAS,GAA4B;AAClE,QAAM,OAAO,iBAAiB;AAE9B,SACE,oBAAC,eAAe,UAAf,EAAwB,OAAO,MAG9B;AAAA,IAAC,OAAO;AAAA,IAAP;AAAA,MACC,SAAS,OAAO,QAAQ,EAAE,SAAS,GAAG,GAAG,EAAE;AAAA,MAC3C,SAAS,EAAE,SAAS,GAAG,GAAG,EAAE;AAAA,MAC5B,YAAY,EAAE,UAAU,KAAK,MAAM,CAAC,GAAG,GAAG,KAAK,CAAC,EAAE;AAAA,MAEjD;AAAA;AAAA,EACH,GACF;AAEJ;AAEO,MAAM,oBAAoB,MAAM,WAAW,cAAc;","names":[]}
@@ -48,27 +48,51 @@ function TiersTable({
48
48
  remove.reset();
49
49
  }
50
50
  const columns = [
51
- { key: "label", header: "Tier", render: (t) => `${t.label} (${t.key})` },
52
- { key: "currency", header: "Currency", render: (t) => t.currency },
53
- { key: "position", header: "Position", render: (t) => t.position },
54
51
  {
55
- key: "limits",
56
- header: "Per transfer \xB7 daily \xB7 monthly",
57
- render: (t) => [
58
- `${formatMinor(t.minimumMinor, t.currency)}\u2013${formatMinor(t.maximumMinor, t.currency)}`,
59
- formatMinor(t.dailyMaximumMinor, t.currency),
60
- t.monthlyMaximumMinor ? formatMinor(t.monthlyMaximumMinor, t.currency) : "no cap"
61
- ].join(" \xB7 ")
52
+ key: "label",
53
+ header: "Tier",
54
+ render: (t) => /* @__PURE__ */ jsxs("span", { children: [
55
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 500 }, children: t.label }),
56
+ !t.enabled && /* @__PURE__ */ jsx("span", { style: { ...TAG, marginLeft: 8 }, children: "Disabled" }),
57
+ /* @__PURE__ */ jsxs(
58
+ "span",
59
+ {
60
+ style: {
61
+ display: "block",
62
+ fontSize: 12,
63
+ color: "var(--text-tertiary)",
64
+ fontFamily: "var(--font-mono, monospace)"
65
+ },
66
+ children: [
67
+ t.key,
68
+ " \xB7 ",
69
+ t.currency,
70
+ " \xB7 position ",
71
+ t.position
72
+ ]
73
+ }
74
+ )
75
+ ] })
62
76
  },
63
77
  {
64
78
  key: "requirements",
65
- header: "Requires",
66
- render: (t) => t.requirements.length === 0 ? "Nothing" : /* @__PURE__ */ jsx("span", { style: { display: "flex", flexWrap: "wrap", gap: 4 }, children: t.requirements.map((r) => /* @__PURE__ */ jsx("span", { style: TAG, children: r }, r)) })
79
+ header: "Needs",
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)) })
81
+ },
82
+ {
83
+ key: "transfer",
84
+ header: "Per transfer",
85
+ render: (t) => `${formatMinor(t.minimumMinor, t.currency)} to ${formatMinor(t.maximumMinor, t.currency)}`
86
+ },
87
+ {
88
+ key: "daily",
89
+ header: "Per day",
90
+ render: (t) => formatMinor(t.dailyMaximumMinor, t.currency)
67
91
  },
68
92
  {
69
- key: "enabled",
70
- header: "Status",
71
- render: (t) => t.enabled ? "Enabled" : "Disabled"
93
+ key: "monthly",
94
+ header: "Per month",
95
+ render: (t) => t.monthlyMaximumMinor ? formatMinor(t.monthlyMaximumMinor, t.currency) : "No cap"
72
96
  },
73
97
  {
74
98
  key: "actions",
@@ -105,7 +129,7 @@ function TiersTable({
105
129
  getRowKey: (t) => t.id,
106
130
  isLoading,
107
131
  isError,
108
- emptyTitle: currency && data?.length ? `No ${currency} tiers` : "No tiers yet",
132
+ emptyTitle: currency && data?.length ? `No ${currency} levels` : "No levels yet",
109
133
  emptyDescription: `With no tier, every business resolves to verification required${currency ? ` in ${currency}` : ""}.`
110
134
  }
111
135
  ),
@@ -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 { key: 'label', header: 'Tier', render: (t) => `${t.label} (${t.key})` },\n { key: 'currency', header: 'Currency', render: (t) => t.currency },\n { key: 'position', header: 'Position', render: (t) => t.position },\n {\n key: 'limits',\n header: 'Per transfer · daily · monthly',\n render: (t) =>\n [\n `${formatMinor(t.minimumMinor, t.currency)}–${formatMinor(t.maximumMinor, t.currency)}`,\n formatMinor(t.dailyMaximumMinor, t.currency),\n t.monthlyMaximumMinor\n ? formatMinor(t.monthlyMaximumMinor, t.currency)\n : 'no cap',\n ].join(' · '),\n },\n {\n key: 'requirements',\n header: 'Requires',\n render: (t) =>\n t.requirements.length === 0 ? (\n 'Nothing'\n ) : (\n <span style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>\n {t.requirements.map((r) => (\n <span key={r} style={TAG}>\n {r}\n </span>\n ))}\n </span>\n ),\n },\n {\n key: 'enabled',\n header: 'Status',\n render: (t) => (t.enabled ? 'Enabled' : 'Disabled'),\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} tiers` : 'No tiers yet'\n }\n emptyDescription={`With no tier, every business resolves to verification required${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":[]}
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: 'Tier',\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.key} · {t.currency} · position {t.position}\n </span>\n </span>\n ),\n },\n {\n key: 'requirements',\n header: 'Needs',\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 {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={`With no tier, every business resolves to verification required${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":[]}
@@ -4,22 +4,16 @@ import { useState } from "react";
4
4
  import { Button } from "@withpotter/ui-kit/button";
5
5
  import {
6
6
  CurrencyFilter,
7
+ Drawer,
7
8
  PageHeader,
8
- PageTabs,
9
9
  currenciesIn,
10
- useCurrencyParam,
11
- useTabParam
10
+ useCurrencyParam
12
11
  } from "../_ui";
13
12
  import { TierPreviewTab } from "./tier-preview-tab";
14
13
  import { TiersTable } from "./tiers-table";
15
14
  import { useTiers } from "./use-tiers";
16
- const TABS = [
17
- { id: "tiers", label: "Tiers" },
18
- { id: "preview", label: "Preview a business" }
19
- ];
20
- const KEYS = TABS.map((t) => t.id);
21
15
  function TiersWorkspace() {
22
- const [tab, setTab] = useTabParam(KEYS);
16
+ const [previewing, setPreviewing] = useState(false);
23
17
  const [editing, setEditing] = useState(null);
24
18
  const [currency, setCurrency] = useCurrencyParam();
25
19
  const tiers = useTiers();
@@ -27,9 +21,9 @@ function TiersWorkspace() {
27
21
  /* @__PURE__ */ jsx(
28
22
  PageHeader,
29
23
  {
30
- title: "Payout tiers",
24
+ title: "Payout limits",
31
25
  description: "Per-currency limits by verification level.",
32
- actions: tab === "tiers" ? /* @__PURE__ */ jsxs("div", { className: "adm-toolbar", children: [
26
+ actions: /* @__PURE__ */ jsxs("div", { className: "adm-toolbar", children: [
33
27
  /* @__PURE__ */ jsx(
34
28
  CurrencyFilter,
35
29
  {
@@ -38,13 +32,22 @@ function TiersWorkspace() {
38
32
  currencies: currenciesIn(tiers.data?.map((t) => t.currency))
39
33
  }
40
34
  ),
41
- /* @__PURE__ */ jsx(Button, { onClick: () => setEditing("new"), children: "New tier" })
42
- ] }) : void 0
35
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => setPreviewing(true), children: "Preview a business" }),
36
+ /* @__PURE__ */ jsx(Button, { onClick: () => setEditing("new"), children: "New level" })
37
+ ] })
43
38
  }
44
39
  ),
45
- /* @__PURE__ */ jsx(PageTabs, { tabs: TABS, active: tab, onChange: setTab }),
46
- tab === "tiers" && /* @__PURE__ */ jsx(TiersTable, { editing, onEdit: setEditing, currency }),
47
- tab === "preview" && /* @__PURE__ */ jsx(TierPreviewTab, {})
40
+ /* @__PURE__ */ jsx(TiersTable, { editing, onEdit: setEditing, currency }),
41
+ /* @__PURE__ */ jsx(
42
+ Drawer,
43
+ {
44
+ open: previewing,
45
+ onClose: () => setPreviewing(false),
46
+ title: "Preview a business",
47
+ subtitle: "Which level this business lands on today, and what it is missing.",
48
+ children: /* @__PURE__ */ jsx(TierPreviewTab, {})
49
+ }
50
+ )
48
51
  ] });
49
52
  }
50
53
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/payout-tiers/tiers-workspace.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { Button } from '@withpotter/ui-kit/button';\nimport type { PayoutTier } from '@withpotter/lib/admin-money-api';\nimport {\n CurrencyFilter,\n PageHeader,\n PageTabs,\n currenciesIn,\n useCurrencyParam,\n useTabParam,\n} from '../_ui';\nimport { TierPreviewTab } from './tier-preview-tab';\nimport { TiersTable } from './tiers-table';\nimport { useTiers } from './use-tiers';\n\nconst TABS = [\n { id: 'tiers', label: 'Tiers' },\n { id: 'preview', label: 'Preview a business' },\n] as const;\n\nconst KEYS = TABS.map((t) => t.id);\n\n/**\n * How much a business may send out, by how far it is verified.\n *\n * This is configuration, not approved policy: nothing is seeded, and with no\n * tier a business resolves to \"verification required\". The preview tab\n * answers the question an admin actually asks: which tier would THIS\n * business get today, and what is it missing?\n */\nexport function TiersWorkspace() {\n const [tab, setTab] = useTabParam(KEYS);\n const [editing, setEditing] = useState<PayoutTier | 'new' | null>(null);\n const [currency, setCurrency] = useCurrencyParam();\n const tiers = useTiers();\n\n return (\n <>\n <PageHeader\n title=\"Payout tiers\"\n description=\"Per-currency limits by verification level.\"\n actions={\n tab === 'tiers' ? (\n <div className=\"adm-toolbar\">\n <CurrencyFilter\n value={currency}\n onChange={setCurrency}\n currencies={currenciesIn(tiers.data?.map((t) => t.currency))}\n />\n <Button onClick={() => setEditing('new')}>New tier</Button>\n </div>\n ) : undefined\n }\n />\n <PageTabs tabs={TABS} active={tab} onChange={setTab} />\n {tab === 'tiers' && (\n <TiersTable editing={editing} onEdit={setEditing} currency={currency} />\n )}\n {tab === 'preview' && <TierPreviewTab />}\n </>\n );\n}\n"],"mappings":";AAuCI,mBAOU,KADF,YANR;AArCJ,SAAS,gBAAgB;AACzB,SAAS,cAAc;AAEvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,sBAAsB;AAC/B,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB;AAEzB,MAAM,OAAO;AAAA,EACX,EAAE,IAAI,SAAS,OAAO,QAAQ;AAAA,EAC9B,EAAE,IAAI,WAAW,OAAO,qBAAqB;AAC/C;AAEA,MAAM,OAAO,KAAK,IAAI,CAAC,MAAM,EAAE,EAAE;AAU1B,SAAS,iBAAiB;AAC/B,QAAM,CAAC,KAAK,MAAM,IAAI,YAAY,IAAI;AACtC,QAAM,CAAC,SAAS,UAAU,IAAI,SAAoC,IAAI;AACtE,QAAM,CAAC,UAAU,WAAW,IAAI,iBAAiB;AACjD,QAAM,QAAQ,SAAS;AAEvB,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,aAAY;AAAA,QACZ,SACE,QAAQ,UACN,qBAAC,SAAI,WAAU,eACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,OAAO;AAAA,cACP,UAAU;AAAA,cACV,YAAY,aAAa,MAAM,MAAM,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC;AAAA;AAAA,UAC7D;AAAA,UACA,oBAAC,UAAO,SAAS,MAAM,WAAW,KAAK,GAAG,sBAAQ;AAAA,WACpD,IACE;AAAA;AAAA,IAER;AAAA,IACA,oBAAC,YAAS,MAAM,MAAM,QAAQ,KAAK,UAAU,QAAQ;AAAA,IACpD,QAAQ,WACP,oBAAC,cAAW,SAAkB,QAAQ,YAAY,UAAoB;AAAA,IAEvE,QAAQ,aAAa,oBAAC,kBAAe;AAAA,KACxC;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/payout-tiers/tiers-workspace.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { Button } from '@withpotter/ui-kit/button';\nimport type { PayoutTier } from '@withpotter/lib/admin-money-api';\nimport {\n CurrencyFilter,\n Drawer,\n PageHeader,\n currenciesIn,\n useCurrencyParam,\n} from '../_ui';\nimport { TierPreviewTab } from './tier-preview-tab';\nimport { TiersTable } from './tiers-table';\nimport { useTiers } from './use-tiers';\n\n/**\n * How much a business may send out, by how far it is verified.\n *\n * This is configuration, not approved policy: nothing is seeded, and with no\n * tier a business resolves to \"verification required\". The preview tab\n * answers the question an admin actually asks: which tier would THIS\n * business get today, and what is it missing?\n */\nexport function TiersWorkspace() {\n const [previewing, setPreviewing] = useState(false);\n const [editing, setEditing] = useState<PayoutTier | 'new' | null>(null);\n const [currency, setCurrency] = useCurrencyParam();\n const tiers = useTiers();\n\n return (\n <>\n <PageHeader\n title=\"Payout limits\"\n description=\"Per-currency limits by verification level.\"\n actions={\n <div className=\"adm-toolbar\">\n <CurrencyFilter\n value={currency}\n onChange={setCurrency}\n currencies={currenciesIn(tiers.data?.map((t) => t.currency))}\n />\n <Button variant=\"secondary\" onClick={() => setPreviewing(true)}>\n Preview a business\n </Button>\n <Button onClick={() => setEditing('new')}>New level</Button>\n </div>\n }\n />\n <TiersTable editing={editing} onEdit={setEditing} currency={currency} />\n <Drawer\n open={previewing}\n onClose={() => setPreviewing(false)}\n title=\"Preview a business\"\n subtitle=\"Which level this business lands on today, and what it is missing.\"\n >\n <TierPreviewTab />\n </Drawer>\n </>\n );\n}\n"],"mappings":";AA+BI,mBAMQ,KADF,YALN;AA7BJ,SAAS,gBAAgB;AACzB,SAAS,cAAc;AAEvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,sBAAsB;AAC/B,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB;AAUlB,SAAS,iBAAiB;AAC/B,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAClD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAoC,IAAI;AACtE,QAAM,CAAC,UAAU,WAAW,IAAI,iBAAiB;AACjD,QAAM,QAAQ,SAAS;AAEvB,SACE,iCACE;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,YAAY,aAAa,MAAM,MAAM,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC;AAAA;AAAA,UAC7D;AAAA,UACA,oBAAC,UAAO,SAAQ,aAAY,SAAS,MAAM,cAAc,IAAI,GAAG,gCAEhE;AAAA,UACA,oBAAC,UAAO,SAAS,MAAM,WAAW,KAAK,GAAG,uBAAS;AAAA,WACrD;AAAA;AAAA,IAEJ;AAAA,IACA,oBAAC,cAAW,SAAkB,QAAQ,YAAY,UAAoB;AAAA,IACtE;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,SAAS,MAAM,cAAc,KAAK;AAAA,QAClC,OAAM;AAAA,QACN,UAAS;AAAA,QAET,8BAAC,kBAAe;AAAA;AAAA,IAClB;AAAA,KACF;AAEJ;","names":[]}
@@ -100,7 +100,7 @@ function RevenueWorkspace() {
100
100
  PageHeader,
101
101
  {
102
102
  title: "Revenue",
103
- description: `${ADMIN_NAME}'s subscription income each month and year, and the merchant sales (GMV) it is based on.`,
103
+ description: `Every revenue on ${ADMIN_NAME} is reported here: its subscription income each month and year, and the merchant sales (GMV) it is based on.`,
104
104
  actions: /* @__PURE__ */ jsxs("div", { className: "adm-toolbar", children: [
105
105
  /* @__PURE__ */ jsx(
106
106
  CurrencyFilter,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/revenue/revenue-workspace.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo } from 'react';\nimport { ADMIN_NAME } from '../_shell/admin-brand';\nimport { usePlatformCockpit, usePlatformRevenue } from '../hooks';\nimport { LoadFailed } from '../audit/load-failed';\nimport {\n AdmLoading,\n CurrencyFilter,\n PageHeader,\n PageTabs,\n PeriodSelector,\n customRange,\n focusCurrency,\n moneyIn,\n periodPhrase,\n rangeFromKey,\n useCurrencyParam,\n usePersistedDateRange,\n useTabParam,\n} from '../_ui';\nimport { RevenueOverview } from './revenue-overview';\nimport { RevenueRetention } from './revenue-retention';\n\nconst TABS = [\n { id: 'overview', label: 'Overview' },\n { id: 'retention', label: 'Retention' },\n] as const;\n\nconst KEYS = TABS.map((t) => t.id);\n\n/** Revenue: headline money on Overview, revenue quality and cohorts on Retention. */\nexport function RevenueWorkspace() {\n const [tab, setTab] = useTabParam(KEYS);\n const {\n rangeKey,\n setRangeKey,\n customStart,\n customEnd,\n onCustomChange,\n } = usePersistedDateRange();\n // Merchant GMV comes back once per currency; `?currency=` narrows it.\n const [currency, setCurrency] = useCurrencyParam();\n\n const range = useMemo(\n () =>\n rangeKey === 'custom'\n ? customRange(customStart, customEnd)\n : rangeFromKey(rangeKey),\n [rangeKey, customStart, customEnd],\n );\n\n const { data, isLoading, isError, refetch } = usePlatformRevenue({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n // Revenue health (MRR movement, logo churn, concentration) is computed as\n // part of the cockpit, not the revenue endpoint — fetched here too rather\n // than duplicating that aggregation.\n const cockpit = usePlatformCockpit({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n\n const currencies = useMemo(() => data?.currencies ?? [], [data]);\n const focus = focusCurrency(currencies, currency);\n // Recurring revenue is the platform's own billing, in the currency the API\n // names for it (`currency`), shown as it comes, never converted.\n const recurringCurrency = data?.currency || 'USD';\n const money = useMemo(() => moneyIn(recurringCurrency), [recurringCurrency]);\n const money2 = useMemo(\n () => moneyIn(recurringCurrency, 2),\n [recurringCurrency],\n );\n\n const span = periodPhrase(range);\n const period = `${span[0].toUpperCase()}${span.slice(1)}`;\n\n let body;\n if (isError) {\n body = (\n <LoadFailed\n message=\"Revenue for this period could not be loaded.\"\n onRetry={() => void refetch()}\n />\n );\n } else if (isLoading || !data) {\n body = <AdmLoading label=\"Loading revenue metrics…\" />;\n } else if (tab === 'retention') {\n body = (\n <RevenueRetention\n quality={data.quality}\n money={money}\n currency={currency}\n caption={currency ? `${period} · ${currency}` : period}\n />\n );\n } else {\n body = (\n <RevenueOverview\n data={data}\n health={cockpit.data?.health}\n money={money}\n money2={money2}\n currency={currency}\n focus={focus}\n period={period}\n span={span}\n recurringCaption={`${period} · ${recurringCurrency}`}\n />\n );\n }\n\n return (\n <div>\n <PageHeader\n title=\"Revenue\"\n description={`${ADMIN_NAME}'s subscription income each month and year, and the merchant sales (GMV) it is based on.`}\n actions={\n <div className=\"adm-toolbar\">\n <CurrencyFilter\n value={currency}\n onChange={setCurrency}\n currencies={currencies}\n />\n <PeriodSelector\n value={rangeKey}\n onChange={setRangeKey}\n customStart={customStart}\n customEnd={customEnd}\n onCustomChange={onCustomChange}\n />\n </div>\n }\n />\n <PageTabs tabs={TABS} active={tab} onChange={setTab} />\n {body}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/revenue/revenue-workspace.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo } from 'react';\nimport { ADMIN_NAME } from '../_shell/admin-brand';\nimport { usePlatformCockpit, usePlatformRevenue } from '../hooks';\nimport { LoadFailed } from '../audit/load-failed';\nimport {\n AdmLoading,\n CurrencyFilter,\n PageHeader,\n PageTabs,\n PeriodSelector,\n customRange,\n focusCurrency,\n moneyIn,\n periodPhrase,\n rangeFromKey,\n useCurrencyParam,\n usePersistedDateRange,\n useTabParam,\n} from '../_ui';\nimport { RevenueOverview } from './revenue-overview';\nimport { RevenueRetention } from './revenue-retention';\n\nconst TABS = [\n { id: 'overview', label: 'Overview' },\n { id: 'retention', label: 'Retention' },\n] as const;\n\nconst KEYS = TABS.map((t) => t.id);\n\n/** Revenue: headline money on Overview, revenue quality and cohorts on Retention. */\nexport function RevenueWorkspace() {\n const [tab, setTab] = useTabParam(KEYS);\n const {\n rangeKey,\n setRangeKey,\n customStart,\n customEnd,\n onCustomChange,\n } = usePersistedDateRange();\n // Merchant GMV comes back once per currency; `?currency=` narrows it.\n const [currency, setCurrency] = useCurrencyParam();\n\n const range = useMemo(\n () =>\n rangeKey === 'custom'\n ? customRange(customStart, customEnd)\n : rangeFromKey(rangeKey),\n [rangeKey, customStart, customEnd],\n );\n\n const { data, isLoading, isError, refetch } = usePlatformRevenue({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n // Revenue health (MRR movement, logo churn, concentration) is computed as\n // part of the cockpit, not the revenue endpoint — fetched here too rather\n // than duplicating that aggregation.\n const cockpit = usePlatformCockpit({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n\n const currencies = useMemo(() => data?.currencies ?? [], [data]);\n const focus = focusCurrency(currencies, currency);\n // Recurring revenue is the platform's own billing, in the currency the API\n // names for it (`currency`), shown as it comes, never converted.\n const recurringCurrency = data?.currency || 'USD';\n const money = useMemo(() => moneyIn(recurringCurrency), [recurringCurrency]);\n const money2 = useMemo(\n () => moneyIn(recurringCurrency, 2),\n [recurringCurrency],\n );\n\n const span = periodPhrase(range);\n const period = `${span[0].toUpperCase()}${span.slice(1)}`;\n\n let body;\n if (isError) {\n body = (\n <LoadFailed\n message=\"Revenue for this period could not be loaded.\"\n onRetry={() => void refetch()}\n />\n );\n } else if (isLoading || !data) {\n body = <AdmLoading label=\"Loading revenue metrics…\" />;\n } else if (tab === 'retention') {\n body = (\n <RevenueRetention\n quality={data.quality}\n money={money}\n currency={currency}\n caption={currency ? `${period} · ${currency}` : period}\n />\n );\n } else {\n body = (\n <RevenueOverview\n data={data}\n health={cockpit.data?.health}\n money={money}\n money2={money2}\n currency={currency}\n focus={focus}\n period={period}\n span={span}\n recurringCaption={`${period} · ${recurringCurrency}`}\n />\n );\n }\n\n return (\n <div>\n <PageHeader\n title=\"Revenue\"\n description={`Every revenue on ${ADMIN_NAME} is reported here: its subscription income each month and year, and the merchant sales (GMV) it is based on.`}\n actions={\n <div className=\"adm-toolbar\">\n <CurrencyFilter\n value={currency}\n onChange={setCurrency}\n currencies={currencies}\n />\n <PeriodSelector\n value={rangeKey}\n onChange={setRangeKey}\n customStart={customStart}\n customEnd={customEnd}\n onCustomChange={onCustomChange}\n />\n </div>\n }\n />\n <PageTabs tabs={TABS} active={tab} onChange={setTab} />\n {body}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@withpotter/pages-admin",
3
- "version": "0.13.0-next.58",
3
+ "version": "0.13.0-next.59",
4
4
  "description": "Box console platform-admin routes: overview, revenue, accounts, tenants, admins, roles, audit.",
5
5
  "license": "UNLICENSED",
6
6
  "sideEffects": false,
@@ -31,9 +31,9 @@
31
31
  "dependencies": {
32
32
  "@radix-ui/react-popover": "^1.1.23",
33
33
  "@tanstack/react-query": "^5.90.20",
34
- "@withpotter/lib": "^0.13.0-next.58",
35
- "@withpotter/platform-admin": "^0.13.0-next.58",
36
- "@withpotter/ui-kit": "^0.13.0-next.58",
34
+ "@withpotter/lib": "^0.13.0-next.59",
35
+ "@withpotter/platform-admin": "^0.13.0-next.59",
36
+ "@withpotter/ui-kit": "^0.13.0-next.59",
37
37
  "lucide-react": "^0.562.0",
38
38
  "motion": "^12.42.2"
39
39
  },