@burdenoff/microfe-coolandlovely 2026.625.2 → 2026.628.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/CoolAndLovelyRoot.js +66 -122
- package/dist/coolandlovely/CoolAndLovelyRoot.js.map +1 -1
- package/dist/coolandlovely/CoolAndLovelyRoutes.js +87 -175
- package/dist/coolandlovely/CoolAndLovelyRoutes.js.map +1 -1
- package/dist/coolandlovely/components/Field.js +81 -0
- package/dist/coolandlovely/components/Field.js.map +1 -0
- package/dist/coolandlovely/components/StateViews.js +34 -0
- package/dist/coolandlovely/components/StateViews.js.map +1 -0
- package/dist/coolandlovely/components/StatusBadge.js +25 -22
- package/dist/coolandlovely/components/StatusBadge.js.map +1 -1
- package/dist/coolandlovely/components/TagList.js +21 -0
- package/dist/coolandlovely/components/TagList.js.map +1 -0
- package/dist/coolandlovely/components/Thumbnail.js +29 -0
- package/dist/coolandlovely/components/Thumbnail.js.map +1 -0
- package/dist/coolandlovely/context/CoolAndLovelyContext.js +10 -10
- package/dist/coolandlovely/context/CoolAndLovelyContext.js.map +1 -1
- package/dist/coolandlovely/hooks/index.js +1 -0
- package/dist/coolandlovely/hooks/useCoolAndLovelyApi.js +462 -169
- package/dist/coolandlovely/hooks/useCoolAndLovelyApi.js.map +1 -1
- package/dist/coolandlovely/pages/HomePage.js +251 -845
- package/dist/coolandlovely/pages/HomePage.js.map +1 -1
- package/dist/coolandlovely/pages/brands/BrandDetailPage.js +319 -0
- package/dist/coolandlovely/pages/brands/BrandDetailPage.js.map +1 -0
- package/dist/coolandlovely/pages/brands/BrandFormPage.js +190 -0
- package/dist/coolandlovely/pages/brands/BrandFormPage.js.map +1 -0
- package/dist/coolandlovely/pages/brands/BrandsListPage.js +158 -0
- package/dist/coolandlovely/pages/brands/BrandsListPage.js.map +1 -0
- package/dist/coolandlovely/pages/collaborations/CollaborationDetailPage.js +253 -0
- package/dist/coolandlovely/pages/collaborations/CollaborationDetailPage.js.map +1 -0
- package/dist/coolandlovely/pages/collaborations/CollaborationFormPage.js +206 -0
- package/dist/coolandlovely/pages/collaborations/CollaborationFormPage.js.map +1 -0
- package/dist/coolandlovely/pages/collaborations/CollaborationsListPage.js +129 -0
- package/dist/coolandlovely/pages/collaborations/CollaborationsListPage.js.map +1 -0
- package/dist/coolandlovely/pages/collections/CollectionDetailPage.js +217 -0
- package/dist/coolandlovely/pages/collections/CollectionDetailPage.js.map +1 -0
- package/dist/coolandlovely/pages/collections/CollectionFormPage.js +217 -0
- package/dist/coolandlovely/pages/collections/CollectionFormPage.js.map +1 -0
- package/dist/coolandlovely/pages/collections/CollectionsListPage.js +122 -0
- package/dist/coolandlovely/pages/collections/CollectionsListPage.js.map +1 -0
- package/dist/coolandlovely/pages/items/StyleItemDetailPage.js +230 -0
- package/dist/coolandlovely/pages/items/StyleItemDetailPage.js.map +1 -0
- package/dist/coolandlovely/pages/items/StyleItemFormPage.js +276 -0
- package/dist/coolandlovely/pages/items/StyleItemFormPage.js.map +1 -0
- package/dist/coolandlovely/pages/items/StyleItemsListPage.js +170 -0
- package/dist/coolandlovely/pages/items/StyleItemsListPage.js.map +1 -0
- package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js +124 -0
- package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js.map +1 -0
- package/dist/coolandlovely/pages/lookbooks/LookbooksListPage.js +156 -0
- package/dist/coolandlovely/pages/lookbooks/LookbooksListPage.js.map +1 -0
- package/dist/coolandlovely/pages/looks/LookDetailPage.js +238 -0
- package/dist/coolandlovely/pages/looks/LookDetailPage.js.map +1 -0
- package/dist/coolandlovely/pages/looks/LookFormPage.js +209 -0
- package/dist/coolandlovely/pages/looks/LookFormPage.js.map +1 -0
- package/dist/coolandlovely/pages/looks/LooksListPage.js +181 -0
- package/dist/coolandlovely/pages/looks/LooksListPage.js.map +1 -0
- package/dist/coolandlovely/pages/profile/StyleProfilePage.js +224 -0
- package/dist/coolandlovely/pages/profile/StyleProfilePage.js.map +1 -0
- package/dist/coolandlovely/pages/wardrobe/WardrobeFormPage.js +173 -0
- package/dist/coolandlovely/pages/wardrobe/WardrobeFormPage.js.map +1 -0
- package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.js +172 -0
- package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.js.map +1 -0
- package/dist/coolandlovely/store/coolandlovelyStore.js +2 -6
- package/dist/coolandlovely/store/coolandlovelyStore.js.map +1 -1
- package/dist/coolandlovely/utils/formatters.js +31 -23
- package/dist/coolandlovely/utils/formatters.js.map +1 -1
- package/dist/coolandlovely/utils/icons.js +34 -0
- package/dist/coolandlovely/utils/icons.js.map +1 -0
- package/dist/index.js +4 -4
- package/package.json +1 -1
- package/dist/coolandlovely/components/InvestmentCard.js +0 -98
- package/dist/coolandlovely/components/InvestmentCard.js.map +0 -1
- package/dist/coolandlovely/components/RatingDisplay.js +0 -35
- package/dist/coolandlovely/components/RatingDisplay.js.map +0 -1
- package/dist/coolandlovely/mocks/data.js +0 -1517
- package/dist/coolandlovely/mocks/data.js.map +0 -1
- package/dist/coolandlovely/mocks/users.js +0 -34
- package/dist/coolandlovely/mocks/users.js.map +0 -1
- package/dist/coolandlovely/pages/admin/AdminAnalyticsPage.js +0 -651
- package/dist/coolandlovely/pages/admin/AdminAnalyticsPage.js.map +0 -1
- package/dist/coolandlovely/pages/admin/AdminDashboard.js +0 -673
- package/dist/coolandlovely/pages/admin/AdminDashboard.js.map +0 -1
- package/dist/coolandlovely/pages/admin/AdminFinancialPage.js +0 -427
- package/dist/coolandlovely/pages/admin/AdminFinancialPage.js.map +0 -1
- package/dist/coolandlovely/pages/admin/AdminOrdersPage.js +0 -665
- package/dist/coolandlovely/pages/admin/AdminOrdersPage.js.map +0 -1
- package/dist/coolandlovely/pages/admin/AdminProfilePage.js +0 -264
- package/dist/coolandlovely/pages/admin/AdminProfilePage.js.map +0 -1
- package/dist/coolandlovely/pages/admin/AdminSettingsPage.js +0 -654
- package/dist/coolandlovely/pages/admin/AdminSettingsPage.js.map +0 -1
- package/dist/coolandlovely/pages/admin/CartManagementPage.js +0 -874
- package/dist/coolandlovely/pages/admin/CartManagementPage.js.map +0 -1
- package/dist/coolandlovely/pages/admin/UserManagementPage.js +0 -898
- package/dist/coolandlovely/pages/admin/UserManagementPage.js.map +0 -1
- package/dist/coolandlovely/pages/cart-owner/AnalyticsDashboard.js +0 -842
- package/dist/coolandlovely/pages/cart-owner/AnalyticsDashboard.js.map +0 -1
- package/dist/coolandlovely/pages/cart-owner/BusinessSettingsPage.js +0 -705
- package/dist/coolandlovely/pages/cart-owner/BusinessSettingsPage.js.map +0 -1
- package/dist/coolandlovely/pages/cart-owner/CartOwnerDashboard.js +0 -802
- package/dist/coolandlovely/pages/cart-owner/CartOwnerDashboard.js.map +0 -1
- package/dist/coolandlovely/pages/cart-owner/CartOwnerProfilePage.js +0 -891
- package/dist/coolandlovely/pages/cart-owner/CartOwnerProfilePage.js.map +0 -1
- package/dist/coolandlovely/pages/cart-owner/CartServiceConfigPage.js +0 -639
- package/dist/coolandlovely/pages/cart-owner/CartServiceConfigPage.js.map +0 -1
- package/dist/coolandlovely/pages/cart-owner/InventoryManagementPage.js +0 -914
- package/dist/coolandlovely/pages/cart-owner/InventoryManagementPage.js.map +0 -1
- package/dist/coolandlovely/pages/cart-owner/OrderManagementPage.js +0 -882
- package/dist/coolandlovely/pages/cart-owner/OrderManagementPage.js.map +0 -1
- package/dist/coolandlovely/pages/cart-owner/StaffManagementPage.js +0 -831
- package/dist/coolandlovely/pages/cart-owner/StaffManagementPage.js.map +0 -1
- package/dist/coolandlovely/pages/common/HelpSupportPage.js +0 -1325
- package/dist/coolandlovely/pages/common/HelpSupportPage.js.map +0 -1
- package/dist/coolandlovely/pages/common/NotificationCenterPage.js +0 -671
- package/dist/coolandlovely/pages/common/NotificationCenterPage.js.map +0 -1
- package/dist/coolandlovely/pages/common/SettingsPage.js +0 -973
- package/dist/coolandlovely/pages/common/SettingsPage.js.map +0 -1
- package/dist/coolandlovely/pages/customer/BookingDetailsPage.js +0 -630
- package/dist/coolandlovely/pages/customer/BookingDetailsPage.js.map +0 -1
- package/dist/coolandlovely/pages/customer/CartDetailPage.js +0 -623
- package/dist/coolandlovely/pages/customer/CartDetailPage.js.map +0 -1
- package/dist/coolandlovely/pages/customer/CartDiscoveryPage.js +0 -558
- package/dist/coolandlovely/pages/customer/CartDiscoveryPage.js.map +0 -1
- package/dist/coolandlovely/pages/customer/CheckoutPage.js +0 -529
- package/dist/coolandlovely/pages/customer/CheckoutPage.js.map +0 -1
- package/dist/coolandlovely/pages/customer/CustomerDashboard.js +0 -538
- package/dist/coolandlovely/pages/customer/CustomerDashboard.js.map +0 -1
- package/dist/coolandlovely/pages/customer/CustomerProfilePage.js +0 -658
- package/dist/coolandlovely/pages/customer/CustomerProfilePage.js.map +0 -1
- package/dist/coolandlovely/pages/customer/DirectServiceBookingPage.js +0 -632
- package/dist/coolandlovely/pages/customer/DirectServiceBookingPage.js.map +0 -1
- package/dist/coolandlovely/pages/customer/FavoritesPage.js +0 -487
- package/dist/coolandlovely/pages/customer/FavoritesPage.js.map +0 -1
- package/dist/coolandlovely/pages/customer/OrderHistoryPage.js +0 -447
- package/dist/coolandlovely/pages/customer/OrderHistoryPage.js.map +0 -1
- package/dist/coolandlovely/pages/customer/OrderTrackingPage.js +0 -425
- package/dist/coolandlovely/pages/customer/OrderTrackingPage.js.map +0 -1
- package/dist/coolandlovely/pages/customer/PaymentMethodsPage.js +0 -494
- package/dist/coolandlovely/pages/customer/PaymentMethodsPage.js.map +0 -1
- package/dist/coolandlovely/pages/customer/ReviewsPage.js +0 -616
- package/dist/coolandlovely/pages/customer/ReviewsPage.js.map +0 -1
- package/dist/coolandlovely/pages/customer/SavedAddressesPage.js +0 -449
- package/dist/coolandlovely/pages/customer/SavedAddressesPage.js.map +0 -1
- package/dist/coolandlovely/pages/investor/InvestmentDetailsPage.js +0 -887
- package/dist/coolandlovely/pages/investor/InvestmentDetailsPage.js.map +0 -1
- package/dist/coolandlovely/pages/investor/InvestmentOpportunitiesPage.js +0 -945
- package/dist/coolandlovely/pages/investor/InvestmentOpportunitiesPage.js.map +0 -1
- package/dist/coolandlovely/pages/investor/InvestorAnalyticsPage.js +0 -687
- package/dist/coolandlovely/pages/investor/InvestorAnalyticsPage.js.map +0 -1
- package/dist/coolandlovely/pages/investor/InvestorDashboard.js +0 -455
- package/dist/coolandlovely/pages/investor/InvestorDashboard.js.map +0 -1
- package/dist/coolandlovely/pages/investor/InvestorInvestmentsPage.js +0 -405
- package/dist/coolandlovely/pages/investor/InvestorInvestmentsPage.js.map +0 -1
- package/dist/coolandlovely/pages/investor/InvestorPortfolioPage.js +0 -602
- package/dist/coolandlovely/pages/investor/InvestorPortfolioPage.js.map +0 -1
- package/dist/coolandlovely/pages/investor/InvestorProfilePage.js +0 -1070
- package/dist/coolandlovely/pages/investor/InvestorProfilePage.js.map +0 -1
- package/dist/coolandlovely/pages/worker/TaskDetailsPage.js +0 -533
- package/dist/coolandlovely/pages/worker/TaskDetailsPage.js.map +0 -1
- package/dist/coolandlovely/pages/worker/WorkerDashboard.js +0 -671
- package/dist/coolandlovely/pages/worker/WorkerDashboard.js.map +0 -1
- package/dist/coolandlovely/pages/worker/WorkerOrdersPage.js +0 -680
- package/dist/coolandlovely/pages/worker/WorkerOrdersPage.js.map +0 -1
- package/dist/coolandlovely/pages/worker/WorkerProfilePage.js +0 -692
- package/dist/coolandlovely/pages/worker/WorkerProfilePage.js.map +0 -1
- package/dist/coolandlovely/pages/worker/WorkerSchedulePage.js +0 -583
- package/dist/coolandlovely/pages/worker/WorkerSchedulePage.js.map +0 -1
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { PageLayout as e } from "../../components/PageLayout.js";
|
|
2
|
+
import { StatusBadge as t } from "../../components/StatusBadge.js";
|
|
3
|
+
import { ErrorView as n, LoadingView as r } from "../../components/StateViews.js";
|
|
4
|
+
import { useMyStyleProfile as i, useUpsertStyleProfile as a } from "../../hooks/useCoolAndLovelyApi.js";
|
|
5
|
+
import "../../hooks/index.js";
|
|
6
|
+
import { parseCsv as o, titleCase as s } from "../../utils/formatters.js";
|
|
7
|
+
import { TagList as c } from "../../components/TagList.js";
|
|
8
|
+
import { SelectField as l, TextAreaField as u, TextField as d } from "../../components/Field.js";
|
|
9
|
+
import { useEffect as f, useState as p } from "react";
|
|
10
|
+
import { Loader2 as m, Pencil as h, Save as g, UserCircle as _, X as v } from "lucide-react";
|
|
11
|
+
import { jsx as y, jsxs as b } from "react/jsx-runtime";
|
|
12
|
+
import { GlassCard as x } from "@burdenoff/fe-libs/ui";
|
|
13
|
+
//#region src/coolandlovely/pages/profile/StyleProfilePage.tsx
|
|
14
|
+
var S = [
|
|
15
|
+
"PUBLIC",
|
|
16
|
+
"FOLLOWERS",
|
|
17
|
+
"PRIVATE"
|
|
18
|
+
], C = {
|
|
19
|
+
displayName: "",
|
|
20
|
+
bio: "",
|
|
21
|
+
visibility: "PRIVATE",
|
|
22
|
+
stylePreferences: "",
|
|
23
|
+
palette: "",
|
|
24
|
+
favouriteBrands: ""
|
|
25
|
+
};
|
|
26
|
+
function w() {
|
|
27
|
+
let w = i(), T = a(), [E, D] = p(!1), [O, k] = p(C), A = w.data, j = !!A;
|
|
28
|
+
f(() => {
|
|
29
|
+
A && k({
|
|
30
|
+
displayName: A.displayName ?? "",
|
|
31
|
+
bio: A.bio ?? "",
|
|
32
|
+
visibility: A.visibility,
|
|
33
|
+
stylePreferences: A.stylePreferences.join(", "),
|
|
34
|
+
palette: A.palette.join(", "),
|
|
35
|
+
favouriteBrands: A.favouriteBrands.join(", ")
|
|
36
|
+
});
|
|
37
|
+
}, [A]), f(() => {
|
|
38
|
+
!w.isLoading && !j && D(!0);
|
|
39
|
+
}, [w.isLoading, j]);
|
|
40
|
+
let M = (e, t) => k((n) => ({
|
|
41
|
+
...n,
|
|
42
|
+
[e]: t
|
|
43
|
+
})), N = S.map((e) => ({
|
|
44
|
+
value: e,
|
|
45
|
+
label: s(e)
|
|
46
|
+
}));
|
|
47
|
+
return w.isLoading ? /* @__PURE__ */ y(e, {
|
|
48
|
+
title: "Style Profile",
|
|
49
|
+
children: /* @__PURE__ */ y(r, { label: "Loading your style profile…" })
|
|
50
|
+
}) : w.isError ? /* @__PURE__ */ y(e, {
|
|
51
|
+
title: "Style Profile",
|
|
52
|
+
children: /* @__PURE__ */ y(n, { onRetry: () => void w.refetch() })
|
|
53
|
+
}) : !E && A ? /* @__PURE__ */ y(e, {
|
|
54
|
+
title: "Style Profile",
|
|
55
|
+
description: "Your taste, palette and sizing — the spine of personalised discovery",
|
|
56
|
+
actions: /* @__PURE__ */ b("button", {
|
|
57
|
+
type: "button",
|
|
58
|
+
onClick: () => D(!0),
|
|
59
|
+
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",
|
|
60
|
+
children: [/* @__PURE__ */ y(h, { className: "h-4 w-4" }), " Edit"]
|
|
61
|
+
}),
|
|
62
|
+
children: /* @__PURE__ */ y("div", {
|
|
63
|
+
className: "mx-auto max-w-2xl",
|
|
64
|
+
children: /* @__PURE__ */ b(x, {
|
|
65
|
+
treatment: "glass",
|
|
66
|
+
className: "p-6",
|
|
67
|
+
children: [
|
|
68
|
+
/* @__PURE__ */ b("div", {
|
|
69
|
+
className: "flex items-center gap-4",
|
|
70
|
+
children: [/* @__PURE__ */ y("div", {
|
|
71
|
+
className: "flex h-14 w-14 items-center justify-center rounded-full bg-accent-purple-subtle text-text-primary",
|
|
72
|
+
children: /* @__PURE__ */ y(_, { className: "h-7 w-7" })
|
|
73
|
+
}), /* @__PURE__ */ b("div", {
|
|
74
|
+
className: "min-w-0 flex-1",
|
|
75
|
+
children: [/* @__PURE__ */ y("h2", {
|
|
76
|
+
className: "truncate text-xl font-semibold text-text-primary",
|
|
77
|
+
children: A.displayName || "Your style profile"
|
|
78
|
+
}), /* @__PURE__ */ y(t, {
|
|
79
|
+
status: A.visibility,
|
|
80
|
+
className: "mt-1"
|
|
81
|
+
})]
|
|
82
|
+
})]
|
|
83
|
+
}),
|
|
84
|
+
A.bio && /* @__PURE__ */ y("p", {
|
|
85
|
+
className: "mt-4 text-sm text-text-secondary",
|
|
86
|
+
children: A.bio
|
|
87
|
+
}),
|
|
88
|
+
/* @__PURE__ */ b("div", {
|
|
89
|
+
className: "mt-6 space-y-4",
|
|
90
|
+
children: [
|
|
91
|
+
/* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("p", {
|
|
92
|
+
className: "mb-1.5 text-xs font-medium uppercase tracking-wide text-text-secondary",
|
|
93
|
+
children: "Style preferences"
|
|
94
|
+
}), A.stylePreferences.length ? /* @__PURE__ */ y(c, {
|
|
95
|
+
tags: A.stylePreferences,
|
|
96
|
+
tone: "accent",
|
|
97
|
+
max: 20
|
|
98
|
+
}) : /* @__PURE__ */ y("p", {
|
|
99
|
+
className: "text-sm text-text-secondary",
|
|
100
|
+
children: "—"
|
|
101
|
+
})] }),
|
|
102
|
+
/* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("p", {
|
|
103
|
+
className: "mb-1.5 text-xs font-medium uppercase tracking-wide text-text-secondary",
|
|
104
|
+
children: "Palette"
|
|
105
|
+
}), A.palette.length ? /* @__PURE__ */ y(c, {
|
|
106
|
+
tags: A.palette,
|
|
107
|
+
max: 20
|
|
108
|
+
}) : /* @__PURE__ */ y("p", {
|
|
109
|
+
className: "text-sm text-text-secondary",
|
|
110
|
+
children: "—"
|
|
111
|
+
})] }),
|
|
112
|
+
/* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("p", {
|
|
113
|
+
className: "mb-1.5 text-xs font-medium uppercase tracking-wide text-text-secondary",
|
|
114
|
+
children: "Favourite brands"
|
|
115
|
+
}), A.favouriteBrands.length ? /* @__PURE__ */ y(c, {
|
|
116
|
+
tags: A.favouriteBrands,
|
|
117
|
+
max: 20
|
|
118
|
+
}) : /* @__PURE__ */ y("p", {
|
|
119
|
+
className: "text-sm text-text-secondary",
|
|
120
|
+
children: "—"
|
|
121
|
+
})] })
|
|
122
|
+
]
|
|
123
|
+
})
|
|
124
|
+
]
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
}) : /* @__PURE__ */ y(e, {
|
|
128
|
+
title: j ? "Edit style profile" : "Create your style profile",
|
|
129
|
+
description: "This powers your personalised discovery feed",
|
|
130
|
+
actions: j ? /* @__PURE__ */ b("button", {
|
|
131
|
+
type: "button",
|
|
132
|
+
onClick: () => D(!1),
|
|
133
|
+
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",
|
|
134
|
+
children: [/* @__PURE__ */ y(v, { className: "h-4 w-4" }), " Cancel"]
|
|
135
|
+
}) : void 0,
|
|
136
|
+
children: /* @__PURE__ */ y("form", {
|
|
137
|
+
onSubmit: (e) => {
|
|
138
|
+
e.preventDefault(), T.mutate({
|
|
139
|
+
displayName: O.displayName.trim() || void 0,
|
|
140
|
+
bio: O.bio.trim() || void 0,
|
|
141
|
+
visibility: O.visibility,
|
|
142
|
+
stylePreferences: o(O.stylePreferences),
|
|
143
|
+
palette: o(O.palette),
|
|
144
|
+
favouriteBrands: o(O.favouriteBrands)
|
|
145
|
+
}, { onSuccess: () => D(!1) });
|
|
146
|
+
},
|
|
147
|
+
className: "mx-auto max-w-2xl",
|
|
148
|
+
children: /* @__PURE__ */ b(x, {
|
|
149
|
+
treatment: "plain",
|
|
150
|
+
className: "p-6",
|
|
151
|
+
children: [
|
|
152
|
+
/* @__PURE__ */ y(d, {
|
|
153
|
+
id: "displayName",
|
|
154
|
+
label: "Display name",
|
|
155
|
+
value: O.displayName,
|
|
156
|
+
onChange: (e) => M("displayName", e),
|
|
157
|
+
placeholder: "How you want to appear"
|
|
158
|
+
}),
|
|
159
|
+
/* @__PURE__ */ y(u, {
|
|
160
|
+
id: "bio",
|
|
161
|
+
label: "Bio",
|
|
162
|
+
value: O.bio,
|
|
163
|
+
onChange: (e) => M("bio", e),
|
|
164
|
+
placeholder: "A sentence about your style…",
|
|
165
|
+
rows: 3,
|
|
166
|
+
className: "mt-4"
|
|
167
|
+
}),
|
|
168
|
+
/* @__PURE__ */ y(l, {
|
|
169
|
+
id: "visibility",
|
|
170
|
+
label: "Visibility",
|
|
171
|
+
value: O.visibility,
|
|
172
|
+
onChange: (e) => M("visibility", e),
|
|
173
|
+
options: N,
|
|
174
|
+
className: "mt-4"
|
|
175
|
+
}),
|
|
176
|
+
/* @__PURE__ */ y(d, {
|
|
177
|
+
id: "stylePreferences",
|
|
178
|
+
label: "Style preferences",
|
|
179
|
+
value: O.stylePreferences,
|
|
180
|
+
onChange: (e) => M("stylePreferences", e),
|
|
181
|
+
placeholder: "minimal, tailored, vintage",
|
|
182
|
+
hint: "Comma-separated",
|
|
183
|
+
className: "mt-4"
|
|
184
|
+
}),
|
|
185
|
+
/* @__PURE__ */ y(d, {
|
|
186
|
+
id: "palette",
|
|
187
|
+
label: "Palette",
|
|
188
|
+
value: O.palette,
|
|
189
|
+
onChange: (e) => M("palette", e),
|
|
190
|
+
placeholder: "neutrals, navy, rust",
|
|
191
|
+
hint: "Comma-separated",
|
|
192
|
+
className: "mt-4"
|
|
193
|
+
}),
|
|
194
|
+
/* @__PURE__ */ y(d, {
|
|
195
|
+
id: "favouriteBrands",
|
|
196
|
+
label: "Favourite brands",
|
|
197
|
+
value: O.favouriteBrands,
|
|
198
|
+
onChange: (e) => M("favouriteBrands", e),
|
|
199
|
+
placeholder: "Aurora Atelier, …",
|
|
200
|
+
hint: "Comma-separated",
|
|
201
|
+
className: "mt-4"
|
|
202
|
+
}),
|
|
203
|
+
T.isError && /* @__PURE__ */ y("p", {
|
|
204
|
+
className: "mt-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
|
|
205
|
+
children: T.error instanceof Error ? T.error.message : "Something went wrong"
|
|
206
|
+
}),
|
|
207
|
+
/* @__PURE__ */ y("div", {
|
|
208
|
+
className: "mt-6 flex justify-end gap-2",
|
|
209
|
+
children: /* @__PURE__ */ b("button", {
|
|
210
|
+
type: "submit",
|
|
211
|
+
disabled: T.isPending,
|
|
212
|
+
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",
|
|
213
|
+
children: [T.isPending ? /* @__PURE__ */ y(m, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ y(g, { className: "h-4 w-4" }), j ? "Save changes" : "Create profile"]
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
]
|
|
217
|
+
})
|
|
218
|
+
})
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
//#endregion
|
|
222
|
+
export { w as StyleProfilePage };
|
|
223
|
+
|
|
224
|
+
//# sourceMappingURL=StyleProfilePage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StyleProfilePage.js","names":[],"sources":["../../../../src/coolandlovely/pages/profile/StyleProfilePage.tsx"],"sourcesContent":["/**\n * Style Profile Page (self-scoped)\n * @module coolandlovely/pages/profile\n *\n * Views and edits the calling shopper's style profile (`cooMyStyleProfile` +\n * `cooUpsertStyleProfile`). One profile per workspace per shopper.\n */\n\nimport { useEffect, useState } from 'react';\nimport { Loader2, Save, UserCircle, Pencil, X } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { TagList } from '../../components/TagList';\nimport { TextField, TextAreaField, SelectField } from '../../components/Field';\nimport { useMyStyleProfile, useUpsertStyleProfile } from '../../hooks';\nimport { parseCsv, titleCase } from '../../utils/formatters';\nimport type { CooVisibility } from '../../types';\n\nconst VISIBILITIES: CooVisibility[] = ['PUBLIC', 'FOLLOWERS', 'PRIVATE'];\n\ninterface FormState {\n displayName: string;\n bio: string;\n visibility: CooVisibility;\n stylePreferences: string;\n palette: string;\n favouriteBrands: string;\n}\n\nconst EMPTY: FormState = {\n displayName: '',\n bio: '',\n visibility: 'PRIVATE',\n stylePreferences: '',\n palette: '',\n favouriteBrands: '',\n};\n\nexport function StyleProfilePage() {\n const query = useMyStyleProfile();\n const upsert = useUpsertStyleProfile();\n\n const [editing, setEditing] = useState(false);\n const [form, setForm] = useState<FormState>(EMPTY);\n\n const profile = query.data;\n const hasProfile = Boolean(profile);\n\n useEffect(() => {\n if (profile) {\n setForm({\n displayName: profile.displayName ?? '',\n bio: profile.bio ?? '',\n visibility: profile.visibility,\n stylePreferences: profile.stylePreferences.join(', '),\n palette: profile.palette.join(', '),\n favouriteBrands: profile.favouriteBrands.join(', '),\n });\n }\n }, [profile]);\n\n // If there is no profile yet, drop straight into edit mode.\n useEffect(() => {\n if (!query.isLoading && !hasProfile) {\n setEditing(true);\n }\n }, [query.isLoading, hasProfile]);\n\n const setField = <K extends keyof FormState>(key: K, value: FormState[K]) =>\n setForm((f) => ({ ...f, [key]: value }));\n\n const visibilityOptions = VISIBILITIES.map((v) => ({ value: v, label: titleCase(v) }));\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n upsert.mutate(\n {\n displayName: form.displayName.trim() || undefined,\n bio: form.bio.trim() || undefined,\n visibility: form.visibility,\n stylePreferences: parseCsv(form.stylePreferences),\n palette: parseCsv(form.palette),\n favouriteBrands: parseCsv(form.favouriteBrands),\n },\n { onSuccess: () => setEditing(false) }\n );\n };\n\n if (query.isLoading) {\n return (\n <PageLayout title=\"Style Profile\">\n <LoadingView label=\"Loading your style profile…\" />\n </PageLayout>\n );\n }\n\n if (query.isError) {\n return (\n <PageLayout title=\"Style Profile\">\n <ErrorView onRetry={() => void query.refetch()} />\n </PageLayout>\n );\n }\n\n // ----- View mode -----\n if (!editing && profile) {\n return (\n <PageLayout\n title=\"Style Profile\"\n description=\"Your taste, palette and sizing — the spine of personalised discovery\"\n actions={\n <button\n type=\"button\"\n onClick={() => setEditing(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 transition-colors hover:bg-action-primary-bg/90\"\n >\n <Pencil className=\"h-4 w-4\" /> Edit\n </button>\n }\n >\n <div className=\"mx-auto max-w-2xl\">\n <GlassCard treatment=\"glass\" className=\"p-6\">\n <div className=\"flex items-center gap-4\">\n <div className=\"flex h-14 w-14 items-center justify-center rounded-full bg-accent-purple-subtle text-text-primary\">\n <UserCircle className=\"h-7 w-7\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <h2 className=\"truncate text-xl font-semibold text-text-primary\">\n {profile.displayName || 'Your style profile'}\n </h2>\n <StatusBadge status={profile.visibility} className=\"mt-1\" />\n </div>\n </div>\n {profile.bio && <p className=\"mt-4 text-sm text-text-secondary\">{profile.bio}</p>}\n\n <div className=\"mt-6 space-y-4\">\n <div>\n <p className=\"mb-1.5 text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Style preferences\n </p>\n {profile.stylePreferences.length ? (\n <TagList tags={profile.stylePreferences} tone=\"accent\" max={20} />\n ) : (\n <p className=\"text-sm text-text-secondary\">—</p>\n )}\n </div>\n <div>\n <p className=\"mb-1.5 text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Palette\n </p>\n {profile.palette.length ? (\n <TagList tags={profile.palette} max={20} />\n ) : (\n <p className=\"text-sm text-text-secondary\">—</p>\n )}\n </div>\n <div>\n <p className=\"mb-1.5 text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Favourite brands\n </p>\n {profile.favouriteBrands.length ? (\n <TagList tags={profile.favouriteBrands} max={20} />\n ) : (\n <p className=\"text-sm text-text-secondary\">—</p>\n )}\n </div>\n </div>\n </GlassCard>\n </div>\n </PageLayout>\n );\n }\n\n // ----- Edit / create mode -----\n return (\n <PageLayout\n title={hasProfile ? 'Edit style profile' : 'Create your style profile'}\n description=\"This powers your personalised discovery feed\"\n actions={\n hasProfile ? (\n <button\n type=\"button\"\n onClick={() => setEditing(false)}\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 <X className=\"h-4 w-4\" /> Cancel\n </button>\n ) : undefined\n }\n >\n <form onSubmit={handleSubmit} className=\"mx-auto max-w-2xl\">\n <GlassCard treatment=\"plain\" className=\"p-6\">\n <TextField\n id=\"displayName\"\n label=\"Display name\"\n value={form.displayName}\n onChange={(v) => setField('displayName', v)}\n placeholder=\"How you want to appear\"\n />\n <TextAreaField\n id=\"bio\"\n label=\"Bio\"\n value={form.bio}\n onChange={(v) => setField('bio', v)}\n placeholder=\"A sentence about your style…\"\n rows={3}\n className=\"mt-4\"\n />\n <SelectField\n id=\"visibility\"\n label=\"Visibility\"\n value={form.visibility}\n onChange={(v) => setField('visibility', v as CooVisibility)}\n options={visibilityOptions}\n className=\"mt-4\"\n />\n <TextField\n id=\"stylePreferences\"\n label=\"Style preferences\"\n value={form.stylePreferences}\n onChange={(v) => setField('stylePreferences', v)}\n placeholder=\"minimal, tailored, vintage\"\n hint=\"Comma-separated\"\n className=\"mt-4\"\n />\n <TextField\n id=\"palette\"\n label=\"Palette\"\n value={form.palette}\n onChange={(v) => setField('palette', v)}\n placeholder=\"neutrals, navy, rust\"\n hint=\"Comma-separated\"\n className=\"mt-4\"\n />\n <TextField\n id=\"favouriteBrands\"\n label=\"Favourite brands\"\n value={form.favouriteBrands}\n onChange={(v) => setField('favouriteBrands', v)}\n placeholder=\"Aurora Atelier, …\"\n hint=\"Comma-separated\"\n className=\"mt-4\"\n />\n\n {upsert.isError && (\n <p className=\"mt-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {upsert.error instanceof Error ? upsert.error.message : 'Something went wrong'}\n </p>\n )}\n\n <div className=\"mt-6 flex justify-end gap-2\">\n <button\n type=\"submit\"\n disabled={upsert.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 {upsert.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n {hasProfile ? 'Save changes' : 'Create profile'}\n </button>\n </div>\n </GlassCard>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { useCoolAndLovely as e } from "../../context/CoolAndLovelyContext.js";
|
|
2
|
+
import { PageLayout as t } from "../../components/PageLayout.js";
|
|
3
|
+
import { useCreateWardrobeItem as n } from "../../hooks/useCoolAndLovelyApi.js";
|
|
4
|
+
import "../../hooks/index.js";
|
|
5
|
+
import { parseCsv as r, titleCase as i } from "../../utils/formatters.js";
|
|
6
|
+
import { STYLE_CATEGORIES as a } from "../../utils/icons.js";
|
|
7
|
+
import { SelectField as o, TextAreaField as s, TextField as c } from "../../components/Field.js";
|
|
8
|
+
import { useState as l } from "react";
|
|
9
|
+
import { ArrowLeft as u, Heart as d, Loader2 as f, Save as p } from "lucide-react";
|
|
10
|
+
import { jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
11
|
+
import { GlassCard as g } from "@burdenoff/fe-libs/ui";
|
|
12
|
+
//#region src/coolandlovely/pages/wardrobe/WardrobeFormPage.tsx
|
|
13
|
+
var _ = [
|
|
14
|
+
"OWNED",
|
|
15
|
+
"WISHLIST",
|
|
16
|
+
"PURCHASED_ON_PLATFORM",
|
|
17
|
+
"GIFTED"
|
|
18
|
+
], v = {
|
|
19
|
+
name: "",
|
|
20
|
+
category: "OTHER",
|
|
21
|
+
source: "OWNED",
|
|
22
|
+
brandName: "",
|
|
23
|
+
colour: "",
|
|
24
|
+
size: "",
|
|
25
|
+
notes: "",
|
|
26
|
+
tags: ""
|
|
27
|
+
};
|
|
28
|
+
function y() {
|
|
29
|
+
let { navigateTo: y } = e(), b = n(), [x, S] = l(v), [C, w] = l(), T = (e, t) => S((n) => ({
|
|
30
|
+
...n,
|
|
31
|
+
[e]: t
|
|
32
|
+
})), E = a.map((e) => ({
|
|
33
|
+
value: e,
|
|
34
|
+
label: i(e)
|
|
35
|
+
})), D = _.map((e) => ({
|
|
36
|
+
value: e,
|
|
37
|
+
label: i(e)
|
|
38
|
+
}));
|
|
39
|
+
return /* @__PURE__ */ m(t, {
|
|
40
|
+
title: "Add a wardrobe piece",
|
|
41
|
+
description: "Catalogue something you own or want",
|
|
42
|
+
actions: /* @__PURE__ */ h("button", {
|
|
43
|
+
type: "button",
|
|
44
|
+
onClick: () => y("/wardrobe"),
|
|
45
|
+
className: "inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken",
|
|
46
|
+
children: [/* @__PURE__ */ m(u, { className: "h-4 w-4" }), " Cancel"]
|
|
47
|
+
}),
|
|
48
|
+
children: /* @__PURE__ */ m("form", {
|
|
49
|
+
onSubmit: (e) => {
|
|
50
|
+
if (e.preventDefault(), !x.name.trim()) {
|
|
51
|
+
w("Name is required");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
w(void 0), b.mutate({
|
|
55
|
+
name: x.name.trim(),
|
|
56
|
+
category: x.category,
|
|
57
|
+
source: x.source,
|
|
58
|
+
brandName: x.brandName.trim() || void 0,
|
|
59
|
+
colour: x.colour.trim() || void 0,
|
|
60
|
+
size: x.size.trim() || void 0,
|
|
61
|
+
notes: x.notes.trim() || void 0,
|
|
62
|
+
tags: r(x.tags)
|
|
63
|
+
}, { onSuccess: () => y("/wardrobe") });
|
|
64
|
+
},
|
|
65
|
+
className: "mx-auto max-w-2xl",
|
|
66
|
+
children: /* @__PURE__ */ h(g, {
|
|
67
|
+
treatment: "plain",
|
|
68
|
+
className: "p-6",
|
|
69
|
+
children: [
|
|
70
|
+
/* @__PURE__ */ h("div", {
|
|
71
|
+
className: "mb-6 flex items-center gap-3",
|
|
72
|
+
children: [/* @__PURE__ */ m("div", {
|
|
73
|
+
className: "flex h-11 w-11 items-center justify-center rounded-lg bg-status-error-bg-subtle text-status-error-text",
|
|
74
|
+
children: /* @__PURE__ */ m(d, { className: "h-5 w-5" })
|
|
75
|
+
}), /* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("h2", {
|
|
76
|
+
className: "text-base font-semibold text-text-primary",
|
|
77
|
+
children: "Piece details"
|
|
78
|
+
}), /* @__PURE__ */ m("p", {
|
|
79
|
+
className: "text-xs text-text-secondary",
|
|
80
|
+
children: "Only you can see your wardrobe."
|
|
81
|
+
})] })]
|
|
82
|
+
}),
|
|
83
|
+
/* @__PURE__ */ m(c, {
|
|
84
|
+
id: "name",
|
|
85
|
+
label: "Name",
|
|
86
|
+
value: x.name,
|
|
87
|
+
onChange: (e) => T("name", e),
|
|
88
|
+
placeholder: "Vintage denim jacket",
|
|
89
|
+
required: !0,
|
|
90
|
+
error: C
|
|
91
|
+
}),
|
|
92
|
+
/* @__PURE__ */ h("div", {
|
|
93
|
+
className: "mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2",
|
|
94
|
+
children: [/* @__PURE__ */ m(o, {
|
|
95
|
+
id: "category",
|
|
96
|
+
label: "Category",
|
|
97
|
+
value: x.category,
|
|
98
|
+
onChange: (e) => T("category", e),
|
|
99
|
+
options: E
|
|
100
|
+
}), /* @__PURE__ */ m(o, {
|
|
101
|
+
id: "source",
|
|
102
|
+
label: "Source",
|
|
103
|
+
value: x.source,
|
|
104
|
+
onChange: (e) => T("source", e),
|
|
105
|
+
options: D
|
|
106
|
+
})]
|
|
107
|
+
}),
|
|
108
|
+
/* @__PURE__ */ h("div", {
|
|
109
|
+
className: "mt-4 grid grid-cols-1 gap-4 sm:grid-cols-3",
|
|
110
|
+
children: [
|
|
111
|
+
/* @__PURE__ */ m(c, {
|
|
112
|
+
id: "brandName",
|
|
113
|
+
label: "Brand",
|
|
114
|
+
value: x.brandName,
|
|
115
|
+
onChange: (e) => T("brandName", e),
|
|
116
|
+
placeholder: "Levi's"
|
|
117
|
+
}),
|
|
118
|
+
/* @__PURE__ */ m(c, {
|
|
119
|
+
id: "colour",
|
|
120
|
+
label: "Colour",
|
|
121
|
+
value: x.colour,
|
|
122
|
+
onChange: (e) => T("colour", e),
|
|
123
|
+
placeholder: "Indigo"
|
|
124
|
+
}),
|
|
125
|
+
/* @__PURE__ */ m(c, {
|
|
126
|
+
id: "size",
|
|
127
|
+
label: "Size",
|
|
128
|
+
value: x.size,
|
|
129
|
+
onChange: (e) => T("size", e),
|
|
130
|
+
placeholder: "M"
|
|
131
|
+
})
|
|
132
|
+
]
|
|
133
|
+
}),
|
|
134
|
+
/* @__PURE__ */ m(s, {
|
|
135
|
+
id: "notes",
|
|
136
|
+
label: "Notes",
|
|
137
|
+
value: x.notes,
|
|
138
|
+
onChange: (e) => T("notes", e),
|
|
139
|
+
placeholder: "Where you got it, how you wear it…",
|
|
140
|
+
rows: 3,
|
|
141
|
+
className: "mt-4"
|
|
142
|
+
}),
|
|
143
|
+
/* @__PURE__ */ m(c, {
|
|
144
|
+
id: "tags",
|
|
145
|
+
label: "Tags",
|
|
146
|
+
value: x.tags,
|
|
147
|
+
onChange: (e) => T("tags", e),
|
|
148
|
+
placeholder: "denim, layering",
|
|
149
|
+
hint: "Comma-separated",
|
|
150
|
+
className: "mt-4"
|
|
151
|
+
}),
|
|
152
|
+
b.isError && /* @__PURE__ */ m("p", {
|
|
153
|
+
className: "mt-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
|
|
154
|
+
children: b.error instanceof Error ? b.error.message : "Something went wrong"
|
|
155
|
+
}),
|
|
156
|
+
/* @__PURE__ */ m("div", {
|
|
157
|
+
className: "mt-6 flex justify-end gap-2",
|
|
158
|
+
children: /* @__PURE__ */ h("button", {
|
|
159
|
+
type: "submit",
|
|
160
|
+
disabled: b.isPending,
|
|
161
|
+
className: "inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50",
|
|
162
|
+
children: [b.isPending ? /* @__PURE__ */ m(f, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ m(p, { className: "h-4 w-4" }), "Add to wardrobe"]
|
|
163
|
+
})
|
|
164
|
+
})
|
|
165
|
+
]
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
//#endregion
|
|
171
|
+
export { y as WardrobeFormPage };
|
|
172
|
+
|
|
173
|
+
//# sourceMappingURL=WardrobeFormPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WardrobeFormPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/wardrobe/WardrobeFormPage.tsx"],"sourcesContent":["/**\n * Wardrobe Item Create Page (self-scoped)\n * @module coolandlovely/pages/wardrobe\n *\n * Drives `cooCreateWardrobeItem`. Belongs to the calling shopper's profile.\n */\n\nimport { useState } from 'react';\nimport { ArrowLeft, Loader2, Save, Heart } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { TextField, TextAreaField, SelectField } from '../../components/Field';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useCreateWardrobeItem } from '../../hooks';\nimport { STYLE_CATEGORIES } from '../../utils/icons';\nimport { parseCsv, titleCase } from '../../utils/formatters';\nimport type { CooStyleCategory, CooWardrobeSource } from '../../types';\n\nconst SOURCES: CooWardrobeSource[] = ['OWNED', 'WISHLIST', 'PURCHASED_ON_PLATFORM', 'GIFTED'];\n\ninterface FormState {\n name: string;\n category: CooStyleCategory;\n source: CooWardrobeSource;\n brandName: string;\n colour: string;\n size: string;\n notes: string;\n tags: string;\n}\n\nconst EMPTY: FormState = {\n name: '',\n category: 'OTHER',\n source: 'OWNED',\n brandName: '',\n colour: '',\n size: '',\n notes: '',\n tags: '',\n};\n\nexport function WardrobeFormPage() {\n const { navigateTo } = useCoolAndLovely();\n const createItem = useCreateWardrobeItem();\n\n const [form, setForm] = useState<FormState>(EMPTY);\n const [nameError, setNameError] = useState<string | undefined>();\n\n const setField = <K extends keyof FormState>(key: K, value: FormState[K]) =>\n setForm((f) => ({ ...f, [key]: value }));\n\n const categoryOptions = STYLE_CATEGORIES.map((c) => ({ value: c, label: titleCase(c) }));\n const sourceOptions = SOURCES.map((s) => ({ value: s, label: titleCase(s) }));\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (!form.name.trim()) {\n setNameError('Name is required');\n return;\n }\n setNameError(undefined);\n\n createItem.mutate(\n {\n name: form.name.trim(),\n category: form.category,\n source: form.source,\n brandName: form.brandName.trim() || undefined,\n colour: form.colour.trim() || undefined,\n size: form.size.trim() || undefined,\n notes: form.notes.trim() || undefined,\n tags: parseCsv(form.tags),\n },\n { onSuccess: () => navigateTo('/wardrobe') }\n );\n };\n\n return (\n <PageLayout\n title=\"Add a wardrobe piece\"\n description=\"Catalogue something you own or want\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/wardrobe')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Cancel\n </button>\n }\n >\n <form onSubmit={handleSubmit} className=\"mx-auto max-w-2xl\">\n <GlassCard treatment=\"plain\" className=\"p-6\">\n <div className=\"mb-6 flex items-center gap-3\">\n <div className=\"flex h-11 w-11 items-center justify-center rounded-lg bg-status-error-bg-subtle text-status-error-text\">\n <Heart className=\"h-5 w-5\" />\n </div>\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">Piece details</h2>\n <p className=\"text-xs text-text-secondary\">Only you can see your wardrobe.</p>\n </div>\n </div>\n\n <TextField\n id=\"name\"\n label=\"Name\"\n value={form.name}\n onChange={(v) => setField('name', v)}\n placeholder=\"Vintage denim jacket\"\n required\n error={nameError}\n />\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <SelectField\n id=\"category\"\n label=\"Category\"\n value={form.category}\n onChange={(v) => setField('category', v as CooStyleCategory)}\n options={categoryOptions}\n />\n <SelectField\n id=\"source\"\n label=\"Source\"\n value={form.source}\n onChange={(v) => setField('source', v as CooWardrobeSource)}\n options={sourceOptions}\n />\n </div>\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <TextField\n id=\"brandName\"\n label=\"Brand\"\n value={form.brandName}\n onChange={(v) => setField('brandName', v)}\n placeholder=\"Levi's\"\n />\n <TextField\n id=\"colour\"\n label=\"Colour\"\n value={form.colour}\n onChange={(v) => setField('colour', v)}\n placeholder=\"Indigo\"\n />\n <TextField\n id=\"size\"\n label=\"Size\"\n value={form.size}\n onChange={(v) => setField('size', v)}\n placeholder=\"M\"\n />\n </div>\n\n <TextAreaField\n id=\"notes\"\n label=\"Notes\"\n value={form.notes}\n onChange={(v) => setField('notes', v)}\n placeholder=\"Where you got it, how you wear it…\"\n rows={3}\n className=\"mt-4\"\n />\n\n <TextField\n id=\"tags\"\n label=\"Tags\"\n value={form.tags}\n onChange={(v) => setField('tags', v)}\n placeholder=\"denim, layering\"\n hint=\"Comma-separated\"\n className=\"mt-4\"\n />\n\n {createItem.isError && (\n <p className=\"mt-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {createItem.error instanceof Error ? createItem.error.message : 'Something went wrong'}\n </p>\n )}\n\n <div className=\"mt-6 flex justify-end gap-2\">\n <button\n type=\"submit\"\n disabled={createItem.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {createItem.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n Add to wardrobe\n </button>\n </div>\n </GlassCard>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { useCoolAndLovely as e } from "../../context/CoolAndLovelyContext.js";
|
|
2
|
+
import { cn as t } from "../../utils/cn.js";
|
|
3
|
+
import { PageLayout as n } from "../../components/PageLayout.js";
|
|
4
|
+
import { StatusBadge as r } from "../../components/StatusBadge.js";
|
|
5
|
+
import { ErrorView as i, LoadingView as a } from "../../components/StateViews.js";
|
|
6
|
+
import { Thumbnail as o } from "../../components/Thumbnail.js";
|
|
7
|
+
import { useLogWardrobeWear as s, useRemoveWardrobeItem as c, useWardrobeItems as l } from "../../hooks/useCoolAndLovelyApi.js";
|
|
8
|
+
import "../../hooks/index.js";
|
|
9
|
+
import { formatRelativeTime as u, titleCase as d } from "../../utils/formatters.js";
|
|
10
|
+
import { STYLE_CATEGORIES as f, categoryIcon as p } from "../../utils/icons.js";
|
|
11
|
+
import { EmptyState as m } from "../../components/EmptyState.js";
|
|
12
|
+
import { useState as h } from "react";
|
|
13
|
+
import { CheckCircle2 as g, Heart as _, Loader2 as v, Plus as y, Trash2 as b } from "lucide-react";
|
|
14
|
+
import { jsx as x, jsxs as S } from "react/jsx-runtime";
|
|
15
|
+
//#region src/coolandlovely/pages/wardrobe/WardrobeListPage.tsx
|
|
16
|
+
var C = [
|
|
17
|
+
{
|
|
18
|
+
value: "ALL",
|
|
19
|
+
label: "All"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
value: "OWNED",
|
|
23
|
+
label: "Owned"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
value: "WISHLIST",
|
|
27
|
+
label: "Wishlist"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
value: "PURCHASED_ON_PLATFORM",
|
|
31
|
+
label: "Purchased"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
value: "GIFTED",
|
|
35
|
+
label: "Gifted"
|
|
36
|
+
}
|
|
37
|
+
];
|
|
38
|
+
function w() {
|
|
39
|
+
let { navigateTo: w } = e(), [T, E] = h("ALL"), [D, O] = h("ALL"), k = l({
|
|
40
|
+
category: T === "ALL" ? void 0 : T,
|
|
41
|
+
source: D === "ALL" ? void 0 : D
|
|
42
|
+
}), A = s(), j = c(), M = k.data ?? [];
|
|
43
|
+
return /* @__PURE__ */ S(n, {
|
|
44
|
+
title: "My Wardrobe",
|
|
45
|
+
description: "A private catalogue of what you own — with a wear log",
|
|
46
|
+
actions: /* @__PURE__ */ S("button", {
|
|
47
|
+
type: "button",
|
|
48
|
+
onClick: () => w("/wardrobe/new"),
|
|
49
|
+
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",
|
|
50
|
+
children: [/* @__PURE__ */ x(y, { className: "h-4 w-4" }), " Add piece"]
|
|
51
|
+
}),
|
|
52
|
+
children: [
|
|
53
|
+
/* @__PURE__ */ x("div", {
|
|
54
|
+
className: "mb-3 flex flex-wrap items-center gap-2",
|
|
55
|
+
children: C.map((e) => /* @__PURE__ */ x("button", {
|
|
56
|
+
type: "button",
|
|
57
|
+
onClick: () => O(e.value),
|
|
58
|
+
className: t("rounded-full px-3 py-1.5 text-sm font-medium transition-colors", D === e.value ? "bg-action-primary-bg text-action-primary-text" : "bg-bg-sunken text-text-secondary hover:text-text-primary"),
|
|
59
|
+
children: e.label
|
|
60
|
+
}, e.value))
|
|
61
|
+
}),
|
|
62
|
+
/* @__PURE__ */ S("div", {
|
|
63
|
+
className: "mb-6 flex flex-wrap items-center gap-2",
|
|
64
|
+
children: [/* @__PURE__ */ x("button", {
|
|
65
|
+
type: "button",
|
|
66
|
+
onClick: () => E("ALL"),
|
|
67
|
+
className: t("rounded-full px-3 py-1.5 text-xs font-medium transition-colors", T === "ALL" ? "bg-accent-purple-subtle text-text-primary" : "bg-bg-sunken text-text-secondary hover:text-text-primary"),
|
|
68
|
+
children: "All categories"
|
|
69
|
+
}), f.map((e) => /* @__PURE__ */ S("button", {
|
|
70
|
+
type: "button",
|
|
71
|
+
onClick: () => E(e),
|
|
72
|
+
className: t("inline-flex items-center gap-1.5 rounded-full px-3 py-1.5 text-xs font-medium transition-colors", T === e ? "bg-accent-purple-subtle text-text-primary" : "bg-bg-sunken text-text-secondary hover:text-text-primary"),
|
|
73
|
+
children: [
|
|
74
|
+
p(e, 14),
|
|
75
|
+
" ",
|
|
76
|
+
d(e)
|
|
77
|
+
]
|
|
78
|
+
}, e))]
|
|
79
|
+
}),
|
|
80
|
+
k.isLoading ? /* @__PURE__ */ x(a, { label: "Loading your wardrobe…" }) : k.isError ? /* @__PURE__ */ x(i, { onRetry: () => void k.refetch() }) : M.length === 0 ? /* @__PURE__ */ x(m, {
|
|
81
|
+
illustration: "empty-data",
|
|
82
|
+
icon: /* @__PURE__ */ x(_, { className: "h-10 w-10" }),
|
|
83
|
+
title: "Your wardrobe is empty",
|
|
84
|
+
description: "Add the pieces you already own (or want) to start your digital closet.",
|
|
85
|
+
action: /* @__PURE__ */ S("button", {
|
|
86
|
+
type: "button",
|
|
87
|
+
onClick: () => w("/wardrobe/new"),
|
|
88
|
+
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",
|
|
89
|
+
children: [/* @__PURE__ */ x(y, { className: "h-4 w-4" }), " Add piece"]
|
|
90
|
+
})
|
|
91
|
+
}) : /* @__PURE__ */ x("div", {
|
|
92
|
+
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
|
|
93
|
+
children: M.map((e) => /* @__PURE__ */ S("div", {
|
|
94
|
+
className: "flex flex-col overflow-hidden rounded-lg border border-border-seam bg-bg-surface shadow-sm",
|
|
95
|
+
children: [
|
|
96
|
+
/* @__PURE__ */ S("div", {
|
|
97
|
+
className: "flex items-start gap-3 p-4",
|
|
98
|
+
children: [/* @__PURE__ */ x(o, {
|
|
99
|
+
icon: p(e.category, 22),
|
|
100
|
+
seed: e.id,
|
|
101
|
+
className: "h-14 w-14 shrink-0"
|
|
102
|
+
}), /* @__PURE__ */ S("div", {
|
|
103
|
+
className: "min-w-0 flex-1",
|
|
104
|
+
children: [
|
|
105
|
+
/* @__PURE__ */ x("p", {
|
|
106
|
+
className: "truncate text-sm font-semibold text-text-primary",
|
|
107
|
+
children: e.name
|
|
108
|
+
}),
|
|
109
|
+
e.brandName && /* @__PURE__ */ x("p", {
|
|
110
|
+
className: "truncate text-xs text-text-secondary",
|
|
111
|
+
children: e.brandName
|
|
112
|
+
}),
|
|
113
|
+
/* @__PURE__ */ S("div", {
|
|
114
|
+
className: "mt-1.5 flex flex-wrap items-center gap-1.5",
|
|
115
|
+
children: [
|
|
116
|
+
/* @__PURE__ */ x(r, { status: e.source }),
|
|
117
|
+
e.colour && /* @__PURE__ */ x("span", {
|
|
118
|
+
className: "rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary",
|
|
119
|
+
children: e.colour
|
|
120
|
+
}),
|
|
121
|
+
e.size && /* @__PURE__ */ x("span", {
|
|
122
|
+
className: "rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary",
|
|
123
|
+
children: e.size
|
|
124
|
+
})
|
|
125
|
+
]
|
|
126
|
+
})
|
|
127
|
+
]
|
|
128
|
+
})]
|
|
129
|
+
}),
|
|
130
|
+
e.notes && /* @__PURE__ */ x("p", {
|
|
131
|
+
className: "px-4 pb-2 text-xs text-text-secondary line-clamp-2",
|
|
132
|
+
children: e.notes
|
|
133
|
+
}),
|
|
134
|
+
/* @__PURE__ */ S("div", {
|
|
135
|
+
className: "mt-auto flex items-center justify-between border-t border-border-seam px-4 py-2.5",
|
|
136
|
+
children: [/* @__PURE__ */ S("span", {
|
|
137
|
+
className: "text-xs text-text-secondary",
|
|
138
|
+
children: [
|
|
139
|
+
"Worn ",
|
|
140
|
+
e.timesWorn,
|
|
141
|
+
"×",
|
|
142
|
+
e.lastWornAt ? ` · ${u(e.lastWornAt)}` : ""
|
|
143
|
+
]
|
|
144
|
+
}), /* @__PURE__ */ S("div", {
|
|
145
|
+
className: "flex items-center gap-1",
|
|
146
|
+
children: [/* @__PURE__ */ S("button", {
|
|
147
|
+
type: "button",
|
|
148
|
+
disabled: A.isPending,
|
|
149
|
+
onClick: () => A.mutate(e.id),
|
|
150
|
+
"aria-label": "Log a wear",
|
|
151
|
+
className: "inline-flex items-center gap-1 rounded-lg px-2 py-1 text-xs font-medium text-status-success-text transition-colors hover:bg-status-success-bg-subtle disabled:opacity-50",
|
|
152
|
+
children: [A.isPending && A.variables === e.id ? /* @__PURE__ */ x(v, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ x(g, { className: "h-3.5 w-3.5" }), "Wore it"]
|
|
153
|
+
}), /* @__PURE__ */ x("button", {
|
|
154
|
+
type: "button",
|
|
155
|
+
disabled: j.isPending,
|
|
156
|
+
onClick: () => j.mutate(e.id),
|
|
157
|
+
"aria-label": "Remove piece",
|
|
158
|
+
className: "inline-flex h-7 w-7 items-center justify-center rounded-lg text-text-secondary transition-colors hover:bg-status-error-bg-subtle hover:text-status-error-text disabled:opacity-50",
|
|
159
|
+
children: /* @__PURE__ */ x(b, { className: "h-3.5 w-3.5" })
|
|
160
|
+
})]
|
|
161
|
+
})]
|
|
162
|
+
})
|
|
163
|
+
]
|
|
164
|
+
}, e.id))
|
|
165
|
+
})
|
|
166
|
+
]
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
//#endregion
|
|
170
|
+
export { w as WardrobeListPage };
|
|
171
|
+
|
|
172
|
+
//# sourceMappingURL=WardrobeListPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WardrobeListPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/wardrobe/WardrobeListPage.tsx"],"sourcesContent":["/**\n * Wardrobe List Page (self-scoped)\n * @module coolandlovely/pages/wardrobe\n *\n * The shopper's digital wardrobe (`cooListWardrobeItems`, self-scoped) with\n * category & source filters, wear logging (`cooLogWardrobeWear`) and removal\n * (`cooRemoveWardrobeItem`).\n */\n\nimport { useState } from 'react';\nimport { Plus, Heart, CheckCircle2, Trash2, Loader2 } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useWardrobeItems, useLogWardrobeWear, useRemoveWardrobeItem } from '../../hooks';\nimport { categoryIcon, STYLE_CATEGORIES } from '../../utils/icons';\nimport { titleCase, formatRelativeTime } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport type { CooStyleCategory, CooWardrobeSource } from '../../types';\n\nconst SOURCES: Array<{ value: CooWardrobeSource | 'ALL'; label: string }> = [\n { value: 'ALL', label: 'All' },\n { value: 'OWNED', label: 'Owned' },\n { value: 'WISHLIST', label: 'Wishlist' },\n { value: 'PURCHASED_ON_PLATFORM', label: 'Purchased' },\n { value: 'GIFTED', label: 'Gifted' },\n];\n\nexport function WardrobeListPage() {\n const { navigateTo } = useCoolAndLovely();\n const [category, setCategory] = useState<CooStyleCategory | 'ALL'>('ALL');\n const [source, setSource] = useState<CooWardrobeSource | 'ALL'>('ALL');\n\n const query = useWardrobeItems({\n category: category === 'ALL' ? undefined : category,\n source: source === 'ALL' ? undefined : source,\n });\n const logWear = useLogWardrobeWear();\n const remove = useRemoveWardrobeItem();\n\n const items = query.data ?? [];\n\n return (\n <PageLayout\n title=\"My Wardrobe\"\n description=\"A private catalogue of what you own — with a wear log\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/wardrobe/new')}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" /> Add piece\n </button>\n }\n >\n {/* Source filter */}\n <div className=\"mb-3 flex flex-wrap items-center gap-2\">\n {SOURCES.map((opt) => (\n <button\n key={opt.value}\n type=\"button\"\n onClick={() => setSource(opt.value)}\n className={cn(\n 'rounded-full px-3 py-1.5 text-sm font-medium transition-colors',\n source === opt.value\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n {opt.label}\n </button>\n ))}\n </div>\n\n {/* Category filter */}\n <div className=\"mb-6 flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setCategory('ALL')}\n className={cn(\n 'rounded-full px-3 py-1.5 text-xs font-medium transition-colors',\n category === 'ALL'\n ? 'bg-accent-purple-subtle text-text-primary'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n All categories\n </button>\n {STYLE_CATEGORIES.map((c) => (\n <button\n key={c}\n type=\"button\"\n onClick={() => setCategory(c)}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-3 py-1.5 text-xs font-medium transition-colors',\n category === c\n ? 'bg-accent-purple-subtle text-text-primary'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n {categoryIcon(c, 14)} {titleCase(c)}\n </button>\n ))}\n </div>\n\n {query.isLoading ? (\n <LoadingView label=\"Loading your wardrobe…\" />\n ) : query.isError ? (\n <ErrorView onRetry={() => void query.refetch()} />\n ) : items.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Heart className=\"h-10 w-10\" />}\n title=\"Your wardrobe is empty\"\n description=\"Add the pieces you already own (or want) to start your digital closet.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/wardrobe/new')}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text\"\n >\n <Plus className=\"h-4 w-4\" /> Add piece\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {items.map((item) => (\n <div\n key={item.id}\n className=\"flex flex-col overflow-hidden rounded-lg border border-border-seam bg-bg-surface shadow-sm\"\n >\n <div className=\"flex items-start gap-3 p-4\">\n <Thumbnail icon={categoryIcon(item.category, 22)} seed={item.id} className=\"h-14 w-14 shrink-0\" />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">{item.name}</p>\n {item.brandName && (\n <p className=\"truncate text-xs text-text-secondary\">{item.brandName}</p>\n )}\n <div className=\"mt-1.5 flex flex-wrap items-center gap-1.5\">\n <StatusBadge status={item.source} />\n {item.colour && (\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n {item.colour}\n </span>\n )}\n {item.size && (\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n {item.size}\n </span>\n )}\n </div>\n </div>\n </div>\n {item.notes && (\n <p className=\"px-4 pb-2 text-xs text-text-secondary line-clamp-2\">{item.notes}</p>\n )}\n <div className=\"mt-auto flex items-center justify-between border-t border-border-seam px-4 py-2.5\">\n <span className=\"text-xs text-text-secondary\">\n Worn {item.timesWorn}×\n {item.lastWornAt ? ` · ${formatRelativeTime(item.lastWornAt)}` : ''}\n </span>\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n disabled={logWear.isPending}\n onClick={() => logWear.mutate(item.id)}\n aria-label=\"Log a wear\"\n className=\"inline-flex items-center gap-1 rounded-lg px-2 py-1 text-xs font-medium text-status-success-text transition-colors hover:bg-status-success-bg-subtle disabled:opacity-50\"\n >\n {logWear.isPending && logWear.variables === item.id ? (\n <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />\n ) : (\n <CheckCircle2 className=\"h-3.5 w-3.5\" />\n )}\n Wore it\n </button>\n <button\n type=\"button\"\n disabled={remove.isPending}\n onClick={() => remove.mutate(item.id)}\n aria-label=\"Remove piece\"\n className=\"inline-flex h-7 w-7 items-center justify-center rounded-lg text-text-secondary transition-colors hover:bg-status-error-bg-subtle hover:text-status-error-text disabled:opacity-50\"\n >\n <Trash2 className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n </div>\n </div>\n ))}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;AAuBA,IAAM,IAAsE;CAC1E;EAAE,OAAO;EAAO,OAAO;EAAO;CAC9B;EAAE,OAAO;EAAS,OAAO;EAAS;CAClC;EAAE,OAAO;EAAY,OAAO;EAAY;CACxC;EAAE,OAAO;EAAyB,OAAO;EAAa;CACtD;EAAE,OAAO;EAAU,OAAO;EAAU;CACrC;AAED,SAAgB,IAAmB;CACjC,IAAM,EAAE,kBAAe,GAAkB,EACnC,CAAC,GAAU,KAAe,EAAmC,MAAM,EACnE,CAAC,GAAQ,KAAa,EAAoC,MAAM,EAEhE,IAAQ,EAAiB;EAC7B,UAAU,MAAa,QAAQ,KAAA,IAAY;EAC3C,QAAQ,MAAW,QAAQ,KAAA,IAAY;EACxC,CAAC,EACI,IAAU,GAAoB,EAC9B,IAAS,GAAuB,EAEhC,IAAQ,EAAM,QAAQ,EAAE;AAE9B,QACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SACE,kBAAC,UAAD;GACE,MAAK;GACL,eAAe,EAAW,gBAAgB;GAC1C,WAAU;aAHZ,CAKE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,aACrB;;YAVb;GAcE,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAQ,KAAK,MACZ,kBAAC,UAAD;KAEE,MAAK;KACL,eAAe,EAAU,EAAI,MAAM;KACnC,WAAW,EACT,kEACA,MAAW,EAAI,QACX,kDACA,2DACL;eAEA,EAAI;KACE,EAXF,EAAI,MAWF,CACT;IACE,CAAA;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAY,MAAM;KACjC,WAAW,EACT,kEACA,MAAa,QACT,8CACA,2DACL;eACF;KAEQ,CAAA,EACR,EAAiB,KAAK,MACrB,kBAAC,UAAD;KAEE,MAAK;KACL,eAAe,EAAY,EAAE;KAC7B,WAAW,EACT,mGACA,MAAa,IACT,8CACA,2DACL;eATH;MAWG,EAAa,GAAG,GAAG;MAAC;MAAE,EAAU,EAAE;MAC5B;OAXF,EAWE,CACT,CACE;;GAEL,EAAM,YACL,kBAAC,GAAD,EAAa,OAAM,0BAA2B,CAAA,GAC5C,EAAM,UACR,kBAAC,GAAD,EAAW,eAAe,KAAK,EAAM,SAAS,EAAI,CAAA,GAChD,EAAM,WAAW,IACnB,kBAAC,GAAD;IACE,cAAa;IACb,MAAM,kBAAC,GAAD,EAAO,WAAU,aAAc,CAAA;IACrC,OAAM;IACN,aAAY;IACZ,QACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,gBAAgB;KAC1C,WAAU;eAHZ,CAKE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,aACrB;;IAEX,CAAA,GAEF,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAM,KAAK,MACV,kBAAC,OAAD;KAEE,WAAU;eAFZ;MAIE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QAAW,MAAM,EAAa,EAAK,UAAU,GAAG;QAAE,MAAM,EAAK;QAAI,WAAU;QAAuB,CAAA,EAClG,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,KAAD;UAAG,WAAU;oBAAoD,EAAK;UAAS,CAAA;SAC9E,EAAK,aACJ,kBAAC,KAAD;UAAG,WAAU;oBAAwC,EAAK;UAAc,CAAA;SAE1E,kBAAC,OAAD;UAAK,WAAU;oBAAf;WACE,kBAAC,GAAD,EAAa,QAAQ,EAAK,QAAU,CAAA;WACnC,EAAK,UACJ,kBAAC,QAAD;YAAM,WAAU;sBACb,EAAK;YACD,CAAA;WAER,EAAK,QACJ,kBAAC,QAAD;YAAM,WAAU;sBACb,EAAK;YACD,CAAA;WAEL;;SACF;UACF;;MACL,EAAK,SACJ,kBAAC,KAAD;OAAG,WAAU;iBAAsD,EAAK;OAAU,CAAA;MAEpF,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBAAhB;SAA8C;SACtC,EAAK;SAAU;SACpB,EAAK,aAAa,MAAM,EAAmB,EAAK,WAAW,KAAK;SAC5D;WACP,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,UAAD;SACE,MAAK;SACL,UAAU,EAAQ;SAClB,eAAe,EAAQ,OAAO,EAAK,GAAG;SACtC,cAAW;SACX,WAAU;mBALZ,CAOG,EAAQ,aAAa,EAAQ,cAAc,EAAK,KAC/C,kBAAC,GAAD,EAAS,WAAU,4BAA6B,CAAA,GAEhD,kBAAC,GAAD,EAAc,WAAU,eAAgB,CAAA,EACxC,UAEK;YACT,kBAAC,UAAD;SACE,MAAK;SACL,UAAU,EAAO;SACjB,eAAe,EAAO,OAAO,EAAK,GAAG;SACrC,cAAW;SACX,WAAU;mBAEV,kBAAC,GAAD,EAAQ,WAAU,eAAgB,CAAA;SAC3B,CAAA,CACL;UACF;;MACF;OA3DC,EAAK,GA2DN,CACN;IACE,CAAA;GAEG"}
|