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

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 (30) 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/accounts/accounts-workspace.js +2 -2
  7. package/dist/admin/accounts/accounts-workspace.js.map +1 -1
  8. package/dist/admin/settings/general-tab.js +2 -2
  9. package/dist/admin/settings/general-tab.js.map +1 -1
  10. package/dist/admin/tenants/[id]/verification/document-file.js +78 -0
  11. package/dist/admin/tenants/[id]/verification/document-file.js.map +1 -0
  12. package/dist/admin/tenants/[id]/verification/document-viewer.js +3 -52
  13. package/dist/admin/tenants/[id]/verification/document-viewer.js.map +1 -1
  14. package/dist/admin/tenants/[id]/verification/document-viewer.module.css +51 -0
  15. package/dist/admin/tenants/[id]/verification/sent-files.js +33 -0
  16. package/dist/admin/tenants/[id]/verification/sent-files.js.map +1 -0
  17. package/dist/admin/tenants/[id]/verification/tenant-verification.js +10 -0
  18. package/dist/admin/tenants/[id]/verification/tenant-verification.js.map +1 -1
  19. package/dist/admin/tenants/tenants-workspace.js +2 -2
  20. package/dist/admin/tenants/tenants-workspace.js.map +1 -1
  21. package/dist/admin/verification/preview-dialog.js +111 -0
  22. package/dist/admin/verification/preview-dialog.js.map +1 -0
  23. package/dist/admin/verification/verification-page.js +19 -3
  24. package/dist/admin/verification/verification-page.js.map +1 -1
  25. package/dist/admin/verification/verification.module.css +10 -0
  26. package/dist/admin/verification/waiting-cases.js +66 -48
  27. package/dist/admin/verification/waiting-cases.js.map +1 -1
  28. package/package.json +5 -5
  29. package/dist/admin/verification/waiting-case-actions.js +0 -68
  30. 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":[]}
