@burdenoff/microfe-coolandlovely 2026.1101.8 → 2026.1102.1

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 (72) hide show
  1. package/dist/coolandlovely/components/WardrobeItemCard.d.ts +2 -1
  2. package/dist/coolandlovely/components/WardrobeItemCard.d.ts.map +1 -1
  3. package/dist/coolandlovely/components/WardrobeItemCard.js +4 -3
  4. package/dist/coolandlovely/components/WardrobeItemCard.js.map +1 -1
  5. package/dist/coolandlovely/components/prices/BestPriceBadge.d.ts +7 -0
  6. package/dist/coolandlovely/components/prices/BestPriceBadge.d.ts.map +1 -0
  7. package/dist/coolandlovely/components/prices/BestPriceBadge.js +22 -0
  8. package/dist/coolandlovely/components/prices/BestPriceBadge.js.map +1 -0
  9. package/dist/coolandlovely/components/prices/LookbookPriceBoard.d.ts +10 -0
  10. package/dist/coolandlovely/components/prices/LookbookPriceBoard.d.ts.map +1 -0
  11. package/dist/coolandlovely/components/prices/LookbookPriceBoard.js +120 -0
  12. package/dist/coolandlovely/components/prices/LookbookPriceBoard.js.map +1 -0
  13. package/dist/coolandlovely/components/prices/PriceComparison.d.ts +5 -0
  14. package/dist/coolandlovely/components/prices/PriceComparison.d.ts.map +1 -0
  15. package/dist/coolandlovely/components/prices/PriceComparison.js +95 -0
  16. package/dist/coolandlovely/components/prices/PriceComparison.js.map +1 -0
  17. package/dist/coolandlovely/components/prices/PriceCurrencySelect.d.ts +6 -0
  18. package/dist/coolandlovely/components/prices/PriceCurrencySelect.d.ts.map +1 -0
  19. package/dist/coolandlovely/components/prices/PriceCurrencySelect.js +38 -0
  20. package/dist/coolandlovely/components/prices/PriceCurrencySelect.js.map +1 -0
  21. package/dist/coolandlovely/components/prices/PricePanel.d.ts +6 -0
  22. package/dist/coolandlovely/components/prices/PricePanel.d.ts.map +1 -0
  23. package/dist/coolandlovely/components/prices/PricePanel.js +105 -0
  24. package/dist/coolandlovely/components/prices/PricePanel.js.map +1 -0
  25. package/dist/coolandlovely/components/prices/PriceWatchDisclosure.d.ts +2 -0
  26. package/dist/coolandlovely/components/prices/PriceWatchDisclosure.d.ts.map +1 -0
  27. package/dist/coolandlovely/components/prices/PriceWatchDisclosure.js +28 -0
  28. package/dist/coolandlovely/components/prices/PriceWatchDisclosure.js.map +1 -0
  29. package/dist/coolandlovely/components/prices/PriceWatchForm.d.ts +5 -0
  30. package/dist/coolandlovely/components/prices/PriceWatchForm.d.ts.map +1 -0
  31. package/dist/coolandlovely/components/prices/PriceWatchForm.js +89 -0
  32. package/dist/coolandlovely/components/prices/PriceWatchForm.js.map +1 -0
  33. package/dist/coolandlovely/components/prices/PriceWatches.d.ts +3 -0
  34. package/dist/coolandlovely/components/prices/PriceWatches.d.ts.map +1 -0
  35. package/dist/coolandlovely/components/prices/PriceWatches.js +189 -0
  36. package/dist/coolandlovely/components/prices/PriceWatches.js.map +1 -0
  37. package/dist/coolandlovely/components/prices/RetailerOfferLink.d.ts +6 -0
  38. package/dist/coolandlovely/components/prices/RetailerOfferLink.d.ts.map +1 -0
  39. package/dist/coolandlovely/components/prices/RetailerOfferLink.js +28 -0
  40. package/dist/coolandlovely/components/prices/RetailerOfferLink.js.map +1 -0
  41. package/dist/coolandlovely/components/prices/money.d.ts +6 -0
  42. package/dist/coolandlovely/components/prices/money.d.ts.map +1 -0
  43. package/dist/coolandlovely/components/prices/money.js +42 -0
  44. package/dist/coolandlovely/components/prices/money.js.map +1 -0
  45. package/dist/coolandlovely/hooks/usePriceApi.d.ts +57 -0
  46. package/dist/coolandlovely/hooks/usePriceApi.d.ts.map +1 -0
  47. package/dist/coolandlovely/hooks/usePriceApi.js +111 -0
  48. package/dist/coolandlovely/hooks/usePriceApi.js.map +1 -0
  49. package/dist/coolandlovely/pages/items/StoreListedPieceView.d.ts.map +1 -1
  50. package/dist/coolandlovely/pages/items/StoreListedPieceView.js +130 -123
  51. package/dist/coolandlovely/pages/items/StoreListedPieceView.js.map +1 -1
  52. package/dist/coolandlovely/pages/items/StyleItemDetailPage.d.ts.map +1 -1
  53. package/dist/coolandlovely/pages/items/StyleItemDetailPage.js +241 -230
  54. package/dist/coolandlovely/pages/items/StyleItemDetailPage.js.map +1 -1
  55. package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.d.ts.map +1 -1
  56. package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js +64 -119
  57. package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js.map +1 -1
  58. package/dist/coolandlovely/pages/profile/StyleProfilePage.d.ts.map +1 -1
  59. package/dist/coolandlovely/pages/profile/StyleProfilePage.js +214 -209
  60. package/dist/coolandlovely/pages/profile/StyleProfilePage.js.map +1 -1
  61. package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.d.ts.map +1 -1
  62. package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.js +83 -49
  63. package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.js.map +1 -1
  64. package/dist/generated/wspace-operations.d.ts +282 -2
  65. package/dist/generated/wspace-operations.d.ts.map +1 -1
  66. package/dist/generated/wspace-operations.js +87 -1
  67. package/dist/generated/wspace-operations.js.map +1 -1
  68. package/dist/generated/wspace-types.d.ts +224 -0
  69. package/dist/generated/wspace-types.d.ts.map +1 -1
  70. package/dist/generated/wspace-types.js +8 -0
  71. package/dist/generated/wspace-types.js.map +1 -0
  72. package/package.json +4 -3
