@burdenoff/microfe-coolandlovely 2026.706.1 → 2026.706.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 (66) hide show
  1. package/dist/coolandlovely/CoolAndLovelyRoutes.js +14 -6
  2. package/dist/coolandlovely/CoolAndLovelyRoutes.js.map +1 -1
  3. package/dist/coolandlovely/components/ActorName.js +29 -0
  4. package/dist/coolandlovely/components/ActorName.js.map +1 -0
  5. package/dist/coolandlovely/components/AddToLookbookButton.js +81 -0
  6. package/dist/coolandlovely/components/AddToLookbookButton.js.map +1 -0
  7. package/dist/coolandlovely/components/Comments.js +109 -0
  8. package/dist/coolandlovely/components/Comments.js.map +1 -0
  9. package/dist/coolandlovely/components/Field.js +22 -2
  10. package/dist/coolandlovely/components/Field.js.map +1 -1
  11. package/dist/coolandlovely/components/FileImage.js +17 -0
  12. package/dist/coolandlovely/components/FileImage.js.map +1 -0
  13. package/dist/coolandlovely/components/FollowButton.js +24 -0
  14. package/dist/coolandlovely/components/FollowButton.js.map +1 -0
  15. package/dist/coolandlovely/components/ImageUploadField.js +71 -0
  16. package/dist/coolandlovely/components/ImageUploadField.js.map +1 -0
  17. package/dist/coolandlovely/components/Thumbnail.js +20 -13
  18. package/dist/coolandlovely/components/Thumbnail.js.map +1 -1
  19. package/dist/coolandlovely/hooks/index.js +2 -0
  20. package/dist/coolandlovely/hooks/useComments.js +68 -0
  21. package/dist/coolandlovely/hooks/useComments.js.map +1 -0
  22. package/dist/coolandlovely/hooks/useCoolAndLovelyApi.js +74 -4
  23. package/dist/coolandlovely/hooks/useCoolAndLovelyApi.js.map +1 -1
  24. package/dist/coolandlovely/hooks/useFiles.js +94 -0
  25. package/dist/coolandlovely/hooks/useFiles.js.map +1 -0
  26. package/dist/coolandlovely/pages/HomePage.js +105 -87
  27. package/dist/coolandlovely/pages/HomePage.js.map +1 -1
  28. package/dist/coolandlovely/pages/brands/BrandDetailPage.js +144 -122
  29. package/dist/coolandlovely/pages/brands/BrandDetailPage.js.map +1 -1
  30. package/dist/coolandlovely/pages/brands/BrandFormPage.js +128 -90
  31. package/dist/coolandlovely/pages/brands/BrandFormPage.js.map +1 -1
  32. package/dist/coolandlovely/pages/brands/BrandsListPage.js +2 -0
  33. package/dist/coolandlovely/pages/brands/BrandsListPage.js.map +1 -1
  34. package/dist/coolandlovely/pages/collections/CollectionDetailPage.js +5 -1
  35. package/dist/coolandlovely/pages/collections/CollectionDetailPage.js.map +1 -1
  36. package/dist/coolandlovely/pages/collections/CollectionFormPage.js +117 -102
  37. package/dist/coolandlovely/pages/collections/CollectionFormPage.js.map +1 -1
  38. package/dist/coolandlovely/pages/collections/CollectionsListPage.js +2 -0
  39. package/dist/coolandlovely/pages/collections/CollectionsListPage.js.map +1 -1
  40. package/dist/coolandlovely/pages/discover/DiscoverPage.js +223 -0
  41. package/dist/coolandlovely/pages/discover/DiscoverPage.js.map +1 -0
  42. package/dist/coolandlovely/pages/items/StyleItemDetailPage.js +99 -95
  43. package/dist/coolandlovely/pages/items/StyleItemDetailPage.js.map +1 -1
  44. package/dist/coolandlovely/pages/items/StyleItemFormPage.js +144 -130
  45. package/dist/coolandlovely/pages/items/StyleItemFormPage.js.map +1 -1
  46. package/dist/coolandlovely/pages/items/StyleItemsListPage.js +2 -0
  47. package/dist/coolandlovely/pages/items/StyleItemsListPage.js.map +1 -1
  48. package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js +190 -99
  49. package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js.map +1 -1
  50. package/dist/coolandlovely/pages/lookbooks/LookbooksListPage.js +81 -68
  51. package/dist/coolandlovely/pages/lookbooks/LookbooksListPage.js.map +1 -1
  52. package/dist/coolandlovely/pages/looks/LookDetailPage.js +195 -155
  53. package/dist/coolandlovely/pages/looks/LookDetailPage.js.map +1 -1
  54. package/dist/coolandlovely/pages/looks/LookFormPage.js +127 -88
  55. package/dist/coolandlovely/pages/looks/LookFormPage.js.map +1 -1
  56. package/dist/coolandlovely/pages/looks/LooksListPage.js +2 -0
  57. package/dist/coolandlovely/pages/looks/LooksListPage.js.map +1 -1
  58. package/dist/coolandlovely/pages/profile/StyleProfilePage.js +169 -101
  59. package/dist/coolandlovely/pages/profile/StyleProfilePage.js.map +1 -1
  60. package/dist/coolandlovely/pages/trends/TrendsPage.js +249 -0
  61. package/dist/coolandlovely/pages/trends/TrendsPage.js.map +1 -0
  62. package/dist/coolandlovely/pages/wardrobe/WardrobeFormPage.js +88 -76
  63. package/dist/coolandlovely/pages/wardrobe/WardrobeFormPage.js.map +1 -1
  64. package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.js +2 -0
  65. package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.js.map +1 -1
  66. package/package.json +1 -1
@@ -1,209 +1,224 @@
1
1
  import { useCoolAndLovely as e } from "../../context/CoolAndLovelyContext.js";
2
2
  import { PageLayout as t } from "../../components/PageLayout.js";
3
3
  import { ErrorView as n, LoadingView as r } from "../../components/StateViews.js";
4
- import { useBrands as i, useCollection as a, useCreateCollection as o, useUpdateCollection as s } from "../../hooks/useCoolAndLovelyApi.js";
4
+ import { COO_FILE_FOLDERS as i } from "../../hooks/useFiles.js";
5
+ import { useBrands as a, useCollection as o, useCreateCollection as s, useUpdateCollection as c } from "../../hooks/useCoolAndLovelyApi.js";
5
6
  import "../../hooks/index.js";