@@ -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":[]}
@@ -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":"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","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":[]}
@@ -0,0 +1,111 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import {
5
+ Button,
6
+ ErrorBanner,
7
+ FormDialog,
8
+ useConfirm,
9
+ useToast
10
+ } from "@withpotter/ui-kit";
11
+ import { AdmLoading } from "../_ui";
12
+ import { useMoneyPermissions } from "../_money/permissions";
13
+ import { documentWord } from "../tenants/[id]/verification/case-checks";
14
+ import {
15
+ barMode,
16
+ caseErrorText
17
+ } from "../tenants/[id]/verification/case-rules";
18
+ import { DenyDialog } from "../tenants/[id]/verification/decision-dialogs";
19
+ import { DocumentFile } from "../tenants/[id]/verification/document-file";
20
+ import { ownerName } from "../tenants/[id]/verification/document-view";
21
+ import { useCase, useDecideCase } from "../tenants/[id]/verification/use-case";
22
+ import styles from "../tenants/[id]/verification/verification.module.css";
23
+ import viewer from "../tenants/[id]/verification/document-viewer.module.css";
24
+ function PreviewDialog({
25
+ tenantId,
26
+ business,
27
+ onClose
28
+ }) {
29
+ const { toast } = useToast();
30
+ const confirm = useConfirm();
31
+ const { manageComplianceHint } = useMoneyPermissions();
32
+ const query = useCase(tenantId);
33
+ const decide = useDecideCase(tenantId);
34
+ const [denying, setDenying] = useState(false);
35
+ const data = query.data;
36
+ const mode = data ? barMode(data.status) : null;
37
+ const wasDeciding = useRef(false);
38
+ useEffect(() => {
39
+ if (mode === "decide") wasDeciding.current = true;
40
+ else if (wasDeciding.current) onClose();
41
+ }, [mode, onClose]);
42
+ async function approve() {
43
+ const ok = await confirm({
44
+ title: `Approve ${business}?`,
45
+ description: "Everyone listed is marked verified.",
46
+ confirmLabel: "Approve"
47
+ });
48
+ if (!ok) return;
49
+ decide.mutate(
50
+ { decision: "APPROVE" },
51
+ {
52
+ onSuccess: () => toast({ title: `${business} approved` }),
53
+ onError: (e) => toast({ title: caseErrorText(e, "decide"), variant: "destructive" })
54
+ }
55
+ );
56
+ }
57
+ const files = data?.documents ?? [];
58
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
59
+ /* @__PURE__ */ jsxs(
60
+ FormDialog,
61
+ {
62
+ open: !denying,
63
+ onClose,
64
+ title: business,
65
+ description: "What the merchant sent",
66
+ size: "lg",
67
+ footer: /* @__PURE__ */ jsxs("div", { className: styles.footer, children: [
68
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: onClose, children: "Close" }),
69
+ mode === "decide" && /* @__PURE__ */ jsxs(Fragment, { children: [
70
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => setDenying(true), children: "Deny" }),
71
+ /* @__PURE__ */ jsx(
72
+ Button,
73
+ {
74
+ loading: decide.isPending,
75
+ onClick: () => void approve(),
76
+ children: "Approve"
77
+ }
78
+ )
79
+ ] })
80
+ ] }),
81
+ children: [
82
+ query.isLoading && /* @__PURE__ */ jsx(AdmLoading, { label: "Loading what was sent\u2026" }),
83
+ (query.isError || !query.isLoading && !data) && /* @__PURE__ */ jsx(ErrorBanner, { children: caseErrorText(query.error, "load") }),
84
+ data && files.length === 0 && /* @__PURE__ */ jsx("p", { className: viewer.note, children: "No photos or documents sent." }),
85
+ data && files.map((d) => /* @__PURE__ */ jsxs("div", { className: styles.stack, children: [
86
+ /* @__PURE__ */ jsxs("span", { className: viewer.note, children: [
87
+ documentWord(d),
88
+ " \xB7 ",
89
+ ownerName(d, data.people)
90
+ ] }),
91
+ /* @__PURE__ */ jsx(DocumentFile, { tenantId, document: d })
92
+ ] }, d.id)),
93
+ manageComplianceHint && /* @__PURE__ */ jsx("span", { className: "adm-caption", children: manageComplianceHint })
94
+ ]
95
+ }
96
+ ),
97
+ /* @__PURE__ */ jsx(
98
+ DenyDialog,
99
+ {
100
+ tenantId,
101
+ business,
102
+ open: denying,
103
+ onClose: () => setDenying(false)
104
+ }
105
+ )
106
+ ] });
107
+ }
108
+ export {
109
+ PreviewDialog
110
+ };
111
+ //# sourceMappingURL=preview-dialog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/admin/verification/preview-dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport {\n Button,\n ErrorBanner,\n FormDialog,\n useConfirm,\n useToast,\n} from \"@withpotter/ui-kit\";\nimport { AdmLoading } from \"../_ui\";\nimport { useMoneyPermissions } from \"../_money/permissions\";\nimport { documentWord } from \"../tenants/[id]/verification/case-checks\";\nimport {\n barMode,\n caseErrorText,\n} from \"../tenants/[id]/verification/case-rules\";\nimport { DenyDialog } from \"../tenants/[id]/verification/decision-dialogs\";\nimport { DocumentFile } from \"../tenants/[id]/verification/document-file\";\nimport { ownerName } from \"../tenants/[id]/verification/document-view\";\nimport { useCase, useDecideCase } from \"../tenants/[id]/verification/use-case\";\nimport styles from \"../tenants/[id]/verification/verification.module.css\";\nimport viewer from \"../tenants/[id]/verification/document-viewer.module.css\";\n\n/**\n * A popup over the Verification list: every photo and document the business\n * sent, full size, with Approve and Deny at the foot, so the admin decides\n * without leaving the list. It closes once the case is no longer waiting.\n */\nexport function PreviewDialog({\n tenantId,\n business,\n onClose,\n}: {\n tenantId: string;\n business: string;\n onClose: () => void;\n}) {\n const { toast } = useToast();\n const confirm = useConfirm();\n const { manageComplianceHint } = useMoneyPermissions();\n const query = useCase(tenantId);\n const decide = useDecideCase(tenantId);\n const [denying, setDenying] = useState(false);\n const data = query.data;\n const mode = data ? barMode(data.status) : null;\n const wasDeciding = useRef(false);\n\n useEffect(() => {\n if (mode === \"decide\") wasDeciding.current = true;\n else if (wasDeciding.current) onClose();\n }, [mode, onClose]);\n\n async function approve() {\n const ok = await confirm({\n title: `Approve ${business}?`,\n description: \"Everyone listed is marked verified.\",\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 const files = data?.documents ?? [];\n\n return (\n <>\n <FormDialog\n open={!denying}\n onClose={onClose}\n title={business}\n description=\"What the merchant sent\"\n size=\"lg\"\n footer={\n <div className={styles.footer}>\n <Button variant=\"secondary\" onClick={onClose}>\n Close\n </Button>\n {mode === \"decide\" && (\n <>\n <Button variant=\"secondary\" onClick={() => setDenying(true)}>\n Deny\n </Button>\n <Button\n loading={decide.isPending}\n onClick={() => void approve()}\n >\n Approve\n </Button>\n </>\n )}\n </div>\n }\n >\n {query.isLoading && <AdmLoading label=\"Loading what was sent…\" />}\n {(query.isError || (!query.isLoading && !data)) && (\n <ErrorBanner>{caseErrorText(query.error, \"load\")}</ErrorBanner>\n )}\n {data && files.length === 0 && (\n <p className={viewer.note}>No photos or documents sent.</p>\n )}\n {data &&\n files.map((d) => (\n <div key={d.id} className={styles.stack}>\n <span className={viewer.note}>\n {documentWord(d)} · {ownerName(d, data.people)}\n </span>\n <DocumentFile tenantId={tenantId} document={d} />\n </div>\n ))}\n {manageComplianceHint && (\n <span className=\"adm-caption\">{manageComplianceHint}</span>\n )}\n </FormDialog>\n <DenyDialog\n tenantId={tenantId}\n business={business}\n open={denying}\n onClose={() => setDenying(false)}\n />\n </>\n );\n}\n"],"mappings":"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","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
+ }
@@ -1,14 +1,16 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import Link from "next/link";
4
+ import { useState } from "react";
4
5
  import { useQuery } from "@tanstack/react-query";
