@burdenoff/microfe-coolandlovely 2026.720.1 → 2026.720.2

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 (58) hide show
  1. package/dist/coolandlovely/CoolAndLovelyRoutes.d.ts.map +1 -1
  2. package/dist/coolandlovely/CoolAndLovelyRoutes.js +8 -0
  3. package/dist/coolandlovely/CoolAndLovelyRoutes.js.map +1 -1
  4. package/dist/coolandlovely/components/Field.d.ts +3 -0
  5. package/dist/coolandlovely/components/Field.d.ts.map +1 -1
  6. package/dist/coolandlovely/components/Field.js +12 -9
  7. package/dist/coolandlovely/components/Field.js.map +1 -1
  8. package/dist/coolandlovely/components/MetricsCard.d.ts +7 -0
  9. package/dist/coolandlovely/components/MetricsCard.d.ts.map +1 -1
  10. package/dist/coolandlovely/components/MetricsCard.js +37 -13
  11. package/dist/coolandlovely/components/MetricsCard.js.map +1 -1
  12. package/dist/coolandlovely/hooks/index.d.ts +1 -1
  13. package/dist/coolandlovely/hooks/index.d.ts.map +1 -1
  14. package/dist/coolandlovely/hooks/useCoolAndLovelyApi.d.ts +54 -4
  15. package/dist/coolandlovely/hooks/useCoolAndLovelyApi.d.ts.map +1 -1
  16. package/dist/coolandlovely/hooks/useCoolAndLovelyApi.js +197 -79
  17. package/dist/coolandlovely/hooks/useCoolAndLovelyApi.js.map +1 -1
  18. package/dist/coolandlovely/pages/HomePage.d.ts.map +1 -1
  19. package/dist/coolandlovely/pages/HomePage.js +112 -101
  20. package/dist/coolandlovely/pages/HomePage.js.map +1 -1
  21. package/dist/coolandlovely/pages/brands/BrandDetailPage.js +9 -9
  22. package/dist/coolandlovely/pages/collaborations/CollaborationDetailPage.d.ts.map +1 -1
  23. package/dist/coolandlovely/pages/collaborations/CollaborationDetailPage.js +135 -111
  24. package/dist/coolandlovely/pages/collaborations/CollaborationDetailPage.js.map +1 -1
  25. package/dist/coolandlovely/pages/collaborations/CollaborationFormPage.d.ts +5 -4
  26. package/dist/coolandlovely/pages/collaborations/CollaborationFormPage.d.ts.map +1 -1
  27. package/dist/coolandlovely/pages/collaborations/CollaborationFormPage.js +201 -107
  28. package/dist/coolandlovely/pages/collaborations/CollaborationFormPage.js.map +1 -1
  29. package/dist/coolandlovely/pages/collections/CollectionDetailPage.d.ts.map +1 -1
  30. package/dist/coolandlovely/pages/collections/CollectionDetailPage.js +110 -99
  31. package/dist/coolandlovely/pages/collections/CollectionDetailPage.js.map +1 -1
  32. package/dist/coolandlovely/pages/discover/DiscoverPage.js +7 -6
  33. package/dist/coolandlovely/pages/discover/DiscoverPage.js.map +1 -1
  34. package/dist/coolandlovely/pages/items/StyleItemDetailPage.js +4 -4
  35. package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.d.ts.map +1 -1
  36. package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js +116 -104
  37. package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js.map +1 -1
  38. package/dist/coolandlovely/pages/looks/LookDetailPage.d.ts.map +1 -1
  39. package/dist/coolandlovely/pages/looks/LookDetailPage.js +15 -13
  40. package/dist/coolandlovely/pages/looks/LookDetailPage.js.map +1 -1
  41. package/dist/coolandlovely/pages/profile/StyleProfilePage.d.ts +11 -6
  42. package/dist/coolandlovely/pages/profile/StyleProfilePage.d.ts.map +1 -1
  43. package/dist/coolandlovely/pages/profile/StyleProfilePage.js +195 -176
  44. package/dist/coolandlovely/pages/profile/StyleProfilePage.js.map +1 -1
  45. package/dist/coolandlovely/types/index.d.ts +40 -0
  46. package/dist/coolandlovely/types/index.d.ts.map +1 -1
  47. package/dist/coolandlovely/utils/index.d.ts +1 -0
  48. package/dist/coolandlovely/utils/index.d.ts.map +1 -1
  49. package/dist/coolandlovely/utils/index.js +5 -0
  50. package/dist/coolandlovely/utils/sparkline.d.ts +31 -0
  51. package/dist/coolandlovely/utils/sparkline.d.ts.map +1 -0
  52. package/dist/coolandlovely/utils/sparkline.js +29 -0
  53. package/dist/coolandlovely/utils/sparkline.js.map +1 -0
  54. package/dist/utils/pageContext.d.ts +8 -1
  55. package/dist/utils/pageContext.d.ts.map +1 -1
  56. package/dist/utils/pageContext.js +22 -27
  57. package/dist/utils/pageContext.js.map +1 -1
  58. package/package.json +1 -1
@@ -3,146 +3,153 @@ import { PageLayout as t } from "../../components/PageLayout.js";
3
3
  import { StatusBadge as n } from "../../components/StatusBadge.js";
4
4
  import { ErrorView as r, LoadingView as i } from "../../components/StateViews.js";
5
5
  import { Thumbnail as a } from "../../components/Thumbnail.js";
6
- import { useCollection as o, usePublishCollection as s } from "../../hooks/useCoolAndLovelyApi.js";
7
- import { formatDate as c, formatPrice as l, titleCase as u } from "../../utils/formatters.js";
6
+ import { useArchiveCollection as o, useCollection as s, usePublishCollection as c } from "../../hooks/useCoolAndLovelyApi.js";
7
+ import { formatDate as l, formatPrice as u, titleCase as d } from "../../utils/formatters.js";
8
8
  import "../../hooks/index.js";
9
- import { TagList as d } from "../../components/TagList.js";
10
9
  import { categoryIcon as f } from "../../utils/icons.js";
11
- import { ColourSwatchRow as p } from "../../components/ColourSwatchRow.js";
12
- import { ArrowLeft as m, Loader2 as h, Pencil as g, Plus as _, Rocket as v, Shapes as y, Store as b } from "lucide-react";
13
- import { useParams as x } from "react-router-dom";
14
- import { Fragment as S, jsx as C, jsxs as w } from "react/jsx-runtime";
15
- import { GlassCard as T } from "@burdenoff/fe-libs/ui";
10
+ import { TagList as p } from "../../components/TagList.js";
11
+ import { ColourSwatchRow as m } from "../../components/ColourSwatchRow.js";
12
+ import { Archive as h, ArrowLeft as g, Loader2 as _, Pencil as v, Plus as y, Rocket as b, Shapes as x, Store as S } from "lucide-react";
13
+ import { useParams as C } from "react-router-dom";
14
+ import { Fragment as w, jsx as T, jsxs as E } from "react/jsx-runtime";
15
+ import { GlassCard as D } from "@burdenoff/fe-libs/ui";
16
16
  //#region src/coolandlovely/pages/collections/CollectionDetailPage.tsx