6
- import { parseCsv as c, titleCase as l } from "../../utils/formatters.js";
7
- import { SelectField as u, TextAreaField as d, TextField as f } from "../../components/Field.js";
8
- import { useEffect as p, useMemo as m, useState as h } from "react";
9
- import { ArrowLeft as g, Loader2 as _, Save as v, Shapes as y } from "lucide-react";
10
- import { useParams as b, useSearchParams as x } from "react-router-dom";
11
- import { jsx as S, jsxs as C } from "react/jsx-runtime";
12
- import { GlassCard as w } from "@burdenoff/fe-libs/ui";
7
+ import { parseCsv as l, titleCase as u } from "../../utils/formatters.js";
8
+ import { SelectField as d, TextAreaField as f, TextField as p } from "../../components/Field.js";
9
+ import { ImageUploadField as m } from "../../components/ImageUploadField.js";
10
+ import { useEffect as h, useMemo as g, useState as _ } from "react";
11
+ import { ArrowLeft as v, Loader2 as y, Save as b, Shapes as x } from "lucide-react";
12
+ import { useParams as S, useSearchParams as C } from "react-router-dom";
13
+ import { jsx as w, jsxs as T } from "react/jsx-runtime";
14
+ import { GlassCard as E } from "@burdenoff/fe-libs/ui";
13
15
  //#region src/coolandlovely/pages/collections/CollectionFormPage.tsx