5
- import { TableRow, Th } from "@withpotter/ui-kit";
6
+ import { Button, TableRow, Th } from "@withpotter/ui-kit";
6
7
  import { adminCaseApi } from "@withpotter/lib/admin-case-api";
7
8
  import { EMPTY, ListCard } from "../_ui/list-card";
8
9
  import list from "../_ui/list-card.module.css";
10
+ import rows from "../accounts/rows.module.css";
9
11
  import { flagWords, waited } from "../tenants/[id]/verification/case-words";
10
12
  import { caseErrorText } from "../tenants/[id]/verification/case-rules";
11
- import { WaitingCaseActions } from "./waiting-case-actions";
13
+ import { PreviewDialog } from "./preview-dialog";
12
14
  const WAITING_LIST_KEY = [
13
15
  "platform-admin",
14
16
  "compliance",
@@ -23,51 +25,67 @@ function WaitingCases() {
23
25
  retry: false
24
26
  });
25
27
  const items = data?.items ?? [];
26
- return /* @__PURE__ */ jsx(
27
- ListCard,
28
- {
29
- title: "Businesses waiting for review",
30
- count: data?.total,
31
- columns: COLUMNS,
32
- minWidth: 720,
33
- isLoading,
34
- isError,
35
- onRetry: () => void refetch(),
36
- errorTitle: caseErrorText(error, "load"),
37
- isEmpty: items.length === 0,
38
- emptyTitle: "No business is waiting for review.",
39
- head: /* @__PURE__ */ jsxs("tr", { children: [
40
- /* @__PURE__ */ jsx(Th, { children: "Business" }),
41
- /* @__PURE__ */ jsx(Th, { children: "People" }),
42
- /* @__PURE__ */ jsx(Th, { children: "Waiting" }),
43
- /* @__PURE__ */ jsx(Th, { children: "Flags" }),
44
- /* @__PURE__ */ jsx(Th, { align: "right", children: /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Actions" }) })
45
- ] }),
46
- children: items.map((c) => {
47
- const flags = flagWords(c.flags);
48
- return /* @__PURE__ */ jsxs(TableRow, { children: [
49
- /* @__PURE__ */ jsx("td", { className: list.nowrap, children: /* @__PURE__ */ jsx(
50
- Link,
51
- {
52
- href: `/admin/tenants/${c.tenantId}?tab=verification`,
53
- className: "text-brand-500 hover:underline",
54
- children: c.businessName ?? "Unnamed business"
55
- }
56
- ) }),
57
- /* @__PURE__ */ jsx("td", { className: list.muted, children: c.peopleCount }),
58
- /* @__PURE__ */ jsx("td", { className: `${list.nowrap} ${list.muted}`, children: c.waitingSince ? waited(c.waitingSince) : EMPTY }),
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
- )
67
- ] }, c.tenantId);
68
- })
69
- }
70
- );
28
+ const [previewing, setPreviewing] = useState(null);
29
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
30
+ /* @__PURE__ */ jsx(
31
+ ListCard,
32
+ {
33
+ title: "Businesses waiting for review",
34
+ count: data?.total,
35
+ columns: COLUMNS,
36
+ minWidth: 720,
37
+ isLoading,
38
+ isError,
39
+ onRetry: () => void refetch(),
40
+ errorTitle: caseErrorText(error, "load"),
41
+ isEmpty: items.length === 0,
42
+ emptyTitle: "No business is waiting for review.",
43
+ head: /* @__PURE__ */ jsxs("tr", { children: [
44
+ /* @__PURE__ */ jsx(Th, { children: "Business" }),
45
+ /* @__PURE__ */ jsx(Th, { children: "People" }),
46
+ /* @__PURE__ */ jsx(Th, { children: "Waiting" }),
47
+ /* @__PURE__ */ jsx(Th, { children: "Flags" }),
48
+ /* @__PURE__ */ jsx(Th, { align: "right", children: /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Actions" }) })
49
+ ] }),
50
+ children: items.map((c) => {
51
+ const flags = flagWords(c.flags);
52
+ return /* @__PURE__ */ jsxs(TableRow, { children: [
53
+ /* @__PURE__ */ jsx("td", { className: list.nowrap, children: /* @__PURE__ */ jsx(
54
+ Link,
55
+ {
56
+ href: `/admin/tenants/${c.tenantId}?tab=verification`,
57
+ className: "text-brand-500 hover:underline",
58
+ children: c.businessName ?? "Unnamed business"
59
+ }
60
+ ) }),
61
+ /* @__PURE__ */ jsx("td", { className: list.muted, children: c.peopleCount }),
62
+ /* @__PURE__ */ jsx("td", { className: `${list.nowrap} ${list.muted}`, children: c.waitingSince ? waited(c.waitingSince) : EMPTY }),
63
+ /* @__PURE__ */ jsx("td", { className: list.muted, children: flags.length ? flags.join(" \xB7 ") : EMPTY }),
64
+ /* @__PURE__ */ jsx("td", { className: rows.menuCell, children: /* @__PURE__ */ jsx(
65
+ Button,
66
+ {
67
+ size: "sm",
68
+ variant: "secondary",
69
+ onClick: () => setPreviewing({
70
+ id: c.tenantId,
71
+ name: c.businessName ?? "Unnamed business"
72
+ }),
73
+ children: "Preview"
74
+ }
75
+ ) })
76
+ ] }, c.tenantId);
77
+ })
78
+ }
79
+ ),
80
+ previewing && /* @__PURE__ */ jsx(
81
+ PreviewDialog,
82
+ {
83
+ tenantId: previewing.id,
84
+ business: previewing.name,
85
+ onClose: () => setPreviewing(null)
86
+ }
87
+ )
88
+ ] });
71
89
  }
