@withpotter/pages-admin 0.13.0-next.103 → 0.13.0-next.105

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 (65) hide show
  1. package/dist/admin/_overview/activation-panel.js +6 -6
  2. package/dist/admin/_overview/activation-panel.js.map +1 -1
  3. package/dist/admin/_overview/attention-groups.js +8 -8
  4. package/dist/admin/_overview/attention-groups.js.map +1 -1
  5. package/dist/admin/_overview/attention-panel.js +3 -3
  6. package/dist/admin/_overview/attention-panel.js.map +1 -1
  7. package/dist/admin/_overview/control-room.js +9 -9
  8. package/dist/admin/_overview/control-room.js.map +1 -1
  9. package/dist/admin/_overview/kpi-row.js +7 -7
  10. package/dist/admin/_overview/kpi-row.js.map +1 -1
  11. package/dist/admin/_overview/leaders-panel.js +1 -1
  12. package/dist/admin/_overview/leaders-panel.js.map +1 -1
  13. package/dist/admin/_overview/portfolio-panel.js +5 -5
  14. package/dist/admin/_overview/portfolio-panel.js.map +1 -1
  15. package/dist/admin/_overview/position-panel.js +6 -6
  16. package/dist/admin/_overview/position-panel.js.map +1 -1
  17. package/dist/admin/_pos/olive-balance.js +40 -0
  18. package/dist/admin/_pos/olive-balance.js.map +1 -0
  19. package/dist/admin/_pos/olive-state.js +23 -0
  20. package/dist/admin/_pos/olive-state.js.map +1 -0
  21. package/dist/admin/_pos/pos-rules.js +2 -0
  22. package/dist/admin/_pos/pos-rules.js.map +1 -1
  23. package/dist/admin/_pos/tenant-operators.js +36 -46
  24. package/dist/admin/_pos/tenant-operators.js.map +1 -1
  25. package/dist/admin/_pos/tenant-outlets.js +77 -0
  26. package/dist/admin/_pos/tenant-outlets.js.map +1 -0
  27. package/dist/admin/_pos/tenant-requests.js +64 -0
  28. package/dist/admin/_pos/tenant-requests.js.map +1 -0
  29. package/dist/admin/_pos/tenant-transactions.js +145 -0
  30. package/dist/admin/_pos/tenant-transactions.js.map +1 -0
  31. package/dist/admin/_pos/use-pos.js +60 -11
  32. package/dist/admin/_pos/use-pos.js.map +1 -1
  33. package/dist/admin/_ui/data-table.js +1 -1
  34. package/dist/admin/_ui/data-table.js.map +1 -1
  35. package/dist/admin/_ui/list-card.js +1 -1
  36. package/dist/admin/_ui/list-card.js.map +1 -1
  37. package/dist/admin/_ui/trend.js +2 -2
  38. package/dist/admin/_ui/trend.js.map +1 -1
  39. package/dist/admin/page.js +6 -6
  40. package/dist/admin/page.js.map +1 -1
  41. package/dist/admin/payout-tiers/_tier-dialog.js +31 -21
  42. package/dist/admin/payout-tiers/_tier-dialog.js.map +1 -1
  43. package/dist/admin/payout-tiers/requirement-labels.js +70 -0
  44. package/dist/admin/payout-tiers/requirement-labels.js.map +1 -0
  45. package/dist/admin/payout-tiers/tier-preview-result.js +10 -9
  46. package/dist/admin/payout-tiers/tier-preview-result.js.map +1 -1
  47. package/dist/admin/payout-tiers/tiers-table.js +6 -7
  48. package/dist/admin/payout-tiers/tiers-table.js.map +1 -1
  49. package/dist/admin/payout-tiers/tiers-workspace.js +2 -2
  50. package/dist/admin/payout-tiers/tiers-workspace.js.map +1 -1
  51. package/dist/admin/revenue/cohort-panel.js +1 -1
  52. package/dist/admin/revenue/cohort-panel.js.map +1 -1
  53. package/dist/admin/revenue/plan-mix-panel.js +2 -2
  54. package/dist/admin/revenue/plan-mix-panel.js.map +1 -1
  55. package/dist/admin/revenue/revenue-kpis.js +3 -3
  56. package/dist/admin/revenue/revenue-kpis.js.map +1 -1
  57. package/dist/admin/revenue/revenue-overview.js +1 -1
  58. package/dist/admin/revenue/revenue-overview.js.map +1 -1
  59. package/dist/admin/revenue/subscriptions-view.js +3 -3
  60. package/dist/admin/revenue/subscriptions-view.js.map +1 -1
  61. package/dist/admin/revenue/top-tenants-panel.js +3 -3
  62. package/dist/admin/revenue/top-tenants-panel.js.map +1 -1
  63. package/dist/admin/tenants/[id]/tenant-pos.js +13 -2
  64. package/dist/admin/tenants/[id]/tenant-pos.js.map +1 -1
  65. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/_pos/use-pos.ts"],"sourcesContent":["'use client';\n\nimport {\n keepPreviousData,\n useMutation,\n useQueries,\n useQuery,\n useQueryClient,\n} from '@tanstack/react-query';\nimport { adminPosApi, type TerminalAct } from '@withpotter/lib/admin-pos-api';\nimport type {\n ShiftFilters,\n TerminalFilters,\n} from '@withpotter/lib/admin-pos-query';\nimport type { PosWindow } from '@withpotter/lib/admin-pos-types';\nimport { isForbidden, isNotFound } from './pos-rules';\n\n/** Every POS query sits under this, so one action refreshes them all. */\nexport const POS_KEY = ['platform-admin', 'pos'] as const;\n\nconst key = (...parts: unknown[]) => [...POS_KEY, ...parts];\n\n/** A 403 or 404 will not change on a retry; anything else gets one more go. */\nconst retry = (count: number, error: unknown) =>\n !isForbidden(error) && !isNotFound(error) && count < 1;\n\nexport function usePosSummary(w: PosWindow, enabled = true) {\n return useQuery({\n queryKey: key('summary', w),\n queryFn: () => adminPosApi.summary(w),\n enabled,\n retry,\n });\n}\n\nexport function usePosTerminals(filters: TerminalFilters, enabled = true) {\n return useQuery({\n queryKey: key('terminals', filters),\n queryFn: () => adminPosApi.terminals(filters),\n placeholderData: keepPreviousData,\n enabled,\n retry,\n });\n}\n\nexport function usePosTerminal(id: string, w: PosWindow, enabled = true) {\n return useQuery({\n queryKey: key('terminal', id, w),\n queryFn: () => adminPosApi.terminal(id, w),\n enabled,\n retry,\n });\n}\n\nexport function usePosHistory(id: string, enabled = true) {\n return useQuery({\n queryKey: key('history', id),\n queryFn: () => adminPosApi.history(id),\n enabled,\n retry,\n });\n}\n\nexport function usePosShifts(filters: ShiftFilters, enabled = true) {\n return useQuery({\n queryKey: key('shifts', filters),\n queryFn: () => adminPosApi.shifts(filters),\n placeholderData: keepPreviousData,\n enabled,\n retry,\n });\n}\n\n/**\n * A business's operators, gathered from its terminals: the admin API lists\n * operators per terminal, not per business, so each live terminal is asked\n * once (the same query its own page uses, so the cache is shared).\n */\nexport function useTenantOperators(\n terminalIds: string[],\n w: PosWindow,\n enabled = true,\n) {\n return useQueries({\n queries: terminalIds.map((id) => ({\n queryKey: key('terminal', id, w),\n queryFn: () => adminPosApi.terminal(id, w),\n enabled,\n retry,\n })),\n });\n}\n\nexport function useTenantPos(tenantId: string, w: PosWindow, enabled = true) {\n return useQuery({\n queryKey: key('tenant', tenantId, w),\n queryFn: () => adminPosApi.tenant(tenantId, w),\n enabled,\n retry,\n });\n}\n\n/**\n * Every action can move the figures, the lists and a business's tab, so\n * all of POS is refetched after it, a 409 included: that means someone\n * else acted first. The refetch is not awaited, so the caller's own\n * `onSuccess` (the toast, closing the form) is not held back by it.\n */\nfunction usePosWrite<V, R>(fn: (v: V) => Promise<R>) {\n const client = useQueryClient();\n\n return useMutation({\n mutationFn: fn,\n onSettled: () => {\n void client.invalidateQueries({ queryKey: POS_KEY });\n },\n });\n}\n\nexport const useTerminalStatus = () =>\n usePosWrite((v: { id: string; act: TerminalAct; reason: string }) =>\n adminPosApi.setTerminalStatus(v.id, v.act, v.reason),\n );\n\nexport const useCloseShift = () =>\n usePosWrite((v: { id: string; reason: string }) =>\n adminPosApi.closeShift(v.id, v.reason),\n );\n"],"mappings":";AAEA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,mBAAqC;AAM9C,SAAS,aAAa,kBAAkB;AAGjC,MAAM,UAAU,CAAC,kBAAkB,KAAK;AAE/C,MAAM,MAAM,IAAI,UAAqB,CAAC,GAAG,SAAS,GAAG,KAAK;AAG1D,MAAM,QAAQ,CAAC,OAAe,UAC5B,CAAC,YAAY,KAAK,KAAK,CAAC,WAAW,KAAK,KAAK,QAAQ;AAEhD,SAAS,cAAc,GAAc,UAAU,MAAM;AAC1D,SAAO,SAAS;AAAA,IACd,UAAU,IAAI,WAAW,CAAC;AAAA,IAC1B,SAAS,MAAM,YAAY,QAAQ,CAAC;AAAA,IACpC;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEO,SAAS,gBAAgB,SAA0B,UAAU,MAAM;AACxE,SAAO,SAAS;AAAA,IACd,UAAU,IAAI,aAAa,OAAO;AAAA,IAClC,SAAS,MAAM,YAAY,UAAU,OAAO;AAAA,IAC5C,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEO,SAAS,eAAe,IAAY,GAAc,UAAU,MAAM;AACvE,SAAO,SAAS;AAAA,IACd,UAAU,IAAI,YAAY,IAAI,CAAC;AAAA,IAC/B,SAAS,MAAM,YAAY,SAAS,IAAI,CAAC;AAAA,IACzC;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEO,SAAS,cAAc,IAAY,UAAU,MAAM;AACxD,SAAO,SAAS;AAAA,IACd,UAAU,IAAI,WAAW,EAAE;AAAA,IAC3B,SAAS,MAAM,YAAY,QAAQ,EAAE;AAAA,IACrC;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEO,SAAS,aAAa,SAAuB,UAAU,MAAM;AAClE,SAAO,SAAS;AAAA,IACd,UAAU,IAAI,UAAU,OAAO;AAAA,IAC/B,SAAS,MAAM,YAAY,OAAO,OAAO;AAAA,IACzC,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAOO,SAAS,mBACd,aACA,GACA,UAAU,MACV;AACA,SAAO,WAAW;AAAA,IAChB,SAAS,YAAY,IAAI,CAAC,QAAQ;AAAA,MAChC,UAAU,IAAI,YAAY,IAAI,CAAC;AAAA,MAC/B,SAAS,MAAM,YAAY,SAAS,IAAI,CAAC;AAAA,MACzC;AAAA,MACA;AAAA,IACF,EAAE;AAAA,EACJ,CAAC;AACH;AAEO,SAAS,aAAa,UAAkB,GAAc,UAAU,MAAM;AAC3E,SAAO,SAAS;AAAA,IACd,UAAU,IAAI,UAAU,UAAU,CAAC;AAAA,IACnC,SAAS,MAAM,YAAY,OAAO,UAAU,CAAC;AAAA,IAC7C;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAQA,SAAS,YAAkB,IAA0B;AACnD,QAAM,SAAS,eAAe;AAE9B,SAAO,YAAY;AAAA,IACjB,YAAY;AAAA,IACZ,WAAW,MAAM;AACf,WAAK,OAAO,kBAAkB,EAAE,UAAU,QAAQ,CAAC;AAAA,IACrD;AAAA,EACF,CAAC;AACH;AAEO,MAAM,oBAAoB,MAC/B;AAAA,EAAY,CAAC,MACX,YAAY,kBAAkB,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM;AACrD;AAEK,MAAM,gBAAgB,MAC3B;AAAA,EAAY,CAAC,MACX,YAAY,WAAW,EAAE,IAAI,EAAE,MAAM;AACvC;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/_pos/use-pos.ts"],"sourcesContent":["'use client';\n\nimport {\n keepPreviousData,\n useMutation,\n useInfiniteQuery,\n useQuery,\n useQueryClient,\n} from '@tanstack/react-query';\nimport { adminPosApi, type TerminalAct } from '@withpotter/lib/admin-pos-api';\nimport type {\n ShiftFilters,\n TerminalFilters,\n} from '@withpotter/lib/admin-pos-query';\nimport type { PosWindow } from '@withpotter/lib/admin-pos-types';\nimport { isForbidden, isNotFound, isNotLinked } from './pos-rules';\n\n/** Every POS query sits under this, so one action refreshes them all. */\nexport const POS_KEY = ['platform-admin', 'pos'] as const;\n\nconst key = (...parts: unknown[]) => [...POS_KEY, ...parts];\n\n/** A 403 or 404 will not change on a retry; anything else gets one more go. */\nconst retry = (count: number, error: unknown) =>\n !isForbidden(error) && !isNotFound(error) && count < 1;\n\nexport function usePosSummary(w: PosWindow, enabled = true) {\n return useQuery({\n queryKey: key('summary', w),\n queryFn: () => adminPosApi.summary(w),\n enabled,\n retry,\n });\n}\n\nexport function usePosTerminals(filters: TerminalFilters, enabled = true) {\n return useQuery({\n queryKey: key('terminals', filters),\n queryFn: () => adminPosApi.terminals(filters),\n placeholderData: keepPreviousData,\n enabled,\n retry,\n });\n}\n\nexport function usePosTerminal(id: string, w: PosWindow, enabled = true) {\n return useQuery({\n queryKey: key('terminal', id, w),\n queryFn: () => adminPosApi.terminal(id, w),\n enabled,\n retry,\n });\n}\n\nexport function usePosHistory(id: string, enabled = true) {\n return useQuery({\n queryKey: key('history', id),\n queryFn: () => adminPosApi.history(id),\n enabled,\n retry,\n });\n}\n\nexport function usePosShifts(filters: ShiftFilters, enabled = true) {\n return useQuery({\n queryKey: key('shifts', filters),\n queryFn: () => adminPosApi.shifts(filters),\n placeholderData: keepPreviousData,\n enabled,\n retry,\n });\n}\n\nexport function useTenantRoster(tenantId: string, enabled = true) {\n return useQuery({\n queryKey: key('roster', tenantId),\n queryFn: () => adminPosApi.roster(tenantId),\n enabled,\n retry,\n });\n}\n\nexport function useTenantTillRequests(tenantId: string, enabled = true) {\n return useQuery({\n queryKey: key('till-requests', tenantId),\n queryFn: () => adminPosApi.tillRequests(tenantId),\n enabled,\n retry,\n });\n}\n\n/** Card payments, newest first, a page at a time. */\nexport function useTenantPayments(tenantId: string, enabled = true) {\n return useInfiniteQuery({\n queryKey: key('payments', tenantId),\n queryFn: ({ pageParam }) =>\n adminPosApi.payments({ tenantId, cursor: pageParam, limit: 25 }),\n initialPageParam: undefined as string | undefined,\n getNextPageParam: (last) => last.nextCursor ?? undefined,\n enabled,\n retry,\n });\n}\n\n/**\n * What the business sees from Olive. Read live and audited on the server, so these are fetched only\n * when their tab or card is on screen, never in the background, and a 400 (Olive not linked) is not retried.\n */\nconst oliveRetry = (count: number, error: unknown) =>\n !isForbidden(error) && !isNotFound(error) && !isNotLinked(error) && count < 1;\n\nexport function useOliveAccount(tenantId: string, enabled = true) {\n return useQuery({\n queryKey: key('olive', tenantId, 'account'),\n queryFn: () => adminPosApi.olive.account(tenantId),\n enabled,\n retry: oliveRetry,\n staleTime: 60_000,\n });\n}\n\nexport function useOliveAccountTransactions(tenantId: string, enabled = true) {\n return useQuery({\n queryKey: key('olive', tenantId, 'account-transactions'),\n queryFn: () => adminPosApi.olive.accountTransactions(tenantId),\n enabled,\n retry: oliveRetry,\n staleTime: 60_000,\n });\n}\n\nexport function useOliveOutlets(tenantId: string, enabled = true) {\n return useQuery({\n queryKey: key('olive', tenantId, 'outlets'),\n queryFn: () => adminPosApi.olive.outlets(tenantId),\n enabled,\n retry: oliveRetry,\n staleTime: 60_000,\n });\n}\n\nexport function useTenantPos(tenantId: string, w: PosWindow, enabled = true) {\n return useQuery({\n queryKey: key('tenant', tenantId, w),\n queryFn: () => adminPosApi.tenant(tenantId, w),\n enabled,\n retry,\n });\n}\n\n/**\n * Every action can move the figures, the lists and a business's tab, so\n * all of POS is refetched after it, a 409 included: that means someone\n * else acted first. The refetch is not awaited, so the caller's own\n * `onSuccess` (the toast, closing the form) is not held back by it.\n */\nfunction usePosWrite<V, R>(fn: (v: V) => Promise<R>) {\n const client = useQueryClient();\n\n return useMutation({\n mutationFn: fn,\n onSettled: () => {\n void client.invalidateQueries({ queryKey: POS_KEY });\n },\n });\n}\n\nexport const useTerminalStatus = () =>\n usePosWrite((v: { id: string; act: TerminalAct; reason: string }) =>\n adminPosApi.setTerminalStatus(v.id, v.act, v.reason),\n );\n\nexport const useCloseShift = () =>\n usePosWrite((v: { id: string; reason: string }) =>\n adminPosApi.closeShift(v.id, v.reason),\n );\n"],"mappings":";AAEA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,mBAAqC;AAM9C,SAAS,aAAa,YAAY,mBAAmB;AAG9C,MAAM,UAAU,CAAC,kBAAkB,KAAK;AAE/C,MAAM,MAAM,IAAI,UAAqB,CAAC,GAAG,SAAS,GAAG,KAAK;AAG1D,MAAM,QAAQ,CAAC,OAAe,UAC5B,CAAC,YAAY,KAAK,KAAK,CAAC,WAAW,KAAK,KAAK,QAAQ;AAEhD,SAAS,cAAc,GAAc,UAAU,MAAM;AAC1D,SAAO,SAAS;AAAA,IACd,UAAU,IAAI,WAAW,CAAC;AAAA,IAC1B,SAAS,MAAM,YAAY,QAAQ,CAAC;AAAA,IACpC;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEO,SAAS,gBAAgB,SAA0B,UAAU,MAAM;AACxE,SAAO,SAAS;AAAA,IACd,UAAU,IAAI,aAAa,OAAO;AAAA,IAClC,SAAS,MAAM,YAAY,UAAU,OAAO;AAAA,IAC5C,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEO,SAAS,eAAe,IAAY,GAAc,UAAU,MAAM;AACvE,SAAO,SAAS;AAAA,IACd,UAAU,IAAI,YAAY,IAAI,CAAC;AAAA,IAC/B,SAAS,MAAM,YAAY,SAAS,IAAI,CAAC;AAAA,IACzC;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEO,SAAS,cAAc,IAAY,UAAU,MAAM;AACxD,SAAO,SAAS;AAAA,IACd,UAAU,IAAI,WAAW,EAAE;AAAA,IAC3B,SAAS,MAAM,YAAY,QAAQ,EAAE;AAAA,IACrC;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEO,SAAS,aAAa,SAAuB,UAAU,MAAM;AAClE,SAAO,SAAS;AAAA,IACd,UAAU,IAAI,UAAU,OAAO;AAAA,IAC/B,SAAS,MAAM,YAAY,OAAO,OAAO;AAAA,IACzC,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEO,SAAS,gBAAgB,UAAkB,UAAU,MAAM;AAChE,SAAO,SAAS;AAAA,IACd,UAAU,IAAI,UAAU,QAAQ;AAAA,IAChC,SAAS,MAAM,YAAY,OAAO,QAAQ;AAAA,IAC1C;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEO,SAAS,sBAAsB,UAAkB,UAAU,MAAM;AACtE,SAAO,SAAS;AAAA,IACd,UAAU,IAAI,iBAAiB,QAAQ;AAAA,IACvC,SAAS,MAAM,YAAY,aAAa,QAAQ;AAAA,IAChD;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAGO,SAAS,kBAAkB,UAAkB,UAAU,MAAM;AAClE,SAAO,iBAAiB;AAAA,IACtB,UAAU,IAAI,YAAY,QAAQ;AAAA,IAClC,SAAS,CAAC,EAAE,UAAU,MACpB,YAAY,SAAS,EAAE,UAAU,QAAQ,WAAW,OAAO,GAAG,CAAC;AAAA,IACjE,kBAAkB;AAAA,IAClB,kBAAkB,CAAC,SAAS,KAAK,cAAc;AAAA,IAC/C;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAMA,MAAM,aAAa,CAAC,OAAe,UACjC,CAAC,YAAY,KAAK,KAAK,CAAC,WAAW,KAAK,KAAK,CAAC,YAAY,KAAK,KAAK,QAAQ;AAEvE,SAAS,gBAAgB,UAAkB,UAAU,MAAM;AAChE,SAAO,SAAS;AAAA,IACd,UAAU,IAAI,SAAS,UAAU,SAAS;AAAA,IAC1C,SAAS,MAAM,YAAY,MAAM,QAAQ,QAAQ;AAAA,IACjD;AAAA,IACA,OAAO;AAAA,IACP,WAAW;AAAA,EACb,CAAC;AACH;AAEO,SAAS,4BAA4B,UAAkB,UAAU,MAAM;AAC5E,SAAO,SAAS;AAAA,IACd,UAAU,IAAI,SAAS,UAAU,sBAAsB;AAAA,IACvD,SAAS,MAAM,YAAY,MAAM,oBAAoB,QAAQ;AAAA,IAC7D;AAAA,IACA,OAAO;AAAA,IACP,WAAW;AAAA,EACb,CAAC;AACH;AAEO,SAAS,gBAAgB,UAAkB,UAAU,MAAM;AAChE,SAAO,SAAS;AAAA,IACd,UAAU,IAAI,SAAS,UAAU,SAAS;AAAA,IAC1C,SAAS,MAAM,YAAY,MAAM,QAAQ,QAAQ;AAAA,IACjD;AAAA,IACA,OAAO;AAAA,IACP,WAAW;AAAA,EACb,CAAC;AACH;AAEO,SAAS,aAAa,UAAkB,GAAc,UAAU,MAAM;AAC3E,SAAO,SAAS;AAAA,IACd,UAAU,IAAI,UAAU,UAAU,CAAC;AAAA,IACnC,SAAS,MAAM,YAAY,OAAO,UAAU,CAAC;AAAA,IAC7C;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAQA,SAAS,YAAkB,IAA0B;AACnD,QAAM,SAAS,eAAe;AAE9B,SAAO,YAAY;AAAA,IACjB,YAAY;AAAA,IACZ,WAAW,MAAM;AACf,WAAK,OAAO,kBAAkB,EAAE,UAAU,QAAQ,CAAC;AAAA,IACrD;AAAA,EACF,CAAC;AACH;AAEO,MAAM,oBAAoB,MAC/B;AAAA,EAAY,CAAC,MACX,YAAY,kBAAkB,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM;AACrD;AAEK,MAAM,gBAAgB,MAC3B;AAAA,EAAY,CAAC,MACX,YAAY,WAAW,EAAE,IAAI,EAAE,MAAM;AACvC;","names":[]}
