@burdenoff/microfe-coolandlovely 2026.716.1 → 2026.720.1
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/components/LookCard.d.ts +3 -1
- package/dist/coolandlovely/components/LookCard.d.ts.map +1 -1
- package/dist/coolandlovely/components/LookCard.js +80 -44
- package/dist/coolandlovely/components/LookCard.js.map +1 -1
- package/dist/coolandlovely/hooks/index.d.ts +1 -1
- package/dist/coolandlovely/hooks/index.d.ts.map +1 -1
- package/dist/coolandlovely/hooks/useCoolAndLovelyApi.d.ts +3 -0
- package/dist/coolandlovely/hooks/useCoolAndLovelyApi.d.ts.map +1 -1
- package/dist/coolandlovely/hooks/useCoolAndLovelyApi.js +58 -22
- package/dist/coolandlovely/hooks/useCoolAndLovelyApi.js.map +1 -1
- package/dist/coolandlovely/pages/HomePage.d.ts.map +1 -1
- package/dist/coolandlovely/pages/HomePage.js +2 -0
- package/dist/coolandlovely/pages/HomePage.js.map +1 -1
- package/dist/coolandlovely/pages/items/StyleItemDetailPage.d.ts.map +1 -1
- package/dist/coolandlovely/pages/items/StyleItemDetailPage.js +2 -0
- package/dist/coolandlovely/pages/items/StyleItemDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/looks/LookDetailPage.d.ts +4 -2
- package/dist/coolandlovely/pages/looks/LookDetailPage.d.ts.map +1 -1
- package/dist/coolandlovely/pages/looks/LookDetailPage.js +134 -128
- package/dist/coolandlovely/pages/looks/LookDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/looks/LooksListPage.d.ts.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/wardrobe/WardrobeFormPage.d.ts.map +1 -1
- package/dist/coolandlovely/pages/wardrobe/WardrobeFormPage.js +46 -23
- package/dist/coolandlovely/pages/wardrobe/WardrobeFormPage.js.map +1 -1
- package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.d.ts.map +1 -1
- package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.js +70 -63
- package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.js.map +1 -1
- package/dist/coolandlovely/types/index.d.ts +11 -0
- package/dist/coolandlovely/types/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -4,27 +4,27 @@ import { PageLayout as n } from "../../components/PageLayout.js";
|
|
|
4
4
|
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
|
-
import { useDeleteLook as s, useLikeLook as c, useLook as l, usePublishLook as u,
|
|
8
|
-
import { AddToLookbookButton as
|
|
9
|
-
import { formatDate as
|
|
7
|
+
import { useDeleteLook as s, useLikeLook as c, useLook as l, usePublishLook as u, useSaveLook as d, useUnlikeLook as f, useUnsaveLook as p, useUpdateLook as m } from "../../hooks/useCoolAndLovelyApi.js";
|
|
8
|
+
import { AddToLookbookButton as h } from "../../components/AddToLookbookButton.js";
|
|
9
|
+
import { formatDate as g, titleCase as _ } from "../../utils/formatters.js";
|
|
10
10
|
import "../../hooks/index.js";
|
|
11
|
-
import { TagList as
|
|
12
|
-
import { Comments as
|
|
13
|
-
import { FollowButton as
|
|
14
|
-
import { ShopItemCard as
|
|
15
|
-
import { useMemo as
|
|
16
|
-
import { ArrowLeft as
|
|
17
|
-
import { useParams as
|
|
18
|
-
import { jsx as
|
|
19
|
-
import { GlassCard as
|
|
20
|
-
import { useActorProfiles as
|
|
11
|
+
import { TagList as v } from "../../components/TagList.js";
|
|
12
|
+
import { Comments as y } from "../../components/Comments.js";
|
|
13
|
+
import { FollowButton as b } from "../../components/FollowButton.js";
|
|
14
|
+
import { ShopItemCard as x } from "../../components/ShopItemCard.js";
|
|
15
|
+
import { useMemo as S, useState as C } from "react";
|
|
16
|
+
import { ArrowLeft as w, Bookmark as T, Heart as E, Loader2 as D, Rocket as O, Sparkles as k, Star as A, Trash2 as j } from "lucide-react";
|
|
17
|
+
import { useParams as M } from "react-router-dom";
|
|
18
|
+
import { jsx as N, jsxs as P } from "react/jsx-runtime";
|
|
19
|
+
import { GlassCard as F } from "@burdenoff/fe-libs/ui";
|
|
20
|
+
import { useActorProfiles as I } from "@burdenoff/fe-libs/shared";
|
|
21
21
|
//#region src/coolandlovely/pages/looks/LookDetailPage.tsx
|
|
22
|
-
function
|
|
22
|
+
function L(e) {
|
|
23
23
|
let t = e.trim().split(/\s+/).filter(Boolean);
|
|
24
24
|
return t.length === 0 ? "?" : t.length === 1 ? t[0].slice(0, 2).toUpperCase() : (t[0][0] + t[t.length - 1][0]).toUpperCase();
|
|
25
25
|
}
|
|
26
|
-
function
|
|
27
|
-
let { apiGatewayUrl: n, authToken: r, workspaceId: i } = e(), [a, o] =
|
|
26
|
+
function R({ authorId: t }) {
|
|
27
|
+
let { apiGatewayUrl: n, authToken: r, workspaceId: i } = e(), [a, o] = C(!1), s = I(S(() => [{
|
|
28
28
|
actorId: t,
|
|
29
29
|
actorType: "user"
|
|
30
30
|
}], [t]), {
|
|
@@ -32,233 +32,239 @@ function F({ authorId: t }) {
|
|
|
32
32
|
authToken: r,
|
|
33
33
|
workspaceId: i
|
|
34
34
|
})[t], c = s?.displayName ?? "Creator", l = s?.avatarUrl;
|
|
35
|
-
return /* @__PURE__ */
|
|
35
|
+
return /* @__PURE__ */ P("span", {
|
|
36
36
|
className: "inline-flex items-center gap-2",
|
|
37
|
-
children: [/* @__PURE__ */
|
|
37
|
+
children: [/* @__PURE__ */ N("span", {
|
|
38
38
|
title: c,
|
|
39
39
|
className: "relative flex h-7 w-7 shrink-0 items-center justify-center overflow-hidden rounded-full bg-bg-sunken ring-1 ring-border-seam",
|
|
40
|
-
children: l && !a ? /* @__PURE__ */
|
|
40
|
+
children: l && !a ? /* @__PURE__ */ N("img", {
|
|
41
41
|
src: l,
|
|
42
42
|
alt: c,
|
|
43
43
|
loading: "lazy",
|
|
44
44
|
onError: () => o(!0),
|
|
45
45
|
className: "h-full w-full object-cover"
|
|
46
|
-
}) : /* @__PURE__ */
|
|
46
|
+
}) : /* @__PURE__ */ N("span", {
|
|
47
47
|
className: "text-[10px] font-semibold text-text-secondary",
|
|
48
|
-
children:
|
|
48
|
+
children: L(c)
|
|
49
49
|
})
|
|
50
|
-
}), /* @__PURE__ */
|
|
50
|
+
}), /* @__PURE__ */ N("span", {
|
|
51
51
|
className: "font-medium text-text-primary",
|
|
52
52
|
title: s?.email ?? t,
|
|
53
53
|
children: c
|
|
54
54
|
})]
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
|
-
function
|
|
58
|
-
let { lookId:
|
|
59
|
-
if (
|
|
57
|
+
function z() {
|
|
58
|
+
let { lookId: S } = M(), { navigateTo: C, currentUser: I } = e(), L = l(S), z = c(), B = f(), V = d(), H = p(), U = u(), W = s(), G = m();
|
|
59
|
+
if (L.isLoading) return /* @__PURE__ */ N(n, {
|
|
60
60
|
title: "Look",
|
|
61
|
-
children: /* @__PURE__ */
|
|
61
|
+
children: /* @__PURE__ */ N(a, { label: "Loading look…" })
|
|
62
62
|
});
|
|
63
|
-
if (
|
|
63
|
+
if (L.isError || !L.data) return /* @__PURE__ */ N(n, {
|
|
64
64
|
title: "Look",
|
|
65
|
-
children: /* @__PURE__ */
|
|
65
|
+
children: /* @__PURE__ */ N(i, {
|
|
66
66
|
title: "Look not found",
|
|
67
|
-
onRetry: () => void
|
|
67
|
+
onRetry: () => void L.refetch()
|
|
68
68
|
})
|
|
69
69
|
});
|
|
70
|
-
let
|
|
71
|
-
return /* @__PURE__ */
|
|
72
|
-
title:
|
|
73
|
-
description:
|
|
74
|
-
actions: /* @__PURE__ */
|
|
70
|
+
let K = L.data, q = K.items ?? [], J = K.publishedAt !== null, Y = !!(I?.id && I.id === K.authorId);
|
|
71
|
+
return /* @__PURE__ */ N(n, {
|
|
72
|
+
title: K.title,
|
|
73
|
+
description: _(K.occasion),
|
|
74
|
+
actions: /* @__PURE__ */ P("div", {
|
|
75
75
|
className: "flex flex-wrap items-center gap-2",
|
|
76
76
|
children: [
|
|
77
|
-
/* @__PURE__ */
|
|
77
|
+
/* @__PURE__ */ P("button", {
|
|
78
78
|
type: "button",
|
|
79
|
-
onClick: () =>
|
|
79
|
+
onClick: () => C("/looks"),
|
|
80
80
|
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",
|
|
81
|
-
children: [/* @__PURE__ */
|
|
81
|
+
children: [/* @__PURE__ */ N(w, { className: "h-4 w-4" }), " Back"]
|
|
82
82
|
}),
|
|
83
|
-
/* @__PURE__ */
|
|
84
|
-
/* @__PURE__ */
|
|
83
|
+
/* @__PURE__ */ N(h, { lookId: K.id }),
|
|
84
|
+
/* @__PURE__ */ P("button", {
|
|
85
85
|
type: "button",
|
|
86
|
-
disabled:
|
|
87
|
-
onClick: () =>
|
|
88
|
-
id:
|
|
89
|
-
input: { isFeatured: !
|
|
86
|
+
disabled: G.isPending,
|
|
87
|
+
onClick: () => G.mutate({
|
|
88
|
+
id: K.id,
|
|
89
|
+
input: { isFeatured: !K.isFeatured }
|
|
90
90
|
}),
|
|
91
|
-
"aria-pressed":
|
|
92
|
-
className: t("inline-flex items-center gap-1.5 rounded-lg border px-3 py-2 text-sm font-medium transition-colors disabled:opacity-50",
|
|
93
|
-
children: [
|
|
91
|
+
"aria-pressed": K.isFeatured,
|
|
92
|
+
className: t("inline-flex items-center gap-1.5 rounded-lg border px-3 py-2 text-sm font-medium transition-colors disabled:opacity-50", K.isFeatured ? "border-status-warning-text/40 bg-status-warning-bg-subtle text-status-warning-text" : "border-border-seam text-text-primary hover:bg-bg-sunken"),
|
|
93
|
+
children: [G.isPending ? /* @__PURE__ */ N(D, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ N(A, { className: t("h-4 w-4", K.isFeatured && "fill-status-warning-text") }), K.isFeatured ? "Featured" : "Feature"]
|
|
94
94
|
}),
|
|
95
|
-
!
|
|
95
|
+
!J && /* @__PURE__ */ P("button", {
|
|
96
96
|
type: "button",
|
|
97
|
-
disabled:
|
|
98
|
-
onClick: () =>
|
|
97
|
+
disabled: U.isPending,
|
|
98
|
+
onClick: () => U.mutate(K.id),
|
|
99
99
|
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 disabled:opacity-50",
|
|
100
|
-
children: [
|
|
100
|
+
children: [U.isPending ? /* @__PURE__ */ N(D, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ N(O, { className: "h-4 w-4" }), "Publish"]
|
|
101
101
|
}),
|
|
102
|
-
/* @__PURE__ */
|
|
102
|
+
/* @__PURE__ */ P("button", {
|
|
103
103
|
type: "button",
|
|
104
|
-
disabled:
|
|
105
|
-
onClick: () =>
|
|
104
|
+
disabled: W.isPending,
|
|
105
|
+
onClick: () => W.mutate(K.id, { onSuccess: () => C("/looks") }),
|
|
106
106
|
className: "inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-50",
|
|
107
|
-
children: [
|
|
107
|
+
children: [W.isPending ? /* @__PURE__ */ N(D, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ N(j, { className: "h-4 w-4" }), "Delete"]
|
|
108
108
|
})
|
|
109
109
|
]
|
|
110
110
|
}),
|
|
111
|
-
children: /* @__PURE__ */
|
|
111
|
+
children: /* @__PURE__ */ P("div", {
|
|
112
112
|
className: "grid grid-cols-1 gap-6 lg:grid-cols-3",
|
|
113
|
-
children: [/* @__PURE__ */
|
|
113
|
+
children: [/* @__PURE__ */ P("div", {
|
|
114
114
|
className: "lg:col-span-2",
|
|
115
115
|
children: [
|
|
116
|
-
/* @__PURE__ */
|
|
116
|
+
/* @__PURE__ */ P(F, {
|
|
117
117
|
treatment: "glass",
|
|
118
118
|
className: "group overflow-hidden p-0",
|
|
119
|
-
children: [/* @__PURE__ */
|
|
120
|
-
icon: /* @__PURE__ */
|
|
121
|
-
seed:
|
|
122
|
-
fileId:
|
|
123
|
-
alt:
|
|
119
|
+
children: [/* @__PURE__ */ N(o, {
|
|
120
|
+
icon: /* @__PURE__ */ N(k, { className: "h-14 w-14" }),
|
|
121
|
+
seed: K.id,
|
|
122
|
+
fileId: K.image?.id,
|
|
123
|
+
alt: K.title,
|
|
124
124
|
className: "aspect-[4/5] max-h-[38rem] w-full rounded-none"
|
|
125
|
-
}), /* @__PURE__ */
|
|
125
|
+
}), /* @__PURE__ */ P("div", {
|
|
126
126
|
className: "p-6",
|
|
127
127
|
children: [
|
|
128
|
-
/* @__PURE__ */
|
|
128
|
+
/* @__PURE__ */ P("div", {
|
|
129
129
|
className: "flex items-center gap-2",
|
|
130
130
|
children: [
|
|
131
|
-
/* @__PURE__ */
|
|
131
|
+
/* @__PURE__ */ N("h2", {
|
|
132
132
|
className: "text-xl font-semibold text-text-primary",
|
|
133
|
-
children:
|
|
133
|
+
children: K.title
|
|
134
134
|
}),
|
|
135
|
-
|
|
136
|
-
/* @__PURE__ */
|
|
137
|
-
status:
|
|
135
|
+
K.isFeatured && /* @__PURE__ */ N(A, { className: "h-4 w-4 fill-status-warning-text text-status-warning-text" }),
|
|
136
|
+
/* @__PURE__ */ N(r, {
|
|
137
|
+
status: J ? "published" : "draft",
|
|
138
138
|
className: "ml-auto"
|
|
139
139
|
})
|
|
140
140
|
]
|
|
141
141
|
}),
|
|
142
|
-
|
|
142
|
+
K.caption && /* @__PURE__ */ N("p", {
|
|
143
143
|
className: "mt-3 text-sm text-text-secondary",
|
|
144
|
-
children:
|
|
144
|
+
children: K.caption
|
|
145
145
|
}),
|
|
146
|
-
|
|
147
|
-
tags:
|
|
146
|
+
K.tags.length > 0 && /* @__PURE__ */ N(v, {
|
|
147
|
+
tags: K.tags,
|
|
148
148
|
className: "mt-4",
|
|
149
149
|
tone: "accent"
|
|
150
150
|
}),
|
|
151
|
-
/* @__PURE__ */
|
|
151
|
+
/* @__PURE__ */ P("div", {
|
|
152
152
|
className: "mt-4 flex flex-wrap items-center gap-3 text-sm text-text-secondary",
|
|
153
|
-
children: [/* @__PURE__ */
|
|
154
|
-
followeeId:
|
|
153
|
+
children: [/* @__PURE__ */ N(R, { authorId: K.authorId }), !Y && /* @__PURE__ */ N(b, {
|
|
154
|
+
followeeId: K.authorId,
|
|
155
155
|
size: "sm"
|
|
156
156
|
})]
|
|
157
157
|
}),
|
|
158
|
-
/* @__PURE__ */
|
|
158
|
+
/* @__PURE__ */ P("div", {
|
|
159
159
|
className: "mt-5 flex items-center gap-3",
|
|
160
|
-
children: [/* @__PURE__ */
|
|
160
|
+
children: [/* @__PURE__ */ P("button", {
|
|
161
161
|
type: "button",
|
|
162
|
-
disabled:
|
|
163
|
-
|
|
164
|
-
|
|
162
|
+
disabled: z.isPending || B.isPending,
|
|
163
|
+
"aria-pressed": !!K.isLikedByMe,
|
|
164
|
+
onClick: () => (K.isLikedByMe ? B : z).mutate(K.id),
|
|
165
|
+
className: t("inline-flex items-center gap-1.5 rounded-lg border px-3 py-2 text-sm font-medium transition-colors disabled:opacity-50", K.isLikedByMe ? "border-status-error-text/40 bg-status-error-bg-subtle text-status-error-text" : "border-border-seam text-text-primary hover:bg-status-error-bg-subtle"),
|
|
165
166
|
children: [
|
|
166
|
-
|
|
167
|
-
"
|
|
168
|
-
|
|
167
|
+
z.isPending || B.isPending ? /* @__PURE__ */ N(D, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ N(E, { className: t("h-4 w-4", K.isLikedByMe && "fill-current") }),
|
|
168
|
+
K.isLikedByMe ? "Liked" : "Like",
|
|
169
|
+
" · ",
|
|
170
|
+
K.likeCount
|
|
169
171
|
]
|
|
170
|
-
}), /* @__PURE__ */
|
|
171
|
-
|
|
172
|
+
}), /* @__PURE__ */ P("button", {
|
|
173
|
+
type: "button",
|
|
174
|
+
disabled: V.isPending || H.isPending,
|
|
175
|
+
"aria-pressed": !!K.isSavedByMe,
|
|
176
|
+
onClick: () => (K.isSavedByMe ? H : V).mutate(K.id),
|
|
177
|
+
className: t("inline-flex items-center gap-1.5 rounded-lg border px-3 py-2 text-sm font-medium transition-colors disabled:opacity-50", K.isSavedByMe ? "border-accent-primary/40 bg-accent-primary/10 text-accent-primary" : "border-border-seam text-text-secondary hover:bg-bg-sunken"),
|
|
172
178
|
children: [
|
|
173
|
-
/* @__PURE__ */
|
|
174
|
-
" ",
|
|
175
|
-
|
|
176
|
-
|
|
179
|
+
V.isPending || H.isPending ? /* @__PURE__ */ N(D, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ N(T, { className: t("h-4 w-4", K.isSavedByMe && "fill-current") }),
|
|
180
|
+
K.isSavedByMe ? "Saved" : "Save",
|
|
181
|
+
" · ",
|
|
182
|
+
K.saveCount
|
|
177
183
|
]
|
|
178
184
|
})]
|
|
179
185
|
})
|
|
180
186
|
]
|
|
181
187
|
})]
|
|
182
188
|
}),
|
|
183
|
-
/* @__PURE__ */
|
|
189
|
+
/* @__PURE__ */ P("section", {
|
|
184
190
|
className: "mt-6",
|
|
185
|
-
children: [/* @__PURE__ */
|
|
191
|
+
children: [/* @__PURE__ */ P("h3", {
|
|
186
192
|
className: "mb-3 text-lg font-semibold text-text-primary",
|
|
187
193
|
children: [
|
|
188
194
|
"Shop the look (",
|
|
189
|
-
|
|
195
|
+
q.length,
|
|
190
196
|
")"
|
|
191
197
|
]
|
|
192
|
-
}),
|
|
198
|
+
}), q.length === 0 ? /* @__PURE__ */ N("p", {
|
|
193
199
|
className: "rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary",
|
|
194
200
|
children: "No items tagged in this look yet."
|
|
195
|
-
}) : /* @__PURE__ */
|
|
201
|
+
}) : /* @__PURE__ */ N("div", {
|
|
196
202
|
className: "grid grid-cols-1 gap-3 sm:grid-cols-2",
|
|
197
|
-
children: [...
|
|
203
|
+
children: [...q].sort((e, t) => e.position - t.position).map((e) => e.styleItem ? /* @__PURE__ */ N(x, {
|
|
198
204
|
id: e.styleItem.id,
|
|
199
205
|
name: e.styleItem.name,
|
|
200
206
|
brandName: e.styleItem.brand?.name,
|
|
201
207
|
price: e.styleItem.price,
|
|
202
208
|
currency: e.styleItem.currency,
|
|
203
209
|
imageFileId: e.styleItem.image?.id,
|
|
204
|
-
onOpen: () =>
|
|
205
|
-
}, e.id) : /* @__PURE__ */
|
|
210
|
+
onOpen: () => C(`/items/${e.styleItem?.id}`)
|
|
211
|
+
}, e.id) : /* @__PURE__ */ P("div", {
|
|
206
212
|
className: "flex flex-col justify-center rounded-xl border border-border-seam bg-bg-surface p-4",
|
|
207
|
-
children: [/* @__PURE__ */
|
|
213
|
+
children: [/* @__PURE__ */ N("p", {
|
|
208
214
|
className: "truncate text-sm font-medium text-text-primary",
|
|
209
215
|
children: "Catalogue piece"
|
|
210
|
-
}), e.styling && /* @__PURE__ */
|
|
216
|
+
}), e.styling && /* @__PURE__ */ N("p", {
|
|
211
217
|
className: "mt-0.5 truncate text-xs text-text-secondary",
|
|
212
218
|
children: e.styling
|
|
213
219
|
})]
|
|
214
220
|
}, e.id))
|
|
215
221
|
})]
|
|
216
222
|
}),
|
|
217
|
-
/* @__PURE__ */
|
|
223
|
+
/* @__PURE__ */ N(y, {
|
|
218
224
|
entityType: "CooLook",
|
|
219
|
-
entityId:
|
|
225
|
+
entityId: K.id
|
|
220
226
|
})
|
|
221
227
|
]
|
|
222
|
-
}), /* @__PURE__ */
|
|
228
|
+
}), /* @__PURE__ */ P("div", {
|
|
223
229
|
className: "space-y-4",
|
|
224
230
|
children: [
|
|
225
|
-
/* @__PURE__ */
|
|
231
|
+
/* @__PURE__ */ P(F, {
|
|
226
232
|
treatment: "plain",
|
|
227
233
|
className: "p-5",
|
|
228
|
-
children: [/* @__PURE__ */
|
|
234
|
+
children: [/* @__PURE__ */ N("h3", {
|
|
229
235
|
className: "mb-3 text-sm font-semibold text-text-primary",
|
|
230
236
|
children: "Details"
|
|
231
|
-
}), /* @__PURE__ */
|
|
237
|
+
}), /* @__PURE__ */ P("dl", {
|
|
232
238
|
className: "space-y-2 text-sm",
|
|
233
239
|
children: [
|
|
234
|
-
/* @__PURE__ */
|
|
240
|
+
/* @__PURE__ */ P("div", {
|
|
235
241
|
className: "flex justify-between gap-2",
|
|
236
|
-
children: [/* @__PURE__ */
|
|
242
|
+
children: [/* @__PURE__ */ N("dt", {
|
|
237
243
|
className: "text-text-secondary",
|
|
238
244
|
children: "Occasion"
|
|
239
|
-
}), /* @__PURE__ */
|
|
245
|
+
}), /* @__PURE__ */ N("dd", {
|
|
240
246
|
className: "text-text-primary",
|
|
241
|
-
children:
|
|
247
|
+
children: _(K.occasion)
|
|
242
248
|
})]
|
|
243
249
|
}),
|
|
244
|
-
/* @__PURE__ */
|
|
250
|
+
/* @__PURE__ */ P("div", {
|
|
245
251
|
className: "flex justify-between gap-2",
|
|
246
|
-
children: [/* @__PURE__ */
|
|
252
|
+
children: [/* @__PURE__ */ N("dt", {
|
|
247
253
|
className: "text-text-secondary",
|
|
248
254
|
children: "Visibility"
|
|
249
|
-
}), /* @__PURE__ */
|
|
255
|
+
}), /* @__PURE__ */ N("dd", {
|
|
250
256
|
className: "text-text-primary",
|
|
251
|
-
children:
|
|
257
|
+
children: _(K.visibility)
|
|
252
258
|
})]
|
|
253
259
|
}),
|
|
254
|
-
/* @__PURE__ */
|
|
260
|
+
/* @__PURE__ */ P("div", {
|
|
255
261
|
className: "flex justify-between gap-2",
|
|
256
|
-
children: [/* @__PURE__ */
|
|
262
|
+
children: [/* @__PURE__ */ N("dt", {
|
|
257
263
|
className: "text-text-secondary",
|
|
258
264
|
children: "Published"
|
|
259
|
-
}), /* @__PURE__ */
|
|
265
|
+
}), /* @__PURE__ */ N("dd", {
|
|
260
266
|
className: "text-text-primary",
|
|
261
|
-
children:
|
|
267
|
+
children: K.publishedAt ? g(K.publishedAt, {
|
|
262
268
|
hour: void 0,
|
|
263
269
|
minute: void 0
|
|
264
270
|
}) : "—"
|
|
@@ -267,27 +273,27 @@ function I() {
|
|
|
267
273
|
]
|
|
268
274
|
})]
|
|
269
275
|
}),
|
|
270
|
-
|
|
276
|
+
K.collaborationId && /* @__PURE__ */ P("button", {
|
|
271
277
|
type: "button",
|
|
272
|
-
onClick: () =>
|
|
278
|
+
onClick: () => C(`/collaborations/${K.collaborationId}`),
|
|
273
279
|
className: "flex w-full items-center gap-3 rounded-lg border border-border-seam bg-bg-surface p-4 text-left transition-colors hover:bg-bg-sunken/50",
|
|
274
|
-
children: [/* @__PURE__ */
|
|
280
|
+
children: [/* @__PURE__ */ N("div", {
|
|
275
281
|
className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-status-warning-bg-subtle text-status-warning-text",
|
|
276
|
-
children: /* @__PURE__ */
|
|
277
|
-
}), /* @__PURE__ */
|
|
282
|
+
children: /* @__PURE__ */ N(k, { className: "h-5 w-5" })
|
|
283
|
+
}), /* @__PURE__ */ P("div", {
|
|
278
284
|
className: "min-w-0",
|
|
279
|
-
children: [/* @__PURE__ */
|
|
285
|
+
children: [/* @__PURE__ */ N("p", {
|
|
280
286
|
className: "truncate text-sm font-medium text-text-primary",
|
|
281
287
|
children: "Part of a collaboration"
|
|
282
|
-
}), /* @__PURE__ */
|
|
288
|
+
}), /* @__PURE__ */ N("p", {
|
|
283
289
|
className: "truncate text-xs text-text-secondary",
|
|
284
290
|
children: "View collaboration"
|
|
285
291
|
})]
|
|
286
292
|
})]
|
|
287
293
|
}),
|
|
288
|
-
(
|
|
294
|
+
(U.isError || W.isError || z.isError || B.isError || V.isError || H.isError) && /* @__PURE__ */ N("p", {
|
|
289
295
|
className: "rounded-lg bg-status-error-bg-subtle px-3 py-2 text-xs text-status-error-text",
|
|
290
|
-
children: (
|
|
296
|
+
children: (U.error ?? W.error ?? z.error ?? B.error ?? V.error ?? H.error) instanceof Error ? (U.error ?? W.error ?? z.error ?? B.error ?? V.error ?? H.error)?.message : "Action failed"
|
|
291
297
|
})
|
|
292
298
|
]
|
|
293
299
|
})]
|
|
@@ -295,6 +301,6 @@ function I() {
|
|
|
295
301
|
});
|
|
296
302
|
}
|
|
297
303
|
//#endregion
|
|
298
|
-
export {
|
|
304
|
+
export { z as LookDetailPage };
|
|
299
305
|
|
|
300
306
|
//# sourceMappingURL=LookDetailPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LookDetailPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/looks/LookDetailPage.tsx"],"sourcesContent":["/**\n * Look Detail Page\n * @module coolandlovely/pages/looks\n *\n * Single look (`cooLook`) with its look items, like (`cooLikeLook`), publish\n * (`cooPublishLook`) and delete (`cooDeleteLook`) actions.\n */\n\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, Heart, Bookmark, Rocket, Trash2, Loader2, Sparkles, Star } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { useActorProfiles, type ActorInput } from '@burdenoff/fe-libs/shared';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { TagList } from '../../components/TagList';\nimport { Comments } from '../../components/Comments';\nimport { FollowButton } from '../../components/FollowButton';\nimport { AddToLookbookButton } from '../../components/AddToLookbookButton';\nimport { ShopItemCard } from '../../components/ShopItemCard';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useLook, useLikeLook, usePublishLook, useDeleteLook, useUpdateLook } from '../../hooks';\nimport { formatDate, titleCase } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\n\n/** Compute up-to-two-letter initials for the avatar fallback. */\nfunction initials(name: string): string {\n const parts = name.trim().split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return '?';\n if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();\n return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();\n}\n\n/**\n * Author chip — avatar + display name resolved via fe-libs `useActorProfiles`\n * on the GLOBAL gateway (never select firstName on a federated wspace User).\n */\nfunction LookAuthor({ authorId }: { authorId: string }) {\n const { apiGatewayUrl, authToken, workspaceId } = useCoolAndLovely();\n const [avatarFailed, setAvatarFailed] = useState(false);\n const actors = useMemo<ActorInput[]>(\n () => [{ actorId: authorId, actorType: 'user' }],\n [authorId]\n );\n const profiles = useActorProfiles(actors, { apiGatewayUrl, authToken, workspaceId });\n const profile = profiles[authorId];\n const name = profile?.displayName ?? 'Creator';\n const avatarUrl = profile?.avatarUrl;\n\n return (\n <span className=\"inline-flex items-center gap-2\">\n <span\n title={name}\n className=\"relative flex h-7 w-7 shrink-0 items-center justify-center overflow-hidden rounded-full bg-bg-sunken ring-1 ring-border-seam\"\n >\n {avatarUrl && !avatarFailed ? (\n <img\n src={avatarUrl}\n alt={name}\n loading=\"lazy\"\n onError={() => setAvatarFailed(true)}\n className=\"h-full w-full object-cover\"\n />\n ) : (\n <span className=\"text-[10px] font-semibold text-text-secondary\">{initials(name)}</span>\n )}\n </span>\n <span className=\"font-medium text-text-primary\" title={profile?.email ?? authorId}>\n {name}\n </span>\n </span>\n );\n}\n\nexport function LookDetailPage() {\n const { lookId } = useParams<{ lookId: string }>();\n const { navigateTo, currentUser } = useCoolAndLovely();\n const query = useLook(lookId);\n const like = useLikeLook();\n const publish = usePublishLook();\n const remove = useDeleteLook();\n const update = useUpdateLook();\n\n if (query.isLoading) {\n return (\n <PageLayout title=\"Look\">\n <LoadingView label=\"Loading look…\" />\n </PageLayout>\n );\n }\n\n if (query.isError || !query.data) {\n return (\n <PageLayout title=\"Look\">\n <ErrorView title=\"Look not found\" onRetry={() => void query.refetch()} />\n </PageLayout>\n );\n }\n\n const look = query.data;\n const items = look.items ?? [];\n const isPublished = look.publishedAt !== null;\n const isOwnLook = Boolean(currentUser?.id && currentUser.id === look.authorId);\n\n return (\n <PageLayout\n title={look.title}\n description={titleCase(look.occasion)}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/looks')}\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\" /> Back\n </button>\n <AddToLookbookButton lookId={look.id} />\n <button\n type=\"button\"\n disabled={update.isPending}\n onClick={() => update.mutate({ id: look.id, input: { isFeatured: !look.isFeatured } })}\n aria-pressed={look.isFeatured}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-lg border px-3 py-2 text-sm font-medium transition-colors disabled:opacity-50',\n look.isFeatured\n ? 'border-status-warning-text/40 bg-status-warning-bg-subtle text-status-warning-text'\n : 'border-border-seam text-text-primary hover:bg-bg-sunken'\n )}\n >\n {update.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Star className={cn('h-4 w-4', look.isFeatured && 'fill-status-warning-text')} />\n )}\n {look.isFeatured ? 'Featured' : 'Feature'}\n </button>\n {!isPublished && (\n <button\n type=\"button\"\n disabled={publish.isPending}\n onClick={() => publish.mutate(look.id)}\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 disabled:opacity-50\"\n >\n {publish.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Rocket className=\"h-4 w-4\" />\n )}\n Publish\n </button>\n )}\n <button\n type=\"button\"\n disabled={remove.isPending}\n onClick={() => remove.mutate(look.id, { onSuccess: () => navigateTo('/looks') })}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-50\"\n >\n {remove.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Trash2 className=\"h-4 w-4\" />\n )}\n Delete\n </button>\n </div>\n }\n >\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"lg:col-span-2\">\n <GlassCard treatment=\"glass\" className=\"group overflow-hidden p-0\">\n <Thumbnail\n icon={<Sparkles className=\"h-14 w-14\" />}\n seed={look.id}\n fileId={look.image?.id}\n alt={look.title}\n className=\"aspect-[4/5] max-h-[38rem] w-full rounded-none\"\n />\n <div className=\"p-6\">\n <div className=\"flex items-center gap-2\">\n <h2 className=\"text-xl font-semibold text-text-primary\">{look.title}</h2>\n {look.isFeatured && (\n <Star className=\"h-4 w-4 fill-status-warning-text text-status-warning-text\" />\n )}\n <StatusBadge status={isPublished ? 'published' : 'draft'} className=\"ml-auto\" />\n </div>\n {look.caption && <p className=\"mt-3 text-sm text-text-secondary\">{look.caption}</p>}\n {look.tags.length > 0 && <TagList tags={look.tags} className=\"mt-4\" tone=\"accent\" />}\n\n <div className=\"mt-4 flex flex-wrap items-center gap-3 text-sm text-text-secondary\">\n <LookAuthor authorId={look.authorId} />\n {!isOwnLook && <FollowButton followeeId={look.authorId} size=\"sm\" />}\n </div>\n\n <div className=\"mt-5 flex items-center gap-3\">\n <button\n type=\"button\"\n disabled={like.isPending}\n onClick={() => like.mutate(look.id)}\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-status-error-bg-subtle disabled:opacity-50\"\n >\n {like.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Heart className=\"h-4 w-4\" />\n )}\n Like · {look.likeCount}\n </button>\n <span className=\"inline-flex items-center gap-1.5 text-sm text-text-secondary\">\n <Bookmark className=\"h-4 w-4\" /> {look.saveCount} saves\n </span>\n </div>\n </div>\n </GlassCard>\n\n {/* Shop the look */}\n <section className=\"mt-6\">\n <h3 className=\"mb-3 text-lg font-semibold text-text-primary\">\n Shop the look ({items.length})\n </h3>\n {items.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n No items tagged in this look yet.\n </p>\n ) : (\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n {[...items]\n .sort((a, b) => a.position - b.position)\n .map((li) =>\n li.styleItem ? (\n <ShopItemCard\n key={li.id}\n id={li.styleItem.id}\n name={li.styleItem.name}\n brandName={li.styleItem.brand?.name}\n price={li.styleItem.price}\n currency={li.styleItem.currency}\n imageFileId={li.styleItem.image?.id}\n onOpen={() => navigateTo(`/items/${li.styleItem?.id}`)}\n />\n ) : (\n <div\n key={li.id}\n className=\"flex flex-col justify-center rounded-xl border border-border-seam bg-bg-surface p-4\"\n >\n <p className=\"truncate text-sm font-medium text-text-primary\">\n Catalogue piece\n </p>\n {li.styling && (\n <p className=\"mt-0.5 truncate text-xs text-text-secondary\">\n {li.styling}\n </p>\n )}\n </div>\n )\n )}\n </div>\n )}\n </section>\n\n <Comments entityType=\"CooLook\" entityId={look.id} />\n </div>\n\n <div className=\"space-y-4\">\n <GlassCard treatment=\"plain\" className=\"p-5\">\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">Details</h3>\n <dl className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Occasion</dt>\n <dd className=\"text-text-primary\">{titleCase(look.occasion)}</dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Visibility</dt>\n <dd className=\"text-text-primary\">{titleCase(look.visibility)}</dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Published</dt>\n <dd className=\"text-text-primary\">\n {look.publishedAt\n ? formatDate(look.publishedAt, { hour: undefined, minute: undefined })\n : '—'}\n </dd>\n </div>\n </dl>\n </GlassCard>\n\n {look.collaborationId && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/collaborations/${look.collaborationId}`)}\n className=\"flex w-full items-center gap-3 rounded-lg border border-border-seam bg-bg-surface p-4 text-left transition-colors hover:bg-bg-sunken/50\"\n >\n <div className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-status-warning-bg-subtle text-status-warning-text\">\n <Sparkles className=\"h-5 w-5\" />\n </div>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n Part of a collaboration\n </p>\n <p className=\"truncate text-xs text-text-secondary\">View collaboration</p>\n </div>\n </button>\n )}\n\n {(publish.isError || remove.isError || like.isError) && (\n <p className=\"rounded-lg bg-status-error-bg-subtle px-3 py-2 text-xs text-status-error-text\">\n {(publish.error ?? remove.error ?? like.error) instanceof Error\n ? (publish.error ?? remove.error ?? like.error)?.message\n : 'Action failed'}\n </p>\n )}\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA4BA,SAAS,EAAS,GAAsB;CACtC,IAAM,IAAQ,EAAK,MAAM,CAAC,MAAM,MAAM,CAAC,OAAO,QAAQ;AAGtD,QAFI,EAAM,WAAW,IAAU,MAC3B,EAAM,WAAW,IAAU,EAAM,GAAG,MAAM,GAAG,EAAE,CAAC,aAAa,IACzD,EAAM,GAAG,KAAK,EAAM,EAAM,SAAS,GAAG,IAAI,aAAa;;AAOjE,SAAS,EAAW,EAAE,eAAkC;CACtD,IAAM,EAAE,kBAAe,cAAW,mBAAgB,GAAkB,EAC9D,CAAC,GAAc,KAAmB,EAAS,GAAM,EAMjD,IADW,EAJF,QACP,CAAC;EAAE,SAAS;EAAU,WAAW;EAAQ,CAAC,EAChD,CAAC,EAAS,CACX,EACyC;EAAE;EAAe;EAAW;EAAa,CAAC,CAC3D,IACnB,IAAO,GAAS,eAAe,WAC/B,IAAY,GAAS;AAE3B,QACE,kBAAC,QAAD;EAAM,WAAU;YAAhB,CACE,kBAAC,QAAD;GACE,OAAO;GACP,WAAU;aAET,KAAa,CAAC,IACb,kBAAC,OAAD;IACE,KAAK;IACL,KAAK;IACL,SAAQ;IACR,eAAe,EAAgB,GAAK;IACpC,WAAU;IACV,CAAA,GAEF,kBAAC,QAAD;IAAM,WAAU;cAAiD,EAAS,EAAK;IAAQ,CAAA;GAEpF,CAAA,EACP,kBAAC,QAAD;GAAM,WAAU;GAAgC,OAAO,GAAS,SAAS;aACtE;GACI,CAAA,CACF;;;AAIX,SAAgB,IAAiB;CAC/B,IAAM,EAAE,cAAW,GAA+B,EAC5C,EAAE,eAAY,mBAAgB,GAAkB,EAChD,IAAQ,EAAQ,EAAO,EACvB,IAAO,GAAa,EACpB,IAAU,GAAgB,EAC1B,IAAS,GAAe,EACxB,IAAS,GAAe;AAE9B,KAAI,EAAM,UACR,QACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD,EAAa,OAAM,iBAAkB,CAAA;EAC1B,CAAA;AAIjB,KAAI,EAAM,WAAW,CAAC,EAAM,KAC1B,QACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD;GAAW,OAAM;GAAiB,eAAe,KAAK,EAAM,SAAS;GAAI,CAAA;EAC9D,CAAA;CAIjB,IAAM,IAAO,EAAM,MACb,IAAQ,EAAK,SAAS,EAAE,EACxB,IAAc,EAAK,gBAAgB,MACnC,IAAY,GAAQ,GAAa,MAAM,EAAY,OAAO,EAAK;AAErE,QACE,kBAAC,GAAD;EACE,OAAO,EAAK;EACZ,aAAa,EAAU,EAAK,SAAS;EACrC,SACE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,SAAS;KACnC,WAAU;eAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA,EAAA,QAC1B;;IACT,kBAAC,GAAD,EAAqB,QAAQ,EAAK,IAAM,CAAA;IACxC,kBAAC,UAAD;KACE,MAAK;KACL,UAAU,EAAO;KACjB,eAAe,EAAO,OAAO;MAAE,IAAI,EAAK;MAAI,OAAO,EAAE,YAAY,CAAC,EAAK,YAAY;MAAE,CAAC;KACtF,gBAAc,EAAK;KACnB,WAAW,EACT,0HACA,EAAK,aACD,uFACA,0DACL;eAVH,CAYG,EAAO,YACN,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,GAE5C,kBAAC,GAAD,EAAM,WAAW,EAAG,WAAW,EAAK,cAAc,2BAA2B,EAAI,CAAA,EAElF,EAAK,aAAa,aAAa,UACzB;;IACR,CAAC,KACA,kBAAC,UAAD;KACE,MAAK;KACL,UAAU,EAAQ;KAClB,eAAe,EAAQ,OAAO,EAAK,GAAG;KACtC,WAAU;eAJZ,CAMG,EAAQ,YACP,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,GAE5C,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA,EAC9B,UAEK;;IAEX,kBAAC,UAAD;KACE,MAAK;KACL,UAAU,EAAO;KACjB,eAAe,EAAO,OAAO,EAAK,IAAI,EAAE,iBAAiB,EAAW,SAAS,EAAE,CAAC;KAChF,WAAU;eAJZ,CAMG,EAAO,YACN,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,GAE5C,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA,EAC9B,SAEK;;IACL;;YAGR,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MAAW,WAAU;MAAQ,WAAU;gBAAvC,CACE,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAU,WAAU,aAAc,CAAA;OACxC,MAAM,EAAK;OACX,QAAQ,EAAK,OAAO;OACpB,KAAK,EAAK;OACV,WAAU;OACV,CAAA,EACF,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,MAAD;WAAI,WAAU;qBAA2C,EAAK;WAAW,CAAA;UACxE,EAAK,cACJ,kBAAC,GAAD,EAAM,WAAU,6DAA8D,CAAA;UAEhF,kBAAC,GAAD;WAAa,QAAQ,IAAc,cAAc;WAAS,WAAU;WAAY,CAAA;UAC5E;;QACL,EAAK,WAAW,kBAAC,KAAD;SAAG,WAAU;mBAAoC,EAAK;SAAY,CAAA;QAClF,EAAK,KAAK,SAAS,KAAK,kBAAC,GAAD;SAAS,MAAM,EAAK;SAAM,WAAU;SAAO,MAAK;SAAW,CAAA;QAEpF,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD,EAAY,UAAU,EAAK,UAAY,CAAA,EACtC,CAAC,KAAa,kBAAC,GAAD;UAAc,YAAY,EAAK;UAAU,MAAK;UAAO,CAAA,CAChE;;QAEN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,UAAD;UACE,MAAK;UACL,UAAU,EAAK;UACf,eAAe,EAAK,OAAO,EAAK,GAAG;UACnC,WAAU;oBAJZ;WAMG,EAAK,YACJ,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,GAE5C,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;WAC7B;WACM,EAAK;WACN;aACT,kBAAC,QAAD;UAAM,WAAU;oBAAhB;WACE,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;;WAAE,EAAK;WAAU;WAC5C;YACH;;QACF;SACI;;KAGZ,kBAAC,WAAD;MAAS,WAAU;gBAAnB,CACE,kBAAC,MAAD;OAAI,WAAU;iBAAd;QAA6D;QAC3C,EAAM;QAAO;QAC1B;UACJ,EAAM,WAAW,IAChB,kBAAC,KAAD;OAAG,WAAU;iBAAqF;OAE9F,CAAA,GAEJ,kBAAC,OAAD;OAAK,WAAU;iBACZ,CAAC,GAAG,EAAM,CACR,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,SAAS,CACvC,KAAK,MACJ,EAAG,YACD,kBAAC,GAAD;QAEE,IAAI,EAAG,UAAU;QACjB,MAAM,EAAG,UAAU;QACnB,WAAW,EAAG,UAAU,OAAO;QAC/B,OAAO,EAAG,UAAU;QACpB,UAAU,EAAG,UAAU;QACvB,aAAa,EAAG,UAAU,OAAO;QACjC,cAAc,EAAW,UAAU,EAAG,WAAW,KAAK;QACtD,EARK,EAAG,GAQR,GAEF,kBAAC,OAAD;QAEE,WAAU;kBAFZ,CAIE,kBAAC,KAAD;SAAG,WAAU;mBAAiD;SAE1D,CAAA,EACH,EAAG,WACF,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAG;SACF,CAAA,CAEF;UAXC,EAAG,GAWJ,CAET;OACC,CAAA,CAEA;;KAEV,kBAAC,GAAD;MAAU,YAAW;MAAU,UAAU,EAAK;MAAM,CAAA;KAChD;OAEN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MAAW,WAAU;MAAQ,WAAU;gBAAvC,CACE,kBAAC,MAAD;OAAI,WAAU;iBAA+C;OAAY,CAAA,EACzE,kBAAC,MAAD;OAAI,WAAU;iBAAd;QACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,MAAD;UAAI,WAAU;oBAAsB;UAAa,CAAA,EACjD,kBAAC,MAAD;UAAI,WAAU;oBAAqB,EAAU,EAAK,SAAS;UAAM,CAAA,CAC7D;;QACN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,MAAD;UAAI,WAAU;oBAAsB;UAAe,CAAA,EACnD,kBAAC,MAAD;UAAI,WAAU;oBAAqB,EAAU,EAAK,WAAW;UAAM,CAAA,CAC/D;;QACN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,MAAD;UAAI,WAAU;oBAAsB;UAAc,CAAA,EAClD,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAK,cACF,EAAW,EAAK,aAAa;WAAE,MAAM,KAAA;WAAW,QAAQ,KAAA;WAAW,CAAC,GACpE;UACD,CAAA,CACD;;QACH;SACK;;KAEX,EAAK,mBACJ,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAW,mBAAmB,EAAK,kBAAkB;MACpE,WAAU;gBAHZ,CAKE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;OAC5B,CAAA,EACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,KAAD;QAAG,WAAU;kBAAiD;QAE1D,CAAA,EACJ,kBAAC,KAAD;QAAG,WAAU;kBAAuC;QAAsB,CAAA,CACtE;SACC;;MAGT,EAAQ,WAAW,EAAO,WAAW,EAAK,YAC1C,kBAAC,KAAD;MAAG,WAAU;iBACT,EAAQ,SAAS,EAAO,SAAS,EAAK,kBAAkB,SACrD,EAAQ,SAAS,EAAO,SAAS,EAAK,QAAQ,UAC/C;MACF,CAAA;KAEF;MACF;;EACK,CAAA"}
|
|
1
|
+
{"version":3,"file":"LookDetailPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/looks/LookDetailPage.tsx"],"sourcesContent":["/**\n * Look Detail Page\n * @module coolandlovely/pages/looks\n *\n * Single look (`cooLook`) with its look items, a true-toggle like\n * (`cooLikeLook` / `cooUnlikeLook`), a true-toggle save (`cooSaveLook` /\n * `cooUnsaveLook`), publish (`cooPublishLook`) and delete (`cooDeleteLook`)\n * actions.\n */\n\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, Heart, Bookmark, Rocket, Trash2, Loader2, Sparkles, Star } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { useActorProfiles, type ActorInput } from '@burdenoff/fe-libs/shared';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { TagList } from '../../components/TagList';\nimport { Comments } from '../../components/Comments';\nimport { FollowButton } from '../../components/FollowButton';\nimport { AddToLookbookButton } from '../../components/AddToLookbookButton';\nimport { ShopItemCard } from '../../components/ShopItemCard';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport {\n useLook,\n useLikeLook,\n useUnlikeLook,\n useSaveLook,\n useUnsaveLook,\n usePublishLook,\n useDeleteLook,\n useUpdateLook,\n} from '../../hooks';\nimport { formatDate, titleCase } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\n\n/** Compute up-to-two-letter initials for the avatar fallback. */\nfunction initials(name: string): string {\n const parts = name.trim().split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return '?';\n if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();\n return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();\n}\n\n/**\n * Author chip — avatar + display name resolved via fe-libs `useActorProfiles`\n * on the GLOBAL gateway (never select firstName on a federated wspace User).\n */\nfunction LookAuthor({ authorId }: { authorId: string }) {\n const { apiGatewayUrl, authToken, workspaceId } = useCoolAndLovely();\n const [avatarFailed, setAvatarFailed] = useState(false);\n const actors = useMemo<ActorInput[]>(\n () => [{ actorId: authorId, actorType: 'user' }],\n [authorId]\n );\n const profiles = useActorProfiles(actors, { apiGatewayUrl, authToken, workspaceId });\n const profile = profiles[authorId];\n const name = profile?.displayName ?? 'Creator';\n const avatarUrl = profile?.avatarUrl;\n\n return (\n <span className=\"inline-flex items-center gap-2\">\n <span\n title={name}\n className=\"relative flex h-7 w-7 shrink-0 items-center justify-center overflow-hidden rounded-full bg-bg-sunken ring-1 ring-border-seam\"\n >\n {avatarUrl && !avatarFailed ? (\n <img\n src={avatarUrl}\n alt={name}\n loading=\"lazy\"\n onError={() => setAvatarFailed(true)}\n className=\"h-full w-full object-cover\"\n />\n ) : (\n <span className=\"text-[10px] font-semibold text-text-secondary\">{initials(name)}</span>\n )}\n </span>\n <span className=\"font-medium text-text-primary\" title={profile?.email ?? authorId}>\n {name}\n </span>\n </span>\n );\n}\n\nexport function LookDetailPage() {\n const { lookId } = useParams<{ lookId: string }>();\n const { navigateTo, currentUser } = useCoolAndLovely();\n const query = useLook(lookId);\n const like = useLikeLook();\n const unlike = useUnlikeLook();\n const save = useSaveLook();\n const unsave = useUnsaveLook();\n const publish = usePublishLook();\n const remove = useDeleteLook();\n const update = useUpdateLook();\n\n if (query.isLoading) {\n return (\n <PageLayout title=\"Look\">\n <LoadingView label=\"Loading look…\" />\n </PageLayout>\n );\n }\n\n if (query.isError || !query.data) {\n return (\n <PageLayout title=\"Look\">\n <ErrorView title=\"Look not found\" onRetry={() => void query.refetch()} />\n </PageLayout>\n );\n }\n\n const look = query.data;\n const items = look.items ?? [];\n const isPublished = look.publishedAt !== null;\n const isOwnLook = Boolean(currentUser?.id && currentUser.id === look.authorId);\n\n return (\n <PageLayout\n title={look.title}\n description={titleCase(look.occasion)}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/looks')}\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\" /> Back\n </button>\n <AddToLookbookButton lookId={look.id} />\n <button\n type=\"button\"\n disabled={update.isPending}\n onClick={() => update.mutate({ id: look.id, input: { isFeatured: !look.isFeatured } })}\n aria-pressed={look.isFeatured}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-lg border px-3 py-2 text-sm font-medium transition-colors disabled:opacity-50',\n look.isFeatured\n ? 'border-status-warning-text/40 bg-status-warning-bg-subtle text-status-warning-text'\n : 'border-border-seam text-text-primary hover:bg-bg-sunken'\n )}\n >\n {update.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Star className={cn('h-4 w-4', look.isFeatured && 'fill-status-warning-text')} />\n )}\n {look.isFeatured ? 'Featured' : 'Feature'}\n </button>\n {!isPublished && (\n <button\n type=\"button\"\n disabled={publish.isPending}\n onClick={() => publish.mutate(look.id)}\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 disabled:opacity-50\"\n >\n {publish.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Rocket className=\"h-4 w-4\" />\n )}\n Publish\n </button>\n )}\n <button\n type=\"button\"\n disabled={remove.isPending}\n onClick={() => remove.mutate(look.id, { onSuccess: () => navigateTo('/looks') })}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-50\"\n >\n {remove.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Trash2 className=\"h-4 w-4\" />\n )}\n Delete\n </button>\n </div>\n }\n >\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"lg:col-span-2\">\n <GlassCard treatment=\"glass\" className=\"group overflow-hidden p-0\">\n <Thumbnail\n icon={<Sparkles className=\"h-14 w-14\" />}\n seed={look.id}\n fileId={look.image?.id}\n alt={look.title}\n className=\"aspect-[4/5] max-h-[38rem] w-full rounded-none\"\n />\n <div className=\"p-6\">\n <div className=\"flex items-center gap-2\">\n <h2 className=\"text-xl font-semibold text-text-primary\">{look.title}</h2>\n {look.isFeatured && (\n <Star className=\"h-4 w-4 fill-status-warning-text text-status-warning-text\" />\n )}\n <StatusBadge status={isPublished ? 'published' : 'draft'} className=\"ml-auto\" />\n </div>\n {look.caption && <p className=\"mt-3 text-sm text-text-secondary\">{look.caption}</p>}\n {look.tags.length > 0 && <TagList tags={look.tags} className=\"mt-4\" tone=\"accent\" />}\n\n <div className=\"mt-4 flex flex-wrap items-center gap-3 text-sm text-text-secondary\">\n <LookAuthor authorId={look.authorId} />\n {!isOwnLook && <FollowButton followeeId={look.authorId} size=\"sm\" />}\n </div>\n\n <div className=\"mt-5 flex items-center gap-3\">\n <button\n type=\"button\"\n disabled={like.isPending || unlike.isPending}\n aria-pressed={Boolean(look.isLikedByMe)}\n onClick={() => (look.isLikedByMe ? unlike : like).mutate(look.id)}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-lg border px-3 py-2 text-sm font-medium transition-colors disabled:opacity-50',\n look.isLikedByMe\n ? 'border-status-error-text/40 bg-status-error-bg-subtle text-status-error-text'\n : 'border-border-seam text-text-primary hover:bg-status-error-bg-subtle'\n )}\n >\n {like.isPending || unlike.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Heart className={cn('h-4 w-4', look.isLikedByMe && 'fill-current')} />\n )}\n {look.isLikedByMe ? 'Liked' : 'Like'} · {look.likeCount}\n </button>\n <button\n type=\"button\"\n disabled={save.isPending || unsave.isPending}\n aria-pressed={Boolean(look.isSavedByMe)}\n onClick={() => (look.isSavedByMe ? unsave : save).mutate(look.id)}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-lg border px-3 py-2 text-sm font-medium transition-colors disabled:opacity-50',\n look.isSavedByMe\n ? 'border-accent-primary/40 bg-accent-primary/10 text-accent-primary'\n : 'border-border-seam text-text-secondary hover:bg-bg-sunken'\n )}\n >\n {save.isPending || unsave.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Bookmark className={cn('h-4 w-4', look.isSavedByMe && 'fill-current')} />\n )}\n {look.isSavedByMe ? 'Saved' : 'Save'} · {look.saveCount}\n </button>\n </div>\n </div>\n </GlassCard>\n\n {/* Shop the look */}\n <section className=\"mt-6\">\n <h3 className=\"mb-3 text-lg font-semibold text-text-primary\">\n Shop the look ({items.length})\n </h3>\n {items.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n No items tagged in this look yet.\n </p>\n ) : (\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n {[...items]\n .sort((a, b) => a.position - b.position)\n .map((li) =>\n li.styleItem ? (\n <ShopItemCard\n key={li.id}\n id={li.styleItem.id}\n name={li.styleItem.name}\n brandName={li.styleItem.brand?.name}\n price={li.styleItem.price}\n currency={li.styleItem.currency}\n imageFileId={li.styleItem.image?.id}\n onOpen={() => navigateTo(`/items/${li.styleItem?.id}`)}\n />\n ) : (\n <div\n key={li.id}\n className=\"flex flex-col justify-center rounded-xl border border-border-seam bg-bg-surface p-4\"\n >\n <p className=\"truncate text-sm font-medium text-text-primary\">\n Catalogue piece\n </p>\n {li.styling && (\n <p className=\"mt-0.5 truncate text-xs text-text-secondary\">\n {li.styling}\n </p>\n )}\n </div>\n )\n )}\n </div>\n )}\n </section>\n\n <Comments entityType=\"CooLook\" entityId={look.id} />\n </div>\n\n <div className=\"space-y-4\">\n <GlassCard treatment=\"plain\" className=\"p-5\">\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">Details</h3>\n <dl className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Occasion</dt>\n <dd className=\"text-text-primary\">{titleCase(look.occasion)}</dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Visibility</dt>\n <dd className=\"text-text-primary\">{titleCase(look.visibility)}</dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Published</dt>\n <dd className=\"text-text-primary\">\n {look.publishedAt\n ? formatDate(look.publishedAt, { hour: undefined, minute: undefined })\n : '—'}\n </dd>\n </div>\n </dl>\n </GlassCard>\n\n {look.collaborationId && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/collaborations/${look.collaborationId}`)}\n className=\"flex w-full items-center gap-3 rounded-lg border border-border-seam bg-bg-surface p-4 text-left transition-colors hover:bg-bg-sunken/50\"\n >\n <div className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-status-warning-bg-subtle text-status-warning-text\">\n <Sparkles className=\"h-5 w-5\" />\n </div>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n Part of a collaboration\n </p>\n <p className=\"truncate text-xs text-text-secondary\">View collaboration</p>\n </div>\n </button>\n )}\n\n {(publish.isError ||\n remove.isError ||\n like.isError ||\n unlike.isError ||\n save.isError ||\n unsave.isError) && (\n <p className=\"rounded-lg bg-status-error-bg-subtle px-3 py-2 text-xs text-status-error-text\">\n {(publish.error ??\n remove.error ??\n like.error ??\n unlike.error ??\n save.error ??\n unsave.error) instanceof Error\n ? (\n publish.error ??\n remove.error ??\n like.error ??\n unlike.error ??\n save.error ??\n unsave.error\n )?.message\n : 'Action failed'}\n </p>\n )}\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LooksListPage.d.ts","sourceRoot":"","sources":["../../../../src/coolandlovely/pages/looks/LooksListPage.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA4BH,wBAAgB,aAAa,
|
|
1
|
+
{"version":3,"file":"LooksListPage.d.ts","sourceRoot":"","sources":["../../../../src/coolandlovely/pages/looks/LooksListPage.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA4BH,wBAAgB,aAAa,gCA6I5B"}
|
|
@@ -109,6 +109,8 @@ function b() {
|
|
|
109
109
|
occasion: e.occasion,
|
|
110
110
|
imageFileId: e.image?.id,
|
|
111
111
|
likeCount: e.likeCount,
|
|
112
|
+
isLikedByMe: e.isLikedByMe,
|
|
113
|
+
isSavedByMe: e.isSavedByMe,
|
|
112
114
|
onOpen: () => b(`/looks/${e.id}`),
|
|
113
115
|
showActions: !0
|
|
114
116
|
}), (e.isFeatured || e.publishedAt === null) && /* @__PURE__ */ _("div", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LooksListPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/looks/LooksListPage.tsx"],"sourcesContent":["/**\n * Looks List Page\n * @module coolandlovely/pages/looks\n *\n * Cursor-paginated feed of published looks (`cooListLooks`) with occasion &\n * featured filters.\n */\n\nimport { useState } from 'react';\nimport { Plus, Sparkles, ChevronLeft, ChevronRight, Star } 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 { LookCard } from '../../components/LookCard';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useLooks } from '../../hooks';\nimport { cn } from '../../utils/cn';\nimport type { CooLookOccasion } from '../../types';\n\nconst PAGE_SIZE = 24;\n\nconst OCCASIONS: Array<{ value: CooLookOccasion | 'ALL'; label: string }> = [\n { value: 'ALL', label: 'All' },\n { value: 'EVERYDAY', label: 'Everyday' },\n { value: 'WORK', label: 'Work' },\n { value: 'EVENING', label: 'Evening' },\n { value: 'WEDDING_GUEST', label: 'Wedding guest' },\n { value: 'VACATION', label: 'Vacation' },\n { value: 'PARTY', label: 'Party' },\n { value: 'ACTIVE', label: 'Active' },\n { value: 'OTHER', label: 'Other' },\n];\n\nexport function LooksListPage() {\n const { navigateTo } = useCoolAndLovely();\n const [occasion, setOccasion] = useState<CooLookOccasion | 'ALL'>('ALL');\n const [featuredOnly, setFeaturedOnly] = useState(false);\n const [offset, setOffset] = useState(0);\n\n const query = useLooks({\n occasion: occasion === 'ALL' ? undefined : occasion,\n featured: featuredOnly ? true : undefined,\n pagination: { limit: PAGE_SIZE, offset },\n });\n\n const looks = query.data?.items ?? [];\n const hasMore = query.data?.pageInfo.hasMore ?? false;\n\n return (\n <PageLayout\n title=\"Looks\"\n description=\"Shop-the-look outfits built from real catalogue pieces\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/looks/new')}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" /> New look\n </button>\n }\n >\n <div className=\"mb-6 flex flex-wrap items-center gap-2\">\n {OCCASIONS.map((opt) => (\n <button\n key={opt.value}\n type=\"button\"\n onClick={() => {\n setOccasion(opt.value);\n setOffset(0);\n }}\n className={cn(\n 'rounded-full px-3 py-1.5 text-sm font-medium transition-colors',\n occasion === 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 <button\n type=\"button\"\n onClick={() => {\n setFeaturedOnly((v) => !v);\n setOffset(0);\n }}\n className={cn(\n 'ml-auto inline-flex items-center gap-1.5 rounded-full px-3 py-1.5 text-sm font-medium transition-colors',\n featuredOnly\n ? 'bg-status-warning-bg-subtle text-status-warning-text'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n <Star className={cn('h-3.5 w-3.5', featuredOnly && 'fill-status-warning-text')} />{' '}\n Featured\n </button>\n </div>\n\n {query.isLoading ? (\n <LoadingView label=\"Loading looks…\" />\n ) : query.isError ? (\n <ErrorView onRetry={() => void query.refetch()} />\n ) : looks.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Sparkles className=\"h-10 w-10\" />}\n title=\"No looks yet\"\n description=\"Publish a shop-the-look outfit from catalogue pieces.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/looks/new')}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text\"\n >\n <Plus className=\"h-4 w-4\" /> New look\n </button>\n }\n />\n ) : (\n <>\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-4\">\n {looks.map((look) => (\n <div key={look.id} className=\"relative\">\n <LookCard\n id={look.id}\n title={look.title}\n occasion={look.occasion}\n imageFileId={look.image?.id}\n likeCount={look.likeCount}\n onOpen={() => navigateTo(`/looks/${look.id}`)}\n showActions\n />\n {(look.isFeatured || look.publishedAt === null) && (\n <div className=\"pointer-events-none absolute left-2 top-2 z-10 flex flex-col items-start gap-1\">\n {look.isFeatured && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-warning-bg-subtle px-2 py-0.5 text-xs font-medium text-status-warning-text shadow-sm backdrop-blur\">\n <Star className=\"h-3 w-3 fill-status-warning-text\" /> Featured\n </span>\n )}\n {look.publishedAt === null && <StatusBadge status=\"draft\" />}\n </div>\n )}\n </div>\n ))}\n </div>\n\n {(offset > 0 || hasMore) && (\n <div className=\"mt-6 flex items-center justify-center gap-2\">\n <button\n type=\"button\"\n disabled={offset === 0}\n onClick={() => setOffset((o) => Math.max(0, o - PAGE_SIZE))}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-40\"\n >\n <ChevronLeft className=\"h-4 w-4\" /> Previous\n </button>\n <span className=\"text-sm text-text-secondary\">Page {offset / PAGE_SIZE + 1}</span>\n <button\n type=\"button\"\n disabled={!hasMore}\n onClick={() => setOffset((o) => o + PAGE_SIZE)}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-40\"\n >\n Next <ChevronRight className=\"h-4 w-4\" />\n </button>\n </div>\n )}\n </>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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
|
|
1
|
+
{"version":3,"file":"LooksListPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/looks/LooksListPage.tsx"],"sourcesContent":["/**\n * Looks List Page\n * @module coolandlovely/pages/looks\n *\n * Cursor-paginated feed of published looks (`cooListLooks`) with occasion &\n * featured filters.\n */\n\nimport { useState } from 'react';\nimport { Plus, Sparkles, ChevronLeft, ChevronRight, Star } 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 { LookCard } from '../../components/LookCard';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useLooks } from '../../hooks';\nimport { cn } from '../../utils/cn';\nimport type { CooLookOccasion } from '../../types';\n\nconst PAGE_SIZE = 24;\n\nconst OCCASIONS: Array<{ value: CooLookOccasion | 'ALL'; label: string }> = [\n { value: 'ALL', label: 'All' },\n { value: 'EVERYDAY', label: 'Everyday' },\n { value: 'WORK', label: 'Work' },\n { value: 'EVENING', label: 'Evening' },\n { value: 'WEDDING_GUEST', label: 'Wedding guest' },\n { value: 'VACATION', label: 'Vacation' },\n { value: 'PARTY', label: 'Party' },\n { value: 'ACTIVE', label: 'Active' },\n { value: 'OTHER', label: 'Other' },\n];\n\nexport function LooksListPage() {\n const { navigateTo } = useCoolAndLovely();\n const [occasion, setOccasion] = useState<CooLookOccasion | 'ALL'>('ALL');\n const [featuredOnly, setFeaturedOnly] = useState(false);\n const [offset, setOffset] = useState(0);\n\n const query = useLooks({\n occasion: occasion === 'ALL' ? undefined : occasion,\n featured: featuredOnly ? true : undefined,\n pagination: { limit: PAGE_SIZE, offset },\n });\n\n const looks = query.data?.items ?? [];\n const hasMore = query.data?.pageInfo.hasMore ?? false;\n\n return (\n <PageLayout\n title=\"Looks\"\n description=\"Shop-the-look outfits built from real catalogue pieces\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/looks/new')}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" /> New look\n </button>\n }\n >\n <div className=\"mb-6 flex flex-wrap items-center gap-2\">\n {OCCASIONS.map((opt) => (\n <button\n key={opt.value}\n type=\"button\"\n onClick={() => {\n setOccasion(opt.value);\n setOffset(0);\n }}\n className={cn(\n 'rounded-full px-3 py-1.5 text-sm font-medium transition-colors',\n occasion === 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 <button\n type=\"button\"\n onClick={() => {\n setFeaturedOnly((v) => !v);\n setOffset(0);\n }}\n className={cn(\n 'ml-auto inline-flex items-center gap-1.5 rounded-full px-3 py-1.5 text-sm font-medium transition-colors',\n featuredOnly\n ? 'bg-status-warning-bg-subtle text-status-warning-text'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n <Star className={cn('h-3.5 w-3.5', featuredOnly && 'fill-status-warning-text')} />{' '}\n Featured\n </button>\n </div>\n\n {query.isLoading ? (\n <LoadingView label=\"Loading looks…\" />\n ) : query.isError ? (\n <ErrorView onRetry={() => void query.refetch()} />\n ) : looks.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Sparkles className=\"h-10 w-10\" />}\n title=\"No looks yet\"\n description=\"Publish a shop-the-look outfit from catalogue pieces.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/looks/new')}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text\"\n >\n <Plus className=\"h-4 w-4\" /> New look\n </button>\n }\n />\n ) : (\n <>\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-4\">\n {looks.map((look) => (\n <div key={look.id} className=\"relative\">\n <LookCard\n id={look.id}\n title={look.title}\n occasion={look.occasion}\n imageFileId={look.image?.id}\n likeCount={look.likeCount}\n isLikedByMe={look.isLikedByMe}\n isSavedByMe={look.isSavedByMe}\n onOpen={() => navigateTo(`/looks/${look.id}`)}\n showActions\n />\n {(look.isFeatured || look.publishedAt === null) && (\n <div className=\"pointer-events-none absolute left-2 top-2 z-10 flex flex-col items-start gap-1\">\n {look.isFeatured && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-warning-bg-subtle px-2 py-0.5 text-xs font-medium text-status-warning-text shadow-sm backdrop-blur\">\n <Star className=\"h-3 w-3 fill-status-warning-text\" /> Featured\n </span>\n )}\n {look.publishedAt === null && <StatusBadge status=\"draft\" />}\n </div>\n )}\n </div>\n ))}\n </div>\n\n {(offset > 0 || hasMore) && (\n <div className=\"mt-6 flex items-center justify-center gap-2\">\n <button\n type=\"button\"\n disabled={offset === 0}\n onClick={() => setOffset((o) => Math.max(0, o - PAGE_SIZE))}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-40\"\n >\n <ChevronLeft className=\"h-4 w-4\" /> Previous\n </button>\n <span className=\"text-sm text-text-secondary\">Page {offset / PAGE_SIZE + 1}</span>\n <button\n type=\"button\"\n disabled={!hasMore}\n onClick={() => setOffset((o) => o + PAGE_SIZE)}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-40\"\n >\n Next <ChevronRight className=\"h-4 w-4\" />\n </button>\n </div>\n )}\n </>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|