@burdenoff/microfe-coolandlovely 2026.706.1 → 2026.706.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/coolandlovely/CoolAndLovelyRoutes.js +14 -6
  2. package/dist/coolandlovely/CoolAndLovelyRoutes.js.map +1 -1
  3. package/dist/coolandlovely/components/ActorName.js +29 -0
  4. package/dist/coolandlovely/components/ActorName.js.map +1 -0
  5. package/dist/coolandlovely/components/AddToLookbookButton.js +81 -0
  6. package/dist/coolandlovely/components/AddToLookbookButton.js.map +1 -0
  7. package/dist/coolandlovely/components/Comments.js +109 -0
  8. package/dist/coolandlovely/components/Comments.js.map +1 -0
  9. package/dist/coolandlovely/components/Field.js +22 -2
  10. package/dist/coolandlovely/components/Field.js.map +1 -1
  11. package/dist/coolandlovely/components/FileImage.js +17 -0
  12. package/dist/coolandlovely/components/FileImage.js.map +1 -0
  13. package/dist/coolandlovely/components/FollowButton.js +24 -0
  14. package/dist/coolandlovely/components/FollowButton.js.map +1 -0
  15. package/dist/coolandlovely/components/ImageUploadField.js +71 -0
  16. package/dist/coolandlovely/components/ImageUploadField.js.map +1 -0
  17. package/dist/coolandlovely/components/Thumbnail.js +20 -13
  18. package/dist/coolandlovely/components/Thumbnail.js.map +1 -1
  19. package/dist/coolandlovely/hooks/index.js +2 -0
  20. package/dist/coolandlovely/hooks/useComments.js +68 -0
  21. package/dist/coolandlovely/hooks/useComments.js.map +1 -0
  22. package/dist/coolandlovely/hooks/useCoolAndLovelyApi.js +74 -4
  23. package/dist/coolandlovely/hooks/useCoolAndLovelyApi.js.map +1 -1
  24. package/dist/coolandlovely/hooks/useFiles.js +94 -0
  25. package/dist/coolandlovely/hooks/useFiles.js.map +1 -0
  26. package/dist/coolandlovely/pages/HomePage.js +105 -87
  27. package/dist/coolandlovely/pages/HomePage.js.map +1 -1
  28. package/dist/coolandlovely/pages/brands/BrandDetailPage.js +144 -122
  29. package/dist/coolandlovely/pages/brands/BrandDetailPage.js.map +1 -1
  30. package/dist/coolandlovely/pages/brands/BrandFormPage.js +128 -90
  31. package/dist/coolandlovely/pages/brands/BrandFormPage.js.map +1 -1
  32. package/dist/coolandlovely/pages/brands/BrandsListPage.js +2 -0
  33. package/dist/coolandlovely/pages/brands/BrandsListPage.js.map +1 -1
  34. package/dist/coolandlovely/pages/collections/CollectionDetailPage.js +5 -1
  35. package/dist/coolandlovely/pages/collections/CollectionDetailPage.js.map +1 -1
  36. package/dist/coolandlovely/pages/collections/CollectionFormPage.js +117 -102
  37. package/dist/coolandlovely/pages/collections/CollectionFormPage.js.map +1 -1
  38. package/dist/coolandlovely/pages/collections/CollectionsListPage.js +2 -0
  39. package/dist/coolandlovely/pages/collections/CollectionsListPage.js.map +1 -1
  40. package/dist/coolandlovely/pages/discover/DiscoverPage.js +223 -0
  41. package/dist/coolandlovely/pages/discover/DiscoverPage.js.map +1 -0
  42. package/dist/coolandlovely/pages/items/StyleItemDetailPage.js +99 -95
  43. package/dist/coolandlovely/pages/items/StyleItemDetailPage.js.map +1 -1
  44. package/dist/coolandlovely/pages/items/StyleItemFormPage.js +144 -130
  45. package/dist/coolandlovely/pages/items/StyleItemFormPage.js.map +1 -1
  46. package/dist/coolandlovely/pages/items/StyleItemsListPage.js +2 -0
  47. package/dist/coolandlovely/pages/items/StyleItemsListPage.js.map +1 -1
  48. package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js +190 -99
  49. package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js.map +1 -1
  50. package/dist/coolandlovely/pages/lookbooks/LookbooksListPage.js +81 -68
  51. package/dist/coolandlovely/pages/lookbooks/LookbooksListPage.js.map +1 -1
  52. package/dist/coolandlovely/pages/looks/LookDetailPage.js +195 -155
  53. package/dist/coolandlovely/pages/looks/LookDetailPage.js.map +1 -1
  54. package/dist/coolandlovely/pages/looks/LookFormPage.js +127 -88
  55. package/dist/coolandlovely/pages/looks/LookFormPage.js.map +1 -1
  56. package/dist/coolandlovely/pages/looks/LooksListPage.js +2 -0
  57. package/dist/coolandlovely/pages/looks/LooksListPage.js.map +1 -1
  58. package/dist/coolandlovely/pages/profile/StyleProfilePage.js +169 -101
  59. package/dist/coolandlovely/pages/profile/StyleProfilePage.js.map +1 -1
  60. package/dist/coolandlovely/pages/trends/TrendsPage.js +249 -0
  61. package/dist/coolandlovely/pages/trends/TrendsPage.js.map +1 -0
  62. package/dist/coolandlovely/pages/wardrobe/WardrobeFormPage.js +88 -76
  63. package/dist/coolandlovely/pages/wardrobe/WardrobeFormPage.js.map +1 -1
  64. package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.js +2 -0
  65. package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.js.map +1 -1
  66. package/package.json +1 -1
