@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.
- package/dist/coolandlovely/components/WardrobeItemCard.d.ts +2 -1
- package/dist/coolandlovely/components/WardrobeItemCard.d.ts.map +1 -1
- package/dist/coolandlovely/components/WardrobeItemCard.js +4 -3
- package/dist/coolandlovely/components/WardrobeItemCard.js.map +1 -1
- package/dist/coolandlovely/components/prices/BestPriceBadge.d.ts +7 -0
- package/dist/coolandlovely/components/prices/BestPriceBadge.d.ts.map +1 -0
- package/dist/coolandlovely/components/prices/BestPriceBadge.js +22 -0
- package/dist/coolandlovely/components/prices/BestPriceBadge.js.map +1 -0
- package/dist/coolandlovely/components/prices/LookbookPriceBoard.d.ts +10 -0
- package/dist/coolandlovely/components/prices/LookbookPriceBoard.d.ts.map +1 -0
- package/dist/coolandlovely/components/prices/LookbookPriceBoard.js +120 -0
- package/dist/coolandlovely/components/prices/LookbookPriceBoard.js.map +1 -0
- package/dist/coolandlovely/components/prices/PriceComparison.d.ts +5 -0
- package/dist/coolandlovely/components/prices/PriceComparison.d.ts.map +1 -0
- package/dist/coolandlovely/components/prices/PriceComparison.js +95 -0
- package/dist/coolandlovely/components/prices/PriceComparison.js.map +1 -0
- package/dist/coolandlovely/components/prices/PriceCurrencySelect.d.ts +6 -0
- package/dist/coolandlovely/components/prices/PriceCurrencySelect.d.ts.map +1 -0
- package/dist/coolandlovely/components/prices/PriceCurrencySelect.js +38 -0
- package/dist/coolandlovely/components/prices/PriceCurrencySelect.js.map +1 -0
- package/dist/coolandlovely/components/prices/PricePanel.d.ts +6 -0
- package/dist/coolandlovely/components/prices/PricePanel.d.ts.map +1 -0
- package/dist/coolandlovely/components/prices/PricePanel.js +105 -0
- package/dist/coolandlovely/components/prices/PricePanel.js.map +1 -0
- package/dist/coolandlovely/components/prices/PriceWatchDisclosure.d.ts +2 -0
- package/dist/coolandlovely/components/prices/PriceWatchDisclosure.d.ts.map +1 -0
- package/dist/coolandlovely/components/prices/PriceWatchDisclosure.js +28 -0
- package/dist/coolandlovely/components/prices/PriceWatchDisclosure.js.map +1 -0
- package/dist/coolandlovely/components/prices/PriceWatchForm.d.ts +5 -0
- package/dist/coolandlovely/components/prices/PriceWatchForm.d.ts.map +1 -0
- package/dist/coolandlovely/components/prices/PriceWatchForm.js +89 -0
- package/dist/coolandlovely/components/prices/PriceWatchForm.js.map +1 -0
- package/dist/coolandlovely/components/prices/PriceWatches.d.ts +3 -0
- package/dist/coolandlovely/components/prices/PriceWatches.d.ts.map +1 -0
- package/dist/coolandlovely/components/prices/PriceWatches.js +189 -0
- package/dist/coolandlovely/components/prices/PriceWatches.js.map +1 -0
- package/dist/coolandlovely/components/prices/RetailerOfferLink.d.ts +6 -0
- package/dist/coolandlovely/components/prices/RetailerOfferLink.d.ts.map +1 -0
- package/dist/coolandlovely/components/prices/RetailerOfferLink.js +28 -0
- package/dist/coolandlovely/components/prices/RetailerOfferLink.js.map +1 -0
- package/dist/coolandlovely/components/prices/money.d.ts +6 -0
- package/dist/coolandlovely/components/prices/money.d.ts.map +1 -0
- package/dist/coolandlovely/components/prices/money.js +42 -0
- package/dist/coolandlovely/components/prices/money.js.map +1 -0
- package/dist/coolandlovely/hooks/usePriceApi.d.ts +57 -0
- package/dist/coolandlovely/hooks/usePriceApi.d.ts.map +1 -0
- package/dist/coolandlovely/hooks/usePriceApi.js +111 -0
- package/dist/coolandlovely/hooks/usePriceApi.js.map +1 -0
- package/dist/coolandlovely/pages/items/StoreListedPieceView.d.ts.map +1 -1
- package/dist/coolandlovely/pages/items/StoreListedPieceView.js +130 -123
- package/dist/coolandlovely/pages/items/StoreListedPieceView.js.map +1 -1
- package/dist/coolandlovely/pages/items/StyleItemDetailPage.d.ts.map +1 -1
- package/dist/coolandlovely/pages/items/StyleItemDetailPage.js +241 -230
- package/dist/coolandlovely/pages/items/StyleItemDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.d.ts.map +1 -1
- package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js +64 -119
- package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/profile/StyleProfilePage.d.ts.map +1 -1
- package/dist/coolandlovely/pages/profile/StyleProfilePage.js +214 -209
- package/dist/coolandlovely/pages/profile/StyleProfilePage.js.map +1 -1
- package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.d.ts.map +1 -1
- package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.js +83 -49
- package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.js.map +1 -1
- package/dist/generated/wspace-operations.d.ts +282 -2
- package/dist/generated/wspace-operations.d.ts.map +1 -1
- package/dist/generated/wspace-operations.js +87 -1
- package/dist/generated/wspace-operations.js.map +1 -1
- package/dist/generated/wspace-types.d.ts +224 -0
- package/dist/generated/wspace-types.d.ts.map +1 -1
- package/dist/generated/wspace-types.js +8 -0
- package/dist/generated/wspace-types.js.map +1 -0
- 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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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"}
|