@burdenoff/microfe-coolandlovely 2026.706.1 → 2026.706.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/dist/coolandlovely/CoolAndLovelyRoot.js +62 -46
  2. package/dist/coolandlovely/CoolAndLovelyRoot.js.map +1 -1
  3. package/dist/coolandlovely/CoolAndLovelyRoutes.js +14 -6
  4. package/dist/coolandlovely/CoolAndLovelyRoutes.js.map +1 -1
  5. package/dist/coolandlovely/components/ActorName.js +29 -0
  6. package/dist/coolandlovely/components/ActorName.js.map +1 -0
  7. package/dist/coolandlovely/components/AddToLookbookButton.js +81 -0
  8. package/dist/coolandlovely/components/AddToLookbookButton.js.map +1 -0
  9. package/dist/coolandlovely/components/BrandCover.js +39 -0
  10. package/dist/coolandlovely/components/BrandCover.js.map +1 -0
  11. package/dist/coolandlovely/components/BrandCreatorAvatars.js +57 -0
  12. package/dist/coolandlovely/components/BrandCreatorAvatars.js.map +1 -0
  13. package/dist/coolandlovely/components/ColourSwatchRow.js +86 -0
  14. package/dist/coolandlovely/components/ColourSwatchRow.js.map +1 -0
  15. package/dist/coolandlovely/components/Comments.js +109 -0
  16. package/dist/coolandlovely/components/Comments.js.map +1 -0
  17. package/dist/coolandlovely/components/Field.js +22 -2
  18. package/dist/coolandlovely/components/Field.js.map +1 -1
  19. package/dist/coolandlovely/components/FileImage.js +17 -0
  20. package/dist/coolandlovely/components/FileImage.js.map +1 -0
  21. package/dist/coolandlovely/components/FollowButton.js +24 -0
  22. package/dist/coolandlovely/components/FollowButton.js.map +1 -0
  23. package/dist/coolandlovely/components/ImageUploadField.js +153 -0
  24. package/dist/coolandlovely/components/ImageUploadField.js.map +1 -0
  25. package/dist/coolandlovely/components/LookCard.js +77 -0
  26. package/dist/coolandlovely/components/LookCard.js.map +1 -0
  27. package/dist/coolandlovely/components/MetricsCard.js +1 -1
  28. package/dist/coolandlovely/components/MetricsCard.js.map +1 -1
  29. package/dist/coolandlovely/components/ShopItemCard.js +45 -0
  30. package/dist/coolandlovely/components/ShopItemCard.js.map +1 -0
  31. package/dist/coolandlovely/components/StepTracker.js +35 -0
  32. package/dist/coolandlovely/components/StepTracker.js.map +1 -0
  33. package/dist/coolandlovely/components/Thumbnail.js +25 -14
  34. package/dist/coolandlovely/components/Thumbnail.js.map +1 -1
  35. package/dist/coolandlovely/components/VisibilityChip.js +32 -0
  36. package/dist/coolandlovely/components/VisibilityChip.js.map +1 -0
  37. package/dist/coolandlovely/hooks/index.js +2 -0
  38. package/dist/coolandlovely/hooks/useComments.js +68 -0
  39. package/dist/coolandlovely/hooks/useComments.js.map +1 -0
  40. package/dist/coolandlovely/hooks/useCoolAndLovelyApi.js +155 -60
  41. package/dist/coolandlovely/hooks/useCoolAndLovelyApi.js.map +1 -1
  42. package/dist/coolandlovely/hooks/useFiles.js +111 -0
  43. package/dist/coolandlovely/hooks/useFiles.js.map +1 -0
  44. package/dist/coolandlovely/pages/HomePage.js +130 -133
  45. package/dist/coolandlovely/pages/HomePage.js.map +1 -1
  46. package/dist/coolandlovely/pages/brands/BrandDetailPage.js +261 -228
  47. package/dist/coolandlovely/pages/brands/BrandDetailPage.js.map +1 -1
  48. package/dist/coolandlovely/pages/brands/BrandFormPage.js +146 -104
  49. package/dist/coolandlovely/pages/brands/BrandFormPage.js.map +1 -1
  50. package/dist/coolandlovely/pages/brands/BrandsListPage.js +13 -11
  51. package/dist/coolandlovely/pages/brands/BrandsListPage.js.map +1 -1
  52. package/dist/coolandlovely/pages/collaborations/CollaborationDetailPage.js +117 -95
  53. package/dist/coolandlovely/pages/collaborations/CollaborationDetailPage.js.map +1 -1
  54. package/dist/coolandlovely/pages/collaborations/CollaborationFormPage.js +1 -1
  55. package/dist/coolandlovely/pages/collaborations/CollaborationsListPage.js +50 -43
  56. package/dist/coolandlovely/pages/collaborations/CollaborationsListPage.js.map +1 -1
  57. package/dist/coolandlovely/pages/collections/CollectionDetailPage.js +134 -107
  58. package/dist/coolandlovely/pages/collections/CollectionDetailPage.js.map +1 -1
  59. package/dist/coolandlovely/pages/collections/CollectionFormPage.js +117 -102
  60. package/dist/coolandlovely/pages/collections/CollectionFormPage.js.map +1 -1
  61. package/dist/coolandlovely/pages/collections/CollectionsListPage.js +59 -54
  62. package/dist/coolandlovely/pages/collections/CollectionsListPage.js.map +1 -1
  63. package/dist/coolandlovely/pages/discover/DiscoverPage.js +223 -0
  64. package/dist/coolandlovely/pages/discover/DiscoverPage.js.map +1 -0
  65. package/dist/coolandlovely/pages/items/StyleItemDetailPage.js +176 -144
  66. package/dist/coolandlovely/pages/items/StyleItemDetailPage.js.map +1 -1
  67. package/dist/coolandlovely/pages/items/StyleItemFormPage.js +144 -130
  68. package/dist/coolandlovely/pages/items/StyleItemFormPage.js.map +1 -1
  69. package/dist/coolandlovely/pages/items/StyleItemsListPage.js +73 -62
  70. package/dist/coolandlovely/pages/items/StyleItemsListPage.js.map +1 -1
  71. package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js +215 -102
  72. package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js.map +1 -1
  73. package/dist/coolandlovely/pages/lookbooks/LookbooksListPage.js +115 -86
  74. package/dist/coolandlovely/pages/lookbooks/LookbooksListPage.js.map +1 -1
  75. package/dist/coolandlovely/pages/looks/LookDetailPage.js +217 -155
  76. package/dist/coolandlovely/pages/looks/LookDetailPage.js.map +1 -1
  77. package/dist/coolandlovely/pages/looks/LookFormPage.js +147 -96
  78. package/dist/coolandlovely/pages/looks/LookFormPage.js.map +1 -1
  79. package/dist/coolandlovely/pages/looks/LooksListPage.js +60 -95
  80. package/dist/coolandlovely/pages/looks/LooksListPage.js.map +1 -1
  81. package/dist/coolandlovely/pages/profile/StyleProfilePage.js +376 -146
  82. package/dist/coolandlovely/pages/profile/StyleProfilePage.js.map +1 -1
  83. package/dist/coolandlovely/pages/trends/TrendsPage.js +249 -0
  84. package/dist/coolandlovely/pages/trends/TrendsPage.js.map +1 -0
  85. package/dist/coolandlovely/pages/wardrobe/WardrobeFormPage.js +136 -88
  86. package/dist/coolandlovely/pages/wardrobe/WardrobeFormPage.js.map +1 -1
  87. package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.js +75 -74
  88. package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.js.map +1 -1
  89. package/package.json +1 -1