@@ -2,144 +2,157 @@ import { useCoolAndLovely as e } from "../../context/CoolAndLovelyContext.js";
2
2
  import { PageLayout as t } from "../../components/PageLayout.js";
3
3
  import { StatusBadge as n } from "../../components/StatusBadge.js";
4
4
  import { ErrorView as r, LoadingView as i } from "../../components/StateViews.js";
5
- import { Thumbnail as a } from "../../components/Thumbnail.js";
6
- import { useCreateLookbook as o, useLookbooks as s } from "../../hooks/useCoolAndLovelyApi.js";
5
+ import { COO_FILE_FOLDERS as a } from "../../hooks/useFiles.js";
6
+ import { Thumbnail as o } from "../../components/Thumbnail.js";
7
+ import { useCreateLookbook as s, useLookbooks as c } from "../../hooks/useCoolAndLovelyApi.js";
7
8
  import "../../hooks/index.js";
8
- import { parseCsv as c, titleCase as l } from "../../utils/formatters.js";
9
- import { TagList as u } from "../../components/TagList.js";
10
- import { SelectField as d, TextAreaField as f, TextField as p } from "../../components/Field.js";
11
- import { EmptyState as m } from "../../components/EmptyState.js";
12
- import { useState as h } from "react";
13
- import { BookHeart as g, Loader2 as _, Plus as v, X as y } from "lucide-react";
14
- import { jsx as b, jsxs as x } from "react/jsx-runtime";
15
- import { GlassCard as S } from "@burdenoff/fe-libs/ui";
9
+ import { parseCsv as l, titleCase as u } from "../../utils/formatters.js";
10
+ import { TagList as d } from "../../components/TagList.js";
11
+ import { SelectField as f, TextAreaField as p, TextField as m } from "../../components/Field.js";
12
+ import { ImageUploadField as h } from "../../components/ImageUploadField.js";
13
+ import { EmptyState as g } from "../../components/EmptyState.js";
14
+ import { useState as _ } from "react";
15
+ import { BookHeart as v, Loader2 as y, Plus as b, X as x } from "lucide-react";
16
+ import { jsx as S, jsxs as C } from "react/jsx-runtime";
17
+ import { GlassCard as w } from "@burdenoff/fe-libs/ui";
16
18
  //#region src/coolandlovely/pages/lookbooks/LookbooksListPage.tsx