14
- var T = [
16
+ var D = [
15
17
  "SPRING",
16
18
  "SUMMER",
17
19
  "AUTUMN",
18
20
  "WINTER",
19
21
  "RESORT",
20
22
  "ALL_SEASON"
21
- ], E = {
23
+ ], O = {
22
24
  brandId: "",
23
25
  name: "",
24
26
  description: "",
25
27
  season: "ALL_SEASON",
26
28
  tags: "",
27
29
  publishAt: "",
28
- endAt: ""
30
+ endAt: "",
31
+ coverFileId: ""
29
32
  };
30
- function D(e) {
33
+ function k(e) {
31
34
  if (!e) return;
32
35
  let t = new Date(e);
33
36
  return Number.isNaN(t.getTime()) ? void 0 : t.toISOString();
34
37
  }
35
- function O(e) {
38
+ function A(e) {
36
39
  if (!e) return "";
37
40
  let t = new Date(e);
38
41
  return Number.isNaN(t.getTime()) ? "" : t.toISOString().slice(0, 10);
39
42
  }
40
- function k() {
41
- let { collectionId: k } = b(), [A] = x(), j = !!k, { navigateTo: M } = e(), N = a(j ? k : void 0), P = i({ pagination: {
43
+ function j() {
44
+ let { collectionId: j } = S(), [M] = C(), N = !!j, { navigateTo: P } = e(), F = o(N ? j : void 0), I = a({ pagination: {
42
45
  limit: 100,
43
46
  offset: 0
44
- } }), F = o(), I = s(), [L, R] = h(() => ({
45
- ...E,
46
- brandId: A.get("brandId") ?? ""
47
- })), [z, B] = h({});
48
- p(() => {
49
- if (j && N.data) {
50
- let e = N.data;
51
- R({
47
+ } }), L = s(), R = c(), [z, B] = _(() => ({
48
+ ...O,
49
+ brandId: M.get("brandId") ?? ""
50
+ })), [V, H] = _({});
51
+ h(() => {
52
+ if (N && F.data) {
53
+ let e = F.data;
54
+ B({
52
55
  brandId: e.brandId,
53
56
  name: e.name,
54
57
  description: e.description ?? "",
55
58
  season: e.season,
56
59
  tags: e.tags.join(", "),
57
- publishAt: O(e.publishAt),
58
- endAt: O(e.endAt)
60
+ publishAt: A(e.publishAt),
61
+ endAt: A(e.endAt),
62
+ coverFileId: e.cover?.id ?? ""
59
63
  });
60
64
  }
61
- }, [j, N.data]);
62
- let V = (e, t) => R((n) => ({
65
+ }, [N, F.data]);
66
+ let U = (e, t) => B((n) => ({
63
67
  ...n,
64
68
  [e]: t
65
- })), H = j ? I : F, U = m(() => (P.data?.items ?? []).map((e) => ({
69
+ })), W = N ? R : L, G = g(() => (I.data?.items ?? []).map((e) => ({
66
70
  value: e.id,
67
71
  label: e.name
68
- })), [P.data]), W = T.map((e) => ({
72
+ })), [I.data]), K = D.map((e) => ({
69
73
  value: e,
70
- label: l(e)
71
- })), G = () => {
74
+ label: u(e)
75
+ })), q = () => {
72
76
  let e = {};
73
- return L.name.trim() || (e.name = "Name is required"), !j && !L.brandId && (e.brandId = "Brand is required"), B(e), Object.keys(e).length === 0;
77
+ return z.name.trim() || (e.name = "Name is required"), !N && !z.brandId && (e.brandId = "Brand is required"), H(e), Object.keys(e).length === 0;
74
78
  };
75
- return j && N.isLoading ? /* @__PURE__ */ S(t, {
79
+ return N && F.isLoading ? /* @__PURE__ */ w(t, {
76
80
  title: "Edit collection",
77
- children: /* @__PURE__ */ S(r, { label: "Loading collection…" })
78
- }) : j && (N.isError || !N.data) ? /* @__PURE__ */ S(t, {
81
+ children: /* @__PURE__ */ w(r, { label: "Loading collection…" })
82
+ }) : N && (F.isError || !F.data) ? /* @__PURE__ */ w(t, {
79
83
  title: "Edit collection",
80
- children: /* @__PURE__ */ S(n, {
84
+ children: /* @__PURE__ */ w(n, {
81
85
  title: "Collection not found",
82
- onRetry: () => void N.refetch()
86
+ onRetry: () => void F.refetch()
83
87
  })
84
- }) : /* @__PURE__ */ S(t, {
85
- title: j ? "Edit collection" : "New collection",
86
- description: j ? "Update this seasonal drop" : "Group pieces into a seasonal drop",
87
- actions: /* @__PURE__ */ C("button", {
88
+ }) : /* @__PURE__ */ w(t, {
89
+ title: N ? "Edit collection" : "New collection",
90
+ description: N ? "Update this seasonal drop" : "Group pieces into a seasonal drop",
91
+ actions: /* @__PURE__ */ T("button", {
88
92
  type: "button",
89
- onClick: () => M(j && k ? `/collections/${k}` : "/collections"),
93
+ onClick: () => P(N && j ? `/collections/${j}` : "/collections"),
90
94
  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",
91
- children: [/* @__PURE__ */ S(g, { className: "h-4 w-4" }), " Cancel"]
95
+ children: [/* @__PURE__ */ w(v, { className: "h-4 w-4" }), " Cancel"]
92
96
  }),
93
- children: /* @__PURE__ */ S("form", {
97
+ children: /* @__PURE__ */ w("form", {
94
98
  onSubmit: (e) => {
95
- e.preventDefault(), G() && (j && k ? I.mutate({
96
- id: k,
99
+ e.preventDefault(), q() && (N && j ? R.mutate({
100
+ id: j,
97
101
  input: {
98
- name: L.name.trim(),
99
- description: L.description.trim() || void 0,
100
- season: L.season,
101
- tags: c(L.tags),
102
- publishAt: D(L.publishAt),
103
- endAt: D(L.endAt)
102
+ name: z.name.trim(),
103
+ description: z.description.trim() || void 0,
104
+ season: z.season,
105
+ tags: l(z.tags),
106
+ publishAt: k(z.publishAt),
107
+ endAt: k(z.endAt),
108
+ coverFileId: z.coverFileId || void 0
104
109
  }
105
- }, { onSuccess: () => M(`/collections/${k}`) }) : F.mutate({
106
- brandId: L.brandId,
107
- name: L.name.trim(),
108
- description: L.description.trim() || void 0,
109
- season: L.season,
110
- tags: c(L.tags),
111
- publishAt: D(L.publishAt),
112
- endAt: D(L.endAt)
113
- }, { onSuccess: (e) => M(`/collections/${e.id}`) }));
110
+ }, { onSuccess: () => P(`/collections/${j}`) }) : L.mutate({
111
+ brandId: z.brandId,
112
+ name: z.name.trim(),
113
+ description: z.description.trim() || void 0,
114
+ season: z.season,
115
+ tags: l(z.tags),
116
+ publishAt: k(z.publishAt),
117
+ endAt: k(z.endAt),
118
+ coverFileId: z.coverFileId || void 0
119
+ }, { onSuccess: (e) => P(`/collections/${e.id}`) }));
114
120
  },
115
121
  className: "mx-auto max-w-2xl",
116
- children: /* @__PURE__ */ C(w, {
122
+ children: /* @__PURE__ */ T(E, {
117
123
  treatment: "plain",
118
124
  className: "p-6",
119
125
  children: [
120
- /* @__PURE__ */ C("div", {
126
+ /* @__PURE__ */ T("div", {
121
127
  className: "mb-6 flex items-center gap-3",
122
- children: [/* @__PURE__ */ S("div", {
128
+ children: [/* @__PURE__ */ w("div", {
123
129
  className: "flex h-11 w-11 items-center justify-center rounded-lg bg-accent-blue-subtle text-accent-blue",
124
- children: /* @__PURE__ */ S(y, { className: "h-5 w-5" })
125
- }), /* @__PURE__ */ C("div", { children: [/* @__PURE__ */ S("h2", {
130
+ children: /* @__PURE__ */ w(x, { className: "h-5 w-5" })
131
+ }), /* @__PURE__ */ T("div", { children: [/* @__PURE__ */ w("h2", {
126
132
  className: "text-base font-semibold text-text-primary",
127
133
  children: "Collection details"
128
- }), /* @__PURE__ */ S("p", {
134
+ }), /* @__PURE__ */ w("p", {
129
135
  className: "text-xs text-text-secondary",
130
136
  children: "A collection belongs to one brand."
131
137
  })] })]
132
138
  }),
133
- !j && /* @__PURE__ */ S(u, {
139
+ !N && /* @__PURE__ */ w(d, {
134
140
  id: "brandId",
135
141
  label: "Brand",
136
- value: L.brandId,
137
- onChange: (e) => V("brandId", e),
138
- options: U,
142
+ value: z.brandId,
143
+ onChange: (e) => U("brandId", e),
144
+ options: G,
139
145
  required: !0,
140
- placeholder: P.isLoading ? "Loading brands…" : "Select a brand",
141
- error: z.brandId,
146
+ placeholder: I.isLoading ? "Loading brands…" : "Select a brand",
147
+ error: V.brandId,
142
148
  className: "mb-4"
143
149
  }),
144
- /* @__PURE__ */ C("div", {
150
+ /* @__PURE__ */ T("div", {
145
151
  className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
146
- children: [/* @__PURE__ */ S(f, {
152
+ children: [/* @__PURE__ */ w(p, {
147
153
  id: "name",
148
154
  label: "Name",
149
- value: L.name,
150
- onChange: (e) => V("name", e),
155
+ value: z.name,
156
+ onChange: (e) => U("name", e),
151
157
  placeholder: "Spring Awakening",
152
158
  required: !0,
153
- error: z.name
154
- }), /* @__PURE__ */ S(u, {
159
+ error: V.name
160
+ }), /* @__PURE__ */ w(d, {
155
161
  id: "season",
156
162
  label: "Season",
157
- value: L.season,
158
- onChange: (e) => V("season", e),
159
- options: W
163
+ value: z.season,
164
+ onChange: (e) => U("season", e),
165
+ options: K
160
166
  })]
161
167
  }),
162
- /* @__PURE__ */ S(d, {
168
+ /* @__PURE__ */ w(f, {
163
169
  id: "description",
164
170
  label: "Description",
165
- value: L.description,
166
- onChange: (e) => V("description", e),
171
+ value: z.description,
172
+ onChange: (e) => U("description", e),
167
173
  placeholder: "Describe the drop…",
168
174
  rows: 3,
169
175
  className: "mt-4"
170
176
  }),
171
- /* @__PURE__ */ C("div", {
177
+ /* @__PURE__ */ T("div", {
172
178
  className: "mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2",
173
- children: [/* @__PURE__ */ S(f, {
179
+ children: [/* @__PURE__ */ w(p, {
174
180
  id: "publishAt",
175
181
  label: "Publish date",
176
182
  type: "date",
177
- value: L.publishAt,
178
- onChange: (e) => V("publishAt", e)
179
- }), /* @__PURE__ */ S(f, {
183
+ value: z.publishAt,
184
+ onChange: (e) => U("publishAt", e)
185
+ }), /* @__PURE__ */ w(p, {
180
186
  id: "endAt",
181
187
  label: "End date",
182
188
  type: "date",
183
- value: L.endAt,
184
- onChange: (e) => V("endAt", e)
189
+ value: z.endAt,
190
+ onChange: (e) => U("endAt", e)
185
191
  })]
186
192
  }),
187
- /* @__PURE__ */ S(f, {
193
+ /* @__PURE__ */ w(p, {
188
194
  id: "tags",
189
195
  label: "Tags",
190
- value: L.tags,
191
- onChange: (e) => V("tags", e),
196
+ value: z.tags,
197
+ onChange: (e) => U("tags", e),
192
198
  placeholder: "pastel, floral",
193
199
  hint: "Comma-separated",
194
200
  className: "mt-4"
195
201
  }),
196
- H.isError && /* @__PURE__ */ S("p", {
202
+ /* @__PURE__ */ w(m, {
203
+ label: "Cover image",
204
+ aspect: "wide",
205
+ value: z.coverFileId,
206
+ onChange: (e) => U("coverFileId", e),
207
+ folder: i.collections,
208
+ hint: "Represents the drop on cards and the collection page",
209
+ className: "mt-4"
210
+ }),
211
+ W.isError && /* @__PURE__ */ w("p", {
197
212
  className: "mt-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
198
- children: H.error instanceof Error ? H.error.message : "Something went wrong"
213
+ children: W.error instanceof Error ? W.error.message : "Something went wrong"
199
214
  }),
200
- /* @__PURE__ */ S("div", {
215
+ /* @__PURE__ */ w("div", {
201
216
  className: "mt-6 flex justify-end gap-2",
202
- children: /* @__PURE__ */ C("button", {
217
+ children: /* @__PURE__ */ T("button", {
203
218
  type: "submit",
204
- disabled: H.isPending,
219
+ disabled: W.isPending,
205
220
  className: "inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50",
206
- children: [H.isPending ? /* @__PURE__ */ S(_, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ S(v, { className: "h-4 w-4" }), j ? "Save changes" : "Create collection"]
221
+ children: [W.isPending ? /* @__PURE__ */ w(y, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ w(b, { className: "h-4 w-4" }), N ? "Save changes" : "Create collection"]
207
222
  })
208
223
  })
209
224
  ]
@@ -212,6 +227,6 @@ function k() {
212
227
  });
213
228
  }
214
229
  //#endregion
215
- export { k as CollectionFormPage };
230
+ export { j as CollectionFormPage };
216
231
 
217
232
  //# sourceMappingURL=CollectionFormPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CollectionFormPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/collections/CollectionFormPage.tsx"],"sourcesContent":["/**\n * Collection Create / Edit Page\n * @module coolandlovely/pages/collections\n *\n * Drives `cooCreateCollection` (new) and `cooUpdateCollection` (edit).\n * Brand selection is required on create and locked on edit.\n */\n\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams, useSearchParams } from 'react-router-dom';\nimport { ArrowLeft, Loader2, Save, Shapes } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { TextField, TextAreaField, SelectField } from '../../components/Field';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useCollection, useCreateCollection, useUpdateCollection, useBrands } from '../../hooks';\nimport { parseCsv, titleCase } from '../../utils/formatters';\nimport type { CooSeason } from '../../types';\n\nconst SEASONS: CooSeason[] = ['SPRING', 'SUMMER', 'AUTUMN', 'WINTER', 'RESORT', 'ALL_SEASON'];\n\ninterface FormState {\n brandId: string;\n name: string;\n description: string;\n season: CooSeason;\n tags: string;\n publishAt: string;\n endAt: string;\n}\n\nconst EMPTY: FormState = {\n brandId: '',\n name: '',\n description: '',\n season: 'ALL_SEASON',\n tags: '',\n publishAt: '',\n endAt: '',\n};\n\n/** ISO timestamp <input type=date> values use yyyy-mm-dd; convert to ISO. */\nfunction toIso(date: string): string | undefined {\n if (!date) return undefined;\n const d = new Date(date);\n return Number.isNaN(d.getTime()) ? undefined : d.toISOString();\n}\n\nfunction toDateInput(iso: string | null): string {\n if (!iso) return '';\n const d = new Date(iso);\n return Number.isNaN(d.getTime()) ? '' : d.toISOString().slice(0, 10);\n}\n\nexport function CollectionFormPage() {\n const { collectionId } = useParams<{ collectionId: string }>();\n const [searchParams] = useSearchParams();\n const isEdit = Boolean(collectionId);\n const { navigateTo } = useCoolAndLovely();\n\n const existing = useCollection(isEdit ? collectionId : undefined);\n const brands = useBrands({ pagination: { limit: 100, offset: 0 } });\n const createCollection = useCreateCollection();\n const updateCollection = useUpdateCollection();\n\n const [form, setForm] = useState<FormState>(() => ({\n ...EMPTY,\n brandId: searchParams.get('brandId') ?? '',\n }));\n const [errors, setErrors] = useState<Partial<Record<keyof FormState, string>>>({});\n\n useEffect(() => {\n if (isEdit && existing.data) {\n const c = existing.data;\n setForm({\n brandId: c.brandId,\n name: c.name,\n description: c.description ?? '',\n season: c.season,\n tags: c.tags.join(', '),\n publishAt: toDateInput(c.publishAt),\n endAt: toDateInput(c.endAt),\n });\n }\n }, [isEdit, existing.data]);\n\n const setField = <K extends keyof FormState>(key: K, value: FormState[K]) =>\n setForm((f) => ({ ...f, [key]: value }));\n\n const mutation = isEdit ? updateCollection : createCollection;\n\n const brandOptions = useMemo(\n () => (brands.data?.items ?? []).map((b) => ({ value: b.id, label: b.name })),\n [brands.data]\n );\n const seasonOptions = SEASONS.map((s) => ({ value: s, label: titleCase(s) }));\n\n const validate = (): boolean => {\n const next: Partial<Record<keyof FormState, string>> = {};\n if (!form.name.trim()) next.name = 'Name is required';\n if (!isEdit && !form.brandId) next.brandId = 'Brand is required';\n setErrors(next);\n return Object.keys(next).length === 0;\n };\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (!validate()) return;\n\n if (isEdit && collectionId) {\n updateCollection.mutate(\n {\n id: collectionId,\n input: {\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n season: form.season,\n tags: parseCsv(form.tags),\n publishAt: toIso(form.publishAt),\n endAt: toIso(form.endAt),\n },\n },\n { onSuccess: () => navigateTo(`/collections/${collectionId}`) }\n );\n } else {\n createCollection.mutate(\n {\n brandId: form.brandId,\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n season: form.season,\n tags: parseCsv(form.tags),\n publishAt: toIso(form.publishAt),\n endAt: toIso(form.endAt),\n },\n { onSuccess: (c) => navigateTo(`/collections/${c.id}`) }\n );\n }\n };\n\n if (isEdit && existing.isLoading) {\n return (\n <PageLayout title=\"Edit collection\">\n <LoadingView label=\"Loading collection…\" />\n </PageLayout>\n );\n }\n\n if (isEdit && (existing.isError || !existing.data)) {\n return (\n <PageLayout title=\"Edit collection\">\n <ErrorView title=\"Collection not found\" onRetry={() => void existing.refetch()} />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={isEdit ? 'Edit collection' : 'New collection'}\n description={isEdit ? 'Update this seasonal drop' : 'Group pieces into a seasonal drop'}\n actions={\n <button\n type=\"button\"\n onClick={() =>\n navigateTo(isEdit && collectionId ? `/collections/${collectionId}` : '/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-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Cancel\n </button>\n }\n >\n <form onSubmit={handleSubmit} className=\"mx-auto max-w-2xl\">\n <GlassCard treatment=\"plain\" className=\"p-6\">\n <div className=\"mb-6 flex items-center gap-3\">\n <div className=\"flex h-11 w-11 items-center justify-center rounded-lg bg-accent-blue-subtle text-accent-blue\">\n <Shapes className=\"h-5 w-5\" />\n </div>\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">Collection details</h2>\n <p className=\"text-xs text-text-secondary\">A collection belongs to one brand.</p>\n </div>\n </div>\n\n {!isEdit && (\n <SelectField\n id=\"brandId\"\n label=\"Brand\"\n value={form.brandId}\n onChange={(v) => setField('brandId', v)}\n options={brandOptions}\n required\n placeholder={brands.isLoading ? 'Loading brands…' : 'Select a brand'}\n error={errors.brandId}\n className=\"mb-4\"\n />\n )}\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <TextField\n id=\"name\"\n label=\"Name\"\n value={form.name}\n onChange={(v) => setField('name', v)}\n placeholder=\"Spring Awakening\"\n required\n error={errors.name}\n />\n <SelectField\n id=\"season\"\n label=\"Season\"\n value={form.season}\n onChange={(v) => setField('season', v as CooSeason)}\n options={seasonOptions}\n />\n </div>\n\n <TextAreaField\n id=\"description\"\n label=\"Description\"\n value={form.description}\n onChange={(v) => setField('description', v)}\n placeholder=\"Describe the drop…\"\n rows={3}\n className=\"mt-4\"\n />\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <TextField\n id=\"publishAt\"\n label=\"Publish date\"\n type=\"date\"\n value={form.publishAt}\n onChange={(v) => setField('publishAt', v)}\n />\n <TextField\n id=\"endAt\"\n label=\"End date\"\n type=\"date\"\n value={form.endAt}\n onChange={(v) => setField('endAt', v)}\n />\n </div>\n\n <TextField\n id=\"tags\"\n label=\"Tags\"\n value={form.tags}\n onChange={(v) => setField('tags', v)}\n placeholder=\"pastel, floral\"\n hint=\"Comma-separated\"\n className=\"mt-4\"\n />\n\n {mutation.isError && (\n <p className=\"mt-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {mutation.error instanceof Error ? mutation.error.message : 'Something went wrong'}\n </p>\n )}\n\n <div className=\"mt-6 flex justify-end gap-2\">\n <button\n type=\"submit\"\n disabled={mutation.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {mutation.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n {isEdit ? 'Save changes' : 'Create collection'}\n </button>\n </div>\n </GlassCard>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"CollectionFormPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/collections/CollectionFormPage.tsx"],"sourcesContent":["/**\n * Collection Create / Edit Page\n * @module coolandlovely/pages/collections\n *\n * Drives `cooCreateCollection` (new) and `cooUpdateCollection` (edit).\n * Brand selection is required on create and locked on edit.\n */\n\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams, useSearchParams } from 'react-router-dom';\nimport { ArrowLeft, Loader2, Save, Shapes } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { TextField, TextAreaField, SelectField } from '../../components/Field';\nimport { ImageUploadField } from '../../components/ImageUploadField';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useCollection, useCreateCollection, useUpdateCollection, useBrands } from '../../hooks';\nimport { COO_FILE_FOLDERS } from '../../hooks/useFiles';\nimport { parseCsv, titleCase } from '../../utils/formatters';\nimport type { CooSeason } from '../../types';\n\nconst SEASONS: CooSeason[] = ['SPRING', 'SUMMER', 'AUTUMN', 'WINTER', 'RESORT', 'ALL_SEASON'];\n\ninterface FormState {\n brandId: string;\n name: string;\n description: string;\n season: CooSeason;\n tags: string;\n publishAt: string;\n endAt: string;\n coverFileId: string;\n}\n\nconst EMPTY: FormState = {\n brandId: '',\n name: '',\n description: '',\n season: 'ALL_SEASON',\n tags: '',\n publishAt: '',\n endAt: '',\n coverFileId: '',\n};\n\n/** ISO timestamp <input type=date> values use yyyy-mm-dd; convert to ISO. */\nfunction toIso(date: string): string | undefined {\n if (!date) return undefined;\n const d = new Date(date);\n return Number.isNaN(d.getTime()) ? undefined : d.toISOString();\n}\n\nfunction toDateInput(iso: string | null): string {\n if (!iso) return '';\n const d = new Date(iso);\n return Number.isNaN(d.getTime()) ? '' : d.toISOString().slice(0, 10);\n}\n\nexport function CollectionFormPage() {\n const { collectionId } = useParams<{ collectionId: string }>();\n const [searchParams] = useSearchParams();\n const isEdit = Boolean(collectionId);\n const { navigateTo } = useCoolAndLovely();\n\n const existing = useCollection(isEdit ? collectionId : undefined);\n const brands = useBrands({ pagination: { limit: 100, offset: 0 } });\n const createCollection = useCreateCollection();\n const updateCollection = useUpdateCollection();\n\n const [form, setForm] = useState<FormState>(() => ({\n ...EMPTY,\n brandId: searchParams.get('brandId') ?? '',\n }));\n const [errors, setErrors] = useState<Partial<Record<keyof FormState, string>>>({});\n\n useEffect(() => {\n if (isEdit && existing.data) {\n const c = existing.data;\n setForm({\n brandId: c.brandId,\n name: c.name,\n description: c.description ?? '',\n season: c.season,\n tags: c.tags.join(', '),\n publishAt: toDateInput(c.publishAt),\n endAt: toDateInput(c.endAt),\n coverFileId: c.cover?.id ?? '',\n });\n }\n }, [isEdit, existing.data]);\n\n const setField = <K extends keyof FormState>(key: K, value: FormState[K]) =>\n setForm((f) => ({ ...f, [key]: value }));\n\n const mutation = isEdit ? updateCollection : createCollection;\n\n const brandOptions = useMemo(\n () => (brands.data?.items ?? []).map((b) => ({ value: b.id, label: b.name })),\n [brands.data]\n );\n const seasonOptions = SEASONS.map((s) => ({ value: s, label: titleCase(s) }));\n\n const validate = (): boolean => {\n const next: Partial<Record<keyof FormState, string>> = {};\n if (!form.name.trim()) next.name = 'Name is required';\n if (!isEdit && !form.brandId) next.brandId = 'Brand is required';\n setErrors(next);\n return Object.keys(next).length === 0;\n };\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (!validate()) return;\n\n if (isEdit && collectionId) {\n updateCollection.mutate(\n {\n id: collectionId,\n input: {\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n season: form.season,\n tags: parseCsv(form.tags),\n publishAt: toIso(form.publishAt),\n endAt: toIso(form.endAt),\n coverFileId: form.coverFileId || undefined,\n },\n },\n { onSuccess: () => navigateTo(`/collections/${collectionId}`) }\n );\n } else {\n createCollection.mutate(\n {\n brandId: form.brandId,\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n season: form.season,\n tags: parseCsv(form.tags),\n publishAt: toIso(form.publishAt),\n endAt: toIso(form.endAt),\n coverFileId: form.coverFileId || undefined,\n },\n { onSuccess: (c) => navigateTo(`/collections/${c.id}`) }\n );\n }\n };\n\n if (isEdit && existing.isLoading) {\n return (\n <PageLayout title=\"Edit collection\">\n <LoadingView label=\"Loading collection…\" />\n </PageLayout>\n );\n }\n\n if (isEdit && (existing.isError || !existing.data)) {\n return (\n <PageLayout title=\"Edit collection\">\n <ErrorView title=\"Collection not found\" onRetry={() => void existing.refetch()} />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={isEdit ? 'Edit collection' : 'New collection'}\n description={isEdit ? 'Update this seasonal drop' : 'Group pieces into a seasonal drop'}\n actions={\n <button\n type=\"button\"\n onClick={() =>\n navigateTo(isEdit && collectionId ? `/collections/${collectionId}` : '/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-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Cancel\n </button>\n }\n >\n <form onSubmit={handleSubmit} className=\"mx-auto max-w-2xl\">\n <GlassCard treatment=\"plain\" className=\"p-6\">\n <div className=\"mb-6 flex items-center gap-3\">\n <div className=\"flex h-11 w-11 items-center justify-center rounded-lg bg-accent-blue-subtle text-accent-blue\">\n <Shapes className=\"h-5 w-5\" />\n </div>\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">Collection details</h2>\n <p className=\"text-xs text-text-secondary\">A collection belongs to one brand.</p>\n </div>\n </div>\n\n {!isEdit && (\n <SelectField\n id=\"brandId\"\n label=\"Brand\"\n value={form.brandId}\n onChange={(v) => setField('brandId', v)}\n options={brandOptions}\n required\n placeholder={brands.isLoading ? 'Loading brands…' : 'Select a brand'}\n error={errors.brandId}\n className=\"mb-4\"\n />\n )}\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <TextField\n id=\"name\"\n label=\"Name\"\n value={form.name}\n onChange={(v) => setField('name', v)}\n placeholder=\"Spring Awakening\"\n required\n error={errors.name}\n />\n <SelectField\n id=\"season\"\n label=\"Season\"\n value={form.season}\n onChange={(v) => setField('season', v as CooSeason)}\n options={seasonOptions}\n />\n </div>\n\n <TextAreaField\n id=\"description\"\n label=\"Description\"\n value={form.description}\n onChange={(v) => setField('description', v)}\n placeholder=\"Describe the drop…\"\n rows={3}\n className=\"mt-4\"\n />\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <TextField\n id=\"publishAt\"\n label=\"Publish date\"\n type=\"date\"\n value={form.publishAt}\n onChange={(v) => setField('publishAt', v)}\n />\n <TextField\n id=\"endAt\"\n label=\"End date\"\n type=\"date\"\n value={form.endAt}\n onChange={(v) => setField('endAt', v)}\n />\n </div>\n\n <TextField\n id=\"tags\"\n label=\"Tags\"\n value={form.tags}\n onChange={(v) => setField('tags', v)}\n placeholder=\"pastel, floral\"\n hint=\"Comma-separated\"\n className=\"mt-4\"\n />\n\n <ImageUploadField\n label=\"Cover image\"\n aspect=\"wide\"\n value={form.coverFileId}\n onChange={(id) => setField('coverFileId', id)}\n folder={COO_FILE_FOLDERS.collections}\n hint=\"Represents the drop on cards and the collection page\"\n className=\"mt-4\"\n />\n\n {mutation.isError && (\n <p className=\"mt-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {mutation.error instanceof Error ? mutation.error.message : 'Something went wrong'}\n </p>\n )}\n\n <div className=\"mt-6 flex justify-end gap-2\">\n <button\n type=\"submit\"\n disabled={mutation.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {mutation.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n {isEdit ? 'Save changes' : 'Create collection'}\n </button>\n </div>\n </GlassCard>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -78,6 +78,8 @@ function v() {
78
78
  children: [/* @__PURE__ */ h(o, {
79
79
  icon: /* @__PURE__ */ h(m, { className: "h-9 w-9" }),
80
80
  seed: e.id,
81
+ fileId: e.cover?.id,
82
+ alt: e.name,
81
83
  className: "h-28 w-full"
82
84
  }), /* @__PURE__ */ g("div", {
83
85
  className: "p-4",
@@ -1 +1 @@
1
- {"version":3,"file":"CollectionsListPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/collections/CollectionsListPage.tsx"],"sourcesContent":["/**\n * Collections List Page\n * @module coolandlovely/pages/collections\n *\n * Lists seasonal collections (`cooListCollections`) with status filter.\n */\n\nimport { useState } from 'react';\nimport { Plus, Shapes } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { TagList } from '../../components/TagList';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useCollections } from '../../hooks';\nimport { titleCase, formatDate } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport type { CooCollectionStatus } from '../../types';\n\nconst STATUS_OPTIONS: Array<{ value: CooCollectionStatus | 'ALL'; label: string }> = [\n { value: 'ALL', label: 'All' },\n { value: 'LIVE', label: 'Live' },\n { value: 'SCHEDULED', label: 'Scheduled' },\n { value: 'DRAFT', label: 'Draft' },\n { value: 'ENDED', label: 'Ended' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\nexport function CollectionsListPage() {\n const { navigateTo } = useCoolAndLovely();\n const [status, setStatus] = useState<CooCollectionStatus | 'ALL'>('ALL');\n\n const query = useCollections({ status: status === 'ALL' ? undefined : status });\n const collections = query.data ?? [];\n\n return (\n <PageLayout\n title=\"Collections\"\n description=\"Seasonal drops and curated edits grouped by brand\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/collections/new')}\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\"\n >\n <Plus className=\"h-4 w-4\" /> New collection\n </button>\n }\n >\n <div className=\"mb-6 flex flex-wrap items-center gap-2\">\n {STATUS_OPTIONS.map((opt) => (\n <button\n key={opt.value}\n type=\"button\"\n onClick={() => setStatus(opt.value)}\n className={cn(\n 'rounded-full px-3 py-1.5 text-sm font-medium transition-colors',\n status === opt.value\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n {opt.label}\n </button>\n ))}\n </div>\n\n {query.isLoading ? (\n <LoadingView label=\"Loading collections…\" />\n ) : query.isError ? (\n <ErrorView onRetry={() => void query.refetch()} />\n ) : collections.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Shapes className=\"h-10 w-10\" />}\n title=\"No collections yet\"\n description=\"Group pieces into a seasonal drop to start a collection.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/collections/new')}\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\"\n >\n <Plus className=\"h-4 w-4\" /> New collection\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {collections.map((c) => (\n <button\n key={c.id}\n type=\"button\"\n onClick={() => navigateTo(`/collections/${c.id}`)}\n className=\"overflow-hidden rounded-lg border border-border-seam bg-bg-surface text-left shadow-sm transition-all hover:border-accent-foreground/20 hover:shadow-md\"\n >\n <Thumbnail\n icon={<Shapes className=\"h-9 w-9\" />}\n seed={c.id}\n className=\"h-28 w-full\"\n />\n <div className=\"p-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <h3 className=\"truncate text-base font-semibold text-text-primary\">{c.name}</h3>\n {c.brand?.name && (\n <p className=\"truncate text-xs text-text-secondary\">{c.brand.name}</p>\n )}\n </div>\n <StatusBadge status={c.status} />\n </div>\n <div className=\"mt-2 flex items-center gap-2 text-xs text-text-secondary\">\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5\">\n {titleCase(c.season)}\n </span>\n {c.publishAt && (\n <span>\n Publishes {formatDate(c.publishAt, { hour: undefined, minute: undefined })}\n </span>\n )}\n </div>\n {c.tags.length > 0 && <TagList tags={c.tags} max={4} className=\"mt-3\" />}\n </div>\n </button>\n ))}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;AAqBA,IAAM,IAA+E;CACnF;EAAE,OAAO;EAAO,OAAO;EAAO;CAC9B;EAAE,OAAO;EAAQ,OAAO;EAAQ;CAChC;EAAE,OAAO;EAAa,OAAO;EAAa;CAC1C;EAAE,OAAO;EAAS,OAAO;EAAS;CAClC;EAAE,OAAO;EAAS,OAAO;EAAS;CAClC;EAAE,OAAO;EAAY,OAAO;EAAY;CACzC;AAED,SAAgB,IAAsB;CACpC,IAAM,EAAE,kBAAe,GAAkB,EACnC,CAAC,GAAQ,KAAa,EAAsC,MAAM,EAElE,IAAQ,EAAe,EAAE,QAAQ,MAAW,QAAQ,KAAA,IAAY,GAAQ,CAAC,EACzE,IAAc,EAAM,QAAQ,EAAE;AAEpC,QACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SACE,kBAAC,UAAD;GACE,MAAK;GACL,eAAe,EAAW,mBAAmB;GAC7C,WAAU;aAHZ,CAKE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,kBACrB;;YAVb,CAaE,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAe,KAAK,MACnB,kBAAC,UAAD;IAEE,MAAK;IACL,eAAe,EAAU,EAAI,MAAM;IACnC,WAAW,EACT,kEACA,MAAW,EAAI,QACX,kDACA,2DACL;cAEA,EAAI;IACE,EAXF,EAAI,MAWF,CACT;GACE,CAAA,EAEL,EAAM,YACL,kBAAC,GAAD,EAAa,OAAM,wBAAyB,CAAA,GAC1C,EAAM,UACR,kBAAC,GAAD,EAAW,eAAe,KAAK,EAAM,SAAS,EAAI,CAAA,GAChD,EAAY,WAAW,IACzB,kBAAC,GAAD;GACE,cAAa;GACb,MAAM,kBAAC,GAAD,EAAQ,WAAU,aAAc,CAAA;GACtC,OAAM;GACN,aAAY;GACZ,QACE,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,EAAW,mBAAmB;IAC7C,WAAU;cAHZ,CAKE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,kBACrB;;GAEX,CAAA,GAEF,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAY,KAAK,MAChB,kBAAC,UAAD;IAEE,MAAK;IACL,eAAe,EAAW,gBAAgB,EAAE,KAAK;IACjD,WAAU;cAJZ,CAME,kBAAC,GAAD;KACE,MAAM,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;KACpC,MAAM,EAAE;KACR,WAAU;KACV,CAAA,EACF,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,MAAD;SAAI,WAAU;mBAAsD,EAAE;SAAU,CAAA,EAC/E,EAAE,OAAO,QACR,kBAAC,KAAD;SAAG,WAAU;mBAAwC,EAAE,MAAM;SAAS,CAAA,CAEpE;WACN,kBAAC,GAAD,EAAa,QAAQ,EAAE,QAAU,CAAA,CAC7B;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAU,EAAE,OAAO;QACf,CAAA,EACN,EAAE,aACD,kBAAC,QAAD,EAAA,UAAA,CAAM,cACO,EAAW,EAAE,WAAW;QAAE,MAAM,KAAA;QAAW,QAAQ,KAAA;QAAW,CAAC,CACrE,EAAA,CAAA,CAEL;;MACL,EAAE,KAAK,SAAS,KAAK,kBAAC,GAAD;OAAS,MAAM,EAAE;OAAM,KAAK;OAAG,WAAU;OAAS,CAAA;MACpE;OACC;MAhCF,EAAE,GAgCA,CACT;GACE,CAAA,CAEG"}
1
+ {"version":3,"file":"CollectionsListPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/collections/CollectionsListPage.tsx"],"sourcesContent":["/**\n * Collections List Page\n * @module coolandlovely/pages/collections\n *\n * Lists seasonal collections (`cooListCollections`) with status filter.\n */\n\nimport { useState } from 'react';\nimport { Plus, Shapes } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { TagList } from '../../components/TagList';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useCollections } from '../../hooks';\nimport { titleCase, formatDate } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport type { CooCollectionStatus } from '../../types';\n\nconst STATUS_OPTIONS: Array<{ value: CooCollectionStatus | 'ALL'; label: string }> = [\n { value: 'ALL', label: 'All' },\n { value: 'LIVE', label: 'Live' },\n { value: 'SCHEDULED', label: 'Scheduled' },\n { value: 'DRAFT', label: 'Draft' },\n { value: 'ENDED', label: 'Ended' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\nexport function CollectionsListPage() {\n const { navigateTo } = useCoolAndLovely();\n const [status, setStatus] = useState<CooCollectionStatus | 'ALL'>('ALL');\n\n const query = useCollections({ status: status === 'ALL' ? undefined : status });\n const collections = query.data ?? [];\n\n return (\n <PageLayout\n title=\"Collections\"\n description=\"Seasonal drops and curated edits grouped by brand\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/collections/new')}\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\"\n >\n <Plus className=\"h-4 w-4\" /> New collection\n </button>\n }\n >\n <div className=\"mb-6 flex flex-wrap items-center gap-2\">\n {STATUS_OPTIONS.map((opt) => (\n <button\n key={opt.value}\n type=\"button\"\n onClick={() => setStatus(opt.value)}\n className={cn(\n 'rounded-full px-3 py-1.5 text-sm font-medium transition-colors',\n status === opt.value\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n {opt.label}\n </button>\n ))}\n </div>\n\n {query.isLoading ? (\n <LoadingView label=\"Loading collections…\" />\n ) : query.isError ? (\n <ErrorView onRetry={() => void query.refetch()} />\n ) : collections.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Shapes className=\"h-10 w-10\" />}\n title=\"No collections yet\"\n description=\"Group pieces into a seasonal drop to start a collection.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/collections/new')}\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\"\n >\n <Plus className=\"h-4 w-4\" /> New collection\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {collections.map((c) => (\n <button\n key={c.id}\n type=\"button\"\n onClick={() => navigateTo(`/collections/${c.id}`)}\n className=\"overflow-hidden rounded-lg border border-border-seam bg-bg-surface text-left shadow-sm transition-all hover:border-accent-foreground/20 hover:shadow-md\"\n >\n <Thumbnail\n icon={<Shapes className=\"h-9 w-9\" />}\n seed={c.id}\n fileId={c.cover?.id}\n alt={c.name}\n className=\"h-28 w-full\"\n />\n <div className=\"p-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <h3 className=\"truncate text-base font-semibold text-text-primary\">{c.name}</h3>\n {c.brand?.name && (\n <p className=\"truncate text-xs text-text-secondary\">{c.brand.name}</p>\n )}\n </div>\n <StatusBadge status={c.status} />\n </div>\n <div className=\"mt-2 flex items-center gap-2 text-xs text-text-secondary\">\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5\">\n {titleCase(c.season)}\n </span>\n {c.publishAt && (\n <span>\n Publishes {formatDate(c.publishAt, { hour: undefined, minute: undefined })}\n </span>\n )}\n </div>\n {c.tags.length > 0 && <TagList tags={c.tags} max={4} className=\"mt-3\" />}\n </div>\n </button>\n ))}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;AAqBA,IAAM,IAA+E;CACnF;EAAE,OAAO;EAAO,OAAO;EAAO;CAC9B;EAAE,OAAO;EAAQ,OAAO;EAAQ;CAChC;EAAE,OAAO;EAAa,OAAO;EAAa;CAC1C;EAAE,OAAO;EAAS,OAAO;EAAS;CAClC;EAAE,OAAO;EAAS,OAAO;EAAS;CAClC;EAAE,OAAO;EAAY,OAAO;EAAY;CACzC;AAED,SAAgB,IAAsB;CACpC,IAAM,EAAE,kBAAe,GAAkB,EACnC,CAAC,GAAQ,KAAa,EAAsC,MAAM,EAElE,IAAQ,EAAe,EAAE,QAAQ,MAAW,QAAQ,KAAA,IAAY,GAAQ,CAAC,EACzE,IAAc,EAAM,QAAQ,EAAE;AAEpC,QACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SACE,kBAAC,UAAD;GACE,MAAK;GACL,eAAe,EAAW,mBAAmB;GAC7C,WAAU;aAHZ,CAKE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,kBACrB;;YAVb,CAaE,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAe,KAAK,MACnB,kBAAC,UAAD;IAEE,MAAK;IACL,eAAe,EAAU,EAAI,MAAM;IACnC,WAAW,EACT,kEACA,MAAW,EAAI,QACX,kDACA,2DACL;cAEA,EAAI;IACE,EAXF,EAAI,MAWF,CACT;GACE,CAAA,EAEL,EAAM,YACL,kBAAC,GAAD,EAAa,OAAM,wBAAyB,CAAA,GAC1C,EAAM,UACR,kBAAC,GAAD,EAAW,eAAe,KAAK,EAAM,SAAS,EAAI,CAAA,GAChD,EAAY,WAAW,IACzB,kBAAC,GAAD;GACE,cAAa;GACb,MAAM,kBAAC,GAAD,EAAQ,WAAU,aAAc,CAAA;GACtC,OAAM;GACN,aAAY;GACZ,QACE,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,EAAW,mBAAmB;IAC7C,WAAU;cAHZ,CAKE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,kBACrB;;GAEX,CAAA,GAEF,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAY,KAAK,MAChB,kBAAC,UAAD;IAEE,MAAK;IACL,eAAe,EAAW,gBAAgB,EAAE,KAAK;IACjD,WAAU;cAJZ,CAME,kBAAC,GAAD;KACE,MAAM,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;KACpC,MAAM,EAAE;KACR,QAAQ,EAAE,OAAO;KACjB,KAAK,EAAE;KACP,WAAU;KACV,CAAA,EACF,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,MAAD;SAAI,WAAU;mBAAsD,EAAE;SAAU,CAAA,EAC/E,EAAE,OAAO,QACR,kBAAC,KAAD;SAAG,WAAU;mBAAwC,EAAE,MAAM;SAAS,CAAA,CAEpE;WACN,kBAAC,GAAD,EAAa,QAAQ,EAAE,QAAU,CAAA,CAC7B;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAU,EAAE,OAAO;QACf,CAAA,EACN,EAAE,aACD,kBAAC,QAAD,EAAA,UAAA,CAAM,cACO,EAAW,EAAE,WAAW;QAAE,MAAM,KAAA;QAAW,QAAQ,KAAA;QAAW,CAAC,CACrE,EAAA,CAAA,CAEL;;MACL,EAAE,KAAK,SAAS,KAAK,kBAAC,GAAD;OAAS,MAAM,EAAE;OAAM,KAAK;OAAG,WAAU;OAAS,CAAA;MACpE;OACC;MAlCF,EAAE,GAkCA,CACT;GACE,CAAA,CAEG"}