@@ -1,21 +1,36 @@
1
1
  import { useCoolAndLovely as e } from "../../context/CoolAndLovelyContext.js";
2
- import { PageLayout as t } from "../../components/PageLayout.js";
3
- import { useCreateWardrobeItem as n } from "../../hooks/useCoolAndLovelyApi.js";
2
+ import { cn as t } from "../../utils/cn.js";
3
+ import { PageLayout as n } from "../../components/PageLayout.js";
4
+ import { COO_FILE_FOLDERS as r } from "../../hooks/useFiles.js";
5
+ import { useCreateWardrobeItem as i, useStyleItems as a } from "../../hooks/useCoolAndLovelyApi.js";
6
+ import { parseCsv as o, titleCase as s } from "../../utils/formatters.js";
4
7
  import "../../hooks/index.js";
5
- import { parseCsv as r, titleCase as i } from "../../utils/formatters.js";
6
- import { STYLE_CATEGORIES as a } from "../../utils/icons.js";
7
- import { SelectField as o, TextAreaField as s, TextField as c } from "../../components/Field.js";
8
- import { useState as l } from "react";
9
- import { ArrowLeft as u, Heart as d, Loader2 as f, Save as p } from "lucide-react";
10
- import { jsx as m, jsxs as h } from "react/jsx-runtime";
11
- import { GlassCard as g } from "@burdenoff/fe-libs/ui";
8
+ import { STYLE_CATEGORIES as c } from "../../utils/icons.js";
9
+ import { SelectField as l, TextAreaField as u, TextField as d } from "../../components/Field.js";
10
+ import { ImageUploadField as f } from "../../components/ImageUploadField.js";
11
+ import { useMemo as p, useState as m } from "react";
12
+ import { ArrowLeft as h, Heart as g, Loader2 as _, Save as v } from "lucide-react";
13
+ import { jsx as y, jsxs as b } from "react/jsx-runtime";
14
+ import { GlassCard as x } from "@burdenoff/fe-libs/ui";
12
15
  //#region src/coolandlovely/pages/wardrobe/WardrobeFormPage.tsx