17
- var C = [
19
+ var T = [
18
20
  "PRIVATE",
19
21
  "FOLLOWERS",
20
22
  "PUBLIC"
21
23
  ];
22
- function w() {
23
- let { navigateTo: w } = e(), T = s(), E = o(), [D, O] = h(!1), [k, A] = h(""), [j, M] = h(""), [N, P] = h("PRIVATE"), [F, I] = h(""), [L, R] = h(), z = T.data ?? [], B = C.map((e) => ({
24
+ function E() {
25
+ let { navigateTo: E } = e(), D = c(), O = s(), [k, A] = _(!1), [j, M] = _(""), [N, P] = _(""), [F, I] = _("PRIVATE"), [L, R] = _(""), [z, B] = _(""), [V, H] = _(), U = D.data ?? [], W = T.map((e) => ({
24
26
  value: e,
25
- label: l(e)
27
+ label: u(e)
26
28
  }));
27
- return /* @__PURE__ */ x(t, {
29
+ return /* @__PURE__ */ C(t, {
28
30
  title: "Lookbooks",
29
31
  description: "Themed boards of saved catalogue pieces and looks",
30
- actions: /* @__PURE__ */ x("button", {
32
+ actions: /* @__PURE__ */ C("button", {
31
33
  type: "button",
32
- onClick: () => O((e) => !e),
34
+ onClick: () => A((e) => !e),
33
35
  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",
34
- children: [b(D ? y : v, { className: "h-4 w-4" }), D ? "Close" : "New lookbook"]
36
+ children: [S(k ? x : b, { className: "h-4 w-4" }), k ? "Close" : "New lookbook"]
35
37
  }),
36
- children: [D && /* @__PURE__ */ b("form", {
38
+ children: [k && /* @__PURE__ */ S("form", {
37
39
  onSubmit: (e) => {
38
- if (e.preventDefault(), !k.trim()) {
39
- R("Name is required");
40
+ if (e.preventDefault(), !j.trim()) {
41
+ H("Name is required");
40
42
  return;
41
43
  }
42
- R(void 0), E.mutate({
43
- name: k.trim(),
44
- description: j.trim() || void 0,
45
- visibility: N,
46
- tags: c(F)
44
+ H(void 0), O.mutate({
45
+ name: j.trim(),
46
+ description: N.trim() || void 0,
47
+ visibility: F,
48
+ tags: l(L),
49
+ coverFileId: z || void 0
47
50
  }, { onSuccess: (e) => {
48
- O(!1), A(""), M(""), I(""), P("PRIVATE"), w(`/lookbooks/${e.id}`);
51
+ A(!1), M(""), P(""), R(""), I("PRIVATE"), B(""), E(`/lookbooks/${e.id}`);
49
52
  } });
50
53
  },
51
54
  className: "mb-6",
52
- children: /* @__PURE__ */ x(S, {
55
+ children: /* @__PURE__ */ C(w, {
53
56
  treatment: "plain",
54
57
  className: "p-5",
55
58
  children: [
56
- /* @__PURE__ */ x("h2", {
59
+ /* @__PURE__ */ C("h2", {
57
60
  className: "mb-4 flex items-center gap-2 text-base font-semibold text-text-primary",
58
- children: [/* @__PURE__ */ b(g, { className: "h-5 w-5" }), " New lookbook"]
61
+ children: [/* @__PURE__ */ S(v, { className: "h-5 w-5" }), " New lookbook"]
59
62
  }),
60
- /* @__PURE__ */ x("div", {
63
+ /* @__PURE__ */ C("div", {
61
64
  className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
62
- children: [/* @__PURE__ */ b(p, {
65
+ children: [/* @__PURE__ */ S(m, {
63
66
  label: "Name",
64
- value: k,
65
- onChange: A,
67
+ value: j,
68
+ onChange: M,
66
69
  placeholder: "Summer wedding guest",
67
70
  required: !0,
68
- error: L
69
- }), /* @__PURE__ */ b(d, {
71
+ error: V
72
+ }), /* @__PURE__ */ S(f, {
70
73
  label: "Visibility",
71
- value: N,
72
- onChange: (e) => P(e),
73
- options: B
74
+ value: F,
75
+ onChange: (e) => I(e),
76
+ options: W
74
77
  })]
75
78
  }),
76
- /* @__PURE__ */ b(f, {
79
+ /* @__PURE__ */ S(p, {
77
80
  label: "Description",
78
- value: j,
79
- onChange: M,
81
+ value: N,
82
+ onChange: P,
80
83
  placeholder: "What this board is for…",
81
84
  rows: 2,
82
85
  className: "mt-4"
83
86
  }),
84
- /* @__PURE__ */ b(p, {
87
+ /* @__PURE__ */ S(m, {
85
88
  label: "Tags",
86
- value: F,
87
- onChange: I,
89
+ value: L,
90
+ onChange: R,
88
91
  placeholder: "summer, formal",
89
92
  hint: "Comma-separated",
90
93
  className: "mt-4"
91
94
  }),
92
- E.isError && /* @__PURE__ */ b("p", {
95
+ /* @__PURE__ */ S(h, {
96
+ label: "Cover image",
97
+ aspect: "wide",
98
+ value: z,
99
+ onChange: B,
100
+ folder: a.lookbooks,
101
+ className: "mt-4"
102
+ }),
103
+ O.isError && /* @__PURE__ */ S("p", {
93
104
  className: "mt-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
94
- children: E.error instanceof Error ? E.error.message : "Something went wrong"
105
+ children: O.error instanceof Error ? O.error.message : "Something went wrong"
95
106
  }),
96
- /* @__PURE__ */ b("div", {
107
+ /* @__PURE__ */ S("div", {
97
108
  className: "mt-4 flex justify-end",
98
- children: /* @__PURE__ */ x("button", {
109
+ children: /* @__PURE__ */ C("button", {
99
110
  type: "submit",
100
- disabled: E.isPending,
111
+ disabled: O.isPending,
101
112
  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",
102
- children: [E.isPending ? /* @__PURE__ */ b(_, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ b(v, { className: "h-4 w-4" }), "Create lookbook"]
113
+ children: [O.isPending ? /* @__PURE__ */ S(y, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ S(b, { className: "h-4 w-4" }), "Create lookbook"]
103
114
  })
104
115
  })
105
116
  ]
106
117
  })
107
- }), T.isLoading ? /* @__PURE__ */ b(i, { label: "Loading lookbooks…" }) : T.isError ? /* @__PURE__ */ b(r, { onRetry: () => void T.refetch() }) : z.length === 0 ? /* @__PURE__ */ b(m, {
118
+ }), D.isLoading ? /* @__PURE__ */ S(i, { label: "Loading lookbooks…" }) : D.isError ? /* @__PURE__ */ S(r, { onRetry: () => void D.refetch() }) : U.length === 0 ? /* @__PURE__ */ S(g, {
108
119
  illustration: "empty-data",
109
- icon: /* @__PURE__ */ b(g, { className: "h-10 w-10" }),
120
+ icon: /* @__PURE__ */ S(v, { className: "h-10 w-10" }),
110
121
  title: "No lookbooks yet",
111
122
  description: "Create a themed board to clip catalogue pieces and looks.",
112
- action: /* @__PURE__ */ x("button", {
123
+ action: /* @__PURE__ */ C("button", {
113
124
  type: "button",
114
- onClick: () => O(!0),
125
+ onClick: () => A(!0),
115
126
  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",
116
- children: [/* @__PURE__ */ b(v, { className: "h-4 w-4" }), " New lookbook"]
127
+ children: [/* @__PURE__ */ S(b, { className: "h-4 w-4" }), " New lookbook"]
117
128
  })
118
- }) : /* @__PURE__ */ b("div", {
129
+ }) : /* @__PURE__ */ S("div", {
119
130
  className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
120
- children: z.map((e) => /* @__PURE__ */ x("button", {
131
+ children: U.map((e) => /* @__PURE__ */ C("button", {
121
132
  type: "button",
122
- onClick: () => w(`/lookbooks/${e.id}`),
133
+ onClick: () => E(`/lookbooks/${e.id}`),
123
134
  className: "overflow-hidden rounded-lg border border-border-seam bg-bg-surface text-left shadow-sm transition-all hover:border-accent-foreground/20 hover:shadow-md",
124
- children: [/* @__PURE__ */ b(a, {
125
- icon: /* @__PURE__ */ b(g, { className: "h-9 w-9" }),
135
+ children: [/* @__PURE__ */ S(o, {
136
+ icon: /* @__PURE__ */ S(v, { className: "h-9 w-9" }),
126
137
  seed: e.id,
138
+ fileId: e.cover?.id,
139
+ alt: e.name,
127
140
  className: "h-28 w-full"
128
- }), /* @__PURE__ */ x("div", {
141
+ }), /* @__PURE__ */ C("div", {
129
142
  className: "p-4",
130
143
  children: [
131
- /* @__PURE__ */ x("div", {
144
+ /* @__PURE__ */ C("div", {
132
145
  className: "flex items-start justify-between gap-2",
133
- children: [/* @__PURE__ */ b("h3", {
146
+ children: [/* @__PURE__ */ S("h3", {
134
147
  className: "truncate text-base font-semibold text-text-primary",
135
148
  children: e.name
136
- }), /* @__PURE__ */ b(n, { status: e.visibility })]
149
+ }), /* @__PURE__ */ S(n, { status: e.visibility })]
137
150
  }),
138
- e.description && /* @__PURE__ */ b("p", {
151
+ e.description && /* @__PURE__ */ S("p", {
139
152
  className: "mt-1.5 line-clamp-2 text-sm text-text-secondary",
140
153
  children: e.description
141
154
  }),
142
- e.tags.length > 0 && /* @__PURE__ */ b(u, {
155
+ e.tags.length > 0 && /* @__PURE__ */ S(d, {
143
156
  tags: e.tags,
144
157
  max: 4,
145
158
  className: "mt-3"
@@ -151,6 +164,6 @@ function w() {
151
164
  });
152
165
  }
153
166
  //#endregion
154
- export { w as LookbooksListPage };
167
+ export { E as LookbooksListPage };
155
168
 
156
169
  //# sourceMappingURL=LookbooksListPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"LookbooksListPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/lookbooks/LookbooksListPage.tsx"],"sourcesContent":["/**\n * Lookbooks List Page\n * @module coolandlovely/pages/lookbooks\n *\n * Lists themed lookbook boards (`cooListLookbooks`) and supports creating one\n * inline (`cooCreateLookbook`).\n */\n\nimport { useState } from 'react';\nimport { Plus, BookHeart, Loader2, X } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { TagList } from '../../components/TagList';\nimport { TextField, TextAreaField, SelectField } from '../../components/Field';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useLookbooks, useCreateLookbook } from '../../hooks';\nimport { parseCsv, titleCase } from '../../utils/formatters';\nimport type { CooVisibility } from '../../types';\n\nconst VISIBILITIES: CooVisibility[] = ['PRIVATE', 'FOLLOWERS', 'PUBLIC'];\n\nexport function LookbooksListPage() {\n const { navigateTo } = useCoolAndLovely();\n const query = useLookbooks();\n const createLookbook = useCreateLookbook();\n\n const [creating, setCreating] = useState(false);\n const [name, setName] = useState('');\n const [description, setDescription] = useState('');\n const [visibility, setVisibility] = useState<CooVisibility>('PRIVATE');\n const [tags, setTags] = useState('');\n const [nameError, setNameError] = useState<string | undefined>();\n\n const lookbooks = query.data ?? [];\n const visibilityOptions = VISIBILITIES.map((v) => ({ value: v, label: titleCase(v) }));\n\n const handleCreate = (e: React.FormEvent) => {\n e.preventDefault();\n if (!name.trim()) {\n setNameError('Name is required');\n return;\n }\n setNameError(undefined);\n createLookbook.mutate(\n {\n name: name.trim(),\n description: description.trim() || undefined,\n visibility,\n tags: parseCsv(tags),\n },\n {\n onSuccess: (lb) => {\n setCreating(false);\n setName('');\n setDescription('');\n setTags('');\n setVisibility('PRIVATE');\n navigateTo(`/lookbooks/${lb.id}`);\n },\n }\n );\n };\n\n return (\n <PageLayout\n title=\"Lookbooks\"\n description=\"Themed boards of saved catalogue pieces and looks\"\n actions={\n <button\n type=\"button\"\n onClick={() => setCreating((v) => !v)}\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 {creating ? <X className=\"h-4 w-4\" /> : <Plus className=\"h-4 w-4\" />}\n {creating ? 'Close' : 'New lookbook'}\n </button>\n }\n >\n {creating && (\n <form onSubmit={handleCreate} className=\"mb-6\">\n <GlassCard treatment=\"plain\" className=\"p-5\">\n <h2 className=\"mb-4 flex items-center gap-2 text-base font-semibold text-text-primary\">\n <BookHeart className=\"h-5 w-5\" /> New lookbook\n </h2>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <TextField\n label=\"Name\"\n value={name}\n onChange={setName}\n placeholder=\"Summer wedding guest\"\n required\n error={nameError}\n />\n <SelectField\n label=\"Visibility\"\n value={visibility}\n onChange={(v) => setVisibility(v as CooVisibility)}\n options={visibilityOptions}\n />\n </div>\n <TextAreaField\n label=\"Description\"\n value={description}\n onChange={setDescription}\n placeholder=\"What this board is for…\"\n rows={2}\n className=\"mt-4\"\n />\n <TextField\n label=\"Tags\"\n value={tags}\n onChange={setTags}\n placeholder=\"summer, formal\"\n hint=\"Comma-separated\"\n className=\"mt-4\"\n />\n {createLookbook.isError && (\n <p className=\"mt-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {createLookbook.error instanceof Error\n ? createLookbook.error.message\n : 'Something went wrong'}\n </p>\n )}\n <div className=\"mt-4 flex justify-end\">\n <button\n type=\"submit\"\n disabled={createLookbook.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 {createLookbook.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Plus className=\"h-4 w-4\" />\n )}\n Create lookbook\n </button>\n </div>\n </GlassCard>\n </form>\n )}\n\n {query.isLoading ? (\n <LoadingView label=\"Loading lookbooks…\" />\n ) : query.isError ? (\n <ErrorView onRetry={() => void query.refetch()} />\n ) : lookbooks.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<BookHeart className=\"h-10 w-10\" />}\n title=\"No lookbooks yet\"\n description=\"Create a themed board to clip catalogue pieces and looks.\"\n action={\n <button\n type=\"button\"\n onClick={() => setCreating(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text\"\n >\n <Plus className=\"h-4 w-4\" /> New lookbook\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {lookbooks.map((lb) => (\n <button\n key={lb.id}\n type=\"button\"\n onClick={() => navigateTo(`/lookbooks/${lb.id}`)}\n className=\"overflow-hidden rounded-lg border border-border-seam bg-bg-surface text-left shadow-sm transition-all hover:border-accent-foreground/20 hover:shadow-md\"\n >\n <Thumbnail\n icon={<BookHeart className=\"h-9 w-9\" />}\n seed={lb.id}\n className=\"h-28 w-full\"\n />\n <div className=\"p-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <h3 className=\"truncate text-base font-semibold text-text-primary\">{lb.name}</h3>\n <StatusBadge status={lb.visibility} />\n </div>\n {lb.description && (\n <p className=\"mt-1.5 line-clamp-2 text-sm text-text-secondary\">\n {lb.description}\n </p>\n )}\n {lb.tags.length > 0 && <TagList tags={lb.tags} max={4} className=\"mt-3\" />}\n </div>\n </button>\n ))}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"LookbooksListPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/lookbooks/LookbooksListPage.tsx"],"sourcesContent":["/**\n * Lookbooks List Page\n * @module coolandlovely/pages/lookbooks\n *\n * Lists themed lookbook boards (`cooListLookbooks`) and supports creating one\n * inline (`cooCreateLookbook`).\n */\n\nimport { useState } from 'react';\nimport { Plus, BookHeart, Loader2, X } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { TagList } from '../../components/TagList';\nimport { TextField, TextAreaField, SelectField } from '../../components/Field';\nimport { ImageUploadField } from '../../components/ImageUploadField';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useLookbooks, useCreateLookbook } from '../../hooks';\nimport { COO_FILE_FOLDERS } from '../../hooks/useFiles';\nimport { parseCsv, titleCase } from '../../utils/formatters';\nimport type { CooVisibility } from '../../types';\n\nconst VISIBILITIES: CooVisibility[] = ['PRIVATE', 'FOLLOWERS', 'PUBLIC'];\n\nexport function LookbooksListPage() {\n const { navigateTo } = useCoolAndLovely();\n const query = useLookbooks();\n const createLookbook = useCreateLookbook();\n\n const [creating, setCreating] = useState(false);\n const [name, setName] = useState('');\n const [description, setDescription] = useState('');\n const [visibility, setVisibility] = useState<CooVisibility>('PRIVATE');\n const [tags, setTags] = useState('');\n const [coverFileId, setCoverFileId] = useState('');\n const [nameError, setNameError] = useState<string | undefined>();\n\n const lookbooks = query.data ?? [];\n const visibilityOptions = VISIBILITIES.map((v) => ({ value: v, label: titleCase(v) }));\n\n const handleCreate = (e: React.FormEvent) => {\n e.preventDefault();\n if (!name.trim()) {\n setNameError('Name is required');\n return;\n }\n setNameError(undefined);\n createLookbook.mutate(\n {\n name: name.trim(),\n description: description.trim() || undefined,\n visibility,\n tags: parseCsv(tags),\n coverFileId: coverFileId || undefined,\n },\n {\n onSuccess: (lb) => {\n setCreating(false);\n setName('');\n setDescription('');\n setTags('');\n setVisibility('PRIVATE');\n setCoverFileId('');\n navigateTo(`/lookbooks/${lb.id}`);\n },\n }\n );\n };\n\n return (\n <PageLayout\n title=\"Lookbooks\"\n description=\"Themed boards of saved catalogue pieces and looks\"\n actions={\n <button\n type=\"button\"\n onClick={() => setCreating((v) => !v)}\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 {creating ? <X className=\"h-4 w-4\" /> : <Plus className=\"h-4 w-4\" />}\n {creating ? 'Close' : 'New lookbook'}\n </button>\n }\n >\n {creating && (\n <form onSubmit={handleCreate} className=\"mb-6\">\n <GlassCard treatment=\"plain\" className=\"p-5\">\n <h2 className=\"mb-4 flex items-center gap-2 text-base font-semibold text-text-primary\">\n <BookHeart className=\"h-5 w-5\" /> New lookbook\n </h2>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <TextField\n label=\"Name\"\n value={name}\n onChange={setName}\n placeholder=\"Summer wedding guest\"\n required\n error={nameError}\n />\n <SelectField\n label=\"Visibility\"\n value={visibility}\n onChange={(v) => setVisibility(v as CooVisibility)}\n options={visibilityOptions}\n />\n </div>\n <TextAreaField\n label=\"Description\"\n value={description}\n onChange={setDescription}\n placeholder=\"What this board is for…\"\n rows={2}\n className=\"mt-4\"\n />\n <TextField\n label=\"Tags\"\n value={tags}\n onChange={setTags}\n placeholder=\"summer, formal\"\n hint=\"Comma-separated\"\n className=\"mt-4\"\n />\n <ImageUploadField\n label=\"Cover image\"\n aspect=\"wide\"\n value={coverFileId}\n onChange={setCoverFileId}\n folder={COO_FILE_FOLDERS.lookbooks}\n className=\"mt-4\"\n />\n {createLookbook.isError && (\n <p className=\"mt-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {createLookbook.error instanceof Error\n ? createLookbook.error.message\n : 'Something went wrong'}\n </p>\n )}\n <div className=\"mt-4 flex justify-end\">\n <button\n type=\"submit\"\n disabled={createLookbook.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 {createLookbook.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Plus className=\"h-4 w-4\" />\n )}\n Create lookbook\n </button>\n </div>\n </GlassCard>\n </form>\n )}\n\n {query.isLoading ? (\n <LoadingView label=\"Loading lookbooks…\" />\n ) : query.isError ? (\n <ErrorView onRetry={() => void query.refetch()} />\n ) : lookbooks.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<BookHeart className=\"h-10 w-10\" />}\n title=\"No lookbooks yet\"\n description=\"Create a themed board to clip catalogue pieces and looks.\"\n action={\n <button\n type=\"button\"\n onClick={() => setCreating(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text\"\n >\n <Plus className=\"h-4 w-4\" /> New lookbook\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {lookbooks.map((lb) => (\n <button\n key={lb.id}\n type=\"button\"\n onClick={() => navigateTo(`/lookbooks/${lb.id}`)}\n className=\"overflow-hidden rounded-lg border border-border-seam bg-bg-surface text-left shadow-sm transition-all hover:border-accent-foreground/20 hover:shadow-md\"\n >\n <Thumbnail\n icon={<BookHeart className=\"h-9 w-9\" />}\n seed={lb.id}\n fileId={lb.cover?.id}\n alt={lb.name}\n className=\"h-28 w-full\"\n />\n <div className=\"p-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <h3 className=\"truncate text-base font-semibold text-text-primary\">{lb.name}</h3>\n <StatusBadge status={lb.visibility} />\n </div>\n {lb.description && (\n <p className=\"mt-1.5 line-clamp-2 text-sm text-text-secondary\">\n {lb.description}\n </p>\n )}\n {lb.tags.length > 0 && <TagList tags={lb.tags} max={4} className=\"mt-3\" />}\n </div>\n </button>\n ))}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}