@@ -0,0 +1,28 @@
1
+ import { Suspense as e, lazy as t, useState as n } from "react";
2
+ import { jsx as r, jsxs as i } from "react/jsx-runtime";
3
+ //#region src/coolandlovely/components/prices/PriceWatchDisclosure.tsx
4
+ var a = t(() => import("./PriceWatches.js").then((e) => ({ default: e.PriceWatches })));
5
+ function o() {
6
+ let [t, o] = n(!1);
7
+ return /* @__PURE__ */ i("div", {
8
+ className: "my-6 space-y-3",
9
+ children: [/* @__PURE__ */ r("button", {
10
+ type: "button",
11
+ "aria-expanded": t,
12
+ className: "rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken",
13
+ onClick: () => o((e) => !e),
14
+ children: "Manage my price watches"
15
+ }), t && /* @__PURE__ */ r(e, {
16
+ fallback: /* @__PURE__ */ r("p", {
17
+ role: "status",
18
+ className: "text-sm text-text-secondary",
19
+ children: "Loading price watches…"
20
+ }),
21
+ children: /* @__PURE__ */ r(a, {})
22
+ })]
23
+ });
24
+ }
25
+ //#endregion
26
+ export { o as PriceWatchDisclosure };
27
+
28
+ //# sourceMappingURL=PriceWatchDisclosure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PriceWatchDisclosure.js","names":[],"sources":["../../../../src/coolandlovely/components/prices/PriceWatchDisclosure.tsx"],"sourcesContent":["import { lazy, Suspense, useState } from 'react';\n\nconst PriceWatches = lazy(() =>\n import('./PriceWatches').then((module) => ({ default: module.PriceWatches }))\n);\n\nexport function PriceWatchDisclosure() {\n const [expanded, setExpanded] = useState(false);\n return (\n <div className=\"my-6 space-y-3\">\n <button\n type=\"button\"\n aria-expanded={expanded}\n className=\"rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n onClick={() => setExpanded((value) => !value)}\n >\n Manage my price watches\n </button>\n {expanded && (\n <Suspense\n fallback={\n <p role=\"status\" className=\"text-sm text-text-secondary\">\n Loading price watches…\n </p>\n }\n >\n <PriceWatches />\n </Suspense>\n )}\n </div>\n );\n}\n"],"mappings":";;;AAEA,IAAM,IAAe,QACnB,OAAO,oBAAiB,CAAC,MAAM,OAAY,EAAE,SAAS,EAAO,aAAa,EAAE,CAC9E;AAEA,SAAgB,IAAuB;CACrC,IAAM,CAAC,GAAU,KAAe,EAAS,EAAK;CAC9C,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,UAAD;GACE,MAAK;GACL,iBAAe;GACf,WAAU;GACV,eAAe,GAAa,MAAU,CAAC,CAAK;aAC7C;EAEO,CAAA,GACP,KACC,kBAAC,GAAD;GACE,UACE,kBAAC,KAAD;IAAG,MAAK;IAAS,WAAU;cAA8B;GAEtD,CAAA;aAGL,kBAAC,GAAD,CAAe,CAAA;EACP,CAAA,CAET;;AAET"}
@@ -0,0 +1,5 @@
1
+ export declare function PriceWatchForm({ styleItemId, currency, }: {
2
+ styleItemId: string;
3
+ currency: string;
4
+ }): import("react").JSX.Element;
5
+ //# sourceMappingURL=PriceWatchForm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PriceWatchForm.d.ts","sourceRoot":"","sources":["../../../../src/coolandlovely/components/prices/PriceWatchForm.tsx"],"names":[],"mappings":"AAQA,wBAAgB,cAAc,CAAC,EAC7B,WAAW,EACX,QAAQ,GACT,EAAE;IACD,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB,+BA2EA"}
@@ -0,0 +1,89 @@
1
+ import { usePriceWatchAction as e } from "../../hooks/usePriceApi.js";
2
+ import { retailerTargetExample as t, retailerTargetMinor as n } from "./money.js";
3
+ import { CooPriceAlertKind as r } from "../../../generated/wspace-types.js";
4
+ import { useId as i, useState as a } from "react";
5
+ import { jsx as o, jsxs as s } from "react/jsx-runtime";
6
+ //#region src/coolandlovely/components/prices/PriceWatchForm.tsx
7
+ var c = "rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken disabled:opacity-50";
8
+ function l({ styleItemId: l, currency: u }) {
9
+ let [d, f] = a(""), [p, m] = a(""), [h, g] = a(""), _ = i(), v = e(), y = async (e) => {
10
+ g(""), m("");
11
+ try {
12
+ let t = e === r.PriceDrop && d ? n(d, u) : null;
13
+ await v.mutateAsync({
14
+ kind: "save",
15
+ input: {
16
+ styleItemId: l,
17
+ currency: u,
18
+ kind: e,
19
+ targetPriceMinor: t
20
+ }
21
+ }), m(e === r.PriceDrop ? "Price watch saved. Monitoring starts after an initial check." : "Stock watch saved. You will be notified when fresh reports show a return from out of stock.");
22
+ } catch (e) {
23
+ g(e instanceof Error ? e.message : "The price watch could not be saved.");
24
+ }
25
+ };
26
+ return /* @__PURE__ */ s("div", {
27
+ className: "space-y-3 border-t border-border-seam pt-4",
28
+ children: [
29
+ /* @__PURE__ */ o("h3", {
30
+ className: "font-semibold text-text-primary",
31
+ children: "Watch this piece"
32
+ }),
33
+ /* @__PURE__ */ o("p", {
34
+ className: "text-sm text-text-secondary",
35
+ children: "New watches start with an initial check. Alerts need fresh retailer data and apply to future changes."
36
+ }),
37
+ /* @__PURE__ */ s("div", {
38
+ className: "flex flex-wrap items-end gap-2",
39
+ children: [
40
+ /* @__PURE__ */ s("label", {
41
+ className: "flex flex-col gap-1 text-sm text-text-secondary",
42
+ htmlFor: _,
43
+ children: [
44
+ "Future target in ",
45
+ u,
46
+ " (optional)",
47
+ /* @__PURE__ */ o("input", {
48
+ id: _,
49
+ inputMode: "decimal",
50
+ className: "rounded-lg border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary",
51
+ value: d,
52
+ onChange: (e) => f(e.target.value),
53
+ placeholder: t(u)
54
+ })
55
+ ]
56
+ }),
57
+ /* @__PURE__ */ o("button", {
58
+ type: "button",
59
+ className: c,
60
+ disabled: v.isPending,
61
+ onClick: () => void y(r.PriceDrop),
62
+ children: "Watch price drops"
63
+ }),
64
+ /* @__PURE__ */ o("button", {
65
+ type: "button",
66
+ className: c,
67
+ disabled: v.isPending,
68
+ onClick: () => void y(r.BackInStock),
69
+ children: "Watch restocks"
70
+ })
71
+ ]
72
+ }),
73
+ p && /* @__PURE__ */ o("p", {
74
+ role: "status",
75
+ className: "text-sm text-status-success-text",
76
+ children: p
77
+ }),
78
+ h && /* @__PURE__ */ o("p", {
79
+ role: "alert",
80
+ className: "text-sm text-status-error-text",
81
+ children: h
82
+ })
83
+ ]
84
+ });
85
+ }
86
+ //#endregion
87
+ export { l as PriceWatchForm };
88
+
89
+ //# sourceMappingURL=PriceWatchForm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PriceWatchForm.js","names":[],"sources":["../../../../src/coolandlovely/components/prices/PriceWatchForm.tsx"],"sourcesContent":["import { useId, useState } from 'react';\nimport { CooPriceAlertKind } from '../../../generated/wspace-types';\nimport { usePriceWatchAction } from '../../hooks/usePriceApi';\nimport { retailerTargetExample, retailerTargetMinor } from './money';\n\nconst buttonClass =\n 'rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken disabled:opacity-50';\n\nexport function PriceWatchForm({\n styleItemId,\n currency,\n}: {\n styleItemId: string;\n currency: string;\n}) {\n const [target, setTarget] = useState('');\n const [message, setMessage] = useState('');\n const [inputError, setInputError] = useState('');\n const targetId = useId();\n const action = usePriceWatchAction();\n const save = async (kind: CooPriceAlertKind) => {\n setInputError('');\n setMessage('');\n try {\n const targetPriceMinor =\n kind === CooPriceAlertKind.PriceDrop && target\n ? retailerTargetMinor(target, currency)\n : null;\n await action.mutateAsync({\n kind: 'save',\n input: { styleItemId, currency, kind, targetPriceMinor },\n });\n setMessage(\n kind === CooPriceAlertKind.PriceDrop\n ? 'Price watch saved. Monitoring starts after an initial check.'\n : 'Stock watch saved. You will be notified when fresh reports show a return from out of stock.'\n );\n } catch (error) {\n setInputError(error instanceof Error ? error.message : 'The price watch could not be saved.');\n }\n };\n return (\n <div className=\"space-y-3 border-t border-border-seam pt-4\">\n <h3 className=\"font-semibold text-text-primary\">Watch this piece</h3>\n <p className=\"text-sm text-text-secondary\">\n New watches start with an initial check. Alerts need fresh retailer data and apply to future\n changes.\n </p>\n <div className=\"flex flex-wrap items-end gap-2\">\n <label className=\"flex flex-col gap-1 text-sm text-text-secondary\" htmlFor={targetId}>\n Future target in {currency} (optional)\n <input\n id={targetId}\n inputMode=\"decimal\"\n className=\"rounded-lg border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n value={target}\n onChange={(event) => setTarget(event.target.value)}\n placeholder={retailerTargetExample(currency)}\n />\n </label>\n <button\n type=\"button\"\n className={buttonClass}\n disabled={action.isPending}\n onClick={() => void save(CooPriceAlertKind.PriceDrop)}\n >\n Watch price drops\n </button>\n <button\n type=\"button\"\n className={buttonClass}\n disabled={action.isPending}\n onClick={() => void save(CooPriceAlertKind.BackInStock)}\n >\n Watch restocks\n </button>\n </div>\n {message && (\n <p role=\"status\" className=\"text-sm text-status-success-text\">\n {message}\n </p>\n )}\n {inputError && (\n <p role=\"alert\" className=\"text-sm text-status-error-text\">\n {inputError}\n </p>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;AAKA,IAAM,IACJ;AAEF,SAAgB,EAAe,EAC7B,gBACA,eAIC;CACD,IAAM,CAAC,GAAQ,KAAa,EAAS,EAAE,GACjC,CAAC,GAAS,KAAc,EAAS,EAAE,GACnC,CAAC,GAAY,KAAiB,EAAS,EAAE,GACzC,IAAW,EAAM,GACjB,IAAS,EAAoB,GAC7B,IAAO,OAAO,MAA4B;EAE9C,AADA,EAAc,EAAE,GAChB,EAAW,EAAE;EACb,IAAI;GACF,IAAM,IACJ,MAAS,EAAkB,aAAa,IACpC,EAAoB,GAAQ,CAAQ,IACpC;GAKN,AAJA,MAAM,EAAO,YAAY;IACvB,MAAM;IACN,OAAO;KAAE;KAAa;KAAU;KAAM;IAAiB;GACzD,CAAC,GACD,EACE,MAAS,EAAkB,YACvB,iEACA,6FACN;EACF,SAAS,GAAO;GACd,EAAc,aAAiB,QAAQ,EAAM,UAAU,qCAAqC;EAC9F;CACF;CACA,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,MAAD;IAAI,WAAU;cAAkC;GAAoB,CAAA;GACpE,kBAAC,KAAD;IAAG,WAAU;cAA8B;GAGxC,CAAA;GACH,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,SAAD;MAAO,WAAU;MAAkD,SAAS;gBAA5E;OAAsF;OAClE;OAAS;OAC3B,kBAAC,SAAD;QACE,IAAI;QACJ,WAAU;QACV,WAAU;QACV,OAAO;QACP,WAAW,MAAU,EAAU,EAAM,OAAO,KAAK;QACjD,aAAa,EAAsB,CAAQ;OAC5C,CAAA;MACI;;KACP,kBAAC,UAAD;MACE,MAAK;MACL,WAAW;MACX,UAAU,EAAO;MACjB,eAAe,KAAK,EAAK,EAAkB,SAAS;gBACrD;KAEO,CAAA;KACR,kBAAC,UAAD;MACE,MAAK;MACL,WAAW;MACX,UAAU,EAAO;MACjB,eAAe,KAAK,EAAK,EAAkB,WAAW;gBACvD;KAEO,CAAA;IACL;;GACJ,KACC,kBAAC,KAAD;IAAG,MAAK;IAAS,WAAU;cACxB;GACA,CAAA;GAEJ,KACC,kBAAC,KAAD;IAAG,MAAK;IAAQ,WAAU;cACvB;GACA,CAAA;EAEF;;AAET"}
@@ -0,0 +1,3 @@
1
+ /** Mounted lazily from Style Profile; actor/context changes remount through the scoped Root. */
2
+ export declare function PriceWatches(): import("react").JSX.Element;
3
+ //# sourceMappingURL=PriceWatches.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PriceWatches.d.ts","sourceRoot":"","sources":["../../../../src/coolandlovely/components/prices/PriceWatches.tsx"],"names":[],"mappings":"AAgFA,gGAAgG;AAChG,wBAAgB,YAAY,gCAiH3B"}
@@ -0,0 +1,189 @@
1
+ import { useCoolAndLovely as e } from "../../context/CoolAndLovelyContext.js";
2
+ import "../../context/index.js";
3
+ import { usePriceWatchAction as t, usePriceWatchDeliveries as n, usePriceWatches as r } from "../../hooks/usePriceApi.js";
4
+ import { formatRetailerPrice as i } from "./money.js";
5
+ import { useState as a } from "react";
6
+ import { jsx as o, jsxs as s } from "react/jsx-runtime";
7
+ //#region src/coolandlovely/components/prices/PriceWatches.tsx
8
+ var c = {
9
+ DELIVERED: "Sent",
10
+ PENDING: "Waiting",
11
+ CLAIMED: "Sending",
12
+ FAILED: "Could not send"
13
+ }, l = "rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken disabled:opacity-50";
14
+ function u({ alertId: e }) {
15
+ let [t, r] = a(0), i = n(e, t, !0);
16
+ return i.isPending ? /* @__PURE__ */ o("p", {
17
+ role: "status",
18
+ className: "text-sm text-text-secondary",
19
+ children: "Loading delivery receipts…"
20
+ }) : i.isError ? /* @__PURE__ */ s("p", {
21
+ role: "alert",
22
+ className: "text-sm text-status-error-text",
23
+ children: [
24
+ "Receipts could not be loaded.",
25
+ " ",
26
+ /* @__PURE__ */ o("button", {
27
+ type: "button",
28
+ className: l,
29
+ onClick: () => void i.refetch(),
30
+ children: "Retry"
31
+ })
32
+ ]
33
+ }) : /* @__PURE__ */ s("div", {
34
+ className: "space-y-2",
35
+ children: [
36
+ /* @__PURE__ */ o("p", {
37
+ className: "text-xs text-text-secondary",
38
+ children: "A delivered receipt confirms platform acceptance. Device display and retries can differ; delivery is at least once."
39
+ }),
40
+ i.data?.items.length ? /* @__PURE__ */ o("ul", {
41
+ className: "space-y-1 text-sm text-text-secondary",
42
+ children: i.data.items.map((e) => /* @__PURE__ */ s("li", { children: [
43
+ c[e.status] ?? "Status unavailable",
44
+ " ·",
45
+ " ",
46
+ new Date(e.createdAt).toLocaleString(),
47
+ e.deliveredAt ? ` · accepted ${new Date(e.deliveredAt).toLocaleString()}` : ""
48
+ ] }, e.id))
49
+ }) : /* @__PURE__ */ o("p", {
50
+ className: "text-sm text-text-secondary",
51
+ children: "No transitions recorded yet."
52
+ }),
53
+ /* @__PURE__ */ s("div", {
54
+ className: "flex gap-2",
55
+ children: [/* @__PURE__ */ o("button", {
56
+ type: "button",
57
+ className: l,
58
+ disabled: t === 0,
59
+ onClick: () => r((e) => Math.max(0, e - 20)),
60
+ children: "Previous receipts"
61
+ }), /* @__PURE__ */ o("button", {
62
+ type: "button",
63
+ className: l,
64
+ disabled: !i.data?.pageInfo.hasMore,
65
+ onClick: () => r((e) => e + 20),
66
+ children: "Next receipts"
67
+ })]
68
+ })
69
+ ]
70
+ });
71
+ }
72
+ function d() {
73
+ let [n, c] = a(0), [d, f] = a(null), p = r(n), m = t(), { navigateTo: h } = e();
74
+ return /* @__PURE__ */ s("section", {
75
+ className: "space-y-4 rounded-xl border border-border-seam bg-bg-surface p-4",
76
+ "aria-label": "My price watches",
77
+ children: [
78
+ /* @__PURE__ */ o("h2", {
79
+ className: "text-lg font-semibold text-text-primary",
80
+ children: "My price watches"
81
+ }),
82
+ /* @__PURE__ */ o("p", {
83
+ className: "text-sm text-text-secondary",
84
+ children: "Private watches for pieces you selected. Manage currency and targets from a piece’s retailer comparison. Withdrawn private pieces disable their watches automatically."
85
+ }),
86
+ p.isPending ? /* @__PURE__ */ o("p", {
87
+ role: "status",
88
+ className: "text-sm text-text-secondary",
89
+ children: "Loading watches…"
90
+ }) : p.isError ? /* @__PURE__ */ s("p", {
91
+ role: "alert",
92
+ className: "text-sm text-status-error-text",
93
+ children: [
94
+ "Watches could not be loaded.",
95
+ " ",
96
+ /* @__PURE__ */ o("button", {
97
+ type: "button",
98
+ className: l,
99
+ onClick: () => void p.refetch(),
100
+ children: "Retry"
101
+ })
102
+ ]
103
+ }) : p.data?.items.length ? /* @__PURE__ */ o("ul", {
104
+ className: "space-y-3",
105
+ children: p.data.items.map((e) => /* @__PURE__ */ s("li", {
106
+ className: "space-y-3 rounded-lg border border-border-seam p-3",
107
+ children: [/* @__PURE__ */ s("div", {
108
+ className: "flex flex-wrap items-center justify-between gap-3",
109
+ children: [/* @__PURE__ */ s("div", { children: [
110
+ /* @__PURE__ */ o("button", {
111
+ type: "button",
112
+ className: "text-left text-sm font-semibold text-text-primary underline",
113
+ onClick: () => h(`/items/${e.styleItemId}`),
114
+ children: "View watched piece"
115
+ }),
116
+ /* @__PURE__ */ s("p", {
117
+ className: "text-sm text-text-secondary",
118
+ children: [
119
+ e.kind === "PRICE_DROP" ? "Price drop" : "Back in stock",
120
+ " ·",
121
+ " ",
122
+ e.currency,
123
+ e.targetPriceMinor == null ? "" : ` · at or below ${i(e.targetPriceMinor, e.currency)}`,
124
+ " ",
125
+ "· ",
126
+ e.enabled ? "Enabled" : "Disabled"
127
+ ]
128
+ }),
129
+ /* @__PURE__ */ o("p", {
130
+ className: "text-xs text-text-secondary",
131
+ children: e.lastEvaluatedAt ? `Last checked ${new Date(e.lastEvaluatedAt).toLocaleString()}` : "Waiting for first check"
132
+ })
133
+ ] }), /* @__PURE__ */ s("div", {
134
+ className: "flex flex-wrap gap-2",
135
+ children: [/* @__PURE__ */ o("button", {
136
+ type: "button",
137
+ className: l,
138
+ "aria-expanded": d === e.id,
139
+ onClick: () => f((t) => t === e.id ? null : e.id),
140
+ children: "Delivery receipts"
141
+ }), /* @__PURE__ */ o("button", {
142
+ type: "button",
143
+ className: l,
144
+ disabled: m.isPending,
145
+ onClick: () => m.mutate({
146
+ kind: "remove",
147
+ id: e.id
148
+ }),
149
+ children: "Remove watch"
150
+ })]
151
+ })]
152
+ }), d === e.id && /* @__PURE__ */ o(u, { alertId: e.id }, e.id)]
153
+ }, e.id))
154
+ }) : /* @__PURE__ */ o("p", {
155
+ className: "text-sm text-text-secondary",
156
+ children: "No price watches on this page. Open a piece’s retailer comparison to create one."
157
+ }),
158
+ m.isError && /* @__PURE__ */ o("p", {
159
+ role: "alert",
160
+ className: "text-sm text-status-error-text",
161
+ children: m.error.message
162
+ }),
163
+ /* @__PURE__ */ s("div", {
164
+ className: "flex gap-2",
165
+ children: [/* @__PURE__ */ o("button", {
166
+ type: "button",
167
+ className: l,
168
+ disabled: n === 0 || p.isFetching,
169
+ onClick: () => {
170
+ c((e) => Math.max(0, e - 20)), f(null);
171
+ },
172
+ children: "Previous watches"
173
+ }), /* @__PURE__ */ o("button", {
174
+ type: "button",
175
+ className: l,
176
+ disabled: !p.data?.pageInfo.hasMore || p.isFetching,
177
+ onClick: () => {
178
+ c((e) => e + 20), f(null);
179
+ },
180
+ children: "Next watches"
181
+ })]
182
+ })
183
+ ]
184
+ });
185
+ }
186
+ //#endregion
187
+ export { d as PriceWatches };
188
+
189
+ //# sourceMappingURL=PriceWatches.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PriceWatches.js","names":[],"sources":["../../../../src/coolandlovely/components/prices/PriceWatches.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useCoolAndLovely } from '../../context';\nimport {\n usePriceWatchAction,\n usePriceWatchDeliveries,\n usePriceWatches,\n} from '../../hooks/usePriceApi';\nimport { formatRetailerPrice } from './money';\n\nconst receiptStatusLabels: Record<string, string> = {\n DELIVERED: 'Sent',\n PENDING: 'Waiting',\n CLAIMED: 'Sending',\n FAILED: 'Could not send',\n};\n\nconst buttonClass =\n 'rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken disabled:opacity-50';\n\nfunction WatchReceipts({ alertId }: { alertId: string }) {\n const [offset, setOffset] = useState(0);\n const query = usePriceWatchDeliveries(alertId, offset, true);\n if (query.isPending)\n return (\n <p role=\"status\" className=\"text-sm text-text-secondary\">\n Loading delivery receipts…\n </p>\n );\n if (query.isError)\n return (\n <p role=\"alert\" className=\"text-sm text-status-error-text\">\n Receipts could not be loaded.{' '}\n <button type=\"button\" className={buttonClass} onClick={() => void query.refetch()}>\n Retry\n </button>\n </p>\n );\n return (\n <div className=\"space-y-2\">\n <p className=\"text-xs text-text-secondary\">\n A delivered receipt confirms platform acceptance. Device display and retries can differ;\n delivery is at least once.\n </p>\n {query.data?.items.length ? (\n <ul className=\"space-y-1 text-sm text-text-secondary\">\n {query.data.items.map((entry) => (\n <li key={entry.id}>\n {receiptStatusLabels[entry.status] ?? 'Status unavailable'} ·{' '}\n {new Date(entry.createdAt).toLocaleString()}\n {entry.deliveredAt\n ? ` · accepted ${new Date(entry.deliveredAt).toLocaleString()}`\n : ''}\n </li>\n ))}\n </ul>\n ) : (\n <p className=\"text-sm text-text-secondary\">No transitions recorded yet.</p>\n )}\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n className={buttonClass}\n disabled={offset === 0}\n onClick={() => setOffset((value) => Math.max(0, value - 20))}\n >\n Previous receipts\n </button>\n <button\n type=\"button\"\n className={buttonClass}\n disabled={!query.data?.pageInfo.hasMore}\n onClick={() => setOffset((value) => value + 20)}\n >\n Next receipts\n </button>\n </div>\n </div>\n );\n}\n\n/** Mounted lazily from Style Profile; actor/context changes remount through the scoped Root. */\nexport function PriceWatches() {\n const [offset, setOffset] = useState(0);\n const [receiptId, setReceiptId] = useState<string | null>(null);\n const query = usePriceWatches(offset);\n const action = usePriceWatchAction();\n const { navigateTo } = useCoolAndLovely();\n return (\n <section\n className=\"space-y-4 rounded-xl border border-border-seam bg-bg-surface p-4\"\n aria-label=\"My price watches\"\n >\n <h2 className=\"text-lg font-semibold text-text-primary\">My price watches</h2>\n <p className=\"text-sm text-text-secondary\">\n Private watches for pieces you selected. Manage currency and targets from a piece’s retailer\n comparison. Withdrawn private pieces disable their watches automatically.\n </p>\n {query.isPending ? (\n <p role=\"status\" className=\"text-sm text-text-secondary\">\n Loading watches…\n </p>\n ) : query.isError ? (\n <p role=\"alert\" className=\"text-sm text-status-error-text\">\n Watches could not be loaded.{' '}\n <button type=\"button\" className={buttonClass} onClick={() => void query.refetch()}>\n Retry\n </button>\n </p>\n ) : query.data?.items.length ? (\n <ul className=\"space-y-3\">\n {query.data.items.map((watch) => (\n <li key={watch.id} className=\"space-y-3 rounded-lg border border-border-seam p-3\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div>\n <button\n type=\"button\"\n className=\"text-left text-sm font-semibold text-text-primary underline\"\n onClick={() => navigateTo(`/items/${watch.styleItemId}`)}\n >\n View watched piece\n </button>\n <p className=\"text-sm text-text-secondary\">\n {watch.kind === 'PRICE_DROP' ? 'Price drop' : 'Back in stock'} ·{' '}\n {watch.currency}\n {watch.targetPriceMinor != null\n ? ` · at or below ${formatRetailerPrice(watch.targetPriceMinor, watch.currency)}`\n : ''}{' '}\n · {watch.enabled ? 'Enabled' : 'Disabled'}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {watch.lastEvaluatedAt\n ? `Last checked ${new Date(watch.lastEvaluatedAt).toLocaleString()}`\n : 'Waiting for first check'}\n </p>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n <button\n type=\"button\"\n className={buttonClass}\n aria-expanded={receiptId === watch.id}\n onClick={() => setReceiptId((value) => (value === watch.id ? null : watch.id))}\n >\n Delivery receipts\n </button>\n <button\n type=\"button\"\n className={buttonClass}\n disabled={action.isPending}\n onClick={() => action.mutate({ kind: 'remove', id: watch.id })}\n >\n Remove watch\n </button>\n </div>\n </div>\n {receiptId === watch.id && <WatchReceipts key={watch.id} alertId={watch.id} />}\n </li>\n ))}\n </ul>\n ) : (\n <p className=\"text-sm text-text-secondary\">\n No price watches on this page. Open a piece’s retailer comparison to create one.\n </p>\n )}\n {action.isError && (\n <p role=\"alert\" className=\"text-sm text-status-error-text\">\n {action.error.message}\n </p>\n )}\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n className={buttonClass}\n disabled={offset === 0 || query.isFetching}\n onClick={() => {\n setOffset((value) => Math.max(0, value - 20));\n setReceiptId(null);\n }}\n >\n Previous watches\n </button>\n <button\n type=\"button\"\n className={buttonClass}\n disabled={!query.data?.pageInfo.hasMore || query.isFetching}\n onClick={() => {\n setOffset((value) => value + 20);\n setReceiptId(null);\n }}\n >\n Next watches\n </button>\n </div>\n </section>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export declare function RetailerOfferLink({ url, priceMinor, currency, }: {
2
+ url: string;
3
+ priceMinor: number;
4
+ currency: string;
5
+ }): import("react").JSX.Element;
6
+ //# sourceMappingURL=RetailerOfferLink.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RetailerOfferLink.d.ts","sourceRoot":"","sources":["../../../../src/coolandlovely/components/prices/RetailerOfferLink.tsx"],"names":[],"mappings":"AAYA,wBAAgB,iBAAiB,CAAC,EAChC,GAAG,EACH,UAAU,EACV,QAAQ,GACT,EAAE;IACD,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB,+BAeA"}
@@ -0,0 +1,28 @@
1
+ import { formatRetailerPrice as e } from "./money.js";
2
+ import { jsxs as t } from "react/jsx-runtime";
3
+ //#region src/coolandlovely/components/prices/RetailerOfferLink.tsx
4
+ function n(e) {
5
+ try {
6
+ let t = new URL(e);
7
+ return t.protocol === "https:" && !t.username && !t.password ? t.href : null;
8
+ } catch {
9
+ return null;
10
+ }
11
+ }
12
+ function r({ url: r, priceMinor: i, currency: a }) {
13
+ let o = n(r), s = e(i, a);
14
+ return o ? /* @__PURE__ */ t("a", {
15
+ className: "rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken",
16
+ href: o,
17
+ target: "_blank",
18
+ rel: "noopener noreferrer",
19
+ children: [s, " · Visit retailer"]
20
+ }) : /* @__PURE__ */ t("span", {
21
+ className: "text-sm text-text-secondary",
22
+ children: [s, " · Retailer link unavailable"]
23
+ });
24
+ }
25
+ //#endregion
26
+ export { r as RetailerOfferLink };
27
+
28
+ //# sourceMappingURL=RetailerOfferLink.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RetailerOfferLink.js","names":[],"sources":["../../../../src/coolandlovely/components/prices/RetailerOfferLink.tsx"],"sourcesContent":["import { formatRetailerPrice } from './money';\n\n/** The backend validates source URLs; the rendered link independently refuses active schemes. */\nfunction safeHref(value: string): string | null {\n try {\n const url = new URL(value);\n return url.protocol === 'https:' && !url.username && !url.password ? url.href : null;\n } catch {\n return null;\n }\n}\n\nexport function RetailerOfferLink({\n url,\n priceMinor,\n currency,\n}: {\n url: string;\n priceMinor: number;\n currency: string;\n}) {\n const href = safeHref(url);\n const price = formatRetailerPrice(priceMinor, currency);\n if (!href)\n return <span className=\"text-sm text-text-secondary\">{price} · Retailer link unavailable</span>;\n return (\n <a\n className=\"rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n href={href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n {price} · Visit retailer\n </a>\n );\n}\n"],"mappings":";;;AAGA,SAAS,EAAS,GAA8B;CAC9C,IAAI;EACF,IAAM,IAAM,IAAI,IAAI,CAAK;EACzB,OAAO,EAAI,aAAa,YAAY,CAAC,EAAI,YAAY,CAAC,EAAI,WAAW,EAAI,OAAO;CAClF,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAgB,EAAkB,EAChC,QACA,eACA,eAKC;CACD,IAAM,IAAO,EAAS,CAAG,GACnB,IAAQ,EAAoB,GAAY,CAAQ;CAGtD,OAFK,IAGH,kBAAC,KAAD;EACE,WAAU;EACJ;EACN,QAAO;EACP,KAAI;YAJN,CAMG,GAAM,mBACN;MATI,kBAAC,QAAD;EAAM,WAAU;YAAhB,CAA+C,GAAM,8BAAkC;;AAWlG"}
@@ -0,0 +1,6 @@
1
+ /** Retailer evidence uses integer minor units; never assume every currency has two decimals. */
2
+ export declare function formatRetailerPrice(minor: number, currency: string, locale?: string): string;
3
+ /** A localized, ungrouped example that matches the decimal keypad's expected separator. */
4
+ export declare function retailerTargetExample(currency: string, locale?: string): string;
5
+ export declare function retailerTargetMinor(value: string, currency: string, locale?: string): number;
6
+ //# sourceMappingURL=money.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"money.d.ts","sourceRoot":"","sources":["../../../../src/coolandlovely/components/prices/money.ts"],"names":[],"mappings":"AAeA,gGAAgG;AAChG,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAI5F;AAED,2FAA2F;AAC3F,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAM/E;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAkC5F"}
@@ -0,0 +1,42 @@
1
+ //#region src/coolandlovely/components/prices/money.ts
2
+ var e = /* @__PURE__ */ new Map();
3
+ function t(t, n) {
4
+ let r = `${n ?? "browser"}:${t}`, i = e.get(r);
5
+ if (i) return i;
6
+ let a = new Intl.NumberFormat(n, {
7
+ style: "currency",
8
+ currency: t
9
+ });
10
+ if (e.size >= 16) {
11
+ let t = e.keys().next().value;
12
+ t !== void 0 && e.delete(t);
13
+ }
14
+ return e.set(r, a), a;
15
+ }
16
+ function n(e, n, r) {
17
+ let i = t(n, r), a = i.resolvedOptions().maximumFractionDigits ?? 2;
18
+ return i.format(e / 10 ** a);
19
+ }
20
+ function r(e, n) {
21
+ return t(e, n).formatToParts(12.34).filter((e) => [
22
+ "integer",
23
+ "decimal",
24
+ "fraction"
25
+ ].includes(e.type)).map((e) => e.value).join("");
26
+ }
27
+ function i(e, n, i) {
28
+ let a = t(n, i), o = a.resolvedOptions().maximumFractionDigits ?? 2, s = /* @__PURE__ */ new Map();
29
+ for (let e = 0; e < 10; e++) {
30
+ let t = a.formatToParts(e).filter((e) => e.type === "integer").map((e) => e.value).join("");
31
+ s.set(t, String(e));
32
+ }
33
+ let c = Array.from(e.trim()).map((e) => s.get(e) ?? e).join(""), l = a.formatToParts(1.1).find((e) => e.type === "decimal")?.value ?? ".", u = c.split(l);
34
+ if (u.length > 2 || !/^\d+$/.test(u[0] ?? "") || u.length === 2 && (o === 0 || !/^\d+$/.test(u[1] ?? "") || (u[1]?.length ?? 0) > o)) throw Error(`Use an ungrouped positive amount such as ${r(n, i)}.`);
35
+ let [d = "0", f = ""] = u, p = Number(d) * 10 ** o + Number(f.padEnd(o, "0"));
36
+ if (!Number.isSafeInteger(p) || p < 1 || p > 2147483647) throw Error("Enter a positive amount within the supported price range.");
37
+ return p;
38
+ }
39
+ //#endregion
40
+ export { n as formatRetailerPrice, r as retailerTargetExample, i as retailerTargetMinor };
41
+
42
+ //# sourceMappingURL=money.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"money.js","names":[],"sources":["../../../../src/coolandlovely/components/prices/money.ts"],"sourcesContent":["// Cache bounded formatting rules only; no price, actor or workspace data is retained.\nconst currencyFormatters = new Map<string, Intl.NumberFormat>();\nfunction currencyFormatter(currency: string, locale?: string): Intl.NumberFormat {\n const key = `${locale ?? 'browser'}:${currency}`;\n const prior = currencyFormatters.get(key);\n if (prior) return prior;\n const formatter = new Intl.NumberFormat(locale, { style: 'currency', currency });\n if (currencyFormatters.size >= 16) {\n const oldest = currencyFormatters.keys().next().value;\n if (oldest !== undefined) currencyFormatters.delete(oldest);\n }\n currencyFormatters.set(key, formatter);\n return formatter;\n}\n\n/** Retailer evidence uses integer minor units; never assume every currency has two decimals. */\nexport function formatRetailerPrice(minor: number, currency: string, locale?: string): string {\n const formatter = currencyFormatter(currency, locale);\n const digits = formatter.resolvedOptions().maximumFractionDigits ?? 2;\n return formatter.format(minor / 10 ** digits);\n}\n\n/** A localized, ungrouped example that matches the decimal keypad's expected separator. */\nexport function retailerTargetExample(currency: string, locale?: string): string {\n return currencyFormatter(currency, locale)\n .formatToParts(12.34)\n .filter((part) => ['integer', 'decimal', 'fraction'].includes(part.type))\n .map((part) => part.value)\n .join('');\n}\n\nexport function retailerTargetMinor(value: string, currency: string, locale?: string): number {\n const formatter = currencyFormatter(currency, locale);\n const digits = formatter.resolvedOptions().maximumFractionDigits ?? 2;\n const digitMap = new Map<string, string>();\n for (let digit = 0; digit < 10; digit++) {\n const localized = formatter\n .formatToParts(digit)\n .filter((part) => part.type === 'integer')\n .map((part) => part.value)\n .join('');\n digitMap.set(localized, String(digit));\n }\n const normalized = Array.from(value.trim())\n .map((character) => digitMap.get(character) ?? character)\n .join('');\n const separator =\n formatter.formatToParts(1.1).find((part) => part.type === 'decimal')?.value ?? '.';\n const parts = normalized.split(separator);\n // Grouping is intentionally refused: accepting both comma and dot can silently change amounts.\n if (\n parts.length > 2 ||\n !/^\\d+$/.test(parts[0] ?? '') ||\n (parts.length === 2 &&\n (digits === 0 || !/^\\d+$/.test(parts[1] ?? '') || (parts[1]?.length ?? 0) > digits))\n ) {\n throw new Error(\n `Use an ungrouped positive amount such as ${retailerTargetExample(currency, locale)}.`\n );\n }\n const [whole = '0', fraction = ''] = parts;\n const minor = Number(whole) * 10 ** digits + Number(fraction.padEnd(digits, '0'));\n if (!Number.isSafeInteger(minor) || minor < 1 || minor > 2147483647)\n throw new Error('Enter a positive amount within the supported price range.');\n return minor;\n}\n"],"mappings":";AACA,IAAM,oBAAqB,IAAI,IAA+B;AAC9D,SAAS,EAAkB,GAAkB,GAAoC;CAC/E,IAAM,IAAM,GAAG,KAAU,UAAU,GAAG,KAChC,IAAQ,EAAmB,IAAI,CAAG;CACxC,IAAI,GAAO,OAAO;CAClB,IAAM,IAAY,IAAI,KAAK,aAAa,GAAQ;EAAE,OAAO;EAAY;CAAS,CAAC;CAC/E,IAAI,EAAmB,QAAQ,IAAI;EACjC,IAAM,IAAS,EAAmB,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;EAChD,AAAI,MAAW,KAAA,KAAW,EAAmB,OAAO,CAAM;CAC5D;CAEA,OADA,EAAmB,IAAI,GAAK,CAAS,GAC9B;AACT;AAGA,SAAgB,EAAoB,GAAe,GAAkB,GAAyB;CAC5F,IAAM,IAAY,EAAkB,GAAU,CAAM,GAC9C,IAAS,EAAU,gBAAgB,CAAC,CAAC,yBAAyB;CACpE,OAAO,EAAU,OAAO,IAAQ,MAAM,CAAM;AAC9C;AAGA,SAAgB,EAAsB,GAAkB,GAAyB;CAC/E,OAAO,EAAkB,GAAU,CAAM,CAAC,CACvC,cAAc,KAAK,CAAC,CACpB,QAAQ,MAAS;EAAC;EAAW;EAAW;CAAU,CAAC,CAAC,SAAS,EAAK,IAAI,CAAC,CAAC,CACxE,KAAK,MAAS,EAAK,KAAK,CAAC,CACzB,KAAK,EAAE;AACZ;AAEA,SAAgB,EAAoB,GAAe,GAAkB,GAAyB;CAC5F,IAAM,IAAY,EAAkB,GAAU,CAAM,GAC9C,IAAS,EAAU,gBAAgB,CAAC,CAAC,yBAAyB,GAC9D,oBAAW,IAAI,IAAoB;CACzC,KAAK,IAAI,IAAQ,GAAG,IAAQ,IAAI,KAAS;EACvC,IAAM,IAAY,EACf,cAAc,CAAK,CAAC,CACpB,QAAQ,MAAS,EAAK,SAAS,SAAS,CAAC,CACzC,KAAK,MAAS,EAAK,KAAK,CAAC,CACzB,KAAK,EAAE;EACV,EAAS,IAAI,GAAW,OAAO,CAAK,CAAC;CACvC;CACA,IAAM,IAAa,MAAM,KAAK,EAAM,KAAK,CAAC,CAAC,CACxC,KAAK,MAAc,EAAS,IAAI,CAAS,KAAK,CAAS,CAAC,CACxD,KAAK,EAAE,GACJ,IACJ,EAAU,cAAc,GAAG,CAAC,CAAC,MAAM,MAAS,EAAK,SAAS,SAAS,CAAC,EAAE,SAAS,KAC3E,IAAQ,EAAW,MAAM,CAAS;CAExC,IACE,EAAM,SAAS,KACf,CAAC,QAAQ,KAAK,EAAM,MAAM,EAAE,KAC3B,EAAM,WAAW,MACf,MAAW,KAAK,CAAC,QAAQ,KAAK,EAAM,MAAM,EAAE,MAAM,EAAM,EAAE,EAAE,UAAU,KAAK,IAE9E,MAAU,MACR,4CAA4C,EAAsB,GAAU,CAAM,EAAE,EACtF;CAEF,IAAM,CAAC,IAAQ,KAAK,IAAW,MAAM,GAC/B,IAAQ,OAAO,CAAK,IAAI,MAAM,IAAS,OAAO,EAAS,OAAO,GAAQ,GAAG,CAAC;CAChF,IAAI,CAAC,OAAO,cAAc,CAAK,KAAK,IAAQ,KAAK,IAAQ,YACvD,MAAU,MAAM,2DAA2D;CAC7E,OAAO;AACT"}
@@ -0,0 +1,57 @@
1
+ import { CooPriceDetailQuery } from '../../generated/wspace-operations';
2
+ import { CooPriceAlertInput } from '../../generated/wspace-types';
3
+
4
+ /** One request for the current card page; linked IDs only, never a per-card request burst. */
5
+ export declare function useBestPrices(styleItemIds: string[], currency: string): import('@tanstack/react-query').UseQueryResult<{
6
+ __typename?: "CooBestPriceSummary";
7
+ styleItemId: string;
8
+ currency: string;
9
+ lowestPriceMinor?: number | null;
10
+ offerCount: number;
11
+ }[], Error>;
12
+ /** History and full merchant details are loaded only when the shopper asks to compare. */
13
+ export declare function usePriceDetail(styleItemId: string, currency: string, start: string, end: string, enabled: boolean): import('@tanstack/react-query').UseQueryResult<CooPriceDetailQuery, Error>;
14
+ export declare function usePriceWatches(offset: number): import('@tanstack/react-query').UseQueryResult<{
15
+ __typename?: "CooPriceAlertConnection";
16
+ items: Array<{
17
+ __typename?: "CooPriceAlert";
18
+ id: string;
19
+ styleItemId: string;
20
+ kind: import('../../generated/wspace-types').CooPriceAlertKind;
21
+ currency: string;
22
+ targetPriceMinor?: number | null;
23
+ enabled: boolean;
24
+ lastEvaluatedAt?: string | null;
25
+ createdAt: string;
26
+ }>;
27
+ pageInfo: {
28
+ __typename?: "CooPageInfo";
29
+ hasMore: boolean;
30
+ cursor?: string | null;
31
+ };
32
+ }, Error>;
33
+ export declare function usePriceWatchDeliveries(alertId: string, offset: number, enabled: boolean): import('@tanstack/react-query').UseQueryResult<{
34
+ __typename?: "CooPriceAlertDeliveryConnection";
35
+ items: Array<{
36
+ __typename?: "CooPriceAlertDelivery";
37
+ id: string;
38
+ status: string;
39
+ notificationId?: string | null;
40
+ lastErrorCode?: string | null;
41
+ createdAt: string;
42
+ deliveredAt?: string | null;
43
+ }>;
44
+ pageInfo: {
45
+ __typename?: "CooPageInfo";
46
+ hasMore: boolean;
47
+ cursor?: string | null;
48
+ };
49
+ }, Error>;
50
+ export declare function usePriceWatchAction(): import('@tanstack/react-query').UseMutationResult<void, Error, {
51
+ kind: "save";
52
+ input: CooPriceAlertInput;
53
+ } | {
54
+ kind: "remove";
55
+ id: string;
56
+ }, unknown>;
57
+ //# sourceMappingURL=usePriceApi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePriceApi.d.ts","sourceRoot":"","sources":["../../../src/coolandlovely/hooks/usePriceApi.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAIV,mBAAmB,EAGpB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAqBvE,8FAA8F;AAC9F,wBAAgB,aAAa,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM;;;;;;YAiBrE;AAED,0FAA0F;AAC1F,wBAAgB,cAAc,CAC5B,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,OAAO,8EAejB;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM;;;kBAX1C,CAAJ;;;;;wBAG2B,CAAC;;uBACY,CAAA;;;;kBAItC,CAAA;;cAGgC,CAAC;;UAelC;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;;;kBAN/E,CAAF;;;sBAMQ,CAAA;qBAA8B,CAAC;;mBAC7C,CAAA;;;kBACS,CAAC;;cACqB,CAAC;;UAYjC;AAED,wBAAgB,mBAAmB;UAKb,MAAM;WAAS,kBAAkB;;UAAa,QAAQ;QAAM,MAAM;YAwBvF"}