72
90
  export {
73
91
  WAITING_LIST_KEY,
@@ -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 { useState } from \"react\";\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\";\nimport { PreviewDialog } from \"./preview-dialog\";\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 * Preview opens a popup with what the merchant sent and Approve / Deny; the\n * business name opens its full Verification tab.\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 const [previewing, setPreviewing] = useState<{\n id: string;\n name: string;\n } | null>(null);\n\n return (\n <>\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\n size=\"sm\"\n variant=\"secondary\"\n onClick={() =>\n setPreviewing({\n id: c.tenantId,\n name: c.businessName ?? \"Unnamed business\",\n })\n }\n >\n Preview\n </Button>\n </td>\n </TableRow>\n );\n })}\n </ListCard>\n {previewing && (\n <PreviewDialog\n tenantId={previewing.id}\n business={previewing.name}\n onClose={() => setPreviewing(null)}\n />\n )}\n </>\n );\n}\n"],"mappings":";AAyCI,mBAcQ,KADF,YAbN;AAvCJ,OAAO,UAAU;AACjB,SAAS,gBAAgB;AACzB,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;AAC9B,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;AAC9B,QAAM,CAAC,YAAY,aAAa,IAAI,SAG1B,IAAI;AAEd,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,OAAO,MAAM;AAAA,QACb,SAAS;AAAA,QACT,UAAU;AAAA,QACV;AAAA,QACA;AAAA,QACA,SAAS,MAAM,KAAK,QAAQ;AAAA,QAC5B,YAAY,cAAc,OAAO,MAAM;AAAA,QACvC,SAAS,MAAM,WAAW;AAAA,QAC1B,YAAW;AAAA,QACX,MACE,qBAAC,QACC;AAAA,8BAAC,MAAG,sBAAQ;AAAA,UACZ,oBAAC,MAAG,oBAAM;AAAA,UACV,oBAAC,MAAG,qBAAO;AAAA,UACX,oBAAC,MAAG,mBAAK;AAAA,UACT,oBAAC,MAAG,OAAM,SACR,8BAAC,UAAK,WAAU,WAAU,qBAAO,GACnC;AAAA,WACF;AAAA,QAGD,gBAAM,IAAI,CAAC,MAAM;AAChB,gBAAM,QAAQ,UAAU,EAAE,KAAK;AAE/B,iBACE,qBAAC,YACC;AAAA,gCAAC,QAAG,WAAW,KAAK,QAClB;AAAA,cAAC;AAAA;AAAA,gBACC,MAAM,kBAAkB,EAAE,QAAQ;AAAA,gBAClC,WAAU;AAAA,gBAET,YAAE,gBAAgB;AAAA;AAAA,YACrB,GACF;AAAA,YACA,oBAAC,QAAG,WAAW,KAAK,OAAQ,YAAE,aAAY;AAAA,YAC1C,oBAAC,QAAG,WAAW,GAAG,KAAK,MAAM,IAAI,KAAK,KAAK,IACxC,YAAE,eAAe,OAAO,EAAE,YAAY,IAAI,OAC7C;AAAA,YACA,oBAAC,QAAG,WAAW,KAAK,OACjB,gBAAM,SAAS,MAAM,KAAK,QAAK,IAAI,OACtC;AAAA,YACA,oBAAC,QAAG,WAAW,KAAK,UAClB;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,SAAQ;AAAA,gBACR,SAAS,MACP,cAAc;AAAA,kBACZ,IAAI,EAAE;AAAA,kBACN,MAAM,EAAE,gBAAgB;AAAA,gBAC1B,CAAC;AAAA,gBAEJ;AAAA;AAAA,YAED,GACF;AAAA,eA7Ba,EAAE,QA8BjB;AAAA,QAEJ,CAAC;AAAA;AAAA,IACH;AAAA,IACC,cACC;AAAA,MAAC;AAAA;AAAA,QACC,UAAU,WAAW;AAAA,QACrB,UAAU,WAAW;AAAA,QACrB,SAAS,MAAM,cAAc,IAAI;AAAA;AAAA,IACnC;AAAA,KAEJ;AAEJ;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@withpotter/pages-admin",
3
- "version": "0.13.0-next.59",
3
+ "version": "0.13.0-next.62",
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.59",
35
- "@withpotter/platform-admin": "^0.13.0-next.59",
36
- "@withpotter/ui-kit": "^0.13.0-next.59",
34
+ "@withpotter/lib": "^0.13.0-next.62",
35
+ "@withpotter/platform-admin": "^0.13.0-next.62",
36
+ "@withpotter/ui-kit": "^0.13.0-next.62",
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":[]}