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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/admin/_money/cash-postings.js +20 -3
  2. package/dist/admin/_money/cash-postings.js.map +1 -1
  3. package/dist/admin/_money/cash.module.css +10 -0
  4. package/dist/admin/_shell/nav-config.js +3 -3
  5. package/dist/admin/_shell/nav-config.js.map +1 -1
  6. package/dist/admin/_ui/workspace-tab.js +11 -1
  7. package/dist/admin/_ui/workspace-tab.js.map +1 -1
  8. package/dist/admin/accounts/accounts-workspace.js +2 -2
  9. package/dist/admin/accounts/accounts-workspace.js.map +1 -1
  10. package/dist/admin/payout-tiers/tiers-table.js +40 -16
  11. package/dist/admin/payout-tiers/tiers-table.js.map +1 -1
  12. package/dist/admin/payout-tiers/tiers-workspace.js +19 -16
  13. package/dist/admin/payout-tiers/tiers-workspace.js.map +1 -1
  14. package/dist/admin/revenue/revenue-workspace.js +1 -1
  15. package/dist/admin/revenue/revenue-workspace.js.map +1 -1
  16. package/dist/admin/settings/general-tab.js +2 -2
  17. package/dist/admin/settings/general-tab.js.map +1 -1
  18. package/dist/admin/tenants/[id]/verification/document-file.js +78 -0
  19. package/dist/admin/tenants/[id]/verification/document-file.js.map +1 -0
  20. package/dist/admin/tenants/[id]/verification/document-viewer.js +3 -52
  21. package/dist/admin/tenants/[id]/verification/document-viewer.js.map +1 -1
  22. package/dist/admin/tenants/[id]/verification/document-viewer.module.css +51 -0
  23. package/dist/admin/tenants/[id]/verification/sent-files.js +33 -0
  24. package/dist/admin/tenants/[id]/verification/sent-files.js.map +1 -0
  25. package/dist/admin/tenants/[id]/verification/tenant-verification.js +10 -0
  26. package/dist/admin/tenants/[id]/verification/tenant-verification.js.map +1 -1
  27. package/dist/admin/tenants/tenants-workspace.js +2 -2
  28. package/dist/admin/tenants/tenants-workspace.js.map +1 -1
  29. package/dist/admin/verification/verification-page.js +19 -3
  30. package/dist/admin/verification/verification-page.js.map +1 -1
  31. package/dist/admin/verification/verification.module.css +10 -0
  32. package/dist/admin/verification/waiting-cases.js +3 -9
  33. package/dist/admin/verification/waiting-cases.js.map +1 -1
  34. package/package.json +5 -5
  35. package/dist/admin/verification/waiting-case-actions.js +0 -68
  36. package/dist/admin/verification/waiting-case-actions.js.map +0 -1
@@ -2,6 +2,7 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
4
  import { Button } from "@withpotter/ui-kit/button";
5
+ import { FilterSelect, Filters } from "../_ui";
5
6
  import { useMoneyPermissions } from "./permissions";
6
7
  import { ProposeCashDialog } from "./propose-dialog";
7
8
  import { ReviewProposalDialog } from "./review-dialog";
@@ -24,8 +25,13 @@ function ActionCard({
24
25
  ] })
25
26
  ] });
26
27
  }