@@ -32,7 +32,7 @@ function DataTable({
32
32
  {
33
33
  tone: "error",
34
34
  title: errorTitle,
35
- description: "Please retry. If this persists, check the API logs."
35
+ description: "Please try again. If it keeps happening, tell the engineering team."
36
36
  }
37
37
  ) }) }),
38
38
  !isLoading && !isError && rows && rows.length === 0 && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: colCount, style: { padding: 0 }, children: /* @__PURE__ */ jsx(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/_ui/data-table.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { EmptyState } from './empty-state';\nimport { Skeleton } from './skeleton';\nimport { tableStyles } from './styles';\n\nexport interface Column<T> {\n /** Unique key for the column. */\n key: string;\n /** Header label. */\n header: string;\n /** Cell renderer. Defaults to String(row[key]). */\n render?: (row: T) => ReactNode;\n /** Render muted (secondary text colour). */\n muted?: boolean;\n /** Prevent wrapping. */\n nowrap?: boolean;\n width?: number | string;\n}\n\n/**\n * Generic admin data table. Owns the loading (skeleton), error and empty\n * states so list pages never hand-roll a <table> again. Supports an optional\n * expandable sub-row per record.\n */\nexport function DataTable<T>({\n columns,\n rows,\n getRowKey,\n isLoading,\n isError,\n errorTitle = 'Failed to load',\n emptyTitle = 'Nothing here yet',\n emptyDescription,\n emptyIcon,\n skeletonRows = 6,\n renderExpanded,\n isExpanded,\n onRowClick,\n}: {\n columns: Column<T>[];\n rows: T[] | undefined;\n getRowKey: (row: T) => string;\n isLoading?: boolean;\n isError?: boolean;\n errorTitle?: string;\n emptyTitle?: string;\n emptyDescription?: string;\n emptyIcon?: React.ComponentType<{ size?: number; color?: string }>;\n skeletonRows?: number;\n renderExpanded?: (row: T) => ReactNode;\n isExpanded?: (row: T) => boolean;\n /** When provided, the whole row becomes clickable (excluding links/buttons). */\n onRowClick?: (row: T) => void;\n}) {\n const colCount = columns.length;\n\n return (\n <div className=\"adm-table-card\">\n <table style={tableStyles.table}>\n <thead>\n <tr style={tableStyles.headRow}>\n {columns.map((c) => (\n <th key={c.key} style={{ ...tableStyles.th, width: c.width }}>\n {c.header}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {isLoading &&\n Array.from({ length: skeletonRows }).map((_, i) => (\n <tr key={`sk-${i}`}>\n {columns.map((c) => (\n <td key={c.key} style={tableStyles.cell}>\n <Skeleton\n width={`${50 + ((i + c.key.length) % 4) * 12}%`}\n />\n </td>\n ))}\n </tr>\n ))}\n\n {!isLoading && isError && (\n <tr>\n <td colSpan={colCount} style={{ padding: 0 }}>\n <EmptyState\n tone=\"error\"\n title={errorTitle}\n description=\"Please retry. If this persists, check the API logs.\"\n />\n </td>\n </tr>\n )}\n\n {!isLoading && !isError && rows && rows.length === 0 && (\n <tr>\n <td colSpan={colCount} style={{ padding: 0 }}>\n <EmptyState\n icon={emptyIcon}\n title={emptyTitle}\n description={emptyDescription}\n />\n </td>\n </tr>\n )}\n\n {!isLoading &&\n !isError &&\n rows?.map((row) => (\n <RowGroup\n key={getRowKey(row)}\n row={row}\n columns={columns}\n renderExpanded={renderExpanded}\n expanded={isExpanded?.(row) ?? false}\n colCount={colCount}\n onRowClick={onRowClick}\n />\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n\nfunction RowGroup<T>({\n row,\n columns,\n renderExpanded,\n expanded,\n colCount,\n onRowClick,\n}: {\n row: T;\n columns: Column<T>[];\n renderExpanded?: (row: T) => ReactNode;\n expanded: boolean;\n colCount: number;\n onRowClick?: (row: T) => void;\n}) {\n const clickable = !!onRowClick;\n return (\n <>\n <tr\n onClick={\n clickable\n ? (e) => {\n // Let inner links/buttons handle their own clicks.\n if ((e.target as HTMLElement).closest('a,button')) return;\n onRowClick!(row);\n }\n : undefined\n }\n style={clickable ? { cursor: 'pointer' } : undefined}\n onMouseEnter={\n clickable\n ? (e) => (e.currentTarget.style.background = 'var(--bg-subtle)')\n : undefined\n }\n onMouseLeave={\n clickable ? (e) => (e.currentTarget.style.background = '') : undefined\n }\n >\n {columns.map((c) => (\n <td\n key={c.key}\n style={{\n ...(c.muted ? tableStyles.cellMuted : tableStyles.cell),\n whiteSpace: c.nowrap ? 'nowrap' : undefined,\n }}\n >\n {c.render\n ? c.render(row)\n : String((row as Record<string, unknown>)[c.key] ?? '—')}\n </td>\n ))}\n </tr>\n {expanded && renderExpanded && (\n <tr>\n <td\n colSpan={colCount}\n style={{ ...tableStyles.cell, background: 'var(--bg-subtle)' }}\n >\n {renderExpanded(row)}\n </td>\n </tr>\n )}\n </>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/_ui/data-table.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { EmptyState } from './empty-state';\nimport { Skeleton } from './skeleton';\nimport { tableStyles } from './styles';\n\nexport interface Column<T> {\n /** Unique key for the column. */\n key: string;\n /** Header label. */\n header: string;\n /** Cell renderer. Defaults to String(row[key]). */\n render?: (row: T) => ReactNode;\n /** Render muted (secondary text colour). */\n muted?: boolean;\n /** Prevent wrapping. */\n nowrap?: boolean;\n width?: number | string;\n}\n\n/**\n * Generic admin data table. Owns the loading (skeleton), error and empty\n * states so list pages never hand-roll a <table> again. Supports an optional\n * expandable sub-row per record.\n */\nexport function DataTable<T>({\n columns,\n rows,\n getRowKey,\n isLoading,\n isError,\n errorTitle = 'Failed to load',\n emptyTitle = 'Nothing here yet',\n emptyDescription,\n emptyIcon,\n skeletonRows = 6,\n renderExpanded,\n isExpanded,\n onRowClick,\n}: {\n columns: Column<T>[];\n rows: T[] | undefined;\n getRowKey: (row: T) => string;\n isLoading?: boolean;\n isError?: boolean;\n errorTitle?: string;\n emptyTitle?: string;\n emptyDescription?: string;\n emptyIcon?: React.ComponentType<{ size?: number; color?: string }>;\n skeletonRows?: number;\n renderExpanded?: (row: T) => ReactNode;\n isExpanded?: (row: T) => boolean;\n /** When provided, the whole row becomes clickable (excluding links/buttons). */\n onRowClick?: (row: T) => void;\n}) {\n const colCount = columns.length;\n\n return (\n <div className=\"adm-table-card\">\n <table style={tableStyles.table}>\n <thead>\n <tr style={tableStyles.headRow}>\n {columns.map((c) => (\n <th key={c.key} style={{ ...tableStyles.th, width: c.width }}>\n {c.header}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {isLoading &&\n Array.from({ length: skeletonRows }).map((_, i) => (\n <tr key={`sk-${i}`}>\n {columns.map((c) => (\n <td key={c.key} style={tableStyles.cell}>\n <Skeleton\n width={`${50 + ((i + c.key.length) % 4) * 12}%`}\n />\n </td>\n ))}\n </tr>\n ))}\n\n {!isLoading && isError && (\n <tr>\n <td colSpan={colCount} style={{ padding: 0 }}>\n <EmptyState\n tone=\"error\"\n title={errorTitle}\n description=\"Please try again. If it keeps happening, tell the engineering team.\"\n />\n </td>\n </tr>\n )}\n\n {!isLoading && !isError && rows && rows.length === 0 && (\n <tr>\n <td colSpan={colCount} style={{ padding: 0 }}>\n <EmptyState\n icon={emptyIcon}\n title={emptyTitle}\n description={emptyDescription}\n />\n </td>\n </tr>\n )}\n\n {!isLoading &&\n !isError &&\n rows?.map((row) => (\n <RowGroup\n key={getRowKey(row)}\n row={row}\n columns={columns}\n renderExpanded={renderExpanded}\n expanded={isExpanded?.(row) ?? false}\n colCount={colCount}\n onRowClick={onRowClick}\n />\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n\nfunction RowGroup<T>({\n row,\n columns,\n renderExpanded,\n expanded,\n colCount,\n onRowClick,\n}: {\n row: T;\n columns: Column<T>[];\n renderExpanded?: (row: T) => ReactNode;\n expanded: boolean;\n colCount: number;\n onRowClick?: (row: T) => void;\n}) {\n const clickable = !!onRowClick;\n return (\n <>\n <tr\n onClick={\n clickable\n ? (e) => {\n // Let inner links/buttons handle their own clicks.\n if ((e.target as HTMLElement).closest('a,button')) return;\n onRowClick!(row);\n }\n : undefined\n }\n style={clickable ? { cursor: 'pointer' } : undefined}\n onMouseEnter={\n clickable\n ? (e) => (e.currentTarget.style.background = 'var(--bg-subtle)')\n : undefined\n }\n onMouseLeave={\n clickable ? (e) => (e.currentTarget.style.background = '') : undefined\n }\n >\n {columns.map((c) => (\n <td\n key={c.key}\n style={{\n ...(c.muted ? tableStyles.cellMuted : tableStyles.cell),\n whiteSpace: c.nowrap ? 'nowrap' : undefined,\n }}\n >\n {c.render\n ? c.render(row)\n : String((row as Record<string, unknown>)[c.key] ?? '—')}\n </td>\n ))}\n </tr>\n {expanded && renderExpanded && (\n <tr>\n <td\n colSpan={colCount}\n style={{ ...tableStyles.cell, background: 'var(--bg-subtle)' }}\n >\n {renderExpanded(row)}\n </td>\n </tr>\n )}\n </>\n );\n}\n"],"mappings":"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","names":[]}
@@ -53,7 +53,7 @@ function ListCard({
53
53
  {
54
54
  tone: "error",
55
55
  title: errorTitle,
56
- description: "Please retry. If this persists, check the API logs.",
56
+ description: "Please try again. If it keeps happening, tell the engineering team.",
57
57
  action: onRetry && /* @__PURE__ */ jsx(Button, { size: "sm", variant: "secondary", onClick: onRetry, children: "Try again" })
58
58
  }
59
59
  )
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/_ui/list-card.tsx"],"sourcesContent":["'use client';\n\nimport type { ComponentType, ReactNode } from 'react';\nimport { Button, TableShell } from '@withpotter/ui-kit';\nimport { EmptyState } from './empty-state';\nimport { Skeleton } from './skeleton';\nimport styles from './list-card.module.css';\n\n/** What an empty cell shows: an en dash, never a blank. */\nexport const EMPTY = '–';\n\n/**\n * A list table in its own card: title, search and filters in the toolbar,\n * rows scrolling under a sticky header, the pager in the footer. Owns the\n * loading, error and empty rows so each list page only declares columns.\n */\nexport function ListCard({\n title,\n count,\n controls,\n head,\n columns,\n minWidth = 760,\n selection,\n isLoading,\n isError,\n onRetry,\n errorTitle,\n isEmpty,\n emptyIcon,\n emptyTitle,\n emptyDescription,\n footer,\n children,\n}: {\n title: string;\n count?: number;\n controls?: ReactNode;\n /** The header <tr> of <Th> cells. */\n head: ReactNode;\n /** Column count, for skeleton and state rows. */\n columns: number;\n minWidth?: number;\n /** Takes the header row's place while rows are ticked. */\n selection?: ReactNode;\n isLoading?: boolean;\n isError?: boolean;\n onRetry?: () => void;\n errorTitle: string;\n isEmpty?: boolean;\n emptyIcon?: ComponentType<{ size?: number; color?: string }>;\n emptyTitle: string;\n emptyDescription?: string;\n footer?: ReactNode;\n children: ReactNode;\n}) {\n const state = (body: ReactNode) => (\n <tr>\n <td colSpan={columns} className=\"!p-0\">\n {body}\n </td>\n </tr>\n );\n\n return (\n <section\n className={`adm-section ${styles.card}`}\n aria-label={title}\n aria-busy={isLoading || undefined}\n >\n <TableShell\n bare\n minWidth={minWidth}\n head={head}\n selection={selection}\n columnCount={columns}\n toolbar={\n <>\n <h2 className={styles.title}>\n {title}\n {count !== undefined && (\n <span className={styles.count}>{count}</span>\n )}\n </h2>\n {controls}\n </>\n }\n footer={footer}\n >\n {isLoading\n ? Array.from({ length: 8 }, (_, row) => (\n <tr key={`sk-${row}`} aria-hidden>\n {Array.from({ length: columns }, (_, col) => (\n <td key={col}>\n <Skeleton width={`${48 + ((row + col) % 4) * 12}%`} />\n </td>\n ))}\n </tr>\n ))\n : isError\n ? state(\n <EmptyState\n tone=\"error\"\n title={errorTitle}\n description=\"Please retry. If this persists, check the API logs.\"\n action={\n onRetry && (\n <Button size=\"sm\" variant=\"secondary\" onClick={onRetry}>\n Try again\n </Button>\n )\n }\n />,\n )\n : isEmpty\n ? state(\n <EmptyState\n icon={emptyIcon}\n title={emptyTitle}\n description={emptyDescription}\n />,\n )\n : children}\n </TableShell>\n </section>\n );\n}\n\n/** Count on the left, Previous · Page n of m · Next on the right. */\nexport function ListPager({\n page,\n totalPages,\n total,\n noun,\n onPage,\n}: {\n page: number;\n totalPages: number;\n total?: number;\n noun: string;\n onPage: (next: number) => void;\n}) {\n return (\n <div className={styles.footer}>\n <span>\n {total != null\n ? `${new Intl.NumberFormat().format(total)} ${noun}`\n : ''}\n </span>\n <div className={styles.pages}>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onClick={() => onPage(Math.max(1, page - 1))}\n disabled={page <= 1}\n >\n Previous\n </Button>\n <span>\n Page {page} of {totalPages}\n </span>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onClick={() => onPage(Math.min(totalPages, page + 1))}\n disabled={page >= totalPages}\n >\n Next\n </Button>\n </div>\n </div>\n );\n}\n"],"mappings":";AA0DM,SAmBI,UAnBJ,KAoBM,YApBN;AAvDN,SAAS,QAAQ,kBAAkB;AACnC,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB;AACzB,OAAO,YAAY;AAGZ,MAAM,QAAQ;AAOd,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAqBG;AACD,QAAM,QAAQ,CAAC,SACb,oBAAC,QACC,8BAAC,QAAG,SAAS,SAAS,WAAU,QAC7B,gBACH,GACF;AAGF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,eAAe,OAAO,IAAI;AAAA,MACrC,cAAY;AAAA,MACZ,aAAW,aAAa;AAAA,MAExB;AAAA,QAAC;AAAA;AAAA,UACC,MAAI;AAAA,UACJ;AAAA,UACA;AAAA,UACA;AAAA,UACA,aAAa;AAAA,UACb,SACE,iCACE;AAAA,iCAAC,QAAG,WAAW,OAAO,OACnB;AAAA;AAAA,cACA,UAAU,UACT,oBAAC,UAAK,WAAW,OAAO,OAAQ,iBAAM;AAAA,eAE1C;AAAA,YACC;AAAA,aACH;AAAA,UAEF;AAAA,UAEC,sBACG,MAAM,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,GAAG,QAC5B,oBAAC,QAAqB,eAAW,MAC9B,gBAAM,KAAK,EAAE,QAAQ,QAAQ,GAAG,CAACA,IAAG,QACnC,oBAAC,QACC,8BAAC,YAAS,OAAO,GAAG,MAAO,MAAM,OAAO,IAAK,EAAE,KAAK,KAD7C,GAET,CACD,KALM,MAAM,GAAG,EAMlB,CACD,IACD,UACE;AAAA,YACE;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,OAAO;AAAA,gBACP,aAAY;AAAA,gBACZ,QACE,WACE,oBAAC,UAAO,MAAK,MAAK,SAAQ,aAAY,SAAS,SAAS,uBAExD;AAAA;AAAA,YAGN;AAAA,UACF,IACA,UACE;AAAA,YACE;AAAA,cAAC;AAAA;AAAA,gBACC,MAAM;AAAA,gBACN,OAAO;AAAA,gBACP,aAAa;AAAA;AAAA,YACf;AAAA,UACF,IACA;AAAA;AAAA,MACV;AAAA;AAAA,EACF;AAEJ;AAGO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,SACE,qBAAC,SAAI,WAAW,OAAO,QACrB;AAAA,wBAAC,UACE,mBAAS,OACN,GAAG,IAAI,KAAK,aAAa,EAAE,OAAO,KAAK,CAAC,IAAI,IAAI,KAChD,IACN;AAAA,IACA,qBAAC,SAAI,WAAW,OAAO,OACrB;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAQ;AAAA,UACR,SAAS,MAAM,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,CAAC;AAAA,UAC3C,UAAU,QAAQ;AAAA,UACnB;AAAA;AAAA,MAED;AAAA,MACA,qBAAC,UAAK;AAAA;AAAA,QACE;AAAA,QAAK;AAAA,QAAK;AAAA,SAClB;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAQ;AAAA,UACR,SAAS,MAAM,OAAO,KAAK,IAAI,YAAY,OAAO,CAAC,CAAC;AAAA,UACpD,UAAU,QAAQ;AAAA,UACnB;AAAA;AAAA,MAED;AAAA,OACF;AAAA,KACF;AAEJ;","names":["_"]}
1
+ {"version":3,"sources":["../../../src/admin/_ui/list-card.tsx"],"sourcesContent":["'use client';\n\nimport type { ComponentType, ReactNode } from 'react';\nimport { Button, TableShell } from '@withpotter/ui-kit';\nimport { EmptyState } from './empty-state';\nimport { Skeleton } from './skeleton';\nimport styles from './list-card.module.css';\n\n/** What an empty cell shows: an en dash, never a blank. */\nexport const EMPTY = '–';\n\n/**\n * A list table in its own card: title, search and filters in the toolbar,\n * rows scrolling under a sticky header, the pager in the footer. Owns the\n * loading, error and empty rows so each list page only declares columns.\n */\nexport function ListCard({\n title,\n count,\n controls,\n head,\n columns,\n minWidth = 760,\n selection,\n isLoading,\n isError,\n onRetry,\n errorTitle,\n isEmpty,\n emptyIcon,\n emptyTitle,\n emptyDescription,\n footer,\n children,\n}: {\n title: string;\n count?: number;\n controls?: ReactNode;\n /** The header <tr> of <Th> cells. */\n head: ReactNode;\n /** Column count, for skeleton and state rows. */\n columns: number;\n minWidth?: number;\n /** Takes the header row's place while rows are ticked. */\n selection?: ReactNode;\n isLoading?: boolean;\n isError?: boolean;\n onRetry?: () => void;\n errorTitle: string;\n isEmpty?: boolean;\n emptyIcon?: ComponentType<{ size?: number; color?: string }>;\n emptyTitle: string;\n emptyDescription?: string;\n footer?: ReactNode;\n children: ReactNode;\n}) {\n const state = (body: ReactNode) => (\n <tr>\n <td colSpan={columns} className=\"!p-0\">\n {body}\n </td>\n </tr>\n );\n\n return (\n <section\n className={`adm-section ${styles.card}`}\n aria-label={title}\n aria-busy={isLoading || undefined}\n >\n <TableShell\n bare\n minWidth={minWidth}\n head={head}\n selection={selection}\n columnCount={columns}\n toolbar={\n <>\n <h2 className={styles.title}>\n {title}\n {count !== undefined && (\n <span className={styles.count}>{count}</span>\n )}\n </h2>\n {controls}\n </>\n }\n footer={footer}\n >\n {isLoading\n ? Array.from({ length: 8 }, (_, row) => (\n <tr key={`sk-${row}`} aria-hidden>\n {Array.from({ length: columns }, (_, col) => (\n <td key={col}>\n <Skeleton width={`${48 + ((row + col) % 4) * 12}%`} />\n </td>\n ))}\n </tr>\n ))\n : isError\n ? state(\n <EmptyState\n tone=\"error\"\n title={errorTitle}\n description=\"Please try again. If it keeps happening, tell the engineering team.\"\n action={\n onRetry && (\n <Button size=\"sm\" variant=\"secondary\" onClick={onRetry}>\n Try again\n </Button>\n )\n }\n />,\n )\n : isEmpty\n ? state(\n <EmptyState\n icon={emptyIcon}\n title={emptyTitle}\n description={emptyDescription}\n />,\n )\n : children}\n </TableShell>\n </section>\n );\n}\n\n/** Count on the left, Previous · Page n of m · Next on the right. */\nexport function ListPager({\n page,\n totalPages,\n total,\n noun,\n onPage,\n}: {\n page: number;\n totalPages: number;\n total?: number;\n noun: string;\n onPage: (next: number) => void;\n}) {\n return (\n <div className={styles.footer}>\n <span>\n {total != null\n ? `${new Intl.NumberFormat().format(total)} ${noun}`\n : ''}\n </span>\n <div className={styles.pages}>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onClick={() => onPage(Math.max(1, page - 1))}\n disabled={page <= 1}\n >\n Previous\n </Button>\n <span>\n Page {page} of {totalPages}\n </span>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onClick={() => onPage(Math.min(totalPages, page + 1))}\n disabled={page >= totalPages}\n >\n Next\n </Button>\n </div>\n </div>\n );\n}\n"],"mappings":";AA0DM,SAmBI,UAnBJ,KAoBM,YApBN;AAvDN,SAAS,QAAQ,kBAAkB;AACnC,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB;AACzB,OAAO,YAAY;AAGZ,MAAM,QAAQ;AAOd,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAqBG;AACD,QAAM,QAAQ,CAAC,SACb,oBAAC,QACC,8BAAC,QAAG,SAAS,SAAS,WAAU,QAC7B,gBACH,GACF;AAGF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,eAAe,OAAO,IAAI;AAAA,MACrC,cAAY;AAAA,MACZ,aAAW,aAAa;AAAA,MAExB;AAAA,QAAC;AAAA;AAAA,UACC,MAAI;AAAA,UACJ;AAAA,UACA;AAAA,UACA;AAAA,UACA,aAAa;AAAA,UACb,SACE,iCACE;AAAA,iCAAC,QAAG,WAAW,OAAO,OACnB;AAAA;AAAA,cACA,UAAU,UACT,oBAAC,UAAK,WAAW,OAAO,OAAQ,iBAAM;AAAA,eAE1C;AAAA,YACC;AAAA,aACH;AAAA,UAEF;AAAA,UAEC,sBACG,MAAM,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,GAAG,QAC5B,oBAAC,QAAqB,eAAW,MAC9B,gBAAM,KAAK,EAAE,QAAQ,QAAQ,GAAG,CAACA,IAAG,QACnC,oBAAC,QACC,8BAAC,YAAS,OAAO,GAAG,MAAO,MAAM,OAAO,IAAK,EAAE,KAAK,KAD7C,GAET,CACD,KALM,MAAM,GAAG,EAMlB,CACD,IACD,UACE;AAAA,YACE;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,OAAO;AAAA,gBACP,aAAY;AAAA,gBACZ,QACE,WACE,oBAAC,UAAO,MAAK,MAAK,SAAQ,aAAY,SAAS,SAAS,uBAExD;AAAA;AAAA,YAGN;AAAA,UACF,IACA,UACE;AAAA,YACE;AAAA,cAAC;AAAA;AAAA,gBACC,MAAM;AAAA,gBACN,OAAO;AAAA,gBACP,aAAa;AAAA;AAAA,YACf;AAAA,UACF,IACA;AAAA;AAAA,MACV;AAAA;AAAA,EACF;AAEJ;AAGO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,SACE,qBAAC,SAAI,WAAW,OAAO,QACrB;AAAA,wBAAC,UACE,mBAAS,OACN,GAAG,IAAI,KAAK,aAAa,EAAE,OAAO,KAAK,CAAC,IAAI,IAAI,KAChD,IACN;AAAA,IACA,qBAAC,SAAI,WAAW,OAAO,OACrB;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAQ;AAAA,UACR,SAAS,MAAM,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,CAAC;AAAA,UAC3C,UAAU,QAAQ;AAAA,UACnB;AAAA;AAAA,MAED;AAAA,MACA,qBAAC,UAAK;AAAA;AAAA,QACE;AAAA,QAAK;AAAA,QAAK;AAAA,SAClB;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAQ;AAAA,UACR,SAAS,MAAM,OAAO,KAAK,IAAI,YAAY,OAAO,CAAC,CAAC;AAAA,UACpD,UAAU,QAAQ;AAAA,UACnB;AAAA;AAAA,MAED;AAAA,OACF;AAAA,KACF;AAEJ;","names":["_"]}
@@ -20,9 +20,9 @@ function GmvOrdersChart({
20
20
  data: gmvTrend,
21
21
  compare: compare ? previousGmvTrend : null,
22
22
  compareLabel: `Previous ${span}`,
23
- label: "GMV",
23
+ label: "Sales",
24
24
  formatValue: money,
25
- emptyMessage: "No merchant GMV in this period",
25
+ emptyMessage: "No sales in this period",
26
26
  height: 160
27
27
  }
28
28
  ),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/_ui/trend.tsx"],"sourcesContent":["'use client';\n\nimport { TimeSeriesChart } from '@withpotter/components/charts';\n\n/** A `{date, N}[]` series reshaped into the `{date, value}` TimeSeriesChart wants. */\nexport function toTrend<K extends string>(\n series: Array<{ date: string } & Record<K, number>> | undefined,\n key: K,\n): Array<{ date: string; value: number }> {\n return (series ?? []).map((p) => ({ date: p.date, value: p[key] }));\n}\n\n/**\n * GMV over orders: the two measures share a window but not a scale, so\n * they're two stacked charts rather than one dual-axis one. Used wherever a\n * page shows both for the same range (Overview, Merchants, Commerce).\n */\nexport function GmvOrdersChart({\n gmvTrend,\n ordersTrend,\n previousGmvTrend,\n previousOrdersTrend,\n compare,\n span,\n money,\n}: {\n gmvTrend: Array<{ date: string; value: number }>;\n ordersTrend: Array<{ date: string; value: number }>;\n previousGmvTrend: Array<{ date: string; value: number }>;\n previousOrdersTrend: Array<{ date: string; value: number }>;\n /** False for \"all time\": there is no earlier period to compare with. */\n compare: boolean;\n span: string;\n money: (value: number) => string;\n}) {\n return (\n <>\n <TimeSeriesChart\n data={gmvTrend}\n compare={compare ? previousGmvTrend : null}\n compareLabel={`Previous ${span}`}\n label=\"GMV\"\n formatValue={money}\n emptyMessage=\"No merchant GMV in this period\"\n height={160}\n />\n <div\n style={{\n margin: '20px 0 8px',\n fontSize: 12,\n fontWeight: 600,\n color: 'var(--text-tertiary)',\n }}\n >\n Orders\n </div>\n <TimeSeriesChart\n data={ordersTrend}\n compare={compare ? previousOrdersTrend : null}\n compareLabel={`Previous ${span}`}\n label=\"Orders\"\n emptyMessage=\"No orders in this period\"\n height={100}\n />\n </>\n );\n}\n"],"mappings":";AAoCI,mBACE,KADF;AAlCJ,SAAS,uBAAuB;AAGzB,SAAS,QACd,QACA,KACwC;AACxC,UAAQ,UAAU,CAAC,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,EAAE,GAAG,EAAE,EAAE;AACpE;AAOO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GASG;AACD,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,SAAS,UAAU,mBAAmB;AAAA,QACtC,cAAc,YAAY,IAAI;AAAA,QAC9B,OAAM;AAAA,QACN,aAAa;AAAA,QACb,cAAa;AAAA,QACb,QAAQ;AAAA;AAAA,IACV;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO;AAAA,UACL,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,YAAY;AAAA,UACZ,OAAO;AAAA,QACT;AAAA,QACD;AAAA;AAAA,IAED;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,SAAS,UAAU,sBAAsB;AAAA,QACzC,cAAc,YAAY,IAAI;AAAA,QAC9B,OAAM;AAAA,QACN,cAAa;AAAA,QACb,QAAQ;AAAA;AAAA,IACV;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/_ui/trend.tsx"],"sourcesContent":["'use client';\n\nimport { TimeSeriesChart } from '@withpotter/components/charts';\n\n/** A `{date, N}[]` series reshaped into the `{date, value}` TimeSeriesChart wants. */\nexport function toTrend<K extends string>(\n series: Array<{ date: string } & Record<K, number>> | undefined,\n key: K,\n): Array<{ date: string; value: number }> {\n return (series ?? []).map((p) => ({ date: p.date, value: p[key] }));\n}\n\n/**\n * GMV over orders: the two measures share a window but not a scale, so\n * they're two stacked charts rather than one dual-axis one. Used wherever a\n * page shows both for the same range (Overview, Merchants, Commerce).\n */\nexport function GmvOrdersChart({\n gmvTrend,\n ordersTrend,\n previousGmvTrend,\n previousOrdersTrend,\n compare,\n span,\n money,\n}: {\n gmvTrend: Array<{ date: string; value: number }>;\n ordersTrend: Array<{ date: string; value: number }>;\n previousGmvTrend: Array<{ date: string; value: number }>;\n previousOrdersTrend: Array<{ date: string; value: number }>;\n /** False for \"all time\": there is no earlier period to compare with. */\n compare: boolean;\n span: string;\n money: (value: number) => string;\n}) {\n return (\n <>\n <TimeSeriesChart\n data={gmvTrend}\n compare={compare ? previousGmvTrend : null}\n compareLabel={`Previous ${span}`}\n label=\"Sales\"\n formatValue={money}\n emptyMessage=\"No sales in this period\"\n height={160}\n />\n <div\n style={{\n margin: '20px 0 8px',\n fontSize: 12,\n fontWeight: 600,\n color: 'var(--text-tertiary)',\n }}\n >\n Orders\n </div>\n <TimeSeriesChart\n data={ordersTrend}\n compare={compare ? previousOrdersTrend : null}\n compareLabel={`Previous ${span}`}\n label=\"Orders\"\n emptyMessage=\"No orders in this period\"\n height={100}\n />\n </>\n );\n}\n"],"mappings":";AAoCI,mBACE,KADF;AAlCJ,SAAS,uBAAuB;AAGzB,SAAS,QACd,QACA,KACwC;AACxC,UAAQ,UAAU,CAAC,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,EAAE,GAAG,EAAE,EAAE;AACpE;AAOO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GASG;AACD,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,SAAS,UAAU,mBAAmB;AAAA,QACtC,cAAc,YAAY,IAAI;AAAA,QAC9B,OAAM;AAAA,QACN,aAAa;AAAA,QACb,cAAa;AAAA,QACb,QAAQ;AAAA;AAAA,IACV;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO;AAAA,UACL,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,YAAY;AAAA,UACZ,OAAO;AAAA,QACT;AAAA,QACD;AAAA;AAAA,IAED;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,SAAS,UAAU,sBAAsB;AAAA,QACzC,cAAc,YAAY,IAAI;AAAA,QAC9B,OAAM;AAAA,QACN,cAAa;AAAA,QACb,QAAQ;AAAA;AAAA,IACV;AAAA,KACF;AAEJ;","names":[]}
@@ -62,12 +62,12 @@ function AdminIndexPage() {
62
62
  const gmvTrend = useMemo(() => toTrend(series, "gmv"), [series]);
63
63
  const ordersTrend = useMemo(() => toTrend(series, "orders"), [series]);
64
64
  if (cockpit.isLoading) {
65
- return /* @__PURE__ */ jsx(AdmLoading, { label: "Loading platform operations" });
65
+ return /* @__PURE__ */ jsx(AdmLoading, { label: "Loading overview" });
66
66
  }
67
67
  if (cockpit.isError || !cockpit.data) {
68
68
  return /* @__PURE__ */ jsxs("div", { className: "adm-panel", role: "alert", children: [
69
- /* @__PURE__ */ jsx("strong", { children: "Platform operations could not be loaded." }),
70
- /* @__PURE__ */ jsx("p", { className: "mb-0 mt-2 text-sm text-[var(--text-secondary)]", children: "The range-aware admin API did not return the operating summary." }),
69
+ /* @__PURE__ */ jsx("strong", { children: "We couldn't load the overview." }),
70
+ /* @__PURE__ */ jsx("p", { className: "mb-0 mt-2 text-sm text-[var(--text-secondary)]", children: "Something went wrong while fetching the numbers. Please try again." }),
71
71
  /* @__PURE__ */ jsx(
72
72
  "button",
73
73
  {
@@ -87,8 +87,8 @@ function AdminIndexPage() {
87
87
  /* @__PURE__ */ jsx(
88
88
  PageHeader,
89
89
  {
90
- title: "Platform operations",
91
- description: "Merchant growth, money movement and exceptions to review.",
90
+ title: "Overview",
91
+ description: "How merchants are growing, how much money is moving, and what needs your attention.",
92
92
  actions: /* @__PURE__ */ jsxs("div", { className: "adm-toolbar", children: [
93
93
  /* @__PURE__ */ jsx(
94
94
  CurrencyFilter,
@@ -130,7 +130,7 @@ function AdminIndexPage() {
130
130
  }
131
131
  ),
132
132
  /* @__PURE__ */ jsxs("div", { className: "adm-grid", children: [
133
- /* @__PURE__ */ jsx("div", { className: "adm-span-8", children: /* @__PURE__ */ jsx(Panel, { title: "Processing volume", caption, children: focus ? /* @__PURE__ */ jsx(
133
+ /* @__PURE__ */ jsx("div", { className: "adm-span-8", children: /* @__PURE__ */ jsx(Panel, { title: "Sales over time", caption, children: focus ? /* @__PURE__ */ jsx(
134
134
  GmvOrdersChart,
135
135
  {
136
136
  gmvTrend,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/admin/page.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo } from 'react';\nimport {\n AdmLoading,\n CurrencyFilter,\n GmvOrdersChart,\n PICK_CURRENCY,\n PageHeader,\n Panel,\n PeriodSelector,\n periodPhrase,\n customRange,\n focusCurrency,\n moneyIn,\n rangeFromKey,\n rowFor,\n toTrend,\n useCurrencyParam,\n usePersistedDateRange,\n} from './_ui';\nimport { useGatewayChannels, usePlatformCockpit } from './hooks';\nimport { ActivationPanel } from './_overview/activation-panel';\nimport { AttentionPanel } from './_overview/attention-panel';\nimport { ControlRoom } from './_overview/control-room';\nimport { KpiRow } from './_overview/kpi-row';\nimport { LeadersPanel } from './_overview/leaders-panel';\nimport { PortfolioPanel } from './_overview/portfolio-panel';\nimport { PositionPanel } from './_overview/position-panel';\nimport { ChannelPanel } from './revenue/channel-panel';\n\nexport default function AdminIndexPage() {\n const {\n rangeKey,\n setRangeKey,\n customStart,\n customEnd,\n onCustomChange,\n compare,\n } = usePersistedDateRange();\n // Money comes back once per currency; `?currency=` narrows it to one.\n const [currency, setCurrency] = useCurrencyParam();\n const range = useMemo(\n () =>\n rangeKey === 'custom'\n ? customRange(customStart, customEnd)\n : rangeFromKey(rangeKey),\n [customEnd, customStart, rangeKey],\n );\n const cockpit = usePlatformCockpit({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n const channels = useGatewayChannels({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n const currencies = useMemo(\n () => cockpit.data?.currencies ?? [],\n [cockpit.data],\n );\n // A chart can only draw one currency: the filter's, or the only one.\n const focus = focusCurrency(currencies, currency);\n const series = useMemo(\n () =>\n rowFor(cockpit.data?.trend.performanceSeriesByCurrency, focus)?.series,\n [cockpit.data, focus],\n );\n const gmvTrend = useMemo(() => toTrend(series, 'gmv'), [series]);\n const ordersTrend = useMemo(() => toTrend(series, 'orders'), [series]);\n\n if (cockpit.isLoading) {\n return <AdmLoading label=\"Loading platform operations\" />;\n }\n\n if (cockpit.isError || !cockpit.data) {\n return (\n <div className=\"adm-panel\" role=\"alert\">\n <strong>Platform operations could not be loaded.</strong>\n <p className=\"mb-0 mt-2 text-sm text-[var(--text-secondary)]\">\n The range-aware admin API did not return the operating summary.\n </p>\n <button\n type=\"button\"\n className=\"mt-4 text-sm font-semibold text-[var(--accent-primary)]\"\n onClick={() => void cockpit.refetch()}\n >\n Try again\n </button>\n </div>\n );\n }\n\n const data = cockpit.data;\n const span = periodPhrase(range);\n const period = `${span[0].toUpperCase()}${span.slice(1)}`;\n const caption = focus ? `${period} · ${focus}` : period;\n\n return (\n <div>\n <PageHeader\n title=\"Platform operations\"\n description=\"Merchant growth, money movement and exceptions to review.\"\n actions={\n <div className=\"adm-toolbar\">\n <CurrencyFilter\n value={currency}\n onChange={setCurrency}\n currencies={currencies}\n />\n <PeriodSelector\n value={rangeKey}\n onChange={setRangeKey}\n customStart={customStart}\n customEnd={customEnd}\n onCustomChange={onCustomChange}\n />\n </div>\n }\n />\n\n <ControlRoom\n attention={data.attention}\n currency={currency}\n totals={data.totals}\n />\n\n <KpiRow\n data={data}\n currency={currency}\n focus={focus}\n compare={compare}\n caption={caption}\n />\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-8\">\n <Panel title=\"Processing volume\" caption={caption}>\n {focus ? (\n <GmvOrdersChart\n gmvTrend={gmvTrend}\n ordersTrend={ordersTrend}\n previousGmvTrend={[]}\n previousOrdersTrend={[]}\n compare={false}\n span={span}\n money={moneyIn(focus)}\n />\n ) : (\n <p className=\"text-sm text-[var(--text-secondary)]\">\n {PICK_CURRENCY}\n </p>\n )}\n </Panel>\n </div>\n <div className=\"adm-span-4\">\n <PositionPanel data={data} currency={currency} />\n </div>\n </div>\n\n {channels.data && channels.data.byChannel.length > 0 && (\n <div className=\"adm-grid\">\n <div className=\"adm-span-12\">\n <ChannelPanel\n totals={channels.data.byChannel}\n focus={focus}\n caption={caption}\n />\n </div>\n </div>\n )}\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-12\">\n <ActivationPanel activation={data.activation} />\n </div>\n </div>\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-4\">\n <AttentionPanel attention={data.attention} />\n </div>\n <div className=\"adm-span-4\">\n <LeadersPanel\n tenants={rowFor(data.topTenantsByCurrency, focus)?.tenants ?? []}\n focus={focus}\n caption={focus ? `${span} · ${focus}` : span}\n />\n </div>\n <div className=\"adm-span-4\">\n <PortfolioPanel\n statuses={data.statusBreakdown}\n industries={data.industryBreakdown}\n />\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/admin/page.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo } from 'react';\nimport {\n AdmLoading,\n CurrencyFilter,\n GmvOrdersChart,\n PICK_CURRENCY,\n PageHeader,\n Panel,\n PeriodSelector,\n periodPhrase,\n customRange,\n focusCurrency,\n moneyIn,\n rangeFromKey,\n rowFor,\n toTrend,\n useCurrencyParam,\n usePersistedDateRange,\n} from './_ui';\nimport { useGatewayChannels, usePlatformCockpit } from './hooks';\nimport { ActivationPanel } from './_overview/activation-panel';\nimport { AttentionPanel } from './_overview/attention-panel';\nimport { ControlRoom } from './_overview/control-room';\nimport { KpiRow } from './_overview/kpi-row';\nimport { LeadersPanel } from './_overview/leaders-panel';\nimport { PortfolioPanel } from './_overview/portfolio-panel';\nimport { PositionPanel } from './_overview/position-panel';\nimport { ChannelPanel } from './revenue/channel-panel';\n\nexport default function AdminIndexPage() {\n const {\n rangeKey,\n setRangeKey,\n customStart,\n customEnd,\n onCustomChange,\n compare,\n } = usePersistedDateRange();\n // Money comes back once per currency; `?currency=` narrows it to one.\n const [currency, setCurrency] = useCurrencyParam();\n const range = useMemo(\n () =>\n rangeKey === 'custom'\n ? customRange(customStart, customEnd)\n : rangeFromKey(rangeKey),\n [customEnd, customStart, rangeKey],\n );\n const cockpit = usePlatformCockpit({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n const channels = useGatewayChannels({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n const currencies = useMemo(\n () => cockpit.data?.currencies ?? [],\n [cockpit.data],\n );\n // A chart can only draw one currency: the filter's, or the only one.\n const focus = focusCurrency(currencies, currency);\n const series = useMemo(\n () =>\n rowFor(cockpit.data?.trend.performanceSeriesByCurrency, focus)?.series,\n [cockpit.data, focus],\n );\n const gmvTrend = useMemo(() => toTrend(series, 'gmv'), [series]);\n const ordersTrend = useMemo(() => toTrend(series, 'orders'), [series]);\n\n if (cockpit.isLoading) {\n return <AdmLoading label=\"Loading overview\" />;\n }\n\n if (cockpit.isError || !cockpit.data) {\n return (\n <div className=\"adm-panel\" role=\"alert\">\n <strong>We couldn&apos;t load the overview.</strong>\n <p className=\"mb-0 mt-2 text-sm text-[var(--text-secondary)]\">\n Something went wrong while fetching the numbers. Please try again.\n </p>\n <button\n type=\"button\"\n className=\"mt-4 text-sm font-semibold text-[var(--accent-primary)]\"\n onClick={() => void cockpit.refetch()}\n >\n Try again\n </button>\n </div>\n );\n }\n\n const data = cockpit.data;\n const span = periodPhrase(range);\n const period = `${span[0].toUpperCase()}${span.slice(1)}`;\n const caption = focus ? `${period} · ${focus}` : period;\n\n return (\n <div>\n <PageHeader\n title=\"Overview\"\n description=\"How merchants are growing, how much money is moving, and what needs your attention.\"\n actions={\n <div className=\"adm-toolbar\">\n <CurrencyFilter\n value={currency}\n onChange={setCurrency}\n currencies={currencies}\n />\n <PeriodSelector\n value={rangeKey}\n onChange={setRangeKey}\n customStart={customStart}\n customEnd={customEnd}\n onCustomChange={onCustomChange}\n />\n </div>\n }\n />\n\n <ControlRoom\n attention={data.attention}\n currency={currency}\n totals={data.totals}\n />\n\n <KpiRow\n data={data}\n currency={currency}\n focus={focus}\n compare={compare}\n caption={caption}\n />\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-8\">\n <Panel title=\"Sales over time\" caption={caption}>\n {focus ? (\n <GmvOrdersChart\n gmvTrend={gmvTrend}\n ordersTrend={ordersTrend}\n previousGmvTrend={[]}\n previousOrdersTrend={[]}\n compare={false}\n span={span}\n money={moneyIn(focus)}\n />\n ) : (\n <p className=\"text-sm text-[var(--text-secondary)]\">\n {PICK_CURRENCY}\n </p>\n )}\n </Panel>\n </div>\n <div className=\"adm-span-4\">\n <PositionPanel data={data} currency={currency} />\n </div>\n </div>\n\n {channels.data && channels.data.byChannel.length > 0 && (\n <div className=\"adm-grid\">\n <div className=\"adm-span-12\">\n <ChannelPanel\n totals={channels.data.byChannel}\n focus={focus}\n caption={caption}\n />\n </div>\n </div>\n )}\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-12\">\n <ActivationPanel activation={data.activation} />\n </div>\n </div>\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-4\">\n <AttentionPanel attention={data.attention} />\n </div>\n <div className=\"adm-span-4\">\n <LeadersPanel\n tenants={rowFor(data.topTenantsByCurrency, focus)?.tenants ?? []}\n focus={focus}\n caption={focus ? `${span} · ${focus}` : span}\n />\n </div>\n <div className=\"adm-span-4\">\n <PortfolioPanel\n statuses={data.statusBreakdown}\n industries={data.industryBreakdown}\n />\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -7,6 +7,7 @@ import { Banner } from "@withpotter/ui-kit/banner";
7
7
  import { Input } from "@withpotter/ui-kit/input";
8
8
  import { Checkbox } from "@withpotter/ui-kit/checkbox";
9
9
  import { Field } from "@withpotter/ui-kit/field";
10
+ import { requirementGroupLabel, requirementLabel } from "./requirement-labels";
10
11
  import { formatMinor, parseMajorToMinor } from "@withpotter/lib/money-minor";
11
12
  import {
12
13
  TIER_REQUIREMENTS
@@ -53,15 +54,15 @@ function TierDialog({
53
54
  const dailyMaximumMinor = parseMajorToMinor(daily, code);
54
55
  const monthlyMaximumMinor = monthly ? parseMajorToMinor(monthly, code) : null;
55
56
  if (!key.trim() || !label.trim())
56
- return setFormError("Key and label are required.");
57
+ return setFormError("Short name and display name are required.");
57
58
  if (!minimumMinor || !maximumMinor || !dailyMaximumMinor)
58
59
  return setFormError(
59
- "Minimum, maximum and daily limit must be valid amounts."
60
+ "The smallest transfer, largest transfer and daily limit must be valid amounts."
60
61
  );
61
62
  if (monthly && !monthlyMaximumMinor)
62
- return setFormError("The monthly limit is not a valid amount.");
63
+ return setFormError("The monthly limit must be a valid amount.");
63
64
  if (!/^\d+$/.test(position))
64
- return setFormError("Position must be a whole number.");
65
+ return setFormError("Priority must be a whole number.");
65
66
  onSave({
66
67
  key: key.trim(),
67
68
  currency: code,
@@ -79,7 +80,7 @@ function TierDialog({
79
80
  FormDialog,
80
81
  {
81
82
  title: tier ? "Edit tier" : "New payout tier",
82
- description: "Limits a business gets once it has passed every listed verification.",
83
+ description: "The sending limits a merchant gets once they have completed every check listed below.",
83
84
  size: "lg",
84
85
  onClose,
85
86
  footer: /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -89,15 +90,24 @@ function TierDialog({
89
90
  children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
90
91
  (formError || error) && /* @__PURE__ */ jsx(Banner, { tone: "error", children: formError ?? error }),
91
92
  /* @__PURE__ */ jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [
92
- /* @__PURE__ */ jsx(Field, { label: "Key", htmlFor: "t-key", required: true, children: /* @__PURE__ */ jsx(
93
- Input,
93
+ /* @__PURE__ */ jsx(
94
+ Field,
94
95
  {
95
- id: "t-key",
96
- value: key,
97
- onChange: (e) => setKey(e.target.value)
96
+ label: "Short name",
97
+ htmlFor: "t-key",
98
+ required: true,
99
+ hint: "A unique code for this level, for example starter.",
100
+ children: /* @__PURE__ */ jsx(
101
+ Input,
102
+ {
103
+ id: "t-key",
104
+ value: key,
105
+ onChange: (e) => setKey(e.target.value)
106
+ }
107
+ )
98
108
  }
99
- ) }),
100
- /* @__PURE__ */ jsx(Field, { label: "Label", htmlFor: "t-label", required: true, children: /* @__PURE__ */ jsx(
109
+ ),
110
+ /* @__PURE__ */ jsx(Field, { label: "Display name", htmlFor: "t-label", required: true, children: /* @__PURE__ */ jsx(
101
111
  Input,
102
112
  {
103
113
  id: "t-label",
@@ -117,9 +127,9 @@ function TierDialog({
117
127
  /* @__PURE__ */ jsx(
118
128
  Field,
119
129
  {
120
- label: "Position",
130
+ label: "Priority",
121
131
  htmlFor: "t-pos",
122
- hint: "Higher tiers are chosen first when a business qualifies for several.",
132
+ hint: "If a merchant qualifies for several levels, the one with the higher number is used.",
123
133
  children: /* @__PURE__ */ jsx(
124
134
  Input,
125
135
  {
@@ -131,7 +141,7 @@ function TierDialog({
131
141
  )
132
142
  }
133
143
  ),
134
- /* @__PURE__ */ jsx(Field, { label: "Per-transfer minimum", htmlFor: "t-min", required: true, children: /* @__PURE__ */ jsx(
144
+ /* @__PURE__ */ jsx(Field, { label: "Smallest single transfer", htmlFor: "t-min", required: true, children: /* @__PURE__ */ jsx(
135
145
  Input,
136
146
  {
137
147
  id: "t-min",
@@ -140,7 +150,7 @@ function TierDialog({
140
150
  onChange: (e) => setMin(e.target.value)
141
151
  }
142
152
  ) }),
143
- /* @__PURE__ */ jsx(Field, { label: "Per-transfer maximum", htmlFor: "t-max", required: true, children: /* @__PURE__ */ jsx(
153
+ /* @__PURE__ */ jsx(Field, { label: "Largest single transfer", htmlFor: "t-max", required: true, children: /* @__PURE__ */ jsx(
144
154
  Input,
145
155
  {
146
156
  id: "t-max",
@@ -149,7 +159,7 @@ function TierDialog({
149
159
  onChange: (e) => setMax(e.target.value)
150
160
  }
151
161
  ) }),
152
- /* @__PURE__ */ jsx(Field, { label: "Daily total", htmlFor: "t-day", required: true, children: /* @__PURE__ */ jsx(
162
+ /* @__PURE__ */ jsx(Field, { label: "Daily limit", htmlFor: "t-day", required: true, children: /* @__PURE__ */ jsx(
153
163
  Input,
154
164
  {
155
165
  id: "t-day",
@@ -158,7 +168,7 @@ function TierDialog({
158
168
  onChange: (e) => setDaily(e.target.value)
159
169
  }
160
170
  ) }),
161
- /* @__PURE__ */ jsx(Field, { label: "Monthly total", htmlFor: "t-mon", hint: "Optional.", children: /* @__PURE__ */ jsx(
171
+ /* @__PURE__ */ jsx(Field, { label: "Monthly limit", htmlFor: "t-mon", hint: "Optional.", children: /* @__PURE__ */ jsx(
162
172
  Input,
163
173
  {
164
174
  id: "t-mon",
@@ -170,16 +180,16 @@ function TierDialog({
170
180
  ] }),
171
181
  /* @__PURE__ */ jsxs("fieldset", { className: "space-y-2", children: [
172
182
  /* @__PURE__ */ jsxs("legend", { className: "text-sm font-medium text-[var(--text-secondary)]", children: [
173
- "Required verifications (",
183
+ "Checks a merchant must complete (",
174
184
  requirements.length,
175
185
  ")"
176
186
  ] }),
177
187
  /* @__PURE__ */ jsx("div", { className: "max-h-56 space-y-3 overflow-y-auto rounded-md border border-[var(--border-default)] p-3", children: Object.entries(TIER_REQUIREMENTS).map(([group, ops]) => /* @__PURE__ */ jsxs("div", { children: [
178
- /* @__PURE__ */ jsx("p", { className: "mb-1 text-xs font-medium text-[var(--text-tertiary)]", children: group }),
188
+ /* @__PURE__ */ jsx("p", { className: "mb-1 text-xs font-medium text-[var(--text-tertiary)]", children: requirementGroupLabel(group) }),
179
189
  /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-x-4 gap-y-1", children: ops.map((op) => /* @__PURE__ */ jsx(
180
190
  Checkbox,
181
191
  {
182
- label: op,
192
+ label: requirementLabel(op),
183
193
  checked: requirements.includes(op),
184
194
  onChange: () => toggle(op)
185
195
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/payout-tiers/_tier-dialog.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { FormDialog } from '@withpotter/ui-kit/form-dialog';\nimport { Button } from '@withpotter/ui-kit/button';\nimport { Banner } from '@withpotter/ui-kit/banner';\nimport { Input } from '@withpotter/ui-kit/input';\nimport { Checkbox } from '@withpotter/ui-kit/checkbox';\nimport { Field } from '@withpotter/ui-kit/field';\nimport { formatMinor, parseMajorToMinor } from '@withpotter/lib/money-minor';\nimport {\n TIER_REQUIREMENTS,\n type PayoutTier,\n type PayoutTierInput,\n} from '@withpotter/lib/admin-money-api';\n\n/** Minor units back to what a person types: \"150000\" NGN is \"1500\". */\nfunction toMajor(minor: string, currency: string): string {\n return formatMinor(minor, currency).replace(/[^\\d.]/g, '');\n}\n\nexport function TierDialog({\n tier,\n saving,\n error,\n onSave,\n onClose,\n}: {\n tier: PayoutTier | null;\n saving: boolean;\n error: string | null;\n onSave: (body: PayoutTierInput) => void;\n onClose: () => void;\n}) {\n const [key, setKey] = useState(tier?.key ?? '');\n const [currency, setCurrency] = useState(tier?.currency ?? 'NGN');\n const [label, setLabel] = useState(tier?.label ?? '');\n const [position, setPosition] = useState(String(tier?.position ?? 0));\n const [requirements, setRequirements] = useState<string[]>(\n tier?.requirements ?? [],\n );\n const [min, setMin] = useState(\n tier ? toMajor(tier.minimumMinor, tier.currency) : '',\n );\n const [max, setMax] = useState(\n tier ? toMajor(tier.maximumMinor, tier.currency) : '',\n );\n const [daily, setDaily] = useState(\n tier ? toMajor(tier.dailyMaximumMinor, tier.currency) : '',\n );\n const [monthly, setMonthly] = useState(\n tier?.monthlyMaximumMinor\n ? toMajor(tier.monthlyMaximumMinor, tier.currency)\n : '',\n );\n const [enabled, setEnabled] = useState(tier?.enabled ?? true);\n const [formError, setFormError] = useState<string | null>(null);\n\n const toggle = (r: string) =>\n setRequirements((c) =>\n c.includes(r) ? c.filter((x) => x !== r) : [...c, r],\n );\n\n const submit = () => {\n setFormError(null);\n const code = currency.trim().toUpperCase();\n const minimumMinor = parseMajorToMinor(min, code);\n const maximumMinor = parseMajorToMinor(max, code);\n const dailyMaximumMinor = parseMajorToMinor(daily, code);\n const monthlyMaximumMinor = monthly\n ? parseMajorToMinor(monthly, code)\n : null;\n if (!key.trim() || !label.trim())\n return setFormError('Key and label are required.');\n if (!minimumMinor || !maximumMinor || !dailyMaximumMinor)\n return setFormError(\n 'Minimum, maximum and daily limit must be valid amounts.',\n );\n if (monthly && !monthlyMaximumMinor)\n return setFormError('The monthly limit is not a valid amount.');\n if (!/^\\d+$/.test(position))\n return setFormError('Position must be a whole number.');\n onSave({\n key: key.trim(),\n currency: code,\n label: label.trim(),\n position: Number(position),\n requirements,\n minimumMinor,\n maximumMinor,\n dailyMaximumMinor,\n monthlyMaximumMinor,\n enabled,\n });\n };\n\n return (\n <FormDialog\n title={tier ? 'Edit tier' : 'New payout tier'}\n description=\"Limits a business gets once it has passed every listed verification.\"\n size=\"lg\"\n onClose={onClose}\n footer={\n <>\n <Button variant=\"secondary\" onClick={onClose}>\n Cancel\n </Button>\n <Button onClick={submit} loading={saving}>\n {tier ? 'Save tier' : 'Create tier'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-4\">\n {(formError || error) && (\n <Banner tone=\"error\">{formError ?? error}</Banner>\n )}\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <Field label=\"Key\" htmlFor=\"t-key\" required>\n <Input\n id=\"t-key\"\n value={key}\n onChange={(e) => setKey(e.target.value)}\n />\n </Field>\n <Field label=\"Label\" htmlFor=\"t-label\" required>\n <Input\n id=\"t-label\"\n value={label}\n onChange={(e) => setLabel(e.target.value)}\n />\n </Field>\n <Field label=\"Currency\" htmlFor=\"t-cur\" required>\n <Input\n id=\"t-cur\"\n value={currency}\n maxLength={3}\n onChange={(e) => setCurrency(e.target.value)}\n />\n </Field>\n <Field\n label=\"Position\"\n htmlFor=\"t-pos\"\n hint=\"Higher tiers are chosen first when a business qualifies for several.\"\n >\n <Input\n id=\"t-pos\"\n inputMode=\"numeric\"\n value={position}\n onChange={(e) => setPosition(e.target.value)}\n />\n </Field>\n <Field label=\"Per-transfer minimum\" htmlFor=\"t-min\" required>\n <Input\n id=\"t-min\"\n inputMode=\"decimal\"\n value={min}\n onChange={(e) => setMin(e.target.value)}\n />\n </Field>\n <Field label=\"Per-transfer maximum\" htmlFor=\"t-max\" required>\n <Input\n id=\"t-max\"\n inputMode=\"decimal\"\n value={max}\n onChange={(e) => setMax(e.target.value)}\n />\n </Field>\n <Field label=\"Daily total\" htmlFor=\"t-day\" required>\n <Input\n id=\"t-day\"\n inputMode=\"decimal\"\n value={daily}\n onChange={(e) => setDaily(e.target.value)}\n />\n </Field>\n <Field label=\"Monthly total\" htmlFor=\"t-mon\" hint=\"Optional.\">\n <Input\n id=\"t-mon\"\n inputMode=\"decimal\"\n value={monthly}\n onChange={(e) => setMonthly(e.target.value)}\n />\n </Field>\n </div>\n\n <fieldset className=\"space-y-2\">\n <legend className=\"text-sm font-medium text-[var(--text-secondary)]\">\n Required verifications ({requirements.length})\n </legend>\n <div className=\"max-h-56 space-y-3 overflow-y-auto rounded-md border border-[var(--border-default)] p-3\">\n {Object.entries(TIER_REQUIREMENTS).map(([group, ops]) => (\n <div key={group}>\n <p className=\"mb-1 text-xs font-medium text-[var(--text-tertiary)]\">\n {group}\n </p>\n <div className=\"flex flex-wrap gap-x-4 gap-y-1\">\n {ops.map((op) => (\n <Checkbox\n key={op}\n label={op}\n checked={requirements.includes(op)}\n onChange={() => toggle(op)}\n />\n ))}\n </div>\n </div>\n ))}\n </div>\n </fieldset>\n\n <Checkbox\n label=\"Enabled\"\n checked={enabled}\n onChange={(e) => setEnabled(e.target.checked)}\n />\n </div>\n </FormDialog>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/payout-tiers/_tier-dialog.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { FormDialog } from '@withpotter/ui-kit/form-dialog';\nimport { Button } from '@withpotter/ui-kit/button';\nimport { Banner } from '@withpotter/ui-kit/banner';\nimport { Input } from '@withpotter/ui-kit/input';\nimport { Checkbox } from '@withpotter/ui-kit/checkbox';\nimport { Field } from '@withpotter/ui-kit/field';\nimport { requirementGroupLabel, requirementLabel } from './requirement-labels';\nimport { formatMinor, parseMajorToMinor } from '@withpotter/lib/money-minor';\nimport {\n TIER_REQUIREMENTS,\n type PayoutTier,\n type PayoutTierInput,\n} from '@withpotter/lib/admin-money-api';\n\n/** Minor units back to what a person types: \"150000\" NGN is \"1500\". */\nfunction toMajor(minor: string, currency: string): string {\n return formatMinor(minor, currency).replace(/[^\\d.]/g, '');\n}\n\nexport function TierDialog({\n tier,\n saving,\n error,\n onSave,\n onClose,\n}: {\n tier: PayoutTier | null;\n saving: boolean;\n error: string | null;\n onSave: (body: PayoutTierInput) => void;\n onClose: () => void;\n}) {\n const [key, setKey] = useState(tier?.key ?? '');\n const [currency, setCurrency] = useState(tier?.currency ?? 'NGN');\n const [label, setLabel] = useState(tier?.label ?? '');\n const [position, setPosition] = useState(String(tier?.position ?? 0));\n const [requirements, setRequirements] = useState<string[]>(\n tier?.requirements ?? [],\n );\n const [min, setMin] = useState(\n tier ? toMajor(tier.minimumMinor, tier.currency) : '',\n );\n const [max, setMax] = useState(\n tier ? toMajor(tier.maximumMinor, tier.currency) : '',\n );\n const [daily, setDaily] = useState(\n tier ? toMajor(tier.dailyMaximumMinor, tier.currency) : '',\n );\n const [monthly, setMonthly] = useState(\n tier?.monthlyMaximumMinor\n ? toMajor(tier.monthlyMaximumMinor, tier.currency)\n : '',\n );\n const [enabled, setEnabled] = useState(tier?.enabled ?? true);\n const [formError, setFormError] = useState<string | null>(null);\n\n const toggle = (r: string) =>\n setRequirements((c) =>\n c.includes(r) ? c.filter((x) => x !== r) : [...c, r],\n );\n\n const submit = () => {\n setFormError(null);\n const code = currency.trim().toUpperCase();\n const minimumMinor = parseMajorToMinor(min, code);\n const maximumMinor = parseMajorToMinor(max, code);\n const dailyMaximumMinor = parseMajorToMinor(daily, code);\n const monthlyMaximumMinor = monthly\n ? parseMajorToMinor(monthly, code)\n : null;\n if (!key.trim() || !label.trim())\n return setFormError('Short name and display name are required.');\n if (!minimumMinor || !maximumMinor || !dailyMaximumMinor)\n return setFormError(\n 'The smallest transfer, largest transfer and daily limit must be valid amounts.',\n );\n if (monthly && !monthlyMaximumMinor)\n return setFormError('The monthly limit must be a valid amount.');\n if (!/^\\d+$/.test(position))\n return setFormError('Priority must be a whole number.');\n onSave({\n key: key.trim(),\n currency: code,\n label: label.trim(),\n position: Number(position),\n requirements,\n minimumMinor,\n maximumMinor,\n dailyMaximumMinor,\n monthlyMaximumMinor,\n enabled,\n });\n };\n\n return (\n <FormDialog\n title={tier ? 'Edit tier' : 'New payout tier'}\n description=\"The sending limits a merchant gets once they have completed every check listed below.\"\n size=\"lg\"\n onClose={onClose}\n footer={\n <>\n <Button variant=\"secondary\" onClick={onClose}>\n Cancel\n </Button>\n <Button onClick={submit} loading={saving}>\n {tier ? 'Save tier' : 'Create tier'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-4\">\n {(formError || error) && (\n <Banner tone=\"error\">{formError ?? error}</Banner>\n )}\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <Field\n label=\"Short name\"\n htmlFor=\"t-key\"\n required\n hint=\"A unique code for this level, for example starter.\"\n >\n <Input\n id=\"t-key\"\n value={key}\n onChange={(e) => setKey(e.target.value)}\n />\n </Field>\n <Field label=\"Display name\" htmlFor=\"t-label\" required>\n <Input\n id=\"t-label\"\n value={label}\n onChange={(e) => setLabel(e.target.value)}\n />\n </Field>\n <Field label=\"Currency\" htmlFor=\"t-cur\" required>\n <Input\n id=\"t-cur\"\n value={currency}\n maxLength={3}\n onChange={(e) => setCurrency(e.target.value)}\n />\n </Field>\n <Field\n label=\"Priority\"\n htmlFor=\"t-pos\"\n hint=\"If a merchant qualifies for several levels, the one with the higher number is used.\"\n >\n <Input\n id=\"t-pos\"\n inputMode=\"numeric\"\n value={position}\n onChange={(e) => setPosition(e.target.value)}\n />\n </Field>\n <Field label=\"Smallest single transfer\" htmlFor=\"t-min\" required>\n <Input\n id=\"t-min\"\n inputMode=\"decimal\"\n value={min}\n onChange={(e) => setMin(e.target.value)}\n />\n </Field>\n <Field label=\"Largest single transfer\" htmlFor=\"t-max\" required>\n <Input\n id=\"t-max\"\n inputMode=\"decimal\"\n value={max}\n onChange={(e) => setMax(e.target.value)}\n />\n </Field>\n <Field label=\"Daily limit\" htmlFor=\"t-day\" required>\n <Input\n id=\"t-day\"\n inputMode=\"decimal\"\n value={daily}\n onChange={(e) => setDaily(e.target.value)}\n />\n </Field>\n <Field label=\"Monthly limit\" htmlFor=\"t-mon\" hint=\"Optional.\">\n <Input\n id=\"t-mon\"\n inputMode=\"decimal\"\n value={monthly}\n onChange={(e) => setMonthly(e.target.value)}\n />\n </Field>\n </div>\n\n <fieldset className=\"space-y-2\">\n <legend className=\"text-sm font-medium text-[var(--text-secondary)]\">\n Checks a merchant must complete ({requirements.length})\n </legend>\n <div className=\"max-h-56 space-y-3 overflow-y-auto rounded-md border border-[var(--border-default)] p-3\">\n {Object.entries(TIER_REQUIREMENTS).map(([group, ops]) => (\n <div key={group}>\n <p className=\"mb-1 text-xs font-medium text-[var(--text-tertiary)]\">\n {requirementGroupLabel(group)}\n </p>\n <div className=\"flex flex-wrap gap-x-4 gap-y-1\">\n {ops.map((op) => (\n <Checkbox\n key={op}\n label={requirementLabel(op)}\n checked={requirements.includes(op)}\n onChange={() => toggle(op)}\n />\n ))}\n </div>\n </div>\n ))}\n </div>\n </fieldset>\n\n <Checkbox\n label=\"Enabled\"\n checked={enabled}\n onChange={(e) => setEnabled(e.target.checked)}\n />\n </div>\n </FormDialog>\n );\n}\n"],"mappings":";AAwGQ,mBACE,KADF;AAtGR,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAC3B,SAAS,cAAc;AACvB,SAAS,cAAc;AACvB,SAAS,aAAa;AACtB,SAAS,gBAAgB;AACzB,SAAS,aAAa;AACtB,SAAS,uBAAuB,wBAAwB;AACxD,SAAS,aAAa,yBAAyB;AAC/C;AAAA,EACE;AAAA,OAGK;AAGP,SAAS,QAAQ,OAAe,UAA0B;AACxD,SAAO,YAAY,OAAO,QAAQ,EAAE,QAAQ,WAAW,EAAE;AAC3D;AAEO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,QAAM,CAAC,KAAK,MAAM,IAAI,SAAS,MAAM,OAAO,EAAE;AAC9C,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,MAAM,YAAY,KAAK;AAChE,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,MAAM,SAAS,EAAE;AACpD,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,OAAO,MAAM,YAAY,CAAC,CAAC;AACpE,QAAM,CAAC,cAAc,eAAe,IAAI;AAAA,IACtC,MAAM,gBAAgB,CAAC;AAAA,EACzB;AACA,QAAM,CAAC,KAAK,MAAM,IAAI;AAAA,IACpB,OAAO,QAAQ,KAAK,cAAc,KAAK,QAAQ,IAAI;AAAA,EACrD;AACA,QAAM,CAAC,KAAK,MAAM,IAAI;AAAA,IACpB,OAAO,QAAQ,KAAK,cAAc,KAAK,QAAQ,IAAI;AAAA,EACrD;AACA,QAAM,CAAC,OAAO,QAAQ,IAAI;AAAA,IACxB,OAAO,QAAQ,KAAK,mBAAmB,KAAK,QAAQ,IAAI;AAAA,EAC1D;AACA,QAAM,CAAC,SAAS,UAAU,IAAI;AAAA,IAC5B,MAAM,sBACF,QAAQ,KAAK,qBAAqB,KAAK,QAAQ,IAC/C;AAAA,EACN;AACA,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,MAAM,WAAW,IAAI;AAC5D,QAAM,CAAC,WAAW,YAAY,IAAI,SAAwB,IAAI;AAE9D,QAAM,SAAS,CAAC,MACd;AAAA,IAAgB,CAAC,MACf,EAAE,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC;AAAA,EACrD;AAEF,QAAM,SAAS,MAAM;AACnB,iBAAa,IAAI;AACjB,UAAM,OAAO,SAAS,KAAK,EAAE,YAAY;AACzC,UAAM,eAAe,kBAAkB,KAAK,IAAI;AAChD,UAAM,eAAe,kBAAkB,KAAK,IAAI;AAChD,UAAM,oBAAoB,kBAAkB,OAAO,IAAI;AACvD,UAAM,sBAAsB,UACxB,kBAAkB,SAAS,IAAI,IAC/B;AACJ,QAAI,CAAC,IAAI,KAAK,KAAK,CAAC,MAAM,KAAK;AAC7B,aAAO,aAAa,2CAA2C;AACjE,QAAI,CAAC,gBAAgB,CAAC,gBAAgB,CAAC;AACrC,aAAO;AAAA,QACL;AAAA,MACF;AACF,QAAI,WAAW,CAAC;AACd,aAAO,aAAa,2CAA2C;AACjE,QAAI,CAAC,QAAQ,KAAK,QAAQ;AACxB,aAAO,aAAa,kCAAkC;AACxD,WAAO;AAAA,MACL,KAAK,IAAI,KAAK;AAAA,MACd,UAAU;AAAA,MACV,OAAO,MAAM,KAAK;AAAA,MAClB,UAAU,OAAO,QAAQ;AAAA,MACzB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,OAAO,cAAc;AAAA,MAC5B,aAAY;AAAA,MACZ,MAAK;AAAA,MACL;AAAA,MACA,QACE,iCACE;AAAA,4BAAC,UAAO,SAAQ,aAAY,SAAS,SAAS,oBAE9C;AAAA,QACA,oBAAC,UAAO,SAAS,QAAQ,SAAS,QAC/B,iBAAO,cAAc,eACxB;AAAA,SACF;AAAA,MAGF,+BAAC,SAAI,WAAU,aACX;AAAA,sBAAa,UACb,oBAAC,UAAO,MAAK,SAAS,uBAAa,OAAM;AAAA,QAE3C,qBAAC,SAAI,WAAU,6BACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,OAAM;AAAA,cACN,SAAQ;AAAA,cACR,UAAQ;AAAA,cACR,MAAK;AAAA,cAEL;AAAA,gBAAC;AAAA;AAAA,kBACC,IAAG;AAAA,kBACH,OAAO;AAAA,kBACP,UAAU,CAAC,MAAM,OAAO,EAAE,OAAO,KAAK;AAAA;AAAA,cACxC;AAAA;AAAA,UACF;AAAA,UACA,oBAAC,SAAM,OAAM,gBAAe,SAAQ,WAAU,UAAQ,MACpD;AAAA,YAAC;AAAA;AAAA,cACC,IAAG;AAAA,cACH,OAAO;AAAA,cACP,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA;AAAA,UAC1C,GACF;AAAA,UACA,oBAAC,SAAM,OAAM,YAAW,SAAQ,SAAQ,UAAQ,MAC9C;AAAA,YAAC;AAAA;AAAA,cACC,IAAG;AAAA,cACH,OAAO;AAAA,cACP,WAAW;AAAA,cACX,UAAU,CAAC,MAAM,YAAY,EAAE,OAAO,KAAK;AAAA;AAAA,UAC7C,GACF;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,OAAM;AAAA,cACN,SAAQ;AAAA,cACR,MAAK;AAAA,cAEL;AAAA,gBAAC;AAAA;AAAA,kBACC,IAAG;AAAA,kBACH,WAAU;AAAA,kBACV,OAAO;AAAA,kBACP,UAAU,CAAC,MAAM,YAAY,EAAE,OAAO,KAAK;AAAA;AAAA,cAC7C;AAAA;AAAA,UACF;AAAA,UACA,oBAAC,SAAM,OAAM,4BAA2B,SAAQ,SAAQ,UAAQ,MAC9D;AAAA,YAAC;AAAA;AAAA,cACC,IAAG;AAAA,cACH,WAAU;AAAA,cACV,OAAO;AAAA,cACP,UAAU,CAAC,MAAM,OAAO,EAAE,OAAO,KAAK;AAAA;AAAA,UACxC,GACF;AAAA,UACA,oBAAC,SAAM,OAAM,2BAA0B,SAAQ,SAAQ,UAAQ,MAC7D;AAAA,YAAC;AAAA;AAAA,cACC,IAAG;AAAA,cACH,WAAU;AAAA,cACV,OAAO;AAAA,cACP,UAAU,CAAC,MAAM,OAAO,EAAE,OAAO,KAAK;AAAA;AAAA,UACxC,GACF;AAAA,UACA,oBAAC,SAAM,OAAM,eAAc,SAAQ,SAAQ,UAAQ,MACjD;AAAA,YAAC;AAAA;AAAA,cACC,IAAG;AAAA,cACH,WAAU;AAAA,cACV,OAAO;AAAA,cACP,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA;AAAA,UAC1C,GACF;AAAA,UACA,oBAAC,SAAM,OAAM,iBAAgB,SAAQ,SAAQ,MAAK,aAChD;AAAA,YAAC;AAAA;AAAA,cACC,IAAG;AAAA,cACH,WAAU;AAAA,cACV,OAAO;AAAA,cACP,UAAU,CAAC,MAAM,WAAW,EAAE,OAAO,KAAK;AAAA;AAAA,UAC5C,GACF;AAAA,WACF;AAAA,QAEA,qBAAC,cAAS,WAAU,aAClB;AAAA,+BAAC,YAAO,WAAU,oDAAmD;AAAA;AAAA,YACjC,aAAa;AAAA,YAAO;AAAA,aACxD;AAAA,UACA,oBAAC,SAAI,WAAU,2FACZ,iBAAO,QAAQ,iBAAiB,EAAE,IAAI,CAAC,CAAC,OAAO,GAAG,MACjD,qBAAC,SACC;AAAA,gCAAC,OAAE,WAAU,wDACV,gCAAsB,KAAK,GAC9B;AAAA,YACA,oBAAC,SAAI,WAAU,kCACZ,cAAI,IAAI,CAAC,OACR;AAAA,cAAC;AAAA;AAAA,gBAEC,OAAO,iBAAiB,EAAE;AAAA,gBAC1B,SAAS,aAAa,SAAS,EAAE;AAAA,gBACjC,UAAU,MAAM,OAAO,EAAE;AAAA;AAAA,cAHpB;AAAA,YAIP,CACD,GACH;AAAA,eAbQ,KAcV,CACD,GACH;AAAA,WACF;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC,OAAM;AAAA,YACN,SAAS;AAAA,YACT,UAAU,CAAC,MAAM,WAAW,EAAE,OAAO,OAAO;AAAA;AAAA,QAC9C;AAAA,SACF;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -0,0 +1,70 @@
1
+ const LABELS = {
2
+ nin: "National ID number (Nigeria)",
3
+ "nin-slip": "National ID slip (Nigeria)",
4
+ "advanced-nin": "Detailed national ID check (Nigeria)",
5
+ "premium-nin": "Full national ID check with photo (Nigeria)",
6
+ bvn: "Bank verification number (Nigeria)",
7
+ "validate-bvn": "Bank verification number match (Nigeria)",
8
+ "bvn-nin-selfie": "Bank number, national ID and selfie match (Nigeria)",
9
+ "phone-number": "Phone number",
10
+ "age-identity": "Age and identity",
11
+ passport: "Passport",
12
+ "drivers-license": "Driver's licence",
13
+ "voter-id": "Voter's card",
14
+ "ghana-card": "Ghana Card",
15
+ "ghana-passport": "Ghana passport",
16
+ "ghana-digital-address": "Ghana digital address",
17
+ "kenya-national-id": "Kenya national ID",
18
+ "kenya-passport": "Kenya passport",
19
+ "kenya-kra-pin": "Kenya tax number",
20
+ "uganda-nin": "Uganda national ID",
21
+ "uganda-voter-id": "Uganda voter's card",
22
+ "south-africa-national-id": "South Africa national ID",
23
+ "angola-national-id": "Angola national ID",
24
+ "zambia-nrc": "Zambia national registration card",
25
+ "zimbabwe-national-id": "Zimbabwe national ID",
26
+ "zimbabwe-credit-check": "Zimbabwe credit check",
27
+ "canada-ekyc": "Canada identity check",
28
+ "uk-ekyc": "UK identity check",
29
+ liveness: "Live selfie check",
30
+ "face-compare": "Selfie matches photo",
31
+ "face-doc-compare": "Selfie matches ID document",
32
+ "document-analysis": "ID document check",
33
+ "generic-ocr": "Text read from a document",
34
+ "utility-bill": "Utility bill",
35
+ "individual-address": "Home address",
36
+ "aml-screening": "Anti-money-laundering screening",
37
+ "user-screening": "Person screening (sanctions and watchlists)",
38
+ "email-check": "Email address check",
39
+ "ip-screening": "Device location check",
40
+ "phone-check": "Phone number check",
41
+ cac: "Company registration (Nigeria)",
42
+ tin: "Tax number",
43
+ "global-business-search": "Worldwide business search",
44
+ "kenya-business": "Kenya business registration",
45
+ "zambia-business": "Zambia business registration",
46
+ "business-document": "Business documents",
47
+ "business-address": "Business address",
48
+ "business-aml-screening": "Business screening (sanctions and watchlists)",
49
+ "business-owners": "Owners listed",
50
+ "business-profile": "Business profile completed",
51
+ "screening-clear": "Screening came back clear"
52
+ };
53
+ const GROUP_LABELS = {
54
+ Individual: "Checks on a person",
55
+ Business: "Checks on a business",
56
+ Readiness: "Account readiness (worked out automatically)"
57
+ };
58
+ function requirementLabel(code) {
59
+ if (LABELS[code]) return LABELS[code];
60
+ const spaced = code.replace(/[-_]+/g, " ").trim();
61
+ return spaced.charAt(0).toUpperCase() + spaced.slice(1);
62
+ }
63
+ function requirementGroupLabel(group) {
64
+ return GROUP_LABELS[group] ?? group;
65
+ }
66
+ export {
67
+ requirementGroupLabel,
68
+ requirementLabel
69
+ };
70
+ //# sourceMappingURL=requirement-labels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/admin/payout-tiers/requirement-labels.ts"],"sourcesContent":["/**\n * Plain-English names for the identity and business checks a payout level can\n * require. The codes are what the API stores; admins should never have to read them.\n */\nconst LABELS: Record<string, string> = {\n nin: 'National ID number (Nigeria)',\n 'nin-slip': 'National ID slip (Nigeria)',\n 'advanced-nin': 'Detailed national ID check (Nigeria)',\n 'premium-nin': 'Full national ID check with photo (Nigeria)',\n bvn: 'Bank verification number (Nigeria)',\n 'validate-bvn': 'Bank verification number match (Nigeria)',\n 'bvn-nin-selfie': 'Bank number, national ID and selfie match (Nigeria)',\n 'phone-number': 'Phone number',\n 'age-identity': 'Age and identity',\n passport: 'Passport',\n 'drivers-license': \"Driver's licence\",\n 'voter-id': \"Voter's card\",\n 'ghana-card': 'Ghana Card',\n 'ghana-passport': 'Ghana passport',\n 'ghana-digital-address': 'Ghana digital address',\n 'kenya-national-id': 'Kenya national ID',\n 'kenya-passport': 'Kenya passport',\n 'kenya-kra-pin': 'Kenya tax number',\n 'uganda-nin': 'Uganda national ID',\n 'uganda-voter-id': \"Uganda voter's card\",\n 'south-africa-national-id': 'South Africa national ID',\n 'angola-national-id': 'Angola national ID',\n 'zambia-nrc': 'Zambia national registration card',\n 'zimbabwe-national-id': 'Zimbabwe national ID',\n 'zimbabwe-credit-check': 'Zimbabwe credit check',\n 'canada-ekyc': 'Canada identity check',\n 'uk-ekyc': 'UK identity check',\n liveness: 'Live selfie check',\n 'face-compare': 'Selfie matches photo',\n 'face-doc-compare': 'Selfie matches ID document',\n 'document-analysis': 'ID document check',\n 'generic-ocr': 'Text read from a document',\n 'utility-bill': 'Utility bill',\n 'individual-address': 'Home address',\n 'aml-screening': 'Anti-money-laundering screening',\n 'user-screening': 'Person screening (sanctions and watchlists)',\n 'email-check': 'Email address check',\n 'ip-screening': 'Device location check',\n 'phone-check': 'Phone number check',\n cac: 'Company registration (Nigeria)',\n tin: 'Tax number',\n 'global-business-search': 'Worldwide business search',\n 'kenya-business': 'Kenya business registration',\n 'zambia-business': 'Zambia business registration',\n 'business-document': 'Business documents',\n 'business-address': 'Business address',\n 'business-aml-screening': 'Business screening (sanctions and watchlists)',\n 'business-owners': 'Owners listed',\n 'business-profile': 'Business profile completed',\n 'screening-clear': 'Screening came back clear',\n};\n\nconst GROUP_LABELS: Record<string, string> = {\n Individual: 'Checks on a person',\n Business: 'Checks on a business',\n Readiness: 'Account readiness (worked out automatically)',\n};\n\n/** \"bvn\" → \"Bank verification number (Nigeria)\". Unknown codes stay readable. */\nexport function requirementLabel(code: string): string {\n if (LABELS[code]) return LABELS[code];\n const spaced = code.replace(/[-_]+/g, ' ').trim();\n return spaced.charAt(0).toUpperCase() + spaced.slice(1);\n}\n\nexport function requirementGroupLabel(group: string): string {\n return GROUP_LABELS[group] ?? group;\n}\n"],"mappings":"AAIA,MAAM,SAAiC;AAAA,EACrC,KAAK;AAAA,EACL,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,KAAK;AAAA,EACL,gBAAgB;AAAA,EAChB,kBAAkB;AAAA,EAClB,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,mBAAmB;AAAA,EACnB,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB,yBAAyB;AAAA,EACzB,qBAAqB;AAAA,EACrB,kBAAkB;AAAA,EAClB,iBAAiB;AAAA,EACjB,cAAc;AAAA,EACd,mBAAmB;AAAA,EACnB,4BAA4B;AAAA,EAC5B,sBAAsB;AAAA,EACtB,cAAc;AAAA,EACd,wBAAwB;AAAA,EACxB,yBAAyB;AAAA,EACzB,eAAe;AAAA,EACf,WAAW;AAAA,EACX,UAAU;AAAA,EACV,gBAAgB;AAAA,EAChB,oBAAoB;AAAA,EACpB,qBAAqB;AAAA,EACrB,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,sBAAsB;AAAA,EACtB,iBAAiB;AAAA,EACjB,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,KAAK;AAAA,EACL,KAAK;AAAA,EACL,0BAA0B;AAAA,EAC1B,kBAAkB;AAAA,EAClB,mBAAmB;AAAA,EACnB,qBAAqB;AAAA,EACrB,oBAAoB;AAAA,EACpB,0BAA0B;AAAA,EAC1B,mBAAmB;AAAA,EACnB,oBAAoB;AAAA,EACpB,mBAAmB;AACrB;AAEA,MAAM,eAAuC;AAAA,EAC3C,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,WAAW;AACb;AAGO,SAAS,iBAAiB,MAAsB;AACrD,MAAI,OAAO,IAAI,EAAG,QAAO,OAAO,IAAI;AACpC,QAAM,SAAS,KAAK,QAAQ,UAAU,GAAG,EAAE,KAAK;AAChD,SAAO,OAAO,OAAO,CAAC,EAAE,YAAY,IAAI,OAAO,MAAM,CAAC;AACxD;AAEO,SAAS,sBAAsB,OAAuB;AAC3D,SAAO,aAAa,KAAK,KAAK;AAChC;","names":[]}
@@ -11,15 +11,16 @@ import {
11
11
  import { StatusBadge } from "@withpotter/components/shared/status-badge";
12
12
  import { limitLines, requirementRows } from "./tier-preview";
13
13
  import styles from "../_money/ledger.module.css";
14
+ import { requirementLabel } from "./requirement-labels";
14
15
  function TierPreviewResult({ preview }) {
15
16
  const { tier } = preview;
16
17
  const rows = requirementRows(preview);
17
18
  return /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 16 }, children: [
18
19
  /* @__PURE__ */ jsxs(Card, { children: [
19
- /* @__PURE__ */ jsx("h3", { className: styles.chosenName, children: tier ? `Lands on ${tier.label} in ${preview.currency}` : `No ${preview.currency} tier qualifies yet` }),
20
+ /* @__PURE__ */ jsx("h3", { className: styles.chosenName, children: tier ? `Gets the ${tier.label} level in ${preview.currency}` : `No ${preview.currency} level fits yet` }),
20
21
  tier ? /* @__PURE__ */ jsxs(FactList, { className: "mt-4", children: [
21
- /* @__PURE__ */ jsx(Fact, { label: "Tier", value: `${tier.label} (${tier.key})` }),
22
- /* @__PURE__ */ jsx(Fact, { label: "Position", value: String(tier.position) }),
22
+ /* @__PURE__ */ jsx(Fact, { label: "Level", value: tier.label }),
23
+ /* @__PURE__ */ jsx(Fact, { label: "Priority", value: String(tier.position) }),
23
24
  limitLines(tier).map(([label, value]) => /* @__PURE__ */ jsx(Fact, { label, value }, label)),
24
25
  /* @__PURE__ */ jsx(
25
26
  Fact,
@@ -28,23 +29,23 @@ function TierPreviewResult({ preview }) {
28
29
  value: tier.enabled ? "Enabled" : "Disabled"
29
30
  }
30
31
  )
31
- ] }) : /* @__PURE__ */ jsx("p", { className: styles.quiet, children: "With no tier, this business resolves to verification required." })
32
+ ] }) : /* @__PURE__ */ jsx("p", { className: styles.quiet, children: "This merchant has to verify their identity before they can send money out." })
32
33
  ] }),
33
34
  /* @__PURE__ */ jsx(
34
35
  TableShell,
35
36
  {
36
37
  minWidth: 0,
37
38
  head: /* @__PURE__ */ jsxs("tr", { children: [
38
- /* @__PURE__ */ jsx(Th, { children: "Requirement" }),
39
- /* @__PURE__ */ jsx(Th, { children: "State" })
39
+ /* @__PURE__ */ jsx(Th, { children: "Check" }),
40
+ /* @__PURE__ */ jsx(Th, { children: "Result" })
40
41
  ] }),
41
42
  children: rows.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: 2, className: styles.quiet, children: "Nothing verified and nothing required." }) }) : rows.map((r) => /* @__PURE__ */ jsxs(TableRow, { children: [
42
- /* @__PURE__ */ jsx("td", { children: r.name }),
43
- /* @__PURE__ */ jsx("td", { children: r.met ? /* @__PURE__ */ jsx(StatusBadge, { status: "completed", label: "Met" }) : /* @__PURE__ */ jsx(
43
+ /* @__PURE__ */ jsx("td", { children: requirementLabel(r.name) }),
44
+ /* @__PURE__ */ jsx("td", { children: r.met ? /* @__PURE__ */ jsx(StatusBadge, { status: "completed", label: "Done" }) : /* @__PURE__ */ jsx(
44
45
  StatusBadge,
45
46
  {
46
47
  status: "action_required",
47
- label: "Missing for the next tier"
48
+ label: "Still needed for the next level"
48
49
  }
49
50
  ) })
50
51
  ] }, r.name))