@classytic/pos-ui 0.1.0 → 1.2.0

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 (87) hide show
  1. package/LICENSE +75 -0
  2. package/dist/components/CartSidebar.d.ts +56 -0
  3. package/dist/components/CartSidebar.js +10 -9
  4. package/dist/components/PosTopBar.d.ts +19 -0
  5. package/dist/components/PosTopBar.js +4 -4
  6. package/dist/components/PrinterSettingsDialog.d.ts +8 -0
  7. package/dist/components/PrinterSettingsDialog.js +506 -20
  8. package/dist/components/ReceiptDeliveryDialog.d.ts +11 -0
  9. package/dist/components/ReceiptDeliveryDialog.js +98 -0
  10. package/dist/components/ReceiptReprintDialog.d.ts +10 -0
  11. package/dist/components/ReceiptReprintDialog.js +49 -22
  12. package/dist/components/index.d.ts +6 -0
  13. package/dist/components/index.js +7 -0
  14. package/dist/dashboard/components/CustomerLookupDialog.js +108 -90
  15. package/dist/dashboard/components/CustomerQuickAddDialog.js +3 -1
  16. package/dist/dashboard/components/DeliveryPanel.js +134 -0
  17. package/dist/dashboard/components/ProductCard.js +17 -7
  18. package/dist/dashboard/components/ProductsPanel.js +4 -4
  19. package/dist/dashboard/components/SplitPaymentPanel.js +5 -5
  20. package/dist/dashboard/components/VariantSelectorDialog.js +53 -11
  21. package/dist/dashboard/components/cart/AddChargeDialog.js +3 -2
  22. package/dist/dashboard/components/cart/CartItems.js +5 -5
  23. package/dist/dashboard/components/cart/CartSummary.js +6 -6
  24. package/dist/dashboard/components/cart/CustomerSection.js +3 -3
  25. package/dist/dashboard/components/cart/DiscountSection.js +4 -3
  26. package/dist/dashboard/components/cart/PointsRedemptionSection.js +5 -4
  27. package/dist/dashboard/pos.types.d.ts +32 -0
  28. package/dist/hardware/agent-adapter.js +127 -0
  29. package/dist/hardware/agent-pairing.d.ts +18 -0
  30. package/dist/hardware/agent-pairing.js +63 -0
  31. package/dist/hardware/context.d.ts +1 -5
  32. package/dist/hardware/index.d.ts +4 -3
  33. package/dist/hardware/index.js +3 -2
  34. package/dist/hardware/ports.d.ts +21 -4
  35. package/dist/hardware/tauri-adapter.d.ts +0 -1
  36. package/dist/hardware/tauri-adapter.js +19 -5
  37. package/dist/hardware/web-adapter.d.ts +22 -7
  38. package/dist/hardware/web-adapter.js +56 -14
  39. package/dist/hooks/branch-scoped-key.js +64 -0
  40. package/dist/hooks/index.d.ts +6 -0
  41. package/dist/hooks/index.js +7 -0
  42. package/dist/hooks/sale-attempt.js +62 -0
  43. package/dist/hooks/useManagerAuth.js +9 -1
  44. package/dist/hooks/usePosCart.d.ts +55 -0
  45. package/dist/hooks/usePosCart.js +224 -117
  46. package/dist/hooks/usePosCustomer.d.ts +31 -0
  47. package/dist/hooks/usePosCustomer.js +32 -78
  48. package/dist/hooks/usePosDelivery.d.ts +28 -0
  49. package/dist/hooks/usePosDelivery.js +133 -0
  50. package/dist/hooks/usePosMultiOrder.d.ts +22 -0
  51. package/dist/hooks/usePosMultiOrder.js +14 -4
  52. package/dist/hooks/usePosPayment.d.ts +22 -0
  53. package/dist/lib/delivery-payload.js +83 -0
  54. package/dist/lib/money.js +96 -23
  55. package/dist/node_modules/react-hook-form/dist/index.esm.js +468 -332
  56. package/dist/runtime/auth-port.d.ts +1 -5
  57. package/dist/runtime/branch-port.d.ts +1 -5
  58. package/dist/runtime/config.d.ts +43 -6
  59. package/dist/runtime/config.js +18 -1
  60. package/dist/runtime/index.d.ts +2 -2
  61. package/dist/runtime/index.js +2 -2
  62. package/dist/screens/OrderHistoryDrawer.d.ts +8 -0
  63. package/dist/screens/OrderHistoryDrawer.js +15 -15
  64. package/dist/screens/ParkedOrdersDrawer.d.ts +12 -0
  65. package/dist/screens/ParkedOrdersDrawer.js +3 -3
  66. package/dist/screens/PaymentScreen.d.ts +4 -0
  67. package/dist/screens/PaymentScreen.js +194 -33
  68. package/dist/screens/ProductScreen.d.ts +4 -0
  69. package/dist/screens/ProductScreen.js +13 -10
  70. package/dist/screens/ReceiptScreen.d.ts +4 -0
  71. package/dist/screens/ReceiptScreen.js +96 -72
  72. package/dist/screens/ShiftCloseScreen.d.ts +4 -0
  73. package/dist/screens/ShiftCloseScreen.js +18 -15
  74. package/dist/screens/ShiftOpenScreen.d.ts +4 -0
  75. package/dist/screens/ShiftOpenScreen.js +75 -19
  76. package/dist/screens/index.d.ts +8 -0
  77. package/dist/screens/index.js +9 -0
  78. package/dist/shell/index.d.ts +2 -0
  79. package/dist/shell/pos-shell.d.ts +41 -8
  80. package/dist/shell/pos-shell.js +25 -18
  81. package/dist/state/index.d.ts +3 -0
  82. package/dist/state/index.js +4 -0
  83. package/dist/state/pos-context.d.ts +13 -0
  84. package/dist/state/pos-state.d.ts +49 -0
  85. package/dist/state/pos-state.js +6 -7
  86. package/dist/utils/pos-helpers.js +137 -34
  87. package/package.json +27 -7
