@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.
- package/dist/coolandlovely/CoolAndLovelyRoutes.js +14 -6
- package/dist/coolandlovely/CoolAndLovelyRoutes.js.map +1 -1
- package/dist/coolandlovely/components/ActorName.js +29 -0
- package/dist/coolandlovely/components/ActorName.js.map +1 -0
- package/dist/coolandlovely/components/AddToLookbookButton.js +81 -0
- package/dist/coolandlovely/components/AddToLookbookButton.js.map +1 -0
- package/dist/coolandlovely/components/Comments.js +109 -0
- package/dist/coolandlovely/components/Comments.js.map +1 -0
- package/dist/coolandlovely/components/Field.js +22 -2
- package/dist/coolandlovely/components/Field.js.map +1 -1
- package/dist/coolandlovely/components/FileImage.js +17 -0
- package/dist/coolandlovely/components/FileImage.js.map +1 -0
- package/dist/coolandlovely/components/FollowButton.js +24 -0
- package/dist/coolandlovely/components/FollowButton.js.map +1 -0
- package/dist/coolandlovely/components/ImageUploadField.js +71 -0
- package/dist/coolandlovely/components/ImageUploadField.js.map +1 -0
- package/dist/coolandlovely/components/Thumbnail.js +20 -13
- package/dist/coolandlovely/components/Thumbnail.js.map +1 -1
- package/dist/coolandlovely/hooks/index.js +2 -0
- package/dist/coolandlovely/hooks/useComments.js +68 -0
- package/dist/coolandlovely/hooks/useComments.js.map +1 -0
- package/dist/coolandlovely/hooks/useCoolAndLovelyApi.js +74 -4
- package/dist/coolandlovely/hooks/useCoolAndLovelyApi.js.map +1 -1
- package/dist/coolandlovely/hooks/useFiles.js +94 -0
- package/dist/coolandlovely/hooks/useFiles.js.map +1 -0
- package/dist/coolandlovely/pages/HomePage.js +105 -87
- package/dist/coolandlovely/pages/HomePage.js.map +1 -1
- package/dist/coolandlovely/pages/brands/BrandDetailPage.js +144 -122
- package/dist/coolandlovely/pages/brands/BrandDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/brands/BrandFormPage.js +128 -90
- package/dist/coolandlovely/pages/brands/BrandFormPage.js.map +1 -1
- package/dist/coolandlovely/pages/brands/BrandsListPage.js +2 -0
- package/dist/coolandlovely/pages/brands/BrandsListPage.js.map +1 -1
- package/dist/coolandlovely/pages/collections/CollectionDetailPage.js +5 -1
- package/dist/coolandlovely/pages/collections/CollectionDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/collections/CollectionFormPage.js +117 -102
- package/dist/coolandlovely/pages/collections/CollectionFormPage.js.map +1 -1
- package/dist/coolandlovely/pages/collections/CollectionsListPage.js +2 -0
- package/dist/coolandlovely/pages/collections/CollectionsListPage.js.map +1 -1
- package/dist/coolandlovely/pages/discover/DiscoverPage.js +223 -0
- package/dist/coolandlovely/pages/discover/DiscoverPage.js.map +1 -0
- package/dist/coolandlovely/pages/items/StyleItemDetailPage.js +99 -95
- package/dist/coolandlovely/pages/items/StyleItemDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/items/StyleItemFormPage.js +144 -130
- package/dist/coolandlovely/pages/items/StyleItemFormPage.js.map +1 -1
- package/dist/coolandlovely/pages/items/StyleItemsListPage.js +2 -0
- package/dist/coolandlovely/pages/items/StyleItemsListPage.js.map +1 -1
- package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js +190 -99
- package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/lookbooks/LookbooksListPage.js +81 -68
- package/dist/coolandlovely/pages/lookbooks/LookbooksListPage.js.map +1 -1
- package/dist/coolandlovely/pages/looks/LookDetailPage.js +195 -155
- package/dist/coolandlovely/pages/looks/LookDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/looks/LookFormPage.js +127 -88
- package/dist/coolandlovely/pages/looks/LookFormPage.js.map +1 -1
- package/dist/coolandlovely/pages/looks/LooksListPage.js +2 -0
- package/dist/coolandlovely/pages/looks/LooksListPage.js.map +1 -1
- package/dist/coolandlovely/pages/profile/StyleProfilePage.js +169 -101
- package/dist/coolandlovely/pages/profile/StyleProfilePage.js.map +1 -1
- package/dist/coolandlovely/pages/trends/TrendsPage.js +249 -0
- package/dist/coolandlovely/pages/trends/TrendsPage.js.map +1 -0
- package/dist/coolandlovely/pages/wardrobe/WardrobeFormPage.js +88 -76
- package/dist/coolandlovely/pages/wardrobe/WardrobeFormPage.js.map +1 -1
- package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.js +2 -0
- package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.js.map +1 -1
- 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 {
|
|
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
|
|
7
|
-
import { SelectField as
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
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
|
|
16
|
+
var D = [
|
|
15
17
|
"SPRING",
|
|
16
18
|
"SUMMER",
|
|
17
19
|
"AUTUMN",
|
|
18
20
|
"WINTER",
|
|
19
21
|
"RESORT",
|
|
20
22
|
"ALL_SEASON"
|
|
21
|
-
],
|
|
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
|
|
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
|
|
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
|
|
41
|
-
let { collectionId:
|
|
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
|
-
} }),
|
|
45
|
-
...
|
|
46
|
-
brandId:
|
|
47
|
-
})), [
|
|
48
|
-
|
|
49
|
-
if (
|
|
50
|
-
let e =
|
|
51
|
-
|
|
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:
|
|
58
|
-
endAt:
|
|
60
|
+
publishAt: A(e.publishAt),
|
|
61
|
+
endAt: A(e.endAt),
|
|
62
|
+
coverFileId: e.cover?.id ?? ""
|
|
59
63
|
});
|
|
60
64
|
}
|
|
61
|
-
}, [
|
|
62
|
-
let
|
|
65
|
+
}, [N, F.data]);
|
|
66
|
+
let U = (e, t) => B((n) => ({
|
|
63
67
|
...n,
|
|
64
68
|
[e]: t
|
|
65
|
-
})),
|
|
69
|
+
})), W = N ? R : L, G = g(() => (I.data?.items ?? []).map((e) => ({
|
|
66
70
|
value: e.id,
|
|
67
71
|
label: e.name
|
|
68
|
-
})), [
|
|
72
|
+
})), [I.data]), K = D.map((e) => ({
|
|
69
73
|
value: e,
|
|
70
|
-
label:
|
|
71
|
-
})),
|
|
74
|
+
label: u(e)
|
|
75
|
+
})), q = () => {
|
|
72
76
|
let e = {};
|
|
73
|
-
return
|
|
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
|
|
79
|
+
return N && F.isLoading ? /* @__PURE__ */ w(t, {
|
|
76
80
|
title: "Edit collection",
|
|
77
|
-
children: /* @__PURE__ */
|
|
78
|
-
}) :
|
|
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__ */
|
|
84
|
+
children: /* @__PURE__ */ w(n, {
|
|
81
85
|
title: "Collection not found",
|
|
82
|
-
onRetry: () => void
|
|
86
|
+
onRetry: () => void F.refetch()
|
|
83
87
|
})
|
|
84
|
-
}) : /* @__PURE__ */
|
|
85
|
-
title:
|
|
86
|
-
description:
|
|
87
|
-
actions: /* @__PURE__ */
|
|
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: () =>
|
|
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__ */
|
|
95
|
+
children: [/* @__PURE__ */ w(v, { className: "h-4 w-4" }), " Cancel"]
|
|
92
96
|
}),
|
|
93
|
-
children: /* @__PURE__ */
|
|
97
|
+
children: /* @__PURE__ */ w("form", {
|
|
94
98
|
onSubmit: (e) => {
|
|
95
|
-
e.preventDefault(),
|
|
96
|
-
id:
|
|
99
|
+
e.preventDefault(), q() && (N && j ? R.mutate({
|
|
100
|
+
id: j,
|
|
97
101
|
input: {
|
|
98
|
-
name:
|
|
99
|
-
description:
|
|
100
|
-
season:
|
|
101
|
-
tags:
|
|
102
|
-
publishAt:
|
|
103
|
-
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: () =>
|
|
106
|
-
brandId:
|
|
107
|
-
name:
|
|
108
|
-
description:
|
|
109
|
-
season:
|
|
110
|
-
tags:
|
|
111
|
-
publishAt:
|
|
112
|
-
endAt:
|
|
113
|
-
|
|
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__ */
|
|
122
|
+
children: /* @__PURE__ */ T(E, {
|
|
117
123
|
treatment: "plain",
|
|
118
124
|
className: "p-6",
|
|
119
125
|
children: [
|
|
120
|
-
/* @__PURE__ */
|
|
126
|
+
/* @__PURE__ */ T("div", {
|
|
121
127
|
className: "mb-6 flex items-center gap-3",
|
|
122
|
-
children: [/* @__PURE__ */
|
|
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__ */
|
|
125
|
-
}), /* @__PURE__ */
|
|
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__ */
|
|
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
|
-
!
|
|
139
|
+
!N && /* @__PURE__ */ w(d, {
|
|
134
140
|
id: "brandId",
|
|
135
141
|
label: "Brand",
|
|
136
|
-
value:
|
|
137
|
-
onChange: (e) =>
|
|
138
|
-
options:
|
|
142
|
+
value: z.brandId,
|
|
143
|
+
onChange: (e) => U("brandId", e),
|
|
144
|
+
options: G,
|
|
139
145
|
required: !0,
|
|
140
|
-
placeholder:
|
|
141
|
-
error:
|
|
146
|
+
placeholder: I.isLoading ? "Loading brands…" : "Select a brand",
|
|
147
|
+
error: V.brandId,
|
|
142
148
|
className: "mb-4"
|
|
143
149
|
}),
|
|
144
|
-
/* @__PURE__ */
|
|
150
|
+
/* @__PURE__ */ T("div", {
|
|
145
151
|
className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
|
|
146
|
-
children: [/* @__PURE__ */
|
|
152
|
+
children: [/* @__PURE__ */ w(p, {
|
|
147
153
|
id: "name",
|
|
148
154
|
label: "Name",
|
|
149
|
-
value:
|
|
150
|
-
onChange: (e) =>
|
|
155
|
+
value: z.name,
|
|
156
|
+
onChange: (e) => U("name", e),
|
|
151
157
|
placeholder: "Spring Awakening",
|
|
152
158
|
required: !0,
|
|
153
|
-
error:
|
|
154
|
-
}), /* @__PURE__ */
|
|
159
|
+
error: V.name
|
|
160
|
+
}), /* @__PURE__ */ w(d, {
|
|
155
161
|
id: "season",
|
|
156
162
|
label: "Season",
|
|
157
|
-
value:
|
|
158
|
-
onChange: (e) =>
|
|
159
|
-
options:
|
|
163
|
+
value: z.season,
|
|
164
|
+
onChange: (e) => U("season", e),
|
|
165
|
+
options: K
|
|
160
166
|
})]
|
|
161
167
|
}),
|
|
162
|
-
/* @__PURE__ */
|
|
168
|
+
/* @__PURE__ */ w(f, {
|
|
163
169
|
id: "description",
|
|
164
170
|
label: "Description",
|
|
165
|
-
value:
|
|
166
|
-
onChange: (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__ */
|
|
177
|
+
/* @__PURE__ */ T("div", {
|
|
172
178
|
className: "mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2",
|
|
173
|
-
children: [/* @__PURE__ */
|
|
179
|
+
children: [/* @__PURE__ */ w(p, {
|
|
174
180
|
id: "publishAt",
|
|
175
181
|
label: "Publish date",
|
|
176
182
|
type: "date",
|
|
177
|
-
value:
|
|
178
|
-
onChange: (e) =>
|
|
179
|
-
}), /* @__PURE__ */
|
|
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:
|
|
184
|
-
onChange: (e) =>
|
|
189
|
+
value: z.endAt,
|
|
190
|
+
onChange: (e) => U("endAt", e)
|
|
185
191
|
})]
|
|
186
192
|
}),
|
|
187
|
-
/* @__PURE__ */
|
|
193
|
+
/* @__PURE__ */ w(p, {
|
|
188
194
|
id: "tags",
|
|
189
195
|
label: "Tags",
|
|
190
|
-
value:
|
|
191
|
-
onChange: (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
|
-
|
|
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:
|
|
213
|
+
children: W.error instanceof Error ? W.error.message : "Something went wrong"
|
|
199
214
|
}),
|
|
200
|
-
/* @__PURE__ */
|
|
215
|
+
/* @__PURE__ */ w("div", {
|
|
201
216
|
className: "mt-6 flex justify-end gap-2",
|
|
202
|
-
children: /* @__PURE__ */
|
|
217
|
+
children: /* @__PURE__ */ T("button", {
|
|
203
218
|
type: "submit",
|
|
204
|
-
disabled:
|
|
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: [
|
|
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 {
|
|
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":";;;;;;;;;;;;;AAoBA,IAAM,IAAuB;CAAC;CAAU;CAAU;CAAU;CAAU;CAAU;CAAa,EAYvF,IAAmB;CACvB,SAAS;CACT,MAAM;CACN,aAAa;CACb,QAAQ;CACR,MAAM;CACN,WAAW;CACX,OAAO;CACR;AAGD,SAAS,EAAM,GAAkC;AAC/C,KAAI,CAAC,EAAM;CACX,IAAM,IAAI,IAAI,KAAK,EAAK;AACxB,QAAO,OAAO,MAAM,EAAE,SAAS,CAAC,GAAG,KAAA,IAAY,EAAE,aAAa;;AAGhE,SAAS,EAAY,GAA4B;AAC/C,KAAI,CAAC,EAAK,QAAO;CACjB,IAAM,IAAI,IAAI,KAAK,EAAI;AACvB,QAAO,OAAO,MAAM,EAAE,SAAS,CAAC,GAAG,KAAK,EAAE,aAAa,CAAC,MAAM,GAAG,GAAG;;AAGtE,SAAgB,IAAqB;CACnC,IAAM,EAAE,oBAAiB,GAAqC,EACxD,CAAC,KAAgB,GAAiB,EAClC,IAAS,EAAQ,GACjB,EAAE,kBAAe,GAAkB,EAEnC,IAAW,EAAc,IAAS,IAAe,KAAA,EAAU,EAC3D,IAAS,EAAU,EAAE,YAAY;EAAE,OAAO;EAAK,QAAQ;EAAG,EAAE,CAAC,EAC7D,IAAmB,GAAqB,EACxC,IAAmB,GAAqB,EAExC,CAAC,GAAM,KAAW,SAA2B;EACjD,GAAG;EACH,SAAS,EAAa,IAAI,UAAU,IAAI;EACzC,EAAE,EACG,CAAC,GAAQ,KAAa,EAAmD,EAAE,CAAC;AAElF,SAAgB;AACd,MAAI,KAAU,EAAS,MAAM;GAC3B,IAAM,IAAI,EAAS;AACnB,KAAQ;IACN,SAAS,EAAE;IACX,MAAM,EAAE;IACR,aAAa,EAAE,eAAe;IAC9B,QAAQ,EAAE;IACV,MAAM,EAAE,KAAK,KAAK,KAAK;IACvB,WAAW,EAAY,EAAE,UAAU;IACnC,OAAO,EAAY,EAAE,MAAM;IAC5B,CAAC;;IAEH,CAAC,GAAQ,EAAS,KAAK,CAAC;CAE3B,IAAM,KAAuC,GAAQ,MACnD,GAAS,OAAO;EAAE,GAAG;GAAI,IAAM;EAAO,EAAE,EAEpC,IAAW,IAAS,IAAmB,GAEvC,IAAe,SACZ,EAAO,MAAM,SAAS,EAAE,EAAE,KAAK,OAAO;EAAE,OAAO,EAAE;EAAI,OAAO,EAAE;EAAM,EAAE,EAC7E,CAAC,EAAO,KAAK,CACd,EACK,IAAgB,EAAQ,KAAK,OAAO;EAAE,OAAO;EAAG,OAAO,EAAU,EAAE;EAAE,EAAE,EAEvE,UAA0B;EAC9B,IAAM,IAAiD,EAAE;AAIzD,SAHK,EAAK,KAAK,MAAM,KAAE,EAAK,OAAO,qBAC/B,CAAC,KAAU,CAAC,EAAK,YAAS,EAAK,UAAU,sBAC7C,EAAU,EAAK,EACR,OAAO,KAAK,EAAK,CAAC,WAAW;;AAsDtC,QAhBI,KAAU,EAAS,YAEnB,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD,EAAa,OAAM,uBAAwB,CAAA;EAChC,CAAA,GAIb,MAAW,EAAS,WAAW,CAAC,EAAS,QAEzC,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD;GAAW,OAAM;GAAuB,eAAe,KAAK,EAAS,SAAS;GAAI,CAAA;EACvE,CAAA,GAKf,kBAAC,GAAD;EACE,OAAO,IAAS,oBAAoB;EACpC,aAAa,IAAS,8BAA8B;EACpD,SACE,kBAAC,UAAD;GACE,MAAK;GACL,eACE,EAAW,KAAU,IAAe,gBAAgB,MAAiB,eAAe;GAEtF,WAAU;aALZ,CAOE,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA,EAAA,UAC1B;;YAGX,kBAAC,QAAD;GAAM,WAnEY,MAAuB;AAC3C,MAAE,gBAAgB,EACb,GAAU,KAEX,KAAU,IACZ,EAAiB,OACf;KACE,IAAI;KACJ,OAAO;MACL,MAAM,EAAK,KAAK,MAAM;MACtB,aAAa,EAAK,YAAY,MAAM,IAAI,KAAA;MACxC,QAAQ,EAAK;MACb,MAAM,EAAS,EAAK,KAAK;MACzB,WAAW,EAAM,EAAK,UAAU;MAChC,OAAO,EAAM,EAAK,MAAM;MACzB;KACF,EACD,EAAE,iBAAiB,EAAW,gBAAgB,IAAe,EAAE,CAChE,GAED,EAAiB,OACf;KACE,SAAS,EAAK;KACd,MAAM,EAAK,KAAK,MAAM;KACtB,aAAa,EAAK,YAAY,MAAM,IAAI,KAAA;KACxC,QAAQ,EAAK;KACb,MAAM,EAAS,EAAK,KAAK;KACzB,WAAW,EAAM,EAAK,UAAU;KAChC,OAAO,EAAM,EAAK,MAAM;KACzB,EACD,EAAE,YAAY,MAAM,EAAW,gBAAgB,EAAE,KAAK,EAAE,CACzD;;GAoC6B,WAAU;aACtC,kBAAC,GAAD;IAAW,WAAU;IAAQ,WAAU;cAAvC;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;OAC1B,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;OAAI,WAAU;iBAA4C;OAAuB,CAAA,EACjF,kBAAC,KAAD;OAAG,WAAU;iBAA8B;OAAsC,CAAA,CAC7E,EAAA,CAAA,CACF;;KAEL,CAAC,KACA,kBAAC,GAAD;MACE,IAAG;MACH,OAAM;MACN,OAAO,EAAK;MACZ,WAAW,MAAM,EAAS,WAAW,EAAE;MACvC,SAAS;MACT,UAAA;MACA,aAAa,EAAO,YAAY,oBAAoB;MACpD,OAAO,EAAO;MACd,WAAU;MACV,CAAA;KAGJ,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OACE,IAAG;OACH,OAAM;OACN,OAAO,EAAK;OACZ,WAAW,MAAM,EAAS,QAAQ,EAAE;OACpC,aAAY;OACZ,UAAA;OACA,OAAO,EAAO;OACd,CAAA,EACF,kBAAC,GAAD;OACE,IAAG;OACH,OAAM;OACN,OAAO,EAAK;OACZ,WAAW,MAAM,EAAS,UAAU,EAAe;OACnD,SAAS;OACT,CAAA,CACE;;KAEN,kBAAC,GAAD;MACE,IAAG;MACH,OAAM;MACN,OAAO,EAAK;MACZ,WAAW,MAAM,EAAS,eAAe,EAAE;MAC3C,aAAY;MACZ,MAAM;MACN,WAAU;MACV,CAAA;KAEF,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OACE,IAAG;OACH,OAAM;OACN,MAAK;OACL,OAAO,EAAK;OACZ,WAAW,MAAM,EAAS,aAAa,EAAE;OACzC,CAAA,EACF,kBAAC,GAAD;OACE,IAAG;OACH,OAAM;OACN,MAAK;OACL,OAAO,EAAK;OACZ,WAAW,MAAM,EAAS,SAAS,EAAE;OACrC,CAAA,CACE;;KAEN,kBAAC,GAAD;MACE,IAAG;MACH,OAAM;MACN,OAAO,EAAK;MACZ,WAAW,MAAM,EAAS,QAAQ,EAAE;MACpC,aAAY;MACZ,MAAK;MACL,WAAU;MACV,CAAA;KAED,EAAS,WACR,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAS,iBAAiB,QAAQ,EAAS,MAAM,UAAU;MAC1D,CAAA;KAGN,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,UAAD;OACE,MAAK;OACL,UAAU,EAAS;OACnB,WAAU;iBAHZ,CAKG,EAAS,YACR,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,GAE5C,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAE7B,IAAS,iBAAiB,oBACpB;;MACL,CAAA;KACI;;GACP,CAAA;EACI,CAAA"}
|
|
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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"}
|
|
@@ -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;
|
|
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"}
|