@burdenoff/microfe-coolandlovely 2026.1101.7 → 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
@@ -1,4 +1,4 @@
1
- import { FC } from 'react';
1
+ import { FC, ReactNode } from 'react';
2
2
  import { CooWardrobeItem } from '../types';
3
3
 
4
4
  interface WardrobeItemCardProps {
@@ -8,6 +8,7 @@ interface WardrobeItemCardProps {
8
8
  isLoggingWear?: boolean;
9
9
  isRemoving?: boolean;
10
10
  className?: string;
11
+ retailerPrice?: ReactNode;
11
12
  }
12
13
  export declare const WardrobeItemCard: FC<WardrobeItemCardProps>;
13
14
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"WardrobeItemCard.d.ts","sourceRoot":"","sources":["../../../src/coolandlovely/components/WardrobeItemCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAOhC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEhD,UAAU,qBAAqB;IAC7B,IAAI,EAAE,eAAe,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,gBAAgB,EAAE,EAAE,CAAC,qBAAqB,CAwGtD,CAAC"}
1
+ {"version":3,"file":"WardrobeItemCard.d.ts","sourceRoot":"","sources":["../../../src/coolandlovely/components/WardrobeItemCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAO3C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEhD,UAAU,qBAAqB;IAC7B,IAAI,EAAE,eAAe,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,SAAS,CAAC;CAC3B;AAED,eAAO,MAAM,gBAAgB,EAAE,EAAE,CAAC,qBAAqB,CA0GtD,CAAC"}
@@ -6,8 +6,8 @@ import { categoryIcon as o } from "../utils/icons.js";
6
6
  import { CheckCircle2 as s, Loader2 as c, Trash2 as l } from "lucide-react";
7
7
  import { jsx as u, jsxs as d } from "react/jsx-runtime";
8
8
  //#region src/coolandlovely/components/WardrobeItemCard.tsx
9
- var f = ({ item: f, onLogWear: p, onRemove: m, isLoggingWear: h, isRemoving: g, className: _ }) => {
10
- let v = f.brandName || f.colour || a(f.category);
9
+ var f = ({ item: f, onLogWear: p, onRemove: m, isLoggingWear: h, isRemoving: g, className: _, retailerPrice: v }) => {
10
+ let y = f.brandName || f.colour || a(f.category);
11
11
  return /* @__PURE__ */ d("div", {
12
12
  className: e("group flex flex-col overflow-hidden rounded-xl border border-border-seam bg-bg-surface shadow-sm transition-shadow hover:shadow-lg", _),
13
13
  children: [/* @__PURE__ */ d("div", {
@@ -58,7 +58,7 @@ var f = ({ item: f, onLogWear: p, onRemove: m, isLoggingWear: h, isRemoving: g,
58
58
  }),
59
59
  /* @__PURE__ */ u("p", {
60
60
  className: "truncate text-xs text-text-secondary",
61
- children: v
61
+ children: y
62
62
  }),
63
63
  /* @__PURE__ */ d("div", {
64
64
  className: "mt-2 flex flex-wrap items-center gap-1.5",
@@ -67,6 +67,7 @@ var f = ({ item: f, onLogWear: p, onRemove: m, isLoggingWear: h, isRemoving: g,
67
67
  children: f.size
68
68
  })]
69
69
  }),
70
+ v,
70
71
  f.notes && /* @__PURE__ */ u("p", {
71
72
  className: "mt-2 line-clamp-2 text-xs text-text-secondary",
72
73
  children: f.notes
@@ -1 +1 @@
1
- {"version":3,"file":"WardrobeItemCard.js","names":[],"sources":["../../../src/coolandlovely/components/WardrobeItemCard.tsx"],"sourcesContent":["/**\n * WardrobeItemCard — a digital wardrobe piece card for list/grids.\n * @module coolandlovely/components\n *\n * Image-forward card with a worn-count badge, hover action bar (log wear / remove),\n * details, size, source and cost-per-wear. Extracted from WardrobeListPage for\n * isolated rendering/tests.\n */\n\nimport type { FC } from 'react';\nimport { CheckCircle2, Loader2, Trash2 } from 'lucide-react';\nimport { Thumbnail } from './Thumbnail';\nimport { StatusBadge } from './StatusBadge';\nimport { categoryIcon } from '../utils/icons';\nimport { titleCase, formatRelativeTime, formatPrice } from '../utils/formatters';\nimport { cn } from '../utils/cn';\nimport type { CooWardrobeItem } from '../types';\n\ninterface WardrobeItemCardProps {\n item: CooWardrobeItem;\n onLogWear?: () => void;\n onRemove?: () => void;\n isLoggingWear?: boolean;\n isRemoving?: boolean;\n className?: string;\n}\n\nexport const WardrobeItemCard: FC<WardrobeItemCardProps> = ({\n item,\n onLogWear,\n onRemove,\n isLoggingWear,\n isRemoving,\n className,\n}) => {\n const subtitle = item.brandName || item.colour || titleCase(item.category);\n\n return (\n <div\n className={cn(\n 'group flex flex-col overflow-hidden rounded-xl border border-border-seam bg-bg-surface shadow-sm transition-shadow hover:shadow-lg',\n className\n )}\n >\n {/* Image-forward hero */}\n <div className=\"relative\">\n <Thumbnail\n icon={categoryIcon(item.category, 40)}\n seed={item.id}\n fileId={item.image?.id}\n alt={item.name}\n className=\"aspect-[4/5] w-full rounded-none\"\n />\n\n {/* Worn-count badge */}\n <div className=\"absolute right-2.5 top-2.5 flex h-12 w-12 flex-col items-center justify-center rounded-full bg-bg-surface/90 text-center leading-none shadow-sm ring-1 ring-[var(--color-accent-soft)] backdrop-blur\">\n <span className=\"text-[9px] font-medium uppercase tracking-wide text-text-secondary\">\n worn\n </span>\n <span className=\"text-sm font-bold tabular-nums text-text-primary\">\n {item.timesWorn}×\n </span>\n </div>\n\n {/* Hover action bar */}\n <div className=\"absolute inset-x-0 bottom-0 flex items-center gap-1.5 bg-gradient-to-t from-bg-canvas/90 to-transparent p-2.5 pt-8 opacity-100 transition-opacity duration-200 sm:opacity-0 sm:focus-within:opacity-100 sm:group-hover:opacity-100\">\n <button\n type=\"button\"\n disabled={isLoggingWear}\n onClick={onLogWear}\n aria-label={`Log a wear for ${item.name}`}\n className=\"inline-flex items-center gap-1 rounded-lg bg-bg-surface/90 px-2.5 py-1 text-xs font-medium text-status-success-text shadow-sm backdrop-blur transition-colors hover:bg-bg-surface disabled:opacity-50\"\n >\n {isLoggingWear ? (\n <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />\n ) : (\n <CheckCircle2 className=\"h-3.5 w-3.5\" />\n )}\n Wore it\n </button>\n <button\n type=\"button\"\n disabled={isRemoving}\n onClick={onRemove}\n aria-label={`Remove ${item.name}`}\n className=\"ml-auto inline-flex h-7 w-7 items-center justify-center rounded-lg bg-bg-surface/90 text-text-secondary shadow-sm backdrop-blur transition-colors hover:text-status-error-text disabled:opacity-50\"\n >\n {isRemoving ? (\n <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />\n ) : (\n <Trash2 className=\"h-3.5 w-3.5\" />\n )}\n </button>\n </div>\n </div>\n\n {/* Details */}\n <div className=\"flex flex-1 flex-col p-4\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">{item.name}</p>\n <p className=\"truncate text-xs text-text-secondary\">{subtitle}</p>\n <div className=\"mt-2 flex flex-wrap items-center gap-1.5\">\n <StatusBadge status={item.source} />\n {item.size && (\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n {item.size}\n </span>\n )}\n </div>\n {item.notes && (\n <p className=\"mt-2 line-clamp-2 text-xs text-text-secondary\">{item.notes}</p>\n )}\n <div className=\"mt-3 flex items-center justify-between gap-2 border-t border-border-seam pt-2 text-xs text-text-secondary\">\n <span>\n {item.lastWornAt ? `Last worn ${formatRelativeTime(item.lastWornAt)}` : 'Not worn yet'}\n </span>\n {item.costPerWear != null ? (\n <span\n title={`${formatPrice(item.purchasePrice ?? 0, item.currency ?? 'USD')} ÷ ${item.timesWorn} wears`}\n className=\"shrink-0 font-medium text-text-primary\"\n >\n {formatPrice(item.costPerWear, item.currency ?? 'USD')}/wear\n </span>\n ) : item.purchasePrice != null ? (\n <span className=\"shrink-0 text-text-secondary\">\n {formatPrice(item.purchasePrice, item.currency ?? 'USD')} paid\n </span>\n ) : null}\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;;;AA2BA,IAAa,KAA+C,EAC1D,SACA,cACA,aACA,kBACA,eACA,mBACI;CACJ,IAAM,IAAW,EAAK,aAAa,EAAK,UAAU,EAAU,EAAK,QAAQ;CAEzE,OACE,kBAAC,OAAD;EACE,WAAW,EACT,sIACA,CACF;YAJF,CAOE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,GAAD;KACE,MAAM,EAAa,EAAK,UAAU,EAAE;KACpC,MAAM,EAAK;KACX,QAAQ,EAAK,OAAO;KACpB,KAAK,EAAK;KACV,WAAU;IACX,CAAA;IAGD,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD;MAAM,WAAU;gBAAqE;KAE/E,CAAA,GACN,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CACG,EAAK,WAAU,GACZ;OACH;;IAGL,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,MAAK;MACL,UAAU;MACV,SAAS;MACT,cAAY,kBAAkB,EAAK;MACnC,WAAU;gBALZ,CAOG,IACC,kBAAC,GAAD,EAAS,WAAU,2BAA4B,CAAA,IAE/C,kBAAC,GAAD,EAAc,WAAU,cAAe,CAAA,GACvC,SAEI;SACR,kBAAC,UAAD;MACE,MAAK;MACL,UAAU;MACV,SAAS;MACT,cAAY,UAAU,EAAK;MAC3B,WAAU;gBAET,IACC,kBAAC,GAAD,EAAS,WAAU,2BAA4B,CAAA,IAE/C,kBAAC,GAAD,EAAQ,WAAU,cAAe,CAAA;KAE7B,CAAA,CACL;;GACF;MAGL,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,KAAD;KAAG,WAAU;eAAoD,EAAK;IAAQ,CAAA;IAC9E,kBAAC,KAAD;KAAG,WAAU;eAAwC;IAAY,CAAA;IACjE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAa,QAAQ,EAAK,OAAS,CAAA,GAClC,EAAK,QACJ,kBAAC,QAAD;MAAM,WAAU;gBACb,EAAK;KACF,CAAA,CAEL;;IACJ,EAAK,SACJ,kBAAC,KAAD;KAAG,WAAU;eAAiD,EAAK;IAAS,CAAA;IAE9E,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD,EAAA,UACG,EAAK,aAAa,aAAa,EAAmB,EAAK,UAAU,MAAM,eACpE,CAAA,GACL,EAAK,eAAe,OAOjB,EAAK,iBAAiB,OAItB,OAHF,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CACG,EAAY,EAAK,eAAe,EAAK,YAAY,KAAK,GAAE,OACrD;UATN,kBAAC,QAAD;MACE,OAAO,GAAG,EAAY,EAAK,iBAAiB,GAAG,EAAK,YAAY,KAAK,EAAE,KAAK,EAAK,UAAU;MAC3F,WAAU;gBAFZ,CAIG,EAAY,EAAK,aAAa,EAAK,YAAY,KAAK,GAAE,OACnD;OAML;;GACF;IACF;;AAET"}
1
+ {"version":3,"file":"WardrobeItemCard.js","names":[],"sources":["../../../src/coolandlovely/components/WardrobeItemCard.tsx"],"sourcesContent":["/**\n * WardrobeItemCard — a digital wardrobe piece card for list/grids.\n * @module coolandlovely/components\n *\n * Image-forward card with a worn-count badge, hover action bar (log wear / remove),\n * details, size, source and cost-per-wear. Extracted from WardrobeListPage for\n * isolated rendering/tests.\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { CheckCircle2, Loader2, Trash2 } from 'lucide-react';\nimport { Thumbnail } from './Thumbnail';\nimport { StatusBadge } from './StatusBadge';\nimport { categoryIcon } from '../utils/icons';\nimport { titleCase, formatRelativeTime, formatPrice } from '../utils/formatters';\nimport { cn } from '../utils/cn';\nimport type { CooWardrobeItem } from '../types';\n\ninterface WardrobeItemCardProps {\n item: CooWardrobeItem;\n onLogWear?: () => void;\n onRemove?: () => void;\n isLoggingWear?: boolean;\n isRemoving?: boolean;\n className?: string;\n retailerPrice?: ReactNode;\n}\n\nexport const WardrobeItemCard: FC<WardrobeItemCardProps> = ({\n item,\n onLogWear,\n onRemove,\n isLoggingWear,\n isRemoving,\n className,\n retailerPrice,\n}) => {\n const subtitle = item.brandName || item.colour || titleCase(item.category);\n\n return (\n <div\n className={cn(\n 'group flex flex-col overflow-hidden rounded-xl border border-border-seam bg-bg-surface shadow-sm transition-shadow hover:shadow-lg',\n className\n )}\n >\n {/* Image-forward hero */}\n <div className=\"relative\">\n <Thumbnail\n icon={categoryIcon(item.category, 40)}\n seed={item.id}\n fileId={item.image?.id}\n alt={item.name}\n className=\"aspect-[4/5] w-full rounded-none\"\n />\n\n {/* Worn-count badge */}\n <div className=\"absolute right-2.5 top-2.5 flex h-12 w-12 flex-col items-center justify-center rounded-full bg-bg-surface/90 text-center leading-none shadow-sm ring-1 ring-[var(--color-accent-soft)] backdrop-blur\">\n <span className=\"text-[9px] font-medium uppercase tracking-wide text-text-secondary\">\n worn\n </span>\n <span className=\"text-sm font-bold tabular-nums text-text-primary\">\n {item.timesWorn}×\n </span>\n </div>\n\n {/* Hover action bar */}\n <div className=\"absolute inset-x-0 bottom-0 flex items-center gap-1.5 bg-gradient-to-t from-bg-canvas/90 to-transparent p-2.5 pt-8 opacity-100 transition-opacity duration-200 sm:opacity-0 sm:focus-within:opacity-100 sm:group-hover:opacity-100\">\n <button\n type=\"button\"\n disabled={isLoggingWear}\n onClick={onLogWear}\n aria-label={`Log a wear for ${item.name}`}\n className=\"inline-flex items-center gap-1 rounded-lg bg-bg-surface/90 px-2.5 py-1 text-xs font-medium text-status-success-text shadow-sm backdrop-blur transition-colors hover:bg-bg-surface disabled:opacity-50\"\n >\n {isLoggingWear ? (\n <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />\n ) : (\n <CheckCircle2 className=\"h-3.5 w-3.5\" />\n )}\n Wore it\n </button>\n <button\n type=\"button\"\n disabled={isRemoving}\n onClick={onRemove}\n aria-label={`Remove ${item.name}`}\n className=\"ml-auto inline-flex h-7 w-7 items-center justify-center rounded-lg bg-bg-surface/90 text-text-secondary shadow-sm backdrop-blur transition-colors hover:text-status-error-text disabled:opacity-50\"\n >\n {isRemoving ? (\n <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />\n ) : (\n <Trash2 className=\"h-3.5 w-3.5\" />\n )}\n </button>\n </div>\n </div>\n\n {/* Details */}\n <div className=\"flex flex-1 flex-col p-4\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">{item.name}</p>\n <p className=\"truncate text-xs text-text-secondary\">{subtitle}</p>\n <div className=\"mt-2 flex flex-wrap items-center gap-1.5\">\n <StatusBadge status={item.source} />\n {item.size && (\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n {item.size}\n </span>\n )}\n </div>\n {retailerPrice}\n {item.notes && (\n <p className=\"mt-2 line-clamp-2 text-xs text-text-secondary\">{item.notes}</p>\n )}\n <div className=\"mt-3 flex items-center justify-between gap-2 border-t border-border-seam pt-2 text-xs text-text-secondary\">\n <span>\n {item.lastWornAt ? `Last worn ${formatRelativeTime(item.lastWornAt)}` : 'Not worn yet'}\n </span>\n {item.costPerWear != null ? (\n <span\n title={`${formatPrice(item.purchasePrice ?? 0, item.currency ?? 'USD')} ÷ ${item.timesWorn} wears`}\n className=\"shrink-0 font-medium text-text-primary\"\n >\n {formatPrice(item.costPerWear, item.currency ?? 'USD')}/wear\n </span>\n ) : item.purchasePrice != null ? (\n <span className=\"shrink-0 text-text-secondary\">\n {formatPrice(item.purchasePrice, item.currency ?? 'USD')} paid\n </span>\n ) : null}\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;;;AA4BA,IAAa,KAA+C,EAC1D,SACA,cACA,aACA,kBACA,eACA,cACA,uBACI;CACJ,IAAM,IAAW,EAAK,aAAa,EAAK,UAAU,EAAU,EAAK,QAAQ;CAEzE,OACE,kBAAC,OAAD;EACE,WAAW,EACT,sIACA,CACF;YAJF,CAOE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,GAAD;KACE,MAAM,EAAa,EAAK,UAAU,EAAE;KACpC,MAAM,EAAK;KACX,QAAQ,EAAK,OAAO;KACpB,KAAK,EAAK;KACV,WAAU;IACX,CAAA;IAGD,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD;MAAM,WAAU;gBAAqE;KAE/E,CAAA,GACN,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CACG,EAAK,WAAU,GACZ;OACH;;IAGL,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,MAAK;MACL,UAAU;MACV,SAAS;MACT,cAAY,kBAAkB,EAAK;MACnC,WAAU;gBALZ,CAOG,IACC,kBAAC,GAAD,EAAS,WAAU,2BAA4B,CAAA,IAE/C,kBAAC,GAAD,EAAc,WAAU,cAAe,CAAA,GACvC,SAEI;SACR,kBAAC,UAAD;MACE,MAAK;MACL,UAAU;MACV,SAAS;MACT,cAAY,UAAU,EAAK;MAC3B,WAAU;gBAET,IACC,kBAAC,GAAD,EAAS,WAAU,2BAA4B,CAAA,IAE/C,kBAAC,GAAD,EAAQ,WAAU,cAAe,CAAA;KAE7B,CAAA,CACL;;GACF;MAGL,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,KAAD;KAAG,WAAU;eAAoD,EAAK;IAAQ,CAAA;IAC9E,kBAAC,KAAD;KAAG,WAAU;eAAwC;IAAY,CAAA;IACjE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAa,QAAQ,EAAK,OAAS,CAAA,GAClC,EAAK,QACJ,kBAAC,QAAD;MAAM,WAAU;gBACb,EAAK;KACF,CAAA,CAEL;;IACJ;IACA,EAAK,SACJ,kBAAC,KAAD;KAAG,WAAU;eAAiD,EAAK;IAAS,CAAA;IAE9E,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD,EAAA,UACG,EAAK,aAAa,aAAa,EAAmB,EAAK,UAAU,MAAM,eACpE,CAAA,GACL,EAAK,eAAe,OAOjB,EAAK,iBAAiB,OAItB,OAHF,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CACG,EAAY,EAAK,eAAe,EAAK,YAAY,KAAK,GAAE,OACrD;UATN,kBAAC,QAAD;MACE,OAAO,GAAG,EAAY,EAAK,iBAAiB,GAAG,EAAK,YAAY,KAAK,EAAE,KAAK,EAAK,UAAU;MAC3F,WAAU;gBAFZ,CAIG,EAAY,EAAK,aAAa,EAAK,YAAY,KAAK,GAAE,OACnD;OAML;;GACF;IACF;;AAET"}
@@ -0,0 +1,7 @@
1
+ import { CooBestPricesQuery } from '../../../generated/wspace-operations';
2
+
3
+ export declare function BestPriceBadge({ summary, onOpen, }: {
4
+ summary: CooBestPricesQuery['cooBestPrices'][number] | undefined;
5
+ onOpen: () => void;
6
+ }): import("react").JSX.Element | null;
7
+ //# sourceMappingURL=BestPriceBadge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BestPriceBadge.d.ts","sourceRoot":"","sources":["../../../../src/coolandlovely/components/prices/BestPriceBadge.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAG/E,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,MAAM,GACP,EAAE;IACD,OAAO,EAAE,kBAAkB,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC;IACjE,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB,sCAYA"}
@@ -0,0 +1,22 @@
1
+ import { formatRetailerPrice as e } from "./money.js";
2
+ import { jsxs as t } from "react/jsx-runtime";
3
+ //#region src/coolandlovely/components/prices/BestPriceBadge.tsx
4
+ function n({ summary: n, onOpen: r }) {
5
+ return n?.lowestPriceMinor == null ? null : /* @__PURE__ */ t("button", {
6
+ type: "button",
7
+ className: "mt-2 text-left text-xs font-medium text-action-primary-bg underline",
8
+ onClick: r,
9
+ children: [
10
+ "Retailers from ",
11
+ e(n.lowestPriceMinor, n.currency),
12
+ " ·",
13
+ " ",
14
+ n.offerCount,
15
+ " offers"
16
+ ]
17
+ });
18
+ }
19
+ //#endregion
20
+ export { n as BestPriceBadge };
21
+
22
+ //# sourceMappingURL=BestPriceBadge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BestPriceBadge.js","names":[],"sources":["../../../../src/coolandlovely/components/prices/BestPriceBadge.tsx"],"sourcesContent":["import type { CooBestPricesQuery } from '../../../generated/wspace-operations';\nimport { formatRetailerPrice } from './money';\n\nexport function BestPriceBadge({\n summary,\n onOpen,\n}: {\n summary: CooBestPricesQuery['cooBestPrices'][number] | undefined;\n onOpen: () => void;\n}) {\n if (summary?.lowestPriceMinor == null) return null;\n return (\n <button\n type=\"button\"\n className=\"mt-2 text-left text-xs font-medium text-action-primary-bg underline\"\n onClick={onOpen}\n >\n Retailers from {formatRetailerPrice(summary.lowestPriceMinor, summary.currency)} ·{' '}\n {summary.offerCount} offers\n </button>\n );\n}\n"],"mappings":";;;AAGA,SAAgB,EAAe,EAC7B,YACA,aAIC;CAED,OADI,GAAS,oBAAoB,OAAa,OAE5C,kBAAC,UAAD;EACE,MAAK;EACL,WAAU;EACV,SAAS;YAHX;GAIC;GACiB,EAAoB,EAAQ,kBAAkB,EAAQ,QAAQ;GAAE;GAAG;GAClF,EAAQ;GAAW;EACd;;AAEZ"}
@@ -0,0 +1,10 @@
1
+ import { CooLook, CooLookbookEntry, CooStyleItem } from '../../types';
2
+
3
+ export declare function LookbookPriceBoard({ entries, looks, items, removingEntryId, onRemove, }: {
4
+ entries: CooLookbookEntry[];
5
+ looks: CooLook[];
6
+ items: CooStyleItem[];
7
+ removingEntryId?: string;
8
+ onRemove: (id: string) => void;
9
+ }): import("react").JSX.Element;
10
+ //# sourceMappingURL=LookbookPriceBoard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LookbookPriceBoard.d.ts","sourceRoot":"","sources":["../../../../src/coolandlovely/components/prices/LookbookPriceBoard.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAQ3E,wBAAgB,kBAAkB,CAAC,EACjC,OAAO,EACP,KAAK,EACL,KAAK,EACL,eAAe,EACf,QAAQ,GACT,EAAE;IACD,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC,+BA6GA"}
@@ -0,0 +1,120 @@
1
+ import { useCoolAndLovely as e } from "../../context/CoolAndLovelyContext.js";
2
+ import "../../context/index.js";
3
+ import { Thumbnail as t } from "../Thumbnail.js";
4
+ import { useBestPrices as n } from "../../hooks/usePriceApi.js";
5
+ import { BestPriceBadge as r } from "./BestPriceBadge.js";
6
+ import { PriceCurrencySelect as i } from "./PriceCurrencySelect.js";
7
+ import { useMemo as a, useState as o } from "react";
8
+ import { Loader2 as s, Sparkles as c, Tag as l, X as u } from "lucide-react";
9
+ import { jsx as d, jsxs as f } from "react/jsx-runtime";
10
+ //#region src/coolandlovely/components/prices/LookbookPriceBoard.tsx
11
+ var p = [
12
+ "aspect-[3/4]",
13
+ "aspect-square",
14
+ "aspect-[4/5]"
15
+ ];
16
+ function m({ entries: m, looks: h, items: g, removingEntryId: _, onRemove: v }) {
17
+ let { navigateTo: y } = e(), [b, x] = o("USD"), S = a(() => new Map(h.map((e) => [e.id, e])), [h]), C = a(() => new Map(g.map((e) => [e.id, e])), [g]), w = a(() => [...new Set(m.map((e) => e.styleItemId).filter((e) => !!e))], [m]), T = n(w.slice(0, 100), b), E = a(() => new Map(T.data?.map((e) => [e.styleItemId, e])), [T.data]);
18
+ return /* @__PURE__ */ f("section", {
19
+ className: "mt-6",
20
+ children: [
21
+ /* @__PURE__ */ d(i, {
22
+ value: b,
23
+ onChange: x,
24
+ currencies: g.map((e) => e.currency)
25
+ }),
26
+ /* @__PURE__ */ f("p", {
27
+ className: "mb-3 text-xs text-text-secondary",
28
+ children: [
29
+ "Retailer quotes for clipped catalog pieces use ",
30
+ b,
31
+ " without currency conversion.",
32
+ w.length > 100 ? " Pricing covers the first 100 linked pieces." : ""
33
+ ]
34
+ }),
35
+ T.isError && /* @__PURE__ */ f("p", {
36
+ role: "alert",
37
+ className: "mb-3 text-sm text-status-error-text",
38
+ children: [
39
+ "Retailer prices could not be loaded.",
40
+ " ",
41
+ /* @__PURE__ */ d("button", {
42
+ type: "button",
43
+ className: "underline",
44
+ onClick: () => void T.refetch(),
45
+ children: "Retry prices"
46
+ })
47
+ ]
48
+ }),
49
+ /* @__PURE__ */ f("h3", {
50
+ className: "mb-3 text-lg font-semibold text-text-primary",
51
+ children: [
52
+ "Board (",
53
+ m.length,
54
+ ")"
55
+ ]
56
+ }),
57
+ m.length === 0 ? /* @__PURE__ */ d("p", {
58
+ className: "rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary",
59
+ children: "This lookbook has no entries yet. Clip a catalogue piece or save a look to add one."
60
+ }) : /* @__PURE__ */ d("div", {
61
+ className: "columns-2 gap-4 md:columns-3",
62
+ children: m.map((e, n) => {
63
+ let i = !!e.lookId, a = e.lookId ? S.get(e.lookId) : void 0, o = e.styleItemId ? C.get(e.styleItemId) : void 0, m = i ? a?.title ?? "Saved look" : o?.name ?? "Clipped piece", h = i ? a?.image?.id : o?.image?.id, g = _ === e.id, b = () => {
64
+ i && e.lookId ? y(`/looks/${e.lookId}`) : e.styleItemId && y(`/items/${e.styleItemId}`);
65
+ };
66
+ return /* @__PURE__ */ f("div", {
67
+ className: "group relative mb-4 break-inside-avoid overflow-hidden rounded-xl border border-border-seam bg-bg-surface shadow-sm transition-shadow hover:shadow-lg",
68
+ children: [
69
+ /* @__PURE__ */ d("button", {
70
+ type: "button",
71
+ onClick: b,
72
+ "aria-label": `Open ${m}`,
73
+ className: "block w-full text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--color-focus-ring)]",
74
+ children: /* @__PURE__ */ d(t, {
75
+ icon: d(i ? c : l, { className: "h-8 w-8" }),
76
+ seed: e.id,
77
+ fileId: h,
78
+ alt: m,
79
+ className: `w-full rounded-none ${p[n % p.length]}`,
80
+ overlay: /* @__PURE__ */ f("div", {
81
+ className: "pointer-events-none absolute inset-x-0 bottom-0 flex flex-col gap-1.5 bg-gradient-to-t from-bg-canvas/90 via-bg-canvas/40 to-transparent p-3 pt-10",
82
+ children: [e.note && /* @__PURE__ */ d("span", {
83
+ className: "inline-flex w-fit max-w-full items-center rounded-md bg-bg-canvas/80 px-2 py-0.5 text-[11px] font-medium text-text-primary backdrop-blur",
84
+ children: /* @__PURE__ */ d("span", {
85
+ className: "truncate",
86
+ children: e.note
87
+ })
88
+ }), /* @__PURE__ */ d("p", {
89
+ className: "truncate text-sm font-semibold text-text-primary",
90
+ children: m
91
+ })]
92
+ })
93
+ })
94
+ }),
95
+ e.styleItemId && /* @__PURE__ */ d("div", {
96
+ className: "px-3 pb-3",
97
+ children: /* @__PURE__ */ d(r, {
98
+ summary: E.get(e.styleItemId),
99
+ onOpen: b
100
+ })
101
+ }),
102
+ /* @__PURE__ */ d("button", {
103
+ type: "button",
104
+ disabled: !!_,
105
+ onClick: () => v(e.id),
106
+ "aria-label": `Remove ${m}`,
107
+ className: "absolute right-2 top-2 z-10 inline-flex h-7 w-7 items-center justify-center rounded-full bg-bg-surface/90 text-text-secondary opacity-100 shadow-sm backdrop-blur transition-opacity hover:text-status-error-text sm:opacity-0 sm:focus-visible:opacity-100 sm:group-hover:opacity-100 disabled:opacity-60",
108
+ children: g ? /* @__PURE__ */ d(s, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ d(u, { className: "h-3.5 w-3.5" })
109
+ })
110
+ ]
111
+ }, e.id);
112
+ })
113
+ })
114
+ ]
115
+ });
116
+ }
117
+ //#endregion
118
+ export { m as LookbookPriceBoard };
119
+
120
+ //# sourceMappingURL=LookbookPriceBoard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LookbookPriceBoard.js","names":[],"sources":["../../../../src/coolandlovely/components/prices/LookbookPriceBoard.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { Loader2, Sparkles, Tag, X } from 'lucide-react';\nimport { useCoolAndLovely } from '../../context';\nimport { useBestPrices } from '../../hooks/usePriceApi';\nimport type { CooLook, CooLookbookEntry, CooStyleItem } from '../../types';\nimport { Thumbnail } from '../Thumbnail';\nimport { BestPriceBadge } from './BestPriceBadge';\nimport { PriceCurrencySelect } from './PriceCurrencySelect';\n\n/** Alternating aspect ratios preserve the existing board rhythm. */\nconst TILE_ASPECTS = ['aspect-[3/4]', 'aspect-square', 'aspect-[4/5]'];\n\nexport function LookbookPriceBoard({\n entries,\n looks,\n items,\n removingEntryId,\n onRemove,\n}: {\n entries: CooLookbookEntry[];\n looks: CooLook[];\n items: CooStyleItem[];\n removingEntryId?: string;\n onRemove: (id: string) => void;\n}) {\n const { navigateTo } = useCoolAndLovely();\n const [priceCurrency, setPriceCurrency] = useState('USD');\n const lookById = useMemo(() => new Map(looks.map((look) => [look.id, look])), [looks]);\n const itemById = useMemo(() => new Map(items.map((item) => [item.id, item])), [items]);\n const priceIds = useMemo(\n () => [\n ...new Set(\n entries.map((entry) => entry.styleItemId).filter((id): id is string => Boolean(id))\n ),\n ],\n [entries]\n );\n const bestPrices = useBestPrices(priceIds.slice(0, 100), priceCurrency);\n const priceByItem = useMemo(\n () => new Map(bestPrices.data?.map((entry) => [entry.styleItemId, entry])),\n [bestPrices.data]\n );\n return (\n <section className=\"mt-6\">\n <PriceCurrencySelect\n value={priceCurrency}\n onChange={setPriceCurrency}\n currencies={items.map((item) => item.currency)}\n />\n <p className=\"mb-3 text-xs text-text-secondary\">\n Retailer quotes for clipped catalog pieces use {priceCurrency} without currency conversion.\n {priceIds.length > 100 ? ' Pricing covers the first 100 linked pieces.' : ''}\n </p>\n {bestPrices.isError && (\n <p role=\"alert\" className=\"mb-3 text-sm text-status-error-text\">\n Retailer prices could not be loaded.{' '}\n <button type=\"button\" className=\"underline\" onClick={() => void bestPrices.refetch()}>\n Retry prices\n </button>\n </p>\n )}\n <h3 className=\"mb-3 text-lg font-semibold text-text-primary\">Board ({entries.length})</h3>\n {entries.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n This lookbook has no entries yet. Clip a catalogue piece or save a look to add one.\n </p>\n ) : (\n <div className=\"columns-2 gap-4 md:columns-3\">\n {entries.map((entry, idx) => {\n const isLook = Boolean(entry.lookId);\n const look = entry.lookId ? lookById.get(entry.lookId) : undefined;\n const item = entry.styleItemId ? itemById.get(entry.styleItemId) : undefined;\n const title = isLook ? (look?.title ?? 'Saved look') : (item?.name ?? 'Clipped piece');\n const imageFileId = isLook ? look?.image?.id : item?.image?.id;\n const removing = removingEntryId === entry.id;\n const onOpen = () => {\n if (isLook && entry.lookId) navigateTo(`/looks/${entry.lookId}`);\n else if (entry.styleItemId) navigateTo(`/items/${entry.styleItemId}`);\n };\n return (\n <div\n key={entry.id}\n className=\"group relative mb-4 break-inside-avoid overflow-hidden rounded-xl border border-border-seam bg-bg-surface shadow-sm transition-shadow hover:shadow-lg\"\n >\n <button\n type=\"button\"\n onClick={onOpen}\n aria-label={`Open ${title}`}\n className=\"block w-full text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <Thumbnail\n icon={isLook ? <Sparkles className=\"h-8 w-8\" /> : <Tag className=\"h-8 w-8\" />}\n seed={entry.id}\n fileId={imageFileId}\n alt={title}\n className={`w-full rounded-none ${TILE_ASPECTS[idx % TILE_ASPECTS.length]}`}\n overlay={\n <div className=\"pointer-events-none absolute inset-x-0 bottom-0 flex flex-col gap-1.5 bg-gradient-to-t from-bg-canvas/90 via-bg-canvas/40 to-transparent p-3 pt-10\">\n {entry.note && (\n <span className=\"inline-flex w-fit max-w-full items-center rounded-md bg-bg-canvas/80 px-2 py-0.5 text-[11px] font-medium text-text-primary backdrop-blur\">\n <span className=\"truncate\">{entry.note}</span>\n </span>\n )}\n <p className=\"truncate text-sm font-semibold text-text-primary\">{title}</p>\n </div>\n }\n />\n </button>\n {entry.styleItemId && (\n <div className=\"px-3 pb-3\">\n <BestPriceBadge summary={priceByItem.get(entry.styleItemId)} onOpen={onOpen} />\n </div>\n )}\n <button\n type=\"button\"\n disabled={Boolean(removingEntryId)}\n onClick={() => onRemove(entry.id)}\n aria-label={`Remove ${title}`}\n className=\"absolute right-2 top-2 z-10 inline-flex h-7 w-7 items-center justify-center rounded-full bg-bg-surface/90 text-text-secondary opacity-100 shadow-sm backdrop-blur transition-opacity hover:text-status-error-text sm:opacity-0 sm:focus-visible:opacity-100 sm:group-hover:opacity-100 disabled:opacity-60\"\n >\n {removing ? (\n <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />\n ) : (\n <X className=\"h-3.5 w-3.5\" />\n )}\n </button>\n </div>\n );\n })}\n </div>\n )}\n </section>\n );\n}\n"],"mappings":";;;;;;;;;;AAUA,IAAM,IAAe;CAAC;CAAgB;CAAiB;AAAc;AAErE,SAAgB,EAAmB,EACjC,YACA,UACA,UACA,oBACA,eAOC;CACD,IAAM,EAAE,kBAAe,EAAiB,GAClC,CAAC,GAAe,KAAoB,EAAS,KAAK,GAClD,IAAW,QAAc,IAAI,IAAI,EAAM,KAAK,MAAS,CAAC,EAAK,IAAI,CAAI,CAAC,CAAC,GAAG,CAAC,CAAK,CAAC,GAC/E,IAAW,QAAc,IAAI,IAAI,EAAM,KAAK,MAAS,CAAC,EAAK,IAAI,CAAI,CAAC,CAAC,GAAG,CAAC,CAAK,CAAC,GAC/E,IAAW,QACT,CACJ,GAAG,IAAI,IACL,EAAQ,KAAK,MAAU,EAAM,WAAW,CAAC,CAAC,QAAQ,MAAqB,EAAQ,CAAG,CACpF,CACF,GACA,CAAC,CAAO,CACV,GACM,IAAa,EAAc,EAAS,MAAM,GAAG,GAAG,GAAG,CAAa,GAChE,IAAc,QACZ,IAAI,IAAI,EAAW,MAAM,KAAK,MAAU,CAAC,EAAM,aAAa,CAAK,CAAC,CAAC,GACzE,CAAC,EAAW,IAAI,CAClB;CACA,OACE,kBAAC,WAAD;EAAS,WAAU;YAAnB;GACE,kBAAC,GAAD;IACE,OAAO;IACP,UAAU;IACV,YAAY,EAAM,KAAK,MAAS,EAAK,QAAQ;GAC9C,CAAA;GACD,kBAAC,KAAD;IAAG,WAAU;cAAb;KAAgD;KACE;KAAc;KAC7D,EAAS,SAAS,MAAM,iDAAiD;IACzE;;GACF,EAAW,WACV,kBAAC,KAAD;IAAG,MAAK;IAAQ,WAAU;cAA1B;KAAgE;KACzB;KACrC,kBAAC,UAAD;MAAQ,MAAK;MAAS,WAAU;MAAY,eAAe,KAAK,EAAW,QAAQ;gBAAG;KAE9E,CAAA;IACP;;GAEL,kBAAC,MAAD;IAAI,WAAU;cAAd;KAA6D;KAAQ,EAAQ;KAAO;IAAK;;GACxF,EAAQ,WAAW,IAClB,kBAAC,KAAD;IAAG,WAAU;cAAqF;GAE/F,CAAA,IAEH,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAQ,KAAK,GAAO,MAAQ;KAC3B,IAAM,IAAS,EAAQ,EAAM,QACvB,IAAO,EAAM,SAAS,EAAS,IAAI,EAAM,MAAM,IAAI,KAAA,GACnD,IAAO,EAAM,cAAc,EAAS,IAAI,EAAM,WAAW,IAAI,KAAA,GAC7D,IAAQ,IAAU,GAAM,SAAS,eAAiB,GAAM,QAAQ,iBAChE,IAAc,IAAS,GAAM,OAAO,KAAK,GAAM,OAAO,IACtD,IAAW,MAAoB,EAAM,IACrC,UAAe;MACnB,AAAI,KAAU,EAAM,SAAQ,EAAW,UAAU,EAAM,QAAQ,IACtD,EAAM,eAAa,EAAW,UAAU,EAAM,aAAa;KACtE;KACA,OACE,kBAAC,OAAD;MAEE,WAAU;gBAFZ;OAIE,kBAAC,UAAD;QACE,MAAK;QACL,SAAS;QACT,cAAY,QAAQ;QACpB,WAAU;kBAEV,kBAAC,GAAD;SACE,MAAe,EAAT,IAAU,IAAmC,GAApC,EAAU,WAAU,UAAW,CAA8B;SAC5E,MAAM,EAAM;SACZ,QAAQ;SACR,KAAK;SACL,WAAW,uBAAuB,EAAa,IAAM,EAAa;SAClE,SACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACG,EAAM,QACL,kBAAC,QAAD;WAAM,WAAU;qBACd,kBAAC,QAAD;YAAM,WAAU;sBAAY,EAAM;WAAW,CAAA;UACzC,CAAA,GAER,kBAAC,KAAD;WAAG,WAAU;qBAAoD;UAAS,CAAA,CACvE;;QAER,CAAA;OACK,CAAA;OACP,EAAM,eACL,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,GAAD;SAAgB,SAAS,EAAY,IAAI,EAAM,WAAW;SAAW;QAAS,CAAA;OAC3E,CAAA;OAEP,kBAAC,UAAD;QACE,MAAK;QACL,UAAU,EAAQ;QAClB,eAAe,EAAS,EAAM,EAAE;QAChC,cAAY,UAAU;QACtB,WAAU;kBAET,IACC,kBAAC,GAAD,EAAS,WAAU,2BAA4B,CAAA,IAE/C,kBAAC,GAAD,EAAG,WAAU,cAAe,CAAA;OAExB,CAAA;MACL;QA7CE,EAAM,EA6CR;IAET,CAAC;GACE,CAAA;EAEA;;AAEb"}
@@ -0,0 +1,5 @@
1
+ export declare function PriceComparison({ styleItemId, currency, }: {
2
+ styleItemId: string;
3
+ currency: string;
4
+ }): import("react").JSX.Element;
5
+ //# sourceMappingURL=PriceComparison.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PriceComparison.d.ts","sourceRoot":"","sources":["../../../../src/coolandlovely/components/prices/PriceComparison.tsx"],"names":[],"mappings":"AAKA,wBAAgB,eAAe,CAAC,EAC9B,WAAW,EACX,QAAQ,GACT,EAAE;IACD,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB,+BAmFA"}
@@ -0,0 +1,95 @@
1
+ import { usePriceDetail as e } from "../../hooks/usePriceApi.js";
2
+ import { formatRetailerPrice as t } from "./money.js";
3
+ import { RetailerOfferLink as n } from "./RetailerOfferLink.js";
4
+ import { useState as r } from "react";
5
+ import { jsx as i, jsxs as a } from "react/jsx-runtime";
6
+ //#region src/coolandlovely/components/prices/PriceComparison.tsx
7
+ function o({ styleItemId: o, currency: s }) {
8
+ let [c] = r(() => {
9
+ let e = /* @__PURE__ */ new Date();
10
+ return {
11
+ end: e.toISOString(),
12
+ start: (/* @__PURE__ */ new Date(e.getTime() - 30 * 864e5)).toISOString()
13
+ };
14
+ }), l = e(o, s, c.start, c.end, !0);
15
+ return l.isPending ? /* @__PURE__ */ i("p", {
16
+ role: "status",
17
+ className: "text-sm text-text-secondary",
18
+ children: "Loading comparison…"
19
+ }) : l.isError ? /* @__PURE__ */ a("p", {
20
+ role: "alert",
21
+ className: "text-sm text-status-error-text",
22
+ children: [
23
+ "Comparison could not be loaded.",
24
+ " ",
25
+ /* @__PURE__ */ i("button", {
26
+ type: "button",
27
+ className: "underline",
28
+ onClick: () => void l.refetch(),
29
+ children: "Retry comparison"
30
+ })
31
+ ]
32
+ }) : /* @__PURE__ */ a("div", {
33
+ className: "space-y-4",
34
+ children: [
35
+ !l.data?.cooComparePrices.length && /* @__PURE__ */ i("p", {
36
+ className: "text-sm text-text-secondary",
37
+ children: "No current retailer offers in this currency."
38
+ }),
39
+ /* @__PURE__ */ i("ul", {
40
+ className: "space-y-2",
41
+ children: l.data?.cooComparePrices.map((e) => /* @__PURE__ */ a("li", {
42
+ className: "flex flex-wrap items-center justify-between gap-2 rounded-lg border border-border-seam p-3",
43
+ children: [/* @__PURE__ */ a("div", { children: [/* @__PURE__ */ i("p", {
44
+ className: "font-medium text-text-primary",
45
+ children: e.retailerName
46
+ }), /* @__PURE__ */ a("p", {
47
+ className: "text-xs text-text-secondary",
48
+ children: ["Observed ", new Date(e.observedAt).toLocaleString()]
49
+ })] }), /* @__PURE__ */ i(n, {
50
+ url: e.productUrl,
51
+ priceMinor: e.priceMinor,
52
+ currency: e.currency
53
+ })]
54
+ }, e.id))
55
+ }),
56
+ /* @__PURE__ */ i("h3", {
57
+ className: "font-semibold text-text-primary",
58
+ children: "Observed daily minimum · last 30 days"
59
+ }),
60
+ /* @__PURE__ */ i("p", {
61
+ className: "text-xs text-text-secondary",
62
+ children: "Only recorded in-stock observations are shown. Missing days are not estimates; this does not include taxes or delivery charges."
63
+ }),
64
+ l.data?.cooPriceHistory.length ? /* @__PURE__ */ i("div", {
65
+ className: "max-h-60 overflow-auto",
66
+ children: /* @__PURE__ */ a("table", {
67
+ className: "w-full text-left text-sm",
68
+ children: [/* @__PURE__ */ i("thead", { children: /* @__PURE__ */ a("tr", { children: [/* @__PURE__ */ i("th", {
69
+ className: "p-2 text-text-secondary",
70
+ scope: "col",
71
+ children: "UTC day"
72
+ }), /* @__PURE__ */ i("th", {
73
+ className: "p-2 text-text-secondary",
74
+ scope: "col",
75
+ children: "Lowest observed"
76
+ })] }) }), /* @__PURE__ */ i("tbody", { children: l.data.cooPriceHistory.map((e) => /* @__PURE__ */ a("tr", { children: [/* @__PURE__ */ i("th", {
77
+ className: "p-2 font-normal text-text-primary",
78
+ scope: "row",
79
+ children: e.day
80
+ }), /* @__PURE__ */ i("td", {
81
+ className: "p-2 text-text-primary",
82
+ children: t(e.lowestPriceMinor, s)
83
+ })] }, e.day)) })]
84
+ })
85
+ }) : /* @__PURE__ */ i("p", {
86
+ className: "text-sm text-text-secondary",
87
+ children: "No observed history in this period."
88
+ })
89
+ ]
90
+ });
91
+ }
92
+ //#endregion
93
+ export { o as PriceComparison };
94
+
95
+ //# sourceMappingURL=PriceComparison.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PriceComparison.js","names":[],"sources":["../../../../src/coolandlovely/components/prices/PriceComparison.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { usePriceDetail } from '../../hooks/usePriceApi';\nimport { formatRetailerPrice } from './money';\nimport { RetailerOfferLink } from './RetailerOfferLink';\n\nexport function PriceComparison({\n styleItemId,\n currency,\n}: {\n styleItemId: string;\n currency: string;\n}) {\n const [period] = useState(() => {\n const end = new Date();\n return { end: end.toISOString(), start: new Date(end.getTime() - 30 * 86400000).toISOString() };\n });\n const query = usePriceDetail(styleItemId, currency, period.start, period.end, true);\n if (query.isPending)\n return (\n <p role=\"status\" className=\"text-sm text-text-secondary\">\n Loading comparison…\n </p>\n );\n if (query.isError)\n return (\n <p role=\"alert\" className=\"text-sm text-status-error-text\">\n Comparison could not be loaded.{' '}\n <button type=\"button\" className=\"underline\" onClick={() => void query.refetch()}>\n Retry comparison\n </button>\n </p>\n );\n return (\n <div className=\"space-y-4\">\n {!query.data?.cooComparePrices.length && (\n <p className=\"text-sm text-text-secondary\">No current retailer offers in this currency.</p>\n )}\n <ul className=\"space-y-2\">\n {query.data?.cooComparePrices.map((offer) => (\n <li\n key={offer.id}\n className=\"flex flex-wrap items-center justify-between gap-2 rounded-lg border border-border-seam p-3\"\n >\n <div>\n <p className=\"font-medium text-text-primary\">{offer.retailerName}</p>\n <p className=\"text-xs text-text-secondary\">\n Observed {new Date(offer.observedAt).toLocaleString()}\n </p>\n </div>\n <RetailerOfferLink\n url={offer.productUrl}\n priceMinor={offer.priceMinor}\n currency={offer.currency}\n />\n </li>\n ))}\n </ul>\n <h3 className=\"font-semibold text-text-primary\">Observed daily minimum · last 30 days</h3>\n <p className=\"text-xs text-text-secondary\">\n Only recorded in-stock observations are shown. Missing days are not estimates; this does not\n include taxes or delivery charges.\n </p>\n {query.data?.cooPriceHistory.length ? (\n <div className=\"max-h-60 overflow-auto\">\n <table className=\"w-full text-left text-sm\">\n <thead>\n <tr>\n <th className=\"p-2 text-text-secondary\" scope=\"col\">\n UTC day\n </th>\n <th className=\"p-2 text-text-secondary\" scope=\"col\">\n Lowest observed\n </th>\n </tr>\n </thead>\n <tbody>\n {query.data.cooPriceHistory.map((day) => (\n <tr key={day.day}>\n <th className=\"p-2 font-normal text-text-primary\" scope=\"row\">\n {day.day}\n </th>\n <td className=\"p-2 text-text-primary\">\n {formatRetailerPrice(day.lowestPriceMinor, currency)}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n ) : (\n <p className=\"text-sm text-text-secondary\">No observed history in this period.</p>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;AAKA,SAAgB,EAAgB,EAC9B,gBACA,eAIC;CACD,IAAM,CAAC,KAAU,QAAe;EAC9B,IAAM,oBAAM,IAAI,KAAK;EACrB,OAAO;GAAE,KAAK,EAAI,YAAY;GAAG,wBAAO,IAAI,KAAK,EAAI,QAAQ,IAAI,KAAK,KAAQ,EAAA,CAAE,YAAY;EAAE;CAChG,CAAC,GACK,IAAQ,EAAe,GAAa,GAAU,EAAO,OAAO,EAAO,KAAK,EAAI;CAgBlF,OAfI,EAAM,YAEN,kBAAC,KAAD;EAAG,MAAK;EAAS,WAAU;YAA8B;CAEtD,CAAA,IAEH,EAAM,UAEN,kBAAC,KAAD;EAAG,MAAK;EAAQ,WAAU;YAA1B;GAA2D;GACzB;GAChC,kBAAC,UAAD;IAAQ,MAAK;IAAS,WAAU;IAAY,eAAe,KAAK,EAAM,QAAQ;cAAG;GAEzE,CAAA;EACP;MAGL,kBAAC,OAAD;EAAK,WAAU;YAAf;GACG,CAAC,EAAM,MAAM,iBAAiB,UAC7B,kBAAC,KAAD;IAAG,WAAU;cAA8B;GAA+C,CAAA;GAE5F,kBAAC,MAAD;IAAI,WAAU;cACX,EAAM,MAAM,iBAAiB,KAAK,MACjC,kBAAC,MAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;MAAG,WAAU;gBAAiC,EAAM;KAAgB,CAAA,GACpE,kBAAC,KAAD;MAAG,WAAU;gBAAb,CAA2C,aAC/B,IAAI,KAAK,EAAM,UAAU,CAAC,CAAC,eAAe,CACnD;OACA,EAAA,CAAA,GACL,kBAAC,GAAD;MACE,KAAK,EAAM;MACX,YAAY,EAAM;MAClB,UAAU,EAAM;KACjB,CAAA,CACC;OAdG,EAAM,EAcT,CACL;GACC,CAAA;GACJ,kBAAC,MAAD;IAAI,WAAU;cAAkC;GAAyC,CAAA;GACzF,kBAAC,KAAD;IAAG,WAAU;cAA8B;GAGxC,CAAA;GACF,EAAM,MAAM,gBAAgB,SAC3B,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,SAAD;KAAO,WAAU;eAAjB,CACE,kBAAC,SAAD,EAAA,UACE,kBAAC,MAAD,EAAA,UAAA,CACE,kBAAC,MAAD;MAAI,WAAU;MAA0B,OAAM;gBAAM;KAEhD,CAAA,GACJ,kBAAC,MAAD;MAAI,WAAU;MAA0B,OAAM;gBAAM;KAEhD,CAAA,CACF,EAAA,CAAA,EACC,CAAA,GACP,kBAAC,SAAD,EAAA,UACG,EAAM,KAAK,gBAAgB,KAAK,MAC/B,kBAAC,MAAD,EAAA,UAAA,CACE,kBAAC,MAAD;MAAI,WAAU;MAAoC,OAAM;gBACrD,EAAI;KACH,CAAA,GACJ,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAoB,EAAI,kBAAkB,CAAQ;KACjD,CAAA,CACF,EAAA,GAPK,EAAI,GAOT,CACL,EACI,CAAA,CACF;;GACJ,CAAA,IAEL,kBAAC,KAAD;IAAG,WAAU;cAA8B;GAAsC,CAAA;EAEhF;;AAET"}
@@ -0,0 +1,6 @@
1
+ export declare function PriceCurrencySelect({ value, onChange, currencies, }: {
2
+ value: string;
3
+ onChange: (currency: string) => void;
4
+ currencies?: string[];
5
+ }): import("react").JSX.Element;
6
+ //# sourceMappingURL=PriceCurrencySelect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PriceCurrencySelect.d.ts","sourceRoot":"","sources":["../../../../src/coolandlovely/components/prices/PriceCurrencySelect.tsx"],"names":[],"mappings":"AAEA,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,QAAQ,EACR,UAAe,GAChB,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB,+BAwBA"}
@@ -0,0 +1,38 @@
1
+ import { useId as e } from "react";
2
+ import { jsx as t, jsxs as n } from "react/jsx-runtime";
3
+ //#region src/coolandlovely/components/prices/PriceCurrencySelect.tsx
4
+ function r({ value: r, onChange: i, currencies: a = [] }) {
5
+ let o = e(), s = [.../* @__PURE__ */ new Set([
6
+ r,
7
+ ...a,
8
+ "USD",
9
+ "INR",
10
+ "EUR",
11
+ "GBP",
12
+ "JPY",
13
+ "BHD",
14
+ "CAD",
15
+ "AUD"
16
+ ])];
17
+ return /* @__PURE__ */ n("div", {
18
+ className: "mb-3 flex flex-wrap items-center gap-2",
19
+ children: [/* @__PURE__ */ t("label", {
20
+ htmlFor: o,
21
+ className: "text-sm text-text-secondary",
22
+ children: "Retailer quote currency"
23
+ }), /* @__PURE__ */ t("select", {
24
+ id: o,
25
+ value: r,
26
+ onChange: (e) => i(e.target.value),
27
+ className: "rounded-lg border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary",
28
+ children: s.map((e) => /* @__PURE__ */ t("option", {
29
+ value: e,
30
+ children: e
31
+ }, e))
32
+ })]
33
+ });
34
+ }
35
+ //#endregion
36
+ export { r as PriceCurrencySelect };
37
+
38
+ //# sourceMappingURL=PriceCurrencySelect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PriceCurrencySelect.js","names":[],"sources":["../../../../src/coolandlovely/components/prices/PriceCurrencySelect.tsx"],"sourcesContent":["import { useId } from 'react';\n\nexport function PriceCurrencySelect({\n value,\n onChange,\n currencies = [],\n}: {\n value: string;\n onChange: (currency: string) => void;\n currencies?: string[];\n}) {\n const id = useId();\n const options = [\n ...new Set([value, ...currencies, 'USD', 'INR', 'EUR', 'GBP', 'JPY', 'BHD', 'CAD', 'AUD']),\n ];\n return (\n <div className=\"mb-3 flex flex-wrap items-center gap-2\">\n <label htmlFor={id} className=\"text-sm text-text-secondary\">\n Retailer quote currency\n </label>\n <select\n id={id}\n value={value}\n onChange={(event) => onChange(event.target.value)}\n className=\"rounded-lg border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n >\n {options.map((currency) => (\n <option key={currency} value={currency}>\n {currency}\n </option>\n ))}\n </select>\n </div>\n );\n}\n"],"mappings":";;;AAEA,SAAgB,EAAoB,EAClC,UACA,aACA,gBAAa,CAAC,KAKb;CACD,IAAM,IAAK,EAAM,GACX,IAAU,CACd,mBAAG,IAAI,IAAI;EAAC;EAAO,GAAG;EAAY;EAAO;EAAO;EAAO;EAAO;EAAO;EAAO;EAAO;CAAK,CAAC,CAC3F;CACA,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,SAAD;GAAO,SAAS;GAAI,WAAU;aAA8B;EAErD,CAAA,GACP,kBAAC,UAAD;GACM;GACG;GACP,WAAW,MAAU,EAAS,EAAM,OAAO,KAAK;GAChD,WAAU;aAET,EAAQ,KAAK,MACZ,kBAAC,UAAD;IAAuB,OAAO;cAC3B;GACK,GAFK,CAEL,CACT;EACK,CAAA,CACL;;AAET"}
@@ -0,0 +1,6 @@
1
+ /** Parent keys by active security context and item so shopper input state cannot leak. */
2
+ export declare function PricePanel({ styleItemId, currency: initialCurrency, }: {
3
+ styleItemId: string;
4
+ currency: string;
5
+ }): import("react").JSX.Element;
6
+ //# sourceMappingURL=PricePanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PricePanel.d.ts","sourceRoot":"","sources":["../../../../src/coolandlovely/components/prices/PricePanel.tsx"],"names":[],"mappings":"AASA,0FAA0F;AAC1F,wBAAgB,UAAU,CAAC,EACzB,WAAW,EACX,QAAQ,EAAE,eAAe,GAC1B,EAAE;IACD,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB,+BAoEA"}
@@ -0,0 +1,105 @@
1
+ import { useBestPrices as e } from "../../hooks/usePriceApi.js";
2
+ import { formatRetailerPrice as t } from "./money.js";
3
+ import { PriceComparison as n } from "./PriceComparison.js";
4
+ import { PriceWatchForm as r } from "./PriceWatchForm.js";
5
+ import { useId as i, useState as a } from "react";
6
+ import { jsx as o, jsxs as s } from "react/jsx-runtime";
7
+ //#region src/coolandlovely/components/prices/PricePanel.tsx
8
+ 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";
9
+ function l({ styleItemId: l, currency: u }) {
10
+ let [d, f] = a(u), [p, m] = a(!1), h = i(), g = e([l], d), _ = g.data?.[0];
11
+ return /* @__PURE__ */ s("section", {
12
+ className: "space-y-4 rounded-xl border border-border-seam bg-bg-surface p-4",
13
+ "aria-label": "Retailer prices and alerts",
14
+ children: [
15
+ /* @__PURE__ */ s("div", {
16
+ className: "flex flex-wrap items-center justify-between gap-3",
17
+ children: [/* @__PURE__ */ o("h2", {
18
+ className: "text-lg font-semibold text-text-primary",
19
+ children: "Retailer prices"
20
+ }), /* @__PURE__ */ s("div", {
21
+ className: "flex items-center gap-2",
22
+ children: [/* @__PURE__ */ o("label", {
23
+ htmlFor: h,
24
+ className: "text-sm text-text-secondary",
25
+ children: "Currency"
26
+ }), /* @__PURE__ */ o("select", {
27
+ id: h,
28
+ className: "rounded-lg border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary",
29
+ value: d,
30
+ onChange: (e) => f(e.target.value),
31
+ children: [.../* @__PURE__ */ new Set([
32
+ u,
33
+ "USD",
34
+ "INR",
35
+ "EUR",
36
+ "GBP",
37
+ "JPY"
38
+ ])].map((e) => /* @__PURE__ */ o("option", {
39
+ value: e,
40
+ children: e
41
+ }, e))
42
+ })]
43
+ })]
44
+ }),
45
+ /* @__PURE__ */ o("p", {
46
+ className: "text-sm text-text-secondary",
47
+ children: "Approved sources and exact product matches only. Prices are not converted. Confirm current price, shipping and stock at the retailer."
48
+ }),
49
+ g.isPending ? /* @__PURE__ */ o("p", {
50
+ role: "status",
51
+ className: "text-sm text-text-secondary",
52
+ children: "Checking current offers…"
53
+ }) : g.isError ? /* @__PURE__ */ s("p", {
54
+ role: "alert",
55
+ className: "text-sm text-status-error-text",
56
+ children: [
57
+ "Prices could not be loaded.",
58
+ " ",
59
+ /* @__PURE__ */ o("button", {
60
+ type: "button",
61
+ className: c,
62
+ onClick: () => void g.refetch(),
63
+ children: "Retry prices"
64
+ })
65
+ ]
66
+ }) : _?.lowestPriceMinor == null ? /* @__PURE__ */ s("p", {
67
+ className: "text-sm text-text-secondary",
68
+ children: [
69
+ "No fresh in-stock retailer offers in ",
70
+ d,
71
+ "."
72
+ ]
73
+ }) : /* @__PURE__ */ s("p", {
74
+ className: "font-semibold text-text-primary",
75
+ children: [
76
+ "From ",
77
+ t(_.lowestPriceMinor, _.currency),
78
+ " across",
79
+ " ",
80
+ _.offerCount,
81
+ " current offers"
82
+ ]
83
+ }),
84
+ /* @__PURE__ */ o("button", {
85
+ type: "button",
86
+ className: c,
87
+ "aria-expanded": p,
88
+ onClick: () => m((e) => !e),
89
+ children: p ? "Hide comparison" : "Compare offers and history"
90
+ }),
91
+ p && /* @__PURE__ */ o(n, {
92
+ styleItemId: l,
93
+ currency: d
94
+ }),
95
+ /* @__PURE__ */ o(r, {
96
+ styleItemId: l,
97
+ currency: d
98
+ }, d)
99
+ ]
100
+ });
101
+ }
102
+ //#endregion
103
+ export { l as PricePanel };
104
+
105
+ //# sourceMappingURL=PricePanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PricePanel.js","names":[],"sources":["../../../../src/coolandlovely/components/prices/PricePanel.tsx"],"sourcesContent":["import { useId, useState } from 'react';\nimport { useBestPrices } from '../../hooks/usePriceApi';\nimport { formatRetailerPrice } from './money';\nimport { PriceComparison } from './PriceComparison';\nimport { PriceWatchForm } from './PriceWatchForm';\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\n/** Parent keys by active security context and item so shopper input state cannot leak. */\nexport function PricePanel({\n styleItemId,\n currency: initialCurrency,\n}: {\n styleItemId: string;\n currency: string;\n}) {\n const [currency, setCurrency] = useState(initialCurrency);\n const [expanded, setExpanded] = useState(false);\n const currencyId = useId();\n const best = useBestPrices([styleItemId], currency);\n const summary = best.data?.[0];\n return (\n <section\n className=\"space-y-4 rounded-xl border border-border-seam bg-bg-surface p-4\"\n aria-label=\"Retailer prices and alerts\"\n >\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Retailer prices</h2>\n <div className=\"flex items-center gap-2\">\n <label htmlFor={currencyId} className=\"text-sm text-text-secondary\">\n Currency\n </label>\n <select\n id={currencyId}\n className=\"rounded-lg border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n value={currency}\n onChange={(event) => setCurrency(event.target.value)}\n >\n {[...new Set([initialCurrency, 'USD', 'INR', 'EUR', 'GBP', 'JPY'])].map((code) => (\n <option key={code} value={code}>\n {code}\n </option>\n ))}\n </select>\n </div>\n </div>\n <p className=\"text-sm text-text-secondary\">\n Approved sources and exact product matches only. Prices are not converted. Confirm current\n price, shipping and stock at the retailer.\n </p>\n {best.isPending ? (\n <p role=\"status\" className=\"text-sm text-text-secondary\">\n Checking current offers…\n </p>\n ) : best.isError ? (\n <p role=\"alert\" className=\"text-sm text-status-error-text\">\n Prices could not be loaded.{' '}\n <button type=\"button\" className={buttonClass} onClick={() => void best.refetch()}>\n Retry prices\n </button>\n </p>\n ) : summary?.lowestPriceMinor != null ? (\n <p className=\"font-semibold text-text-primary\">\n From {formatRetailerPrice(summary.lowestPriceMinor, summary.currency)} across{' '}\n {summary.offerCount} current offers\n </p>\n ) : (\n <p className=\"text-sm text-text-secondary\">\n No fresh in-stock retailer offers in {currency}.\n </p>\n )}\n <button\n type=\"button\"\n className={buttonClass}\n aria-expanded={expanded}\n onClick={() => setExpanded((value) => !value)}\n >\n {expanded ? 'Hide comparison' : 'Compare offers and history'}\n </button>\n {expanded && <PriceComparison styleItemId={styleItemId} currency={currency} />}\n <PriceWatchForm key={currency} styleItemId={styleItemId} currency={currency} />\n </section>\n );\n}\n"],"mappings":";;;;;;;AAMA,IAAM,IACJ;AAGF,SAAgB,EAAW,EACzB,gBACA,UAAU,KAIT;CACD,IAAM,CAAC,GAAU,KAAe,EAAS,CAAe,GAClD,CAAC,GAAU,KAAe,EAAS,EAAK,GACxC,IAAa,EAAM,GACnB,IAAO,EAAc,CAAC,CAAW,GAAG,CAAQ,GAC5C,IAAU,EAAK,OAAO;CAC5B,OACE,kBAAC,WAAD;EACE,WAAU;EACV,cAAW;YAFb;GAIE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eAA0C;IAAmB,CAAA,GAC3E,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,SAAD;MAAO,SAAS;MAAY,WAAU;gBAA8B;KAE7D,CAAA,GACP,kBAAC,UAAD;MACE,IAAI;MACJ,WAAU;MACV,OAAO;MACP,WAAW,MAAU,EAAY,EAAM,OAAO,KAAK;gBAElD,CAAC,mBAAG,IAAI,IAAI;OAAC;OAAiB;OAAO;OAAO;OAAO;OAAO;MAAK,CAAC,CAAC,CAAC,CAAC,KAAK,MACvE,kBAAC,UAAD;OAAmB,OAAO;iBACvB;MACK,GAFK,CAEL,CACT;KACK,CAAA,CACL;MACF;;GACL,kBAAC,KAAD;IAAG,WAAU;cAA8B;GAGxC,CAAA;GACF,EAAK,YACJ,kBAAC,KAAD;IAAG,MAAK;IAAS,WAAU;cAA8B;GAEtD,CAAA,IACD,EAAK,UACP,kBAAC,KAAD;IAAG,MAAK;IAAQ,WAAU;cAA1B;KAA2D;KAC7B;KAC5B,kBAAC,UAAD;MAAQ,MAAK;MAAS,WAAW;MAAa,eAAe,KAAK,EAAK,QAAQ;gBAAG;KAE1E,CAAA;IACP;QACD,GAAS,oBAAoB,OAM/B,kBAAC,KAAD;IAAG,WAAU;cAAb;KAA2C;KACH;KAAS;IAC9C;QAPH,kBAAC,KAAD;IAAG,WAAU;cAAb;KAA+C;KACvC,EAAoB,EAAQ,kBAAkB,EAAQ,QAAQ;KAAE;KAAQ;KAC7E,EAAQ;KAAW;IACnB;;GAML,kBAAC,UAAD;IACE,MAAK;IACL,WAAW;IACX,iBAAe;IACf,eAAe,GAAa,MAAU,CAAC,CAAK;cAE3C,IAAW,oBAAoB;GAC1B,CAAA;GACP,KAAY,kBAAC,GAAD;IAA8B;IAAuB;GAAW,CAAA;GAC7E,kBAAC,GAAD;IAA4C;IAAuB;GAAW,GAAzD,CAAyD;EACvE;;AAEb"}
@@ -0,0 +1,2 @@
1
+ export declare function PriceWatchDisclosure(): import("react").JSX.Element;
2
+ //# sourceMappingURL=PriceWatchDisclosure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PriceWatchDisclosure.d.ts","sourceRoot":"","sources":["../../../../src/coolandlovely/components/prices/PriceWatchDisclosure.tsx"],"names":[],"mappings":"AAMA,wBAAgB,oBAAoB,gCAyBnC"}