13
- var _ = [
14
- "OWNED",
15
- "WISHLIST",
16
- "PURCHASED_ON_PLATFORM",
17
- "GIFTED"
18
- ], v = {
16
+ var S = [
17
+ {
18
+ value: "OWNED",
19
+ label: "Owned"
20
+ },
21
+ {
22
+ value: "WISHLIST",
23
+ label: "Wishlist"
24
+ },
25
+ {
26
+ value: "PURCHASED_ON_PLATFORM",
27
+ label: "Purchased"
28
+ },
29
+ {
30
+ value: "GIFTED",
31
+ label: "Gifted"
32
+ }
33
+ ], C = {
19
34
  name: "",
20
35
  category: "OTHER",
21
36
  source: "OWNED",
@@ -23,143 +38,176 @@ var _ = [
23
38
  colour: "",
24
39
  size: "",
25
40
  notes: "",
26
- tags: ""
41
+ tags: "",
42
+ styleItemId: "",
43
+ imageFileId: ""
27
44
  };
28
- function y() {
29
- let { navigateTo: y } = e(), b = n(), [x, S] = l(v), [C, w] = l(), T = (e, t) => S((n) => ({
45
+ function w() {
46
+ let { navigateTo: w } = e(), T = i(), E = a({ pagination: {
47
+ limit: 100,
48
+ offset: 0
49
+ } }), [D, O] = m(C), [k, A] = m(), j = (e, t) => O((n) => ({
30
50
  ...n,
31
51
  [e]: t
32
- })), E = a.map((e) => ({
52
+ })), M = c.map((e) => ({
33
53
  value: e,
34
- label: i(e)
35
- })), D = _.map((e) => ({
36
- value: e,
37
- label: i(e)
38
- }));
39
- return /* @__PURE__ */ m(t, {
54
+ label: s(e)
55
+ })), N = p(() => (E.data?.items ?? []).filter((e) => e.status !== "RETIRED"), [E.data]), P = p(() => N.map((e) => ({
56
+ value: e.id,
57
+ label: `${e.name}${e.brand?.name ? ` · ${e.brand.name}` : ""}`
58
+ })), [N]);
59
+ return /* @__PURE__ */ y(n, {
40
60
  title: "Add a wardrobe piece",
41
61
  description: "Catalogue something you own or want",
42
- actions: /* @__PURE__ */ h("button", {
62
+ actions: /* @__PURE__ */ b("button", {
43
63
  type: "button",
44
- onClick: () => y("/wardrobe"),
64
+ onClick: () => w("/wardrobe"),
45
65
  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__ */ m(u, { className: "h-4 w-4" }), " Cancel"]
66
+ children: [/* @__PURE__ */ y(h, { className: "h-4 w-4" }), " Cancel"]
47
67
  }),
48
- children: /* @__PURE__ */ m("form", {
68
+ children: /* @__PURE__ */ y("form", {
49
69
  onSubmit: (e) => {
50
- if (e.preventDefault(), !x.name.trim()) {
51
- w("Name is required");
70
+ if (e.preventDefault(), !D.name.trim()) {
71
+ A("Name is required");
52
72
  return;
53
73
  }
54
- w(void 0), b.mutate({
55
- name: x.name.trim(),
56
- category: x.category,
57
- source: x.source,
58
- brandName: x.brandName.trim() || void 0,
59
- colour: x.colour.trim() || void 0,
60
- size: x.size.trim() || void 0,
61
- notes: x.notes.trim() || void 0,
62
- tags: r(x.tags)
63
- }, { onSuccess: () => y("/wardrobe") });
74
+ A(void 0), T.mutate({
75
+ name: D.name.trim(),
76
+ category: D.category,
77
+ source: D.source,
78
+ brandName: D.brandName.trim() || void 0,
79
+ colour: D.colour.trim() || void 0,
80
+ size: D.size.trim() || void 0,
81
+ notes: D.notes.trim() || void 0,
82
+ tags: o(D.tags),
83
+ styleItemId: D.styleItemId || void 0,
84
+ imageFileId: D.imageFileId || void 0
85
+ }, { onSuccess: () => w("/wardrobe") });
64
86
  },
65
87
  className: "mx-auto max-w-2xl",
66
- children: /* @__PURE__ */ h(g, {
88
+ children: /* @__PURE__ */ b(x, {
67
89
  treatment: "plain",
68
90
  className: "p-6",
69
91
  children: [
70
- /* @__PURE__ */ h("div", {
92
+ /* @__PURE__ */ b("div", {
71
93
  className: "mb-6 flex items-center gap-3",
72
- children: [/* @__PURE__ */ m("div", {
94
+ children: [/* @__PURE__ */ y("div", {
73
95
  className: "flex h-11 w-11 items-center justify-center rounded-lg bg-status-error-bg-subtle text-status-error-text",
74
- children: /* @__PURE__ */ m(d, { className: "h-5 w-5" })
75
- }), /* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("h2", {
96
+ children: /* @__PURE__ */ y(g, { className: "h-5 w-5" })
97
+ }), /* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("h2", {
76
98
  className: "text-base font-semibold text-text-primary",
77
99
  children: "Piece details"
78
- }), /* @__PURE__ */ m("p", {
100
+ }), /* @__PURE__ */ y("p", {
79
101
  className: "text-xs text-text-secondary",
80
102
  children: "Only you can see your wardrobe."
81
103
  })] })]
82
104
  }),
83
- /* @__PURE__ */ m(c, {
105
+ /* @__PURE__ */ y(d, {
84
106
  id: "name",
85
107
  label: "Name",
86
- value: x.name,
87
- onChange: (e) => T("name", e),
108
+ value: D.name,
109
+ onChange: (e) => j("name", e),
88
110
  placeholder: "Vintage denim jacket",
89
111
  required: !0,
90
- error: C
112
+ error: k
113
+ }),
114
+ /* @__PURE__ */ y(l, {
115
+ id: "category",
116
+ label: "Category",
117
+ value: D.category,
118
+ onChange: (e) => j("category", e),
119
+ options: M,
120
+ className: "mt-4"
91
121
  }),
92
- /* @__PURE__ */ h("div", {
93
- className: "mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2",
94
- children: [/* @__PURE__ */ m(o, {
95
- id: "category",
96
- label: "Category",
97
- value: x.category,
98
- onChange: (e) => T("category", e),
99
- options: E
100
- }), /* @__PURE__ */ m(o, {
101
- id: "source",
102
- label: "Source",
103
- value: x.source,
104
- onChange: (e) => T("source", e),
105
- options: D
122
+ /* @__PURE__ */ b("div", {
123
+ className: "mt-4",
124
+ children: [/* @__PURE__ */ y("span", {
125
+ className: "mb-1.5 block text-sm font-medium text-text-primary",
126
+ children: "Source"
127
+ }), /* @__PURE__ */ y("div", {
128
+ className: "flex flex-wrap gap-2",
129
+ children: S.map((e) => /* @__PURE__ */ y("button", {
130
+ type: "button",
131
+ onClick: () => j("source", e.value),
132
+ "aria-pressed": D.source === e.value,
133
+ className: t("rounded-full px-3 py-1.5 text-sm font-medium transition-colors", D.source === e.value ? "bg-action-primary-bg text-action-primary-text" : "bg-bg-sunken text-text-secondary hover:text-text-primary"),
134
+ children: e.label
135
+ }, e.value))
106
136
  })]
107
137
  }),
108
- /* @__PURE__ */ h("div", {
138
+ /* @__PURE__ */ b("div", {
109
139
  className: "mt-4 grid grid-cols-1 gap-4 sm:grid-cols-3",
110
140
  children: [
111
- /* @__PURE__ */ m(c, {
141
+ /* @__PURE__ */ y(d, {
112
142
  id: "brandName",
113
143
  label: "Brand",
114
- value: x.brandName,
115
- onChange: (e) => T("brandName", e),
144
+ value: D.brandName,
145
+ onChange: (e) => j("brandName", e),
116
146
  placeholder: "Levi's"
117
147
  }),
118
- /* @__PURE__ */ m(c, {
148
+ /* @__PURE__ */ y(d, {
119
149
  id: "colour",
120
150
  label: "Colour",
121
- value: x.colour,
122
- onChange: (e) => T("colour", e),
151
+ value: D.colour,
152
+ onChange: (e) => j("colour", e),
123
153
  placeholder: "Indigo"
124
154
  }),
125
- /* @__PURE__ */ m(c, {
155
+ /* @__PURE__ */ y(d, {
126
156
  id: "size",
127
157
  label: "Size",
128
- value: x.size,
129
- onChange: (e) => T("size", e),
158
+ value: D.size,
159
+ onChange: (e) => j("size", e),
130
160
  placeholder: "M"
131
161
  })
132
162
  ]
133
163
  }),
134
- /* @__PURE__ */ m(s, {
164
+ /* @__PURE__ */ y(l, {
165
+ id: "styleItemId",
166
+ label: "Link to a style item (optional)",
167
+ value: D.styleItemId,
168
+ onChange: (e) => j("styleItemId", e),
169
+ options: P,
170
+ placeholder: E.isLoading ? "Loading catalogue…" : "None — describe it yourself above",
171
+ hint: "Connect this piece to a catalogue item to keep its brand and imagery in sync.",
172
+ className: "mt-4"
173
+ }),
174
+ /* @__PURE__ */ y(u, {
135
175
  id: "notes",
136
176
  label: "Notes",
137
- value: x.notes,
138
- onChange: (e) => T("notes", e),
177
+ value: D.notes,
178
+ onChange: (e) => j("notes", e),
139
179
  placeholder: "Where you got it, how you wear it…",
140
180
  rows: 3,
141
181
  className: "mt-4"
142
182
  }),
143
- /* @__PURE__ */ m(c, {
183
+ /* @__PURE__ */ y(d, {
144
184
  id: "tags",
145
185
  label: "Tags",
146
- value: x.tags,
147
- onChange: (e) => T("tags", e),
186
+ value: D.tags,
187
+ onChange: (e) => j("tags", e),
148
188
  placeholder: "denim, layering",
149
189
  hint: "Comma-separated",
150
190
  className: "mt-4"
151
191
  }),
152
- b.isError && /* @__PURE__ */ m("p", {
192
+ /* @__PURE__ */ y(f, {
193
+ label: "Photo",
194
+ value: D.imageFileId,
195
+ onChange: (e) => j("imageFileId", e),
196
+ folder: r.wardrobe,
197
+ hint: "Your photo of the piece (private to you)",
198
+ className: "mt-4"
199
+ }),
200
+ T.isError && /* @__PURE__ */ y("p", {
153
201
  className: "mt-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
154
- children: b.error instanceof Error ? b.error.message : "Something went wrong"
202
+ children: T.error instanceof Error ? T.error.message : "Something went wrong"
155
203
  }),
156
- /* @__PURE__ */ m("div", {
204
+ /* @__PURE__ */ y("div", {
157
205
  className: "mt-6 flex justify-end gap-2",
158
- children: /* @__PURE__ */ h("button", {
206
+ children: /* @__PURE__ */ b("button", {
159
207
  type: "submit",
160
- disabled: b.isPending,
208
+ disabled: T.isPending,
161
209
  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: [b.isPending ? /* @__PURE__ */ m(f, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ m(p, { className: "h-4 w-4" }), "Add to wardrobe"]
210
+ children: [T.isPending ? /* @__PURE__ */ y(_, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ y(v, { className: "h-4 w-4" }), "Add to wardrobe"]
163
211
  })
164
212
  })
165
213
  ]
@@ -168,6 +216,6 @@ function y() {
168
216
  });
169
217
  }
170
218
  //#endregion
171
- export { y as WardrobeFormPage };
219
+ export { w as WardrobeFormPage };
172
220
 
173
221
  //# 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. A\n * piece can optionally be linked to a catalogue style item (`styleItemId`), and\n * its source is chosen with a segmented control.\n */\n\nimport { useMemo, 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, useStyleItems } from '../../hooks';\nimport { COO_FILE_FOLDERS } from '../../hooks/useFiles';\nimport { STYLE_CATEGORIES } from '../../utils/icons';\nimport { parseCsv, titleCase } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport type { CooStyleCategory, CooWardrobeSource } from '../../types';\n\nconst SOURCES: Array<{ value: CooWardrobeSource; label: string }> = [\n { value: 'OWNED', label: 'Owned' },\n { value: 'WISHLIST', label: 'Wishlist' },\n { value: 'PURCHASED_ON_PLATFORM', label: 'Purchased' },\n { value: 'GIFTED', label: 'Gifted' },\n];\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 styleItemId: 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 styleItemId: '',\n imageFileId: '',\n};\n\nexport function WardrobeFormPage() {\n const { navigateTo } = useCoolAndLovely();\n const createItem = useCreateWardrobeItem();\n const styleItems = useStyleItems({ pagination: { limit: 100, offset: 0 } });\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\n const usableStyleItems = useMemo(\n () => (styleItems.data?.items ?? []).filter((i) => i.status !== 'RETIRED'),\n [styleItems.data]\n );\n\n const styleItemOptions = useMemo(\n () =>\n usableStyleItems.map((i) => ({\n value: i.id,\n label: `${i.name}${i.brand?.name ? ` · ${i.brand.name}` : ''}`,\n })),\n [usableStyleItems]\n );\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 styleItemId: form.styleItemId || undefined,\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 <SelectField\n id=\"category\"\n label=\"Category\"\n value={form.category}\n onChange={(v) => setField('category', v as CooStyleCategory)}\n options={categoryOptions}\n className=\"mt-4\"\n />\n\n {/* Segmented source control */}\n <div className=\"mt-4\">\n <span className=\"mb-1.5 block text-sm font-medium text-text-primary\">Source</span>\n <div className=\"flex flex-wrap gap-2\">\n {SOURCES.map((opt) => (\n <button\n key={opt.value}\n type=\"button\"\n onClick={() => setField('source', opt.value)}\n aria-pressed={form.source === opt.value}\n className={cn(\n 'rounded-full px-3 py-1.5 text-sm font-medium transition-colors',\n form.source === 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 </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 <SelectField\n id=\"styleItemId\"\n label=\"Link to a style item (optional)\"\n value={form.styleItemId}\n onChange={(v) => setField('styleItemId', v)}\n options={styleItemOptions}\n placeholder={\n styleItems.isLoading ? 'Loading catalogue…' : 'None — describe it yourself above'\n }\n hint=\"Connect this piece to a catalogue item to keep its brand and imagery in sync.\"\n className=\"mt-4\"\n />\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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"}
@@ -5,8 +5,8 @@ import { StatusBadge as r } from "../../components/StatusBadge.js";
5
5
  import { ErrorView as i, LoadingView as a } from "../../components/StateViews.js";
6
6
  import { Thumbnail as o } from "../../components/Thumbnail.js";
7
7
  import { useLogWardrobeWear as s, useRemoveWardrobeItem as c, useWardrobeItems as l } from "../../hooks/useCoolAndLovelyApi.js";
8
- import "../../hooks/index.js";
9
8
  import { formatRelativeTime as u, titleCase as d } from "../../utils/formatters.js";
9
+ import "../../hooks/index.js";
10
10
  import { STYLE_CATEGORIES as f, categoryIcon as p } from "../../utils/icons.js";
11
11
  import { EmptyState as m } from "../../components/EmptyState.js";
12
12
  import { useState as h } from "react";
@@ -89,79 +89,80 @@ function w() {
89
89
  children: [/* @__PURE__ */ x(y, { className: "h-4 w-4" }), " Add piece"]
90
90
  })
91
91
  }) : /* @__PURE__ */ x("div", {
92
- className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
93
- children: M.map((e) => /* @__PURE__ */ S("div", {
94
- className: "flex flex-col overflow-hidden rounded-lg border border-border-seam bg-bg-surface shadow-sm",
95
- children: [
96
- /* @__PURE__ */ S("div", {
97
- className: "flex items-start gap-3 p-4",
98
- children: [/* @__PURE__ */ x(o, {
99
- icon: p(e.category, 22),
100
- seed: e.id,
101
- className: "h-14 w-14 shrink-0"
102
- }), /* @__PURE__ */ S("div", {
103
- className: "min-w-0 flex-1",
104
- children: [
105
- /* @__PURE__ */ x("p", {
106
- className: "truncate text-sm font-semibold text-text-primary",
107
- children: e.name
108
- }),
109
- e.brandName && /* @__PURE__ */ x("p", {
110
- className: "truncate text-xs text-text-secondary",
111
- children: e.brandName
112
- }),
113
- /* @__PURE__ */ S("div", {
114
- className: "mt-1.5 flex flex-wrap items-center gap-1.5",
115
- children: [
116
- /* @__PURE__ */ x(r, { status: e.source }),
117
- e.colour && /* @__PURE__ */ x("span", {
118
- className: "rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary",
119
- children: e.colour
120
- }),
121
- e.size && /* @__PURE__ */ x("span", {
122
- className: "rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary",
123
- children: e.size
124
- })
125
- ]
126
- })
127
- ]
128
- })]
129
- }),
130
- e.notes && /* @__PURE__ */ x("p", {
131
- className: "px-4 pb-2 text-xs text-text-secondary line-clamp-2",
132
- children: e.notes
133
- }),
134
- /* @__PURE__ */ S("div", {
135
- className: "mt-auto flex items-center justify-between border-t border-border-seam px-4 py-2.5",
136
- children: [/* @__PURE__ */ S("span", {
137
- className: "text-xs text-text-secondary",
138
- children: [
139
- "Worn ",
140
- e.timesWorn,
141
- "×",
142
- e.lastWornAt ? ` · ${u(e.lastWornAt)}` : ""
143
- ]
144
- }), /* @__PURE__ */ S("div", {
145
- className: "flex items-center gap-1",
146
- children: [/* @__PURE__ */ S("button", {
147
- type: "button",
148
- disabled: A.isPending,
149
- onClick: () => A.mutate(e.id),
150
- "aria-label": "Log a wear",
151
- className: "inline-flex items-center gap-1 rounded-lg px-2 py-1 text-xs font-medium text-status-success-text transition-colors hover:bg-status-success-bg-subtle disabled:opacity-50",
152
- children: [A.isPending && A.variables === e.id ? /* @__PURE__ */ x(v, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ x(g, { className: "h-3.5 w-3.5" }), "Wore it"]
153
- }), /* @__PURE__ */ x("button", {
154
- type: "button",
155
- disabled: j.isPending,
156
- onClick: () => j.mutate(e.id),
157
- "aria-label": "Remove piece",
158
- className: "inline-flex h-7 w-7 items-center justify-center rounded-lg text-text-secondary transition-colors hover:bg-status-error-bg-subtle hover:text-status-error-text disabled:opacity-50",
159
- children: /* @__PURE__ */ x(b, { className: "h-3.5 w-3.5" })
160
- })]
161
- })]
162
- })
163
- ]
164
- }, e.id))
92
+ className: "grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4",
93
+ children: M.map((e) => {
94
+ let t = e.brandName || e.colour || d(e.category), n = A.isPending && A.variables === e.id, i = j.isPending && j.variables === e.id;
95
+ return /* @__PURE__ */ S("div", {
96
+ className: "group flex flex-col overflow-hidden rounded-xl border border-border-seam bg-bg-surface shadow-sm transition-shadow hover:shadow-lg",
97
+ children: [/* @__PURE__ */ S("div", {
98
+ className: "relative",
99
+ children: [
100
+ /* @__PURE__ */ x(o, {
101
+ icon: p(e.category, 40),
102
+ seed: e.id,
103
+ fileId: e.image?.id,
104
+ alt: e.name,
105
+ className: "aspect-[4/5] w-full rounded-none"
106
+ }),
107
+ /* @__PURE__ */ S("div", {
108
+ className: "absolute right-2.5 top-2.5 flex h-12 w-12 flex-col items-center justify-center rounded-full bg-bg-surface/90 text-center leading-none shadow-sm ring-1 ring-[var(--color-accent-soft)] backdrop-blur",
109
+ children: [/* @__PURE__ */ x("span", {
110
+ className: "text-[9px] font-medium uppercase tracking-wide text-text-secondary",
111
+ children: "worn"
112
+ }), /* @__PURE__ */ S("span", {
113
+ className: "text-sm font-bold tabular-nums text-text-primary",
114
+ children: [e.timesWorn, "×"]
115
+ })]
116
+ }),
117
+ /* @__PURE__ */ S("div", {
118
+ className: "absolute inset-x-0 bottom-0 flex items-center gap-1.5 bg-gradient-to-t from-bg-canvas/90 to-transparent p-2.5 pt-8 opacity-100 transition-opacity duration-200 sm:opacity-0 sm:focus-within:opacity-100 sm:group-hover:opacity-100",
119
+ children: [/* @__PURE__ */ S("button", {
120
+ type: "button",
121
+ disabled: A.isPending,
122
+ onClick: () => A.mutate(e.id),
123
+ "aria-label": `Log a wear for ${e.name}`,
124
+ className: "inline-flex items-center gap-1 rounded-lg bg-bg-surface/90 px-2.5 py-1 text-xs font-medium text-status-success-text shadow-sm backdrop-blur transition-colors hover:bg-bg-surface disabled:opacity-50",
125
+ children: [n ? /* @__PURE__ */ x(v, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ x(g, { className: "h-3.5 w-3.5" }), "Wore it"]
126
+ }), /* @__PURE__ */ x("button", {
127
+ type: "button",
128
+ disabled: j.isPending,
129
+ onClick: () => j.mutate(e.id),
130
+ "aria-label": `Remove ${e.name}`,
131
+ className: "ml-auto inline-flex h-7 w-7 items-center justify-center rounded-lg bg-bg-surface/90 text-text-secondary shadow-sm backdrop-blur transition-colors hover:text-status-error-text disabled:opacity-50",
132
+ children: i ? /* @__PURE__ */ x(v, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ x(b, { className: "h-3.5 w-3.5" })
133
+ })]
134
+ })
135
+ ]
136
+ }), /* @__PURE__ */ S("div", {
137
+ className: "flex flex-1 flex-col p-4",
138
+ children: [
139
+ /* @__PURE__ */ x("p", {
140
+ className: "truncate text-sm font-semibold text-text-primary",
141
+ children: e.name
142
+ }),
143
+ /* @__PURE__ */ x("p", {
144
+ className: "truncate text-xs text-text-secondary",
145
+ children: t
146
+ }),
147
+ /* @__PURE__ */ S("div", {
148
+ className: "mt-2 flex flex-wrap items-center gap-1.5",
149
+ children: [/* @__PURE__ */ x(r, { status: e.source }), e.size && /* @__PURE__ */ x("span", {
150
+ className: "rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary",
151
+ children: e.size
152
+ })]
153
+ }),
154
+ e.notes && /* @__PURE__ */ x("p", {
155
+ className: "mt-2 line-clamp-2 text-xs text-text-secondary",
156
+ children: e.notes
157
+ }),
158
+ /* @__PURE__ */ x("div", {
159
+ className: "mt-3 border-t border-border-seam pt-2 text-xs text-text-secondary",
160
+ children: e.lastWornAt ? `Last worn ${u(e.lastWornAt)}` : "Not worn yet"
161
+ })
162
+ ]
163
+ })]
164
+ }, e.id);
165
+ })
165
166
  })
166
167
  ]
167
168
  });
@@ -1 +1 @@
1
- {"version":3,"file":"WardrobeListPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/wardrobe/WardrobeListPage.tsx"],"sourcesContent":["/**\n * Wardrobe List Page (self-scoped)\n * @module coolandlovely/pages/wardrobe\n *\n * The shopper's digital wardrobe (`cooListWardrobeItems`, self-scoped) with\n * category & source filters, wear logging (`cooLogWardrobeWear`) and removal\n * (`cooRemoveWardrobeItem`).\n */\n\nimport { useState } from 'react';\nimport { Plus, Heart, CheckCircle2, Trash2, Loader2 } 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 { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useWardrobeItems, useLogWardrobeWear, useRemoveWardrobeItem } from '../../hooks';\nimport { categoryIcon, STYLE_CATEGORIES } from '../../utils/icons';\nimport { titleCase, formatRelativeTime } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport type { CooStyleCategory, CooWardrobeSource } from '../../types';\n\nconst SOURCES: Array<{ value: CooWardrobeSource | 'ALL'; label: string }> = [\n { value: 'ALL', label: 'All' },\n { value: 'OWNED', label: 'Owned' },\n { value: 'WISHLIST', label: 'Wishlist' },\n { value: 'PURCHASED_ON_PLATFORM', label: 'Purchased' },\n { value: 'GIFTED', label: 'Gifted' },\n];\n\nexport function WardrobeListPage() {\n const { navigateTo } = useCoolAndLovely();\n const [category, setCategory] = useState<CooStyleCategory | 'ALL'>('ALL');\n const [source, setSource] = useState<CooWardrobeSource | 'ALL'>('ALL');\n\n const query = useWardrobeItems({\n category: category === 'ALL' ? undefined : category,\n source: source === 'ALL' ? undefined : source,\n });\n const logWear = useLogWardrobeWear();\n const remove = useRemoveWardrobeItem();\n\n const items = query.data ?? [];\n\n return (\n <PageLayout\n title=\"My Wardrobe\"\n description=\"A private catalogue of what you own — with a wear log\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/wardrobe/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\" /> Add piece\n </button>\n }\n >\n {/* Source filter */}\n <div className=\"mb-3 flex flex-wrap items-center gap-2\">\n {SOURCES.map((opt) => (\n <button\n key={opt.value}\n type=\"button\"\n onClick={() => setSource(opt.value)}\n className={cn(\n 'rounded-full px-3 py-1.5 text-sm font-medium transition-colors',\n source === 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 {/* Category filter */}\n <div className=\"mb-6 flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setCategory('ALL')}\n className={cn(\n 'rounded-full px-3 py-1.5 text-xs font-medium transition-colors',\n category === 'ALL'\n ? 'bg-accent-purple-subtle text-text-primary'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n All categories\n </button>\n {STYLE_CATEGORIES.map((c) => (\n <button\n key={c}\n type=\"button\"\n onClick={() => setCategory(c)}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-3 py-1.5 text-xs font-medium transition-colors',\n category === c\n ? 'bg-accent-purple-subtle text-text-primary'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n {categoryIcon(c, 14)} {titleCase(c)}\n </button>\n ))}\n </div>\n\n {query.isLoading ? (\n <LoadingView label=\"Loading your wardrobe…\" />\n ) : query.isError ? (\n <ErrorView onRetry={() => void query.refetch()} />\n ) : items.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Heart className=\"h-10 w-10\" />}\n title=\"Your wardrobe is empty\"\n description=\"Add the pieces you already own (or want) to start your digital closet.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/wardrobe/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\" /> Add piece\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {items.map((item) => (\n <div\n key={item.id}\n className=\"flex flex-col overflow-hidden rounded-lg border border-border-seam bg-bg-surface shadow-sm\"\n >\n <div className=\"flex items-start gap-3 p-4\">\n <Thumbnail\n icon={categoryIcon(item.category, 22)}\n seed={item.id}\n className=\"h-14 w-14 shrink-0\"\n />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">{item.name}</p>\n {item.brandName && (\n <p className=\"truncate text-xs text-text-secondary\">{item.brandName}</p>\n )}\n <div className=\"mt-1.5 flex flex-wrap items-center gap-1.5\">\n <StatusBadge status={item.source} />\n {item.colour && (\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n {item.colour}\n </span>\n )}\n {item.size && (\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n {item.size}\n </span>\n )}\n </div>\n </div>\n </div>\n {item.notes && (\n <p className=\"px-4 pb-2 text-xs text-text-secondary line-clamp-2\">{item.notes}</p>\n )}\n <div className=\"mt-auto flex items-center justify-between border-t border-border-seam px-4 py-2.5\">\n <span className=\"text-xs text-text-secondary\">\n Worn {item.timesWorn}×\n {item.lastWornAt ? ` · ${formatRelativeTime(item.lastWornAt)}` : ''}\n </span>\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n disabled={logWear.isPending}\n onClick={() => logWear.mutate(item.id)}\n aria-label=\"Log a wear\"\n className=\"inline-flex items-center gap-1 rounded-lg px-2 py-1 text-xs font-medium text-status-success-text transition-colors hover:bg-status-success-bg-subtle disabled:opacity-50\"\n >\n {logWear.isPending && logWear.variables === item.id ? (\n <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />\n ) : (\n <CheckCircle2 className=\"h-3.5 w-3.5\" />\n )}\n Wore it\n </button>\n <button\n type=\"button\"\n disabled={remove.isPending}\n onClick={() => remove.mutate(item.id)}\n aria-label=\"Remove piece\"\n className=\"inline-flex h-7 w-7 items-center justify-center rounded-lg text-text-secondary transition-colors hover:bg-status-error-bg-subtle hover:text-status-error-text disabled:opacity-50\"\n >\n <Trash2 className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n </div>\n </div>\n ))}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"WardrobeListPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/wardrobe/WardrobeListPage.tsx"],"sourcesContent":["/**\n * Wardrobe List Page (self-scoped)\n * @module coolandlovely/pages/wardrobe\n *\n * The shopper's digital wardrobe (`cooListWardrobeItems`, self-scoped) rendered\n * as an image-forward gallery — each card is a large piece photo with a circular\n * \"Worn N×\" badge and a hover-revealed action bar (wear log via\n * `cooLogWardrobeWear`, removal via `cooRemoveWardrobeItem`), with category &\n * source filters.\n */\n\nimport { useState } from 'react';\nimport { Plus, Heart, CheckCircle2, Trash2, Loader2 } 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 { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useWardrobeItems, useLogWardrobeWear, useRemoveWardrobeItem } from '../../hooks';\nimport { categoryIcon, STYLE_CATEGORIES } from '../../utils/icons';\nimport { titleCase, formatRelativeTime } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport type { CooStyleCategory, CooWardrobeSource } from '../../types';\n\nconst SOURCES: Array<{ value: CooWardrobeSource | 'ALL'; label: string }> = [\n { value: 'ALL', label: 'All' },\n { value: 'OWNED', label: 'Owned' },\n { value: 'WISHLIST', label: 'Wishlist' },\n { value: 'PURCHASED_ON_PLATFORM', label: 'Purchased' },\n { value: 'GIFTED', label: 'Gifted' },\n];\n\nexport function WardrobeListPage() {\n const { navigateTo } = useCoolAndLovely();\n const [category, setCategory] = useState<CooStyleCategory | 'ALL'>('ALL');\n const [source, setSource] = useState<CooWardrobeSource | 'ALL'>('ALL');\n\n const query = useWardrobeItems({\n category: category === 'ALL' ? undefined : category,\n source: source === 'ALL' ? undefined : source,\n });\n const logWear = useLogWardrobeWear();\n const remove = useRemoveWardrobeItem();\n\n const items = query.data ?? [];\n\n return (\n <PageLayout\n title=\"My Wardrobe\"\n description=\"A private catalogue of what you own — with a wear log\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/wardrobe/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\" /> Add piece\n </button>\n }\n >\n {/* Source filter */}\n <div className=\"mb-3 flex flex-wrap items-center gap-2\">\n {SOURCES.map((opt) => (\n <button\n key={opt.value}\n type=\"button\"\n onClick={() => setSource(opt.value)}\n className={cn(\n 'rounded-full px-3 py-1.5 text-sm font-medium transition-colors',\n source === 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 {/* Category filter */}\n <div className=\"mb-6 flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setCategory('ALL')}\n className={cn(\n 'rounded-full px-3 py-1.5 text-xs font-medium transition-colors',\n category === 'ALL'\n ? 'bg-accent-purple-subtle text-text-primary'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n All categories\n </button>\n {STYLE_CATEGORIES.map((c) => (\n <button\n key={c}\n type=\"button\"\n onClick={() => setCategory(c)}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-3 py-1.5 text-xs font-medium transition-colors',\n category === c\n ? 'bg-accent-purple-subtle text-text-primary'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n {categoryIcon(c, 14)} {titleCase(c)}\n </button>\n ))}\n </div>\n\n {query.isLoading ? (\n <LoadingView label=\"Loading your wardrobe…\" />\n ) : query.isError ? (\n <ErrorView onRetry={() => void query.refetch()} />\n ) : items.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Heart className=\"h-10 w-10\" />}\n title=\"Your wardrobe is empty\"\n description=\"Add the pieces you already own (or want) to start your digital closet.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/wardrobe/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\" /> Add piece\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4\">\n {items.map((item) => {\n const subtitle = item.brandName || item.colour || titleCase(item.category);\n const wearing = logWear.isPending && logWear.variables === item.id;\n const removing = remove.isPending && remove.variables === item.id;\n return (\n <div\n key={item.id}\n className=\"group flex flex-col overflow-hidden rounded-xl border border-border-seam bg-bg-surface shadow-sm transition-shadow hover:shadow-lg\"\n >\n {/* Image-forward hero */}\n <div className=\"relative\">\n <Thumbnail\n icon={categoryIcon(item.category, 40)}\n seed={item.id}\n fileId={item.image?.id}\n alt={item.name}\n className=\"aspect-[4/5] w-full rounded-none\"\n />\n\n {/* Worn-count badge */}\n <div className=\"absolute right-2.5 top-2.5 flex h-12 w-12 flex-col items-center justify-center rounded-full bg-bg-surface/90 text-center leading-none shadow-sm ring-1 ring-[var(--color-accent-soft)] backdrop-blur\">\n <span className=\"text-[9px] font-medium uppercase tracking-wide text-text-secondary\">\n worn\n </span>\n <span className=\"text-sm font-bold tabular-nums text-text-primary\">\n {item.timesWorn}×\n </span>\n </div>\n\n {/* Hover action bar (keeps the resting card pure imagery) */}\n <div className=\"absolute inset-x-0 bottom-0 flex items-center gap-1.5 bg-gradient-to-t from-bg-canvas/90 to-transparent p-2.5 pt-8 opacity-100 transition-opacity duration-200 sm:opacity-0 sm:focus-within:opacity-100 sm:group-hover:opacity-100\">\n <button\n type=\"button\"\n disabled={logWear.isPending}\n onClick={() => logWear.mutate(item.id)}\n aria-label={`Log a wear for ${item.name}`}\n className=\"inline-flex items-center gap-1 rounded-lg bg-bg-surface/90 px-2.5 py-1 text-xs font-medium text-status-success-text shadow-sm backdrop-blur transition-colors hover:bg-bg-surface disabled:opacity-50\"\n >\n {wearing ? (\n <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />\n ) : (\n <CheckCircle2 className=\"h-3.5 w-3.5\" />\n )}\n Wore it\n </button>\n <button\n type=\"button\"\n disabled={remove.isPending}\n onClick={() => remove.mutate(item.id)}\n aria-label={`Remove ${item.name}`}\n className=\"ml-auto inline-flex h-7 w-7 items-center justify-center rounded-lg bg-bg-surface/90 text-text-secondary shadow-sm backdrop-blur transition-colors hover:text-status-error-text disabled:opacity-50\"\n >\n {removing ? (\n <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />\n ) : (\n <Trash2 className=\"h-3.5 w-3.5\" />\n )}\n </button>\n </div>\n </div>\n\n {/* Details */}\n <div className=\"flex flex-1 flex-col p-4\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">{item.name}</p>\n <p className=\"truncate text-xs text-text-secondary\">{subtitle}</p>\n <div className=\"mt-2 flex flex-wrap items-center gap-1.5\">\n <StatusBadge status={item.source} />\n {item.size && (\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n {item.size}\n </span>\n )}\n </div>\n {item.notes && (\n <p className=\"mt-2 line-clamp-2 text-xs text-text-secondary\">{item.notes}</p>\n )}\n <div className=\"mt-3 border-t border-border-seam pt-2 text-xs text-text-secondary\">\n {item.lastWornAt\n ? `Last worn ${formatRelativeTime(item.lastWornAt)}`\n : 'Not worn yet'}\n </div>\n </div>\n </div>\n );\n })}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-coolandlovely",
3
- "version": "2026.706.1",
3
+ "version": "2026.706.3",
4
4
  "description": "Burdenoff CoolAndLovely Microfrontend",
5
5
  "type": "module",
6
6
  "files": [