@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
|
@@ -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 {
|
|
6
|
-
import {
|
|
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
|
|
9
|
-
import { TagList as
|
|
10
|
-
import { SelectField as
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
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
|
|
19
|
+
var T = [
|
|
18
20
|
"PRIVATE",
|
|
19
21
|
"FOLLOWERS",
|
|
20
22
|
"PUBLIC"
|
|
21
23
|
];
|
|
22
|
-
function
|
|
23
|
-
let { navigateTo:
|
|
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:
|
|
27
|
+
label: u(e)
|
|
26
28
|
}));
|
|
27
|
-
return /* @__PURE__ */
|
|
29
|
+
return /* @__PURE__ */ C(t, {
|
|
28
30
|
title: "Lookbooks",
|
|
29
31
|
description: "Themed boards of saved catalogue pieces and looks",
|
|
30
|
-
actions: /* @__PURE__ */
|
|
32
|
+
actions: /* @__PURE__ */ C("button", {
|
|
31
33
|
type: "button",
|
|
32
|
-
onClick: () =>
|
|
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: [
|
|
36
|
+
children: [S(k ? x : b, { className: "h-4 w-4" }), k ? "Close" : "New lookbook"]
|
|
35
37
|
}),
|
|
36
|
-
children: [
|
|
38
|
+
children: [k && /* @__PURE__ */ S("form", {
|
|
37
39
|
onSubmit: (e) => {
|
|
38
|
-
if (e.preventDefault(), !
|
|
39
|
-
|
|
40
|
+
if (e.preventDefault(), !j.trim()) {
|
|
41
|
+
H("Name is required");
|
|
40
42
|
return;
|
|
41
43
|
}
|
|
42
|
-
|
|
43
|
-
name:
|
|
44
|
-
description:
|
|
45
|
-
visibility:
|
|
46
|
-
tags:
|
|
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
|
-
|
|
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__ */
|
|
55
|
+
children: /* @__PURE__ */ C(w, {
|
|
53
56
|
treatment: "plain",
|
|
54
57
|
className: "p-5",
|
|
55
58
|
children: [
|
|
56
|
-
/* @__PURE__ */
|
|
59
|
+
/* @__PURE__ */ C("h2", {
|
|
57
60
|
className: "mb-4 flex items-center gap-2 text-base font-semibold text-text-primary",
|
|
58
|
-
children: [/* @__PURE__ */
|
|
61
|
+
children: [/* @__PURE__ */ S(v, { className: "h-5 w-5" }), " New lookbook"]
|
|
59
62
|
}),
|
|
60
|
-
/* @__PURE__ */
|
|
63
|
+
/* @__PURE__ */ C("div", {
|
|
61
64
|
className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
|
|
62
|
-
children: [/* @__PURE__ */
|
|
65
|
+
children: [/* @__PURE__ */ S(m, {
|
|
63
66
|
label: "Name",
|
|
64
|
-
value:
|
|
65
|
-
onChange:
|
|
67
|
+
value: j,
|
|
68
|
+
onChange: M,
|
|
66
69
|
placeholder: "Summer wedding guest",
|
|
67
70
|
required: !0,
|
|
68
|
-
error:
|
|
69
|
-
}), /* @__PURE__ */
|
|
71
|
+
error: V
|
|
72
|
+
}), /* @__PURE__ */ S(f, {
|
|
70
73
|
label: "Visibility",
|
|
71
|
-
value:
|
|
72
|
-
onChange: (e) =>
|
|
73
|
-
options:
|
|
74
|
+
value: F,
|
|
75
|
+
onChange: (e) => I(e),
|
|
76
|
+
options: W
|
|
74
77
|
})]
|
|
75
78
|
}),
|
|
76
|
-
/* @__PURE__ */
|
|
79
|
+
/* @__PURE__ */ S(p, {
|
|
77
80
|
label: "Description",
|
|
78
|
-
value:
|
|
79
|
-
onChange:
|
|
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__ */
|
|
87
|
+
/* @__PURE__ */ S(m, {
|
|
85
88
|
label: "Tags",
|
|
86
|
-
value:
|
|
87
|
-
onChange:
|
|
89
|
+
value: L,
|
|
90
|
+
onChange: R,
|
|
88
91
|
placeholder: "summer, formal",
|
|
89
92
|
hint: "Comma-separated",
|
|
90
93
|
className: "mt-4"
|
|
91
94
|
}),
|
|
92
|
-
|
|
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:
|
|
105
|
+
children: O.error instanceof Error ? O.error.message : "Something went wrong"
|
|
95
106
|
}),
|
|
96
|
-
/* @__PURE__ */
|
|
107
|
+
/* @__PURE__ */ S("div", {
|
|
97
108
|
className: "mt-4 flex justify-end",
|
|
98
|
-
children: /* @__PURE__ */
|
|
109
|
+
children: /* @__PURE__ */ C("button", {
|
|
99
110
|
type: "submit",
|
|
100
|
-
disabled:
|
|
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: [
|
|
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
|
-
}),
|
|
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__ */
|
|
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__ */
|
|
123
|
+
action: /* @__PURE__ */ C("button", {
|
|
113
124
|
type: "button",
|
|
114
|
-
onClick: () =>
|
|
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
|
|
127
|
+
children: [/* @__PURE__ */ S(b, { className: "h-4 w-4" }), " New lookbook"]
|
|
117
128
|
})
|
|
118
|
-
}) : /* @__PURE__ */
|
|
129
|
+
}) : /* @__PURE__ */ S("div", {
|
|
119
130
|
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
|
|
120
|
-
children:
|
|
131
|
+
children: U.map((e) => /* @__PURE__ */ C("button", {
|
|
121
132
|
type: "button",
|
|
122
|
-
onClick: () =>
|
|
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__ */
|
|
125
|
-
icon: /* @__PURE__ */
|
|
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__ */
|
|
141
|
+
}), /* @__PURE__ */ C("div", {
|
|
129
142
|
className: "p-4",
|
|
130
143
|
children: [
|
|
131
|
-
/* @__PURE__ */
|
|
144
|
+
/* @__PURE__ */ C("div", {
|
|
132
145
|
className: "flex items-start justify-between gap-2",
|
|
133
|
-
children: [/* @__PURE__ */
|
|
146
|
+
children: [/* @__PURE__ */ S("h3", {
|
|
134
147
|
className: "truncate text-base font-semibold text-text-primary",
|
|
135
148
|
children: e.name
|
|
136
|
-
}), /* @__PURE__ */
|
|
149
|
+
}), /* @__PURE__ */ S(n, { status: e.visibility })]
|
|
137
150
|
}),
|
|
138
|
-
e.description && /* @__PURE__ */
|
|
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__ */
|
|
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 {
|
|
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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"}
|