@burdenoff/microfe-coolandlovely 2026.706.2 → 2026.706.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/coolandlovely/CoolAndLovelyRoot.js +62 -46
- package/dist/coolandlovely/CoolAndLovelyRoot.js.map +1 -1
- package/dist/coolandlovely/components/AddToLookbookButton.js +22 -22
- package/dist/coolandlovely/components/AddToLookbookButton.js.map +1 -1
- package/dist/coolandlovely/components/BrandCover.js +39 -0
- package/dist/coolandlovely/components/BrandCover.js.map +1 -0
- package/dist/coolandlovely/components/BrandCreatorAvatars.js +57 -0
- package/dist/coolandlovely/components/BrandCreatorAvatars.js.map +1 -0
- package/dist/coolandlovely/components/ColourSwatchRow.js +86 -0
- package/dist/coolandlovely/components/ColourSwatchRow.js.map +1 -0
- package/dist/coolandlovely/components/Comments.js +4 -4
- package/dist/coolandlovely/components/Comments.js.map +1 -1
- package/dist/coolandlovely/components/FileImage.js.map +1 -1
- package/dist/coolandlovely/components/ImageUploadField.js +139 -57
- package/dist/coolandlovely/components/ImageUploadField.js.map +1 -1
- package/dist/coolandlovely/components/LookCard.js +77 -0
- package/dist/coolandlovely/components/LookCard.js.map +1 -0
- package/dist/coolandlovely/components/MetricsCard.js +1 -1
- package/dist/coolandlovely/components/MetricsCard.js.map +1 -1
- package/dist/coolandlovely/components/ShopItemCard.js +45 -0
- package/dist/coolandlovely/components/ShopItemCard.js.map +1 -0
- package/dist/coolandlovely/components/StepTracker.js +35 -0
- package/dist/coolandlovely/components/StepTracker.js.map +1 -0
- package/dist/coolandlovely/components/Thumbnail.js +15 -11
- package/dist/coolandlovely/components/Thumbnail.js.map +1 -1
- package/dist/coolandlovely/components/VisibilityChip.js +32 -0
- package/dist/coolandlovely/components/VisibilityChip.js.map +1 -0
- package/dist/coolandlovely/hooks/useCoolAndLovelyApi.js +94 -69
- package/dist/coolandlovely/hooks/useCoolAndLovelyApi.js.map +1 -1
- package/dist/coolandlovely/hooks/useFiles.js +49 -32
- package/dist/coolandlovely/hooks/useFiles.js.map +1 -1
- package/dist/coolandlovely/pages/HomePage.js +112 -133
- package/dist/coolandlovely/pages/HomePage.js.map +1 -1
- package/dist/coolandlovely/pages/brands/BrandDetailPage.js +253 -242
- package/dist/coolandlovely/pages/brands/BrandDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/brands/BrandFormPage.js +116 -112
- package/dist/coolandlovely/pages/brands/BrandFormPage.js.map +1 -1
- package/dist/coolandlovely/pages/brands/BrandsListPage.js +13 -13
- package/dist/coolandlovely/pages/brands/BrandsListPage.js.map +1 -1
- package/dist/coolandlovely/pages/collaborations/CollaborationDetailPage.js +117 -95
- package/dist/coolandlovely/pages/collaborations/CollaborationDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/collaborations/CollaborationFormPage.js +1 -1
- package/dist/coolandlovely/pages/collaborations/CollaborationsListPage.js +50 -43
- package/dist/coolandlovely/pages/collaborations/CollaborationsListPage.js.map +1 -1
- package/dist/coolandlovely/pages/collections/CollectionDetailPage.js +131 -108
- package/dist/coolandlovely/pages/collections/CollectionDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/collections/CollectionFormPage.js +1 -1
- package/dist/coolandlovely/pages/collections/CollectionsListPage.js +59 -56
- package/dist/coolandlovely/pages/collections/CollectionsListPage.js.map +1 -1
- package/dist/coolandlovely/pages/discover/DiscoverPage.js +1 -1
- package/dist/coolandlovely/pages/discover/DiscoverPage.js.map +1 -1
- package/dist/coolandlovely/pages/items/StyleItemDetailPage.js +175 -147
- package/dist/coolandlovely/pages/items/StyleItemDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/items/StyleItemFormPage.js +1 -1
- package/dist/coolandlovely/pages/items/StyleItemsListPage.js +71 -62
- package/dist/coolandlovely/pages/items/StyleItemsListPage.js.map +1 -1
- package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js +149 -127
- package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/lookbooks/LookbooksListPage.js +69 -53
- package/dist/coolandlovely/pages/lookbooks/LookbooksListPage.js.map +1 -1
- package/dist/coolandlovely/pages/looks/LookDetailPage.js +135 -113
- package/dist/coolandlovely/pages/looks/LookDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/looks/LookFormPage.js +122 -110
- package/dist/coolandlovely/pages/looks/LookFormPage.js.map +1 -1
- package/dist/coolandlovely/pages/looks/LooksListPage.js +60 -97
- package/dist/coolandlovely/pages/looks/LooksListPage.js.map +1 -1
- package/dist/coolandlovely/pages/profile/StyleProfilePage.js +372 -210
- package/dist/coolandlovely/pages/profile/StyleProfilePage.js.map +1 -1
- package/dist/coolandlovely/pages/trends/TrendsPage.js +1 -1
- package/dist/coolandlovely/pages/trends/TrendsPage.js.map +1 -1
- package/dist/coolandlovely/pages/wardrobe/WardrobeFormPage.js +130 -94
- package/dist/coolandlovely/pages/wardrobe/WardrobeFormPage.js.map +1 -1
- package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.js +75 -76
- package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,119 +3,135 @@ import { CoolAndLovelyRoutes as t } from "./CoolAndLovelyRoutes.js";
|
|
|
3
3
|
import { useCallback as n, useEffect as r } from "react";
|
|
4
4
|
import { QueryClient as i, QueryClientProvider as a } from "@tanstack/react-query";
|
|
5
5
|
import { useI18n as o } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
6
|
-
import { BookHeart as s,
|
|
7
|
-
import { jsx as
|
|
8
|
-
import { FeatureFlagProvider as
|
|
6
|
+
import { BookHeart as s, Compass as c, Handshake as l, Heart as u, Home as d, Shapes as f, Shirt as p, Sparkles as m, Store as h, TrendingUp as g, UserCircle as _ } from "lucide-react";
|
|
7
|
+
import { jsx as v } from "react/jsx-runtime";
|
|
8
|
+
import { FeatureFlagProvider as y } from "@burdenoff/fe-libs/shared/feature-flags";
|
|
9
9
|
//#region src/coolandlovely/CoolAndLovelyRoot.tsx
|
|
10
|
-
var
|
|
10
|
+
var b = new i({ defaultOptions: { queries: {
|
|
11
11
|
staleTime: 300 * 1e3,
|
|
12
12
|
retry: 1
|
|
13
|
-
} } }),
|
|
14
|
-
let { registerNavItems:
|
|
15
|
-
let n =
|
|
13
|
+
} } }), x = (i) => {
|
|
14
|
+
let { registerNavItems: x, registerFooterItems: S, basePath: C = "/" } = i, { t: w } = o(), T = n((e, t) => {
|
|
15
|
+
let n = w(e);
|
|
16
16
|
return n === e ? t : n;
|
|
17
|
-
}, [
|
|
17
|
+
}, [w]), E = (e, t) => (e.endsWith("/") ? e.slice(0, -1) : e) + (t.startsWith("/") ? t : `/${t}`);
|
|
18
18
|
return r(() => {
|
|
19
|
-
if (
|
|
19
|
+
if (x) return x([
|
|
20
20
|
{
|
|
21
21
|
id: "coolandlovely-overview",
|
|
22
|
-
label:
|
|
23
|
-
path:
|
|
24
|
-
icon: /* @__PURE__ */
|
|
22
|
+
label: T("coolandlovely.nav.overview", "Overview"),
|
|
23
|
+
path: E(C, ""),
|
|
24
|
+
icon: /* @__PURE__ */ v(d, { size: 20 }),
|
|
25
25
|
order: 1,
|
|
26
26
|
group: "main"
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
29
|
id: "coolandlovely-brands",
|
|
30
|
-
label:
|
|
31
|
-
path:
|
|
32
|
-
icon: /* @__PURE__ */
|
|
30
|
+
label: T("coolandlovely.nav.brands", "Brands"),
|
|
31
|
+
path: E(C, "brands"),
|
|
32
|
+
icon: /* @__PURE__ */ v(h, { size: 20 }),
|
|
33
33
|
order: 10,
|
|
34
34
|
group: "catalog"
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
id: "coolandlovely-collections",
|
|
38
|
-
label:
|
|
39
|
-
path:
|
|
40
|
-
icon: /* @__PURE__ */
|
|
38
|
+
label: T("coolandlovely.nav.collections", "Collections"),
|
|
39
|
+
path: E(C, "collections"),
|
|
40
|
+
icon: /* @__PURE__ */ v(f, { size: 20 }),
|
|
41
41
|
order: 11,
|
|
42
42
|
group: "catalog"
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
45
|
id: "coolandlovely-items",
|
|
46
|
-
label:
|
|
47
|
-
path:
|
|
48
|
-
icon: /* @__PURE__ */
|
|
46
|
+
label: T("coolandlovely.nav.items", "Style Items"),
|
|
47
|
+
path: E(C, "items"),
|
|
48
|
+
icon: /* @__PURE__ */ v(p, { size: 20 }),
|
|
49
49
|
order: 12,
|
|
50
50
|
group: "catalog"
|
|
51
51
|
},
|
|
52
52
|
{
|
|
53
53
|
id: "coolandlovely-looks",
|
|
54
|
-
label:
|
|
55
|
-
path:
|
|
56
|
-
icon: /* @__PURE__ */
|
|
54
|
+
label: T("coolandlovely.nav.looks", "Looks"),
|
|
55
|
+
path: E(C, "looks"),
|
|
56
|
+
icon: /* @__PURE__ */ v(m, { size: 20 }),
|
|
57
57
|
order: 20,
|
|
58
58
|
group: "social"
|
|
59
59
|
},
|
|
60
60
|
{
|
|
61
61
|
id: "coolandlovely-collaborations",
|
|
62
|
-
label:
|
|
63
|
-
path:
|
|
64
|
-
icon: /* @__PURE__ */
|
|
62
|
+
label: T("coolandlovely.nav.collaborations", "Collaborations"),
|
|
63
|
+
path: E(C, "collaborations"),
|
|
64
|
+
icon: /* @__PURE__ */ v(l, { size: 20 }),
|
|
65
65
|
order: 21,
|
|
66
66
|
group: "social"
|
|
67
67
|
},
|
|
68
68
|
{
|
|
69
69
|
id: "coolandlovely-wardrobe",
|
|
70
|
-
label:
|
|
71
|
-
path:
|
|
72
|
-
icon: /* @__PURE__ */
|
|
70
|
+
label: T("coolandlovely.nav.wardrobe", "My Wardrobe"),
|
|
71
|
+
path: E(C, "wardrobe"),
|
|
72
|
+
icon: /* @__PURE__ */ v(u, { size: 20 }),
|
|
73
73
|
order: 30,
|
|
74
74
|
group: "my-style"
|
|
75
75
|
},
|
|
76
76
|
{
|
|
77
77
|
id: "coolandlovely-lookbooks",
|
|
78
|
-
label:
|
|
79
|
-
path:
|
|
80
|
-
icon: /* @__PURE__ */
|
|
78
|
+
label: T("coolandlovely.nav.lookbooks", "Lookbooks"),
|
|
79
|
+
path: E(C, "lookbooks"),
|
|
80
|
+
icon: /* @__PURE__ */ v(s, { size: 20 }),
|
|
81
81
|
order: 31,
|
|
82
82
|
group: "my-style"
|
|
83
83
|
},
|
|
84
84
|
{
|
|
85
85
|
id: "coolandlovely-profile",
|
|
86
|
-
label:
|
|
87
|
-
path:
|
|
88
|
-
icon: /* @__PURE__ */
|
|
86
|
+
label: T("coolandlovely.nav.profile", "Style Profile"),
|
|
87
|
+
path: E(C, "profile"),
|
|
88
|
+
icon: /* @__PURE__ */ v(_, { size: 20 }),
|
|
89
89
|
order: 32,
|
|
90
90
|
group: "my-style"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: "coolandlovely-discover",
|
|
94
|
+
label: T("coolandlovely.nav.discover", "Discover"),
|
|
95
|
+
path: E(C, "discover"),
|
|
96
|
+
icon: /* @__PURE__ */ v(c, { size: 20 }),
|
|
97
|
+
order: 40,
|
|
98
|
+
group: "signals"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: "coolandlovely-trends",
|
|
102
|
+
label: T("coolandlovely.nav.trends", "Trends"),
|
|
103
|
+
path: E(C, "trends"),
|
|
104
|
+
icon: /* @__PURE__ */ v(g, { size: 20 }),
|
|
105
|
+
order: 41,
|
|
106
|
+
group: "signals"
|
|
91
107
|
}
|
|
92
108
|
]);
|
|
93
109
|
}, [
|
|
94
|
-
y,
|
|
95
110
|
x,
|
|
96
|
-
C
|
|
111
|
+
C,
|
|
112
|
+
T
|
|
97
113
|
]), r(() => {
|
|
98
|
-
if (
|
|
114
|
+
if (S) return S([{
|
|
99
115
|
id: "coolandlovely-status",
|
|
100
|
-
label:
|
|
116
|
+
label: T("coolandlovely.footer.ready", "CoolAndLovely"),
|
|
101
117
|
type: "status",
|
|
102
118
|
order: 10,
|
|
103
119
|
section: "left"
|
|
104
120
|
}]);
|
|
105
|
-
}, [
|
|
106
|
-
client:
|
|
107
|
-
children: /* @__PURE__ */
|
|
121
|
+
}, [S, T]), /* @__PURE__ */ v(a, {
|
|
122
|
+
client: b,
|
|
123
|
+
children: /* @__PURE__ */ v(y, {
|
|
108
124
|
workspaceId: null,
|
|
109
125
|
gateway: "global",
|
|
110
126
|
defaultEnabled: !0,
|
|
111
|
-
children: /* @__PURE__ */
|
|
127
|
+
children: /* @__PURE__ */ v(e, {
|
|
112
128
|
...i,
|
|
113
|
-
children: /* @__PURE__ */
|
|
129
|
+
children: /* @__PURE__ */ v(t, {})
|
|
114
130
|
})
|
|
115
131
|
})
|
|
116
132
|
});
|
|
117
133
|
};
|
|
118
134
|
//#endregion
|
|
119
|
-
export {
|
|
135
|
+
export { x as CoolAndLovelyRoot };
|
|
120
136
|
|
|
121
137
|
//# sourceMappingURL=CoolAndLovelyRoot.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CoolAndLovelyRoot.js","names":[],"sources":["../../src/coolandlovely/CoolAndLovelyRoot.tsx"],"sourcesContent":["/**\n * CoolAndLovely Root Component\n * @module coolandlovely\n *\n * Single Root export consumed by host application shells. The export name and\n * prop contract (basePath / navigate / currentUser / apiGatewayUrl /\n * registerNavItems / registerFooterItems / workspace context) are stable — the\n * host imports `CoolAndLovelyRoot` the same way regardless of the internal UI.\n */\n\nimport type { FC } from 'react';\nimport { useCallback, useEffect } from 'react';\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n Home,\n Store,\n Shapes,\n Shirt,\n Sparkles,\n Handshake,\n Heart,\n BookHeart,\n UserCircle,\n} from 'lucide-react';\nimport { CoolAndLovelyProvider } from './context/CoolAndLovelyContext';\nimport { FeatureFlagProvider } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { CoolAndLovelyRoutes } from './CoolAndLovelyRoutes';\nimport type { CoolAndLovelyRootProps, NavItem } from './types';\n\n// Create a query client for this microfrontend\nconst queryClient = new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 5 * 60 * 1000,\n retry: 1,\n },\n },\n});\n\nexport const CoolAndLovelyRoot: FC<CoolAndLovelyRootProps> = (props) => {\n const { registerNavItems, registerFooterItems, basePath = '/' } = props;\n const { t: translate } = useI18n();\n const t = useCallback(\n (key: string, fallback: string): string => {\n const value = translate(key);\n return value === key ? fallback : value;\n },\n [translate]\n );\n\n // Helper to join paths correctly\n const joinPath = (base: string, path: string): string => {\n const cleanBase = base.endsWith('/') ? base.slice(0, -1) : base;\n const cleanPath = path.startsWith('/') ? path : `/${path}`;\n return cleanBase + cleanPath;\n };\n\n // Register navigation items with the shell on mount.\n useEffect(() => {\n if (!registerNavItems) return;\n\n const navItems: NavItem[] = [\n {\n id: 'coolandlovely-overview',\n label: t('coolandlovely.nav.overview', 'Overview'),\n path: joinPath(basePath, ''),\n icon: <Home size={20} />,\n order: 1,\n group: 'main',\n },\n // Catalog\n {\n id: 'coolandlovely-brands',\n label: t('coolandlovely.nav.brands', 'Brands'),\n path: joinPath(basePath, 'brands'),\n icon: <Store size={20} />,\n order: 10,\n group: 'catalog',\n },\n {\n id: 'coolandlovely-collections',\n label: t('coolandlovely.nav.collections', 'Collections'),\n path: joinPath(basePath, 'collections'),\n icon: <Shapes size={20} />,\n order: 11,\n group: 'catalog',\n },\n {\n id: 'coolandlovely-items',\n label: t('coolandlovely.nav.items', 'Style Items'),\n path: joinPath(basePath, 'items'),\n icon: <Shirt size={20} />,\n order: 12,\n group: 'catalog',\n },\n // Social\n {\n id: 'coolandlovely-looks',\n label: t('coolandlovely.nav.looks', 'Looks'),\n path: joinPath(basePath, 'looks'),\n icon: <Sparkles size={20} />,\n order: 20,\n group: 'social',\n },\n {\n id: 'coolandlovely-collaborations',\n label: t('coolandlovely.nav.collaborations', 'Collaborations'),\n path: joinPath(basePath, 'collaborations'),\n icon: <Handshake size={20} />,\n order: 21,\n group: 'social',\n },\n // My style\n {\n id: 'coolandlovely-wardrobe',\n label: t('coolandlovely.nav.wardrobe', 'My Wardrobe'),\n path: joinPath(basePath, 'wardrobe'),\n icon: <Heart size={20} />,\n order: 30,\n group: 'my-style',\n },\n {\n id: 'coolandlovely-lookbooks',\n label: t('coolandlovely.nav.lookbooks', 'Lookbooks'),\n path: joinPath(basePath, 'lookbooks'),\n icon: <BookHeart size={20} />,\n order: 31,\n group: 'my-style',\n },\n {\n id: 'coolandlovely-profile',\n label: t('coolandlovely.nav.profile', 'Style Profile'),\n path: joinPath(basePath, 'profile'),\n icon: <UserCircle size={20} />,\n order: 32,\n group: 'my-style',\n },\n ];\n\n const cleanup = registerNavItems(navItems);\n return cleanup;\n }, [registerNavItems, basePath, t]);\n\n // Register footer items\n useEffect(() => {\n if (!registerFooterItems) return;\n\n const footerItems = [\n {\n id: 'coolandlovely-status',\n label: t('coolandlovely.footer.ready', 'CoolAndLovely'),\n type: 'status' as const,\n order: 10,\n section: 'left' as const,\n },\n ];\n\n const cleanup = registerFooterItems(footerItems);\n return cleanup;\n }, [registerFooterItems, t]);\n\n return (\n <QueryClientProvider client={queryClient}>\n <FeatureFlagProvider workspaceId={null} gateway=\"global\" defaultEnabled={true}>\n <CoolAndLovelyProvider {...props}>\n <CoolAndLovelyRoutes />\n </CoolAndLovelyProvider>\n </FeatureFlagProvider>\n </QueryClientProvider>\n );\n};\n"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"CoolAndLovelyRoot.js","names":[],"sources":["../../src/coolandlovely/CoolAndLovelyRoot.tsx"],"sourcesContent":["/**\n * CoolAndLovely Root Component\n * @module coolandlovely\n *\n * Single Root export consumed by host application shells. The export name and\n * prop contract (basePath / navigate / currentUser / apiGatewayUrl /\n * registerNavItems / registerFooterItems / workspace context) are stable — the\n * host imports `CoolAndLovelyRoot` the same way regardless of the internal UI.\n */\n\nimport type { FC } from 'react';\nimport { useCallback, useEffect } from 'react';\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n Home,\n Store,\n Shapes,\n Shirt,\n Sparkles,\n Handshake,\n Heart,\n BookHeart,\n UserCircle,\n Compass,\n TrendingUp,\n} from 'lucide-react';\nimport { CoolAndLovelyProvider } from './context/CoolAndLovelyContext';\nimport { FeatureFlagProvider } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { CoolAndLovelyRoutes } from './CoolAndLovelyRoutes';\nimport type { CoolAndLovelyRootProps, NavItem } from './types';\n\n// Create a query client for this microfrontend\nconst queryClient = new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 5 * 60 * 1000,\n retry: 1,\n },\n },\n});\n\nexport const CoolAndLovelyRoot: FC<CoolAndLovelyRootProps> = (props) => {\n const { registerNavItems, registerFooterItems, basePath = '/' } = props;\n const { t: translate } = useI18n();\n const t = useCallback(\n (key: string, fallback: string): string => {\n const value = translate(key);\n return value === key ? fallback : value;\n },\n [translate]\n );\n\n // Helper to join paths correctly\n const joinPath = (base: string, path: string): string => {\n const cleanBase = base.endsWith('/') ? base.slice(0, -1) : base;\n const cleanPath = path.startsWith('/') ? path : `/${path}`;\n return cleanBase + cleanPath;\n };\n\n // Register navigation items with the shell on mount.\n useEffect(() => {\n if (!registerNavItems) return;\n\n const navItems: NavItem[] = [\n {\n id: 'coolandlovely-overview',\n label: t('coolandlovely.nav.overview', 'Overview'),\n path: joinPath(basePath, ''),\n icon: <Home size={20} />,\n order: 1,\n group: 'main',\n },\n // Catalog\n {\n id: 'coolandlovely-brands',\n label: t('coolandlovely.nav.brands', 'Brands'),\n path: joinPath(basePath, 'brands'),\n icon: <Store size={20} />,\n order: 10,\n group: 'catalog',\n },\n {\n id: 'coolandlovely-collections',\n label: t('coolandlovely.nav.collections', 'Collections'),\n path: joinPath(basePath, 'collections'),\n icon: <Shapes size={20} />,\n order: 11,\n group: 'catalog',\n },\n {\n id: 'coolandlovely-items',\n label: t('coolandlovely.nav.items', 'Style Items'),\n path: joinPath(basePath, 'items'),\n icon: <Shirt size={20} />,\n order: 12,\n group: 'catalog',\n },\n // Social\n {\n id: 'coolandlovely-looks',\n label: t('coolandlovely.nav.looks', 'Looks'),\n path: joinPath(basePath, 'looks'),\n icon: <Sparkles size={20} />,\n order: 20,\n group: 'social',\n },\n {\n id: 'coolandlovely-collaborations',\n label: t('coolandlovely.nav.collaborations', 'Collaborations'),\n path: joinPath(basePath, 'collaborations'),\n icon: <Handshake size={20} />,\n order: 21,\n group: 'social',\n },\n // My style\n {\n id: 'coolandlovely-wardrobe',\n label: t('coolandlovely.nav.wardrobe', 'My Wardrobe'),\n path: joinPath(basePath, 'wardrobe'),\n icon: <Heart size={20} />,\n order: 30,\n group: 'my-style',\n },\n {\n id: 'coolandlovely-lookbooks',\n label: t('coolandlovely.nav.lookbooks', 'Lookbooks'),\n path: joinPath(basePath, 'lookbooks'),\n icon: <BookHeart size={20} />,\n order: 31,\n group: 'my-style',\n },\n {\n id: 'coolandlovely-profile',\n label: t('coolandlovely.nav.profile', 'Style Profile'),\n path: joinPath(basePath, 'profile'),\n icon: <UserCircle size={20} />,\n order: 32,\n group: 'my-style',\n },\n {\n id: 'coolandlovely-discover',\n label: t('coolandlovely.nav.discover', 'Discover'),\n path: joinPath(basePath, 'discover'),\n icon: <Compass size={20} />,\n order: 40,\n group: 'signals',\n },\n {\n id: 'coolandlovely-trends',\n label: t('coolandlovely.nav.trends', 'Trends'),\n path: joinPath(basePath, 'trends'),\n icon: <TrendingUp size={20} />,\n order: 41,\n group: 'signals',\n },\n ];\n\n const cleanup = registerNavItems(navItems);\n return cleanup;\n }, [registerNavItems, basePath, t]);\n\n // Register footer items\n useEffect(() => {\n if (!registerFooterItems) return;\n\n const footerItems = [\n {\n id: 'coolandlovely-status',\n label: t('coolandlovely.footer.ready', 'CoolAndLovely'),\n type: 'status' as const,\n order: 10,\n section: 'left' as const,\n },\n ];\n\n const cleanup = registerFooterItems(footerItems);\n return cleanup;\n }, [registerFooterItems, t]);\n\n return (\n <QueryClientProvider client={queryClient}>\n <FeatureFlagProvider workspaceId={null} gateway=\"global\" defaultEnabled={true}>\n <CoolAndLovelyProvider {...props}>\n <CoolAndLovelyRoutes />\n </CoolAndLovelyProvider>\n </FeatureFlagProvider>\n </QueryClientProvider>\n );\n};\n"],"mappings":";;;;;;;;;AAiCA,IAAM,IAAc,IAAI,EAAY,EAClC,gBAAgB,EACd,SAAS;CACP,WAAW,MAAS;CACpB,OAAO;CACR,EACF,EACF,CAAC,EAEW,KAAiD,MAAU;CACtE,IAAM,EAAE,qBAAkB,wBAAqB,cAAW,QAAQ,GAC5D,EAAE,GAAG,MAAc,GAAS,EAC5B,IAAI,GACP,GAAa,MAA6B;EACzC,IAAM,IAAQ,EAAU,EAAI;AAC5B,SAAO,MAAU,IAAM,IAAW;IAEpC,CAAC,EAAU,CACZ,EAGK,KAAY,GAAc,OACZ,EAAK,SAAS,IAAI,GAAG,EAAK,MAAM,GAAG,GAAG,GAAG,MACzC,EAAK,WAAW,IAAI,GAAG,IAAO,IAAI;AA4HtD,QAvHA,QAAgB;AACT,QAiGL,QADgB,EA9FY;GAC1B;IACE,IAAI;IACJ,OAAO,EAAE,8BAA8B,WAAW;IAClD,MAAM,EAAS,GAAU,GAAG;IAC5B,MAAM,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA;IACxB,OAAO;IACP,OAAO;IACR;GAED;IACE,IAAI;IACJ,OAAO,EAAE,4BAA4B,SAAS;IAC9C,MAAM,EAAS,GAAU,SAAS;IAClC,MAAM,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA;IACzB,OAAO;IACP,OAAO;IACR;GACD;IACE,IAAI;IACJ,OAAO,EAAE,iCAAiC,cAAc;IACxD,MAAM,EAAS,GAAU,cAAc;IACvC,MAAM,kBAAC,GAAD,EAAQ,MAAM,IAAM,CAAA;IAC1B,OAAO;IACP,OAAO;IACR;GACD;IACE,IAAI;IACJ,OAAO,EAAE,2BAA2B,cAAc;IAClD,MAAM,EAAS,GAAU,QAAQ;IACjC,MAAM,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA;IACzB,OAAO;IACP,OAAO;IACR;GAED;IACE,IAAI;IACJ,OAAO,EAAE,2BAA2B,QAAQ;IAC5C,MAAM,EAAS,GAAU,QAAQ;IACjC,MAAM,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA;IAC5B,OAAO;IACP,OAAO;IACR;GACD;IACE,IAAI;IACJ,OAAO,EAAE,oCAAoC,iBAAiB;IAC9D,MAAM,EAAS,GAAU,iBAAiB;IAC1C,MAAM,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA;IAC7B,OAAO;IACP,OAAO;IACR;GAED;IACE,IAAI;IACJ,OAAO,EAAE,8BAA8B,cAAc;IACrD,MAAM,EAAS,GAAU,WAAW;IACpC,MAAM,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA;IACzB,OAAO;IACP,OAAO;IACR;GACD;IACE,IAAI;IACJ,OAAO,EAAE,+BAA+B,YAAY;IACpD,MAAM,EAAS,GAAU,YAAY;IACrC,MAAM,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA;IAC7B,OAAO;IACP,OAAO;IACR;GACD;IACE,IAAI;IACJ,OAAO,EAAE,6BAA6B,gBAAgB;IACtD,MAAM,EAAS,GAAU,UAAU;IACnC,MAAM,kBAAC,GAAD,EAAY,MAAM,IAAM,CAAA;IAC9B,OAAO;IACP,OAAO;IACR;GACD;IACE,IAAI;IACJ,OAAO,EAAE,8BAA8B,WAAW;IAClD,MAAM,EAAS,GAAU,WAAW;IACpC,MAAM,kBAAC,GAAD,EAAS,MAAM,IAAM,CAAA;IAC3B,OAAO;IACP,OAAO;IACR;GACD;IACE,IAAI;IACJ,OAAO,EAAE,4BAA4B,SAAS;IAC9C,MAAM,EAAS,GAAU,SAAS;IAClC,MAAM,kBAAC,GAAD,EAAY,MAAM,IAAM,CAAA;IAC9B,OAAO;IACP,OAAO;IACR;GACF,CAEyC;IAEzC;EAAC;EAAkB;EAAU;EAAE,CAAC,EAGnC,QAAgB;AACT,QAaL,QADgB,EAVI,CAClB;GACE,IAAI;GACJ,OAAO,EAAE,8BAA8B,gBAAgB;GACvD,MAAM;GACN,OAAO;GACP,SAAS;GACV,CACF,CAE+C;IAE/C,CAAC,GAAqB,EAAE,CAAC,EAG1B,kBAAC,GAAD;EAAqB,QAAQ;YAC3B,kBAAC,GAAD;GAAqB,aAAa;GAAM,SAAQ;GAAS,gBAAgB;aACvE,kBAAC,GAAD;IAAuB,GAAI;cACzB,kBAAC,GAAD,EAAuB,CAAA;IACD,CAAA;GACJ,CAAA;EACF,CAAA"}
|
|
@@ -6,71 +6,71 @@ import { BookHeart as s, Check as c, ChevronDown as l, Loader2 as u, Plus as d }
|
|
|
6
6
|
import { jsx as f, jsxs as p } from "react/jsx-runtime";
|
|
7
7
|
//#region src/coolandlovely/components/AddToLookbookButton.tsx
|
|
8
8
|
var m = ({ lookId: m, styleItemId: h, className: g }) => {
|
|
9
|
-
let { navigateTo: _ } = e(),
|
|
9
|
+
let { navigateTo: _, currentUser: v } = e(), y = r(v?.id), b = n(), [x, S] = o(!1), [C, w] = o(null), T = a(null);
|
|
10
10
|
i(() => {
|
|
11
|
-
if (!
|
|
11
|
+
if (!x) return;
|
|
12
12
|
let e = (e) => {
|
|
13
|
-
|
|
13
|
+
T.current && !T.current.contains(e.target) && S(!1);
|
|
14
14
|
};
|
|
15
15
|
return document.addEventListener("mousedown", e), () => document.removeEventListener("mousedown", e);
|
|
16
|
-
}, [
|
|
17
|
-
let
|
|
18
|
-
|
|
16
|
+
}, [x]);
|
|
17
|
+
let E = (y.data ?? []).filter((e) => e.ownerId === v?.id), D = (e) => {
|
|
18
|
+
b.mutate({
|
|
19
19
|
lookbookId: e,
|
|
20
20
|
lookId: m,
|
|
21
21
|
styleItemId: h
|
|
22
22
|
}, { onSuccess: () => {
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
w(e), setTimeout(() => {
|
|
24
|
+
S(!1), w(null);
|
|
25
25
|
}, 900);
|
|
26
26
|
} });
|
|
27
27
|
};
|
|
28
28
|
return /* @__PURE__ */ p("div", {
|
|
29
|
-
ref:
|
|
29
|
+
ref: T,
|
|
30
30
|
className: t("relative", g),
|
|
31
31
|
children: [/* @__PURE__ */ p("button", {
|
|
32
32
|
type: "button",
|
|
33
|
-
onClick: () =>
|
|
33
|
+
onClick: () => S((e) => !e),
|
|
34
34
|
"aria-haspopup": "menu",
|
|
35
|
-
"aria-expanded":
|
|
35
|
+
"aria-expanded": x,
|
|
36
36
|
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 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]",
|
|
37
37
|
children: [
|
|
38
38
|
/* @__PURE__ */ f(s, { className: "h-4 w-4" }),
|
|
39
39
|
" Add to lookbook",
|
|
40
40
|
/* @__PURE__ */ f(l, { className: "h-3.5 w-3.5" })
|
|
41
41
|
]
|
|
42
|
-
}),
|
|
42
|
+
}), x && /* @__PURE__ */ p("div", {
|
|
43
43
|
role: "menu",
|
|
44
44
|
className: "absolute right-0 z-20 mt-1 w-60 overflow-hidden rounded-lg border border-border-seam bg-bg-surface shadow-lg",
|
|
45
|
-
children: [
|
|
45
|
+
children: [y.isLoading ? /* @__PURE__ */ p("div", {
|
|
46
46
|
className: "flex items-center gap-2 px-3 py-3 text-sm text-text-secondary",
|
|
47
47
|
children: [/* @__PURE__ */ f(u, { className: "h-4 w-4 animate-spin" }), " Loading lookbooks…"]
|
|
48
|
-
}) :
|
|
48
|
+
}) : E.length === 0 ? /* @__PURE__ */ p("button", {
|
|
49
49
|
type: "button",
|
|
50
50
|
onClick: () => {
|
|
51
|
-
|
|
51
|
+
S(!1), _("/lookbooks");
|
|
52
52
|
},
|
|
53
53
|
className: "flex w-full items-center gap-2 px-3 py-3 text-left text-sm text-text-primary hover:bg-bg-sunken",
|
|
54
54
|
children: [/* @__PURE__ */ f(d, { className: "h-4 w-4" }), " Create your first lookbook"]
|
|
55
55
|
}) : /* @__PURE__ */ f("ul", {
|
|
56
56
|
className: "max-h-64 overflow-auto py-1",
|
|
57
|
-
children:
|
|
58
|
-
let t =
|
|
57
|
+
children: E.map((e) => {
|
|
58
|
+
let t = C === e.id;
|
|
59
59
|
return /* @__PURE__ */ f("li", { children: /* @__PURE__ */ p("button", {
|
|
60
60
|
type: "button",
|
|
61
61
|
role: "menuitem",
|
|
62
|
-
disabled:
|
|
63
|
-
onClick: () =>
|
|
62
|
+
disabled: b.isPending,
|
|
63
|
+
onClick: () => D(e.id),
|
|
64
64
|
className: "flex w-full items-center justify-between gap-2 px-3 py-2 text-left text-sm text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-50",
|
|
65
65
|
children: [/* @__PURE__ */ f("span", {
|
|
66
66
|
className: "truncate",
|
|
67
67
|
children: e.name
|
|
68
|
-
}), t ? /* @__PURE__ */ f(c, { className: "h-4 w-4 shrink-0 text-status-success-text" }) :
|
|
68
|
+
}), t ? /* @__PURE__ */ f(c, { className: "h-4 w-4 shrink-0 text-status-success-text" }) : b.isPending ? /* @__PURE__ */ f(u, { className: "h-4 w-4 shrink-0 animate-spin text-text-secondary" }) : null]
|
|
69
69
|
}) }, e.id);
|
|
70
70
|
})
|
|
71
|
-
}),
|
|
71
|
+
}), b.isError && /* @__PURE__ */ f("p", {
|
|
72
72
|
className: "border-t border-border-seam px-3 py-2 text-xs text-status-error-text",
|
|
73
|
-
children:
|
|
73
|
+
children: b.error instanceof Error ? b.error.message : "Could not add"
|
|
74
74
|
})]
|
|
75
75
|
})]
|
|
76
76
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AddToLookbookButton.js","names":[],"sources":["../../../src/coolandlovely/components/AddToLookbookButton.tsx"],"sourcesContent":["/**\n * AddToLookbookButton — clip a look or style item into one of the caller's\n * lookbooks (cooAddLookbookEntry).\n * @module coolandlovely/components\n *\n * Exactly one of `lookId` / `styleItemId` should be provided. Opens a small\n * menu of the caller's lookbooks; picking one adds the entry. If the shopper\n * has no lookbooks yet, offers a jump to the lookbooks page.\n */\n\nimport type { FC } from 'react';\nimport { useEffect, useRef, useState } from 'react';\nimport { BookHeart, Check, ChevronDown, Loader2, Plus } from 'lucide-react';\nimport { useCoolAndLovely } from '../context/CoolAndLovelyContext';\nimport { useAddLookbookEntry, useLookbooks } from '../hooks/useCoolAndLovelyApi';\nimport { cn } from '../utils/cn';\n\ninterface AddToLookbookButtonProps {\n lookId?: string;\n styleItemId?: string;\n className?: string;\n}\n\nexport const AddToLookbookButton: FC<AddToLookbookButtonProps> = ({\n lookId,\n styleItemId,\n className,\n}) => {\n const { navigateTo } = useCoolAndLovely();\n const lookbooks = useLookbooks();\n const addEntry = useAddLookbookEntry();\n const [open, setOpen] = useState(false);\n const [addedTo, setAddedTo] = useState<string | null>(null);\n const containerRef = useRef<HTMLDivElement | null>(null);\n\n useEffect(() => {\n if (!open) return;\n const onClick = (e: MouseEvent) => {\n if (containerRef.current && !containerRef.current.contains(e.target as Node)) {\n setOpen(false);\n }\n };\n document.addEventListener('mousedown', onClick);\n return () => document.removeEventListener('mousedown', onClick);\n }, [open]);\n\n const boards = lookbooks.data ?? [];\n\n const handleAdd = (lookbookId: string) => {\n addEntry.mutate(\n { lookbookId, lookId, styleItemId },\n {\n onSuccess: () => {\n setAddedTo(lookbookId);\n setTimeout(() => {\n setOpen(false);\n setAddedTo(null);\n }, 900);\n },\n }\n );\n };\n\n return (\n <div ref={containerRef} className={cn('relative', className)}>\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n aria-haspopup=\"menu\"\n aria-expanded={open}\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 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <BookHeart className=\"h-4 w-4\" /> Add to lookbook\n <ChevronDown className=\"h-3.5 w-3.5\" />\n </button>\n\n {open && (\n <div\n role=\"menu\"\n className=\"absolute right-0 z-20 mt-1 w-60 overflow-hidden rounded-lg border border-border-seam bg-bg-surface shadow-lg\"\n >\n {lookbooks.isLoading ? (\n <div className=\"flex items-center gap-2 px-3 py-3 text-sm text-text-secondary\">\n <Loader2 className=\"h-4 w-4 animate-spin\" /> Loading lookbooks…\n </div>\n ) : boards.length === 0 ? (\n <button\n type=\"button\"\n onClick={() => {\n setOpen(false);\n navigateTo('/lookbooks');\n }}\n className=\"flex w-full items-center gap-2 px-3 py-3 text-left text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Plus className=\"h-4 w-4\" /> Create your first lookbook\n </button>\n ) : (\n <ul className=\"max-h-64 overflow-auto py-1\">\n {boards.map((lb) => {\n const done = addedTo === lb.id;\n return (\n <li key={lb.id}>\n <button\n type=\"button\"\n role=\"menuitem\"\n disabled={addEntry.isPending}\n onClick={() => handleAdd(lb.id)}\n className=\"flex w-full items-center justify-between gap-2 px-3 py-2 text-left text-sm text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-50\"\n >\n <span className=\"truncate\">{lb.name}</span>\n {done ? (\n <Check className=\"h-4 w-4 shrink-0 text-status-success-text\" />\n ) : addEntry.isPending ? (\n <Loader2 className=\"h-4 w-4 shrink-0 animate-spin text-text-secondary\" />\n ) : null}\n </button>\n </li>\n );\n })}\n </ul>\n )}\n {addEntry.isError && (\n <p className=\"border-t border-border-seam px-3 py-2 text-xs text-status-error-text\">\n {addEntry.error instanceof Error ? addEntry.error.message : 'Could not add'}\n </p>\n )}\n </div>\n )}\n </div>\n );\n};\n"],"mappings":";;;;;;;AAuBA,IAAa,KAAqD,EAChE,WACA,gBACA,mBACI;CACJ,IAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"AddToLookbookButton.js","names":[],"sources":["../../../src/coolandlovely/components/AddToLookbookButton.tsx"],"sourcesContent":["/**\n * AddToLookbookButton — clip a look or style item into one of the caller's\n * lookbooks (cooAddLookbookEntry).\n * @module coolandlovely/components\n *\n * Exactly one of `lookId` / `styleItemId` should be provided. Opens a small\n * menu of the caller's lookbooks; picking one adds the entry. If the shopper\n * has no lookbooks yet, offers a jump to the lookbooks page.\n */\n\nimport type { FC } from 'react';\nimport { useEffect, useRef, useState } from 'react';\nimport { BookHeart, Check, ChevronDown, Loader2, Plus } from 'lucide-react';\nimport { useCoolAndLovely } from '../context/CoolAndLovelyContext';\nimport { useAddLookbookEntry, useLookbooks } from '../hooks/useCoolAndLovelyApi';\nimport { cn } from '../utils/cn';\n\ninterface AddToLookbookButtonProps {\n lookId?: string;\n styleItemId?: string;\n className?: string;\n}\n\nexport const AddToLookbookButton: FC<AddToLookbookButtonProps> = ({\n lookId,\n styleItemId,\n className,\n}) => {\n const { navigateTo, currentUser } = useCoolAndLovely();\n const lookbooks = useLookbooks(currentUser?.id);\n const addEntry = useAddLookbookEntry();\n const [open, setOpen] = useState(false);\n const [addedTo, setAddedTo] = useState<string | null>(null);\n const containerRef = useRef<HTMLDivElement | null>(null);\n\n useEffect(() => {\n if (!open) return;\n const onClick = (e: MouseEvent) => {\n if (containerRef.current && !containerRef.current.contains(e.target as Node)) {\n setOpen(false);\n }\n };\n document.addEventListener('mousedown', onClick);\n return () => document.removeEventListener('mousedown', onClick);\n }, [open]);\n\n const boards = (lookbooks.data ?? []).filter((lb) => lb.ownerId === currentUser?.id);\n\n const handleAdd = (lookbookId: string) => {\n addEntry.mutate(\n { lookbookId, lookId, styleItemId },\n {\n onSuccess: () => {\n setAddedTo(lookbookId);\n setTimeout(() => {\n setOpen(false);\n setAddedTo(null);\n }, 900);\n },\n }\n );\n };\n\n return (\n <div ref={containerRef} className={cn('relative', className)}>\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n aria-haspopup=\"menu\"\n aria-expanded={open}\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 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <BookHeart className=\"h-4 w-4\" /> Add to lookbook\n <ChevronDown className=\"h-3.5 w-3.5\" />\n </button>\n\n {open && (\n <div\n role=\"menu\"\n className=\"absolute right-0 z-20 mt-1 w-60 overflow-hidden rounded-lg border border-border-seam bg-bg-surface shadow-lg\"\n >\n {lookbooks.isLoading ? (\n <div className=\"flex items-center gap-2 px-3 py-3 text-sm text-text-secondary\">\n <Loader2 className=\"h-4 w-4 animate-spin\" /> Loading lookbooks…\n </div>\n ) : boards.length === 0 ? (\n <button\n type=\"button\"\n onClick={() => {\n setOpen(false);\n navigateTo('/lookbooks');\n }}\n className=\"flex w-full items-center gap-2 px-3 py-3 text-left text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Plus className=\"h-4 w-4\" /> Create your first lookbook\n </button>\n ) : (\n <ul className=\"max-h-64 overflow-auto py-1\">\n {boards.map((lb) => {\n const done = addedTo === lb.id;\n return (\n <li key={lb.id}>\n <button\n type=\"button\"\n role=\"menuitem\"\n disabled={addEntry.isPending}\n onClick={() => handleAdd(lb.id)}\n className=\"flex w-full items-center justify-between gap-2 px-3 py-2 text-left text-sm text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-50\"\n >\n <span className=\"truncate\">{lb.name}</span>\n {done ? (\n <Check className=\"h-4 w-4 shrink-0 text-status-success-text\" />\n ) : addEntry.isPending ? (\n <Loader2 className=\"h-4 w-4 shrink-0 animate-spin text-text-secondary\" />\n ) : null}\n </button>\n </li>\n );\n })}\n </ul>\n )}\n {addEntry.isError && (\n <p className=\"border-t border-border-seam px-3 py-2 text-xs text-status-error-text\">\n {addEntry.error instanceof Error ? addEntry.error.message : 'Could not add'}\n </p>\n )}\n </div>\n )}\n </div>\n );\n};\n"],"mappings":";;;;;;;AAuBA,IAAa,KAAqD,EAChE,WACA,gBACA,mBACI;CACJ,IAAM,EAAE,eAAY,mBAAgB,GAAkB,EAChD,IAAY,EAAa,GAAa,GAAG,EACzC,IAAW,GAAqB,EAChC,CAAC,GAAM,KAAW,EAAS,GAAM,EACjC,CAAC,GAAS,KAAc,EAAwB,KAAK,EACrD,IAAe,EAA8B,KAAK;AAExD,SAAgB;AACd,MAAI,CAAC,EAAM;EACX,IAAM,KAAW,MAAkB;AACjC,GAAI,EAAa,WAAW,CAAC,EAAa,QAAQ,SAAS,EAAE,OAAe,IAC1E,EAAQ,GAAM;;AAIlB,SADA,SAAS,iBAAiB,aAAa,EAAQ,QAClC,SAAS,oBAAoB,aAAa,EAAQ;IAC9D,CAAC,EAAK,CAAC;CAEV,IAAM,KAAU,EAAU,QAAQ,EAAE,EAAE,QAAQ,MAAO,EAAG,YAAY,GAAa,GAAG,EAE9E,KAAa,MAAuB;AACxC,IAAS,OACP;GAAE;GAAY;GAAQ;GAAa,EACnC,EACE,iBAAiB;AAEf,GADA,EAAW,EAAW,EACtB,iBAAiB;AAEf,IADA,EAAQ,GAAM,EACd,EAAW,KAAK;MACf,IAAI;KAEV,CACF;;AAGH,QACE,kBAAC,OAAD;EAAK,KAAK;EAAc,WAAW,EAAG,YAAY,EAAU;YAA5D,CACE,kBAAC,UAAD;GACE,MAAK;GACL,eAAe,GAAS,MAAM,CAAC,EAAE;GACjC,iBAAc;GACd,iBAAe;GACf,WAAU;aALZ;IAOE,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA;;IACjC,kBAAC,GAAD,EAAa,WAAU,eAAgB,CAAA;IAChC;MAER,KACC,kBAAC,OAAD;GACE,MAAK;GACL,WAAU;aAFZ,CAIG,EAAU,YACT,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,EAAA,sBACxC;QACJ,EAAO,WAAW,IACpB,kBAAC,UAAD;IACE,MAAK;IACL,eAAe;AAEb,KADA,EAAQ,GAAM,EACd,EAAW,aAAa;;IAE1B,WAAU;cANZ,CAQE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,8BACrB;QAET,kBAAC,MAAD;IAAI,WAAU;cACX,EAAO,KAAK,MAAO;KAClB,IAAM,IAAO,MAAY,EAAG;AAC5B,YACE,kBAAC,MAAD,EAAA,UACE,kBAAC,UAAD;MACE,MAAK;MACL,MAAK;MACL,UAAU,EAAS;MACnB,eAAe,EAAU,EAAG,GAAG;MAC/B,WAAU;gBALZ,CAOE,kBAAC,QAAD;OAAM,WAAU;iBAAY,EAAG;OAAY,CAAA,EAC1C,IACC,kBAAC,GAAD,EAAO,WAAU,6CAA8C,CAAA,GAC7D,EAAS,YACX,kBAAC,GAAD,EAAS,WAAU,qDAAsD,CAAA,GACvE,KACG;SACN,EAfI,EAAG,GAeP;MAEP;IACC,CAAA,EAEN,EAAS,WACR,kBAAC,KAAD;IAAG,WAAU;cACV,EAAS,iBAAiB,QAAQ,EAAS,MAAM,UAAU;IAC1D,CAAA,CAEF;KAEJ"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { cn as e } from "../utils/cn.js";
|
|
2
|
+
import { Thumbnail as t } from "./Thumbnail.js";
|
|
3
|
+
import { Store as n } from "lucide-react";
|
|
4
|
+
import { jsx as r, jsxs as i } from "react/jsx-runtime";
|
|
5
|
+
//#region src/coolandlovely/components/BrandCover.tsx
|
|
6
|
+
var a = ({ coverFileId: a, logoFileId: o, name: s, seed: c, className: l, showLogo: u = !1, overlay: d }) => {
|
|
7
|
+
let f = c ?? s;
|
|
8
|
+
return /* @__PURE__ */ i("div", {
|
|
9
|
+
className: e("relative", l),
|
|
10
|
+
children: [
|
|
11
|
+
/* @__PURE__ */ r(t, {
|
|
12
|
+
icon: /* @__PURE__ */ r(n, { className: "h-8 w-8" }),
|
|
13
|
+
seed: f,
|
|
14
|
+
fileId: a,
|
|
15
|
+
alt: s,
|
|
16
|
+
className: "aspect-[16/6] w-full",
|
|
17
|
+
overlay: /* @__PURE__ */ r("div", { className: "pointer-events-none absolute inset-x-0 bottom-0 h-2/3 bg-gradient-to-t from-bg-canvas/70 to-transparent" })
|
|
18
|
+
}),
|
|
19
|
+
d ? /* @__PURE__ */ r("div", {
|
|
20
|
+
className: "absolute inset-0",
|
|
21
|
+
children: d
|
|
22
|
+
}) : null,
|
|
23
|
+
u ? /* @__PURE__ */ r("div", {
|
|
24
|
+
className: "absolute bottom-0 left-4 translate-y-1/2",
|
|
25
|
+
children: /* @__PURE__ */ r(t, {
|
|
26
|
+
icon: /* @__PURE__ */ r(n, { className: "h-6 w-6" }),
|
|
27
|
+
seed: f,
|
|
28
|
+
fileId: o,
|
|
29
|
+
alt: `${s} logo`,
|
|
30
|
+
className: "h-16 w-16 rounded-full shadow-md ring-2 ring-bg-surface"
|
|
31
|
+
})
|
|
32
|
+
}) : null
|
|
33
|
+
]
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
//#endregion
|
|
37
|
+
export { a as BrandCover };
|
|
38
|
+
|
|
39
|
+
//# sourceMappingURL=BrandCover.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BrandCover.js","names":[],"sources":["../../../src/coolandlovely/components/BrandCover.tsx"],"sourcesContent":["/**\n * BrandCover — a brand cover banner with an optional overlapping round logo.\n * @module coolandlovely/components\n *\n * A photographic cover (`Thumbnail` on the cover File) with a bottom token scrim\n * so overlaid text/logos read against imagery; an optional circular logo avatar\n * straddles the bottom-left, and an optional `overlay` node (name / handle /\n * actions) renders over the scrim. Reused by the brands list card and the brand\n * detail hero. Token-only.\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { Store } from 'lucide-react';\nimport { Thumbnail } from './Thumbnail';\nimport { cn } from '../utils/cn';\n\ninterface BrandCoverProps {\n coverFileId?: string | null;\n logoFileId?: string | null;\n name: string;\n /** Stable seed for the tint (usually the brand id). Falls back to `name`. */\n seed?: string;\n className?: string;\n /** When true, render the circular logo avatar straddling the bottom-left. */\n showLogo?: boolean;\n /** Content rendered over the scrim (e.g. name + @handle + actions). */\n overlay?: ReactNode;\n}\n\nexport const BrandCover: FC<BrandCoverProps> = ({\n coverFileId,\n logoFileId,\n name,\n seed,\n className,\n showLogo = false,\n overlay,\n}) => {\n const tintSeed = seed ?? name;\n return (\n <div className={cn('relative', className)}>\n <Thumbnail\n icon={<Store className=\"h-8 w-8\" />}\n seed={tintSeed}\n fileId={coverFileId}\n alt={name}\n className=\"aspect-[16/6] w-full\"\n overlay={\n <div className=\"pointer-events-none absolute inset-x-0 bottom-0 h-2/3 bg-gradient-to-t from-bg-canvas/70 to-transparent\" />\n }\n />\n {overlay ? <div className=\"absolute inset-0\">{overlay}</div> : null}\n {showLogo ? (\n <div className=\"absolute bottom-0 left-4 translate-y-1/2\">\n <Thumbnail\n icon={<Store className=\"h-6 w-6\" />}\n seed={tintSeed}\n fileId={logoFileId}\n alt={`${name} logo`}\n className=\"h-16 w-16 rounded-full shadow-md ring-2 ring-bg-surface\"\n />\n </div>\n ) : null}\n </div>\n );\n};\n"],"mappings":";;;;;AA6BA,IAAa,KAAmC,EAC9C,gBACA,eACA,SACA,SACA,cACA,cAAW,IACX,iBACI;CACJ,IAAM,IAAW,KAAQ;AACzB,QACE,kBAAC,OAAD;EAAK,WAAW,EAAG,YAAY,EAAU;YAAzC;GACE,kBAAC,GAAD;IACE,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;IACnC,MAAM;IACN,QAAQ;IACR,KAAK;IACL,WAAU;IACV,SACE,kBAAC,OAAD,EAAK,WAAU,2GAA4G,CAAA;IAE7H,CAAA;GACD,IAAU,kBAAC,OAAD;IAAK,WAAU;cAAoB;IAAc,CAAA,GAAG;GAC9D,IACC,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KACE,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;KACnC,MAAM;KACN,QAAQ;KACR,KAAK,GAAG,EAAK;KACb,WAAU;KACV,CAAA;IACE,CAAA,GACJ;GACA"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { useCoolAndLovely as e } from "../context/CoolAndLovelyContext.js";
|
|
2
|
+
import { cn as t } from "../utils/cn.js";
|
|
3
|
+
import { Thumbnail as n } from "./Thumbnail.js";
|
|
4
|
+
import { useMemo as r, useState as i } from "react";
|
|
5
|
+
import { Store as a } from "lucide-react";
|
|
6
|
+
import { jsx as o, jsxs as s } from "react/jsx-runtime";
|
|
7
|
+
import { useActorProfiles as c } from "@burdenoff/fe-libs/shared";
|
|
8
|
+
//#region src/coolandlovely/components/BrandCreatorAvatars.tsx
|
|
9
|
+
function l(e) {
|
|
10
|
+
let t = e.trim().split(/\s+/).filter(Boolean);
|
|
11
|
+
return t.length === 0 ? "?" : t.length === 1 ? t[0].slice(0, 2).toUpperCase() : (t[0][0] + t[t.length - 1][0]).toUpperCase();
|
|
12
|
+
}
|
|
13
|
+
var u = ({ brandLogoFileId: u, brandSeed: d, creatorId: f, size: p = "md", className: m }) => {
|
|
14
|
+
let { apiGatewayUrl: h, authToken: g, workspaceId: _ } = e(), [v, y] = i(!1), b = c(r(() => [{
|
|
15
|
+
actorId: f,
|
|
16
|
+
actorType: "user"
|
|
17
|
+
}], [f]), {
|
|
18
|
+
apiGatewayUrl: h,
|
|
19
|
+
authToken: g,
|
|
20
|
+
workspaceId: _
|
|
21
|
+
})[f], x = b?.displayName ?? "Creator", S = b?.avatarUrl, C = p === "sm" ? "h-7 w-7" : "h-10 w-10", w = p === "sm" ? "h-3.5 w-3.5" : "h-5 w-5", T = p === "sm" ? "text-[10px]" : "text-xs";
|
|
22
|
+
return /* @__PURE__ */ s("div", {
|
|
23
|
+
className: t("flex items-center gap-1.5", m),
|
|
24
|
+
children: [
|
|
25
|
+
/* @__PURE__ */ o(n, {
|
|
26
|
+
icon: /* @__PURE__ */ o(a, { className: w }),
|
|
27
|
+
seed: d,
|
|
28
|
+
fileId: u,
|
|
29
|
+
alt: "Brand logo",
|
|
30
|
+
className: t(C, "rounded-full ring-2 ring-bg-surface")
|
|
31
|
+
}),
|
|
32
|
+
/* @__PURE__ */ o("span", {
|
|
33
|
+
className: "text-xs font-medium text-text-secondary",
|
|
34
|
+
"aria-hidden": "true",
|
|
35
|
+
children: "×"
|
|
36
|
+
}),
|
|
37
|
+
/* @__PURE__ */ o("span", {
|
|
38
|
+
title: x,
|
|
39
|
+
className: t("relative flex shrink-0 items-center justify-center overflow-hidden rounded-full bg-bg-sunken ring-2 ring-bg-surface", C),
|
|
40
|
+
children: S && !v ? /* @__PURE__ */ o("img", {
|
|
41
|
+
src: S,
|
|
42
|
+
alt: x,
|
|
43
|
+
loading: "lazy",
|
|
44
|
+
onError: () => y(!0),
|
|
45
|
+
className: "h-full w-full object-cover"
|
|
46
|
+
}) : /* @__PURE__ */ o("span", {
|
|
47
|
+
className: t("font-semibold text-text-secondary", T),
|
|
48
|
+
children: l(x)
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
]
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
//#endregion
|
|
55
|
+
export { u as BrandCreatorAvatars };
|
|
56
|
+
|
|
57
|
+
//# sourceMappingURL=BrandCreatorAvatars.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BrandCreatorAvatars.js","names":[],"sources":["../../../src/coolandlovely/components/BrandCreatorAvatars.tsx"],"sourcesContent":["/**\n * BrandCreatorAvatars — a brand-logo × creator-avatar pair.\n * @module coolandlovely/components\n *\n * The signature of a collaboration: the brand's round logo joined by a small \"×\"\n * to the creator's avatar. The creator's name + avatar are resolved via fe-libs\n * `useActorProfiles` on the GLOBAL gateway (NEVER select firstName on a federated\n * wspace User). Brand imagery reuses `Thumbnail` (round). Used by the\n * collaborations list and the collaboration detail header.\n */\n\nimport type { FC } from 'react';\nimport { useMemo, useState } from 'react';\nimport { Store } from 'lucide-react';\nimport { useActorProfiles, type ActorInput } from '@burdenoff/fe-libs/shared';\nimport { Thumbnail } from './Thumbnail';\nimport { useCoolAndLovely } from '../context/CoolAndLovelyContext';\nimport { cn } from '../utils/cn';\n\ninterface BrandCreatorAvatarsProps {\n brandLogoFileId?: string | null;\n /** Stable seed for the brand logo tint (usually the brand id). */\n brandSeed?: string;\n /** Actor id of the creator (resolved to name + avatar via useActorProfiles). */\n creatorId: string;\n size?: 'sm' | 'md';\n className?: string;\n}\n\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\nexport const BrandCreatorAvatars: FC<BrandCreatorAvatarsProps> = ({\n brandLogoFileId,\n brandSeed,\n creatorId,\n size = 'md',\n className,\n}) => {\n const { apiGatewayUrl, authToken, workspaceId } = useCoolAndLovely();\n const [avatarFailed, setAvatarFailed] = useState(false);\n\n const actors = useMemo<ActorInput[]>(\n () => [{ actorId: creatorId, actorType: 'user' }],\n [creatorId]\n );\n const profiles = useActorProfiles(actors, { apiGatewayUrl, authToken, workspaceId });\n const creator = profiles[creatorId];\n const creatorName = creator?.displayName ?? 'Creator';\n const avatarUrl = creator?.avatarUrl;\n\n const dim = size === 'sm' ? 'h-7 w-7' : 'h-10 w-10';\n const iconSize = size === 'sm' ? 'h-3.5 w-3.5' : 'h-5 w-5';\n const initialsSize = size === 'sm' ? 'text-[10px]' : 'text-xs';\n\n return (\n <div className={cn('flex items-center gap-1.5', className)}>\n <Thumbnail\n icon={<Store className={iconSize} />}\n seed={brandSeed}\n fileId={brandLogoFileId}\n alt=\"Brand logo\"\n className={cn(dim, 'rounded-full ring-2 ring-bg-surface')}\n />\n <span className=\"text-xs font-medium text-text-secondary\" aria-hidden=\"true\">\n ×\n </span>\n <span\n title={creatorName}\n className={cn(\n 'relative flex shrink-0 items-center justify-center overflow-hidden rounded-full bg-bg-sunken ring-2 ring-bg-surface',\n dim\n )}\n >\n {avatarUrl && !avatarFailed ? (\n <img\n src={avatarUrl}\n alt={creatorName}\n loading=\"lazy\"\n onError={() => setAvatarFailed(true)}\n className=\"h-full w-full object-cover\"\n />\n ) : (\n <span className={cn('font-semibold text-text-secondary', initialsSize)}>\n {initials(creatorName)}\n </span>\n )}\n </span>\n </div>\n );\n};\n"],"mappings":";;;;;;;;AA6BA,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;;AAGjE,IAAa,KAAqD,EAChE,oBACA,cACA,cACA,UAAO,MACP,mBACI;CACJ,IAAM,EAAE,kBAAe,cAAW,mBAAgB,GAAkB,EAC9D,CAAC,GAAc,KAAmB,EAAS,GAAM,EAOjD,IADW,EAJF,QACP,CAAC;EAAE,SAAS;EAAW,WAAW;EAAQ,CAAC,EACjD,CAAC,EAAU,CACZ,EACyC;EAAE;EAAe;EAAW;EAAa,CAAC,CAC3D,IACnB,IAAc,GAAS,eAAe,WACtC,IAAY,GAAS,WAErB,IAAM,MAAS,OAAO,YAAY,aAClC,IAAW,MAAS,OAAO,gBAAgB,WAC3C,IAAe,MAAS,OAAO,gBAAgB;AAErD,QACE,kBAAC,OAAD;EAAK,WAAW,EAAG,6BAA6B,EAAU;YAA1D;GACE,kBAAC,GAAD;IACE,MAAM,kBAAC,GAAD,EAAO,WAAW,GAAY,CAAA;IACpC,MAAM;IACN,QAAQ;IACR,KAAI;IACJ,WAAW,EAAG,GAAK,sCAAsC;IACzD,CAAA;GACF,kBAAC,QAAD;IAAM,WAAU;IAA0C,eAAY;cAAO;IAEtE,CAAA;GACP,kBAAC,QAAD;IACE,OAAO;IACP,WAAW,EACT,uHACA,EACD;cAEA,KAAa,CAAC,IACb,kBAAC,OAAD;KACE,KAAK;KACL,KAAK;KACL,SAAQ;KACR,eAAe,EAAgB,GAAK;KACpC,WAAU;KACV,CAAA,GAEF,kBAAC,QAAD;KAAM,WAAW,EAAG,qCAAqC,EAAa;eACnE,EAAS,EAAY;KACjB,CAAA;IAEJ,CAAA;GACH"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { cn as e } from "../utils/cn.js";
|
|
2
|
+
import { jsx as t, jsxs as n } from "react/jsx-runtime";
|
|
3
|
+
//#region src/coolandlovely/components/ColourSwatchRow.tsx
|
|
4
|
+
var r = {
|
|
5
|
+
black: "#111111",
|
|
6
|
+
white: "#ffffff",
|
|
7
|
+
ivory: "#fffff0",
|
|
8
|
+
cream: "#f5f1e3",
|
|
9
|
+
ecru: "#c2b280",
|
|
10
|
+
beige: "#f0e6d2",
|
|
11
|
+
sand: "#d8c3a5",
|
|
12
|
+
stone: "#a8a29e",
|
|
13
|
+
taupe: "#b38b6d",
|
|
14
|
+
grey: "#9ca3af",
|
|
15
|
+
gray: "#9ca3af",
|
|
16
|
+
charcoal: "#36454f",
|
|
17
|
+
silver: "#c0c0c0",
|
|
18
|
+
navy: "#1f2a44",
|
|
19
|
+
blue: "#2563eb",
|
|
20
|
+
cobalt: "#0047ab",
|
|
21
|
+
denim: "#1560bd",
|
|
22
|
+
indigo: "#4f46e5",
|
|
23
|
+
teal: "#0d9488",
|
|
24
|
+
turquoise: "#40e0d0",
|
|
25
|
+
green: "#16a34a",
|
|
26
|
+
olive: "#808000",
|
|
27
|
+
sage: "#9caf88",
|
|
28
|
+
mint: "#a7e8bd",
|
|
29
|
+
emerald: "#10b981",
|
|
30
|
+
khaki: "#c3b091",
|
|
31
|
+
mustard: "#e1ad01",
|
|
32
|
+
yellow: "#eab308",
|
|
33
|
+
gold: "#d4af37",
|
|
34
|
+
orange: "#ea580c",
|
|
35
|
+
rust: "#b7410e",
|
|
36
|
+
terracotta: "#e2725b",
|
|
37
|
+
coral: "#ff7f50",
|
|
38
|
+
peach: "#ffcba4",
|
|
39
|
+
camel: "#c19a6b",
|
|
40
|
+
tan: "#d2b48c",
|
|
41
|
+
brown: "#7c4a2d",
|
|
42
|
+
chocolate: "#5c3a21",
|
|
43
|
+
wine: "#722f37",
|
|
44
|
+
burgundy: "#800020",
|
|
45
|
+
maroon: "#800000",
|
|
46
|
+
red: "#dc2626",
|
|
47
|
+
pink: "#f9a8d4",
|
|
48
|
+
blush: "#e5a4a4",
|
|
49
|
+
rose: "#f43f5e",
|
|
50
|
+
lavender: "#b57edc",
|
|
51
|
+
lilac: "#c8a2c8",
|
|
52
|
+
purple: "#9333ea",
|
|
53
|
+
plum: "#8e4585"
|
|
54
|
+
}, i = Object.keys(r).sort((e, t) => t.length - e.length);
|
|
55
|
+
function a(e) {
|
|
56
|
+
let t = e.trim().toLowerCase();
|
|
57
|
+
if (!t) return null;
|
|
58
|
+
let n = r[t];
|
|
59
|
+
if (n) return n;
|
|
60
|
+
let a = i.find((e) => t.includes(e));
|
|
61
|
+
return a ? r[a] : null;
|
|
62
|
+
}
|
|
63
|
+
var o = ({ colours: r, max: i = 6, className: o }) => {
|
|
64
|
+
let s = r.filter((e) => e && e.trim().length > 0);
|
|
65
|
+
if (s.length === 0) return null;
|
|
66
|
+
let c = s.slice(0, i), l = s.length - c.length;
|
|
67
|
+
return /* @__PURE__ */ n("div", {
|
|
68
|
+
className: e("flex flex-wrap items-center gap-1.5", o),
|
|
69
|
+
children: [c.map((n, r) => {
|
|
70
|
+
let i = a(n);
|
|
71
|
+
return /* @__PURE__ */ t("span", {
|
|
72
|
+
title: n,
|
|
73
|
+
"aria-label": n,
|
|
74
|
+
style: i ? { backgroundColor: i } : void 0,
|
|
75
|
+
className: e("inline-block h-4 w-4 rounded-full ring-1 ring-inset ring-border-seam", i ? void 0 : "bg-bg-sunken")
|
|
76
|
+
}, `${n}-${r}`);
|
|
77
|
+
}), l > 0 ? /* @__PURE__ */ n("span", {
|
|
78
|
+
className: "text-xs font-medium text-text-secondary tabular-nums",
|
|
79
|
+
children: ["+", l]
|
|
80
|
+
}) : null]
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
//#endregion
|
|
84
|
+
export { o as ColourSwatchRow };
|
|
85
|
+
|
|
86
|
+
//# sourceMappingURL=ColourSwatchRow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ColourSwatchRow.js","names":[],"sources":["../../../src/coolandlovely/components/ColourSwatchRow.tsx"],"sourcesContent":["/**\n * ColourSwatchRow — render a set of colour names as small swatch dots.\n * @module coolandlovely/components\n *\n * CoolAndLovely style items / wardrobe / palettes carry free-text colour names\n * (e.g. \"navy\", \"rust\", \"camel\"). We map the common ones to a representative\n * CSS colour so the catalogue reads like a real fashion product rather than a\n * row of grey chips. The swatch fill is CONTENT data (the colour the garment\n * actually is), not a theme decision, so an inline `backgroundColor` is used for\n * mapped names; unknown names fall back to a neutral token-styled dot that keeps\n * its label in a tooltip. Everything structural/text uses semantic tokens.\n */\n\nimport type { CSSProperties, FC } from 'react';\nimport { cn } from '../utils/cn';\n\ninterface ColourSwatchRowProps {\n colours: string[];\n /** Max swatches to show before collapsing into a \"+N\" chip. */\n max?: number;\n className?: string;\n}\n\n/**\n * Common fashion colour names → a representative CSS colour. Keys are matched\n * case-insensitively; multi-word names (e.g. \"dusty rose\", \"forest green\") fall\n * back to a keyword scan so \"… green\" still resolves to green.\n */\nconst COLOUR_MAP: Record<string, string> = {\n black: '#111111',\n white: '#ffffff',\n ivory: '#fffff0',\n cream: '#f5f1e3',\n ecru: '#c2b280',\n beige: '#f0e6d2',\n sand: '#d8c3a5',\n stone: '#a8a29e',\n taupe: '#b38b6d',\n grey: '#9ca3af',\n gray: '#9ca3af',\n charcoal: '#36454f',\n silver: '#c0c0c0',\n navy: '#1f2a44',\n blue: '#2563eb',\n cobalt: '#0047ab',\n denim: '#1560bd',\n indigo: '#4f46e5',\n teal: '#0d9488',\n turquoise: '#40e0d0',\n green: '#16a34a',\n olive: '#808000',\n sage: '#9caf88',\n mint: '#a7e8bd',\n emerald: '#10b981',\n khaki: '#c3b091',\n mustard: '#e1ad01',\n yellow: '#eab308',\n gold: '#d4af37',\n orange: '#ea580c',\n rust: '#b7410e',\n terracotta: '#e2725b',\n coral: '#ff7f50',\n peach: '#ffcba4',\n camel: '#c19a6b',\n tan: '#d2b48c',\n brown: '#7c4a2d',\n chocolate: '#5c3a21',\n wine: '#722f37',\n burgundy: '#800020',\n maroon: '#800000',\n red: '#dc2626',\n pink: '#f9a8d4',\n blush: '#e5a4a4',\n rose: '#f43f5e',\n lavender: '#b57edc',\n lilac: '#c8a2c8',\n purple: '#9333ea',\n plum: '#8e4585',\n};\n\n/** Ordered keyword list for the \"contains\" fallback (longer keys first). */\nconst COLOUR_KEYWORDS = Object.keys(COLOUR_MAP).sort((a, b) => b.length - a.length);\n\nfunction resolveColour(name: string): string | null {\n const normalized = name.trim().toLowerCase();\n if (!normalized) return null;\n const direct = COLOUR_MAP[normalized];\n if (direct) return direct;\n const keyword = COLOUR_KEYWORDS.find((key) => normalized.includes(key));\n return keyword ? COLOUR_MAP[keyword] : null;\n}\n\nexport const ColourSwatchRow: FC<ColourSwatchRowProps> = ({ colours, max = 6, className }) => {\n const cleaned = colours.filter((c) => c && c.trim().length > 0);\n if (cleaned.length === 0) return null;\n\n const shown = cleaned.slice(0, max);\n const overflow = cleaned.length - shown.length;\n\n return (\n <div className={cn('flex flex-wrap items-center gap-1.5', className)}>\n {shown.map((colour, index) => {\n const css = resolveColour(colour);\n const style: CSSProperties | undefined = css ? { backgroundColor: css } : undefined;\n return (\n <span\n key={`${colour}-${index}`}\n title={colour}\n aria-label={colour}\n style={style}\n className={cn(\n 'inline-block h-4 w-4 rounded-full ring-1 ring-inset ring-border-seam',\n css ? undefined : 'bg-bg-sunken'\n )}\n />\n );\n })}\n {overflow > 0 ? (\n <span className=\"text-xs font-medium text-text-secondary tabular-nums\">+{overflow}</span>\n ) : null}\n </div>\n );\n};\n"],"mappings":";;;AA4BA,IAAM,IAAqC;CACzC,OAAO;CACP,OAAO;CACP,OAAO;CACP,OAAO;CACP,MAAM;CACN,OAAO;CACP,MAAM;CACN,OAAO;CACP,OAAO;CACP,MAAM;CACN,MAAM;CACN,UAAU;CACV,QAAQ;CACR,MAAM;CACN,MAAM;CACN,QAAQ;CACR,OAAO;CACP,QAAQ;CACR,MAAM;CACN,WAAW;CACX,OAAO;CACP,OAAO;CACP,MAAM;CACN,MAAM;CACN,SAAS;CACT,OAAO;CACP,SAAS;CACT,QAAQ;CACR,MAAM;CACN,QAAQ;CACR,MAAM;CACN,YAAY;CACZ,OAAO;CACP,OAAO;CACP,OAAO;CACP,KAAK;CACL,OAAO;CACP,WAAW;CACX,MAAM;CACN,UAAU;CACV,QAAQ;CACR,KAAK;CACL,MAAM;CACN,OAAO;CACP,MAAM;CACN,UAAU;CACV,OAAO;CACP,QAAQ;CACR,MAAM;CACP,EAGK,IAAkB,OAAO,KAAK,EAAW,CAAC,MAAM,GAAG,MAAM,EAAE,SAAS,EAAE,OAAO;AAEnF,SAAS,EAAc,GAA6B;CAClD,IAAM,IAAa,EAAK,MAAM,CAAC,aAAa;AAC5C,KAAI,CAAC,EAAY,QAAO;CACxB,IAAM,IAAS,EAAW;AAC1B,KAAI,EAAQ,QAAO;CACnB,IAAM,IAAU,EAAgB,MAAM,MAAQ,EAAW,SAAS,EAAI,CAAC;AACvE,QAAO,IAAU,EAAW,KAAW;;AAGzC,IAAa,KAA6C,EAAE,YAAS,SAAM,GAAG,mBAAgB;CAC5F,IAAM,IAAU,EAAQ,QAAQ,MAAM,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE;AAC/D,KAAI,EAAQ,WAAW,EAAG,QAAO;CAEjC,IAAM,IAAQ,EAAQ,MAAM,GAAG,EAAI,EAC7B,IAAW,EAAQ,SAAS,EAAM;AAExC,QACE,kBAAC,OAAD;EAAK,WAAW,EAAG,uCAAuC,EAAU;YAApE,CACG,EAAM,KAAK,GAAQ,MAAU;GAC5B,IAAM,IAAM,EAAc,EAAO;AAEjC,UACE,kBAAC,QAAD;IAEE,OAAO;IACP,cAAY;IACL,OAN8B,IAAM,EAAE,iBAAiB,GAAK,GAAG,KAAA;IAOtE,WAAW,EACT,wEACA,IAAM,KAAA,IAAY,eACnB;IACD,EARK,GAAG,EAAO,GAAG,IAQlB;IAEJ,EACD,IAAW,IACV,kBAAC,QAAD;GAAM,WAAU;aAAhB,CAAuE,KAAE,EAAgB;OACvF,KACA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cn as e } from "../utils/cn.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { formatRelativeTime as t } from "../utils/formatters.js";
|
|
3
|
+
import { useAddComment as n, useEntityComments as r } from "../hooks/useComments.js";
|
|
4
4
|
import { ActorName as i } from "./ActorName.js";
|
|
5
5
|
import { useState as a } from "react";
|
|
6
6
|
import { Loader2 as o, MessageSquare as s, Send as c } from "lucide-react";
|
|
@@ -8,7 +8,7 @@ import { jsx as l, jsxs as u } from "react/jsx-runtime";
|
|
|
8
8
|
import { GlassCard as d } from "@burdenoff/fe-libs/ui";
|
|
9
9
|
//#region src/coolandlovely/components/Comments.tsx
|
|
10
10
|
var f = ({ entityType: f, entityId: p, title: m = "Reviews & feedback", className: h }) => {
|
|
11
|
-
let g =
|
|
11
|
+
let g = r(f, p), _ = n(), [v, y] = a(""), b = g.data?.comments ?? [], x = g.data?.total ?? b.length;
|
|
12
12
|
return /* @__PURE__ */ u("section", {
|
|
13
13
|
className: e("mt-6", h),
|
|
14
14
|
children: [
|
|
@@ -91,7 +91,7 @@ var f = ({ entityType: f, entityId: p, title: m = "Reviews & feedback", classNam
|
|
|
91
91
|
className: "truncate text-sm font-medium text-text-primary"
|
|
92
92
|
}), /* @__PURE__ */ u("span", {
|
|
93
93
|
className: "shrink-0 text-xs text-text-secondary",
|
|
94
|
-
children: [
|
|
94
|
+
children: [t(e.createdAt), e.edited ? " · edited" : ""]
|
|
95
95
|
})]
|
|
96
96
|
}), /* @__PURE__ */ l("p", {
|
|
97
97
|
className: "mt-1 whitespace-pre-wrap break-words text-sm text-text-secondary",
|