28
+ const SHOW = [
29
+ { value: "post", label: "Cash postings" },
30
+ { value: "waiting", label: "Waiting for approval" }
31
+ ];
27
32
  function CashPostings() {
28
33
  const perms = useMoneyPermissions();
34
+ const [show, setShow] = useState(SHOW[0].value);
29
35
  const [proposing, setProposing] = useState(false);
30
36
  const [reviewing, setReviewing] = useState(false);
31
37
  const [review, setReview] = useState({ id: "", nonce: 0 });
@@ -34,7 +40,18 @@ function CashPostings() {
34
40
  setReviewing(true);
35
41
  };
36
42
  return /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 16 }, children: [
37
- /* @__PURE__ */ jsxs("div", { className: styles.cashGrid, children: [
43
+ /* @__PURE__ */ jsx("div", { className: styles.show, children: /* @__PURE__ */ jsx(Filters, { label: "Choose what to show", children: /* @__PURE__ */ jsx(
44
+ FilterSelect,
45
+ {
46
+ label: "Show",
47
+ value: show,
48
+ allValue: SHOW[0].value,
49
+ onChange: setShow,
50
+ options: SHOW,
51
+ className: styles.showSelect
52
+ }
53
+ ) }) }),
54
+ show === "post" && /* @__PURE__ */ jsxs("div", { className: styles.cashGrid, children: [
38
55
  /* @__PURE__ */ jsx(
39
56
  ActionCard,
40
57
  {
@@ -50,14 +67,14 @@ function CashPostings() {
50
67
  ActionCard,
51
68
  {
52
69
  title: "Review by ID",
53
- body: "Open a posting by its ID when it is not in the list below.",
70
+ body: "Open a posting by its ID when it is not in Waiting for approval.",
54
71
  cta: "Review by ID",
55
72
  hint: perms.approveHint,
56
73
  onClick: () => openReview("")
57
74
  }
58
75
  )
59
76
  ] }),
60
- /* @__PURE__ */ jsx(WaitingProposals, { onReview: openReview }),
77
+ show === "waiting" && /* @__PURE__ */ jsx(WaitingProposals, { onReview: openReview }),
61
78
  /* @__PURE__ */ jsx(ProposeCashDialog, { open: proposing, onClose: () => setProposing(false) }),
62
79
  /* @__PURE__ */ jsx(
63
80
  ReviewProposalDialog,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/_money/cash-postings.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { Button } from '@withpotter/ui-kit/button';\nimport { useMoneyPermissions } from './permissions';\nimport { ProposeCashDialog } from './propose-dialog';\nimport { ReviewProposalDialog } from './review-dialog';\nimport { WaitingProposals } from './waiting-proposals';\nimport styles from './cash.module.css';\n\nfunction ActionCard({\n title,\n body,\n cta,\n hint,\n primary,\n onClick,\n}: {\n title: string;\n body: string;\n cta: string;\n /** Set when this admin may lack the permission; the API decides. */\n hint?: string;\n primary?: boolean;\n onClick: () => void;\n}) {\n return (\n <div className={styles.actionCard}>\n <h3 className={styles.actionTitle}>{title}</h3>\n <p className={styles.actionBody}>{body}</p>\n <div className={styles.actionFoot}>\n <Button variant={primary ? 'primary' : 'secondary'} onClick={onClick}>\n {cta}\n </Button>\n {hint && <span className=\"adm-caption\">{hint}</span>}\n </div>\n </div>\n );\n}\n\n/**\n * Maker-checker cash postings: one admin proposes, a different admin\n * approves or rejects it from the waiting list (or by its ID).\n */\nexport function CashPostings() {\n const perms = useMoneyPermissions();\n const [proposing, setProposing] = useState(false);\n const [reviewing, setReviewing] = useState(false);\n // A fresh dialog per opening, so a pasted ID never leaks into the next one.\n const [review, setReview] = useState({ id: '', nonce: 0 });\n const openReview = (id: string) => {\n setReview((r) => ({ id, nonce: r.nonce + 1 }));\n setReviewing(true);\n };\n\n return (\n <div style={{ display: 'grid', gap: 16 }}>\n <div className={styles.cashGrid}>\n <ActionCard\n primary\n title=\"Propose a cash posting\"\n body=\"Book opening cash, or cash a merchant handed over off-platform. Nothing moves until a second admin approves.\"\n cta=\"Propose cash posting\"\n hint={perms.proposeHint}\n onClick={() => setProposing(true)}\n />\n <ActionCard\n title=\"Review by ID\"\n body=\"Open a posting by its ID when it is not in the list below.\"\n cta=\"Review by ID\"\n hint={perms.approveHint}\n onClick={() => openReview('')}\n />\n </div>\n <WaitingProposals onReview={openReview} />\n <ProposeCashDialog open={proposing} onClose={() => setProposing(false)} />\n <ReviewProposalDialog\n key={review.nonce}\n open={reviewing}\n initialId={review.id}\n onClose={() => setReviewing(false)}\n />\n </div>\n );\n}\n"],"mappings":";AA4BM,cAEA,YAFA;AA1BN,SAAS,gBAAgB;AACzB,SAAS,cAAc;AACvB,SAAS,2BAA2B;AACpC,SAAS,yBAAyB;AAClC,SAAS,4BAA4B;AACrC,SAAS,wBAAwB;AACjC,OAAO,YAAY;AAEnB,SAAS,WAAW;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAQG;AACD,SACE,qBAAC,SAAI,WAAW,OAAO,YACrB;AAAA,wBAAC,QAAG,WAAW,OAAO,aAAc,iBAAM;AAAA,IAC1C,oBAAC,OAAE,WAAW,OAAO,YAAa,gBAAK;AAAA,IACvC,qBAAC,SAAI,WAAW,OAAO,YACrB;AAAA,0BAAC,UAAO,SAAS,UAAU,YAAY,aAAa,SACjD,eACH;AAAA,MACC,QAAQ,oBAAC,UAAK,WAAU,eAAe,gBAAK;AAAA,OAC/C;AAAA,KACF;AAEJ;AAMO,SAAS,eAAe;AAC7B,QAAM,QAAQ,oBAAoB;AAClC,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAChD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAEhD,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,EAAE,IAAI,IAAI,OAAO,EAAE,CAAC;AACzD,QAAM,aAAa,CAAC,OAAe;AACjC,cAAU,CAAC,OAAO,EAAE,IAAI,OAAO,EAAE,QAAQ,EAAE,EAAE;AAC7C,iBAAa,IAAI;AAAA,EACnB;AAEA,SACE,qBAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,KAAK,GAAG,GACrC;AAAA,yBAAC,SAAI,WAAW,OAAO,UACrB;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,SAAO;AAAA,UACP,OAAM;AAAA,UACN,MAAK;AAAA,UACL,KAAI;AAAA,UACJ,MAAM,MAAM;AAAA,UACZ,SAAS,MAAM,aAAa,IAAI;AAAA;AAAA,MAClC;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,MAAK;AAAA,UACL,KAAI;AAAA,UACJ,MAAM,MAAM;AAAA,UACZ,SAAS,MAAM,WAAW,EAAE;AAAA;AAAA,MAC9B;AAAA,OACF;AAAA,IACA,oBAAC,oBAAiB,UAAU,YAAY;AAAA,IACxC,oBAAC,qBAAkB,MAAM,WAAW,SAAS,MAAM,aAAa,KAAK,GAAG;AAAA,IACxE;AAAA,MAAC;AAAA;AAAA,QAEC,MAAM;AAAA,QACN,WAAW,OAAO;AAAA,QAClB,SAAS,MAAM,aAAa,KAAK;AAAA;AAAA,MAH5B,OAAO;AAAA,IAId;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/_money/cash-postings.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { Button } from '@withpotter/ui-kit/button';\nimport { FilterSelect, Filters } from '../_ui';\nimport { useMoneyPermissions } from './permissions';\nimport { ProposeCashDialog } from './propose-dialog';\nimport { ReviewProposalDialog } from './review-dialog';\nimport { WaitingProposals } from './waiting-proposals';\nimport styles from './cash.module.css';\n\nfunction ActionCard({\n title,\n body,\n cta,\n hint,\n primary,\n onClick,\n}: {\n title: string;\n body: string;\n cta: string;\n /** Set when this admin may lack the permission; the API decides. */\n hint?: string;\n primary?: boolean;\n onClick: () => void;\n}) {\n return (\n <div className={styles.actionCard}>\n <h3 className={styles.actionTitle}>{title}</h3>\n <p className={styles.actionBody}>{body}</p>\n <div className={styles.actionFoot}>\n <Button variant={primary ? 'primary' : 'secondary'} onClick={onClick}>\n {cta}\n </Button>\n {hint && <span className=\"adm-caption\">{hint}</span>}\n </div>\n </div>\n );\n}\n\nconst SHOW = [\n { value: 'post', label: 'Cash postings' },\n { value: 'waiting', label: 'Waiting for approval' },\n];\n\n/**\n * Maker-checker cash postings: one admin proposes, a different admin\n * approves or rejects it from the waiting list (or by its ID). One part at a\n * time, chosen from a dropdown on the right.\n */\nexport function CashPostings() {\n const perms = useMoneyPermissions();\n const [show, setShow] = useState(SHOW[0].value);\n const [proposing, setProposing] = useState(false);\n const [reviewing, setReviewing] = useState(false);\n // A fresh dialog per opening, so a pasted ID never leaks into the next one.\n const [review, setReview] = useState({ id: '', nonce: 0 });\n const openReview = (id: string) => {\n setReview((r) => ({ id, nonce: r.nonce + 1 }));\n setReviewing(true);\n };\n\n return (\n <div style={{ display: 'grid', gap: 16 }}>\n <div className={styles.show}>\n <Filters label=\"Choose what to show\">\n <FilterSelect\n label=\"Show\"\n value={show}\n allValue={SHOW[0].value}\n onChange={setShow}\n options={SHOW}\n className={styles.showSelect}\n />\n </Filters>\n </div>\n {show === 'post' && (\n <div className={styles.cashGrid}>\n <ActionCard\n primary\n title=\"Propose a cash posting\"\n body=\"Book opening cash, or cash a merchant handed over off-platform. Nothing moves until a second admin approves.\"\n cta=\"Propose cash posting\"\n hint={perms.proposeHint}\n onClick={() => setProposing(true)}\n />\n <ActionCard\n title=\"Review by ID\"\n body=\"Open a posting by its ID when it is not in Waiting for approval.\"\n cta=\"Review by ID\"\n hint={perms.approveHint}\n onClick={() => openReview('')}\n />\n </div>\n )}\n {show === 'waiting' && <WaitingProposals onReview={openReview} />}\n <ProposeCashDialog open={proposing} onClose={() => setProposing(false)} />\n <ReviewProposalDialog\n key={review.nonce}\n open={reviewing}\n initialId={review.id}\n onClose={() => setReviewing(false)}\n />\n </div>\n );\n}\n"],"mappings":";AA6BM,cAEA,YAFA;AA3BN,SAAS,gBAAgB;AACzB,SAAS,cAAc;AACvB,SAAS,cAAc,eAAe;AACtC,SAAS,2BAA2B;AACpC,SAAS,yBAAyB;AAClC,SAAS,4BAA4B;AACrC,SAAS,wBAAwB;AACjC,OAAO,YAAY;AAEnB,SAAS,WAAW;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAQG;AACD,SACE,qBAAC,SAAI,WAAW,OAAO,YACrB;AAAA,wBAAC,QAAG,WAAW,OAAO,aAAc,iBAAM;AAAA,IAC1C,oBAAC,OAAE,WAAW,OAAO,YAAa,gBAAK;AAAA,IACvC,qBAAC,SAAI,WAAW,OAAO,YACrB;AAAA,0BAAC,UAAO,SAAS,UAAU,YAAY,aAAa,SACjD,eACH;AAAA,MACC,QAAQ,oBAAC,UAAK,WAAU,eAAe,gBAAK;AAAA,OAC/C;AAAA,KACF;AAEJ;AAEA,MAAM,OAAO;AAAA,EACX,EAAE,OAAO,QAAQ,OAAO,gBAAgB;AAAA,EACxC,EAAE,OAAO,WAAW,OAAO,uBAAuB;AACpD;AAOO,SAAS,eAAe;AAC7B,QAAM,QAAQ,oBAAoB;AAClC,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK,CAAC,EAAE,KAAK;AAC9C,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAChD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAEhD,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,EAAE,IAAI,IAAI,OAAO,EAAE,CAAC;AACzD,QAAM,aAAa,CAAC,OAAe;AACjC,cAAU,CAAC,OAAO,EAAE,IAAI,OAAO,EAAE,QAAQ,EAAE,EAAE;AAC7C,iBAAa,IAAI;AAAA,EACnB;AAEA,SACE,qBAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,KAAK,GAAG,GACrC;AAAA,wBAAC,SAAI,WAAW,OAAO,MACrB,8BAAC,WAAQ,OAAM,uBACb;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,OAAO;AAAA,QACP,UAAU,KAAK,CAAC,EAAE;AAAA,QAClB,UAAU;AAAA,QACV,SAAS;AAAA,QACT,WAAW,OAAO;AAAA;AAAA,IACpB,GACF,GACF;AAAA,IACC,SAAS,UACR,qBAAC,SAAI,WAAW,OAAO,UACrB;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,SAAO;AAAA,UACP,OAAM;AAAA,UACN,MAAK;AAAA,UACL,KAAI;AAAA,UACJ,MAAM,MAAM;AAAA,UACZ,SAAS,MAAM,aAAa,IAAI;AAAA;AAAA,MAClC;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,MAAK;AAAA,UACL,KAAI;AAAA,UACJ,MAAM,MAAM;AAAA,UACZ,SAAS,MAAM,WAAW,EAAE;AAAA;AAAA,MAC9B;AAAA,OACF;AAAA,IAED,SAAS,aAAa,oBAAC,oBAAiB,UAAU,YAAY;AAAA,IAC/D,oBAAC,qBAAkB,MAAM,WAAW,SAAS,MAAM,aAAa,KAAK,GAAG;AAAA,IACxE;AAAA,MAAC;AAAA;AAAA,QAEC,MAAM;AAAA,QACN,WAAW,OAAO;AAAA,QAClB,SAAS,MAAM,aAAa,KAAK;AAAA;AAAA,MAH5B,OAAO;AAAA,IAId;AAAA,KACF;AAEJ;","names":[]}
@@ -116,3 +116,13 @@
116
116
  margin: 0;
117
117
  padding: 16px 20px 24px;
118
118
  }
119
+
120
+ /* Which part shows: the dropdown on the right. */
121
+ .show {
122
+ display: flex;
123
+ justify-content: flex-end;
124
+ }
125
+
126
+ .show .showSelect {
127
+ width: 220px;
128
+ }
@@ -39,10 +39,10 @@ const NAV_GROUPS = [
39
39
  ]
40
40
  },
41
41
  {
42
- label: "Merchants",
42
+ label: "Workspaces",
43
43
  items: [
44
- { href: "/admin/tenants", label: "Merchants", icon: Building2 },
45
- { href: "/admin/accounts", label: "People", icon: Users },
44
+ { href: "/admin/tenants", label: "Workspaces", icon: Building2 },
45
+ { href: "/admin/accounts", label: "User accounts", icon: Users },
46
46
  { href: "/admin/sales", label: "Sales", icon: Target, module: "sales" },
47
47
  {
48
48
  href: "/admin/verification",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/_shell/nav-config.ts"],"sourcesContent":["import {\n LayoutGrid,\n Building2,\n Users,\n ShieldCheck,\n ScrollText,\n KeyRound,\n Coins,\n MessageSquareText,\n Headphones,\n Star,\n Settings,\n Target,\n Gauge,\n BadgeCheck,\n Mail,\n Landmark,\n ArrowDownToLine,\n Percent,\n MonitorSmartphone,\n Store,\n} from 'lucide-react';\nimport type { AdminModule } from './nav-modules';\n\nexport type NavItem = {\n href: string;\n label: string;\n icon: typeof LayoutGrid;\n superadmin?: boolean;\n permission?: string;\n activeRoutes?: string[];\n /** Which inbox count to show beside the label. */\n inbox?: 'support' | 'feedback';\n /** Listed only when the host has turned this module on. */\n module?: AdminModule;\n /** Listed only while this module is off: the entry stands in for a page that module would otherwise carry as a tab. */\n unlessModule?: AdminModule;\n};\n\nexport type NavGroup = { label: string | null; items: NavItem[] };\n\n/**\n * Pages a host adds to one of the groups below (a new label adds a group;\n * `null` is the top group with Overview). `replace` drops that group's Box\n * items first, so a host can fold a group into one entry of its own.\n */\nexport type NavAddition = {\n group: string | null;\n items: NavItem[];\n replace?: boolean;\n};\n\n/**\n * Second-level tabs shown above the page while inside Product feedback.\n * Product insights is not one: no box serves it.\n */\nexport const PRODUCT_FEEDBACK_NAV: NavItem[] = [\n { href: '/admin/feedback', label: 'Feedback', icon: MessageSquareText },\n {\n href: '/admin/reviews',\n label: 'Reviews',\n icon: Star,\n permission: 'reviews.read',\n module: 'reviews',\n },\n];\n\n/**\n * Grouped like a banking control room: the merchant first, then money, then\n * operations, then the platform itself. Same order and words as the Box admin.\n */\nexport const NAV_GROUPS: NavGroup[] = [\n {\n label: null,\n items: [\n { href: '/admin', label: 'Overview', icon: LayoutGrid },\n { href: '/admin/revenue', label: 'Revenue', icon: Coins },\n ],\n },\n {\n label: 'Merchants',\n items: [\n { href: '/admin/tenants', label: 'Merchants', icon: Building2 },\n { href: '/admin/accounts', label: 'People', icon: Users },\n { href: '/admin/sales', label: 'Sales', icon: Target, module: 'sales' },\n {\n href: '/admin/verification',\n label: 'Verification',\n icon: BadgeCheck,\n permission: 'compliance.read',\n module: 'verification',\n },\n {\n href: '/admin/newsletters',\n label: 'Newsletters',\n icon: Mail,\n permission: 'newsletters.read',\n module: 'newsletters',\n },\n ],\n },\n {\n label: 'Money',\n items: [\n {\n href: '/admin/wallets',\n label: 'Wallets & ledger',\n icon: Landmark,\n module: 'wallets',\n },\n {\n href: '/admin/withdrawals',\n label: 'Withdrawals',\n icon: ArrowDownToLine,\n module: 'withdrawals',\n },\n {\n href: '/admin/pricing',\n label: 'Pricing',\n icon: Percent,\n module: 'pricing',\n },\n {\n href: '/admin/payout-tiers',\n label: 'Payout tiers',\n icon: Gauge,\n permission: 'money.payout_tiers.read',\n },\n ],\n },\n {\n label: 'Operations',\n items: [\n {\n href: '/admin/pos',\n label: 'POS',\n icon: MonitorSmartphone,\n permission: 'pos.read',\n module: 'pos',\n },\n {\n href: '/admin/commerce',\n label: 'Commerce',\n icon: Store,\n module: 'commerce',\n },\n ],\n },\n {\n label: 'Inbox',\n items: [\n {\n href: '/admin/support',\n label: 'Support',\n icon: Headphones,\n permission: 'support.read',\n inbox: 'support',\n module: 'support',\n },\n {\n href: '/admin/feedback',\n label: 'Product feedback',\n icon: MessageSquareText,\n activeRoutes: PRODUCT_FEEDBACK_NAV.map(({ href }) => href),\n inbox: 'feedback',\n module: 'feedback',\n },\n {\n href: '/admin/reviews',\n label: 'Reviews',\n icon: Star,\n permission: 'reviews.read',\n module: 'reviews',\n unlessModule: 'feedback',\n },\n ],\n },\n {\n label: 'Platform',\n items: [\n {\n href: '/admin/admins',\n label: 'Admins',\n icon: ShieldCheck,\n superadmin: true,\n },\n {\n href: '/admin/roles',\n label: 'Roles',\n icon: KeyRound,\n superadmin: true,\n },\n { href: '/admin/audit', label: 'Audit log', icon: ScrollText },\n {\n href: '/admin/settings',\n label: 'Settings',\n icon: Settings,\n module: 'settings',\n },\n ],\n },\n];\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/_shell/nav-config.ts"],"sourcesContent":["import {\n LayoutGrid,\n Building2,\n Users,\n ShieldCheck,\n ScrollText,\n KeyRound,\n Coins,\n MessageSquareText,\n Headphones,\n Star,\n Settings,\n Target,\n Gauge,\n BadgeCheck,\n Mail,\n Landmark,\n ArrowDownToLine,\n Percent,\n MonitorSmartphone,\n Store,\n} from 'lucide-react';\nimport type { AdminModule } from './nav-modules';\n\nexport type NavItem = {\n href: string;\n label: string;\n icon: typeof LayoutGrid;\n superadmin?: boolean;\n permission?: string;\n activeRoutes?: string[];\n /** Which inbox count to show beside the label. */\n inbox?: 'support' | 'feedback';\n /** Listed only when the host has turned this module on. */\n module?: AdminModule;\n /** Listed only while this module is off: the entry stands in for a page that module would otherwise carry as a tab. */\n unlessModule?: AdminModule;\n};\n\nexport type NavGroup = { label: string | null; items: NavItem[] };\n\n/**\n * Pages a host adds to one of the groups below (a new label adds a group;\n * `null` is the top group with Overview). `replace` drops that group's Box\n * items first, so a host can fold a group into one entry of its own.\n */\nexport type NavAddition = {\n group: string | null;\n items: NavItem[];\n replace?: boolean;\n};\n\n/**\n * Second-level tabs shown above the page while inside Product feedback.\n * Product insights is not one: no box serves it.\n */\nexport const PRODUCT_FEEDBACK_NAV: NavItem[] = [\n { href: '/admin/feedback', label: 'Feedback', icon: MessageSquareText },\n {\n href: '/admin/reviews',\n label: 'Reviews',\n icon: Star,\n permission: 'reviews.read',\n module: 'reviews',\n },\n];\n\n/**\n * Grouped like a banking control room: the merchant first, then money, then\n * operations, then the platform itself. Same order and words as the Box admin.\n */\nexport const NAV_GROUPS: NavGroup[] = [\n {\n label: null,\n items: [\n { href: '/admin', label: 'Overview', icon: LayoutGrid },\n { href: '/admin/revenue', label: 'Revenue', icon: Coins },\n ],\n },\n {\n label: 'Workspaces',\n items: [\n { href: '/admin/tenants', label: 'Workspaces', icon: Building2 },\n { href: '/admin/accounts', label: 'User accounts', icon: Users },\n { href: '/admin/sales', label: 'Sales', icon: Target, module: 'sales' },\n {\n href: '/admin/verification',\n label: 'Verification',\n icon: BadgeCheck,\n permission: 'compliance.read',\n module: 'verification',\n },\n {\n href: '/admin/newsletters',\n label: 'Newsletters',\n icon: Mail,\n permission: 'newsletters.read',\n module: 'newsletters',\n },\n ],\n },\n {\n label: 'Money',\n items: [\n {\n href: '/admin/wallets',\n label: 'Wallets & ledger',\n icon: Landmark,\n module: 'wallets',\n },\n {\n href: '/admin/withdrawals',\n label: 'Withdrawals',\n icon: ArrowDownToLine,\n module: 'withdrawals',\n },\n {\n href: '/admin/pricing',\n label: 'Pricing',\n icon: Percent,\n module: 'pricing',\n },\n {\n href: '/admin/payout-tiers',\n label: 'Payout tiers',\n icon: Gauge,\n permission: 'money.payout_tiers.read',\n },\n ],\n },\n {\n label: 'Operations',\n items: [\n {\n href: '/admin/pos',\n label: 'POS',\n icon: MonitorSmartphone,\n permission: 'pos.read',\n module: 'pos',\n },\n {\n href: '/admin/commerce',\n label: 'Commerce',\n icon: Store,\n module: 'commerce',\n },\n ],\n },\n {\n label: 'Inbox',\n items: [\n {\n href: '/admin/support',\n label: 'Support',\n icon: Headphones,\n permission: 'support.read',\n inbox: 'support',\n module: 'support',\n },\n {\n href: '/admin/feedback',\n label: 'Product feedback',\n icon: MessageSquareText,\n activeRoutes: PRODUCT_FEEDBACK_NAV.map(({ href }) => href),\n inbox: 'feedback',\n module: 'feedback',\n },\n {\n href: '/admin/reviews',\n label: 'Reviews',\n icon: Star,\n permission: 'reviews.read',\n module: 'reviews',\n unlessModule: 'feedback',\n },\n ],\n },\n {\n label: 'Platform',\n items: [\n {\n href: '/admin/admins',\n label: 'Admins',\n icon: ShieldCheck,\n superadmin: true,\n },\n {\n href: '/admin/roles',\n label: 'Roles',\n icon: KeyRound,\n superadmin: true,\n },\n { href: '/admin/audit', label: 'Audit log', icon: ScrollText },\n {\n href: '/admin/settings',\n label: 'Settings',\n icon: Settings,\n module: 'settings',\n },\n ],\n },\n];\n"],"mappings":"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","names":[]}
@@ -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":[]}
@@ -45,8 +45,8 @@ function AccountsWorkspace() {
45
45
  /* @__PURE__ */ jsx(
46
46
  PageHeader,
47
47
  {
48
- title: "People",
49
- description: `Every person registered on ${ADMIN_NAME}.`,
48
+ title: "User accounts",
49
+ description: `Everyone who signs in to ${ADMIN_NAME}, across all workspaces.`,
50
50
  actions: tab === "overview" && /* @__PURE__ */ jsx("div", { className: "adm-toolbar", children: /* @__PURE__ */ jsx(
51
51
  PeriodSelector,
52
52
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/accounts/accounts-workspace.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo, useState } from 'react';\nimport { ADMIN_NAME } from '../_shell/admin-brand';\nimport {\n PageHeader,\n PageTabs,\n PeriodSelector,\n customRange,\n periodPhrase,\n rangeFromKey,\n usePersistedDateRange,\n useTabParam,\n} from '../_ui';\nimport { usePlatformAccountsAnalytics } from '../hooks';\nimport { AccountPanel, type AccountSummary } from './account-panel';\nimport { AccountsAnalytics } from './accounts-analytics';\nimport { AccountsTable } from './accounts-table';\n\nconst TABS = [\n { id: 'overview', label: 'Overview' },\n { id: 'all', label: 'All accounts' },\n] as const;\n\nconst KEYS = TABS.map((t) => t.id);\n\n/** People: the period's picture on Overview, every account on its own tab. */\nexport function AccountsWorkspace() {\n const [tab, setTab] = useTabParam(KEYS);\n const [selected, setSelected] = useState<AccountSummary | null>(null);\n const {\n rangeKey,\n setRangeKey,\n customStart,\n customEnd,\n onCustomChange,\n compare,\n } = usePersistedDateRange();\n const range = useMemo(\n () =>\n rangeKey === 'custom'\n ? customRange(customStart, customEnd)\n : rangeFromKey(rangeKey),\n [rangeKey, customStart, customEnd],\n );\n const analytics = usePlatformAccountsAnalytics({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n const span = periodPhrase(range);\n\n return (\n <div>\n <PageHeader\n title=\"People\"\n description={`Every person registered on ${ADMIN_NAME}.`}\n actions={\n tab === 'overview' && (\n <div className=\"adm-toolbar\">\n <PeriodSelector\n value={rangeKey}\n onChange={setRangeKey}\n customStart={customStart}\n customEnd={customEnd}\n onCustomChange={onCustomChange}\n />\n </div>\n )\n }\n />\n <PageTabs tabs={TABS} active={tab} onChange={setTab} />\n\n {tab === 'overview' && (\n <>\n <AccountsAnalytics\n data={analytics.data}\n isLoading={analytics.isLoading}\n isError={analytics.isError}\n onRetry={() => void analytics.refetch()}\n caption={`${span[0].toUpperCase()}${span.slice(1)}`}\n span={span}\n compare={compare}\n onSelectAccount={setSelected}\n />\n <AccountPanel account={selected} onClose={() => setSelected(null)} />\n </>\n )}\n\n {tab === 'all' && (\n <AccountsTable selected={selected} onSelect={setSelected} />\n )}\n </div>\n );\n}\n"],"mappings":";AA2Dc,SAcN,UAdM,KAcN,YAdM;AAzDd,SAAS,SAAS,gBAAgB;AAClC,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,oCAAoC;AAC7C,SAAS,oBAAyC;AAClD,SAAS,yBAAyB;AAClC,SAAS,qBAAqB;AAE9B,MAAM,OAAO;AAAA,EACX,EAAE,IAAI,YAAY,OAAO,WAAW;AAAA,EACpC,EAAE,IAAI,OAAO,OAAO,eAAe;AACrC;AAEA,MAAM,OAAO,KAAK,IAAI,CAAC,MAAM,EAAE,EAAE;AAG1B,SAAS,oBAAoB;AAClC,QAAM,CAAC,KAAK,MAAM,IAAI,YAAY,IAAI;AACtC,QAAM,CAAC,UAAU,WAAW,IAAI,SAAgC,IAAI;AACpE,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,sBAAsB;AAC1B,QAAM,QAAQ;AAAA,IACZ,MACE,aAAa,WACT,YAAY,aAAa,SAAS,IAClC,aAAa,QAAQ;AAAA,IAC3B,CAAC,UAAU,aAAa,SAAS;AAAA,EACnC;AACA,QAAM,YAAY,6BAA6B;AAAA,IAC7C,WAAW,MAAM;AAAA,IACjB,SAAS,MAAM;AAAA,EACjB,CAAC;AACD,QAAM,OAAO,aAAa,KAAK;AAE/B,SACE,qBAAC,SACC;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,aAAa,8BAA8B,UAAU;AAAA,QACrD,SACE,QAAQ,cACN,oBAAC,SAAI,WAAU,eACb;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,YACP,UAAU;AAAA,YACV;AAAA,YACA;AAAA,YACA;AAAA;AAAA,QACF,GACF;AAAA;AAAA,IAGN;AAAA,IACA,oBAAC,YAAS,MAAM,MAAM,QAAQ,KAAK,UAAU,QAAQ;AAAA,IAEpD,QAAQ,cACP,iCACE;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAM,UAAU;AAAA,UAChB,WAAW,UAAU;AAAA,UACrB,SAAS,UAAU;AAAA,UACnB,SAAS,MAAM,KAAK,UAAU,QAAQ;AAAA,UACtC,SAAS,GAAG,KAAK,CAAC,EAAE,YAAY,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC;AAAA,UACjD;AAAA,UACA;AAAA,UACA,iBAAiB;AAAA;AAAA,MACnB;AAAA,MACA,oBAAC,gBAAa,SAAS,UAAU,SAAS,MAAM,YAAY,IAAI,GAAG;AAAA,OACrE;AAAA,IAGD,QAAQ,SACP,oBAAC,iBAAc,UAAoB,UAAU,aAAa;AAAA,KAE9D;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/accounts/accounts-workspace.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo, useState } from 'react';\nimport { ADMIN_NAME } from '../_shell/admin-brand';\nimport {\n PageHeader,\n PageTabs,\n PeriodSelector,\n customRange,\n periodPhrase,\n rangeFromKey,\n usePersistedDateRange,\n useTabParam,\n} from '../_ui';\nimport { usePlatformAccountsAnalytics } from '../hooks';\nimport { AccountPanel, type AccountSummary } from './account-panel';\nimport { AccountsAnalytics } from './accounts-analytics';\nimport { AccountsTable } from './accounts-table';\n\nconst TABS = [\n { id: 'overview', label: 'Overview' },\n { id: 'all', label: 'All accounts' },\n] as const;\n\nconst KEYS = TABS.map((t) => t.id);\n\n/** People: the period's picture on Overview, every account on its own tab. */\nexport function AccountsWorkspace() {\n const [tab, setTab] = useTabParam(KEYS);\n const [selected, setSelected] = useState<AccountSummary | null>(null);\n const {\n rangeKey,\n setRangeKey,\n customStart,\n customEnd,\n onCustomChange,\n compare,\n } = usePersistedDateRange();\n const range = useMemo(\n () =>\n rangeKey === 'custom'\n ? customRange(customStart, customEnd)\n : rangeFromKey(rangeKey),\n [rangeKey, customStart, customEnd],\n );\n const analytics = usePlatformAccountsAnalytics({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n const span = periodPhrase(range);\n\n return (\n <div>\n <PageHeader\n title=\"User accounts\"\n description={`Everyone who signs in to ${ADMIN_NAME}, across all workspaces.`}\n actions={\n tab === 'overview' && (\n <div className=\"adm-toolbar\">\n <PeriodSelector\n value={rangeKey}\n onChange={setRangeKey}\n customStart={customStart}\n customEnd={customEnd}\n onCustomChange={onCustomChange}\n />\n </div>\n )\n }\n />\n <PageTabs tabs={TABS} active={tab} onChange={setTab} />\n\n {tab === 'overview' && (\n <>\n <AccountsAnalytics\n data={analytics.data}\n isLoading={analytics.isLoading}\n isError={analytics.isError}\n onRetry={() => void analytics.refetch()}\n caption={`${span[0].toUpperCase()}${span.slice(1)}`}\n span={span}\n compare={compare}\n onSelectAccount={setSelected}\n />\n <AccountPanel account={selected} onClose={() => setSelected(null)} />\n </>\n )}\n\n {tab === 'all' && (\n <AccountsTable selected={selected} onSelect={setSelected} />\n )}\n </div>\n );\n}\n"],"mappings":";AA2Dc,SAcN,UAdM,KAcN,YAdM;AAzDd,SAAS,SAAS,gBAAgB;AAClC,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,oCAAoC;AAC7C,SAAS,oBAAyC;AAClD,SAAS,yBAAyB;AAClC,SAAS,qBAAqB;AAE9B,MAAM,OAAO;AAAA,EACX,EAAE,IAAI,YAAY,OAAO,WAAW;AAAA,EACpC,EAAE,IAAI,OAAO,OAAO,eAAe;AACrC;AAEA,MAAM,OAAO,KAAK,IAAI,CAAC,MAAM,EAAE,EAAE;AAG1B,SAAS,oBAAoB;AAClC,QAAM,CAAC,KAAK,MAAM,IAAI,YAAY,IAAI;AACtC,QAAM,CAAC,UAAU,WAAW,IAAI,SAAgC,IAAI;AACpE,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,sBAAsB;AAC1B,QAAM,QAAQ;AAAA,IACZ,MACE,aAAa,WACT,YAAY,aAAa,SAAS,IAClC,aAAa,QAAQ;AAAA,IAC3B,CAAC,UAAU,aAAa,SAAS;AAAA,EACnC;AACA,QAAM,YAAY,6BAA6B;AAAA,IAC7C,WAAW,MAAM;AAAA,IACjB,SAAS,MAAM;AAAA,EACjB,CAAC;AACD,QAAM,OAAO,aAAa,KAAK;AAE/B,SACE,qBAAC,SACC;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,aAAa,4BAA4B,UAAU;AAAA,QACnD,SACE,QAAQ,cACN,oBAAC,SAAI,WAAU,eACb;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,YACP,UAAU;AAAA,YACV;AAAA,YACA;AAAA,YACA;AAAA;AAAA,QACF,GACF;AAAA;AAAA,IAGN;AAAA,IACA,oBAAC,YAAS,MAAM,MAAM,QAAQ,KAAK,UAAU,QAAQ;AAAA,IAEpD,QAAQ,cACP,iCACE;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAM,UAAU;AAAA,UAChB,WAAW,UAAU;AAAA,UACrB,SAAS,UAAU;AAAA,UACnB,SAAS,MAAM,KAAK,UAAU,QAAQ;AAAA,UACtC,SAAS,GAAG,KAAK,CAAC,EAAE,YAAY,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC;AAAA,UACjD;AAAA,UACA;AAAA,UACA,iBAAiB;AAAA;AAAA,MACnB;AAAA,MACA,oBAAC,gBAAa,SAAS,UAAU,SAAS,MAAM,YAAY,IAAI,GAAG;AAAA,OACrE;AAAA,IAGD,QAAQ,SACP,oBAAC,iBAAc,UAAoB,UAAU,aAAa;AAAA,KAE9D;AAEJ;","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":[]}
@@ -17,8 +17,8 @@ const THEMES = [
17
17
  const START_OPTIONS = [
18
18
  { value: "overview", label: "Overview" },
19
19
  { value: "support", label: "Support" },
20
- { value: "merchants", label: "Merchants" },
21
- { value: "people", label: "People" }
20
+ { value: "merchants", label: "Workspaces" },
21
+ { value: "people", label: "User accounts" }
22
22
  ];
23
23
  function GeneralTab() {
24
24
  const settings = useSettings();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/settings/general-tab.tsx"],"sourcesContent":["'use client';\n\nimport { Select } from '@withpotter/ui-kit';\nimport { useTheme } from '@withpotter/lib/theme-context';\nimport { RANGE_OPTIONS, usePersistedDateRange, type RangeKey } from '../_ui';\nimport { useSettings, type AdminSettings } from '../_settings';\nimport { useModuleOn } from '../_shell/modules-context';\nimport { useSidebarCollapsed, sidebarStore } from '../_shell/use-shell-prefs';\nimport { Segmented, SettingRow, SettingsCard } from './setting-row';\nimport { useSave } from './use-save';\nimport styles from './settings.module.css';\n\ntype Theme = 'system' | 'light' | 'dark';\n\nconst THEMES = [\n { value: 'system' as Theme, label: 'System' },\n { value: 'light' as Theme, label: 'Light' },\n { value: 'dark' as Theme, label: 'Dark' },\n];\n\nconst START_OPTIONS: Array<{\n value: AdminSettings['startPage'];\n label: string;\n}> = [\n { value: 'overview', label: 'Overview' },\n { value: 'support', label: 'Support' },\n { value: 'merchants', label: 'Merchants' },\n { value: 'people', label: 'People' },\n];\n\n/**\n * Look, layout and the views every page opens with. No display currency on a\n * box: money is shown per currency and never converted, so there is nothing\n * to pick. Support is a start page only where this box serves Support.\n */\nexport function GeneralTab() {\n const settings = useSettings();\n const { update, saved } = useSave();\n const { theme, setTheme } = useTheme();\n const { rangeKey, setRangeKey } = usePersistedDateRange();\n const collapsed = useSidebarCollapsed();\n const supportOn = useModuleOn('support');\n const startOptions = START_OPTIONS.filter(\n (o) => supportOn || o.value !== 'support'\n );\n\n return (\n <div className={styles.stack}>\n <SettingsCard title=\"Appearance\">\n <SettingRow\n label=\"Theme\"\n description=\"System follows your computer's light or dark setting.\"\n >\n <Segmented\n label=\"Theme\"\n value={theme}\n options={THEMES}\n onChange={(next) => {\n setTheme(next);\n saved();\n }}\n />\n </SettingRow>\n <SettingRow\n label=\"Density\"\n description=\"Compact fits more rows and cards on screen.\"\n >\n <Segmented\n label=\"Density\"\n value={settings.density}\n options={[\n { value: 'comfortable', label: 'Comfortable' },\n { value: 'compact', label: 'Compact' },\n ]}\n onChange={(density) => update({ density })}\n />\n </SettingRow>\n <SettingRow\n label=\"Sidebar\"\n description=\"Collapsed shows icons only. You can also toggle it from the sidebar.\"\n >\n <Segmented\n label=\"Sidebar\"\n value={collapsed ? 'collapsed' : 'expanded'}\n options={[\n { value: 'expanded', label: 'Expanded' },\n { value: 'collapsed', label: 'Collapsed' },\n ]}\n onChange={(next) => {\n sidebarStore.set(next === 'collapsed');\n saved();\n }}\n />\n </SettingRow>\n </SettingsCard>\n\n <SettingsCard title=\"Defaults\" description=\"What pages open with.\">\n <SettingRow\n label=\"Default period\"\n description=\"The date range reports and charts start on.\"\n >\n <Select\n className={styles.select}\n ariaLabel=\"Default period\"\n value={rangeKey}\n options={RANGE_OPTIONS.map((o) => ({\n value: o.key,\n label: o.label,\n }))}\n onChange={(key) => {\n setRangeKey(key as RangeKey);\n saved();\n }}\n />\n </SettingRow>\n <SettingRow\n label=\"Start page\"\n description=\"Where the console opens when you sign in or reload on Overview.\"\n >\n <Select\n className={styles.select}\n ariaLabel=\"Start page\"\n value={settings.startPage}\n options={startOptions}\n onChange={(value) =>\n update({ startPage: value as AdminSettings['startPage'] })\n }\n />\n </SettingRow>\n </SettingsCard>\n </div>\n );\n}\n"],"mappings":";AAgDM,SAKI,KALJ;AA9CN,SAAS,cAAc;AACvB,SAAS,gBAAgB;AACzB,SAAS,eAAe,6BAA4C;AACpE,SAAS,mBAAuC;AAChD,SAAS,mBAAmB;AAC5B,SAAS,qBAAqB,oBAAoB;AAClD,SAAS,WAAW,YAAY,oBAAoB;AACpD,SAAS,eAAe;AACxB,OAAO,YAAY;AAInB,MAAM,SAAS;AAAA,EACb,EAAE,OAAO,UAAmB,OAAO,SAAS;AAAA,EAC5C,EAAE,OAAO,SAAkB,OAAO,QAAQ;AAAA,EAC1C,EAAE,OAAO,QAAiB,OAAO,OAAO;AAC1C;AAEA,MAAM,gBAGD;AAAA,EACH,EAAE,OAAO,YAAY,OAAO,WAAW;AAAA,EACvC,EAAE,OAAO,WAAW,OAAO,UAAU;AAAA,EACrC,EAAE,OAAO,aAAa,OAAO,YAAY;AAAA,EACzC,EAAE,OAAO,UAAU,OAAO,SAAS;AACrC;AAOO,SAAS,aAAa;AAC3B,QAAM,WAAW,YAAY;AAC7B,QAAM,EAAE,QAAQ,MAAM,IAAI,QAAQ;AAClC,QAAM,EAAE,OAAO,SAAS,IAAI,SAAS;AACrC,QAAM,EAAE,UAAU,YAAY,IAAI,sBAAsB;AACxD,QAAM,YAAY,oBAAoB;AACtC,QAAM,YAAY,YAAY,SAAS;AACvC,QAAM,eAAe,cAAc;AAAA,IACjC,CAAC,MAAM,aAAa,EAAE,UAAU;AAAA,EAClC;AAEA,SACE,qBAAC,SAAI,WAAW,OAAO,OACrB;AAAA,yBAAC,gBAAa,OAAM,cAClB;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,aAAY;AAAA,UAEZ;AAAA,YAAC;AAAA;AAAA,cACC,OAAM;AAAA,cACN,OAAO;AAAA,cACP,SAAS;AAAA,cACT,UAAU,CAAC,SAAS;AAClB,yBAAS,IAAI;AACb,sBAAM;AAAA,cACR;AAAA;AAAA,UACF;AAAA;AAAA,MACF;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,aAAY;AAAA,UAEZ;AAAA,YAAC;AAAA;AAAA,cACC,OAAM;AAAA,cACN,OAAO,SAAS;AAAA,cAChB,SAAS;AAAA,gBACP,EAAE,OAAO,eAAe,OAAO,cAAc;AAAA,gBAC7C,EAAE,OAAO,WAAW,OAAO,UAAU;AAAA,cACvC;AAAA,cACA,UAAU,CAAC,YAAY,OAAO,EAAE,QAAQ,CAAC;AAAA;AAAA,UAC3C;AAAA;AAAA,MACF;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,aAAY;AAAA,UAEZ;AAAA,YAAC;AAAA;AAAA,cACC,OAAM;AAAA,cACN,OAAO,YAAY,cAAc;AAAA,cACjC,SAAS;AAAA,gBACP,EAAE,OAAO,YAAY,OAAO,WAAW;AAAA,gBACvC,EAAE,OAAO,aAAa,OAAO,YAAY;AAAA,cAC3C;AAAA,cACA,UAAU,CAAC,SAAS;AAClB,6BAAa,IAAI,SAAS,WAAW;AACrC,sBAAM;AAAA,cACR;AAAA;AAAA,UACF;AAAA;AAAA,MACF;AAAA,OACF;AAAA,IAEA,qBAAC,gBAAa,OAAM,YAAW,aAAY,yBACzC;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,aAAY;AAAA,UAEZ;AAAA,YAAC;AAAA;AAAA,cACC,WAAW,OAAO;AAAA,cAClB,WAAU;AAAA,cACV,OAAO;AAAA,cACP,SAAS,cAAc,IAAI,CAAC,OAAO;AAAA,gBACjC,OAAO,EAAE;AAAA,gBACT,OAAO,EAAE;AAAA,cACX,EAAE;AAAA,cACF,UAAU,CAAC,QAAQ;AACjB,4BAAY,GAAe;AAC3B,sBAAM;AAAA,cACR;AAAA;AAAA,UACF;AAAA;AAAA,MACF;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,aAAY;AAAA,UAEZ;AAAA,YAAC;AAAA;AAAA,cACC,WAAW,OAAO;AAAA,cAClB,WAAU;AAAA,cACV,OAAO,SAAS;AAAA,cAChB,SAAS;AAAA,cACT,UAAU,CAAC,UACT,OAAO,EAAE,WAAW,MAAoC,CAAC;AAAA;AAAA,UAE7D;AAAA;AAAA,MACF;AAAA,OACF;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/settings/general-tab.tsx"],"sourcesContent":["'use client';\n\nimport { Select } from '@withpotter/ui-kit';\nimport { useTheme } from '@withpotter/lib/theme-context';\nimport { RANGE_OPTIONS, usePersistedDateRange, type RangeKey } from '../_ui';\nimport { useSettings, type AdminSettings } from '../_settings';\nimport { useModuleOn } from '../_shell/modules-context';\nimport { useSidebarCollapsed, sidebarStore } from '../_shell/use-shell-prefs';\nimport { Segmented, SettingRow, SettingsCard } from './setting-row';\nimport { useSave } from './use-save';\nimport styles from './settings.module.css';\n\ntype Theme = 'system' | 'light' | 'dark';\n\nconst THEMES = [\n { value: 'system' as Theme, label: 'System' },\n { value: 'light' as Theme, label: 'Light' },\n { value: 'dark' as Theme, label: 'Dark' },\n];\n\nconst START_OPTIONS: Array<{\n value: AdminSettings['startPage'];\n label: string;\n}> = [\n { value: 'overview', label: 'Overview' },\n { value: 'support', label: 'Support' },\n { value: 'merchants', label: 'Workspaces' },\n { value: 'people', label: 'User accounts' },\n];\n\n/**\n * Look, layout and the views every page opens with. No display currency on a\n * box: money is shown per currency and never converted, so there is nothing\n * to pick. Support is a start page only where this box serves Support.\n */\nexport function GeneralTab() {\n const settings = useSettings();\n const { update, saved } = useSave();\n const { theme, setTheme } = useTheme();\n const { rangeKey, setRangeKey } = usePersistedDateRange();\n const collapsed = useSidebarCollapsed();\n const supportOn = useModuleOn('support');\n const startOptions = START_OPTIONS.filter(\n (o) => supportOn || o.value !== 'support'\n );\n\n return (\n <div className={styles.stack}>\n <SettingsCard title=\"Appearance\">\n <SettingRow\n label=\"Theme\"\n description=\"System follows your computer's light or dark setting.\"\n >\n <Segmented\n label=\"Theme\"\n value={theme}\n options={THEMES}\n onChange={(next) => {\n setTheme(next);\n saved();\n }}\n />\n </SettingRow>\n <SettingRow\n label=\"Density\"\n description=\"Compact fits more rows and cards on screen.\"\n >\n <Segmented\n label=\"Density\"\n value={settings.density}\n options={[\n { value: 'comfortable', label: 'Comfortable' },\n { value: 'compact', label: 'Compact' },\n ]}\n onChange={(density) => update({ density })}\n />\n </SettingRow>\n <SettingRow\n label=\"Sidebar\"\n description=\"Collapsed shows icons only. You can also toggle it from the sidebar.\"\n >\n <Segmented\n label=\"Sidebar\"\n value={collapsed ? 'collapsed' : 'expanded'}\n options={[\n { value: 'expanded', label: 'Expanded' },\n { value: 'collapsed', label: 'Collapsed' },\n ]}\n onChange={(next) => {\n sidebarStore.set(next === 'collapsed');\n saved();\n }}\n />\n </SettingRow>\n </SettingsCard>\n\n <SettingsCard title=\"Defaults\" description=\"What pages open with.\">\n <SettingRow\n label=\"Default period\"\n description=\"The date range reports and charts start on.\"\n >\n <Select\n className={styles.select}\n ariaLabel=\"Default period\"\n value={rangeKey}\n options={RANGE_OPTIONS.map((o) => ({\n value: o.key,\n label: o.label,\n }))}\n onChange={(key) => {\n setRangeKey(key as RangeKey);\n saved();\n }}\n />\n </SettingRow>\n <SettingRow\n label=\"Start page\"\n description=\"Where the console opens when you sign in or reload on Overview.\"\n >\n <Select\n className={styles.select}\n ariaLabel=\"Start page\"\n value={settings.startPage}\n options={startOptions}\n onChange={(value) =>\n update({ startPage: value as AdminSettings['startPage'] })\n }\n />\n </SettingRow>\n </SettingsCard>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,78 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import { useQuery } from "@tanstack/react-query";
5
+ import { adminCaseApi } from "@withpotter/lib/admin-case-api";
6
+ import { AdmLoading } from "../../../_ui";
7
+ import { documentWord } from "./case-checks";
8
+ import { caseErrorText } from "./case-rules";
9
+ import { documentLinkKey, pdfSource, viewerKind } from "./document-view";
10
+ import styles from "./document-viewer.module.css";
11
+ function DocumentFile({
12
+ tenantId,
13
+ document,
14
+ size = "full"
15
+ }) {
16
+ const link = useQuery({
17
+ queryKey: [...documentLinkKey(tenantId, document.id), size],
18
+ queryFn: () => adminCaseApi.viewDocument(tenantId, document.id),
19
+ retry: false,
20
+ staleTime: Infinity,
21
+ gcTime: 0,
22
+ refetchOnWindowFocus: false,
23
+ refetchOnReconnect: false
24
+ });
25
+ const name = documentWord(document);
26
+ const thumb = size === "thumb";
27
+ if (link.isLoading)
28
+ return thumb ? /* @__PURE__ */ jsx("div", { className: styles.thumbFrame, children: /* @__PURE__ */ jsx("span", { className: styles.note, children: "Loading\u2026" }) }) : /* @__PURE__ */ jsx(AdmLoading, { label: "Opening document\u2026" });
29
+ if (link.isError || !link.data)
30
+ return /* @__PURE__ */ jsx("p", { className: styles.note, role: "alert", children: caseErrorText(link.error, "document") });
31
+ const kind = viewerKind(link.data.mimeType);
32
+ if (kind === "image")
33
+ return /* @__PURE__ */ jsx(Photo, { url: link.data.url, name, thumb });
34
+ if (kind === "pdf" && thumb)
35
+ return /* @__PURE__ */ jsx("div", { className: styles.thumbFrame, children: /* @__PURE__ */ jsx("span", { className: styles.note, children: "PDF. Open it to read." }) });
36
+ if (kind === "pdf")
37
+ return /* @__PURE__ */ jsx("div", { className: styles.frame, children: /* @__PURE__ */ jsx(
38
+ "iframe",
39
+ {
40
+ className: styles.pdf,
41
+ src: pdfSource(link.data.url),
42
+ title: name,
43
+ referrerPolicy: "no-referrer"
44
+ }
45
+ ) });
46
+ return /* @__PURE__ */ jsx("p", { className: styles.note, children: "This file type can't be shown here." });
47
+ }
48
+ function Photo({
49
+ url,
50
+ name,
51
+ thumb
52
+ }) {
53
+ const [state, setState] = useState(
54
+ "loading"
55
+ );
56
+ return /* @__PURE__ */ jsxs("div", { className: thumb ? styles.thumbFrame : styles.frame, children: [
57
+ state === "loading" && /* @__PURE__ */ jsx("span", { className: styles.note, children: "Loading photo\u2026" }),
58
+ state === "failed" && /* @__PURE__ */ jsx("span", { className: styles.note, role: "alert", children: "The photo did not load. Close it and open it again." }),
59
+ /* @__PURE__ */ jsx(
60
+ "img",
61
+ {
62
+ className: thumb ? styles.thumbImage : styles.image,
63
+ src: url,
64
+ alt: name,
65
+ style: state === "shown" ? void 0 : { display: "none" },
66
+ referrerPolicy: "no-referrer",
67
+ draggable: false,
68
+ onLoad: () => setState("shown"),
69
+ onError: () => setState("failed"),
70
+ onContextMenu: (e) => e.preventDefault()
71
+ }
72
+ )
73
+ ] });
74
+ }
75
+ export {
76
+ DocumentFile
77
+ };
78
+ //# sourceMappingURL=document-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../src/admin/tenants/%5Bid%5D/verification/document-file.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { useQuery } from '@tanstack/react-query';\nimport { adminCaseApi } from '@withpotter/lib/admin-case-api';\nimport type { CaseDocument } from '@withpotter/lib/admin-case-types';\nimport { AdmLoading } from '../../../_ui';\nimport { documentWord } from './case-checks';\nimport { caseErrorText } from './case-rules';\nimport { documentLinkKey, pdfSource, viewerKind } from './document-view';\nimport styles from './document-viewer.module.css';\n\n/**\n * One file a business sent, drawn from a fresh short-lived link: full size in\n * the document panel, or small on the case itself so the admin sees it before\n * deciding. The two sizes ask for their own link, so the panel never opens on\n * one that has run out while the small copy sat on the page.\n */\nexport function DocumentFile({\n tenantId,\n document,\n size = 'full',\n}: {\n tenantId: string;\n document: CaseDocument;\n size?: 'full' | 'thumb';\n}) {\n // One link per opening: no retries or refetches, each would be another logged view.\n const link = useQuery({\n queryKey: [...documentLinkKey(tenantId, document.id), size],\n queryFn: () => adminCaseApi.viewDocument(tenantId, document.id),\n retry: false,\n staleTime: Infinity,\n gcTime: 0,\n refetchOnWindowFocus: false,\n refetchOnReconnect: false,\n });\n const name = documentWord(document);\n const thumb = size === 'thumb';\n\n if (link.isLoading)\n return thumb ? (\n <div className={styles.thumbFrame}>\n <span className={styles.note}>Loading…</span>\n </div>\n ) : (\n <AdmLoading label=\"Opening document…\" />\n );\n\n if (link.isError || !link.data)\n return (\n <p className={styles.note} role=\"alert\">\n {caseErrorText(link.error, 'document')}\n </p>\n );\n\n const kind = viewerKind(link.data.mimeType);\n\n if (kind === 'image')\n return <Photo url={link.data.url} name={name} thumb={thumb} />;\n\n if (kind === 'pdf' && thumb)\n return (\n <div className={styles.thumbFrame}>\n <span className={styles.note}>PDF. Open it to read.</span>\n </div>\n );\n\n if (kind === 'pdf')\n return (\n <div className={styles.frame}>\n <iframe\n className={styles.pdf}\n src={pdfSource(link.data.url)}\n title={name}\n referrerPolicy=\"no-referrer\"\n />\n </div>\n );\n\n return <p className={styles.note}>This file type can't be shown here.</p>;\n}\n\n/** The photo, with a word while it downloads and if it never arrives. */\nfunction Photo({\n url,\n name,\n thumb,\n}: {\n url: string;\n name: string;\n thumb: boolean;\n}) {\n const [state, setState] = useState<'loading' | 'shown' | 'failed'>(\n 'loading',\n );\n\n return (\n <div className={thumb ? styles.thumbFrame : styles.frame}>\n {state === 'loading' && (\n <span className={styles.note}>Loading photo…</span>\n )}\n {state === 'failed' && (\n <span className={styles.note} role=\"alert\">\n The photo did not load. Close it and open it again.\n </span>\n )}\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n className={thumb ? styles.thumbImage : styles.image}\n src={url}\n alt={name}\n // The class sets display, which would beat `hidden`.\n style={state === 'shown' ? undefined : { display: 'none' }}\n referrerPolicy=\"no-referrer\"\n draggable={false}\n onLoad={() => setState('shown')}\n onError={() => setState('failed')}\n onContextMenu={(e) => e.preventDefault()}\n />\n </div>\n );\n}\n"],"mappings":";AA2CQ,cAuDJ,YAvDI;AAzCR,SAAS,gBAAgB;AACzB,SAAS,gBAAgB;AACzB,SAAS,oBAAoB;AAE7B,SAAS,kBAAkB;AAC3B,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAC9B,SAAS,iBAAiB,WAAW,kBAAkB;AACvD,OAAO,YAAY;AAQZ,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA,OAAO;AACT,GAIG;AAED,QAAM,OAAO,SAAS;AAAA,IACpB,UAAU,CAAC,GAAG,gBAAgB,UAAU,SAAS,EAAE,GAAG,IAAI;AAAA,IAC1D,SAAS,MAAM,aAAa,aAAa,UAAU,SAAS,EAAE;AAAA,IAC9D,OAAO;AAAA,IACP,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,sBAAsB;AAAA,IACtB,oBAAoB;AAAA,EACtB,CAAC;AACD,QAAM,OAAO,aAAa,QAAQ;AAClC,QAAM,QAAQ,SAAS;AAEvB,MAAI,KAAK;AACP,WAAO,QACL,oBAAC,SAAI,WAAW,OAAO,YACrB,8BAAC,UAAK,WAAW,OAAO,MAAM,2BAAQ,GACxC,IAEA,oBAAC,cAAW,OAAM,0BAAoB;AAG1C,MAAI,KAAK,WAAW,CAAC,KAAK;AACxB,WACE,oBAAC,OAAE,WAAW,OAAO,MAAM,MAAK,SAC7B,wBAAc,KAAK,OAAO,UAAU,GACvC;AAGJ,QAAM,OAAO,WAAW,KAAK,KAAK,QAAQ;AAE1C,MAAI,SAAS;AACX,WAAO,oBAAC,SAAM,KAAK,KAAK,KAAK,KAAK,MAAY,OAAc;AAE9D,MAAI,SAAS,SAAS;AACpB,WACE,oBAAC,SAAI,WAAW,OAAO,YACrB,8BAAC,UAAK,WAAW,OAAO,MAAM,mCAAqB,GACrD;AAGJ,MAAI,SAAS;AACX,WACE,oBAAC,SAAI,WAAW,OAAO,OACrB;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,OAAO;AAAA,QAClB,KAAK,UAAU,KAAK,KAAK,GAAG;AAAA,QAC5B,OAAO;AAAA,QACP,gBAAe;AAAA;AAAA,IACjB,GACF;AAGJ,SAAO,oBAAC,OAAE,WAAW,OAAO,MAAM,iDAAmC;AACvE;AAGA,SAAS,MAAM;AAAA,EACb;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,CAAC,OAAO,QAAQ,IAAI;AAAA,IACxB;AAAA,EACF;AAEA,SACE,qBAAC,SAAI,WAAW,QAAQ,OAAO,aAAa,OAAO,OAChD;AAAA,cAAU,aACT,oBAAC,UAAK,WAAW,OAAO,MAAM,iCAAc;AAAA,IAE7C,UAAU,YACT,oBAAC,UAAK,WAAW,OAAO,MAAM,MAAK,SAAQ,iEAE3C;AAAA,IAGF;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,QAAQ,OAAO,aAAa,OAAO;AAAA,QAC9C,KAAK;AAAA,QACL,KAAK;AAAA,QAEL,OAAO,UAAU,UAAU,SAAY,EAAE,SAAS,OAAO;AAAA,QACzD,gBAAe;AAAA,QACf,WAAW;AAAA,QACX,QAAQ,MAAM,SAAS,OAAO;AAAA,QAC9B,SAAS,MAAM,SAAS,QAAQ;AAAA,QAChC,eAAe,CAAC,MAAM,EAAE,eAAe;AAAA;AAAA,IACzC;AAAA,KACF;AAEJ;","names":[]}
@@ -1,19 +1,12 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
- import { useQuery } from "@tanstack/react-query";
5
4
  import { Button } from "@withpotter/ui-kit";
6
- import { adminCaseApi } from "@withpotter/lib/admin-case-api";
7
- import { AdmLoading, Drawer } from "../../../_ui";
5
+ import { Drawer } from "../../../_ui";
8
6
  import { documentWord } from "./case-checks";
9
- import { caseErrorText } from "./case-rules";
10
7
  import { checkTone, checkWord, shortDate } from "./case-words";
11
- import {
12
- documentLinkKey,
13
- ownerName,
14
- pdfSource,
15
- viewerKind
16
- } from "./document-view";
8
+ import { DocumentFile } from "./document-file";
9
+ import { ownerName } from "./document-view";
17
10
  import styles from "./document-viewer.module.css";
18
11
  function DocumentViewer({
19
12
  tenantId,
@@ -47,48 +40,6 @@ function DocumentViewer({
47
40
  }
48
41
  );
49
42
  }
50
- function DocumentFile({
51
- tenantId,
52
- document
53
- }) {
54
- const link = useQuery({
55
- queryKey: documentLinkKey(tenantId, document.id),
56
- queryFn: () => adminCaseApi.viewDocument(tenantId, document.id),
57
- retry: false,
58
- staleTime: Infinity,
59
- gcTime: 0,
60
- refetchOnWindowFocus: false,
61
- refetchOnReconnect: false
62
- });
63
- const name = documentWord(document);
64
- if (link.isLoading) return /* @__PURE__ */ jsx(AdmLoading, { label: "Opening document\u2026" });
65
- if (link.isError || !link.data)
66
- return /* @__PURE__ */ jsx("p", { className: styles.note, role: "alert", children: caseErrorText(link.error, "document") });
67
- const kind = viewerKind(link.data.mimeType);
68
- if (kind === "image")
69
- return /* @__PURE__ */ jsx("div", { className: styles.frame, children: /* @__PURE__ */ jsx(
70
- "img",
71
- {
72
- className: styles.image,
73
- src: link.data.url,
74
- alt: name,
75
- referrerPolicy: "no-referrer",
76
- draggable: false,
77
- onContextMenu: (e) => e.preventDefault()
78
- }
79
- ) });
80
- if (kind === "pdf")
81
- return /* @__PURE__ */ jsx("div", { className: styles.frame, children: /* @__PURE__ */ jsx(
82
- "iframe",
83
- {
84
- className: styles.pdf,
85
- src: pdfSource(link.data.url),
86
- title: name,
87
- referrerPolicy: "no-referrer"
88
- }
89
- ) });
90
- return /* @__PURE__ */ jsx("p", { className: styles.note, children: "This file type can't be shown here." });
91
- }
92
43
  export {
93
44
  DocumentViewer
94
45
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/admin/tenants/%5Bid%5D/verification/document-viewer.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { useQuery } from '@tanstack/react-query';\nimport { Button } from '@withpotter/ui-kit';\nimport { adminCaseApi } from '@withpotter/lib/admin-case-api';\nimport type {\n CaseDocument,\n CasePerson,\n} from '@withpotter/lib/admin-case-types';\nimport { AdmLoading, Drawer } from '../../../_ui';\nimport { documentWord } from './case-checks';\nimport { caseErrorText } from './case-rules';\nimport { checkTone, checkWord, shortDate } from './case-words';\nimport {\n documentLinkKey,\n ownerName,\n pdfSource,\n viewerKind,\n} from './document-view';\nimport styles from './document-viewer.module.css';\n\n/**\n * One document a business sent, in a side panel beside the case (Square):\n * the photo or PDF, whose it is and what its check said. Close is the only\n * action; the file is shown, never offered as a download. Each opening asks\n * the server for a fresh 60-second link, and the server logs it.\n */\nexport function DocumentViewer({\n tenantId,\n document,\n people,\n onClose,\n}: {\n tenantId: string;\n document: CaseDocument | null;\n people: CasePerson[];\n onClose: () => void;\n}) {\n // Keep the last document on screen while the panel slides out.\n const [shown, setShown] = useState(document);\n\n if (document && document !== shown) setShown(document);\n\n return (\n <Drawer\n open={Boolean(document)}\n onClose={onClose}\n width={640}\n title={shown ? documentWord(shown) : 'Document'}\n subtitle={shown ? ownerName(shown, people) : undefined}\n meta={\n shown ? (\n <span\n className={styles.tone}\n data-tone={checkTone(shown.checkStatus)}\n >\n {checkWord(shown.checkStatus, null)}\n {shown.sentAt ? ` · Sent ${shortDate(shown.sentAt)}` : ''}\n </span>\n ) : undefined\n }\n footer={\n <Button variant=\"secondary\" onClick={onClose}>\n Close\n </Button>\n }\n >\n {shown && document && (\n <DocumentFile tenantId={tenantId} document={shown} />\n )}\n </Drawer>\n );\n}\n\nfunction DocumentFile({\n tenantId,\n document,\n}: {\n tenantId: string;\n document: CaseDocument;\n}) {\n // One link per opening: no retries or refetches, each would be another logged view.\n const link = useQuery({\n queryKey: documentLinkKey(tenantId, document.id),\n queryFn: () => adminCaseApi.viewDocument(tenantId, document.id),\n retry: false,\n staleTime: Infinity,\n gcTime: 0,\n refetchOnWindowFocus: false,\n refetchOnReconnect: false,\n });\n const name = documentWord(document);\n\n if (link.isLoading) return <AdmLoading label=\"Opening document…\" />;\n\n if (link.isError || !link.data)\n return (\n <p className={styles.note} role=\"alert\">\n {caseErrorText(link.error, 'document')}\n </p>\n );\n\n const kind = viewerKind(link.data.mimeType);\n\n if (kind === 'image')\n return (\n <div className={styles.frame}>\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n className={styles.image}\n src={link.data.url}\n alt={name}\n referrerPolicy=\"no-referrer\"\n draggable={false}\n onContextMenu={(e) => e.preventDefault()}\n />\n </div>\n );\n\n if (kind === 'pdf')\n return (\n <div className={styles.frame}>\n <iframe\n className={styles.pdf}\n src={pdfSource(link.data.url)}\n title={name}\n referrerPolicy=\"no-referrer\"\n />\n </div>\n );\n\n return <p className={styles.note}>This file type can't be shown here.</p>;\n}\n"],"mappings":";AAqDU,SAUF,KAVE;AAnDV,SAAS,gBAAgB;AACzB,SAAS,gBAAgB;AACzB,SAAS,cAAc;AACvB,SAAS,oBAAoB;AAK7B,SAAS,YAAY,cAAc;AACnC,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAC9B,SAAS,WAAW,WAAW,iBAAiB;AAChD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,OAAO,YAAY;AAQZ,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AAED,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,QAAQ;AAE3C,MAAI,YAAY,aAAa,MAAO,UAAS,QAAQ;AAErD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,QAAQ,QAAQ;AAAA,MACtB;AAAA,MACA,OAAO;AAAA,MACP,OAAO,QAAQ,aAAa,KAAK,IAAI;AAAA,MACrC,UAAU,QAAQ,UAAU,OAAO,MAAM,IAAI;AAAA,MAC7C,MACE,QACE;AAAA,QAAC;AAAA;AAAA,UACC,WAAW,OAAO;AAAA,UAClB,aAAW,UAAU,MAAM,WAAW;AAAA,UAErC;AAAA,sBAAU,MAAM,aAAa,IAAI;AAAA,YACjC,MAAM,SAAS,cAAW,UAAU,MAAM,MAAM,CAAC,KAAK;AAAA;AAAA;AAAA,MACzD,IACE;AAAA,MAEN,QACE,oBAAC,UAAO,SAAQ,aAAY,SAAS,SAAS,mBAE9C;AAAA,MAGD,mBAAS,YACR,oBAAC,gBAAa,UAAoB,UAAU,OAAO;AAAA;AAAA,EAEvD;AAEJ;AAEA,SAAS,aAAa;AAAA,EACpB;AAAA,EACA;AACF,GAGG;AAED,QAAM,OAAO,SAAS;AAAA,IACpB,UAAU,gBAAgB,UAAU,SAAS,EAAE;AAAA,IAC/C,SAAS,MAAM,aAAa,aAAa,UAAU,SAAS,EAAE;AAAA,IAC9D,OAAO;AAAA,IACP,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,sBAAsB;AAAA,IACtB,oBAAoB;AAAA,EACtB,CAAC;AACD,QAAM,OAAO,aAAa,QAAQ;AAElC,MAAI,KAAK,UAAW,QAAO,oBAAC,cAAW,OAAM,0BAAoB;AAEjE,MAAI,KAAK,WAAW,CAAC,KAAK;AACxB,WACE,oBAAC,OAAE,WAAW,OAAO,MAAM,MAAK,SAC7B,wBAAc,KAAK,OAAO,UAAU,GACvC;AAGJ,QAAM,OAAO,WAAW,KAAK,KAAK,QAAQ;AAE1C,MAAI,SAAS;AACX,WACE,oBAAC,SAAI,WAAW,OAAO,OAErB;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,OAAO;AAAA,QAClB,KAAK,KAAK,KAAK;AAAA,QACf,KAAK;AAAA,QACL,gBAAe;AAAA,QACf,WAAW;AAAA,QACX,eAAe,CAAC,MAAM,EAAE,eAAe;AAAA;AAAA,IACzC,GACF;AAGJ,MAAI,SAAS;AACX,WACE,oBAAC,SAAI,WAAW,OAAO,OACrB;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,OAAO;AAAA,QAClB,KAAK,UAAU,KAAK,KAAK,GAAG;AAAA,QAC5B,OAAO;AAAA,QACP,gBAAe;AAAA;AAAA,IACjB,GACF;AAGJ,SAAO,oBAAC,OAAE,WAAW,OAAO,MAAM,iDAAmC;AACvE;","names":[]}
1
+ {"version":3,"sources":["../../../../../src/admin/tenants/%5Bid%5D/verification/document-viewer.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { Button } from '@withpotter/ui-kit';\nimport type {\n CaseDocument,\n CasePerson,\n} from '@withpotter/lib/admin-case-types';\nimport { Drawer } from '../../../_ui';\nimport { documentWord } from './case-checks';\nimport { checkTone, checkWord, shortDate } from './case-words';\nimport { DocumentFile } from './document-file';\nimport { ownerName } from './document-view';\nimport styles from './document-viewer.module.css';\n\n/**\n * One document a business sent, in a side panel beside the case (Square):\n * the photo or PDF, whose it is and what its check said. Close is the only\n * action; the file is shown, never offered as a download. Each opening asks\n * the server for a fresh 60-second link, and the server logs it.\n */\nexport function DocumentViewer({\n tenantId,\n document,\n people,\n onClose,\n}: {\n tenantId: string;\n document: CaseDocument | null;\n people: CasePerson[];\n onClose: () => void;\n}) {\n // Keep the last document on screen while the panel slides out.\n const [shown, setShown] = useState(document);\n\n if (document && document !== shown) setShown(document);\n\n return (\n <Drawer\n open={Boolean(document)}\n onClose={onClose}\n width={640}\n title={shown ? documentWord(shown) : 'Document'}\n subtitle={shown ? ownerName(shown, people) : undefined}\n meta={\n shown ? (\n <span\n className={styles.tone}\n data-tone={checkTone(shown.checkStatus)}\n >\n {checkWord(shown.checkStatus, null)}\n {shown.sentAt ? ` · Sent ${shortDate(shown.sentAt)}` : ''}\n </span>\n ) : undefined\n }\n footer={\n <Button variant=\"secondary\" onClick={onClose}>\n Close\n </Button>\n }\n >\n {shown && document && (\n <DocumentFile tenantId={tenantId} document={shown} />\n )}\n </Drawer>\n );\n}\n"],"mappings":";AA8CU,SAUF,KAVE;AA5CV,SAAS,gBAAgB;AACzB,SAAS,cAAc;AAKvB,SAAS,cAAc;AACvB,SAAS,oBAAoB;AAC7B,SAAS,WAAW,WAAW,iBAAiB;AAChD,SAAS,oBAAoB;AAC7B,SAAS,iBAAiB;AAC1B,OAAO,YAAY;AAQZ,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AAED,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,QAAQ;AAE3C,MAAI,YAAY,aAAa,MAAO,UAAS,QAAQ;AAErD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,QAAQ,QAAQ;AAAA,MACtB;AAAA,MACA,OAAO;AAAA,MACP,OAAO,QAAQ,aAAa,KAAK,IAAI;AAAA,MACrC,UAAU,QAAQ,UAAU,OAAO,MAAM,IAAI;AAAA,MAC7C,MACE,QACE;AAAA,QAAC;AAAA;AAAA,UACC,WAAW,OAAO;AAAA,UAClB,aAAW,UAAU,MAAM,WAAW;AAAA,UAErC;AAAA,sBAAU,MAAM,aAAa,IAAI;AAAA,YACjC,MAAM,SAAS,cAAW,UAAU,MAAM,MAAM,CAAC,KAAK;AAAA;AAAA;AAAA,MACzD,IACE;AAAA,MAEN,QACE,oBAAC,UAAO,SAAQ,aAAY,SAAS,SAAS,mBAE9C;AAAA,MAGD,mBAAS,YACR,oBAAC,gBAAa,UAAoB,UAAU,OAAO;AAAA;AAAA,EAEvD;AAEJ;","names":[]}
@@ -45,3 +45,54 @@
45
45
  .tone[data-tone='bad'] {
46
46
  color: var(--error);
47
47
  }
48
+
49
+ /* What the merchant sent, on the case: a row of small copies to press. */
50
+ .thumbs {
51
+ display: grid;
52
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
53
+ gap: 12px;
54
+ margin: 0;
55
+ padding: 0;
56
+ list-style: none;
57
+ }
58
+
59
+ .thumb {
60
+ display: grid;
61
+ gap: 4px;
62
+ width: 100%;
63
+ padding: 0;
64
+ border: 0;
65
+ background: none;
66
+ text-align: left;
67
+ cursor: pointer;
68
+ }
69
+
70
+ .thumbFrame {
71
+ display: grid;
72
+ place-items: center;
73
+ height: 180px;
74
+ border: 1px solid var(--border-default);
75
+ border-radius: var(--adm-card-radius);
76
+ background: var(--adm-canvas);
77
+ overflow: hidden;
78
+ }
79
+
80
+ .thumb:hover .thumbFrame,
81
+ .thumb:focus-visible .thumbFrame {
82
+ border-color: var(--border-strong);
83
+ }
84
+
85
+ .thumbImage {
86
+ display: block;
87
+ width: 100%;
88
+ height: 100%;
89
+ object-fit: contain;
90
+ user-select: none;
91
+ -webkit-user-drag: none;
92
+ }
93
+
94
+ .thumbLabel {
95
+ font-size: 13px;
96
+ line-height: 20px;
97
+ color: var(--text-primary);
98
+ }
@@ -0,0 +1,33 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Panel } from "../../../_ui";
4
+ import { documentWord } from "./case-checks";
5
+ import { DocumentFile } from "./document-file";
6
+ import { ownerName } from "./document-view";
7
+ import styles from "./document-viewer.module.css";
8
+ function SentFiles({
9
+ tenantId,
10
+ documents,
11
+ people,
12
+ onOpen
13
+ }) {
14
+ const files = documents ?? [];
15
+ return /* @__PURE__ */ jsx(Panel, { title: "What the merchant sent", count: files.length, children: files.length === 0 ? /* @__PURE__ */ jsx("p", { className: styles.note, children: "No photos or documents sent." }) : /* @__PURE__ */ jsx("ul", { className: styles.thumbs, children: files.map((d) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
16
+ "button",
17
+ {
18
+ type: "button",
19
+ className: styles.thumb,
20
+ onClick: () => onOpen(d),
21
+ "aria-label": `Open ${documentWord(d)} full size`,
22
+ children: [
23
+ /* @__PURE__ */ jsx(DocumentFile, { tenantId, document: d, size: "thumb" }),
24
+ /* @__PURE__ */ jsx("span", { className: styles.thumbLabel, children: documentWord(d) }),
25
+ /* @__PURE__ */ jsx("span", { className: styles.note, children: ownerName(d, people) })
26
+ ]
27
+ }
28
+ ) }, d.id)) }) });
29
+ }
30
+ export {
31
+ SentFiles
32
+ };
33
+ //# sourceMappingURL=sent-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../src/admin/tenants/%5Bid%5D/verification/sent-files.tsx"],"sourcesContent":["'use client';\n\nimport type {\n CaseDocument,\n CasePerson,\n} from '@withpotter/lib/admin-case-types';\nimport { Panel } from '../../../_ui';\nimport { documentWord } from './case-checks';\nimport { DocumentFile } from './document-file';\nimport { ownerName } from './document-view';\nimport styles from './document-viewer.module.css';\n\n/**\n * Every photo and document the business sent, shown on the case itself, so\n * the admin looks at them before Approve or Deny. A press opens one full size\n * in the document panel.\n */\nexport function SentFiles({\n tenantId,\n documents,\n people,\n onOpen,\n}: {\n tenantId: string;\n documents: CaseDocument[] | undefined;\n people: CasePerson[];\n onOpen: (document: CaseDocument) => void;\n}) {\n const files = documents ?? [];\n\n return (\n <Panel title=\"What the merchant sent\" count={files.length}>\n {files.length === 0 ? (\n <p className={styles.note}>No photos or documents sent.</p>\n ) : (\n <ul className={styles.thumbs}>\n {files.map((d) => (\n <li key={d.id}>\n <button\n type=\"button\"\n className={styles.thumb}\n onClick={() => onOpen(d)}\n aria-label={`Open ${documentWord(d)} full size`}\n >\n <DocumentFile tenantId={tenantId} document={d} size=\"thumb\" />\n <span className={styles.thumbLabel}>{documentWord(d)}</span>\n <span className={styles.note}>{ownerName(d, people)}</span>\n </button>\n </li>\n ))}\n </ul>\n )}\n </Panel>\n );\n}\n"],"mappings":";AAiCQ,cAKM,YALN;AA3BR,SAAS,aAAa;AACtB,SAAS,oBAAoB;AAC7B,SAAS,oBAAoB;AAC7B,SAAS,iBAAiB;AAC1B,OAAO,YAAY;AAOZ,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,QAAM,QAAQ,aAAa,CAAC;AAE5B,SACE,oBAAC,SAAM,OAAM,0BAAyB,OAAO,MAAM,QAChD,gBAAM,WAAW,IAChB,oBAAC,OAAE,WAAW,OAAO,MAAM,0CAA4B,IAEvD,oBAAC,QAAG,WAAW,OAAO,QACnB,gBAAM,IAAI,CAAC,MACV,oBAAC,QACC;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,WAAW,OAAO;AAAA,MAClB,SAAS,MAAM,OAAO,CAAC;AAAA,MACvB,cAAY,QAAQ,aAAa,CAAC,CAAC;AAAA,MAEnC;AAAA,4BAAC,gBAAa,UAAoB,UAAU,GAAG,MAAK,SAAQ;AAAA,QAC5D,oBAAC,UAAK,WAAW,OAAO,YAAa,uBAAa,CAAC,GAAE;AAAA,QACrD,oBAAC,UAAK,WAAW,OAAO,MAAO,oBAAU,GAAG,MAAM,GAAE;AAAA;AAAA;AAAA,EACtD,KAVO,EAAE,EAWX,CACD,GACH,GAEJ;AAEJ;","names":[]}
@@ -12,6 +12,7 @@ import { DecisionBar } from "./decision-bar";
12
12
  import { photoOf } from "./document-view";
13
13
  import { DocumentViewer } from "./document-viewer";
14
14
  import { PersonCard } from "./person-card";
15
+ import { SentFiles } from "./sent-files";
15
16
  import { useAdminNames, useCase } from "./use-case";
16
17
  import styles from "./verification.module.css";
17
18
  function TenantVerification({ tenantId }) {
@@ -53,6 +54,15 @@ function TenantVerification({ tenantId }) {
53
54
  ] });
54
55
  return /* @__PURE__ */ jsxs("div", { className: styles.stack, children: [
55
56
  /* @__PURE__ */ jsx(CaseStatus, { data, nameOf }),
57
+ /* @__PURE__ */ jsx(
58
+ SentFiles,
59
+ {
60
+ tenantId,
61
+ documents: data.documents,
62
+ people: data.people,
63
+ onOpen: setViewing
64
+ }
65
+ ),
56
66
  data.people.length > 0 ? /* @__PURE__ */ jsx("div", { className: styles.people, children: data.people.map((p) => {
57
67
  const photo = photoOf(data.documents, p.id);
58
68
  return /* @__PURE__ */ jsx(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/admin/tenants/%5Bid%5D/verification/tenant-verification.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport type { CaseDocument } from '@withpotter/lib/admin-case-types';\nimport { Button, EmptyNote, ErrorBanner } from '@withpotter/ui-kit';\nimport { AdmLoading } from '../../../_ui';\nimport { useMoneyPermissions } from '../../../_money/permissions';\nimport { caseErrorText, isNoCase } from './case-rules';\nimport { BusinessRecord, ControlChecks } from './business-records';\nimport { CaseHistory } from './case-history';\nimport { CaseStatus } from './case-status';\nimport { DecisionBar } from './decision-bar';\nimport { photoOf } from './document-view';\nimport { DocumentViewer } from './document-viewer';\nimport { PersonCard } from './person-card';\nimport { useAdminNames, useCase } from './use-case';\nimport styles from './verification.module.css';\n\n/**\n * Merchant 360 → Verification, laid out as a record: where the case stands\n * at the top, one card per person, what the business sent and the checks\n * run on it, the history, and the decision pinned to the bottom while there\n * is one to make. A document opens in a side panel beside the record.\n */\nexport function TenantVerification({ tenantId }: { tenantId: string }) {\n const query = useCase(tenantId);\n const nameOf = useAdminNames();\n const { readComplianceHint, revealComplianceHint } = useMoneyPermissions();\n const [viewing, setViewing] = useState<CaseDocument | null>(null);\n const data = query.data;\n const viewButton = (document: CaseDocument) => (\n <Button\n size=\"sm\"\n variant=\"ghost\"\n title={revealComplianceHint}\n aria-label={\n document.kind === 'ID_PHOTO' ? 'View ID photo' : 'View certificate'\n }\n onClick={() => setViewing(document)}\n >\n View\n </Button>\n );\n\n if (query.isLoading) return <AdmLoading label=\"Loading verification…\" />;\n\n if (query.isError && isNoCase(query.error))\n return (\n <EmptyNote well>This business has not started verification.</EmptyNote>\n );\n\n if (query.isError || !data)\n return (\n <div className={styles.stack}>\n <ErrorBanner>\n {caseErrorText(query.error, 'load')}{' '}\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onClick={() => void query.refetch()}\n >\n Try again\n </Button>\n </ErrorBanner>\n {readComplianceHint && (\n <span className=\"adm-caption\">{readComplianceHint}</span>\n )}\n </div>\n );\n\n return (\n <div className={styles.stack}>\n <CaseStatus data={data} nameOf={nameOf} />\n {data.people.length > 0 ? (\n <div className={styles.people}>\n {data.people.map((p) => {\n const photo = photoOf(data.documents, p.id);\n\n return (\n <PersonCard\n key={p.id}\n tenantId={tenantId}\n person={p}\n photoButton={photo ? viewButton(photo) : null}\n />\n );\n })}\n </div>\n ) : (\n <EmptyNote well>No directors or owners added yet.</EmptyNote>\n )}\n {data.documents && (\n <BusinessRecord\n profile={data.profile}\n documents={data.documents}\n viewButton={viewButton}\n />\n )}\n <ControlChecks checks={data.checks} />\n <CaseHistory events={data.history} nameOf={nameOf} />\n <DecisionBar data={data} />\n <DocumentViewer\n tenantId={tenantId}\n document={viewing}\n people={data.people}\n onClose={() => setViewing(null)}\n />\n </div>\n );\n}\n"],"mappings":";AA+BI,cAuBI,YAvBJ;AA7BJ,SAAS,gBAAgB;AAEzB,SAAS,QAAQ,WAAW,mBAAmB;AAC/C,SAAS,kBAAkB;AAC3B,SAAS,2BAA2B;AACpC,SAAS,eAAe,gBAAgB;AACxC,SAAS,gBAAgB,qBAAqB;AAC9C,SAAS,mBAAmB;AAC5B,SAAS,kBAAkB;AAC3B,SAAS,mBAAmB;AAC5B,SAAS,eAAe;AACxB,SAAS,sBAAsB;AAC/B,SAAS,kBAAkB;AAC3B,SAAS,eAAe,eAAe;AACvC,OAAO,YAAY;AAQZ,SAAS,mBAAmB,EAAE,SAAS,GAAyB;AACrE,QAAM,QAAQ,QAAQ,QAAQ;AAC9B,QAAM,SAAS,cAAc;AAC7B,QAAM,EAAE,oBAAoB,qBAAqB,IAAI,oBAAoB;AACzE,QAAM,CAAC,SAAS,UAAU,IAAI,SAA8B,IAAI;AAChE,QAAM,OAAO,MAAM;AACnB,QAAM,aAAa,CAAC,aAClB;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,SAAQ;AAAA,MACR,OAAO;AAAA,MACP,cACE,SAAS,SAAS,aAAa,kBAAkB;AAAA,MAEnD,SAAS,MAAM,WAAW,QAAQ;AAAA,MACnC;AAAA;AAAA,EAED;AAGF,MAAI,MAAM,UAAW,QAAO,oBAAC,cAAW,OAAM,8BAAwB;AAEtE,MAAI,MAAM,WAAW,SAAS,MAAM,KAAK;AACvC,WACE,oBAAC,aAAU,MAAI,MAAC,yDAA2C;AAG/D,MAAI,MAAM,WAAW,CAAC;AACpB,WACE,qBAAC,SAAI,WAAW,OAAO,OACrB;AAAA,2BAAC,eACE;AAAA,sBAAc,MAAM,OAAO,MAAM;AAAA,QAAG;AAAA,QACrC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,SAAS,MAAM,KAAK,MAAM,QAAQ;AAAA,YACnC;AAAA;AAAA,QAED;AAAA,SACF;AAAA,MACC,sBACC,oBAAC,UAAK,WAAU,eAAe,8BAAmB;AAAA,OAEtD;AAGJ,SACE,qBAAC,SAAI,WAAW,OAAO,OACrB;AAAA,wBAAC,cAAW,MAAY,QAAgB;AAAA,IACvC,KAAK,OAAO,SAAS,IACpB,oBAAC,SAAI,WAAW,OAAO,QACpB,eAAK,OAAO,IAAI,CAAC,MAAM;AACtB,YAAM,QAAQ,QAAQ,KAAK,WAAW,EAAE,EAAE;AAE1C,aACE;AAAA,QAAC;AAAA;AAAA,UAEC;AAAA,UACA,QAAQ;AAAA,UACR,aAAa,QAAQ,WAAW,KAAK,IAAI;AAAA;AAAA,QAHpC,EAAE;AAAA,MAIT;AAAA,IAEJ,CAAC,GACH,IAEA,oBAAC,aAAU,MAAI,MAAC,+CAAiC;AAAA,IAElD,KAAK,aACJ;AAAA,MAAC;AAAA;AAAA,QACC,SAAS,KAAK;AAAA,QACd,WAAW,KAAK;AAAA,QAChB;AAAA;AAAA,IACF;AAAA,IAEF,oBAAC,iBAAc,QAAQ,KAAK,QAAQ;AAAA,IACpC,oBAAC,eAAY,QAAQ,KAAK,SAAS,QAAgB;AAAA,IACnD,oBAAC,eAAY,MAAY;AAAA,IACzB;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,UAAU;AAAA,QACV,QAAQ,KAAK;AAAA,QACb,SAAS,MAAM,WAAW,IAAI;AAAA;AAAA,IAChC;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../../../src/admin/tenants/%5Bid%5D/verification/tenant-verification.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport type { CaseDocument } from '@withpotter/lib/admin-case-types';\nimport { Button, EmptyNote, ErrorBanner } from '@withpotter/ui-kit';\nimport { AdmLoading } from '../../../_ui';\nimport { useMoneyPermissions } from '../../../_money/permissions';\nimport { caseErrorText, isNoCase } from './case-rules';\nimport { BusinessRecord, ControlChecks } from './business-records';\nimport { CaseHistory } from './case-history';\nimport { CaseStatus } from './case-status';\nimport { DecisionBar } from './decision-bar';\nimport { photoOf } from './document-view';\nimport { DocumentViewer } from './document-viewer';\nimport { PersonCard } from './person-card';\nimport { SentFiles } from './sent-files';\nimport { useAdminNames, useCase } from './use-case';\nimport styles from './verification.module.css';\n\n/**\n * Merchant 360 → Verification, laid out as a record: where the case stands\n * at the top, then every photo and document the business sent, one card per person, what the business sent and the checks\n * run on it, the history, and the decision pinned to the bottom while there\n * is one to make. A document opens in a side panel beside the record.\n */\nexport function TenantVerification({ tenantId }: { tenantId: string }) {\n const query = useCase(tenantId);\n const nameOf = useAdminNames();\n const { readComplianceHint, revealComplianceHint } = useMoneyPermissions();\n const [viewing, setViewing] = useState<CaseDocument | null>(null);\n const data = query.data;\n const viewButton = (document: CaseDocument) => (\n <Button\n size=\"sm\"\n variant=\"ghost\"\n title={revealComplianceHint}\n aria-label={\n document.kind === 'ID_PHOTO' ? 'View ID photo' : 'View certificate'\n }\n onClick={() => setViewing(document)}\n >\n View\n </Button>\n );\n\n if (query.isLoading) return <AdmLoading label=\"Loading verification…\" />;\n\n if (query.isError && isNoCase(query.error))\n return (\n <EmptyNote well>This business has not started verification.</EmptyNote>\n );\n\n if (query.isError || !data)\n return (\n <div className={styles.stack}>\n <ErrorBanner>\n {caseErrorText(query.error, 'load')}{' '}\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onClick={() => void query.refetch()}\n >\n Try again\n </Button>\n </ErrorBanner>\n {readComplianceHint && (\n <span className=\"adm-caption\">{readComplianceHint}</span>\n )}\n </div>\n );\n\n return (\n <div className={styles.stack}>\n <CaseStatus data={data} nameOf={nameOf} />\n <SentFiles\n tenantId={tenantId}\n documents={data.documents}\n people={data.people}\n onOpen={setViewing}\n />\n {data.people.length > 0 ? (\n <div className={styles.people}>\n {data.people.map((p) => {\n const photo = photoOf(data.documents, p.id);\n\n return (\n <PersonCard\n key={p.id}\n tenantId={tenantId}\n person={p}\n photoButton={photo ? viewButton(photo) : null}\n />\n );\n })}\n </div>\n ) : (\n <EmptyNote well>No directors or owners added yet.</EmptyNote>\n )}\n {data.documents && (\n <BusinessRecord\n profile={data.profile}\n documents={data.documents}\n viewButton={viewButton}\n />\n )}\n <ControlChecks checks={data.checks} />\n <CaseHistory events={data.history} nameOf={nameOf} />\n <DecisionBar data={data} />\n <DocumentViewer\n tenantId={tenantId}\n document={viewing}\n people={data.people}\n onClose={() => setViewing(null)}\n />\n </div>\n );\n}\n"],"mappings":";AAgCI,cAuBI,YAvBJ;AA9BJ,SAAS,gBAAgB;AAEzB,SAAS,QAAQ,WAAW,mBAAmB;AAC/C,SAAS,kBAAkB;AAC3B,SAAS,2BAA2B;AACpC,SAAS,eAAe,gBAAgB;AACxC,SAAS,gBAAgB,qBAAqB;AAC9C,SAAS,mBAAmB;AAC5B,SAAS,kBAAkB;AAC3B,SAAS,mBAAmB;AAC5B,SAAS,eAAe;AACxB,SAAS,sBAAsB;AAC/B,SAAS,kBAAkB;AAC3B,SAAS,iBAAiB;AAC1B,SAAS,eAAe,eAAe;AACvC,OAAO,YAAY;AAQZ,SAAS,mBAAmB,EAAE,SAAS,GAAyB;AACrE,QAAM,QAAQ,QAAQ,QAAQ;AAC9B,QAAM,SAAS,cAAc;AAC7B,QAAM,EAAE,oBAAoB,qBAAqB,IAAI,oBAAoB;AACzE,QAAM,CAAC,SAAS,UAAU,IAAI,SAA8B,IAAI;AAChE,QAAM,OAAO,MAAM;AACnB,QAAM,aAAa,CAAC,aAClB;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,SAAQ;AAAA,MACR,OAAO;AAAA,MACP,cACE,SAAS,SAAS,aAAa,kBAAkB;AAAA,MAEnD,SAAS,MAAM,WAAW,QAAQ;AAAA,MACnC;AAAA;AAAA,EAED;AAGF,MAAI,MAAM,UAAW,QAAO,oBAAC,cAAW,OAAM,8BAAwB;AAEtE,MAAI,MAAM,WAAW,SAAS,MAAM,KAAK;AACvC,WACE,oBAAC,aAAU,MAAI,MAAC,yDAA2C;AAG/D,MAAI,MAAM,WAAW,CAAC;AACpB,WACE,qBAAC,SAAI,WAAW,OAAO,OACrB;AAAA,2BAAC,eACE;AAAA,sBAAc,MAAM,OAAO,MAAM;AAAA,QAAG;AAAA,QACrC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,SAAS,MAAM,KAAK,MAAM,QAAQ;AAAA,YACnC;AAAA;AAAA,QAED;AAAA,SACF;AAAA,MACC,sBACC,oBAAC,UAAK,WAAU,eAAe,8BAAmB;AAAA,OAEtD;AAGJ,SACE,qBAAC,SAAI,WAAW,OAAO,OACrB;AAAA,wBAAC,cAAW,MAAY,QAAgB;AAAA,IACxC;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,WAAW,KAAK;AAAA,QAChB,QAAQ,KAAK;AAAA,QACb,QAAQ;AAAA;AAAA,IACV;AAAA,IACC,KAAK,OAAO,SAAS,IACpB,oBAAC,SAAI,WAAW,OAAO,QACpB,eAAK,OAAO,IAAI,CAAC,MAAM;AACtB,YAAM,QAAQ,QAAQ,KAAK,WAAW,EAAE,EAAE;AAE1C,aACE;AAAA,QAAC;AAAA;AAAA,UAEC;AAAA,UACA,QAAQ;AAAA,UACR,aAAa,QAAQ,WAAW,KAAK,IAAI;AAAA;AAAA,QAHpC,EAAE;AAAA,MAIT;AAAA,IAEJ,CAAC,GACH,IAEA,oBAAC,aAAU,MAAI,MAAC,+CAAiC;AAAA,IAElD,KAAK,aACJ;AAAA,MAAC;AAAA;AAAA,QACC,SAAS,KAAK;AAAA,QACd,WAAW,KAAK;AAAA,QAChB;AAAA;AAAA,IACF;AAAA,IAEF,oBAAC,iBAAc,QAAQ,KAAK,QAAQ;AAAA,IACpC,oBAAC,eAAY,QAAQ,KAAK,SAAS,QAAgB;AAAA,IACnD,oBAAC,eAAY,MAAY;AAAA,IACzB;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,UAAU;AAAA,QACV,QAAQ,KAAK;AAAA,QACb,SAAS,MAAM,WAAW,IAAI;AAAA;AAAA,IAChC;AAAA,KACF;AAEJ;","names":[]}
@@ -50,8 +50,8 @@ function TenantsWorkspace() {
50
50
  /* @__PURE__ */ jsx(
51
51
  PageHeader,
52
52
  {
53
- title: "Merchants",
54
- description: "Every business on the platform, how it\u2019s doing and what it sells.",
53
+ title: "Workspaces",
54
+ description: "Every workspace on the platform, how it\u2019s doing and what it sells.",
55
55
  actions: tab === "overview" && /* @__PURE__ */ jsxs("div", { className: "adm-toolbar", children: [
56
56
  /* @__PURE__ */ jsx(
57
57
  CurrencyFilter,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/tenants/tenants-workspace.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo } from 'react';\nimport {\n CurrencyFilter,\n PageHeader,\n PageTabs,\n PeriodSelector,\n customRange,\n focusCurrency,\n periodPhrase,\n rangeFromKey,\n useCurrencyParam,\n usePersistedDateRange,\n useTabParam,\n} from '../_ui';\nimport { usePlatformTenantsAnalytics } from '../hooks';\nimport { TenantsAnalytics } from './tenants-analytics';\nimport { TenantsTable } from './tenants-table';\n\nconst TABS = [\n { id: 'overview', label: 'Overview' },\n { id: 'all', label: 'All merchants' },\n] as const;\n\nconst KEYS = TABS.map((t) => t.id);\n\n/** Merchants: the period's picture on Overview, every business on its own tab. */\nexport function TenantsWorkspace() {\n const [tab, setTab] = useTabParam(KEYS);\n const {\n rangeKey,\n setRangeKey,\n customStart,\n customEnd,\n onCustomChange,\n compare,\n } = usePersistedDateRange();\n // Merchant GMV comes back once per currency; `?currency=` narrows it.\n const [currency, setCurrency] = useCurrencyParam();\n const range = useMemo(\n () =>\n rangeKey === 'custom'\n ? customRange(customStart, customEnd)\n : rangeFromKey(rangeKey),\n [rangeKey, customStart, customEnd],\n );\n const analytics = usePlatformTenantsAnalytics({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n\n const currencies = useMemo(\n () => analytics.data?.currencies ?? [],\n [analytics.data],\n );\n const span = periodPhrase(range);\n\n return (\n <div>\n <PageHeader\n title=\"Merchants\"\n description=\"Every business on the platform, how it’s doing and what it sells.\"\n actions={\n tab === 'overview' && (\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 <PageTabs tabs={TABS} active={tab} onChange={setTab} />\n\n {tab === 'overview' && (\n <TenantsAnalytics\n data={analytics.data}\n isLoading={analytics.isLoading}\n isError={analytics.isError}\n onRetry={() => void analytics.refetch()}\n currency={currency}\n focus={focusCurrency(currencies, currency)}\n caption={`${span[0].toUpperCase()}${span.slice(1)}`}\n span={span}\n compare={compare}\n />\n )}\n\n {tab === 'all' && <TenantsTable />}\n </div>\n );\n}\n"],"mappings":";AAiEY,SACE,KADF;AA/DZ,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,OACK;AACP,SAAS,mCAAmC;AAC5C,SAAS,wBAAwB;AACjC,SAAS,oBAAoB;AAE7B,MAAM,OAAO;AAAA,EACX,EAAE,IAAI,YAAY,OAAO,WAAW;AAAA,EACpC,EAAE,IAAI,OAAO,OAAO,gBAAgB;AACtC;AAEA,MAAM,OAAO,KAAK,IAAI,CAAC,MAAM,EAAE,EAAE;AAG1B,SAAS,mBAAmB;AACjC,QAAM,CAAC,KAAK,MAAM,IAAI,YAAY,IAAI;AACtC,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,UAAU,aAAa,SAAS;AAAA,EACnC;AACA,QAAM,YAAY,4BAA4B;AAAA,IAC5C,WAAW,MAAM;AAAA,IACjB,SAAS,MAAM;AAAA,EACjB,CAAC;AAED,QAAM,aAAa;AAAA,IACjB,MAAM,UAAU,MAAM,cAAc,CAAC;AAAA,IACrC,CAAC,UAAU,IAAI;AAAA,EACjB;AACA,QAAM,OAAO,aAAa,KAAK;AAE/B,SACE,qBAAC,SACC;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,aAAY;AAAA,QACZ,SACE,QAAQ,cACN,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,IAGN;AAAA,IACA,oBAAC,YAAS,MAAM,MAAM,QAAQ,KAAK,UAAU,QAAQ;AAAA,IAEpD,QAAQ,cACP;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,UAAU;AAAA,QAChB,WAAW,UAAU;AAAA,QACrB,SAAS,UAAU;AAAA,QACnB,SAAS,MAAM,KAAK,UAAU,QAAQ;AAAA,QACtC;AAAA,QACA,OAAO,cAAc,YAAY,QAAQ;AAAA,QACzC,SAAS,GAAG,KAAK,CAAC,EAAE,YAAY,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC;AAAA,QACjD;AAAA,QACA;AAAA;AAAA,IACF;AAAA,IAGD,QAAQ,SAAS,oBAAC,gBAAa;AAAA,KAClC;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/tenants/tenants-workspace.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo } from 'react';\nimport {\n CurrencyFilter,\n PageHeader,\n PageTabs,\n PeriodSelector,\n customRange,\n focusCurrency,\n periodPhrase,\n rangeFromKey,\n useCurrencyParam,\n usePersistedDateRange,\n useTabParam,\n} from '../_ui';\nimport { usePlatformTenantsAnalytics } from '../hooks';\nimport { TenantsAnalytics } from './tenants-analytics';\nimport { TenantsTable } from './tenants-table';\n\nconst TABS = [\n { id: 'overview', label: 'Overview' },\n { id: 'all', label: 'All merchants' },\n] as const;\n\nconst KEYS = TABS.map((t) => t.id);\n\n/** Merchants: the period's picture on Overview, every business on its own tab. */\nexport function TenantsWorkspace() {\n const [tab, setTab] = useTabParam(KEYS);\n const {\n rangeKey,\n setRangeKey,\n customStart,\n customEnd,\n onCustomChange,\n compare,\n } = usePersistedDateRange();\n // Merchant GMV comes back once per currency; `?currency=` narrows it.\n const [currency, setCurrency] = useCurrencyParam();\n const range = useMemo(\n () =>\n rangeKey === 'custom'\n ? customRange(customStart, customEnd)\n : rangeFromKey(rangeKey),\n [rangeKey, customStart, customEnd],\n );\n const analytics = usePlatformTenantsAnalytics({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n\n const currencies = useMemo(\n () => analytics.data?.currencies ?? [],\n [analytics.data],\n );\n const span = periodPhrase(range);\n\n return (\n <div>\n <PageHeader\n title=\"Workspaces\"\n description=\"Every workspace on the platform, how it’s doing and what it sells.\"\n actions={\n tab === 'overview' && (\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 <PageTabs tabs={TABS} active={tab} onChange={setTab} />\n\n {tab === 'overview' && (\n <TenantsAnalytics\n data={analytics.data}\n isLoading={analytics.isLoading}\n isError={analytics.isError}\n onRetry={() => void analytics.refetch()}\n currency={currency}\n focus={focusCurrency(currencies, currency)}\n caption={`${span[0].toUpperCase()}${span.slice(1)}`}\n span={span}\n compare={compare}\n />\n )}\n\n {tab === 'all' && <TenantsTable />}\n </div>\n );\n}\n"],"mappings":";AAiEY,SACE,KADF;AA/DZ,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,OACK;AACP,SAAS,mCAAmC;AAC5C,SAAS,wBAAwB;AACjC,SAAS,oBAAoB;AAE7B,MAAM,OAAO;AAAA,EACX,EAAE,IAAI,YAAY,OAAO,WAAW;AAAA,EACpC,EAAE,IAAI,OAAO,OAAO,gBAAgB;AACtC;AAEA,MAAM,OAAO,KAAK,IAAI,CAAC,MAAM,EAAE,EAAE;AAG1B,SAAS,mBAAmB;AACjC,QAAM,CAAC,KAAK,MAAM,IAAI,YAAY,IAAI;AACtC,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,UAAU,aAAa,SAAS;AAAA,EACnC;AACA,QAAM,YAAY,4BAA4B;AAAA,IAC5C,WAAW,MAAM;AAAA,IACjB,SAAS,MAAM;AAAA,EACjB,CAAC;AAED,QAAM,aAAa;AAAA,IACjB,MAAM,UAAU,MAAM,cAAc,CAAC;AAAA,IACrC,CAAC,UAAU,IAAI;AAAA,EACjB;AACA,QAAM,OAAO,aAAa,KAAK;AAE/B,SACE,qBAAC,SACC;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,aAAY;AAAA,QACZ,SACE,QAAQ,cACN,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,IAGN;AAAA,IACA,oBAAC,YAAS,MAAM,MAAM,QAAQ,KAAK,UAAU,QAAQ;AAAA,IAEpD,QAAQ,cACP;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,UAAU;AAAA,QAChB,WAAW,UAAU;AAAA,QACrB,SAAS,UAAU;AAAA,QACnB,SAAS,MAAM,KAAK,UAAU,QAAQ;AAAA,QACtC;AAAA,QACA,OAAO,cAAc,YAAY,QAAQ;AAAA,QACzC,SAAS,GAAG,KAAK,CAAC,EAAE,YAAY,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC;AAAA,QACjD;AAAA,QACA;AAAA;AAAA,IACF;AAAA,IAGD,QAAQ,SAAS,oBAAC,gBAAa;AAAA,KAClC;AAEJ;","names":[]}
@@ -1,15 +1,31 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import { PageHeader } from "../_ui";
3
+ import { useState } from "react";
4
+ import { FilterSelect, Filters, PageHeader } from "../_ui";
4
5
  import { ReviewQueue } from "./review-queue";
5
6
  import { WaitingCases } from "./waiting-cases";
6
7
  import styles from "./verification.module.css";
8
+ const SHOW = [
9
+ { value: "businesses", label: "Businesses waiting for review" },
10
+ { value: "screening", label: "Screening matches" }
11
+ ];
7
12
  function VerificationPage() {
13
+ const [show, setShow] = useState(SHOW[0].value);
8
14
  return /* @__PURE__ */ jsxs("div", { children: [
9
15
  /* @__PURE__ */ jsx(PageHeader, { title: "Verification" }),
10
16
  /* @__PURE__ */ jsxs("div", { className: styles.stack, children: [
11
- /* @__PURE__ */ jsx(WaitingCases, {}),
12
- /* @__PURE__ */ jsx(ReviewQueue, {})
17
+ /* @__PURE__ */ jsx("div", { className: styles.show, children: /* @__PURE__ */ jsx(Filters, { label: "Choose which queue to show", children: /* @__PURE__ */ jsx(
18
+ FilterSelect,
19
+ {
20
+ label: "Show",
21
+ value: show,
22
+ allValue: SHOW[0].value,
23
+ onChange: setShow,
24
+ options: SHOW,
25
+ className: styles.showSelect
26
+ }
27
+ ) }) }),
28
+ show === "businesses" ? /* @__PURE__ */ jsx(WaitingCases, {}) : /* @__PURE__ */ jsx(ReviewQueue, {})
13
29
  ] })
14
30
  ] });
15
31
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/verification/verification-page.tsx"],"sourcesContent":["'use client';\n\nimport { PageHeader } from '../_ui';\nimport { ReviewQueue } from './review-queue';\nimport { WaitingCases } from './waiting-cases';\nimport styles from './verification.module.css';\n\n/**\n * Verification: the businesses waiting for a decision on their case (each\n * opens on its merchant page), and the screening matches to clear or reject.\n */\nexport function VerificationPage() {\n return (\n <div>\n <PageHeader title=\"Verification\" />\n <div className={styles.stack}>\n <WaitingCases />\n <ReviewQueue />\n </div>\n </div>\n );\n}\n"],"mappings":";AAcM,cACA,YADA;AAZN,SAAS,kBAAkB;AAC3B,SAAS,mBAAmB;AAC5B,SAAS,oBAAoB;AAC7B,OAAO,YAAY;AAMZ,SAAS,mBAAmB;AACjC,SACE,qBAAC,SACC;AAAA,wBAAC,cAAW,OAAM,gBAAe;AAAA,IACjC,qBAAC,SAAI,WAAW,OAAO,OACrB;AAAA,0BAAC,gBAAa;AAAA,MACd,oBAAC,eAAY;AAAA,OACf;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/verification/verification-page.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { FilterSelect, Filters, PageHeader } from '../_ui';\nimport { ReviewQueue } from './review-queue';\nimport { WaitingCases } from './waiting-cases';\nimport styles from './verification.module.css';\n\nconst SHOW = [\n { value: 'businesses', label: 'Businesses waiting for review' },\n { value: 'screening', label: 'Screening matches' },\n];\n\n/**\n * Verification: one queue at a time, chosen on the right, so a long list\n * never pushes the other down the page. The businesses waiting for a decision\n * (each opens on its merchant page), or the screening matches to clear or reject.\n */\nexport function VerificationPage() {\n const [show, setShow] = useState(SHOW[0].value);\n\n return (\n <div>\n <PageHeader title=\"Verification\" />\n <div className={styles.stack}>\n <div className={styles.show}>\n <Filters label=\"Choose which queue to show\">\n <FilterSelect\n label=\"Show\"\n value={show}\n allValue={SHOW[0].value}\n onChange={setShow}\n options={SHOW}\n className={styles.showSelect}\n />\n </Filters>\n </div>\n {show === 'businesses' ? <WaitingCases /> : <ReviewQueue />}\n </div>\n </div>\n );\n}\n"],"mappings":";AAuBM,cACA,YADA;AArBN,SAAS,gBAAgB;AACzB,SAAS,cAAc,SAAS,kBAAkB;AAClD,SAAS,mBAAmB;AAC5B,SAAS,oBAAoB;AAC7B,OAAO,YAAY;AAEnB,MAAM,OAAO;AAAA,EACX,EAAE,OAAO,cAAc,OAAO,gCAAgC;AAAA,EAC9D,EAAE,OAAO,aAAa,OAAO,oBAAoB;AACnD;AAOO,SAAS,mBAAmB;AACjC,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK,CAAC,EAAE,KAAK;AAE9C,SACE,qBAAC,SACC;AAAA,wBAAC,cAAW,OAAM,gBAAe;AAAA,IACjC,qBAAC,SAAI,WAAW,OAAO,OACrB;AAAA,0BAAC,SAAI,WAAW,OAAO,MACrB,8BAAC,WAAQ,OAAM,8BACb;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,OAAO;AAAA,UACP,UAAU,KAAK,CAAC,EAAE;AAAA,UAClB,UAAU;AAAA,UACV,SAAS;AAAA,UACT,WAAW,OAAO;AAAA;AAAA,MACpB,GACF,GACF;AAAA,MACC,SAAS,eAAe,oBAAC,gBAAa,IAAK,oBAAC,eAAY;AAAA,OAC3D;AAAA,KACF;AAEJ;","names":[]}
@@ -18,3 +18,13 @@
18
18
  justify-content: flex-end;
19
19
  gap: 8px;
20
20
  }
21
+
22
+ /* The queue chooser, on the right above the list. */
23
+ .show {
24
+ display: flex;
25
+ justify-content: flex-end;
26
+ }
27
+
28
+ .show .showSelect {
29
+ width: 260px;
30
+ }
@@ -2,13 +2,13 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import Link from "next/link";
4
4
  import { useQuery } from "@tanstack/react-query";
5
- import { TableRow, Th } from "@withpotter/ui-kit";
5
+ import { Button, TableRow, Th } from "@withpotter/ui-kit";
6
6
  import { adminCaseApi } from "@withpotter/lib/admin-case-api";
7
7
  import { EMPTY, ListCard } from "../_ui/list-card";
8
8
  import list from "../_ui/list-card.module.css";
9
+ import rows from "../accounts/rows.module.css";
9
10
  import { flagWords, waited } from "../tenants/[id]/verification/case-words";
10
11
  import { caseErrorText } from "../tenants/[id]/verification/case-rules";
11
- import { WaitingCaseActions } from "./waiting-case-actions";
12
12
  const WAITING_LIST_KEY = [
13
13
  "platform-admin",
14
14
  "compliance",
@@ -57,13 +57,7 @@ function WaitingCases() {
57
57
  /* @__PURE__ */ jsx("td", { className: list.muted, children: c.peopleCount }),
58
58
  /* @__PURE__ */ jsx("td", { className: `${list.nowrap} ${list.muted}`, children: c.waitingSince ? waited(c.waitingSince) : EMPTY }),
59
59
  /* @__PURE__ */ jsx("td", { className: list.muted, children: flags.length ? flags.join(" \xB7 ") : EMPTY }),
60
- /* @__PURE__ */ jsx(
61
- WaitingCaseActions,
62
- {
63
- tenantId: c.tenantId,
64
- businessName: c.businessName
65
- }
66
- )
60
+ /* @__PURE__ */ jsx("td", { className: rows.menuCell, children: /* @__PURE__ */ jsx(Button, { size: "sm", variant: "secondary", asChild: true, children: /* @__PURE__ */ jsx(Link, { href: `/admin/tenants/${c.tenantId}?tab=verification`, children: "Review" }) }) })
67
61
  ] }, c.tenantId);
68
62
  })
69
63
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/verification/waiting-cases.tsx"],"sourcesContent":["'use client';\n\nimport Link from 'next/link';\nimport { useQuery } from '@tanstack/react-query';\nimport { TableRow, Th } from '@withpotter/ui-kit';\nimport { adminCaseApi } from '@withpotter/lib/admin-case-api';\nimport { EMPTY, ListCard } from '../_ui/list-card';\nimport list from '../_ui/list-card.module.css';\nimport { flagWords, waited } from '../tenants/[id]/verification/case-words';\nimport { caseErrorText } from '../tenants/[id]/verification/case-rules';\nimport { WaitingCaseActions } from './waiting-case-actions';\n\nexport const WAITING_LIST_KEY = [\n 'platform-admin',\n 'compliance',\n 'cases',\n 'waiting',\n];\n\nconst COLUMNS = 5;\n\n/**\n * Businesses whose checks are back and wait for a decision, oldest first.\n * Each row approves or rejects the case in place.\n */\nexport function WaitingCases() {\n const { data, isLoading, isError, error, refetch } = useQuery({\n queryKey: WAITING_LIST_KEY,\n queryFn: () => adminCaseApi.list({ status: 'WAITING_REVIEW', limit: 100 }),\n retry: false,\n });\n const items = data?.items ?? [];\n\n return (\n <ListCard\n title=\"Businesses waiting for review\"\n count={data?.total}\n columns={COLUMNS}\n minWidth={720}\n isLoading={isLoading}\n isError={isError}\n onRetry={() => void refetch()}\n errorTitle={caseErrorText(error, 'load')}\n isEmpty={items.length === 0}\n emptyTitle=\"No business is waiting for review.\"\n head={\n <tr>\n <Th>Business</Th>\n <Th>People</Th>\n <Th>Waiting</Th>\n <Th>Flags</Th>\n <Th align=\"right\">\n <span className=\"sr-only\">Actions</span>\n </Th>\n </tr>\n }\n >\n {items.map((c) => {\n const flags = flagWords(c.flags);\n\n return (\n <TableRow key={c.tenantId}>\n <td className={list.nowrap}>\n <Link\n href={`/admin/tenants/${c.tenantId}?tab=verification`}\n className=\"text-brand-500 hover:underline\"\n >\n {c.businessName ?? 'Unnamed business'}\n </Link>\n </td>\n <td className={list.muted}>{c.peopleCount}</td>\n <td className={`${list.nowrap} ${list.muted}`}>\n {c.waitingSince ? waited(c.waitingSince) : EMPTY}\n </td>\n <td className={list.muted}>\n {flags.length ? flags.join(' · ') : EMPTY}\n </td>\n <WaitingCaseActions\n tenantId={c.tenantId}\n businessName={c.businessName}\n />\n </TableRow>\n );\n })}\n </ListCard>\n );\n}\n"],"mappings":";AA8CQ,SACE,KADF;AA5CR,OAAO,UAAU;AACjB,SAAS,gBAAgB;AACzB,SAAS,UAAU,UAAU;AAC7B,SAAS,oBAAoB;AAC7B,SAAS,OAAO,gBAAgB;AAChC,OAAO,UAAU;AACjB,SAAS,WAAW,cAAc;AAClC,SAAS,qBAAqB;AAC9B,SAAS,0BAA0B;AAE5B,MAAM,mBAAmB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,MAAM,UAAU;AAMT,SAAS,eAAe;AAC7B,QAAM,EAAE,MAAM,WAAW,SAAS,OAAO,QAAQ,IAAI,SAAS;AAAA,IAC5D,UAAU;AAAA,IACV,SAAS,MAAM,aAAa,KAAK,EAAE,QAAQ,kBAAkB,OAAO,IAAI,CAAC;AAAA,IACzE,OAAO;AAAA,EACT,CAAC;AACD,QAAM,QAAQ,MAAM,SAAS,CAAC;AAE9B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAM;AAAA,MACN,OAAO,MAAM;AAAA,MACb,SAAS;AAAA,MACT,UAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA,SAAS,MAAM,KAAK,QAAQ;AAAA,MAC5B,YAAY,cAAc,OAAO,MAAM;AAAA,MACvC,SAAS,MAAM,WAAW;AAAA,MAC1B,YAAW;AAAA,MACX,MACE,qBAAC,QACC;AAAA,4BAAC,MAAG,sBAAQ;AAAA,QACZ,oBAAC,MAAG,oBAAM;AAAA,QACV,oBAAC,MAAG,qBAAO;AAAA,QACX,oBAAC,MAAG,mBAAK;AAAA,QACT,oBAAC,MAAG,OAAM,SACR,8BAAC,UAAK,WAAU,WAAU,qBAAO,GACnC;AAAA,SACF;AAAA,MAGD,gBAAM,IAAI,CAAC,MAAM;AAChB,cAAM,QAAQ,UAAU,EAAE,KAAK;AAE/B,eACE,qBAAC,YACC;AAAA,8BAAC,QAAG,WAAW,KAAK,QAClB;AAAA,YAAC;AAAA;AAAA,cACC,MAAM,kBAAkB,EAAE,QAAQ;AAAA,cAClC,WAAU;AAAA,cAET,YAAE,gBAAgB;AAAA;AAAA,UACrB,GACF;AAAA,UACA,oBAAC,QAAG,WAAW,KAAK,OAAQ,YAAE,aAAY;AAAA,UAC1C,oBAAC,QAAG,WAAW,GAAG,KAAK,MAAM,IAAI,KAAK,KAAK,IACxC,YAAE,eAAe,OAAO,EAAE,YAAY,IAAI,OAC7C;AAAA,UACA,oBAAC,QAAG,WAAW,KAAK,OACjB,gBAAM,SAAS,MAAM,KAAK,QAAK,IAAI,OACtC;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,UAAU,EAAE;AAAA,cACZ,cAAc,EAAE;AAAA;AAAA,UAClB;AAAA,aAnBa,EAAE,QAoBjB;AAAA,MAEJ,CAAC;AAAA;AAAA,EACH;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/verification/waiting-cases.tsx"],"sourcesContent":["'use client';\n\nimport Link from 'next/link';\nimport { useQuery } from '@tanstack/react-query';\nimport { Button, TableRow, Th } from '@withpotter/ui-kit';\nimport { adminCaseApi } from '@withpotter/lib/admin-case-api';\nimport { EMPTY, ListCard } from '../_ui/list-card';\nimport list from '../_ui/list-card.module.css';\nimport rows from '../accounts/rows.module.css';\nimport { flagWords, waited } from '../tenants/[id]/verification/case-words';\nimport { caseErrorText } from '../tenants/[id]/verification/case-rules';\n\nexport const WAITING_LIST_KEY = [\n 'platform-admin',\n 'compliance',\n 'cases',\n 'waiting',\n];\n\nconst COLUMNS = 5;\n\n/**\n * Businesses whose checks are back and wait for a decision, oldest first.\n * Each row opens the business's Verification tab, where the admin sees what\n * the merchant sent before Approve or Deny: there is no decision from the list.\n */\nexport function WaitingCases() {\n const { data, isLoading, isError, error, refetch } = useQuery({\n queryKey: WAITING_LIST_KEY,\n queryFn: () => adminCaseApi.list({ status: 'WAITING_REVIEW', limit: 100 }),\n retry: false,\n });\n const items = data?.items ?? [];\n\n return (\n <ListCard\n title=\"Businesses waiting for review\"\n count={data?.total}\n columns={COLUMNS}\n minWidth={720}\n isLoading={isLoading}\n isError={isError}\n onRetry={() => void refetch()}\n errorTitle={caseErrorText(error, 'load')}\n isEmpty={items.length === 0}\n emptyTitle=\"No business is waiting for review.\"\n head={\n <tr>\n <Th>Business</Th>\n <Th>People</Th>\n <Th>Waiting</Th>\n <Th>Flags</Th>\n <Th align=\"right\">\n <span className=\"sr-only\">Actions</span>\n </Th>\n </tr>\n }\n >\n {items.map((c) => {\n const flags = flagWords(c.flags);\n\n return (\n <TableRow key={c.tenantId}>\n <td className={list.nowrap}>\n <Link\n href={`/admin/tenants/${c.tenantId}?tab=verification`}\n className=\"text-brand-500 hover:underline\"\n >\n {c.businessName ?? 'Unnamed business'}\n </Link>\n </td>\n <td className={list.muted}>{c.peopleCount}</td>\n <td className={`${list.nowrap} ${list.muted}`}>\n {c.waitingSince ? waited(c.waitingSince) : EMPTY}\n </td>\n <td className={list.muted}>\n {flags.length ? flags.join(' · ') : EMPTY}\n </td>\n <td className={rows.menuCell}>\n <Button size=\"sm\" variant=\"secondary\" asChild>\n <Link href={`/admin/tenants/${c.tenantId}?tab=verification`}>\n Review\n </Link>\n </Button>\n </td>\n </TableRow>\n );\n })}\n </ListCard>\n );\n}\n"],"mappings":";AA+CQ,SACE,KADF;AA7CR,OAAO,UAAU;AACjB,SAAS,gBAAgB;AACzB,SAAS,QAAQ,UAAU,UAAU;AACrC,SAAS,oBAAoB;AAC7B,SAAS,OAAO,gBAAgB;AAChC,OAAO,UAAU;AACjB,OAAO,UAAU;AACjB,SAAS,WAAW,cAAc;AAClC,SAAS,qBAAqB;AAEvB,MAAM,mBAAmB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,MAAM,UAAU;AAOT,SAAS,eAAe;AAC7B,QAAM,EAAE,MAAM,WAAW,SAAS,OAAO,QAAQ,IAAI,SAAS;AAAA,IAC5D,UAAU;AAAA,IACV,SAAS,MAAM,aAAa,KAAK,EAAE,QAAQ,kBAAkB,OAAO,IAAI,CAAC;AAAA,IACzE,OAAO;AAAA,EACT,CAAC;AACD,QAAM,QAAQ,MAAM,SAAS,CAAC;AAE9B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAM;AAAA,MACN,OAAO,MAAM;AAAA,MACb,SAAS;AAAA,MACT,UAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA,SAAS,MAAM,KAAK,QAAQ;AAAA,MAC5B,YAAY,cAAc,OAAO,MAAM;AAAA,MACvC,SAAS,MAAM,WAAW;AAAA,MAC1B,YAAW;AAAA,MACX,MACE,qBAAC,QACC;AAAA,4BAAC,MAAG,sBAAQ;AAAA,QACZ,oBAAC,MAAG,oBAAM;AAAA,QACV,oBAAC,MAAG,qBAAO;AAAA,QACX,oBAAC,MAAG,mBAAK;AAAA,QACT,oBAAC,MAAG,OAAM,SACR,8BAAC,UAAK,WAAU,WAAU,qBAAO,GACnC;AAAA,SACF;AAAA,MAGD,gBAAM,IAAI,CAAC,MAAM;AAChB,cAAM,QAAQ,UAAU,EAAE,KAAK;AAE/B,eACE,qBAAC,YACC;AAAA,8BAAC,QAAG,WAAW,KAAK,QAClB;AAAA,YAAC;AAAA;AAAA,cACC,MAAM,kBAAkB,EAAE,QAAQ;AAAA,cAClC,WAAU;AAAA,cAET,YAAE,gBAAgB;AAAA;AAAA,UACrB,GACF;AAAA,UACA,oBAAC,QAAG,WAAW,KAAK,OAAQ,YAAE,aAAY;AAAA,UAC1C,oBAAC,QAAG,WAAW,GAAG,KAAK,MAAM,IAAI,KAAK,KAAK,IACxC,YAAE,eAAe,OAAO,EAAE,YAAY,IAAI,OAC7C;AAAA,UACA,oBAAC,QAAG,WAAW,KAAK,OACjB,gBAAM,SAAS,MAAM,KAAK,QAAK,IAAI,OACtC;AAAA,UACA,oBAAC,QAAG,WAAW,KAAK,UAClB,8BAAC,UAAO,MAAK,MAAK,SAAQ,aAAY,SAAO,MAC3C,8BAAC,QAAK,MAAM,kBAAkB,EAAE,QAAQ,qBAAqB,oBAE7D,GACF,GACF;AAAA,aAtBa,EAAE,QAuBjB;AAAA,MAEJ,CAAC;AAAA;AAAA,EACH;AAEJ;","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.61",
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.61",
35
+ "@withpotter/platform-admin": "^0.13.0-next.61",
36
+ "@withpotter/ui-kit": "^0.13.0-next.61",
37
37
  "lucide-react": "^0.562.0",
38
38
  "motion": "^12.42.2"
39
39
  },
@@ -50,7 +50,7 @@
50
50
  "typescript": "^5"
51
51
  },
52
52
  "publishConfig": {
53
- "registry": "https://registry.npmjs.org/",
53
+ "registry": "https://registry.npmjs.org",
54
54
  "access": "restricted"
55
55
  }
56
56
  }
@@ -1,68 +0,0 @@
1
- "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
3
- import { useState } from "react";
4
- import { Check, X } from "lucide-react";
5
- import {
6
- OverflowItem,
7
- OverflowMenu,
8
- useConfirm,
9
- useToast
10
- } from "@withpotter/ui-kit";
11
- import { useMoneyPermissions } from "../_money/permissions";
12
- import rows from "../accounts/rows.module.css";
13
- import { caseErrorText } from "../tenants/[id]/verification/case-rules";
14
- import { DenyDialog } from "../tenants/[id]/verification/decision-dialogs";
15
- import { useDecideCase } from "../tenants/[id]/verification/use-case";
16
- function WaitingCaseActions({
17
- tenantId,
18
- businessName
19
- }) {
20
- const { toast } = useToast();
21
- const confirm = useConfirm();
22
- const { manageComplianceHint } = useMoneyPermissions();
23
- const decide = useDecideCase(tenantId);
24
- const [rejecting, setRejecting] = useState(false);
25
- const business = businessName ?? "This business";
26
- async function approve() {
27
- const ok = await confirm({
28
- title: `Approve ${business}?`,
29
- description: ["Everyone listed is marked verified.", manageComplianceHint].filter(Boolean).join(" "),
30
- confirmLabel: "Approve"
31
- });
32
- if (!ok) return;
33
- decide.mutate(
34
- { decision: "APPROVE" },
35
- {
36
- onSuccess: () => toast({ title: `${business} approved` }),
37
- onError: (e) => toast({ title: caseErrorText(e, "decide"), variant: "destructive" })
38
- }
39
- );
40
- }
41
- return /* @__PURE__ */ jsxs("td", { className: rows.menuCell, children: [
42
- /* @__PURE__ */ jsxs(OverflowMenu, { label: `Actions for ${business}`, children: [
43
- /* @__PURE__ */ jsx(OverflowItem, { icon: /* @__PURE__ */ jsx(Check, {}), onClick: () => void approve(), children: "Approve" }),
44
- /* @__PURE__ */ jsx(
45
- OverflowItem,
46
- {
47
- icon: /* @__PURE__ */ jsx(X, {}),
48
- destructive: true,
49
- onClick: () => setRejecting(true),
50
- children: "Reject"
51
- }
52
- )
53
- ] }),
54
- /* @__PURE__ */ jsx(
55
- DenyDialog,
56
- {
57
- tenantId,
58
- business,
59
- open: rejecting,
60
- onClose: () => setRejecting(false)
61
- }
62
- )
63
- ] });
64
- }
65
- export {
66
- WaitingCaseActions
67
- };
68
- //# sourceMappingURL=waiting-case-actions.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/admin/verification/waiting-case-actions.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { Check, X } from 'lucide-react';\nimport {\n OverflowItem,\n OverflowMenu,\n useConfirm,\n useToast,\n} from '@withpotter/ui-kit';\nimport { useMoneyPermissions } from '../_money/permissions';\nimport rows from '../accounts/rows.module.css';\nimport { caseErrorText } from '../tenants/[id]/verification/case-rules';\nimport { DenyDialog } from '../tenants/[id]/verification/decision-dialogs';\nimport { useDecideCase } from '../tenants/[id]/verification/use-case';\n\n/**\n * Approve or reject a waiting business from the list, after checking its\n * BVN and NIN by hand. Reject asks for a reason and a note, the same form\n * as on the business's own page; the server checks the permission.\n */\nexport function WaitingCaseActions({\n tenantId,\n businessName,\n}: {\n tenantId: string;\n businessName: string | null;\n}) {\n const { toast } = useToast();\n const confirm = useConfirm();\n const { manageComplianceHint } = useMoneyPermissions();\n const decide = useDecideCase(tenantId);\n const [rejecting, setRejecting] = useState(false);\n const business = businessName ?? 'This business';\n\n async function approve() {\n const ok = await confirm({\n title: `Approve ${business}?`,\n description: ['Everyone listed is marked verified.', manageComplianceHint]\n .filter(Boolean)\n .join(' '),\n confirmLabel: 'Approve',\n });\n\n if (!ok) return;\n\n decide.mutate(\n { decision: 'APPROVE' },\n {\n onSuccess: () => toast({ title: `${business} approved` }),\n onError: (e) =>\n toast({ title: caseErrorText(e, 'decide'), variant: 'destructive' }),\n },\n );\n }\n\n return (\n <td className={rows.menuCell}>\n <OverflowMenu label={`Actions for ${business}`}>\n <OverflowItem icon={<Check />} onClick={() => void approve()}>\n Approve\n </OverflowItem>\n <OverflowItem\n icon={<X />}\n destructive\n onClick={() => setRejecting(true)}\n >\n Reject\n </OverflowItem>\n </OverflowMenu>\n <DenyDialog\n tenantId={tenantId}\n business={business}\n open={rejecting}\n onClose={() => setRejecting(false)}\n />\n </td>\n );\n}\n"],"mappings":";AA0DM,SACsB,KADtB;AAxDN,SAAS,gBAAgB;AACzB,SAAS,OAAO,SAAS;AACzB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,2BAA2B;AACpC,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAC9B,SAAS,kBAAkB;AAC3B,SAAS,qBAAqB;AAOvB,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AACF,GAGG;AACD,QAAM,EAAE,MAAM,IAAI,SAAS;AAC3B,QAAM,UAAU,WAAW;AAC3B,QAAM,EAAE,qBAAqB,IAAI,oBAAoB;AACrD,QAAM,SAAS,cAAc,QAAQ;AACrC,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAChD,QAAM,WAAW,gBAAgB;AAEjC,iBAAe,UAAU;AACvB,UAAM,KAAK,MAAM,QAAQ;AAAA,MACvB,OAAO,WAAW,QAAQ;AAAA,MAC1B,aAAa,CAAC,uCAAuC,oBAAoB,EACtE,OAAO,OAAO,EACd,KAAK,GAAG;AAAA,MACX,cAAc;AAAA,IAChB,CAAC;AAED,QAAI,CAAC,GAAI;AAET,WAAO;AAAA,MACL,EAAE,UAAU,UAAU;AAAA,MACtB;AAAA,QACE,WAAW,MAAM,MAAM,EAAE,OAAO,GAAG,QAAQ,YAAY,CAAC;AAAA,QACxD,SAAS,CAAC,MACR,MAAM,EAAE,OAAO,cAAc,GAAG,QAAQ,GAAG,SAAS,cAAc,CAAC;AAAA,MACvE;AAAA,IACF;AAAA,EACF;AAEA,SACE,qBAAC,QAAG,WAAW,KAAK,UAClB;AAAA,yBAAC,gBAAa,OAAO,eAAe,QAAQ,IAC1C;AAAA,0BAAC,gBAAa,MAAM,oBAAC,SAAM,GAAI,SAAS,MAAM,KAAK,QAAQ,GAAG,qBAE9D;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,MAAM,oBAAC,KAAE;AAAA,UACT,aAAW;AAAA,UACX,SAAS,MAAM,aAAa,IAAI;AAAA,UACjC;AAAA;AAAA,MAED;AAAA,OACF;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA,MAAM;AAAA,QACN,SAAS,MAAM,aAAa,KAAK;AAAA;AAAA,IACnC;AAAA,KACF;AAEJ;","names":[]}