17
- function E() {
18
- let { collectionId: E } = x(), { navigateTo: D } = e(), O = o(E), k = s();
19
- if (O.isLoading) return /* @__PURE__ */ C(t, {
17
+ function O() {
18
+ let { collectionId: O } = C(), { navigateTo: k } = e(), A = s(O), j = c(), M = o();
19
+ if (A.isLoading) return /* @__PURE__ */ T(t, {
20
20
  title: "Collection",
21
- children: /* @__PURE__ */ C(i, { label: "Loading collection…" })
21
+ children: /* @__PURE__ */ T(i, { label: "Loading collection…" })
22
22
  });
23
- if (O.isError || !O.data) return /* @__PURE__ */ C(t, {
23
+ if (A.isError || !A.data) return /* @__PURE__ */ T(t, {
24
24
  title: "Collection",
25
- children: /* @__PURE__ */ C(r, {
25
+ children: /* @__PURE__ */ T(r, {
26
26
  title: "Collection not found",
27
- onRetry: () => void O.refetch()
27
+ onRetry: () => void A.refetch()
28
28
  })
29
29
  });
30
- let A = O.data, j = A.items ?? [], M = A.status === "DRAFT" || A.status === "SCHEDULED";
31
- return /* @__PURE__ */ C(t, {
32
- title: A.name,
33
- description: A.brand?.name ?? void 0,
34
- actions: /* @__PURE__ */ w("div", {
30
+ let N = A.data, P = N.items ?? [], F = N.status === "DRAFT" || N.status === "SCHEDULED", I = N.status === "ARCHIVED";
31
+ return /* @__PURE__ */ T(t, {
32
+ title: N.name,
33
+ description: N.brand?.name ?? void 0,
34
+ actions: /* @__PURE__ */ E("div", {
35
35
  className: "flex flex-wrap items-center gap-2",
36
36
  children: [
37
- /* @__PURE__ */ w("button", {
37
+ /* @__PURE__ */ E("button", {
38
38
  type: "button",
39
- onClick: () => D("/collections"),
39
+ onClick: () => k("/collections"),
40
40
  className: "inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken",
41
- children: [/* @__PURE__ */ C(m, { className: "h-4 w-4" }), " Back"]
41
+ children: [/* @__PURE__ */ T(g, { className: "h-4 w-4" }), " Back"]
42
42
  }),
43
- /* @__PURE__ */ w("button", {
43
+ /* @__PURE__ */ E("button", {
44
44
  type: "button",
45
- onClick: () => D(`/collections/${A.id}/edit`),
45
+ onClick: () => k(`/collections/${N.id}/edit`),
46
46
  className: "inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken",
47
- children: [/* @__PURE__ */ C(g, { className: "h-4 w-4" }), " Edit"]
47
+ children: [/* @__PURE__ */ T(v, { className: "h-4 w-4" }), " Edit"]
48
48
  }),
49
- M && /* @__PURE__ */ w("button", {
49
+ F && /* @__PURE__ */ E("button", {
50
50
  type: "button",
51
- disabled: k.isPending,
52
- onClick: () => k.mutate(A.id),
51
+ disabled: j.isPending,
52
+ onClick: () => j.mutate(N.id),
53
53
  className: "inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50",
54
- children: [k.isPending ? /* @__PURE__ */ C(h, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ C(v, { className: "h-4 w-4" }), "Publish"]
54
+ children: [j.isPending ? /* @__PURE__ */ T(_, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ T(b, { className: "h-4 w-4" }), "Publish"]
55
+ }),
56
+ !I && /* @__PURE__ */ E("button", {
57
+ type: "button",
58
+ disabled: M.isPending,
59
+ onClick: () => M.mutate(N.id, { onSuccess: () => k("/collections") }),
60
+ className: "inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-50",
61
+ children: [M.isPending ? /* @__PURE__ */ T(_, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ T(h, { className: "h-4 w-4" }), "Archive"]
55
62
  })
56
63
  ]
57
64
  }),
58
- children: /* @__PURE__ */ w("div", {
65
+ children: /* @__PURE__ */ E("div", {
59
66
  className: "grid grid-cols-1 gap-6 lg:grid-cols-3",
60
- children: [/* @__PURE__ */ w("div", {
67
+ children: [/* @__PURE__ */ E("div", {
61
68
  className: "lg:col-span-2",
62
- children: [/* @__PURE__ */ w(T, {
69
+ children: [/* @__PURE__ */ E(D, {
63
70
  treatment: "glass",
64
71
  className: "group overflow-hidden p-0",
65
- children: [/* @__PURE__ */ C(a, {
66
- icon: /* @__PURE__ */ C(y, { className: "h-12 w-12" }),
67
- seed: A.id,
68
- fileId: A.cover?.id,
69
- alt: A.name,
72
+ children: [/* @__PURE__ */ T(a, {
73
+ icon: /* @__PURE__ */ T(x, { className: "h-12 w-12" }),
74
+ seed: N.id,
75
+ fileId: N.cover?.id,
76
+ alt: N.name,
70
77
  className: "aspect-[16/9] w-full sm:aspect-[21/9]",
71
- overlay: /* @__PURE__ */ w(S, { children: [/* @__PURE__ */ C("div", { className: "pointer-events-none absolute inset-0 bg-gradient-to-t from-bg-canvas via-bg-canvas/55 to-transparent" }), /* @__PURE__ */ w("div", {
78
+ overlay: /* @__PURE__ */ E(w, { children: [/* @__PURE__ */ T("div", { className: "pointer-events-none absolute inset-0 bg-gradient-to-t from-bg-canvas via-bg-canvas/55 to-transparent" }), /* @__PURE__ */ E("div", {
72
79
  className: "absolute inset-x-0 bottom-0 flex flex-wrap items-end justify-between gap-3 p-6",
73
- children: [/* @__PURE__ */ w("div", {
80
+ children: [/* @__PURE__ */ E("div", {
74
81
  className: "min-w-0",
75
- children: [/* @__PURE__ */ C("h2", {
82
+ children: [/* @__PURE__ */ T("h2", {
76
83
  className: "font-serif text-3xl font-semibold leading-tight text-text-primary",
77
- children: A.name
78
- }), /* @__PURE__ */ w("div", {
84
+ children: N.name
85
+ }), /* @__PURE__ */ E("div", {
79
86
  className: "mt-2 flex flex-wrap items-center gap-2 text-sm text-text-secondary",
80
87
  children: [
81
- A.brand?.name && /* @__PURE__ */ C("span", { children: A.brand.name }),
82
- A.brand?.name && /* @__PURE__ */ C("span", {
88
+ N.brand?.name && /* @__PURE__ */ T("span", { children: N.brand.name }),
89
+ N.brand?.name && /* @__PURE__ */ T("span", {
83
90
  "aria-hidden": "true",
84
91
  className: "opacity-60",
85
92
  children: "·"
86
93
  }),
87
- /* @__PURE__ */ C("span", {
94
+ /* @__PURE__ */ T("span", {
88
95
  className: "rounded-full border border-border-seam bg-bg-surface/80 px-2.5 py-0.5 text-xs font-medium text-text-primary backdrop-blur-sm",
89
- children: u(A.season)
96
+ children: d(N.season)
90
97
  })
91
98
  ]
92
99
  })]
93
- }), /* @__PURE__ */ C(n, {
94
- status: A.status,
100
+ }), /* @__PURE__ */ T(n, {
101
+ status: N.status,
95
102
  className: "shrink-0 shadow-sm"
96
103
  })]
97
104
  })] })
98
- }), (A.description || A.tags.length > 0) && /* @__PURE__ */ w("div", {
105
+ }), (N.description || N.tags.length > 0) && /* @__PURE__ */ E("div", {
99
106
  className: "p-6",
100
- children: [A.description && /* @__PURE__ */ C("p", {
107
+ children: [N.description && /* @__PURE__ */ T("p", {
101
108
  className: "text-sm text-text-secondary",
102
- children: A.description
103
- }), A.tags.length > 0 && /* @__PURE__ */ C(d, {
104
- tags: A.tags,
105
- className: A.description ? "mt-4" : void 0
109
+ children: N.description
110
+ }), N.tags.length > 0 && /* @__PURE__ */ T(p, {
111
+ tags: N.tags,
112
+ className: N.description ? "mt-4" : void 0
106
113
  })]
107
114
  })]
108
- }), /* @__PURE__ */ w("section", {
115
+ }), /* @__PURE__ */ E("section", {
109
116
  className: "mt-6",
110
117
  children: [
111
- /* @__PURE__ */ w("h3", {
118
+ /* @__PURE__ */ E("h3", {
112
119
  className: "mb-3 text-lg font-semibold text-text-primary",
113
120
  children: [
114
121
  "Items (",
115
- j.length,
122
+ P.length,
116
123
  ")"
117
124
  ]
118
125
  }),
119
- j.length === 0 ? /* @__PURE__ */ C("p", {
126
+ P.length === 0 ? /* @__PURE__ */ T("p", {
120
127
  className: "rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary",
121
128
  children: "No items in this collection yet."
122
- }) : /* @__PURE__ */ C("div", {
129
+ }) : /* @__PURE__ */ T("div", {
123
130
  className: "grid grid-cols-2 gap-3 sm:grid-cols-3",
124
- children: j.map((e) => /* @__PURE__ */ w("button", {
131
+ children: P.map((e) => /* @__PURE__ */ E("button", {
125
132
  type: "button",
126
- onClick: () => D(`/items/${e.id}`),
133
+ onClick: () => k(`/items/${e.id}`),
127
134
  className: "group overflow-hidden rounded-lg border border-border-seam bg-bg-surface text-left transition-all hover:border-accent-foreground/20 hover:shadow-md",
128
- children: [/* @__PURE__ */ C(a, {
135
+ children: [/* @__PURE__ */ T(a, {
129
136
  icon: f(e.category, 28),
130
137
  seed: e.id,
131
138
  fileId: e.image?.id,
132
139
  alt: e.name,
133
140
  className: "aspect-[3/4] w-full"
134
- }), /* @__PURE__ */ w("div", {
141
+ }), /* @__PURE__ */ E("div", {
135
142
  className: "p-3",
136
143
  children: [
137
- /* @__PURE__ */ C("p", {
144
+ /* @__PURE__ */ T("p", {
138
145
  className: "truncate text-sm font-medium text-text-primary",
139
146
  children: e.name
140
147
  }),
141
- /* @__PURE__ */ C("p", {
148
+ /* @__PURE__ */ T("p", {
142
149
  className: "mt-0.5 text-xs font-semibold text-text-primary tabular-nums",
143
- children: l(e.price, e.currency)
150
+ children: u(e.price, e.currency)
144
151
  }),
145
- e.colours.length > 0 && /* @__PURE__ */ C(p, {
152
+ e.colours.length > 0 && /* @__PURE__ */ T(m, {
146
153
  colours: e.colours,
147
154
  max: 5,
148
155
  className: "mt-2"
@@ -151,57 +158,57 @@ function E() {
151
158
  })]
152
159
  }, e.id))
153
160
  }),
154
- /* @__PURE__ */ w("button", {
161
+ /* @__PURE__ */ E("button", {
155
162
  type: "button",
156
- onClick: () => D(`/items/new?brandId=${A.brandId}&collectionId=${A.id}`),
163
+ onClick: () => k(`/items/new?brandId=${N.brandId}&collectionId=${N.id}`),
157
164
  className: "mt-3 inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken",
158
- children: [/* @__PURE__ */ C(_, { className: "h-4 w-4" }), " Add item to this collection"]
165
+ children: [/* @__PURE__ */ T(y, { className: "h-4 w-4" }), " Add item to this collection"]
159
166
  })
160
167
  ]
161
168
  })]
162
- }), /* @__PURE__ */ w("div", {
169
+ }), /* @__PURE__ */ E("div", {
163
170
  className: "space-y-4",
164
171
  children: [
165
- /* @__PURE__ */ w(T, {
172
+ /* @__PURE__ */ E(D, {
166
173
  treatment: "plain",
167
174
  className: "p-5",
168
- children: [/* @__PURE__ */ C("h3", {
175
+ children: [/* @__PURE__ */ T("h3", {
169
176
  className: "mb-3 text-sm font-semibold text-text-primary",
170
177
  children: "Details"
171
- }), /* @__PURE__ */ w("dl", {
178
+ }), /* @__PURE__ */ E("dl", {
172
179
  className: "space-y-2 text-sm",
173
180
  children: [
174
- /* @__PURE__ */ w("div", {
181
+ /* @__PURE__ */ E("div", {
175
182
  className: "flex justify-between gap-2",
176
- children: [/* @__PURE__ */ C("dt", {
183
+ children: [/* @__PURE__ */ T("dt", {
177
184
  className: "text-text-secondary",
178
185
  children: "Season"
179
- }), /* @__PURE__ */ C("dd", {
186
+ }), /* @__PURE__ */ T("dd", {
180
187
  className: "text-text-primary",
181
- children: u(A.season)
188
+ children: d(N.season)
182
189
  })]
183
190
  }),
184
- /* @__PURE__ */ w("div", {
191
+ /* @__PURE__ */ E("div", {
185
192
  className: "flex justify-between gap-2",
186
- children: [/* @__PURE__ */ C("dt", {
193
+ children: [/* @__PURE__ */ T("dt", {
187
194
  className: "text-text-secondary",
188
195
  children: "Publish at"
189
- }), /* @__PURE__ */ C("dd", {
196
+ }), /* @__PURE__ */ T("dd", {
190
197
  className: "text-text-primary",
191
- children: A.publishAt ? c(A.publishAt, {
198
+ children: N.publishAt ? l(N.publishAt, {
192
199
  hour: void 0,
193
200
  minute: void 0
194
201
  }) : "—"
195
202
  })]
196
203
  }),
197
- /* @__PURE__ */ w("div", {
204
+ /* @__PURE__ */ E("div", {
198
205
  className: "flex justify-between gap-2",
199
- children: [/* @__PURE__ */ C("dt", {
206
+ children: [/* @__PURE__ */ T("dt", {
200
207
  className: "text-text-secondary",
201
208
  children: "Ends at"
202
- }), /* @__PURE__ */ C("dd", {
209
+ }), /* @__PURE__ */ T("dd", {
203
210
  className: "text-text-primary",
204
- children: A.endAt ? c(A.endAt, {
211
+ children: N.endAt ? l(N.endAt, {
205
212
  hour: void 0,
206
213
  minute: void 0
207
214
  }) : "—"
@@ -210,28 +217,32 @@ function E() {
210
217
  ]
211
218
  })]
212
219
  }),
213
- A.brand && /* @__PURE__ */ w("button", {
220
+ N.brand && /* @__PURE__ */ E("button", {
214
221
  type: "button",
215
- onClick: () => D(`/brands/${A.brand?.id}`),
222
+ onClick: () => k(`/brands/${N.brand?.id}`),
216
223
  className: "flex w-full items-center gap-3 rounded-lg border border-border-seam bg-bg-surface p-4 text-left transition-colors hover:bg-bg-sunken/50",
217
- children: [/* @__PURE__ */ C(a, {
218
- icon: /* @__PURE__ */ C(b, { className: "h-5 w-5" }),
219
- seed: A.brand.id,
224
+ children: [/* @__PURE__ */ T(a, {
225
+ icon: /* @__PURE__ */ T(S, { className: "h-5 w-5" }),
226
+ seed: N.brand.id,
220
227
  className: "h-10 w-10 shrink-0"
221
- }), /* @__PURE__ */ w("div", {
228
+ }), /* @__PURE__ */ E("div", {
222
229
  className: "min-w-0",
223
- children: [/* @__PURE__ */ C("p", {
230
+ children: [/* @__PURE__ */ T("p", {
224
231
  className: "truncate text-sm font-medium text-text-primary",
225
- children: A.brand.name
226
- }), /* @__PURE__ */ C("p", {
232
+ children: N.brand.name
233
+ }), /* @__PURE__ */ T("p", {
227
234
  className: "truncate text-xs text-text-secondary",
228
235
  children: "View brand"
229
236
  })]
230
237
  })]
231
238
  }),
232
- k.isError && /* @__PURE__ */ C("p", {
239
+ j.isError && /* @__PURE__ */ T("p", {
240
+ className: "rounded-lg bg-status-error-bg-subtle px-3 py-2 text-xs text-status-error-text",
241
+ children: j.error instanceof Error ? j.error.message : "Publish failed"
242
+ }),
243
+ M.isError && /* @__PURE__ */ T("p", {
233
244
  className: "rounded-lg bg-status-error-bg-subtle px-3 py-2 text-xs text-status-error-text",
234
- children: k.error instanceof Error ? k.error.message : "Publish failed"
245
+ children: M.error instanceof Error ? M.error.message : "Archive failed"
235
246
  })
236
247
  ]
237
248
  })]
@@ -239,6 +250,6 @@ function E() {
239
250
  });
240
251
  }
241
252
  //#endregion
242
- export { E as CollectionDetailPage };
253
+ export { O as CollectionDetailPage };
243
254
 
244
255
  //# sourceMappingURL=CollectionDetailPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CollectionDetailPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/collections/CollectionDetailPage.tsx"],"sourcesContent":["/**\n * Collection Detail Page\n * @module coolandlovely/pages/collections\n *\n * Single collection (`cooCollection`) with publish (`cooPublishCollection`),\n * edit, its brand link and member items.\n */\n\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, Pencil, Rocket, Loader2, Shapes, Store, Plus } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { TagList } from '../../components/TagList';\nimport { ColourSwatchRow } from '../../components/ColourSwatchRow';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useCollection, usePublishCollection } from '../../hooks';\nimport { categoryIcon } from '../../utils/icons';\nimport { formatPrice, formatDate, titleCase } from '../../utils/formatters';\n\nexport function CollectionDetailPage() {\n const { collectionId } = useParams<{ collectionId: string }>();\n const { navigateTo } = useCoolAndLovely();\n const query = useCollection(collectionId);\n const publish = usePublishCollection();\n\n if (query.isLoading) {\n return (\n <PageLayout title=\"Collection\">\n <LoadingView label=\"Loading collection…\" />\n </PageLayout>\n );\n }\n\n if (query.isError || !query.data) {\n return (\n <PageLayout title=\"Collection\">\n <ErrorView title=\"Collection not found\" onRetry={() => void query.refetch()} />\n </PageLayout>\n );\n }\n\n const collection = query.data;\n const items = collection.items ?? [];\n const canPublish = collection.status === 'DRAFT' || collection.status === 'SCHEDULED';\n\n return (\n <PageLayout\n title={collection.name}\n description={collection.brand?.name ?? undefined}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/collections')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/collections/${collection.id}/edit`)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Pencil className=\"h-4 w-4\" /> Edit\n </button>\n {canPublish && (\n <button\n type=\"button\"\n disabled={publish.isPending}\n onClick={() => publish.mutate(collection.id)}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {publish.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Rocket className=\"h-4 w-4\" />\n )}\n Publish\n </button>\n )}\n </div>\n }\n >\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"lg:col-span-2\">\n <GlassCard treatment=\"glass\" className=\"group overflow-hidden p-0\">\n <Thumbnail\n icon={<Shapes className=\"h-12 w-12\" />}\n seed={collection.id}\n fileId={collection.cover?.id}\n alt={collection.name}\n className=\"aspect-[16/9] w-full sm:aspect-[21/9]\"\n overlay={\n <>\n <div className=\"pointer-events-none absolute inset-0 bg-gradient-to-t from-bg-canvas via-bg-canvas/55 to-transparent\" />\n <div className=\"absolute inset-x-0 bottom-0 flex flex-wrap items-end justify-between gap-3 p-6\">\n <div className=\"min-w-0\">\n <h2 className=\"font-serif text-3xl font-semibold leading-tight text-text-primary\">\n {collection.name}\n </h2>\n <div className=\"mt-2 flex flex-wrap items-center gap-2 text-sm text-text-secondary\">\n {collection.brand?.name && <span>{collection.brand.name}</span>}\n {collection.brand?.name && (\n <span aria-hidden=\"true\" className=\"opacity-60\">\n ·\n </span>\n )}\n <span className=\"rounded-full border border-border-seam bg-bg-surface/80 px-2.5 py-0.5 text-xs font-medium text-text-primary backdrop-blur-sm\">\n {titleCase(collection.season)}\n </span>\n </div>\n </div>\n <StatusBadge status={collection.status} className=\"shrink-0 shadow-sm\" />\n </div>\n </>\n }\n />\n {(collection.description || collection.tags.length > 0) && (\n <div className=\"p-6\">\n {collection.description && (\n <p className=\"text-sm text-text-secondary\">{collection.description}</p>\n )}\n {collection.tags.length > 0 && (\n <TagList\n tags={collection.tags}\n className={collection.description ? 'mt-4' : undefined}\n />\n )}\n </div>\n )}\n </GlassCard>\n\n <section className=\"mt-6\">\n <h3 className=\"mb-3 text-lg font-semibold text-text-primary\">Items ({items.length})</h3>\n {items.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n No items in this collection yet.\n </p>\n ) : (\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-3\">\n {items.map((item) => (\n <button\n key={item.id}\n type=\"button\"\n onClick={() => navigateTo(`/items/${item.id}`)}\n className=\"group overflow-hidden rounded-lg border border-border-seam bg-bg-surface text-left transition-all hover:border-accent-foreground/20 hover:shadow-md\"\n >\n <Thumbnail\n icon={categoryIcon(item.category, 28)}\n seed={item.id}\n fileId={item.image?.id}\n alt={item.name}\n className=\"aspect-[3/4] w-full\"\n />\n <div className=\"p-3\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{item.name}</p>\n <p className=\"mt-0.5 text-xs font-semibold text-text-primary tabular-nums\">\n {formatPrice(item.price, item.currency)}\n </p>\n {item.colours.length > 0 && (\n <ColourSwatchRow colours={item.colours} max={5} className=\"mt-2\" />\n )}\n </div>\n </button>\n ))}\n </div>\n )}\n <button\n type=\"button\"\n onClick={() =>\n navigateTo(`/items/new?brandId=${collection.brandId}&collectionId=${collection.id}`)\n }\n className=\"mt-3 inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Plus className=\"h-4 w-4\" /> Add item to this collection\n </button>\n </section>\n </div>\n\n <div className=\"space-y-4\">\n <GlassCard treatment=\"plain\" className=\"p-5\">\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">Details</h3>\n <dl className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Season</dt>\n <dd className=\"text-text-primary\">{titleCase(collection.season)}</dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Publish at</dt>\n <dd className=\"text-text-primary\">\n {collection.publishAt\n ? formatDate(collection.publishAt, { hour: undefined, minute: undefined })\n : '—'}\n </dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Ends at</dt>\n <dd className=\"text-text-primary\">\n {collection.endAt\n ? formatDate(collection.endAt, { hour: undefined, minute: undefined })\n : '—'}\n </dd>\n </div>\n </dl>\n </GlassCard>\n\n {collection.brand && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/brands/${collection.brand?.id}`)}\n className=\"flex w-full items-center gap-3 rounded-lg border border-border-seam bg-bg-surface p-4 text-left transition-colors hover:bg-bg-sunken/50\"\n >\n <Thumbnail\n icon={<Store className=\"h-5 w-5\" />}\n seed={collection.brand.id}\n className=\"h-10 w-10 shrink-0\"\n />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {collection.brand.name}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">View brand</p>\n </div>\n </button>\n )}\n\n {publish.isError && (\n <p className=\"rounded-lg bg-status-error-bg-subtle px-3 py-2 text-xs text-status-error-text\">\n {publish.error instanceof Error ? publish.error.message : 'Publish failed'}\n </p>\n )}\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"CollectionDetailPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/collections/CollectionDetailPage.tsx"],"sourcesContent":["/**\n * Collection Detail Page\n * @module coolandlovely/pages/collections\n *\n * Single collection (`cooCollection`) with publish (`cooPublishCollection`),\n * edit, its brand link and member items.\n */\n\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, Pencil, Rocket, Archive, Loader2, Shapes, Store, Plus } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { TagList } from '../../components/TagList';\nimport { ColourSwatchRow } from '../../components/ColourSwatchRow';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useCollection, usePublishCollection, useArchiveCollection } from '../../hooks';\nimport { categoryIcon } from '../../utils/icons';\nimport { formatPrice, formatDate, titleCase } from '../../utils/formatters';\n\nexport function CollectionDetailPage() {\n const { collectionId } = useParams<{ collectionId: string }>();\n const { navigateTo } = useCoolAndLovely();\n const query = useCollection(collectionId);\n const publish = usePublishCollection();\n const archive = useArchiveCollection();\n\n if (query.isLoading) {\n return (\n <PageLayout title=\"Collection\">\n <LoadingView label=\"Loading collection…\" />\n </PageLayout>\n );\n }\n\n if (query.isError || !query.data) {\n return (\n <PageLayout title=\"Collection\">\n <ErrorView title=\"Collection not found\" onRetry={() => void query.refetch()} />\n </PageLayout>\n );\n }\n\n const collection = query.data;\n const items = collection.items ?? [];\n const canPublish = collection.status === 'DRAFT' || collection.status === 'SCHEDULED';\n const isArchived = collection.status === 'ARCHIVED';\n\n return (\n <PageLayout\n title={collection.name}\n description={collection.brand?.name ?? undefined}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/collections')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/collections/${collection.id}/edit`)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Pencil className=\"h-4 w-4\" /> Edit\n </button>\n {canPublish && (\n <button\n type=\"button\"\n disabled={publish.isPending}\n onClick={() => publish.mutate(collection.id)}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {publish.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Rocket className=\"h-4 w-4\" />\n )}\n Publish\n </button>\n )}\n {!isArchived && (\n <button\n type=\"button\"\n disabled={archive.isPending}\n onClick={() =>\n archive.mutate(collection.id, { onSuccess: () => navigateTo('/collections') })\n }\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-50\"\n >\n {archive.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Archive className=\"h-4 w-4\" />\n )}\n Archive\n </button>\n )}\n </div>\n }\n >\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"lg:col-span-2\">\n <GlassCard treatment=\"glass\" className=\"group overflow-hidden p-0\">\n <Thumbnail\n icon={<Shapes className=\"h-12 w-12\" />}\n seed={collection.id}\n fileId={collection.cover?.id}\n alt={collection.name}\n className=\"aspect-[16/9] w-full sm:aspect-[21/9]\"\n overlay={\n <>\n <div className=\"pointer-events-none absolute inset-0 bg-gradient-to-t from-bg-canvas via-bg-canvas/55 to-transparent\" />\n <div className=\"absolute inset-x-0 bottom-0 flex flex-wrap items-end justify-between gap-3 p-6\">\n <div className=\"min-w-0\">\n <h2 className=\"font-serif text-3xl font-semibold leading-tight text-text-primary\">\n {collection.name}\n </h2>\n <div className=\"mt-2 flex flex-wrap items-center gap-2 text-sm text-text-secondary\">\n {collection.brand?.name && <span>{collection.brand.name}</span>}\n {collection.brand?.name && (\n <span aria-hidden=\"true\" className=\"opacity-60\">\n ·\n </span>\n )}\n <span className=\"rounded-full border border-border-seam bg-bg-surface/80 px-2.5 py-0.5 text-xs font-medium text-text-primary backdrop-blur-sm\">\n {titleCase(collection.season)}\n </span>\n </div>\n </div>\n <StatusBadge status={collection.status} className=\"shrink-0 shadow-sm\" />\n </div>\n </>\n }\n />\n {(collection.description || collection.tags.length > 0) && (\n <div className=\"p-6\">\n {collection.description && (\n <p className=\"text-sm text-text-secondary\">{collection.description}</p>\n )}\n {collection.tags.length > 0 && (\n <TagList\n tags={collection.tags}\n className={collection.description ? 'mt-4' : undefined}\n />\n )}\n </div>\n )}\n </GlassCard>\n\n <section className=\"mt-6\">\n <h3 className=\"mb-3 text-lg font-semibold text-text-primary\">Items ({items.length})</h3>\n {items.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n No items in this collection yet.\n </p>\n ) : (\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-3\">\n {items.map((item) => (\n <button\n key={item.id}\n type=\"button\"\n onClick={() => navigateTo(`/items/${item.id}`)}\n className=\"group overflow-hidden rounded-lg border border-border-seam bg-bg-surface text-left transition-all hover:border-accent-foreground/20 hover:shadow-md\"\n >\n <Thumbnail\n icon={categoryIcon(item.category, 28)}\n seed={item.id}\n fileId={item.image?.id}\n alt={item.name}\n className=\"aspect-[3/4] w-full\"\n />\n <div className=\"p-3\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{item.name}</p>\n <p className=\"mt-0.5 text-xs font-semibold text-text-primary tabular-nums\">\n {formatPrice(item.price, item.currency)}\n </p>\n {item.colours.length > 0 && (\n <ColourSwatchRow colours={item.colours} max={5} className=\"mt-2\" />\n )}\n </div>\n </button>\n ))}\n </div>\n )}\n <button\n type=\"button\"\n onClick={() =>\n navigateTo(`/items/new?brandId=${collection.brandId}&collectionId=${collection.id}`)\n }\n className=\"mt-3 inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Plus className=\"h-4 w-4\" /> Add item to this collection\n </button>\n </section>\n </div>\n\n <div className=\"space-y-4\">\n <GlassCard treatment=\"plain\" className=\"p-5\">\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">Details</h3>\n <dl className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Season</dt>\n <dd className=\"text-text-primary\">{titleCase(collection.season)}</dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Publish at</dt>\n <dd className=\"text-text-primary\">\n {collection.publishAt\n ? formatDate(collection.publishAt, { hour: undefined, minute: undefined })\n : '—'}\n </dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Ends at</dt>\n <dd className=\"text-text-primary\">\n {collection.endAt\n ? formatDate(collection.endAt, { hour: undefined, minute: undefined })\n : '—'}\n </dd>\n </div>\n </dl>\n </GlassCard>\n\n {collection.brand && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/brands/${collection.brand?.id}`)}\n className=\"flex w-full items-center gap-3 rounded-lg border border-border-seam bg-bg-surface p-4 text-left transition-colors hover:bg-bg-sunken/50\"\n >\n <Thumbnail\n icon={<Store className=\"h-5 w-5\" />}\n seed={collection.brand.id}\n className=\"h-10 w-10 shrink-0\"\n />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {collection.brand.name}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">View brand</p>\n </div>\n </button>\n )}\n\n {publish.isError && (\n <p className=\"rounded-lg bg-status-error-bg-subtle px-3 py-2 text-xs text-status-error-text\">\n {publish.error instanceof Error ? publish.error.message : 'Publish failed'}\n </p>\n )}\n {archive.isError && (\n <p className=\"rounded-lg bg-status-error-bg-subtle px-3 py-2 text-xs text-status-error-text\">\n {archive.error instanceof Error ? archive.error.message : 'Archive failed'}\n </p>\n )}\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -5,8 +5,8 @@ import { Thumbnail as i } from "../../components/Thumbnail.js";
5
5
  import { useLooks as a, useMyFollowing as o, useMyStyleProfile as s, useStyleItems as c } from "../../hooks/useCoolAndLovelyApi.js";
6
6
  import { titleCase as l } from "../../utils/formatters.js";
7
7
  import "../../hooks/index.js";
8
- import { TagList as u } from "../../components/TagList.js";
9
- import { EmptyState as d } from "../../components/EmptyState.js";
8
+ import { EmptyState as u } from "../../components/EmptyState.js";
9
+ import { ColourSwatchRow as d } from "../../components/ColourSwatchRow.js";
10
10
  import { useMemo as f } from "react";
11
11
  import { Compass as p, Heart as m, ShoppingBag as h, Sparkles as g, UserCheck as _, Users as v } from "lucide-react";
12
12
  import { jsx as y, jsxs as b } from "react/jsx-runtime";
@@ -103,7 +103,7 @@ function E() {
103
103
  title: "Made for you",
104
104
  icon: /* @__PURE__ */ y(p, { className: "h-5 w-5" }),
105
105
  sublabel: N.hasTaste ? "Ranked to match your style profile" : "Most-loved looks on the platform",
106
- children: N.madeForYou.length === 0 ? /* @__PURE__ */ y(d, {
106
+ children: N.madeForYou.length === 0 ? /* @__PURE__ */ y(u, {
107
107
  title: "No looks yet",
108
108
  description: "Once looks are published they will appear here, tuned to your taste."
109
109
  }) : /* @__PURE__ */ y("div", {
@@ -169,8 +169,9 @@ function E() {
169
169
  className: "mt-0.5 text-xs text-text-secondary",
170
170
  children: l(e.category)
171
171
  }),
172
- /* @__PURE__ */ y(u, {
173
- tags: e.colours.slice(0, 3),
172
+ /* @__PURE__ */ y(d, {
173
+ colours: e.colours,
174
+ max: 5,
174
175
  className: "mt-2"
175
176
  })
176
177
  ]
@@ -182,7 +183,7 @@ function E() {
182
183
  title: "From people you follow",
183
184
  icon: /* @__PURE__ */ y(_, { className: "h-5 w-5" }),
184
185
  sublabel: N.followingProfiles.length === 0 ? "Follow creators and shoppers to see their looks here" : `You follow ${N.followingProfiles.length} ${N.followingProfiles.length === 1 ? "person" : "people"}`,
185
- children: N.followingProfiles.length === 0 ? /* @__PURE__ */ y(d, {
186
+ children: N.followingProfiles.length === 0 ? /* @__PURE__ */ y(u, {
186
187
  title: "You are not following anyone yet",
187
188
  description: "Open a look or a style profile and tap Follow to build a feed from people whose taste you love.",
188
189
  icon: /* @__PURE__ */ y(v, { className: "h-8 w-8" })
@@ -1 +1 @@
1
- {"version":3,"file":"DiscoverPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/discover/DiscoverPage.tsx"],"sourcesContent":["/**\n * Discover Page\n * @module coolandlovely/pages/discover\n *\n * A profile-tuned discovery feed computed entirely client-side over the REAL\n * catalogue + social lists (cooListLooks / cooListStyleItems) and the caller's\n * own style profile (cooMyStyleProfile) + follow graph (cooMyFollowing). It only\n * ranks what is actually on the platform against the taste the shopper declared\n * — no fabricated recommendations, no black-box \"AI\" (that is roadmap). When the\n * shopper has no profile yet, it invites them to build one.\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { useMemo } from 'react';\nimport { Compass, Sparkles, Heart, ShoppingBag, UserCheck, Users } from 'lucide-react';\nimport { EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { TagList } from '../../components/TagList';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useMyStyleProfile, useLooks, useStyleItems, useMyFollowing } from '../../hooks';\nimport type { CooLook, CooStyleItem } from '../../types';\nimport { titleCase } from '../../utils/formatters';\n\n/** Lower-cased token set used for overlap scoring. */\nfunction tokenSet(values: string[]): Set<string> {\n return new Set(values.map((v) => v.trim().toLowerCase()).filter(Boolean));\n}\n\n/** Count how many of `candidate`'s tokens appear in the taste set. */\nfunction overlap(candidate: string[], taste: Set<string>): number {\n let n = 0;\n for (const c of candidate) {\n if (taste.has(c.trim().toLowerCase())) n += 1;\n }\n return n;\n}\n\ninterface SectionProps {\n title: string;\n icon: ReactNode;\n children: ReactNode;\n sublabel?: string;\n}\n\nconst Section: FC<SectionProps> = ({ title, icon, sublabel, children }) => (\n <section className=\"mt-8\">\n <div className=\"mb-3\">\n <h2 className=\"flex items-center gap-2 text-lg font-semibold text-text-primary\">\n {icon} {title}\n </h2>\n {sublabel ? <p className=\"mt-0.5 text-sm text-text-secondary\">{sublabel}</p> : null}\n </div>\n {children}\n </section>\n);\n\nexport function DiscoverPage() {\n const { navigateTo } = useCoolAndLovely();\n const profile = useMyStyleProfile();\n const looks = useLooks({ pagination: { limit: 100, offset: 0 } });\n const items = useStyleItems({ pagination: { limit: 100, offset: 0 } });\n const following = useMyFollowing();\n\n const isLoading = profile.isLoading || looks.isLoading || items.isLoading;\n const isError = looks.isError && items.isError;\n\n const feed = useMemo(() => {\n const p = profile.data;\n const lookRows = looks.data?.items ?? [];\n const itemRows = items.data?.items ?? [];\n const followingOwners = new Set((following.data ?? []).map((f) => f.ownerId));\n\n // Taste = declared style preferences ∪ palette ∪ favourite brands.\n const taste = tokenSet([\n ...(p?.stylePreferences ?? []),\n ...(p?.palette ?? []),\n ...(p?.favouriteBrands ?? []),\n ]);\n const paletteTokens = tokenSet(p?.palette ?? []);\n const brandTokens = tokenSet(p?.favouriteBrands ?? []);\n\n const scoreLook = (l: CooLook): number =>\n overlap(l.tags, taste) * 2 +\n (taste.has(l.occasion.toLowerCase()) ? 1 : 0) +\n l.likeCount * 0.05 +\n l.saveCount * 0.05;\n\n const scoreItem = (it: CooStyleItem): number =>\n overlap(it.tags, taste) * 2 +\n overlap(it.colours, paletteTokens) * 2 +\n (taste.has(it.category.toLowerCase()) ? 1 : 0) +\n (brandTokens.size && it.tags.some((t) => brandTokens.has(t.toLowerCase())) ? 1 : 0);\n\n const hasTaste = taste.size > 0;\n\n // \"Made for you\": when the shopper has taste, rank by match then engagement;\n // otherwise fall back to most-loved looks so the feed is never empty.\n const madeForYou = [...lookRows]\n .sort((a, b) =>\n hasTaste\n ? scoreLook(b) - scoreLook(a)\n : b.likeCount + b.saveCount - (a.likeCount + a.saveCount)\n )\n .slice(0, 8);\n\n const inYourPalette = [...itemRows]\n .filter((it) => (hasTaste ? scoreItem(it) > 0 : true))\n .sort((a, b) => scoreItem(b) - scoreItem(a))\n .slice(0, 8);\n\n const fromFollowing = lookRows\n .filter((l) => followingOwners.has(l.authorId))\n .sort((a, b) => b.likeCount + b.saveCount - (a.likeCount + a.saveCount))\n .slice(0, 8);\n\n return {\n hasProfile: Boolean(p),\n hasTaste,\n madeForYou,\n inYourPalette,\n fromFollowing,\n followingProfiles: following.data ?? [],\n };\n }, [profile.data, looks.data, items.data, following.data]);\n\n if (isLoading) {\n return (\n <PageLayout title=\"Discover\">\n <LoadingView label=\"Curating your feed…\" />\n </PageLayout>\n );\n }\n\n if (isError) {\n return (\n <PageLayout title=\"Discover\">\n <ErrorView\n onRetry={() => {\n void looks.refetch();\n void items.refetch();\n }}\n />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout title=\"Discover\" description=\"Looks and pieces tuned to your taste\" hero>\n <PagePurpose className=\"mb-6\">\n Your personalised feed, computed live from your style profile against the pieces and looks\n on the platform — the closer a piece matches the aesthetics, palette and brands you follow,\n the higher it ranks. Nothing here is fabricated.\n </PagePurpose>\n\n {!feed.hasProfile ? (\n <EmphasisPanel className=\"mb-8\">\n <div className=\"flex flex-col items-start gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Build your style profile</h2>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Tell us your aesthetics, palette and favourite brands and this feed tunes itself to\n you.\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => navigateTo('/profile')}\n className=\"inline-flex shrink-0 items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-opacity hover:opacity-90\"\n >\n <Sparkles className=\"h-4 w-4\" /> Create profile\n </button>\n </div>\n </EmphasisPanel>\n ) : null}\n\n <Section\n title=\"Made for you\"\n icon={<Compass className=\"h-5 w-5\" />}\n sublabel={\n feed.hasTaste ? 'Ranked to match your style profile' : 'Most-loved looks on the platform'\n }\n >\n {feed.madeForYou.length === 0 ? (\n <EmptyState\n title=\"No looks yet\"\n description=\"Once looks are published they will appear here, tuned to your taste.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n {feed.madeForYou.map((look) => (\n <button\n key={look.id}\n type=\"button\"\n onClick={() => navigateTo(`/looks/${look.id}`)}\n className=\"group flex flex-col overflow-hidden rounded-lg border border-border-seam bg-bg-surface text-left transition-colors hover:border-accent-foreground/20 hover:shadow-md\"\n >\n <Thumbnail\n icon={<Sparkles className=\"h-6 w-6\" />}\n seed={look.id}\n fileId={look.image?.id}\n alt={look.title}\n className=\"aspect-[4/5] w-full rounded-none\"\n />\n <div className=\"min-w-0 p-3\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{look.title}</p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">{titleCase(look.occasion)}</p>\n <span className=\"mt-2 inline-flex items-center gap-1 text-xs text-text-secondary\">\n <Heart className=\"h-3.5 w-3.5\" /> {look.likeCount}\n </span>\n </div>\n </button>\n ))}\n </div>\n )}\n </Section>\n\n {feed.inYourPalette.length > 0 ? (\n <Section\n title=\"In your palette\"\n icon={<ShoppingBag className=\"h-5 w-5\" />}\n sublabel=\"Pieces that match your colours, categories and tags\"\n >\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n {feed.inYourPalette.map((item) => (\n <button\n key={item.id}\n type=\"button\"\n onClick={() => navigateTo(`/items/${item.id}`)}\n className=\"group flex flex-col overflow-hidden rounded-lg border border-border-seam bg-bg-surface text-left transition-colors hover:border-accent-foreground/20 hover:shadow-md\"\n >\n <Thumbnail\n icon={<ShoppingBag className=\"h-6 w-6\" />}\n seed={item.id}\n fileId={item.image?.id}\n alt={item.name}\n className=\"aspect-[4/5] w-full rounded-none\"\n />\n <div className=\"min-w-0 p-3\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{item.name}</p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">{titleCase(item.category)}</p>\n <TagList tags={item.colours.slice(0, 3)} className=\"mt-2\" />\n </div>\n </button>\n ))}\n </div>\n </Section>\n ) : null}\n\n <Section\n title=\"From people you follow\"\n icon={<UserCheck className=\"h-5 w-5\" />}\n sublabel={\n feed.followingProfiles.length === 0\n ? 'Follow creators and shoppers to see their looks here'\n : `You follow ${feed.followingProfiles.length} ${feed.followingProfiles.length === 1 ? 'person' : 'people'}`\n }\n >\n {feed.followingProfiles.length === 0 ? (\n <EmptyState\n title=\"You are not following anyone yet\"\n description=\"Open a look or a style profile and tap Follow to build a feed from people whose taste you love.\"\n icon={<Users className=\"h-8 w-8\" />}\n />\n ) : feed.fromFollowing.length === 0 ? (\n <div className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n The people you follow have not published looks visible to you yet.\n </div>\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n {feed.fromFollowing.map((look) => (\n <button\n key={look.id}\n type=\"button\"\n onClick={() => navigateTo(`/looks/${look.id}`)}\n className=\"group flex flex-col overflow-hidden rounded-lg border border-border-seam bg-bg-surface text-left transition-colors hover:border-accent-foreground/20 hover:shadow-md\"\n >\n <Thumbnail\n icon={<Sparkles className=\"h-6 w-6\" />}\n seed={look.id}\n fileId={look.image?.id}\n alt={look.title}\n className=\"aspect-[4/5] w-full rounded-none\"\n />\n <div className=\"min-w-0 p-3\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{look.title}</p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">{titleCase(look.occasion)}</p>\n </div>\n </button>\n ))}\n </div>\n )}\n </Section>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"DiscoverPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/discover/DiscoverPage.tsx"],"sourcesContent":["/**\n * Discover Page\n * @module coolandlovely/pages/discover\n *\n * A profile-tuned discovery feed computed entirely client-side over the REAL\n * catalogue + social lists (cooListLooks / cooListStyleItems) and the caller's\n * own style profile (cooMyStyleProfile) + follow graph (cooMyFollowing). It only\n * ranks what is actually on the platform against the taste the shopper declared\n * — no fabricated recommendations, no black-box \"AI\" (that is roadmap). When the\n * shopper has no profile yet, it invites them to build one.\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { useMemo } from 'react';\nimport { Compass, Sparkles, Heart, ShoppingBag, UserCheck, Users } from 'lucide-react';\nimport { EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { ColourSwatchRow } from '../../components/ColourSwatchRow';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useMyStyleProfile, useLooks, useStyleItems, useMyFollowing } from '../../hooks';\nimport type { CooLook, CooStyleItem } from '../../types';\nimport { titleCase } from '../../utils/formatters';\n\n/** Lower-cased token set used for overlap scoring. */\nfunction tokenSet(values: string[]): Set<string> {\n return new Set(values.map((v) => v.trim().toLowerCase()).filter(Boolean));\n}\n\n/** Count how many of `candidate`'s tokens appear in the taste set. */\nfunction overlap(candidate: string[], taste: Set<string>): number {\n let n = 0;\n for (const c of candidate) {\n if (taste.has(c.trim().toLowerCase())) n += 1;\n }\n return n;\n}\n\ninterface SectionProps {\n title: string;\n icon: ReactNode;\n children: ReactNode;\n sublabel?: string;\n}\n\nconst Section: FC<SectionProps> = ({ title, icon, sublabel, children }) => (\n <section className=\"mt-8\">\n <div className=\"mb-3\">\n <h2 className=\"flex items-center gap-2 text-lg font-semibold text-text-primary\">\n {icon} {title}\n </h2>\n {sublabel ? <p className=\"mt-0.5 text-sm text-text-secondary\">{sublabel}</p> : null}\n </div>\n {children}\n </section>\n);\n\nexport function DiscoverPage() {\n const { navigateTo } = useCoolAndLovely();\n const profile = useMyStyleProfile();\n const looks = useLooks({ pagination: { limit: 100, offset: 0 } });\n const items = useStyleItems({ pagination: { limit: 100, offset: 0 } });\n const following = useMyFollowing();\n\n const isLoading = profile.isLoading || looks.isLoading || items.isLoading;\n const isError = looks.isError && items.isError;\n\n const feed = useMemo(() => {\n const p = profile.data;\n const lookRows = looks.data?.items ?? [];\n const itemRows = items.data?.items ?? [];\n const followingOwners = new Set((following.data ?? []).map((f) => f.ownerId));\n\n // Taste = declared style preferences ∪ palette ∪ favourite brands.\n const taste = tokenSet([\n ...(p?.stylePreferences ?? []),\n ...(p?.palette ?? []),\n ...(p?.favouriteBrands ?? []),\n ]);\n const paletteTokens = tokenSet(p?.palette ?? []);\n const brandTokens = tokenSet(p?.favouriteBrands ?? []);\n\n const scoreLook = (l: CooLook): number =>\n overlap(l.tags, taste) * 2 +\n (taste.has(l.occasion.toLowerCase()) ? 1 : 0) +\n l.likeCount * 0.05 +\n l.saveCount * 0.05;\n\n const scoreItem = (it: CooStyleItem): number =>\n overlap(it.tags, taste) * 2 +\n overlap(it.colours, paletteTokens) * 2 +\n (taste.has(it.category.toLowerCase()) ? 1 : 0) +\n (brandTokens.size && it.tags.some((t) => brandTokens.has(t.toLowerCase())) ? 1 : 0);\n\n const hasTaste = taste.size > 0;\n\n // \"Made for you\": when the shopper has taste, rank by match then engagement;\n // otherwise fall back to most-loved looks so the feed is never empty.\n const madeForYou = [...lookRows]\n .sort((a, b) =>\n hasTaste\n ? scoreLook(b) - scoreLook(a)\n : b.likeCount + b.saveCount - (a.likeCount + a.saveCount)\n )\n .slice(0, 8);\n\n const inYourPalette = [...itemRows]\n .filter((it) => (hasTaste ? scoreItem(it) > 0 : true))\n .sort((a, b) => scoreItem(b) - scoreItem(a))\n .slice(0, 8);\n\n const fromFollowing = lookRows\n .filter((l) => followingOwners.has(l.authorId))\n .sort((a, b) => b.likeCount + b.saveCount - (a.likeCount + a.saveCount))\n .slice(0, 8);\n\n return {\n hasProfile: Boolean(p),\n hasTaste,\n madeForYou,\n inYourPalette,\n fromFollowing,\n followingProfiles: following.data ?? [],\n };\n }, [profile.data, looks.data, items.data, following.data]);\n\n if (isLoading) {\n return (\n <PageLayout title=\"Discover\">\n <LoadingView label=\"Curating your feed…\" />\n </PageLayout>\n );\n }\n\n if (isError) {\n return (\n <PageLayout title=\"Discover\">\n <ErrorView\n onRetry={() => {\n void looks.refetch();\n void items.refetch();\n }}\n />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout title=\"Discover\" description=\"Looks and pieces tuned to your taste\" hero>\n <PagePurpose className=\"mb-6\">\n Your personalised feed, computed live from your style profile against the pieces and looks\n on the platform — the closer a piece matches the aesthetics, palette and brands you follow,\n the higher it ranks. Nothing here is fabricated.\n </PagePurpose>\n\n {!feed.hasProfile ? (\n <EmphasisPanel className=\"mb-8\">\n <div className=\"flex flex-col items-start gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Build your style profile</h2>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Tell us your aesthetics, palette and favourite brands and this feed tunes itself to\n you.\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => navigateTo('/profile')}\n className=\"inline-flex shrink-0 items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-opacity hover:opacity-90\"\n >\n <Sparkles className=\"h-4 w-4\" /> Create profile\n </button>\n </div>\n </EmphasisPanel>\n ) : null}\n\n <Section\n title=\"Made for you\"\n icon={<Compass className=\"h-5 w-5\" />}\n sublabel={\n feed.hasTaste ? 'Ranked to match your style profile' : 'Most-loved looks on the platform'\n }\n >\n {feed.madeForYou.length === 0 ? (\n <EmptyState\n title=\"No looks yet\"\n description=\"Once looks are published they will appear here, tuned to your taste.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n {feed.madeForYou.map((look) => (\n <button\n key={look.id}\n type=\"button\"\n onClick={() => navigateTo(`/looks/${look.id}`)}\n className=\"group flex flex-col overflow-hidden rounded-lg border border-border-seam bg-bg-surface text-left transition-colors hover:border-accent-foreground/20 hover:shadow-md\"\n >\n <Thumbnail\n icon={<Sparkles className=\"h-6 w-6\" />}\n seed={look.id}\n fileId={look.image?.id}\n alt={look.title}\n className=\"aspect-[4/5] w-full rounded-none\"\n />\n <div className=\"min-w-0 p-3\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{look.title}</p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">{titleCase(look.occasion)}</p>\n <span className=\"mt-2 inline-flex items-center gap-1 text-xs text-text-secondary\">\n <Heart className=\"h-3.5 w-3.5\" /> {look.likeCount}\n </span>\n </div>\n </button>\n ))}\n </div>\n )}\n </Section>\n\n {feed.inYourPalette.length > 0 ? (\n <Section\n title=\"In your palette\"\n icon={<ShoppingBag className=\"h-5 w-5\" />}\n sublabel=\"Pieces that match your colours, categories and tags\"\n >\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n {feed.inYourPalette.map((item) => (\n <button\n key={item.id}\n type=\"button\"\n onClick={() => navigateTo(`/items/${item.id}`)}\n className=\"group flex flex-col overflow-hidden rounded-lg border border-border-seam bg-bg-surface text-left transition-colors hover:border-accent-foreground/20 hover:shadow-md\"\n >\n <Thumbnail\n icon={<ShoppingBag className=\"h-6 w-6\" />}\n seed={item.id}\n fileId={item.image?.id}\n alt={item.name}\n className=\"aspect-[4/5] w-full rounded-none\"\n />\n <div className=\"min-w-0 p-3\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{item.name}</p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">{titleCase(item.category)}</p>\n <ColourSwatchRow colours={item.colours} max={5} className=\"mt-2\" />\n </div>\n </button>\n ))}\n </div>\n </Section>\n ) : null}\n\n <Section\n title=\"From people you follow\"\n icon={<UserCheck className=\"h-5 w-5\" />}\n sublabel={\n feed.followingProfiles.length === 0\n ? 'Follow creators and shoppers to see their looks here'\n : `You follow ${feed.followingProfiles.length} ${feed.followingProfiles.length === 1 ? 'person' : 'people'}`\n }\n >\n {feed.followingProfiles.length === 0 ? (\n <EmptyState\n title=\"You are not following anyone yet\"\n description=\"Open a look or a style profile and tap Follow to build a feed from people whose taste you love.\"\n icon={<Users className=\"h-8 w-8\" />}\n />\n ) : feed.fromFollowing.length === 0 ? (\n <div className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n The people you follow have not published looks visible to you yet.\n </div>\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n {feed.fromFollowing.map((look) => (\n <button\n key={look.id}\n type=\"button\"\n onClick={() => navigateTo(`/looks/${look.id}`)}\n className=\"group flex flex-col overflow-hidden rounded-lg border border-border-seam bg-bg-surface text-left transition-colors hover:border-accent-foreground/20 hover:shadow-md\"\n >\n <Thumbnail\n icon={<Sparkles className=\"h-6 w-6\" />}\n seed={look.id}\n fileId={look.image?.id}\n alt={look.title}\n className=\"aspect-[4/5] w-full rounded-none\"\n />\n <div className=\"min-w-0 p-3\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{look.title}</p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">{titleCase(look.occasion)}</p>\n </div>\n </button>\n ))}\n </div>\n )}\n </Section>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -8,8 +8,8 @@ import { AddToLookbookButton as l } from "../../components/AddToLookbookButton.j
8
8
  import { formatDate as u, formatPrice as d, titleCase as f } from "../../utils/formatters.js";
9
9
  import { LookCard as p } from "../../components/LookCard.js";
10
10
  import "../../hooks/index.js";
11
- import { TagList as m } from "../../components/TagList.js";
12
- import { categoryIcon as h } from "../../utils/icons.js";
11
+ import { categoryIcon as m } from "../../utils/icons.js";
12
+ import { TagList as h } from "../../components/TagList.js";
13
13
  import { ColourSwatchRow as g } from "../../components/ColourSwatchRow.js";
14
14
  import { Archive as _, ArrowLeft as v, ChevronRight as y, Loader2 as b, Pencil as x, Shapes as S } from "lucide-react";
15
15
  import { useParams as C } from "react-router-dom";
@@ -69,7 +69,7 @@ function D() {
69
69
  treatment: "glass",
70
70
  className: "group overflow-hidden p-0",
71
71
  children: [/* @__PURE__ */ w(a, {
72
- icon: h(M.category, 48),
72
+ icon: m(M.category, 48),
73
73
  seed: M.id,
74
74
  fileId: M.image?.id,
75
75
  alt: M.name,
@@ -139,7 +139,7 @@ function D() {
139
139
  M.tags.length > 0 && /* @__PURE__ */ T("div", { children: [/* @__PURE__ */ w("p", {
140
140
  className: "mb-2 text-xs font-medium uppercase tracking-wide text-text-secondary",
141
141
  children: "Tags"
142
- }), /* @__PURE__ */ w(m, {
142
+ }), /* @__PURE__ */ w(h, {
143
143
  tags: M.tags,
144
144
  max: 12,
145
145
  tone: "accent"
@@ -1 +1 @@
1
- {"version":3,"file":"LookbookDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/coolandlovely/pages/lookbooks/LookbookDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AA2BH,wBAAgB,kBAAkB,gCAmRjC"}
1
+ {"version":3,"file":"LookbookDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/coolandlovely/pages/lookbooks/LookbookDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AA4BH,wBAAgB,kBAAkB,gCAoSjC"}