@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
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { useCoolAndLovely as e } from "../../context/CoolAndLovelyContext.js";
|
|
2
|
+
import { cn as t } from "../../utils/cn.js";
|
|
3
|
+
import { PageLayout as n } from "../../components/PageLayout.js";
|
|
4
|
+
import { MetricsCard as r } from "../../components/MetricsCard.js";
|
|
5
|
+
import { ErrorView as i, LoadingView as a } from "../../components/StateViews.js";
|
|
6
|
+
import { useBrands as o, useLooks as s, useStyleItems as c } from "../../hooks/useCoolAndLovelyApi.js";
|
|
7
|
+
import "../../hooks/index.js";
|
|
8
|
+
import { titleCase as l } from "../../utils/formatters.js";
|
|
9
|
+
import { useMemo as u } from "react";
|
|
10
|
+
import { CalendarHeart as d, Heart as f, Palette as p, Shapes as m, Shirt as h, Sparkles as g, Store as _, TrendingUp as v } from "lucide-react";
|
|
11
|
+
import { jsx as y, jsxs as b } from "react/jsx-runtime";
|
|
12
|
+
import { EmphasisPanel as x, GlassCard as S, PagePurpose as C } from "@burdenoff/fe-libs/ui";
|
|
13
|
+
//#region src/coolandlovely/pages/trends/TrendsPage.tsx
|
|
14
|
+
function w(e, t = 8) {
|
|
15
|
+
return [...e.entries()].map(([e, t]) => ({
|
|
16
|
+
label: e,
|
|
17
|
+
value: t
|
|
18
|
+
})).sort((e, t) => t.value - e.value).slice(0, t);
|
|
19
|
+
}
|
|
20
|
+
var T = ({ title: e, icon: n, items: r, unit: i, emptyLabel: a, className: o }) => {
|
|
21
|
+
let s = r.reduce((e, t) => Math.max(e, t.value), 0) || 1;
|
|
22
|
+
return /* @__PURE__ */ b(S, {
|
|
23
|
+
treatment: "plain",
|
|
24
|
+
className: t("p-5", o),
|
|
25
|
+
children: [/* @__PURE__ */ b("h3", {
|
|
26
|
+
className: "mb-4 flex items-center gap-2 text-base font-semibold text-text-primary",
|
|
27
|
+
children: [
|
|
28
|
+
n,
|
|
29
|
+
" ",
|
|
30
|
+
e
|
|
31
|
+
]
|
|
32
|
+
}), r.length === 0 ? /* @__PURE__ */ y("p", {
|
|
33
|
+
className: "text-sm text-text-secondary",
|
|
34
|
+
children: a
|
|
35
|
+
}) : /* @__PURE__ */ y("ul", {
|
|
36
|
+
className: "space-y-3",
|
|
37
|
+
children: r.map((e) => /* @__PURE__ */ b("li", { children: [/* @__PURE__ */ b("div", {
|
|
38
|
+
className: "mb-1 flex items-center justify-between gap-2 text-sm",
|
|
39
|
+
children: [/* @__PURE__ */ y("span", {
|
|
40
|
+
className: "truncate text-text-primary",
|
|
41
|
+
children: e.label
|
|
42
|
+
}), /* @__PURE__ */ b("span", {
|
|
43
|
+
className: "shrink-0 tabular-nums text-text-secondary",
|
|
44
|
+
children: [
|
|
45
|
+
e.value.toLocaleString(),
|
|
46
|
+
" ",
|
|
47
|
+
i
|
|
48
|
+
]
|
|
49
|
+
})]
|
|
50
|
+
}), /* @__PURE__ */ y("div", {
|
|
51
|
+
className: "h-2 w-full overflow-hidden rounded-full bg-bg-sunken",
|
|
52
|
+
role: "img",
|
|
53
|
+
"aria-label": `${e.label}: ${e.value} ${i}`,
|
|
54
|
+
children: /* @__PURE__ */ y("div", {
|
|
55
|
+
className: "h-full rounded-full bg-action-primary-bg",
|
|
56
|
+
style: { width: `${Math.max(4, Math.round(e.value / s * 100))}%` }
|
|
57
|
+
})
|
|
58
|
+
})] }, e.label))
|
|
59
|
+
})]
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
function E() {
|
|
63
|
+
e();
|
|
64
|
+
let t = o({ pagination: {
|
|
65
|
+
limit: 100,
|
|
66
|
+
offset: 0
|
|
67
|
+
} }), S = c({ pagination: {
|
|
68
|
+
limit: 100,
|
|
69
|
+
offset: 0
|
|
70
|
+
} }), E = s({ pagination: {
|
|
71
|
+
limit: 100,
|
|
72
|
+
offset: 0
|
|
73
|
+
} }), D = t.isLoading || S.isLoading || E.isLoading, O = t.isError && S.isError && E.isError, k = u(() => {
|
|
74
|
+
let e = t.data?.items ?? [], n = S.data?.items ?? [], r = E.data?.items ?? [], i = /* @__PURE__ */ new Map(), a = /* @__PURE__ */ new Map();
|
|
75
|
+
for (let e of n) {
|
|
76
|
+
i.set(l(e.category), (i.get(l(e.category)) ?? 0) + 1);
|
|
77
|
+
for (let t of e.colours) {
|
|
78
|
+
let e = l(t);
|
|
79
|
+
a.set(e, (a.get(e) ?? 0) + 1);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
let o = /* @__PURE__ */ new Map();
|
|
83
|
+
for (let t of e) for (let e of t.aesthetics) {
|
|
84
|
+
let t = l(e);
|
|
85
|
+
o.set(t, (o.get(t) ?? 0) + 1);
|
|
86
|
+
}
|
|
87
|
+
let s = /* @__PURE__ */ new Map(), c = 0, u = 0;
|
|
88
|
+
for (let e of r) {
|
|
89
|
+
let t = e.likeCount + e.saveCount, n = l(e.occasion);
|
|
90
|
+
s.set(n, (s.get(n) ?? 0) + t), c += e.likeCount, u += e.saveCount;
|
|
91
|
+
}
|
|
92
|
+
let d = [...r].sort((e, t) => t.likeCount + t.saveCount - (e.likeCount + e.saveCount)).slice(0, 5);
|
|
93
|
+
return {
|
|
94
|
+
brandCount: e.length,
|
|
95
|
+
itemCount: n.length,
|
|
96
|
+
lookCount: r.length,
|
|
97
|
+
totalLikes: c,
|
|
98
|
+
totalSaves: u,
|
|
99
|
+
topCategories: w(i, 8),
|
|
100
|
+
topColours: w(a, 8),
|
|
101
|
+
topAesthetics: w(o, 8),
|
|
102
|
+
risingOccasions: w(s, 8),
|
|
103
|
+
topLooks: d
|
|
104
|
+
};
|
|
105
|
+
}, [
|
|
106
|
+
t.data,
|
|
107
|
+
S.data,
|
|
108
|
+
E.data
|
|
109
|
+
]);
|
|
110
|
+
if (D) return /* @__PURE__ */ y(n, {
|
|
111
|
+
title: "Trends",
|
|
112
|
+
children: /* @__PURE__ */ y(a, { label: "Reading platform signals…" })
|
|
113
|
+
});
|
|
114
|
+
if (O) return /* @__PURE__ */ y(n, {
|
|
115
|
+
title: "Trends",
|
|
116
|
+
children: /* @__PURE__ */ y(i, { onRetry: () => {
|
|
117
|
+
t.refetch(), S.refetch(), E.refetch();
|
|
118
|
+
} })
|
|
119
|
+
});
|
|
120
|
+
let A = [
|
|
121
|
+
{
|
|
122
|
+
icon: /* @__PURE__ */ y(_, { className: "h-5 w-5" }),
|
|
123
|
+
value: k.brandCount,
|
|
124
|
+
label: "Brands sampled"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
icon: /* @__PURE__ */ y(h, { className: "h-5 w-5" }),
|
|
128
|
+
value: k.itemCount,
|
|
129
|
+
label: "Items sampled"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
icon: /* @__PURE__ */ y(g, { className: "h-5 w-5" }),
|
|
133
|
+
value: k.lookCount,
|
|
134
|
+
label: "Looks sampled"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
icon: /* @__PURE__ */ y(f, { className: "h-5 w-5" }),
|
|
138
|
+
value: k.totalLikes + k.totalSaves,
|
|
139
|
+
label: "Total engagement"
|
|
140
|
+
}
|
|
141
|
+
];
|
|
142
|
+
return /* @__PURE__ */ b(n, {
|
|
143
|
+
title: "Trends",
|
|
144
|
+
description: "Signals from platform activity",
|
|
145
|
+
hero: !0,
|
|
146
|
+
children: [
|
|
147
|
+
/* @__PURE__ */ y(C, {
|
|
148
|
+
className: "mb-6",
|
|
149
|
+
children: "These are honest, client-side signals computed from the pieces, brands and looks currently on CoolAndLovely — the most-listed categories and colours, the aesthetics brands describe themselves with, and which occasions are drawing the most likes and saves. Nothing here is fabricated; it is a live read of the catalogue and social activity."
|
|
150
|
+
}),
|
|
151
|
+
/* @__PURE__ */ b(x, {
|
|
152
|
+
className: "mb-8",
|
|
153
|
+
children: [/* @__PURE__ */ y("h2", {
|
|
154
|
+
className: "mb-4 text-lg font-semibold text-text-primary",
|
|
155
|
+
children: "At a glance"
|
|
156
|
+
}), /* @__PURE__ */ y("div", {
|
|
157
|
+
className: "grid grid-cols-2 gap-4 lg:grid-cols-4",
|
|
158
|
+
children: A.map((e) => /* @__PURE__ */ y(r, {
|
|
159
|
+
icon: e.icon,
|
|
160
|
+
value: e.value.toLocaleString(),
|
|
161
|
+
label: e.label
|
|
162
|
+
}, e.label))
|
|
163
|
+
})]
|
|
164
|
+
}),
|
|
165
|
+
/* @__PURE__ */ b("div", {
|
|
166
|
+
className: "grid grid-cols-1 gap-4 lg:grid-cols-2",
|
|
167
|
+
children: [
|
|
168
|
+
/* @__PURE__ */ y(T, {
|
|
169
|
+
title: "Top categories",
|
|
170
|
+
icon: /* @__PURE__ */ y(m, { className: "h-5 w-5" }),
|
|
171
|
+
items: k.topCategories,
|
|
172
|
+
unit: "items",
|
|
173
|
+
emptyLabel: "No style items yet."
|
|
174
|
+
}),
|
|
175
|
+
/* @__PURE__ */ y(T, {
|
|
176
|
+
title: "Most-listed colours",
|
|
177
|
+
icon: /* @__PURE__ */ y(p, { className: "h-5 w-5" }),
|
|
178
|
+
items: k.topColours,
|
|
179
|
+
unit: "items",
|
|
180
|
+
emptyLabel: "No colours recorded on items yet."
|
|
181
|
+
}),
|
|
182
|
+
/* @__PURE__ */ y(T, {
|
|
183
|
+
title: "Brand aesthetics",
|
|
184
|
+
icon: /* @__PURE__ */ y(_, { className: "h-5 w-5" }),
|
|
185
|
+
items: k.topAesthetics,
|
|
186
|
+
unit: "brands",
|
|
187
|
+
emptyLabel: "No brand aesthetics recorded yet."
|
|
188
|
+
}),
|
|
189
|
+
/* @__PURE__ */ y(T, {
|
|
190
|
+
title: "Rising occasions",
|
|
191
|
+
icon: /* @__PURE__ */ y(d, { className: "h-5 w-5" }),
|
|
192
|
+
items: k.risingOccasions,
|
|
193
|
+
unit: "engagement",
|
|
194
|
+
emptyLabel: "No looks published yet."
|
|
195
|
+
})
|
|
196
|
+
]
|
|
197
|
+
}),
|
|
198
|
+
/* @__PURE__ */ b("section", {
|
|
199
|
+
className: "mt-8",
|
|
200
|
+
children: [/* @__PURE__ */ b("h2", {
|
|
201
|
+
className: "mb-3 flex items-center gap-2 text-lg font-semibold text-text-primary",
|
|
202
|
+
children: [/* @__PURE__ */ y(v, { className: "h-5 w-5" }), " Most-loved looks"]
|
|
203
|
+
}), k.topLooks.length === 0 ? /* @__PURE__ */ y("p", {
|
|
204
|
+
className: "rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary",
|
|
205
|
+
children: "No looks published yet."
|
|
206
|
+
}) : /* @__PURE__ */ y("div", {
|
|
207
|
+
className: "divide-y divide-border-seam rounded-lg border border-border-seam bg-bg-surface",
|
|
208
|
+
children: k.topLooks.map((e, t) => /* @__PURE__ */ b("div", {
|
|
209
|
+
className: "flex items-center gap-4 px-4 py-3",
|
|
210
|
+
children: [
|
|
211
|
+
/* @__PURE__ */ y("span", {
|
|
212
|
+
className: "w-6 shrink-0 text-center text-sm font-semibold tabular-nums text-text-secondary",
|
|
213
|
+
children: t + 1
|
|
214
|
+
}),
|
|
215
|
+
/* @__PURE__ */ b("div", {
|
|
216
|
+
className: "min-w-0 flex-1",
|
|
217
|
+
children: [/* @__PURE__ */ y("p", {
|
|
218
|
+
className: "truncate text-sm font-medium text-text-primary",
|
|
219
|
+
children: e.title
|
|
220
|
+
}), /* @__PURE__ */ y("p", {
|
|
221
|
+
className: "text-xs text-text-secondary",
|
|
222
|
+
children: l(e.occasion)
|
|
223
|
+
})]
|
|
224
|
+
}),
|
|
225
|
+
/* @__PURE__ */ b("span", {
|
|
226
|
+
className: "flex shrink-0 items-center gap-3 text-xs text-text-secondary",
|
|
227
|
+
children: [/* @__PURE__ */ b("span", {
|
|
228
|
+
className: "inline-flex items-center gap-1",
|
|
229
|
+
children: [
|
|
230
|
+
/* @__PURE__ */ y(f, { className: "h-3.5 w-3.5" }),
|
|
231
|
+
" ",
|
|
232
|
+
e.likeCount
|
|
233
|
+
]
|
|
234
|
+
}), /* @__PURE__ */ b("span", {
|
|
235
|
+
className: "tabular-nums",
|
|
236
|
+
children: [e.saveCount, " saves"]
|
|
237
|
+
})]
|
|
238
|
+
})
|
|
239
|
+
]
|
|
240
|
+
}, e.id))
|
|
241
|
+
})]
|
|
242
|
+
})
|
|
243
|
+
]
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
//#endregion
|
|
247
|
+
export { E as TrendsPage };
|
|
248
|
+
|
|
249
|
+
//# sourceMappingURL=TrendsPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TrendsPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/trends/TrendsPage.tsx"],"sourcesContent":["/**\n * Trends Page\n * @module coolandlovely/pages/trends\n *\n * Client-side analytics computed over the real catalogue + social lists\n * (cooListBrands / cooListStyleItems / cooListLooks). Honest by construction —\n * it only counts and ranks what is actually on the platform. No fabricated\n * metrics. Header labels it \"Signals from platform activity.\"\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { useMemo } from 'react';\nimport {\n TrendingUp,\n Palette,\n Shapes,\n Sparkles,\n Store,\n Shirt,\n Heart,\n CalendarHeart,\n} from 'lucide-react';\nimport { EmphasisPanel, GlassCard, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { MetricsCard } from '../../components/MetricsCard';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useBrands, useStyleItems, useLooks } from '../../hooks';\nimport { titleCase } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\n\ninterface Ranked {\n label: string;\n value: number;\n sublabel?: string;\n}\n\nfunction rank(map: Map<string, number>, top = 8): Ranked[] {\n return [...map.entries()]\n .map(([label, value]) => ({ label, value }))\n .sort((a, b) => b.value - a.value)\n .slice(0, top);\n}\n\ninterface StatBarsProps {\n title: string;\n icon: ReactNode;\n items: Ranked[];\n unit: string;\n emptyLabel: string;\n className?: string;\n}\n\nconst StatBars: FC<StatBarsProps> = ({ title, icon, items, unit, emptyLabel, className }) => {\n const max = items.reduce((m, i) => Math.max(m, i.value), 0) || 1;\n return (\n <GlassCard treatment=\"plain\" className={cn('p-5', className)}>\n <h3 className=\"mb-4 flex items-center gap-2 text-base font-semibold text-text-primary\">\n {icon} {title}\n </h3>\n {items.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">{emptyLabel}</p>\n ) : (\n <ul className=\"space-y-3\">\n {items.map((item) => (\n <li key={item.label}>\n <div className=\"mb-1 flex items-center justify-between gap-2 text-sm\">\n <span className=\"truncate text-text-primary\">{item.label}</span>\n <span className=\"shrink-0 tabular-nums text-text-secondary\">\n {item.value.toLocaleString()} {unit}\n </span>\n </div>\n <div\n className=\"h-2 w-full overflow-hidden rounded-full bg-bg-sunken\"\n role=\"img\"\n aria-label={`${item.label}: ${item.value} ${unit}`}\n >\n <div\n className=\"h-full rounded-full bg-action-primary-bg\"\n style={{ width: `${Math.max(4, Math.round((item.value / max) * 100))}%` }}\n />\n </div>\n </li>\n ))}\n </ul>\n )}\n </GlassCard>\n );\n};\n\nexport function TrendsPage() {\n useCoolAndLovely();\n const brands = useBrands({ pagination: { limit: 100, offset: 0 } });\n const items = useStyleItems({ pagination: { limit: 100, offset: 0 } });\n const looks = useLooks({ pagination: { limit: 100, offset: 0 } });\n\n const isLoading = brands.isLoading || items.isLoading || looks.isLoading;\n const isError = brands.isError && items.isError && looks.isError;\n\n const analysis = useMemo(() => {\n const brandRows = brands.data?.items ?? [];\n const itemRows = items.data?.items ?? [];\n const lookRows = looks.data?.items ?? [];\n\n const categoryMap = new Map<string, number>();\n const colourMap = new Map<string, number>();\n for (const it of itemRows) {\n categoryMap.set(titleCase(it.category), (categoryMap.get(titleCase(it.category)) ?? 0) + 1);\n for (const c of it.colours) {\n const key = titleCase(c);\n colourMap.set(key, (colourMap.get(key) ?? 0) + 1);\n }\n }\n\n const aestheticMap = new Map<string, number>();\n for (const b of brandRows) {\n for (const a of b.aesthetics) {\n const key = titleCase(a);\n aestheticMap.set(key, (aestheticMap.get(key) ?? 0) + 1);\n }\n }\n\n const occasionEngagement = new Map<string, number>();\n let totalLikes = 0;\n let totalSaves = 0;\n for (const l of lookRows) {\n const eng = l.likeCount + l.saveCount;\n const key = titleCase(l.occasion);\n occasionEngagement.set(key, (occasionEngagement.get(key) ?? 0) + eng);\n totalLikes += l.likeCount;\n totalSaves += l.saveCount;\n }\n\n const topLooks = [...lookRows]\n .sort((a, b) => b.likeCount + b.saveCount - (a.likeCount + a.saveCount))\n .slice(0, 5);\n\n return {\n brandCount: brandRows.length,\n itemCount: itemRows.length,\n lookCount: lookRows.length,\n totalLikes,\n totalSaves,\n topCategories: rank(categoryMap, 8),\n topColours: rank(colourMap, 8),\n topAesthetics: rank(aestheticMap, 8),\n risingOccasions: rank(occasionEngagement, 8),\n topLooks,\n };\n }, [brands.data, items.data, looks.data]);\n\n if (isLoading) {\n return (\n <PageLayout title=\"Trends\">\n <LoadingView label=\"Reading platform signals…\" />\n </PageLayout>\n );\n }\n\n if (isError) {\n return (\n <PageLayout title=\"Trends\">\n <ErrorView\n onRetry={() => {\n void brands.refetch();\n void items.refetch();\n void looks.refetch();\n }}\n />\n </PageLayout>\n );\n }\n\n const metrics = [\n { icon: <Store className=\"h-5 w-5\" />, value: analysis.brandCount, label: 'Brands sampled' },\n { icon: <Shirt className=\"h-5 w-5\" />, value: analysis.itemCount, label: 'Items sampled' },\n { icon: <Sparkles className=\"h-5 w-5\" />, value: analysis.lookCount, label: 'Looks sampled' },\n {\n icon: <Heart className=\"h-5 w-5\" />,\n value: analysis.totalLikes + analysis.totalSaves,\n label: 'Total engagement',\n },\n ];\n\n return (\n <PageLayout\n title=\"Trends\"\n description=\"Signals from platform activity\"\n hero\n >\n <PagePurpose className=\"mb-6\">\n These are honest, client-side signals computed from the pieces, brands and looks currently\n on CoolAndLovely — the most-listed categories and colours, the aesthetics brands describe\n themselves with, and which occasions are drawing the most likes and saves. Nothing here is\n fabricated; it is a live read of the catalogue and social activity.\n </PagePurpose>\n\n <EmphasisPanel className=\"mb-8\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">At a glance</h2>\n <div className=\"grid grid-cols-2 gap-4 lg:grid-cols-4\">\n {metrics.map((m) => (\n <MetricsCard\n key={m.label}\n icon={m.icon}\n value={m.value.toLocaleString()}\n label={m.label}\n />\n ))}\n </div>\n </EmphasisPanel>\n\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n <StatBars\n title=\"Top categories\"\n icon={<Shapes className=\"h-5 w-5\" />}\n items={analysis.topCategories}\n unit=\"items\"\n emptyLabel=\"No style items yet.\"\n />\n <StatBars\n title=\"Most-listed colours\"\n icon={<Palette className=\"h-5 w-5\" />}\n items={analysis.topColours}\n unit=\"items\"\n emptyLabel=\"No colours recorded on items yet.\"\n />\n <StatBars\n title=\"Brand aesthetics\"\n icon={<Store className=\"h-5 w-5\" />}\n items={analysis.topAesthetics}\n unit=\"brands\"\n emptyLabel=\"No brand aesthetics recorded yet.\"\n />\n <StatBars\n title=\"Rising occasions\"\n icon={<CalendarHeart className=\"h-5 w-5\" />}\n items={analysis.risingOccasions}\n unit=\"engagement\"\n emptyLabel=\"No looks published yet.\"\n />\n </div>\n\n <section className=\"mt-8\">\n <h2 className=\"mb-3 flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <TrendingUp className=\"h-5 w-5\" /> Most-loved looks\n </h2>\n {analysis.topLooks.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n No looks published yet.\n </p>\n ) : (\n <div className=\"divide-y divide-border-seam rounded-lg border border-border-seam bg-bg-surface\">\n {analysis.topLooks.map((look, idx) => (\n <div key={look.id} className=\"flex items-center gap-4 px-4 py-3\">\n <span className=\"w-6 shrink-0 text-center text-sm font-semibold tabular-nums text-text-secondary\">\n {idx + 1}\n </span>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{look.title}</p>\n <p className=\"text-xs text-text-secondary\">{titleCase(look.occasion)}</p>\n </div>\n <span className=\"flex shrink-0 items-center gap-3 text-xs text-text-secondary\">\n <span className=\"inline-flex items-center gap-1\">\n <Heart className=\"h-3.5 w-3.5\" /> {look.likeCount}\n </span>\n <span className=\"tabular-nums\">{look.saveCount} saves</span>\n </span>\n </div>\n ))}\n </div>\n )}\n </section>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
import { useCoolAndLovely as e } from "../../context/CoolAndLovelyContext.js";
|
|
2
2
|
import { PageLayout as t } from "../../components/PageLayout.js";
|
|
3
|
-
import {
|
|
3
|
+
import { COO_FILE_FOLDERS as n } from "../../hooks/useFiles.js";
|
|
4
|
+
import { useCreateWardrobeItem as r } from "../../hooks/useCoolAndLovelyApi.js";
|
|
4
5
|
import "../../hooks/index.js";
|
|
5
|
-
import { parseCsv as
|
|
6
|
-
import { STYLE_CATEGORIES as
|
|
7
|
-
import { SelectField as
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
6
|
+
import { parseCsv as i, titleCase as a } from "../../utils/formatters.js";
|
|
7
|
+
import { STYLE_CATEGORIES as o } from "../../utils/icons.js";
|
|
8
|
+
import { SelectField as s, TextAreaField as c, TextField as l } from "../../components/Field.js";
|
|
9
|
+
import { ImageUploadField as u } from "../../components/ImageUploadField.js";
|
|
10
|
+
import { useState as d } from "react";
|
|
11
|
+
import { ArrowLeft as f, Heart as p, Loader2 as m, Save as h } from "lucide-react";
|
|
12
|
+
import { jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
13
|
+
import { GlassCard as v } from "@burdenoff/fe-libs/ui";
|
|
12
14
|
//#region src/coolandlovely/pages/wardrobe/WardrobeFormPage.tsx
|
|
13
|
-
var
|
|
15
|
+
var y = [
|
|
14
16
|
"OWNED",
|
|
15
17
|
"WISHLIST",
|
|
16
18
|
"PURCHASED_ON_PLATFORM",
|
|
17
19
|
"GIFTED"
|
|
18
|
-
],
|
|
20
|
+
], b = {
|
|
19
21
|
name: "",
|
|
20
22
|
category: "OTHER",
|
|
21
23
|
source: "OWNED",
|
|
@@ -23,143 +25,153 @@ var _ = [
|
|
|
23
25
|
colour: "",
|
|
24
26
|
size: "",
|
|
25
27
|
notes: "",
|
|
26
|
-
tags: ""
|
|
28
|
+
tags: "",
|
|
29
|
+
imageFileId: ""
|
|
27
30
|
};
|
|
28
|
-
function
|
|
29
|
-
let { navigateTo:
|
|
31
|
+
function x() {
|
|
32
|
+
let { navigateTo: x } = e(), S = r(), [C, w] = d(b), [T, E] = d(), D = (e, t) => w((n) => ({
|
|
30
33
|
...n,
|
|
31
34
|
[e]: t
|
|
32
|
-
})),
|
|
35
|
+
})), O = o.map((e) => ({
|
|
33
36
|
value: e,
|
|
34
|
-
label:
|
|
35
|
-
})),
|
|
37
|
+
label: a(e)
|
|
38
|
+
})), k = y.map((e) => ({
|
|
36
39
|
value: e,
|
|
37
|
-
label:
|
|
40
|
+
label: a(e)
|
|
38
41
|
}));
|
|
39
|
-
return /* @__PURE__ */
|
|
42
|
+
return /* @__PURE__ */ g(t, {
|
|
40
43
|
title: "Add a wardrobe piece",
|
|
41
44
|
description: "Catalogue something you own or want",
|
|
42
|
-
actions: /* @__PURE__ */
|
|
45
|
+
actions: /* @__PURE__ */ _("button", {
|
|
43
46
|
type: "button",
|
|
44
|
-
onClick: () =>
|
|
47
|
+
onClick: () => x("/wardrobe"),
|
|
45
48
|
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",
|
|
46
|
-
children: [/* @__PURE__ */
|
|
49
|
+
children: [/* @__PURE__ */ g(f, { className: "h-4 w-4" }), " Cancel"]
|
|
47
50
|
}),
|
|
48
|
-
children: /* @__PURE__ */
|
|
51
|
+
children: /* @__PURE__ */ g("form", {
|
|
49
52
|
onSubmit: (e) => {
|
|
50
|
-
if (e.preventDefault(), !
|
|
51
|
-
|
|
53
|
+
if (e.preventDefault(), !C.name.trim()) {
|
|
54
|
+
E("Name is required");
|
|
52
55
|
return;
|
|
53
56
|
}
|
|
54
|
-
|
|
55
|
-
name:
|
|
56
|
-
category:
|
|
57
|
-
source:
|
|
58
|
-
brandName:
|
|
59
|
-
colour:
|
|
60
|
-
size:
|
|
61
|
-
notes:
|
|
62
|
-
tags:
|
|
63
|
-
|
|
57
|
+
E(void 0), S.mutate({
|
|
58
|
+
name: C.name.trim(),
|
|
59
|
+
category: C.category,
|
|
60
|
+
source: C.source,
|
|
61
|
+
brandName: C.brandName.trim() || void 0,
|
|
62
|
+
colour: C.colour.trim() || void 0,
|
|
63
|
+
size: C.size.trim() || void 0,
|
|
64
|
+
notes: C.notes.trim() || void 0,
|
|
65
|
+
tags: i(C.tags),
|
|
66
|
+
imageFileId: C.imageFileId || void 0
|
|
67
|
+
}, { onSuccess: () => x("/wardrobe") });
|
|
64
68
|
},
|
|
65
69
|
className: "mx-auto max-w-2xl",
|
|
66
|
-
children: /* @__PURE__ */
|
|
70
|
+
children: /* @__PURE__ */ _(v, {
|
|
67
71
|
treatment: "plain",
|
|
68
72
|
className: "p-6",
|
|
69
73
|
children: [
|
|
70
|
-
/* @__PURE__ */
|
|
74
|
+
/* @__PURE__ */ _("div", {
|
|
71
75
|
className: "mb-6 flex items-center gap-3",
|
|
72
|
-
children: [/* @__PURE__ */
|
|
76
|
+
children: [/* @__PURE__ */ g("div", {
|
|
73
77
|
className: "flex h-11 w-11 items-center justify-center rounded-lg bg-status-error-bg-subtle text-status-error-text",
|
|
74
|
-
children: /* @__PURE__ */
|
|
75
|
-
}), /* @__PURE__ */
|
|
78
|
+
children: /* @__PURE__ */ g(p, { className: "h-5 w-5" })
|
|
79
|
+
}), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("h2", {
|
|
76
80
|
className: "text-base font-semibold text-text-primary",
|
|
77
81
|
children: "Piece details"
|
|
78
|
-
}), /* @__PURE__ */
|
|
82
|
+
}), /* @__PURE__ */ g("p", {
|
|
79
83
|
className: "text-xs text-text-secondary",
|
|
80
84
|
children: "Only you can see your wardrobe."
|
|
81
85
|
})] })]
|
|
82
86
|
}),
|
|
83
|
-
/* @__PURE__ */
|
|
87
|
+
/* @__PURE__ */ g(l, {
|
|
84
88
|
id: "name",
|
|
85
89
|
label: "Name",
|
|
86
|
-
value:
|
|
87
|
-
onChange: (e) =>
|
|
90
|
+
value: C.name,
|
|
91
|
+
onChange: (e) => D("name", e),
|
|
88
92
|
placeholder: "Vintage denim jacket",
|
|
89
93
|
required: !0,
|
|
90
|
-
error:
|
|
94
|
+
error: T
|
|
91
95
|
}),
|
|
92
|
-
/* @__PURE__ */
|
|
96
|
+
/* @__PURE__ */ _("div", {
|
|
93
97
|
className: "mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2",
|
|
94
|
-
children: [/* @__PURE__ */
|
|
98
|
+
children: [/* @__PURE__ */ g(s, {
|
|
95
99
|
id: "category",
|
|
96
100
|
label: "Category",
|
|
97
|
-
value:
|
|
98
|
-
onChange: (e) =>
|
|
99
|
-
options:
|
|
100
|
-
}), /* @__PURE__ */
|
|
101
|
+
value: C.category,
|
|
102
|
+
onChange: (e) => D("category", e),
|
|
103
|
+
options: O
|
|
104
|
+
}), /* @__PURE__ */ g(s, {
|
|
101
105
|
id: "source",
|
|
102
106
|
label: "Source",
|
|
103
|
-
value:
|
|
104
|
-
onChange: (e) =>
|
|
105
|
-
options:
|
|
107
|
+
value: C.source,
|
|
108
|
+
onChange: (e) => D("source", e),
|
|
109
|
+
options: k
|
|
106
110
|
})]
|
|
107
111
|
}),
|
|
108
|
-
/* @__PURE__ */
|
|
112
|
+
/* @__PURE__ */ _("div", {
|
|
109
113
|
className: "mt-4 grid grid-cols-1 gap-4 sm:grid-cols-3",
|
|
110
114
|
children: [
|
|
111
|
-
/* @__PURE__ */
|
|
115
|
+
/* @__PURE__ */ g(l, {
|
|
112
116
|
id: "brandName",
|
|
113
117
|
label: "Brand",
|
|
114
|
-
value:
|
|
115
|
-
onChange: (e) =>
|
|
118
|
+
value: C.brandName,
|
|
119
|
+
onChange: (e) => D("brandName", e),
|
|
116
120
|
placeholder: "Levi's"
|
|
117
121
|
}),
|
|
118
|
-
/* @__PURE__ */
|
|
122
|
+
/* @__PURE__ */ g(l, {
|
|
119
123
|
id: "colour",
|
|
120
124
|
label: "Colour",
|
|
121
|
-
value:
|
|
122
|
-
onChange: (e) =>
|
|
125
|
+
value: C.colour,
|
|
126
|
+
onChange: (e) => D("colour", e),
|
|
123
127
|
placeholder: "Indigo"
|
|
124
128
|
}),
|
|
125
|
-
/* @__PURE__ */
|
|
129
|
+
/* @__PURE__ */ g(l, {
|
|
126
130
|
id: "size",
|
|
127
131
|
label: "Size",
|
|
128
|
-
value:
|
|
129
|
-
onChange: (e) =>
|
|
132
|
+
value: C.size,
|
|
133
|
+
onChange: (e) => D("size", e),
|
|
130
134
|
placeholder: "M"
|
|
131
135
|
})
|
|
132
136
|
]
|
|
133
137
|
}),
|
|
134
|
-
/* @__PURE__ */
|
|
138
|
+
/* @__PURE__ */ g(c, {
|
|
135
139
|
id: "notes",
|
|
136
140
|
label: "Notes",
|
|
137
|
-
value:
|
|
138
|
-
onChange: (e) =>
|
|
141
|
+
value: C.notes,
|
|
142
|
+
onChange: (e) => D("notes", e),
|
|
139
143
|
placeholder: "Where you got it, how you wear it…",
|
|
140
144
|
rows: 3,
|
|
141
145
|
className: "mt-4"
|
|
142
146
|
}),
|
|
143
|
-
/* @__PURE__ */
|
|
147
|
+
/* @__PURE__ */ g(l, {
|
|
144
148
|
id: "tags",
|
|
145
149
|
label: "Tags",
|
|
146
|
-
value:
|
|
147
|
-
onChange: (e) =>
|
|
150
|
+
value: C.tags,
|
|
151
|
+
onChange: (e) => D("tags", e),
|
|
148
152
|
placeholder: "denim, layering",
|
|
149
153
|
hint: "Comma-separated",
|
|
150
154
|
className: "mt-4"
|
|
151
155
|
}),
|
|
152
|
-
|
|
156
|
+
/* @__PURE__ */ g(u, {
|
|
157
|
+
label: "Photo",
|
|
158
|
+
value: C.imageFileId,
|
|
159
|
+
onChange: (e) => D("imageFileId", e),
|
|
160
|
+
folder: n.wardrobe,
|
|
161
|
+
hint: "Your photo of the piece (private to you)",
|
|
162
|
+
className: "mt-4"
|
|
163
|
+
}),
|
|
164
|
+
S.isError && /* @__PURE__ */ g("p", {
|
|
153
165
|
className: "mt-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
|
|
154
|
-
children:
|
|
166
|
+
children: S.error instanceof Error ? S.error.message : "Something went wrong"
|
|
155
167
|
}),
|
|
156
|
-
/* @__PURE__ */
|
|
168
|
+
/* @__PURE__ */ g("div", {
|
|
157
169
|
className: "mt-6 flex justify-end gap-2",
|
|
158
|
-
children: /* @__PURE__ */
|
|
170
|
+
children: /* @__PURE__ */ _("button", {
|
|
159
171
|
type: "submit",
|
|
160
|
-
disabled:
|
|
172
|
+
disabled: S.isPending,
|
|
161
173
|
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",
|
|
162
|
-
children: [
|
|
174
|
+
children: [S.isPending ? /* @__PURE__ */ g(m, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ g(h, { className: "h-4 w-4" }), "Add to wardrobe"]
|
|
163
175
|
})
|
|
164
176
|
})
|
|
165
177
|
]
|
|
@@ -168,6 +180,6 @@ function y() {
|
|
|
168
180
|
});
|
|
169
181
|
}
|
|
170
182
|
//#endregion
|
|
171
|
-
export {
|
|
183
|
+
export { x as WardrobeFormPage };
|
|
172
184
|
|
|
173
185
|
//# sourceMappingURL=WardrobeFormPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WardrobeFormPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/wardrobe/WardrobeFormPage.tsx"],"sourcesContent":["/**\n * Wardrobe Item Create Page (self-scoped)\n * @module coolandlovely/pages/wardrobe\n *\n * Drives `cooCreateWardrobeItem`. Belongs to the calling shopper's profile.\n */\n\nimport { useState } from 'react';\nimport { ArrowLeft, Loader2, Save, Heart } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { TextField, TextAreaField, SelectField } from '../../components/Field';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useCreateWardrobeItem } from '../../hooks';\nimport { STYLE_CATEGORIES } from '../../utils/icons';\nimport { parseCsv, titleCase } from '../../utils/formatters';\nimport type { CooStyleCategory, CooWardrobeSource } from '../../types';\n\nconst SOURCES: CooWardrobeSource[] = ['OWNED', 'WISHLIST', 'PURCHASED_ON_PLATFORM', 'GIFTED'];\n\ninterface FormState {\n name: string;\n category: CooStyleCategory;\n source: CooWardrobeSource;\n brandName: string;\n colour: string;\n size: string;\n notes: string;\n tags: string;\n}\n\nconst EMPTY: FormState = {\n name: '',\n category: 'OTHER',\n source: 'OWNED',\n brandName: '',\n colour: '',\n size: '',\n notes: '',\n tags: '',\n};\n\nexport function WardrobeFormPage() {\n const { navigateTo } = useCoolAndLovely();\n const createItem = useCreateWardrobeItem();\n\n const [form, setForm] = useState<FormState>(EMPTY);\n const [nameError, setNameError] = useState<string | undefined>();\n\n const setField = <K extends keyof FormState>(key: K, value: FormState[K]) =>\n setForm((f) => ({ ...f, [key]: value }));\n\n const categoryOptions = STYLE_CATEGORIES.map((c) => ({ value: c, label: titleCase(c) }));\n const sourceOptions = SOURCES.map((s) => ({ value: s, label: titleCase(s) }));\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (!form.name.trim()) {\n setNameError('Name is required');\n return;\n }\n setNameError(undefined);\n\n createItem.mutate(\n {\n name: form.name.trim(),\n category: form.category,\n source: form.source,\n brandName: form.brandName.trim() || undefined,\n colour: form.colour.trim() || undefined,\n size: form.size.trim() || undefined,\n notes: form.notes.trim() || undefined,\n tags: parseCsv(form.tags),\n },\n { onSuccess: () => navigateTo('/wardrobe') }\n );\n };\n\n return (\n <PageLayout\n title=\"Add a wardrobe piece\"\n description=\"Catalogue something you own or want\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/wardrobe')}\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-status-error-bg-subtle text-status-error-text\">\n <Heart className=\"h-5 w-5\" />\n </div>\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">Piece details</h2>\n <p className=\"text-xs text-text-secondary\">Only you can see your wardrobe.</p>\n </div>\n </div>\n\n <TextField\n id=\"name\"\n label=\"Name\"\n value={form.name}\n onChange={(v) => setField('name', v)}\n placeholder=\"Vintage denim jacket\"\n required\n error={nameError}\n />\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <SelectField\n id=\"category\"\n label=\"Category\"\n value={form.category}\n onChange={(v) => setField('category', v as CooStyleCategory)}\n options={categoryOptions}\n />\n <SelectField\n id=\"source\"\n label=\"Source\"\n value={form.source}\n onChange={(v) => setField('source', v as CooWardrobeSource)}\n options={sourceOptions}\n />\n </div>\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <TextField\n id=\"brandName\"\n label=\"Brand\"\n value={form.brandName}\n onChange={(v) => setField('brandName', v)}\n placeholder=\"Levi's\"\n />\n <TextField\n id=\"colour\"\n label=\"Colour\"\n value={form.colour}\n onChange={(v) => setField('colour', v)}\n placeholder=\"Indigo\"\n />\n <TextField\n id=\"size\"\n label=\"Size\"\n value={form.size}\n onChange={(v) => setField('size', v)}\n placeholder=\"M\"\n />\n </div>\n\n <TextAreaField\n id=\"notes\"\n label=\"Notes\"\n value={form.notes}\n onChange={(v) => setField('notes', v)}\n placeholder=\"Where you got it, how you wear it…\"\n rows={3}\n className=\"mt-4\"\n />\n\n <TextField\n id=\"tags\"\n label=\"Tags\"\n value={form.tags}\n onChange={(v) => setField('tags', v)}\n placeholder=\"denim, layering\"\n hint=\"Comma-separated\"\n className=\"mt-4\"\n />\n\n {createItem.isError && (\n <p className=\"mt-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {createItem.error instanceof Error\n ? createItem.error.message\n : 'Something went wrong'}\n </p>\n )}\n\n <div className=\"mt-6 flex justify-end gap-2\">\n <button\n type=\"submit\"\n disabled={createItem.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 {createItem.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n Add to wardrobe\n </button>\n </div>\n </GlassCard>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"WardrobeFormPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/wardrobe/WardrobeFormPage.tsx"],"sourcesContent":["/**\n * Wardrobe Item Create Page (self-scoped)\n * @module coolandlovely/pages/wardrobe\n *\n * Drives `cooCreateWardrobeItem`. Belongs to the calling shopper's profile.\n */\n\nimport { useState } from 'react';\nimport { ArrowLeft, Loader2, Save, Heart } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { TextField, TextAreaField, SelectField } from '../../components/Field';\nimport { ImageUploadField } from '../../components/ImageUploadField';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useCreateWardrobeItem } from '../../hooks';\nimport { COO_FILE_FOLDERS } from '../../hooks/useFiles';\nimport { STYLE_CATEGORIES } from '../../utils/icons';\nimport { parseCsv, titleCase } from '../../utils/formatters';\nimport type { CooStyleCategory, CooWardrobeSource } from '../../types';\n\nconst SOURCES: CooWardrobeSource[] = ['OWNED', 'WISHLIST', 'PURCHASED_ON_PLATFORM', 'GIFTED'];\n\ninterface FormState {\n name: string;\n category: CooStyleCategory;\n source: CooWardrobeSource;\n brandName: string;\n colour: string;\n size: string;\n notes: string;\n tags: string;\n imageFileId: string;\n}\n\nconst EMPTY: FormState = {\n name: '',\n category: 'OTHER',\n source: 'OWNED',\n brandName: '',\n colour: '',\n size: '',\n notes: '',\n tags: '',\n imageFileId: '',\n};\n\nexport function WardrobeFormPage() {\n const { navigateTo } = useCoolAndLovely();\n const createItem = useCreateWardrobeItem();\n\n const [form, setForm] = useState<FormState>(EMPTY);\n const [nameError, setNameError] = useState<string | undefined>();\n\n const setField = <K extends keyof FormState>(key: K, value: FormState[K]) =>\n setForm((f) => ({ ...f, [key]: value }));\n\n const categoryOptions = STYLE_CATEGORIES.map((c) => ({ value: c, label: titleCase(c) }));\n const sourceOptions = SOURCES.map((s) => ({ value: s, label: titleCase(s) }));\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (!form.name.trim()) {\n setNameError('Name is required');\n return;\n }\n setNameError(undefined);\n\n createItem.mutate(\n {\n name: form.name.trim(),\n category: form.category,\n source: form.source,\n brandName: form.brandName.trim() || undefined,\n colour: form.colour.trim() || undefined,\n size: form.size.trim() || undefined,\n notes: form.notes.trim() || undefined,\n tags: parseCsv(form.tags),\n imageFileId: form.imageFileId || undefined,\n },\n { onSuccess: () => navigateTo('/wardrobe') }\n );\n };\n\n return (\n <PageLayout\n title=\"Add a wardrobe piece\"\n description=\"Catalogue something you own or want\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/wardrobe')}\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-status-error-bg-subtle text-status-error-text\">\n <Heart className=\"h-5 w-5\" />\n </div>\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">Piece details</h2>\n <p className=\"text-xs text-text-secondary\">Only you can see your wardrobe.</p>\n </div>\n </div>\n\n <TextField\n id=\"name\"\n label=\"Name\"\n value={form.name}\n onChange={(v) => setField('name', v)}\n placeholder=\"Vintage denim jacket\"\n required\n error={nameError}\n />\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <SelectField\n id=\"category\"\n label=\"Category\"\n value={form.category}\n onChange={(v) => setField('category', v as CooStyleCategory)}\n options={categoryOptions}\n />\n <SelectField\n id=\"source\"\n label=\"Source\"\n value={form.source}\n onChange={(v) => setField('source', v as CooWardrobeSource)}\n options={sourceOptions}\n />\n </div>\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <TextField\n id=\"brandName\"\n label=\"Brand\"\n value={form.brandName}\n onChange={(v) => setField('brandName', v)}\n placeholder=\"Levi's\"\n />\n <TextField\n id=\"colour\"\n label=\"Colour\"\n value={form.colour}\n onChange={(v) => setField('colour', v)}\n placeholder=\"Indigo\"\n />\n <TextField\n id=\"size\"\n label=\"Size\"\n value={form.size}\n onChange={(v) => setField('size', v)}\n placeholder=\"M\"\n />\n </div>\n\n <TextAreaField\n id=\"notes\"\n label=\"Notes\"\n value={form.notes}\n onChange={(v) => setField('notes', v)}\n placeholder=\"Where you got it, how you wear it…\"\n rows={3}\n className=\"mt-4\"\n />\n\n <TextField\n id=\"tags\"\n label=\"Tags\"\n value={form.tags}\n onChange={(v) => setField('tags', v)}\n placeholder=\"denim, layering\"\n hint=\"Comma-separated\"\n className=\"mt-4\"\n />\n\n <ImageUploadField\n label=\"Photo\"\n value={form.imageFileId}\n onChange={(id) => setField('imageFileId', id)}\n folder={COO_FILE_FOLDERS.wardrobe}\n hint=\"Your photo of the piece (private to you)\"\n className=\"mt-4\"\n />\n\n {createItem.isError && (\n <p className=\"mt-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {createItem.error instanceof Error\n ? createItem.error.message\n : 'Something went wrong'}\n </p>\n )}\n\n <div className=\"mt-6 flex justify-end gap-2\">\n <button\n type=\"submit\"\n disabled={createItem.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 {createItem.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n Add to wardrobe\n </button>\n </div>\n </GlassCard>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|