@@ -13,10 +13,17 @@ function getInvoke() {
13
13
  }
14
14
  var TauriReceiptPrinter = class {
15
15
  async print(payload) {
16
- await getInvoke()("printer_print", {
17
- payload,
18
- config: readPrinterConfig() ?? void 0
19
- });
16
+ const invoke = getInvoke();
17
+ const config = readPrinterConfig() ?? void 0;
18
+ try {
19
+ await invoke("printer_print", {
20
+ payload,
21
+ config
22
+ });
23
+ return "confirmed";
24
+ } catch {
25
+ return "unknown";
26
+ }
20
27
  }
21
28
  async isAvailable() {
22
29
  try {
@@ -28,7 +35,14 @@ var TauriReceiptPrinter = class {
28
35
  };
29
36
  var TauriCashDrawer = class {
30
37
  async open() {
31
- await getInvoke()("drawer_open", { config: readPrinterConfig() ?? void 0 });
38
+ const invoke = getInvoke();
39
+ const config = readPrinterConfig() ?? void 0;
40
+ try {
41
+ await invoke("drawer_open", { config });
42
+ return "confirmed";
43
+ } catch {
44
+ return "unknown";
45
+ }
32
46
  }
33
47
  async isAvailable() {
34
48
  try {
@@ -1,15 +1,30 @@
1
- import { PosHardware } from "./ports.js";
2
-
1
+ import { PosHardware, PrintOutcome } from "./ports.js";
3
2
  //#region src/hardware/web-adapter.d.ts
4
3
  /** DOM id of the rendered receipt — used by `printDocument` for print isolation. */
5
4
  declare const RECEIPT_DOM_ID = "pos-receipt";
5
+ /**
6
+ * The browser `window.print()` path, factored out so it can ALSO be called
7
+ * directly as an explicit cashier-visible fallback after a failed/unknown
8
+ * agent print (see Stream 1.4: "not an automatic retry that might create a
9
+ * duplicate receipt" — the fallback is a deliberate second action, not
10
+ * something this file chains on its own).
11
+ */
12
+ declare function printViaBrowserWindow(): Promise<PrintOutcome>;
6
13
  /**
7
14
  * Auto-detects the host: returns native Tauri adapters when the React
8
- * tree is running inside the `cashier-tauri` webview, otherwise returns
9
- * the browser stubs. Consumers stay agnostic — `<HardwareProvider>` in
10
- * `pos-shell.tsx` calls this once at mount and the same React tree
11
- * works on both surfaces with no per-component branching.
15
+ * tree is running inside the `cashier-tauri` webview; otherwise checks for a
16
+ * paired `@classytic/pos-agent` device (see `agent-pairing.ts`) and prefers
17
+ * it over the plain browser stubs. Consumers stay agnostic `<HardwareProvider>`
18
+ * in `pos-shell.tsx` calls this once at mount and the same React tree works
19
+ * across all three surfaces with no per-component branching.
20
+ *
21
+ * This read is SYNCHRONOUS (`readAgentPairing()`, localStorage) because
22
+ * `createWebHardware()` runs inside `useMemo` and must return a stable object
23
+ * immediately — see the plan's Stream 1.4 on why adapter selection cannot be
24
+ * probed asynchronously at boot. `PosAgentReceiptPrinter`/`PosAgentCashDrawer`
25
+ * are cheap to construct (no eager network call); the actual agent request
26
+ * happens lazily inside `print()`/`open()`/`isAvailable()`.
12
27
  */
13
28
  declare function createWebHardware(): PosHardware;
14
29
  //#endregion
15
- export { RECEIPT_DOM_ID, createWebHardware };
30
+ export { RECEIPT_DOM_ID, createWebHardware, printViaBrowserWindow };
@@ -1,4 +1,6 @@
1
1
  import { createTauriHardware, isTauri } from "./tauri-adapter.js";
2
+ import { readAgentPairing } from "./agent-pairing.js";
3
+ import { PosAgentCashDrawer, PosAgentReceiptPrinter } from "./agent-adapter.js";
2
4
  import { printDocument } from "@classytic/fluid/client/core";
3
5
 
4
6
  //#region src/hardware/web-adapter.ts
@@ -22,21 +24,53 @@ import { printDocument } from "@classytic/fluid/client/core";
22
24
  */
23
25
  /** DOM id of the rendered receipt — used by `printDocument` for print isolation. */
24
26
  const RECEIPT_DOM_ID = "pos-receipt";
25
- var WebReceiptPrinter = class {
26
- async print() {
27
- if (typeof window === "undefined") throw new Error("WebReceiptPrinter requires a browser window");
27
+ /**
28
+ * The browser `window.print()` path, factored out so it can ALSO be called
29
+ * directly as an explicit cashier-visible fallback after a failed/unknown
30
+ * agent print (see Stream 1.4: "not an automatic retry that might create a
31
+ * duplicate receipt" — the fallback is a deliberate second action, not
32
+ * something this file chains on its own).
33
+ */
34
+ async function printViaBrowserWindow() {
35
+ if (typeof window === "undefined") return "failed";
36
+ try {
28
37
  printDocument(RECEIPT_DOM_ID);
38
+ return "confirmed";
39
+ } catch {
40
+ return "failed";
41
+ }
42
+ }
43
+ var WebCashDrawer = class {
44
+ async open() {
45
+ console.warn("[pos/hardware] Cash drawer kick is unavailable in the browser adapter. Pair a printer via POS settings or use the Tauri shell.");
46
+ return "failed";
29
47
  }
30
48
  async isAvailable() {
31
- return typeof window !== "undefined";
49
+ return false;
32
50
  }
33
51
  };
34
- var WebCashDrawer = class {
52
+ var CompositeReceiptPrinter = class {
53
+ async print(payload, copyType) {
54
+ const pairing = readAgentPairing();
55
+ if (!pairing) return printViaBrowserWindow();
56
+ return new PosAgentReceiptPrinter(readAgentPairing, pairing.deviceId).print(payload, copyType);
57
+ }
58
+ async isAvailable() {
59
+ const pairing = readAgentPairing();
60
+ if (!pairing) return typeof window !== "undefined";
61
+ return new PosAgentReceiptPrinter(readAgentPairing, pairing.deviceId).isAvailable();
62
+ }
63
+ };
64
+ var CompositeCashDrawer = class {
35
65
  async open() {
36
- console.warn("[pos/hardware] Cash drawer kick is unavailable in the browser adapter. Install @classytic/pos-agent or use the Tauri shell.");
66
+ const pairing = readAgentPairing();
67
+ if (!pairing) return new WebCashDrawer().open();
68
+ return new PosAgentCashDrawer(readAgentPairing, pairing.deviceId).open();
37
69
  }
38
70
  async isAvailable() {
39
- return false;
71
+ const pairing = readAgentPairing();
72
+ if (!pairing) return false;
73
+ return new PosAgentCashDrawer(readAgentPairing, pairing.deviceId).isAvailable();
40
74
  }
41
75
  };
42
76
  var WebBarcodeScannerStub = class {
@@ -61,20 +95,28 @@ var WebPaymentTerminal = class {
61
95
  };
62
96
  /**
63
97
  * Auto-detects the host: returns native Tauri adapters when the React
64
- * tree is running inside the `cashier-tauri` webview, otherwise returns
65
- * the browser stubs. Consumers stay agnostic — `<HardwareProvider>` in
66
- * `pos-shell.tsx` calls this once at mount and the same React tree
67
- * works on both surfaces with no per-component branching.
98
+ * tree is running inside the `cashier-tauri` webview; otherwise checks for a
99
+ * paired `@classytic/pos-agent` device (see `agent-pairing.ts`) and prefers
100
+ * it over the plain browser stubs. Consumers stay agnostic `<HardwareProvider>`
101
+ * in `pos-shell.tsx` calls this once at mount and the same React tree works
102
+ * across all three surfaces with no per-component branching.
103
+ *
104
+ * This read is SYNCHRONOUS (`readAgentPairing()`, localStorage) because
105
+ * `createWebHardware()` runs inside `useMemo` and must return a stable object
106
+ * immediately — see the plan's Stream 1.4 on why adapter selection cannot be
107
+ * probed asynchronously at boot. `PosAgentReceiptPrinter`/`PosAgentCashDrawer`
108
+ * are cheap to construct (no eager network call); the actual agent request
109
+ * happens lazily inside `print()`/`open()`/`isAvailable()`.
68
110
  */
69
111
  function createWebHardware() {
70
112
  if (isTauri()) return createTauriHardware();
71
113
  return {
72
- printer: new WebReceiptPrinter(),
73
- drawer: new WebCashDrawer(),
114
+ printer: new CompositeReceiptPrinter(),
115
+ drawer: new CompositeCashDrawer(),
74
116
  scanner: new WebBarcodeScannerStub(),
75
117
  terminal: new WebPaymentTerminal()
76
118
  };
77
119
  }
78
120
 
79
121
  //#endregion
80
- export { RECEIPT_DOM_ID, createWebHardware };
122
+ export { RECEIPT_DOM_ID, createWebHardware, printViaBrowserWindow };
@@ -0,0 +1,64 @@
1
+ "use client";
2
+
3
+ //#region src/hooks/branch-scoped-key.ts
4
+ /**
5
+ * Per-branch localStorage keys for the register — and the ONE rule about them:
6
+ * **an unresolved branch is not a branch.**
7
+ *
8
+ * ## The bug this exists to make impossible
9
+ *
10
+ * `usePosCart` keyed its cart as `pos:cart:${branchId ?? "default"}` and
11
+ * `usePosMultiOrder` did the same for parked sales. `branchId` is
12
+ * `selectedBranch?.id`, so it is `undefined` for the first paint or two while the
13
+ * branch context resolves — and every other consumer in these screens already
14
+ * knows that (`{ enabled: !!branchId }`, `if (!branchId) return`). Only the
15
+ * storage key invented a value.
16
+ *
17
+ * The result, observed on a real register: a cashier taps a membership, gets an
18
+ * "Added to cart" toast, the line is written to `pos:cart:default`, and then the
19
+ * branch resolves, the hook's key becomes `pos:cart:<branchId>`, and the cart
20
+ * renders EMPTY. Nothing errored. The cashier re-rings the sale, or worse assumes
21
+ * the till is broken mid-queue. `PaymentScreen` reads the same key, so the line
22
+ * could not be paid for either.
23
+ *
24
+ * It is also a cross-branch leak in slow motion: `default` is shared by every
25
+ * branch, so whatever survives there surfaces at whichever till loads next.
26
+ *
27
+ * This is the codebase's documented failure mode (AGENTS.md "FAIL LOUD", rule 1
28
+ * and rule 3) in frontend clothing: a general fallback silently substituted for a
29
+ * specific value that had not arrived yet, producing a plausible screen instead
30
+ * of an error.
31
+ *
32
+ * ## Why a sentinel key rather than a conditional hook
33
+ *
34
+ * `useLocalStorage` needs a stable string key — a hook cannot be skipped. So an
35
+ * unresolved branch gets `UNRESOLVED_BRANCH_KEY`, and **every mutator refuses to
36
+ * write while that is the active key.** Nothing is ever stored under the
37
+ * sentinel, which is what makes the loss impossible: there is no orphan to lose.
38
+ * The window is under a second, and callers surface it (`ready === false`) rather
39
+ * than accepting taps into the void.
40
+ */
41
+ /**
42
+ * The key used while the branch is unknown. Deliberately unusable: no mutator
43
+ * writes to it, so it never accumulates state to be orphaned.
44
+ *
45
+ * `__` prefix so it cannot collide with a real branch id.
46
+ */
47
+ const UNRESOLVED_BRANCH = "__unresolved__";
48
+ /**
49
+ * Build a branch-scoped storage key.
50
+ *
51
+ * A whitespace-only or empty `branchId` counts as unresolved: it would produce
52
+ * `pos:cart:` and silently share one namespace across branches, which is the
53
+ * same defect wearing a different value.
54
+ */
55
+ function branchScopedKey(namespace, branchId) {
56
+ const resolved = typeof branchId === "string" && branchId.trim().length > 0;
57
+ return {
58
+ key: `${namespace}:${resolved ? branchId.trim() : UNRESOLVED_BRANCH}`,
59
+ ready: resolved
60
+ };
61
+ }
62
+
63
+ //#endregion
64
+ export { UNRESOLVED_BRANCH, branchScopedKey };
@@ -0,0 +1,6 @@
1
+ import { UsePosCartReturn, usePosCart } from "./usePosCart.js";
2
+ import { UsePosCustomerReturn, usePosCustomer } from "./usePosCustomer.js";
3
+ import { PaymentOption, SplitPaymentEntry, UsePosPaymentReturn, paymentNeedsReference, usePosPayment } from "./usePosPayment.js";
4
+ import { ParkedOrder, UsePosMultiOrderReturn, usePosMultiOrder } from "./usePosMultiOrder.js";
5
+ import { PosDeliveryAddress, PosDeliveryMethod, PosPaymentTerm, UsePosDeliveryReturn, usePosDelivery } from "./usePosDelivery.js";
6
+ export { ParkedOrder, type PaymentOption, PosDeliveryAddress, PosDeliveryMethod, PosPaymentTerm, type SplitPaymentEntry, UsePosCartReturn, UsePosCustomerReturn, UsePosDeliveryReturn, UsePosMultiOrderReturn, UsePosPaymentReturn, paymentNeedsReference, usePosCart, usePosCustomer, usePosDelivery, usePosMultiOrder, usePosPayment };
@@ -0,0 +1,7 @@
1
+ import { usePosCart } from "./usePosCart.js";
2
+ import { usePosCustomer } from "./usePosCustomer.js";
3
+ import { usePosMultiOrder } from "./usePosMultiOrder.js";
4
+ import { paymentNeedsReference, usePosPayment } from "./usePosPayment.js";
5
+ import { usePosDelivery } from "./usePosDelivery.js";
6
+
7
+ export { paymentNeedsReference, usePosCart, usePosCustomer, usePosDelivery, usePosMultiOrder, usePosPayment };
@@ -0,0 +1,62 @@
1
+ //#region src/hooks/sale-attempt.ts
2
+ /**
3
+ * The sale-attempt key — what makes a retry a RETRY instead of a second sale.
4
+ *
5
+ * ## The bug this exists to make impossible
6
+ *
7
+ * `PaymentScreen` sent `idempotencyKey: generateIdempotencyKey()` inline in the
8
+ * checkout payload. Every submit therefore carried a fresh UUID, which means the
9
+ * server saw every submit as a distinct sale. The path that costs money:
10
+ *
11
+ * cashier taps "Complete Sale" → request times out (the order COMMITTED, the
12
+ * response never arrived) → error toast → cashier taps again → new key → a
13
+ * second order, a second stock decrement, a second charge.
14
+ *
15
+ * Nothing errors. Both orders are valid. The drawer reconciles against two sales
16
+ * that the customer paid for once. This is AGENTS.md FAIL LOUD rule 3 — absence
17
+ * of an answer is not a negative answer — and the SDK docblock even warned that
18
+ * "a random idempotency-key fallback defeats deduplication while looking
19
+ * correctly wired."
20
+ *
21
+ * The server was never the problem. `packages/order`'s order model carries a
22
+ * partial-unique index on `idempotencyKey` and `@spinekit/pos` passes it as the
23
+ * placement's `winnerFilter`, so a repeated key replays the winning order rather
24
+ * than creating another. That guarantee was fully built and simply never
25
+ * reachable, because the client never sent the same key twice.
26
+ *
27
+ * ## The rule
28
+ *
29
+ * The key identifies the ATTEMPT — one cashier trying to close one cart — not
30
+ * the HTTP call. So it must:
31
+ *
32
+ * - be minted ONCE, when checkout starts;
33
+ * - SURVIVE a failure and a page reload (that is when retries happen);
34
+ * - be reused verbatim by every retry of that same sale;
35
+ * - be discarded only once the sale has actually succeeded.
36
+ *
37
+ * `usePosCart` binds that lifetime to branch-scoped storage and clears it in
38
+ * `resetCart()` — which already is the "this sale is finished" boundary. The two
39
+ * functions here are the pure part, split out so the rule is unit-testable
40
+ * without a DOM.
41
+ */
42
+ /** Mint a fresh attempt id. Prefixed so it is identifiable in server logs. */
43
+ function newSaleAttemptId() {
44
+ if (typeof globalThis.crypto?.randomUUID === "function") return `pos_${globalThis.crypto.randomUUID()}`;
45
+ return `pos_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
46
+ }
47
+ /**
48
+ * Reuse the stored attempt key if there is one, otherwise mint.
49
+ *
50
+ * The whole point is the FIRST branch: a stored key means an attempt is already
51
+ * in flight or has already failed once, and reusing it is what lets the server
52
+ * recognise the retry. Whitespace and `""` count as absent — a blank key
53
+ * type-checks as a string and dedupes nothing, so treating it as present would
54
+ * reinstate the bug with a value that merely looks set.
55
+ */
56
+ function resolveSaleAttemptKey(stored, mint = newSaleAttemptId) {
57
+ if (typeof stored === "string" && stored.trim().length > 0) return stored.trim();
58
+ return mint();
59
+ }
60
+
61
+ //#endregion
62
+ export { newSaleAttemptId, resolveSaleAttemptKey };
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { usePosAuth } from "../runtime/auth-port.js";
4
4
  import { useCallback, useState } from "react";
5
+ import { presentError } from "@classytic/fluid/error-contract";
5
6
  import { useMutation } from "@tanstack/react-query";
6
7
 
7
8
  //#region src/hooks/useManagerAuth.ts
@@ -74,7 +75,14 @@ function useManagerAuth(options = {}) {
74
75
  authorize,
75
76
  clearAuth,
76
77
  isPending: verifyMutation.isPending,
77
- error: verifyMutation.error?.message || null,
78
+ /**
79
+ * A single line for `ManagerAuthDialog`'s banner, resolved through the
80
+ * shared error contract so a refusal the server CODED (a role denial) does
81
+ * not read the same as an unreachable network. The dialog has one text
82
+ * slot, so the presentation's `message` is the whole thing — it is written
83
+ * to stand alone without its title.
84
+ */
85
+ error: verifyMutation.error ? presentError(verifyMutation.error, "Those credentials were not accepted.").message : null,
78
86
  reset: verifyMutation.reset
79
87
  };
80
88
  }
@@ -0,0 +1,55 @@
1
+ import { PosCartItem as PosCartItem$1 } from "../dashboard/pos.types.js";
2
+ import { PosProduct } from "@classytic/commerce-sdk/sales";
3
+ //#region src/hooks/usePosCart.d.ts
4
+ interface UsePosCartReturn {
5
+ cart: PosCartItem$1[];
6
+ membershipCardId: string;
7
+ pointsToRedeemInput: string;
8
+ discountInput: string;
9
+ setDiscountInput: (value: string) => void;
10
+ setMembershipCardId: (value: string) => void;
11
+ setPointsToRedeemInput: (value: string) => void;
12
+ addToCart: (product: PosProduct, variantSku?: string) => void;
13
+ /**
14
+ * Append a cashier-authored ad-hoc charge line to the cart (gift wrap,
15
+ * damaged-product reimbursement, custom handling).
16
+ *
17
+ * The label is what the cashier types; the system derives a
18
+ * slug-shaped `serviceCode` from it for reporting consistency
19
+ * (`Gift wrap` and `gift wrap` collapse to `gift-wrap`). `amountMajor`
20
+ * is in major units (matches `unitPrice` semantics on existing cart
21
+ * items); PaymentScreen converts to minor units at the order-create boundary.
22
+ */
23
+ addCharge: (label: string, amountMajor: number) => void;
24
+ updateQuantity: (index: number, delta: number) => void;
25
+ removeItem: (index: number) => void;
26
+ clearCart: () => void;
27
+ resetCart: () => void;
28
+ /** Replace the entire cart (used by resume-parked-order). */
29
+ replaceCart: (items: PosCartItem$1[]) => void;
30
+ /**
31
+ * The idempotency key for checking THIS cart out — minted on first call and
32
+ * then stable until `resetCart()`, which runs only after a sale succeeds.
33
+ *
34
+ * Call it at submit and send the result as `PosOrderPayload.idempotencyKey`.
35
+ * Because it is persisted per branch, a retry after a timeout — or after a
36
+ * reload — sends the SAME key, which is the only thing that lets the server's
37
+ * replay-the-winner index return the existing order instead of ringing a
38
+ * second sale. See `sale-attempt.ts` for the full account.
39
+ *
40
+ * Returns `null` while the branch is unresolved: an attempt cannot be scoped
41
+ * to a branch that does not exist yet, and minting into the sentinel namespace
42
+ * would produce a key that is discarded a moment later — which is the same
43
+ * defect wearing a different value.
44
+ */
45
+ beginSaleAttempt: () => string | null;
46
+ /**
47
+ * Is the cart bound to a resolved branch? While `false` every mutator refuses
48
+ * (see `branch-scoped-key.ts`) — gate the checkout affordance on it rather than
49
+ * letting a cashier discover it from an error toast.
50
+ */
51
+ ready: boolean;
52
+ }
53
+ declare function usePosCart(branchId?: string): UsePosCartReturn;
54
+ //#endregion
55
+ export { UsePosCartReturn, usePosCart };