@burdenoff/microfe-coolandlovely 2026.706.1 → 2026.706.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/coolandlovely/CoolAndLovelyRoutes.js +14 -6
- package/dist/coolandlovely/CoolAndLovelyRoutes.js.map +1 -1
- package/dist/coolandlovely/components/ActorName.js +29 -0
- package/dist/coolandlovely/components/ActorName.js.map +1 -0
- package/dist/coolandlovely/components/AddToLookbookButton.js +81 -0
- package/dist/coolandlovely/components/AddToLookbookButton.js.map +1 -0
- package/dist/coolandlovely/components/Comments.js +109 -0
- package/dist/coolandlovely/components/Comments.js.map +1 -0
- package/dist/coolandlovely/components/Field.js +22 -2
- package/dist/coolandlovely/components/Field.js.map +1 -1
- package/dist/coolandlovely/components/FileImage.js +17 -0
- package/dist/coolandlovely/components/FileImage.js.map +1 -0
- package/dist/coolandlovely/components/FollowButton.js +24 -0
- package/dist/coolandlovely/components/FollowButton.js.map +1 -0
- package/dist/coolandlovely/components/ImageUploadField.js +71 -0
- package/dist/coolandlovely/components/ImageUploadField.js.map +1 -0
- package/dist/coolandlovely/components/Thumbnail.js +20 -13
- package/dist/coolandlovely/components/Thumbnail.js.map +1 -1
- package/dist/coolandlovely/hooks/index.js +2 -0
- package/dist/coolandlovely/hooks/useComments.js +68 -0
- package/dist/coolandlovely/hooks/useComments.js.map +1 -0
- package/dist/coolandlovely/hooks/useCoolAndLovelyApi.js +74 -4
- package/dist/coolandlovely/hooks/useCoolAndLovelyApi.js.map +1 -1
- package/dist/coolandlovely/hooks/useFiles.js +94 -0
- package/dist/coolandlovely/hooks/useFiles.js.map +1 -0
- package/dist/coolandlovely/pages/HomePage.js +105 -87
- package/dist/coolandlovely/pages/HomePage.js.map +1 -1
- package/dist/coolandlovely/pages/brands/BrandDetailPage.js +144 -122
- package/dist/coolandlovely/pages/brands/BrandDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/brands/BrandFormPage.js +128 -90
- package/dist/coolandlovely/pages/brands/BrandFormPage.js.map +1 -1
- package/dist/coolandlovely/pages/brands/BrandsListPage.js +2 -0
- package/dist/coolandlovely/pages/brands/BrandsListPage.js.map +1 -1
- package/dist/coolandlovely/pages/collections/CollectionDetailPage.js +5 -1
- package/dist/coolandlovely/pages/collections/CollectionDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/collections/CollectionFormPage.js +117 -102
- package/dist/coolandlovely/pages/collections/CollectionFormPage.js.map +1 -1
- package/dist/coolandlovely/pages/collections/CollectionsListPage.js +2 -0
- package/dist/coolandlovely/pages/collections/CollectionsListPage.js.map +1 -1
- package/dist/coolandlovely/pages/discover/DiscoverPage.js +223 -0
- package/dist/coolandlovely/pages/discover/DiscoverPage.js.map +1 -0
- package/dist/coolandlovely/pages/items/StyleItemDetailPage.js +99 -95
- package/dist/coolandlovely/pages/items/StyleItemDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/items/StyleItemFormPage.js +144 -130
- package/dist/coolandlovely/pages/items/StyleItemFormPage.js.map +1 -1
- package/dist/coolandlovely/pages/items/StyleItemsListPage.js +2 -0
- package/dist/coolandlovely/pages/items/StyleItemsListPage.js.map +1 -1
- package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js +190 -99
- package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/lookbooks/LookbooksListPage.js +81 -68
- package/dist/coolandlovely/pages/lookbooks/LookbooksListPage.js.map +1 -1
- package/dist/coolandlovely/pages/looks/LookDetailPage.js +195 -155
- package/dist/coolandlovely/pages/looks/LookDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/looks/LookFormPage.js +127 -88
- package/dist/coolandlovely/pages/looks/LookFormPage.js.map +1 -1
- package/dist/coolandlovely/pages/looks/LooksListPage.js +2 -0
- package/dist/coolandlovely/pages/looks/LooksListPage.js.map +1 -1
- package/dist/coolandlovely/pages/profile/StyleProfilePage.js +169 -101
- package/dist/coolandlovely/pages/profile/StyleProfilePage.js.map +1 -1
- package/dist/coolandlovely/pages/trends/TrendsPage.js +249 -0
- package/dist/coolandlovely/pages/trends/TrendsPage.js.map +1 -0
- package/dist/coolandlovely/pages/wardrobe/WardrobeFormPage.js +88 -76
- package/dist/coolandlovely/pages/wardrobe/WardrobeFormPage.js.map +1 -1
- package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.js +2 -0
- package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -4,15 +4,15 @@ import { Suspense as n, lazy as r } from "react";
|
|
|
4
4
|
import { Navigate as i, Route as a, Routes as o, useLocation as s } from "react-router-dom";
|
|
5
5
|
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
6
6
|
//#region src/coolandlovely/CoolAndLovelyRoutes.tsx
|
|
7
|
-
var u = r(() => import("./pages/brands/BrandsListPage.js").then((e) => ({ default: e.BrandsListPage }))), d = r(() => import("./pages/brands/BrandDetailPage.js").then((e) => ({ default: e.BrandDetailPage }))), f = r(() => import("./pages/brands/BrandFormPage.js").then((e) => ({ default: e.BrandFormPage }))), p = r(() => import("./pages/collections/CollectionsListPage.js").then((e) => ({ default: e.CollectionsListPage }))), m = r(() => import("./pages/collections/CollectionDetailPage.js").then((e) => ({ default: e.CollectionDetailPage }))), h = r(() => import("./pages/collections/CollectionFormPage.js").then((e) => ({ default: e.CollectionFormPage }))), g = r(() => import("./pages/items/StyleItemsListPage.js").then((e) => ({ default: e.StyleItemsListPage }))), _ = r(() => import("./pages/items/StyleItemDetailPage.js").then((e) => ({ default: e.StyleItemDetailPage }))), v = r(() => import("./pages/items/StyleItemFormPage.js").then((e) => ({ default: e.StyleItemFormPage }))), y = r(() => import("./pages/looks/LooksListPage.js").then((e) => ({ default: e.LooksListPage }))), b = r(() => import("./pages/looks/LookDetailPage.js").then((e) => ({ default: e.LookDetailPage }))), x = r(() => import("./pages/looks/LookFormPage.js").then((e) => ({ default: e.LookFormPage }))), S = r(() => import("./pages/collaborations/CollaborationsListPage.js").then((e) => ({ default: e.CollaborationsListPage }))), C = r(() => import("./pages/collaborations/CollaborationDetailPage.js").then((e) => ({ default: e.CollaborationDetailPage }))), w = r(() => import("./pages/collaborations/CollaborationFormPage.js").then((e) => ({ default: e.CollaborationFormPage }))), T = r(() => import("./pages/wardrobe/WardrobeListPage.js").then((e) => ({ default: e.WardrobeListPage }))), E = r(() => import("./pages/wardrobe/WardrobeFormPage.js").then((e) => ({ default: e.WardrobeFormPage }))), D = r(() => import("./pages/lookbooks/LookbooksListPage.js").then((e) => ({ default: e.LookbooksListPage }))), O = r(() => import("./pages/lookbooks/LookbookDetailPage.js").then((e) => ({ default: e.LookbookDetailPage }))), k = r(() => import("./pages/profile/StyleProfilePage.js").then((e) => ({ default: e.StyleProfilePage }))), A = () => /* @__PURE__ */ c("div", {
|
|
7
|
+
var u = r(() => import("./pages/brands/BrandsListPage.js").then((e) => ({ default: e.BrandsListPage }))), d = r(() => import("./pages/brands/BrandDetailPage.js").then((e) => ({ default: e.BrandDetailPage }))), f = r(() => import("./pages/brands/BrandFormPage.js").then((e) => ({ default: e.BrandFormPage }))), p = r(() => import("./pages/collections/CollectionsListPage.js").then((e) => ({ default: e.CollectionsListPage }))), m = r(() => import("./pages/collections/CollectionDetailPage.js").then((e) => ({ default: e.CollectionDetailPage }))), h = r(() => import("./pages/collections/CollectionFormPage.js").then((e) => ({ default: e.CollectionFormPage }))), g = r(() => import("./pages/items/StyleItemsListPage.js").then((e) => ({ default: e.StyleItemsListPage }))), _ = r(() => import("./pages/items/StyleItemDetailPage.js").then((e) => ({ default: e.StyleItemDetailPage }))), v = r(() => import("./pages/items/StyleItemFormPage.js").then((e) => ({ default: e.StyleItemFormPage }))), y = r(() => import("./pages/looks/LooksListPage.js").then((e) => ({ default: e.LooksListPage }))), b = r(() => import("./pages/looks/LookDetailPage.js").then((e) => ({ default: e.LookDetailPage }))), x = r(() => import("./pages/looks/LookFormPage.js").then((e) => ({ default: e.LookFormPage }))), S = r(() => import("./pages/collaborations/CollaborationsListPage.js").then((e) => ({ default: e.CollaborationsListPage }))), C = r(() => import("./pages/collaborations/CollaborationDetailPage.js").then((e) => ({ default: e.CollaborationDetailPage }))), w = r(() => import("./pages/collaborations/CollaborationFormPage.js").then((e) => ({ default: e.CollaborationFormPage }))), T = r(() => import("./pages/wardrobe/WardrobeListPage.js").then((e) => ({ default: e.WardrobeListPage }))), E = r(() => import("./pages/wardrobe/WardrobeFormPage.js").then((e) => ({ default: e.WardrobeFormPage }))), D = r(() => import("./pages/lookbooks/LookbooksListPage.js").then((e) => ({ default: e.LookbooksListPage }))), O = r(() => import("./pages/lookbooks/LookbookDetailPage.js").then((e) => ({ default: e.LookbookDetailPage }))), k = r(() => import("./pages/profile/StyleProfilePage.js").then((e) => ({ default: e.StyleProfilePage }))), A = r(() => import("./pages/discover/DiscoverPage.js").then((e) => ({ default: e.DiscoverPage }))), j = r(() => import("./pages/trends/TrendsPage.js").then((e) => ({ default: e.TrendsPage }))), M = () => /* @__PURE__ */ c("div", {
|
|
8
8
|
className: "flex min-h-[400px] items-center justify-center",
|
|
9
9
|
children: /* @__PURE__ */ c("div", { className: "h-8 w-8 animate-spin rounded-full border-b-2 border-primary" })
|
|
10
|
-
}),
|
|
11
|
-
let { basePath: r, isStandalone:
|
|
12
|
-
return
|
|
10
|
+
}), N = () => {
|
|
11
|
+
let { basePath: r, isStandalone: N } = e(), P = s(), F = r === "/" ? "" : (r ?? "").replace(/\/$/, "");
|
|
12
|
+
return F && !N && !(P.pathname === F || P.pathname.startsWith(F + "/")) ? null : /* @__PURE__ */ c("div", {
|
|
13
13
|
className: "flex min-h-full flex-col",
|
|
14
14
|
children: /* @__PURE__ */ c(n, {
|
|
15
|
-
fallback: /* @__PURE__ */ c(
|
|
15
|
+
fallback: /* @__PURE__ */ c(M, {}),
|
|
16
16
|
children: /* @__PURE__ */ l(o, { children: [
|
|
17
17
|
/* @__PURE__ */ c(a, {
|
|
18
18
|
index: !0,
|
|
@@ -110,6 +110,14 @@ var u = r(() => import("./pages/brands/BrandsListPage.js").then((e) => ({ defaul
|
|
|
110
110
|
path: "profile",
|
|
111
111
|
element: /* @__PURE__ */ c(k, {})
|
|
112
112
|
}),
|
|
113
|
+
/* @__PURE__ */ c(a, {
|
|
114
|
+
path: "discover",
|
|
115
|
+
element: /* @__PURE__ */ c(A, {})
|
|
116
|
+
}),
|
|
117
|
+
/* @__PURE__ */ c(a, {
|
|
118
|
+
path: "trends",
|
|
119
|
+
element: /* @__PURE__ */ c(j, {})
|
|
120
|
+
}),
|
|
113
121
|
/* @__PURE__ */ c(a, {
|
|
114
122
|
path: "*",
|
|
115
123
|
element: /* @__PURE__ */ c(i, {
|
|
@@ -122,6 +130,6 @@ var u = r(() => import("./pages/brands/BrandsListPage.js").then((e) => ({ defaul
|
|
|
122
130
|
});
|
|
123
131
|
};
|
|
124
132
|
//#endregion
|
|
125
|
-
export {
|
|
133
|
+
export { N as CoolAndLovelyRoutes };
|
|
126
134
|
|
|
127
135
|
//# sourceMappingURL=CoolAndLovelyRoutes.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CoolAndLovelyRoutes.js","names":[],"sources":["../../src/coolandlovely/CoolAndLovelyRoutes.tsx"],"sourcesContent":["/**\n * CoolAndLovely Routes\n * @module coolandlovely\n *\n * Real fashion-domain routes wired to the live `wspace-coolandlovely-svc`\n * GraphQL contract: overview, brands, collections, style items, looks,\n * collaborations, wardrobe, lookbooks and the shopper's style profile.\n */\n\nimport { type FC, lazy, Suspense } from 'react';\nimport { Routes, Route, Navigate, useLocation } from 'react-router-dom';\nimport { useCoolAndLovely } from './context/CoolAndLovelyContext';\n\n// Eager-loaded overview (primary landing page)\nimport { HomePage } from './pages/HomePage';\n\n// Brands\nconst BrandsListPage = lazy(() =>\n import('./pages/brands/BrandsListPage').then((m) => ({ default: m.BrandsListPage }))\n);\nconst BrandDetailPage = lazy(() =>\n import('./pages/brands/BrandDetailPage').then((m) => ({ default: m.BrandDetailPage }))\n);\nconst BrandFormPage = lazy(() =>\n import('./pages/brands/BrandFormPage').then((m) => ({ default: m.BrandFormPage }))\n);\n\n// Collections\nconst CollectionsListPage = lazy(() =>\n import('./pages/collections/CollectionsListPage').then((m) => ({\n default: m.CollectionsListPage,\n }))\n);\nconst CollectionDetailPage = lazy(() =>\n import('./pages/collections/CollectionDetailPage').then((m) => ({\n default: m.CollectionDetailPage,\n }))\n);\nconst CollectionFormPage = lazy(() =>\n import('./pages/collections/CollectionFormPage').then((m) => ({ default: m.CollectionFormPage }))\n);\n\n// Style items\nconst StyleItemsListPage = lazy(() =>\n import('./pages/items/StyleItemsListPage').then((m) => ({ default: m.StyleItemsListPage }))\n);\nconst StyleItemDetailPage = lazy(() =>\n import('./pages/items/StyleItemDetailPage').then((m) => ({ default: m.StyleItemDetailPage }))\n);\nconst StyleItemFormPage = lazy(() =>\n import('./pages/items/StyleItemFormPage').then((m) => ({ default: m.StyleItemFormPage }))\n);\n\n// Looks\nconst LooksListPage = lazy(() =>\n import('./pages/looks/LooksListPage').then((m) => ({ default: m.LooksListPage }))\n);\nconst LookDetailPage = lazy(() =>\n import('./pages/looks/LookDetailPage').then((m) => ({ default: m.LookDetailPage }))\n);\nconst LookFormPage = lazy(() =>\n import('./pages/looks/LookFormPage').then((m) => ({ default: m.LookFormPage }))\n);\n\n// Collaborations\nconst CollaborationsListPage = lazy(() =>\n import('./pages/collaborations/CollaborationsListPage').then((m) => ({\n default: m.CollaborationsListPage,\n }))\n);\nconst CollaborationDetailPage = lazy(() =>\n import('./pages/collaborations/CollaborationDetailPage').then((m) => ({\n default: m.CollaborationDetailPage,\n }))\n);\nconst CollaborationFormPage = lazy(() =>\n import('./pages/collaborations/CollaborationFormPage').then((m) => ({\n default: m.CollaborationFormPage,\n }))\n);\n\n// Wardrobe (self-scoped)\nconst WardrobeListPage = lazy(() =>\n import('./pages/wardrobe/WardrobeListPage').then((m) => ({ default: m.WardrobeListPage }))\n);\nconst WardrobeFormPage = lazy(() =>\n import('./pages/wardrobe/WardrobeFormPage').then((m) => ({ default: m.WardrobeFormPage }))\n);\n\n// Lookbooks\nconst LookbooksListPage = lazy(() =>\n import('./pages/lookbooks/LookbooksListPage').then((m) => ({ default: m.LookbooksListPage }))\n);\nconst LookbookDetailPage = lazy(() =>\n import('./pages/lookbooks/LookbookDetailPage').then((m) => ({ default: m.LookbookDetailPage }))\n);\n\n// Style profile (self-scoped)\nconst StyleProfilePage = lazy(() =>\n import('./pages/profile/StyleProfilePage').then((m) => ({ default: m.StyleProfilePage }))\n);\n\nconst PageLoader: FC = () => (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <div className=\"h-8 w-8 animate-spin rounded-full border-b-2 border-primary\" />\n </div>\n);\n\nexport const CoolAndLovelyRoutes: FC = () => {\n const { basePath, isStandalone } = useCoolAndLovely();\n const location = useLocation();\n\n const normalizedBasePath = basePath === '/' ? '' : (basePath ?? '').replace(/\\/$/, '');\n\n // Guard: only render if the path matches the basePath (when integrated).\n if (normalizedBasePath && !isStandalone) {\n const pathMatches =\n location.pathname === normalizedBasePath ||\n location.pathname.startsWith(normalizedBasePath + '/');\n if (!pathMatches) {\n return null;\n }\n }\n\n return (\n <div className=\"flex min-h-full flex-col\">\n <Suspense fallback={<PageLoader />}>\n <Routes>\n {/* Overview */}\n <Route index element={<HomePage />} />\n\n {/* Brands */}\n <Route path=\"brands\" element={<BrandsListPage />} />\n <Route path=\"brands/new\" element={<BrandFormPage />} />\n <Route path=\"brands/:brandId\" element={<BrandDetailPage />} />\n <Route path=\"brands/:brandId/edit\" element={<BrandFormPage />} />\n\n {/* Collections */}\n <Route path=\"collections\" element={<CollectionsListPage />} />\n <Route path=\"collections/new\" element={<CollectionFormPage />} />\n <Route path=\"collections/:collectionId\" element={<CollectionDetailPage />} />\n <Route path=\"collections/:collectionId/edit\" element={<CollectionFormPage />} />\n\n {/* Style items */}\n <Route path=\"items\" element={<StyleItemsListPage />} />\n <Route path=\"items/new\" element={<StyleItemFormPage />} />\n <Route path=\"items/:itemId\" element={<StyleItemDetailPage />} />\n <Route path=\"items/:itemId/edit\" element={<StyleItemFormPage />} />\n\n {/* Looks */}\n <Route path=\"looks\" element={<LooksListPage />} />\n <Route path=\"looks/new\" element={<LookFormPage />} />\n <Route path=\"looks/:lookId\" element={<LookDetailPage />} />\n\n {/* Collaborations */}\n <Route path=\"collaborations\" element={<CollaborationsListPage />} />\n <Route path=\"collaborations/new\" element={<CollaborationFormPage />} />\n <Route path=\"collaborations/:collaborationId\" element={<CollaborationDetailPage />} />\n\n {/* Wardrobe (self-scoped) */}\n <Route path=\"wardrobe\" element={<WardrobeListPage />} />\n <Route path=\"wardrobe/new\" element={<WardrobeFormPage />} />\n\n {/* Lookbooks */}\n <Route path=\"lookbooks\" element={<LookbooksListPage />} />\n <Route path=\"lookbooks/:lookbookId\" element={<LookbookDetailPage />} />\n\n {/* Style profile (self-scoped) */}\n <Route path=\"profile\" element={<StyleProfilePage />} />\n\n {/* Fallback */}\n <Route path=\"*\" element={<Navigate to=\"\" replace />} />\n </Routes>\n </Suspense>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CoolAndLovelyRoutes.js","names":[],"sources":["../../src/coolandlovely/CoolAndLovelyRoutes.tsx"],"sourcesContent":["/**\n * CoolAndLovely Routes\n * @module coolandlovely\n *\n * Real fashion-domain routes wired to the live `wspace-coolandlovely-svc`\n * GraphQL contract: overview, brands, collections, style items, looks,\n * collaborations, wardrobe, lookbooks and the shopper's style profile.\n */\n\nimport { type FC, lazy, Suspense } from 'react';\nimport { Routes, Route, Navigate, useLocation } from 'react-router-dom';\nimport { useCoolAndLovely } from './context/CoolAndLovelyContext';\n\n// Eager-loaded overview (primary landing page)\nimport { HomePage } from './pages/HomePage';\n\n// Brands\nconst BrandsListPage = lazy(() =>\n import('./pages/brands/BrandsListPage').then((m) => ({ default: m.BrandsListPage }))\n);\nconst BrandDetailPage = lazy(() =>\n import('./pages/brands/BrandDetailPage').then((m) => ({ default: m.BrandDetailPage }))\n);\nconst BrandFormPage = lazy(() =>\n import('./pages/brands/BrandFormPage').then((m) => ({ default: m.BrandFormPage }))\n);\n\n// Collections\nconst CollectionsListPage = lazy(() =>\n import('./pages/collections/CollectionsListPage').then((m) => ({\n default: m.CollectionsListPage,\n }))\n);\nconst CollectionDetailPage = lazy(() =>\n import('./pages/collections/CollectionDetailPage').then((m) => ({\n default: m.CollectionDetailPage,\n }))\n);\nconst CollectionFormPage = lazy(() =>\n import('./pages/collections/CollectionFormPage').then((m) => ({ default: m.CollectionFormPage }))\n);\n\n// Style items\nconst StyleItemsListPage = lazy(() =>\n import('./pages/items/StyleItemsListPage').then((m) => ({ default: m.StyleItemsListPage }))\n);\nconst StyleItemDetailPage = lazy(() =>\n import('./pages/items/StyleItemDetailPage').then((m) => ({ default: m.StyleItemDetailPage }))\n);\nconst StyleItemFormPage = lazy(() =>\n import('./pages/items/StyleItemFormPage').then((m) => ({ default: m.StyleItemFormPage }))\n);\n\n// Looks\nconst LooksListPage = lazy(() =>\n import('./pages/looks/LooksListPage').then((m) => ({ default: m.LooksListPage }))\n);\nconst LookDetailPage = lazy(() =>\n import('./pages/looks/LookDetailPage').then((m) => ({ default: m.LookDetailPage }))\n);\nconst LookFormPage = lazy(() =>\n import('./pages/looks/LookFormPage').then((m) => ({ default: m.LookFormPage }))\n);\n\n// Collaborations\nconst CollaborationsListPage = lazy(() =>\n import('./pages/collaborations/CollaborationsListPage').then((m) => ({\n default: m.CollaborationsListPage,\n }))\n);\nconst CollaborationDetailPage = lazy(() =>\n import('./pages/collaborations/CollaborationDetailPage').then((m) => ({\n default: m.CollaborationDetailPage,\n }))\n);\nconst CollaborationFormPage = lazy(() =>\n import('./pages/collaborations/CollaborationFormPage').then((m) => ({\n default: m.CollaborationFormPage,\n }))\n);\n\n// Wardrobe (self-scoped)\nconst WardrobeListPage = lazy(() =>\n import('./pages/wardrobe/WardrobeListPage').then((m) => ({ default: m.WardrobeListPage }))\n);\nconst WardrobeFormPage = lazy(() =>\n import('./pages/wardrobe/WardrobeFormPage').then((m) => ({ default: m.WardrobeFormPage }))\n);\n\n// Lookbooks\nconst LookbooksListPage = lazy(() =>\n import('./pages/lookbooks/LookbooksListPage').then((m) => ({ default: m.LookbooksListPage }))\n);\nconst LookbookDetailPage = lazy(() =>\n import('./pages/lookbooks/LookbookDetailPage').then((m) => ({ default: m.LookbookDetailPage }))\n);\n\n// Style profile (self-scoped)\nconst StyleProfilePage = lazy(() =>\n import('./pages/profile/StyleProfilePage').then((m) => ({ default: m.StyleProfilePage }))\n);\n\n// Discovery + trends (client-side analytics over the real catalogue/social lists)\nconst DiscoverPage = lazy(() =>\n import('./pages/discover/DiscoverPage').then((m) => ({ default: m.DiscoverPage }))\n);\nconst TrendsPage = lazy(() =>\n import('./pages/trends/TrendsPage').then((m) => ({ default: m.TrendsPage }))\n);\n\nconst PageLoader: FC = () => (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <div className=\"h-8 w-8 animate-spin rounded-full border-b-2 border-primary\" />\n </div>\n);\n\nexport const CoolAndLovelyRoutes: FC = () => {\n const { basePath, isStandalone } = useCoolAndLovely();\n const location = useLocation();\n\n const normalizedBasePath = basePath === '/' ? '' : (basePath ?? '').replace(/\\/$/, '');\n\n // Guard: only render if the path matches the basePath (when integrated).\n if (normalizedBasePath && !isStandalone) {\n const pathMatches =\n location.pathname === normalizedBasePath ||\n location.pathname.startsWith(normalizedBasePath + '/');\n if (!pathMatches) {\n return null;\n }\n }\n\n return (\n <div className=\"flex min-h-full flex-col\">\n <Suspense fallback={<PageLoader />}>\n <Routes>\n {/* Overview */}\n <Route index element={<HomePage />} />\n\n {/* Brands */}\n <Route path=\"brands\" element={<BrandsListPage />} />\n <Route path=\"brands/new\" element={<BrandFormPage />} />\n <Route path=\"brands/:brandId\" element={<BrandDetailPage />} />\n <Route path=\"brands/:brandId/edit\" element={<BrandFormPage />} />\n\n {/* Collections */}\n <Route path=\"collections\" element={<CollectionsListPage />} />\n <Route path=\"collections/new\" element={<CollectionFormPage />} />\n <Route path=\"collections/:collectionId\" element={<CollectionDetailPage />} />\n <Route path=\"collections/:collectionId/edit\" element={<CollectionFormPage />} />\n\n {/* Style items */}\n <Route path=\"items\" element={<StyleItemsListPage />} />\n <Route path=\"items/new\" element={<StyleItemFormPage />} />\n <Route path=\"items/:itemId\" element={<StyleItemDetailPage />} />\n <Route path=\"items/:itemId/edit\" element={<StyleItemFormPage />} />\n\n {/* Looks */}\n <Route path=\"looks\" element={<LooksListPage />} />\n <Route path=\"looks/new\" element={<LookFormPage />} />\n <Route path=\"looks/:lookId\" element={<LookDetailPage />} />\n\n {/* Collaborations */}\n <Route path=\"collaborations\" element={<CollaborationsListPage />} />\n <Route path=\"collaborations/new\" element={<CollaborationFormPage />} />\n <Route path=\"collaborations/:collaborationId\" element={<CollaborationDetailPage />} />\n\n {/* Wardrobe (self-scoped) */}\n <Route path=\"wardrobe\" element={<WardrobeListPage />} />\n <Route path=\"wardrobe/new\" element={<WardrobeFormPage />} />\n\n {/* Lookbooks */}\n <Route path=\"lookbooks\" element={<LookbooksListPage />} />\n <Route path=\"lookbooks/:lookbookId\" element={<LookbookDetailPage />} />\n\n {/* Style profile (self-scoped) */}\n <Route path=\"profile\" element={<StyleProfilePage />} />\n\n {/* Discovery + trends */}\n <Route path=\"discover\" element={<DiscoverPage />} />\n <Route path=\"trends\" element={<TrendsPage />} />\n\n {/* Fallback */}\n <Route path=\"*\" element={<Navigate to=\"\" replace />} />\n </Routes>\n </Suspense>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useCoolAndLovely as e } from "../context/CoolAndLovelyContext.js";
|
|
2
|
+
import { useMemo as t } from "react";
|
|
3
|
+
import { jsx as n } from "react/jsx-runtime";
|
|
4
|
+
import { useActorProfiles as r } from "@burdenoff/fe-libs/shared";
|
|
5
|
+
//#region src/coolandlovely/components/ActorName.tsx
|
|
6
|
+
var i = ({ id: i, actorType: a = "user", className: o, fallback: s = "—" }) => {
|
|
7
|
+
let { apiGatewayUrl: c, authToken: l, workspaceId: u } = e(), d = r(t(() => i ? [{
|
|
8
|
+
actorId: i,
|
|
9
|
+
actorType: a
|
|
10
|
+
}] : [], [i, a]), {
|
|
11
|
+
apiGatewayUrl: c,
|
|
12
|
+
authToken: l,
|
|
13
|
+
workspaceId: u
|
|
14
|
+
});
|
|
15
|
+
if (!i) return /* @__PURE__ */ n("span", {
|
|
16
|
+
className: o,
|
|
17
|
+
children: s
|
|
18
|
+
});
|
|
19
|
+
let f = d[i], p = f?.displayName ?? i;
|
|
20
|
+
return /* @__PURE__ */ n("span", {
|
|
21
|
+
className: o,
|
|
22
|
+
title: f?.email ?? i,
|
|
23
|
+
children: p
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
//#endregion
|
|
27
|
+
export { i as ActorName };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=ActorName.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ActorName.js","names":[],"sources":["../../../src/coolandlovely/components/ActorName.tsx"],"sourcesContent":["/**\n * ActorName — render an actor UUID as a human-readable display name.\n * @module coolandlovely/components\n *\n * Wraps fe-libs `useActorProfiles`, which resolves identities via the GLOBAL\n * gateway (workspace-member → org-member → admin lookup → local fallback) with\n * an internal 5-minute cache. NEVER select firstName/name on a federated\n * wspace `User` — resolve names here instead. The MFE is handed a *workspace*\n * `apiGatewayUrl`; `useActorProfiles` detects that and only uses it for the\n * workspace-scoped tier, resolving the global gateway from the hostname itself,\n * so forwarding our workspace URL is safe. Failures degrade to a short-id\n * fallback, never an error.\n */\n\nimport type { FC } from 'react';\nimport { useMemo } from 'react';\nimport { useActorProfiles, type ActorInput } from '@burdenoff/fe-libs/shared';\nimport { useCoolAndLovely } from '../context/CoolAndLovelyContext';\n\ninterface ActorNameProps {\n /** Actor id (usually a user UUID). */\n id?: string | null;\n /** Actor type — 'user' (default), 'app', 'service', 'super_admin'. */\n actorType?: string;\n className?: string;\n /** Shown when `id` is absent/empty (default \"—\"). */\n fallback?: string;\n}\n\nexport const ActorName: FC<ActorNameProps> = ({\n id,\n actorType = 'user',\n className,\n fallback = '—',\n}) => {\n const { apiGatewayUrl, authToken, workspaceId } = useCoolAndLovely();\n\n const actors = useMemo<ActorInput[]>(\n () => (id ? [{ actorId: id, actorType }] : []),\n [id, actorType]\n );\n const profiles = useActorProfiles(actors, { apiGatewayUrl, authToken, workspaceId });\n\n if (!id) return <span className={className}>{fallback}</span>;\n\n const profile = profiles[id];\n const displayName = profile?.displayName ?? id;\n\n return (\n <span className={className} title={profile?.email ?? id}>\n {displayName}\n </span>\n );\n};\n"],"mappings":";;;;;AA6BA,IAAa,KAAiC,EAC5C,OACA,eAAY,QACZ,cACA,cAAW,UACP;CACJ,IAAM,EAAE,kBAAe,cAAW,mBAAgB,GAAkB,EAM9D,IAAW,EAJF,QACN,IAAK,CAAC;EAAE,SAAS;EAAI;EAAW,CAAC,GAAG,EAAE,EAC7C,CAAC,GAAI,EAAU,CAChB,EACyC;EAAE;EAAe;EAAW;EAAa,CAAC;AAEpF,KAAI,CAAC,EAAI,QAAO,kBAAC,QAAD;EAAiB;YAAY;EAAgB,CAAA;CAE7D,IAAM,IAAU,EAAS,IACnB,IAAc,GAAS,eAAe;AAE5C,QACE,kBAAC,QAAD;EAAiB;EAAW,OAAO,GAAS,SAAS;YAClD;EACI,CAAA"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { useCoolAndLovely as e } from "../context/CoolAndLovelyContext.js";
|
|
2
|
+
import { cn as t } from "../utils/cn.js";
|
|
3
|
+
import { useAddLookbookEntry as n, useLookbooks as r } from "../hooks/useCoolAndLovelyApi.js";
|
|
4
|
+
import { useEffect as i, useRef as a, useState as o } from "react";
|
|
5
|
+
import { BookHeart as s, Check as c, ChevronDown as l, Loader2 as u, Plus as d } from "lucide-react";
|
|
6
|
+
import { jsx as f, jsxs as p } from "react/jsx-runtime";
|
|
7
|
+
//#region src/coolandlovely/components/AddToLookbookButton.tsx
|
|
8
|
+
var m = ({ lookId: m, styleItemId: h, className: g }) => {
|
|
9
|
+
let { navigateTo: _ } = e(), v = r(), y = n(), [b, x] = o(!1), [S, C] = o(null), w = a(null);
|
|
10
|
+
i(() => {
|
|
11
|
+
if (!b) return;
|
|
12
|
+
let e = (e) => {
|
|
13
|
+
w.current && !w.current.contains(e.target) && x(!1);
|
|
14
|
+
};
|
|
15
|
+
return document.addEventListener("mousedown", e), () => document.removeEventListener("mousedown", e);
|
|
16
|
+
}, [b]);
|
|
17
|
+
let T = v.data ?? [], E = (e) => {
|
|
18
|
+
y.mutate({
|
|
19
|
+
lookbookId: e,
|
|
20
|
+
lookId: m,
|
|
21
|
+
styleItemId: h
|
|
22
|
+
}, { onSuccess: () => {
|
|
23
|
+
C(e), setTimeout(() => {
|
|
24
|
+
x(!1), C(null);
|
|
25
|
+
}, 900);
|
|
26
|
+
} });
|
|
27
|
+
};
|
|
28
|
+
return /* @__PURE__ */ p("div", {
|
|
29
|
+
ref: w,
|
|
30
|
+
className: t("relative", g),
|
|
31
|
+
children: [/* @__PURE__ */ p("button", {
|
|
32
|
+
type: "button",
|
|
33
|
+
onClick: () => x((e) => !e),
|
|
34
|
+
"aria-haspopup": "menu",
|
|
35
|
+
"aria-expanded": b,
|
|
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
|
+
children: [
|
|
38
|
+
/* @__PURE__ */ f(s, { className: "h-4 w-4" }),
|
|
39
|
+
" Add to lookbook",
|
|
40
|
+
/* @__PURE__ */ f(l, { className: "h-3.5 w-3.5" })
|
|
41
|
+
]
|
|
42
|
+
}), b && /* @__PURE__ */ p("div", {
|
|
43
|
+
role: "menu",
|
|
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: [v.isLoading ? /* @__PURE__ */ p("div", {
|
|
46
|
+
className: "flex items-center gap-2 px-3 py-3 text-sm text-text-secondary",
|
|
47
|
+
children: [/* @__PURE__ */ f(u, { className: "h-4 w-4 animate-spin" }), " Loading lookbooks…"]
|
|
48
|
+
}) : T.length === 0 ? /* @__PURE__ */ p("button", {
|
|
49
|
+
type: "button",
|
|
50
|
+
onClick: () => {
|
|
51
|
+
x(!1), _("/lookbooks");
|
|
52
|
+
},
|
|
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
|
+
children: [/* @__PURE__ */ f(d, { className: "h-4 w-4" }), " Create your first lookbook"]
|
|
55
|
+
}) : /* @__PURE__ */ f("ul", {
|
|
56
|
+
className: "max-h-64 overflow-auto py-1",
|
|
57
|
+
children: T.map((e) => {
|
|
58
|
+
let t = S === e.id;
|
|
59
|
+
return /* @__PURE__ */ f("li", { children: /* @__PURE__ */ p("button", {
|
|
60
|
+
type: "button",
|
|
61
|
+
role: "menuitem",
|
|
62
|
+
disabled: y.isPending,
|
|
63
|
+
onClick: () => E(e.id),
|
|
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
|
+
children: [/* @__PURE__ */ f("span", {
|
|
66
|
+
className: "truncate",
|
|
67
|
+
children: e.name
|
|
68
|
+
}), t ? /* @__PURE__ */ f(c, { className: "h-4 w-4 shrink-0 text-status-success-text" }) : y.isPending ? /* @__PURE__ */ f(u, { className: "h-4 w-4 shrink-0 animate-spin text-text-secondary" }) : null]
|
|
69
|
+
}) }, e.id);
|
|
70
|
+
})
|
|
71
|
+
}), y.isError && /* @__PURE__ */ f("p", {
|
|
72
|
+
className: "border-t border-border-seam px-3 py-2 text-xs text-status-error-text",
|
|
73
|
+
children: y.error instanceof Error ? y.error.message : "Could not add"
|
|
74
|
+
})]
|
|
75
|
+
})]
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
//#endregion
|
|
79
|
+
export { m as AddToLookbookButton };
|
|
80
|
+
|
|
81
|
+
//# sourceMappingURL=AddToLookbookButton.js.map
|
|
@@ -0,0 +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,kBAAe,GAAkB,EACnC,IAAY,GAAc,EAC1B,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,IAAS,EAAU,QAAQ,EAAE,EAE7B,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,109 @@
|
|
|
1
|
+
import { cn as e } from "../utils/cn.js";
|
|
2
|
+
import { useAddComment as t, useEntityComments as n } from "../hooks/useComments.js";
|
|
3
|
+
import { formatRelativeTime as r } from "../utils/formatters.js";
|
|
4
|
+
import { ActorName as i } from "./ActorName.js";
|
|
5
|
+
import { useState as a } from "react";
|
|
6
|
+
import { Loader2 as o, MessageSquare as s, Send as c } from "lucide-react";
|
|
7
|
+
import { jsx as l, jsxs as u } from "react/jsx-runtime";
|
|
8
|
+
import { GlassCard as d } from "@burdenoff/fe-libs/ui";
|
|
9
|
+
//#region src/coolandlovely/components/Comments.tsx
|
|
10
|
+
var f = ({ entityType: f, entityId: p, title: m = "Reviews & feedback", className: h }) => {
|
|
11
|
+
let g = n(f, p), _ = t(), [v, y] = a(""), b = g.data?.comments ?? [], x = g.data?.total ?? b.length;
|
|
12
|
+
return /* @__PURE__ */ u("section", {
|
|
13
|
+
className: e("mt-6", h),
|
|
14
|
+
children: [
|
|
15
|
+
/* @__PURE__ */ u("h3", {
|
|
16
|
+
className: "mb-3 flex items-center gap-2 text-lg font-semibold text-text-primary",
|
|
17
|
+
children: [
|
|
18
|
+
/* @__PURE__ */ l(s, { className: "h-5 w-5" }),
|
|
19
|
+
" ",
|
|
20
|
+
m,
|
|
21
|
+
" (",
|
|
22
|
+
x,
|
|
23
|
+
")"
|
|
24
|
+
]
|
|
25
|
+
}),
|
|
26
|
+
/* @__PURE__ */ u(d, {
|
|
27
|
+
treatment: "plain",
|
|
28
|
+
className: "p-4",
|
|
29
|
+
children: [/* @__PURE__ */ u("form", {
|
|
30
|
+
onSubmit: (e) => {
|
|
31
|
+
e.preventDefault();
|
|
32
|
+
let t = v.trim();
|
|
33
|
+
!t || !p || _.mutate({
|
|
34
|
+
entityType: f,
|
|
35
|
+
entityId: p,
|
|
36
|
+
text: t
|
|
37
|
+
}, { onSuccess: () => y("") });
|
|
38
|
+
},
|
|
39
|
+
className: "flex flex-col gap-2 sm:flex-row sm:items-start",
|
|
40
|
+
children: [
|
|
41
|
+
/* @__PURE__ */ l("label", {
|
|
42
|
+
htmlFor: "coo-comment-input",
|
|
43
|
+
className: "sr-only",
|
|
44
|
+
children: "Write a review or leave feedback"
|
|
45
|
+
}),
|
|
46
|
+
/* @__PURE__ */ l("textarea", {
|
|
47
|
+
id: "coo-comment-input",
|
|
48
|
+
value: v,
|
|
49
|
+
onChange: (e) => y(e.target.value),
|
|
50
|
+
rows: 2,
|
|
51
|
+
placeholder: "Share a review or leave feedback…",
|
|
52
|
+
className: "w-full flex-1 resize-y rounded-lg border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]"
|
|
53
|
+
}),
|
|
54
|
+
/* @__PURE__ */ u("button", {
|
|
55
|
+
type: "submit",
|
|
56
|
+
disabled: !v.trim() || !p || _.isPending,
|
|
57
|
+
className: "inline-flex shrink-0 items-center justify-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50",
|
|
58
|
+
children: [_.isPending ? /* @__PURE__ */ l(o, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ l(c, { className: "h-4 w-4" }), "Post"]
|
|
59
|
+
})
|
|
60
|
+
]
|
|
61
|
+
}), _.isError && /* @__PURE__ */ l("p", {
|
|
62
|
+
className: "mt-2 text-xs text-status-error-text",
|
|
63
|
+
children: _.error instanceof Error ? _.error.message : "Could not post"
|
|
64
|
+
})]
|
|
65
|
+
}),
|
|
66
|
+
/* @__PURE__ */ l("div", {
|
|
67
|
+
className: "mt-4",
|
|
68
|
+
children: g.isLoading ? /* @__PURE__ */ u("div", {
|
|
69
|
+
className: "flex items-center justify-center gap-2 py-6 text-sm text-text-secondary",
|
|
70
|
+
children: [/* @__PURE__ */ l(o, { className: "h-4 w-4 animate-spin" }), " Loading reviews…"]
|
|
71
|
+
}) : g.isError ? /* @__PURE__ */ u("div", {
|
|
72
|
+
className: "rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary",
|
|
73
|
+
children: ["Reviews could not be loaded right now.", /* @__PURE__ */ l("button", {
|
|
74
|
+
type: "button",
|
|
75
|
+
onClick: () => void g.refetch(),
|
|
76
|
+
className: "ml-2 font-medium text-action-primary-bg hover:underline",
|
|
77
|
+
children: "Retry"
|
|
78
|
+
})]
|
|
79
|
+
}) : b.length === 0 ? /* @__PURE__ */ l("p", {
|
|
80
|
+
className: "rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary",
|
|
81
|
+
children: "No reviews yet. Be the first to share feedback."
|
|
82
|
+
}) : /* @__PURE__ */ l("ul", {
|
|
83
|
+
className: "divide-y divide-border-seam rounded-lg border border-border-seam bg-bg-surface",
|
|
84
|
+
children: b.map((e) => /* @__PURE__ */ u("li", {
|
|
85
|
+
className: "px-4 py-3",
|
|
86
|
+
children: [/* @__PURE__ */ u("div", {
|
|
87
|
+
className: "flex items-center justify-between gap-2",
|
|
88
|
+
children: [/* @__PURE__ */ l(i, {
|
|
89
|
+
id: e.authorId,
|
|
90
|
+
actorType: e.authorType,
|
|
91
|
+
className: "truncate text-sm font-medium text-text-primary"
|
|
92
|
+
}), /* @__PURE__ */ u("span", {
|
|
93
|
+
className: "shrink-0 text-xs text-text-secondary",
|
|
94
|
+
children: [r(e.createdAt), e.edited ? " · edited" : ""]
|
|
95
|
+
})]
|
|
96
|
+
}), /* @__PURE__ */ l("p", {
|
|
97
|
+
className: "mt-1 whitespace-pre-wrap break-words text-sm text-text-secondary",
|
|
98
|
+
children: e.text
|
|
99
|
+
})]
|
|
100
|
+
}, e.id))
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
]
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
//#endregion
|
|
107
|
+
export { f as Comments };
|
|
108
|
+
|
|
109
|
+
//# sourceMappingURL=Comments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Comments.js","names":[],"sources":["../../../src/coolandlovely/components/Comments.tsx"],"sourcesContent":["/**\n * Comments — reviews & feedback thread for an entity (looks, brands).\n * @module coolandlovely/components\n *\n * Reuses wspace-conversations-svc `EntityComment` (NO coolandlovely comment\n * table). Lists + posts comments for (entityType, entityId). Commenter names\n * resolve via fe-libs useActorProfiles (GLOBAL gateway) — see ActorName.\n */\n\nimport type { FC } from 'react';\nimport { useState } from 'react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { Loader2, MessageSquare, Send } from 'lucide-react';\nimport { ActorName } from './ActorName';\nimport { useAddComment, useEntityComments } from '../hooks/useComments';\nimport { formatRelativeTime } from '../utils/formatters';\nimport { cn } from '../utils/cn';\n\ninterface CommentsProps {\n /** Conversations entity type, e.g. \"CooLook\" / \"CooBrand\". */\n entityType: string;\n /** The entity id (skips rendering until present). */\n entityId?: string;\n /** Section heading (default \"Reviews & feedback\"). */\n title?: string;\n className?: string;\n}\n\nexport const Comments: FC<CommentsProps> = ({\n entityType,\n entityId,\n title = 'Reviews & feedback',\n className,\n}) => {\n const query = useEntityComments(entityType, entityId);\n const addComment = useAddComment();\n const [draft, setDraft] = useState('');\n\n const comments = query.data?.comments ?? [];\n const total = query.data?.total ?? comments.length;\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n const text = draft.trim();\n if (!text || !entityId) return;\n addComment.mutate(\n { entityType, entityId, text },\n { onSuccess: () => setDraft('') }\n );\n };\n\n return (\n <section className={cn('mt-6', className)}>\n <h3 className=\"mb-3 flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <MessageSquare className=\"h-5 w-5\" /> {title} ({total})\n </h3>\n\n <GlassCard treatment=\"plain\" className=\"p-4\">\n <form onSubmit={handleSubmit} className=\"flex flex-col gap-2 sm:flex-row sm:items-start\">\n <label htmlFor=\"coo-comment-input\" className=\"sr-only\">\n Write a review or leave feedback\n </label>\n <textarea\n id=\"coo-comment-input\"\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n rows={2}\n placeholder=\"Share a review or leave feedback…\"\n className=\"w-full flex-1 resize-y rounded-lg border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n <button\n type=\"submit\"\n disabled={!draft.trim() || !entityId || addComment.isPending}\n className=\"inline-flex shrink-0 items-center justify-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {addComment.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Send className=\"h-4 w-4\" />\n )}\n Post\n </button>\n </form>\n {addComment.isError && (\n <p className=\"mt-2 text-xs text-status-error-text\">\n {addComment.error instanceof Error ? addComment.error.message : 'Could not post'}\n </p>\n )}\n </GlassCard>\n\n <div className=\"mt-4\">\n {query.isLoading ? (\n <div className=\"flex items-center justify-center gap-2 py-6 text-sm text-text-secondary\">\n <Loader2 className=\"h-4 w-4 animate-spin\" /> Loading reviews…\n </div>\n ) : query.isError ? (\n <div className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n Reviews could not be loaded right now.\n <button\n type=\"button\"\n onClick={() => void query.refetch()}\n className=\"ml-2 font-medium text-action-primary-bg hover:underline\"\n >\n Retry\n </button>\n </div>\n ) : comments.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n No reviews yet. Be the first to share feedback.\n </p>\n ) : (\n <ul className=\"divide-y divide-border-seam rounded-lg border border-border-seam bg-bg-surface\">\n {comments.map((c) => (\n <li key={c.id} className=\"px-4 py-3\">\n <div className=\"flex items-center justify-between gap-2\">\n <ActorName\n id={c.authorId}\n actorType={c.authorType}\n className=\"truncate text-sm font-medium text-text-primary\"\n />\n <span className=\"shrink-0 text-xs text-text-secondary\">\n {formatRelativeTime(c.createdAt)}\n {c.edited ? ' · edited' : ''}\n </span>\n </div>\n <p className=\"mt-1 whitespace-pre-wrap break-words text-sm text-text-secondary\">\n {c.text}\n </p>\n </li>\n ))}\n </ul>\n )}\n </div>\n </section>\n );\n};\n"],"mappings":";;;;;;;;;AA4BA,IAAa,KAA+B,EAC1C,eACA,aACA,WAAQ,sBACR,mBACI;CACJ,IAAM,IAAQ,EAAkB,GAAY,EAAS,EAC/C,IAAa,GAAe,EAC5B,CAAC,GAAO,KAAY,EAAS,GAAG,EAEhC,IAAW,EAAM,MAAM,YAAY,EAAE,EACrC,IAAQ,EAAM,MAAM,SAAS,EAAS;AAY5C,QACE,kBAAC,WAAD;EAAS,WAAW,EAAG,QAAQ,EAAU;YAAzC;GACE,kBAAC,MAAD;IAAI,WAAU;cAAd;KACE,kBAAC,GAAD,EAAe,WAAU,WAAY,CAAA;;KAAE;KAAM;KAAG;KAAM;KACnD;;GAEL,kBAAC,GAAD;IAAW,WAAU;IAAQ,WAAU;cAAvC,CACE,kBAAC,QAAD;KAAM,WAjBU,MAAuB;AAC3C,QAAE,gBAAgB;MAClB,IAAM,IAAO,EAAM,MAAM;AACrB,OAAC,KAAQ,CAAC,KACd,EAAW,OACT;OAAE;OAAY;OAAU;OAAM,EAC9B,EAAE,iBAAiB,EAAS,GAAG,EAAE,CAClC;;KAUiC,WAAU;eAAxC;MACE,kBAAC,SAAD;OAAO,SAAQ;OAAoB,WAAU;iBAAU;OAE/C,CAAA;MACR,kBAAC,YAAD;OACE,IAAG;OACH,OAAO;OACP,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;OACzC,MAAM;OACN,aAAY;OACZ,WAAU;OACV,CAAA;MACF,kBAAC,UAAD;OACE,MAAK;OACL,UAAU,CAAC,EAAM,MAAM,IAAI,CAAC,KAAY,EAAW;OACnD,WAAU;iBAHZ,CAKG,EAAW,YACV,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,GAE5C,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAC5B,OAEK;;MACJ;QACN,EAAW,WACV,kBAAC,KAAD;KAAG,WAAU;eACV,EAAW,iBAAiB,QAAQ,EAAW,MAAM,UAAU;KAC9D,CAAA,CAEI;;GAEZ,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAM,YACL,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,EAAA,oBACxC;SACJ,EAAM,UACR,kBAAC,OAAD;KAAK,WAAU;eAAf,CAAoG,0CAElG,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,KAAK,EAAM,SAAS;MACnC,WAAU;gBACX;MAEQ,CAAA,CACL;SACJ,EAAS,WAAW,IACtB,kBAAC,KAAD;KAAG,WAAU;eAAqF;KAE9F,CAAA,GAEJ,kBAAC,MAAD;KAAI,WAAU;eACX,EAAS,KAAK,MACb,kBAAC,MAAD;MAAe,WAAU;gBAAzB,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QACE,IAAI,EAAE;QACN,WAAW,EAAE;QACb,WAAU;QACV,CAAA,EACF,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CACG,EAAmB,EAAE,UAAU,EAC/B,EAAE,SAAS,cAAc,GACrB;UACH;UACN,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAE;OACD,CAAA,CACD;QAfI,EAAE,GAeN,CACL;KACC,CAAA;IAEH,CAAA;GACE"}
|
|
@@ -53,7 +53,27 @@ var r = ({ label: r, htmlFor: i, required: a, hint: o, error: s, children: c, cl
|
|
|
53
53
|
onChange: (e) => s(e.target.value),
|
|
54
54
|
className: e(i, "resize-y", d && "border-status-error-text")
|
|
55
55
|
})
|
|
56
|
-
}), s = ({ id:
|
|
56
|
+
}), s = ({ id: r, label: i, checked: a, onChange: o, hint: s, disabled: c, className: l }) => /* @__PURE__ */ n("label", {
|
|
57
|
+
htmlFor: r,
|
|
58
|
+
className: e("flex cursor-pointer items-start gap-3 rounded-lg border border-border-seam bg-bg-surface px-3 py-2.5", c && "cursor-not-allowed opacity-60", l),
|
|
59
|
+
children: [/* @__PURE__ */ t("input", {
|
|
60
|
+
id: r,
|
|
61
|
+
type: "checkbox",
|
|
62
|
+
checked: a,
|
|
63
|
+
disabled: c,
|
|
64
|
+
onChange: (e) => o(e.target.checked),
|
|
65
|
+
className: "mt-0.5 h-4 w-4 shrink-0 rounded border-border-seam text-action-primary-bg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]"
|
|
66
|
+
}), /* @__PURE__ */ n("span", {
|
|
67
|
+
className: "min-w-0",
|
|
68
|
+
children: [/* @__PURE__ */ t("span", {
|
|
69
|
+
className: "block text-sm font-medium text-text-primary",
|
|
70
|
+
children: i
|
|
71
|
+
}), s && /* @__PURE__ */ t("span", {
|
|
72
|
+
className: "mt-0.5 block text-xs text-text-secondary",
|
|
73
|
+
children: s
|
|
74
|
+
})]
|
|
75
|
+
})]
|
|
76
|
+
}), c = ({ id: a, label: o, value: s, onChange: c, options: l, required: u, hint: d, error: f, placeholder: p, className: m }) => /* @__PURE__ */ t(r, {
|
|
57
77
|
label: o,
|
|
58
78
|
htmlFor: a,
|
|
59
79
|
required: u,
|
|
@@ -76,6 +96,6 @@ var r = ({ label: r, htmlFor: i, required: a, hint: o, error: s, children: c, cl
|
|
|
76
96
|
})
|
|
77
97
|
});
|
|
78
98
|
//#endregion
|
|
79
|
-
export {
|
|
99
|
+
export { r as FieldShell, c as SelectField, o as TextAreaField, a as TextField, s as ToggleField };
|
|
80
100
|
|
|
81
101
|
//# sourceMappingURL=Field.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Field.js","names":[],"sources":["../../../src/coolandlovely/components/Field.tsx"],"sourcesContent":["/**\n * Form field primitives\n * @module coolandlovely/components\n *\n * Small, strictly-typed, token-only form controls used across the create/edit\n * forms (no third-party form lib — these wrap native inputs with consistent\n * labels, hints and error styling).\n */\n\nimport type { FC, ReactNode, ChangeEvent } from 'react';\nimport { cn } from '../utils/cn';\n\ninterface FieldShellProps {\n label: string;\n htmlFor?: string;\n required?: boolean;\n hint?: string;\n error?: string;\n children: ReactNode;\n className?: string;\n}\n\nexport const FieldShell: FC<FieldShellProps> = ({\n label,\n htmlFor,\n required,\n hint,\n error,\n children,\n className,\n}) => (\n <div className={cn('flex flex-col gap-1.5', className)}>\n <label htmlFor={htmlFor} className=\"text-sm font-medium text-text-primary\">\n {label}\n {required && <span className=\"ml-0.5 text-status-error-text\">*</span>}\n </label>\n {children}\n {error ? (\n <p className=\"text-xs text-status-error-text\">{error}</p>\n ) : hint ? (\n <p className=\"text-xs text-text-secondary\">{hint}</p>\n ) : null}\n </div>\n);\n\nconst baseControl =\n 'w-full rounded-lg border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\n\ninterface TextFieldProps {\n id?: string;\n label: string;\n value: string;\n onChange: (value: string) => void;\n placeholder?: string;\n required?: boolean;\n hint?: string;\n error?: string;\n type?: 'text' | 'url' | 'number' | 'date';\n className?: string;\n}\n\nexport const TextField: FC<TextFieldProps> = ({\n id,\n label,\n value,\n onChange,\n placeholder,\n required,\n hint,\n error,\n type = 'text',\n className,\n}) => (\n <FieldShell\n label={label}\n htmlFor={id}\n required={required}\n hint={hint}\n error={error}\n className={className}\n >\n <input\n id={id}\n type={type}\n value={value}\n placeholder={placeholder}\n required={required}\n onChange={(e: ChangeEvent<HTMLInputElement>) => onChange(e.target.value)}\n className={cn(baseControl, error && 'border-status-error-text')}\n />\n </FieldShell>\n);\n\ninterface TextAreaFieldProps {\n id?: string;\n label: string;\n value: string;\n onChange: (value: string) => void;\n placeholder?: string;\n required?: boolean;\n hint?: string;\n error?: string;\n rows?: number;\n className?: string;\n}\n\nexport const TextAreaField: FC<TextAreaFieldProps> = ({\n id,\n label,\n value,\n onChange,\n placeholder,\n required,\n hint,\n error,\n rows = 3,\n className,\n}) => (\n <FieldShell\n label={label}\n htmlFor={id}\n required={required}\n hint={hint}\n error={error}\n className={className}\n >\n <textarea\n id={id}\n value={value}\n placeholder={placeholder}\n required={required}\n rows={rows}\n onChange={(e: ChangeEvent<HTMLTextAreaElement>) => onChange(e.target.value)}\n className={cn(baseControl, 'resize-y', error && 'border-status-error-text')}\n />\n </FieldShell>\n);\n\ninterface SelectOption {\n value: string;\n label: string;\n}\n\ninterface SelectFieldProps {\n id?: string;\n label: string;\n value: string;\n onChange: (value: string) => void;\n options: SelectOption[];\n required?: boolean;\n hint?: string;\n error?: string;\n placeholder?: string;\n className?: string;\n}\n\nexport const SelectField: FC<SelectFieldProps> = ({\n id,\n label,\n value,\n onChange,\n options,\n required,\n hint,\n error,\n placeholder,\n className,\n}) => (\n <FieldShell\n label={label}\n htmlFor={id}\n required={required}\n hint={hint}\n error={error}\n className={className}\n >\n <select\n id={id}\n value={value}\n required={required}\n onChange={(e: ChangeEvent<HTMLSelectElement>) => onChange(e.target.value)}\n className={cn(baseControl, error && 'border-status-error-text')}\n >\n {placeholder && <option value=\"\">{placeholder}</option>}\n {options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </FieldShell>\n);\n"],"mappings":";;;AAsBA,IAAa,KAAmC,EAC9C,UACA,YACA,aACA,SACA,UACA,aACA,mBAEA,kBAAC,OAAD;CAAK,WAAW,EAAG,yBAAyB,EAAU;WAAtD;EACE,kBAAC,SAAD;GAAgB;GAAS,WAAU;aAAnC,CACG,GACA,KAAY,kBAAC,QAAD;IAAM,WAAU;cAAgC;IAAQ,CAAA,CAC/D;;EACP;EACA,IACC,kBAAC,KAAD;GAAG,WAAU;aAAkC;GAAU,CAAA,GACvD,IACF,kBAAC,KAAD;GAAG,WAAU;aAA+B;GAAS,CAAA,GACnD;EACA;IAGF,IACJ,sMAeW,KAAiC,EAC5C,OACA,UACA,UACA,aACA,gBACA,aACA,SACA,UACA,UAAO,QACP,mBAEA,kBAAC,GAAD;CACS;CACP,SAAS;CACC;CACJ;CACC;CACI;WAEX,kBAAC,SAAD;EACM;EACE;EACC;EACM;EACH;EACV,WAAW,MAAqC,EAAS,EAAE,OAAO,MAAM;EACxE,WAAW,EAAG,GAAa,KAAS,2BAA2B;EAC/D,CAAA;CACS,CAAA,EAgBF,KAAyC,EACpD,OACA,UACA,UACA,aACA,gBACA,aACA,SACA,UACA,UAAO,GACP,mBAEA,kBAAC,GAAD;CACS;CACP,SAAS;CACC;CACJ;CACC;CACI;WAEX,kBAAC,YAAD;EACM;EACG;EACM;EACH;EACJ;EACN,WAAW,MAAwC,EAAS,EAAE,OAAO,MAAM;EAC3E,WAAW,EAAG,GAAa,YAAY,KAAS,2BAA2B;EAC3E,CAAA;CACS,CAAA,
|
|
1
|
+
{"version":3,"file":"Field.js","names":[],"sources":["../../../src/coolandlovely/components/Field.tsx"],"sourcesContent":["/**\n * Form field primitives\n * @module coolandlovely/components\n *\n * Small, strictly-typed, token-only form controls used across the create/edit\n * forms (no third-party form lib — these wrap native inputs with consistent\n * labels, hints and error styling).\n */\n\nimport type { FC, ReactNode, ChangeEvent } from 'react';\nimport { cn } from '../utils/cn';\n\ninterface FieldShellProps {\n label: string;\n htmlFor?: string;\n required?: boolean;\n hint?: string;\n error?: string;\n children: ReactNode;\n className?: string;\n}\n\nexport const FieldShell: FC<FieldShellProps> = ({\n label,\n htmlFor,\n required,\n hint,\n error,\n children,\n className,\n}) => (\n <div className={cn('flex flex-col gap-1.5', className)}>\n <label htmlFor={htmlFor} className=\"text-sm font-medium text-text-primary\">\n {label}\n {required && <span className=\"ml-0.5 text-status-error-text\">*</span>}\n </label>\n {children}\n {error ? (\n <p className=\"text-xs text-status-error-text\">{error}</p>\n ) : hint ? (\n <p className=\"text-xs text-text-secondary\">{hint}</p>\n ) : null}\n </div>\n);\n\nconst baseControl =\n 'w-full rounded-lg border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\n\ninterface TextFieldProps {\n id?: string;\n label: string;\n value: string;\n onChange: (value: string) => void;\n placeholder?: string;\n required?: boolean;\n hint?: string;\n error?: string;\n type?: 'text' | 'url' | 'number' | 'date';\n className?: string;\n}\n\nexport const TextField: FC<TextFieldProps> = ({\n id,\n label,\n value,\n onChange,\n placeholder,\n required,\n hint,\n error,\n type = 'text',\n className,\n}) => (\n <FieldShell\n label={label}\n htmlFor={id}\n required={required}\n hint={hint}\n error={error}\n className={className}\n >\n <input\n id={id}\n type={type}\n value={value}\n placeholder={placeholder}\n required={required}\n onChange={(e: ChangeEvent<HTMLInputElement>) => onChange(e.target.value)}\n className={cn(baseControl, error && 'border-status-error-text')}\n />\n </FieldShell>\n);\n\ninterface TextAreaFieldProps {\n id?: string;\n label: string;\n value: string;\n onChange: (value: string) => void;\n placeholder?: string;\n required?: boolean;\n hint?: string;\n error?: string;\n rows?: number;\n className?: string;\n}\n\nexport const TextAreaField: FC<TextAreaFieldProps> = ({\n id,\n label,\n value,\n onChange,\n placeholder,\n required,\n hint,\n error,\n rows = 3,\n className,\n}) => (\n <FieldShell\n label={label}\n htmlFor={id}\n required={required}\n hint={hint}\n error={error}\n className={className}\n >\n <textarea\n id={id}\n value={value}\n placeholder={placeholder}\n required={required}\n rows={rows}\n onChange={(e: ChangeEvent<HTMLTextAreaElement>) => onChange(e.target.value)}\n className={cn(baseControl, 'resize-y', error && 'border-status-error-text')}\n />\n </FieldShell>\n);\n\ninterface ToggleFieldProps {\n id?: string;\n label: string;\n checked: boolean;\n onChange: (checked: boolean) => void;\n hint?: string;\n disabled?: boolean;\n className?: string;\n}\n\n/** A token-only checkbox row (used for boolean flags e.g. Featured). */\nexport const ToggleField: FC<ToggleFieldProps> = ({\n id,\n label,\n checked,\n onChange,\n hint,\n disabled,\n className,\n}) => (\n <label\n htmlFor={id}\n className={cn(\n 'flex cursor-pointer items-start gap-3 rounded-lg border border-border-seam bg-bg-surface px-3 py-2.5',\n disabled && 'cursor-not-allowed opacity-60',\n className\n )}\n >\n <input\n id={id}\n type=\"checkbox\"\n checked={checked}\n disabled={disabled}\n onChange={(e: ChangeEvent<HTMLInputElement>) => onChange(e.target.checked)}\n className=\"mt-0.5 h-4 w-4 shrink-0 rounded border-border-seam text-action-primary-bg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n />\n <span className=\"min-w-0\">\n <span className=\"block text-sm font-medium text-text-primary\">{label}</span>\n {hint && <span className=\"mt-0.5 block text-xs text-text-secondary\">{hint}</span>}\n </span>\n </label>\n);\n\ninterface SelectOption {\n value: string;\n label: string;\n}\n\ninterface SelectFieldProps {\n id?: string;\n label: string;\n value: string;\n onChange: (value: string) => void;\n options: SelectOption[];\n required?: boolean;\n hint?: string;\n error?: string;\n placeholder?: string;\n className?: string;\n}\n\nexport const SelectField: FC<SelectFieldProps> = ({\n id,\n label,\n value,\n onChange,\n options,\n required,\n hint,\n error,\n placeholder,\n className,\n}) => (\n <FieldShell\n label={label}\n htmlFor={id}\n required={required}\n hint={hint}\n error={error}\n className={className}\n >\n <select\n id={id}\n value={value}\n required={required}\n onChange={(e: ChangeEvent<HTMLSelectElement>) => onChange(e.target.value)}\n className={cn(baseControl, error && 'border-status-error-text')}\n >\n {placeholder && <option value=\"\">{placeholder}</option>}\n {options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </FieldShell>\n);\n"],"mappings":";;;AAsBA,IAAa,KAAmC,EAC9C,UACA,YACA,aACA,SACA,UACA,aACA,mBAEA,kBAAC,OAAD;CAAK,WAAW,EAAG,yBAAyB,EAAU;WAAtD;EACE,kBAAC,SAAD;GAAgB;GAAS,WAAU;aAAnC,CACG,GACA,KAAY,kBAAC,QAAD;IAAM,WAAU;cAAgC;IAAQ,CAAA,CAC/D;;EACP;EACA,IACC,kBAAC,KAAD;GAAG,WAAU;aAAkC;GAAU,CAAA,GACvD,IACF,kBAAC,KAAD;GAAG,WAAU;aAA+B;GAAS,CAAA,GACnD;EACA;IAGF,IACJ,sMAeW,KAAiC,EAC5C,OACA,UACA,UACA,aACA,gBACA,aACA,SACA,UACA,UAAO,QACP,mBAEA,kBAAC,GAAD;CACS;CACP,SAAS;CACC;CACJ;CACC;CACI;WAEX,kBAAC,SAAD;EACM;EACE;EACC;EACM;EACH;EACV,WAAW,MAAqC,EAAS,EAAE,OAAO,MAAM;EACxE,WAAW,EAAG,GAAa,KAAS,2BAA2B;EAC/D,CAAA;CACS,CAAA,EAgBF,KAAyC,EACpD,OACA,UACA,UACA,aACA,gBACA,aACA,SACA,UACA,UAAO,GACP,mBAEA,kBAAC,GAAD;CACS;CACP,SAAS;CACC;CACJ;CACC;CACI;WAEX,kBAAC,YAAD;EACM;EACG;EACM;EACH;EACJ;EACN,WAAW,MAAwC,EAAS,EAAE,OAAO,MAAM;EAC3E,WAAW,EAAG,GAAa,YAAY,KAAS,2BAA2B;EAC3E,CAAA;CACS,CAAA,EAcF,KAAqC,EAChD,OACA,UACA,YACA,aACA,SACA,aACA,mBAEA,kBAAC,SAAD;CACE,SAAS;CACT,WAAW,EACT,wGACA,KAAY,iCACZ,EACD;WANH,CAQE,kBAAC,SAAD;EACM;EACJ,MAAK;EACI;EACC;EACV,WAAW,MAAqC,EAAS,EAAE,OAAO,QAAQ;EAC1E,WAAU;EACV,CAAA,EACF,kBAAC,QAAD;EAAM,WAAU;YAAhB,CACE,kBAAC,QAAD;GAAM,WAAU;aAA+C;GAAa,CAAA,EAC3E,KAAQ,kBAAC,QAAD;GAAM,WAAU;aAA4C;GAAY,CAAA,CAC5E;IACD;IAqBG,KAAqC,EAChD,OACA,UACA,UACA,aACA,YACA,aACA,SACA,UACA,gBACA,mBAEA,kBAAC,GAAD;CACS;CACP,SAAS;CACC;CACJ;CACC;CACI;WAEX,kBAAC,UAAD;EACM;EACG;EACG;EACV,WAAW,MAAsC,EAAS,EAAE,OAAO,MAAM;EACzE,WAAW,EAAG,GAAa,KAAS,2BAA2B;YALjE,CAOG,KAAe,kBAAC,UAAD;GAAQ,OAAM;aAAI;GAAqB,CAAA,EACtD,EAAQ,KAAK,MACZ,kBAAC,UAAD;GAAwB,OAAO,EAAI;aAChC,EAAI;GACE,EAFI,EAAI,MAER,CACT,CACK;;CACE,CAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { cn as e } from "../utils/cn.js";
|
|
2
|
+
import { useFileUrl as t } from "../hooks/useFiles.js";
|
|
3
|
+
import { jsx as n } from "react/jsx-runtime";
|
|
4
|
+
//#region src/coolandlovely/components/FileImage.tsx
|
|
5
|
+
var r = ({ fileId: r, alt: i = "", className: a }) => {
|
|
6
|
+
let { data: o } = t(r);
|
|
7
|
+
return !r || !o ? null : /* @__PURE__ */ n("img", {
|
|
8
|
+
src: o,
|
|
9
|
+
alt: i,
|
|
10
|
+
loading: "lazy",
|
|
11
|
+
className: e("h-full w-full object-cover", a)
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
//#endregion
|
|
15
|
+
export { r as FileImage };
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=FileImage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileImage.js","names":[],"sources":["../../../src/coolandlovely/components/FileImage.tsx"],"sourcesContent":["/**\n * FileImage — render a wspace-files File id as an <img>.\n * @module coolandlovely/components\n *\n * Resolves the id to a short-lived presigned URL (useFileUrl, 6h TTL). Renders\n * nothing until resolved so callers can lay a glyph fallback underneath (no\n * broken <img>, no layout shift).\n */\n\nimport type { FC } from 'react';\nimport { useFileUrl } from '../hooks/useFiles';\nimport { cn } from '../utils/cn';\n\ninterface FileImageProps {\n fileId?: string | null;\n alt?: string;\n className?: string;\n}\n\nexport const FileImage: FC<FileImageProps> = ({ fileId, alt = '', className }) => {\n const { data: url } = useFileUrl(fileId);\n if (!fileId || !url) return null;\n return <img src={url} alt={alt} loading=\"lazy\" className={cn('h-full w-full object-cover', className)} />;\n};\n"],"mappings":";;;;AAmBA,IAAa,KAAiC,EAAE,WAAQ,SAAM,IAAI,mBAAgB;CAChF,IAAM,EAAE,MAAM,MAAQ,EAAW,EAAO;AAExC,QADI,CAAC,KAAU,CAAC,IAAY,OACrB,kBAAC,OAAD;EAAK,KAAK;EAAU;EAAK,SAAQ;EAAO,WAAW,EAAG,8BAA8B,EAAU;EAAI,CAAA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { cn as e } from "../utils/cn.js";
|
|
2
|
+
import { useFollow as t, useIsFollowing as n, useUnfollow as r } from "../hooks/useCoolAndLovelyApi.js";
|
|
3
|
+
import { Loader2 as i, UserCheck as a, UserPlus as o } from "lucide-react";
|
|
4
|
+
import { jsx as s, jsxs as c } from "react/jsx-runtime";
|
|
5
|
+
//#region src/coolandlovely/components/FollowButton.tsx
|
|
6
|
+
var l = ({ followeeId: l, className: u, size: d = "md" }) => {
|
|
7
|
+
let f = n(l ?? void 0), p = t(), m = r();
|
|
8
|
+
if (!l) return null;
|
|
9
|
+
let h = f.data ?? !1, g = p.isPending || m.isPending || f.isLoading;
|
|
10
|
+
return /* @__PURE__ */ c("button", {
|
|
11
|
+
type: "button",
|
|
12
|
+
onClick: () => {
|
|
13
|
+
l && (h ? m.mutate(l) : p.mutate(l));
|
|
14
|
+
},
|
|
15
|
+
disabled: g,
|
|
16
|
+
"aria-pressed": h,
|
|
17
|
+
className: e("inline-flex items-center gap-1.5 rounded-lg font-medium transition-colors disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]", d === "sm" ? "px-2.5 py-1 text-xs" : "px-3 py-2 text-sm", h ? "border border-border-seam text-text-primary hover:bg-bg-sunken" : "bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90", u),
|
|
18
|
+
children: [g ? /* @__PURE__ */ s(i, { className: e("animate-spin", d === "sm" ? "h-3.5 w-3.5" : "h-4 w-4") }) : s(h ? a : o, { className: d === "sm" ? "h-3.5 w-3.5" : "h-4 w-4" }), h ? "Following" : "Follow"]
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
//#endregion
|
|
22
|
+
export { l as FollowButton };
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=FollowButton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FollowButton.js","names":[],"sources":["../../../src/coolandlovely/components/FollowButton.tsx"],"sourcesContent":["/**\n * FollowButton — follow / unfollow a user actor (the follow graph).\n * @module coolandlovely/components\n *\n * Backed by cooFollow / cooUnfollow / cooIsFollowing (added this release). The\n * follow target is an actor id (a look's authorId, a style-profile owner, …).\n * Self-follow is impossible on the server; callers should not render this for\n * their own id.\n */\n\nimport type { FC } from 'react';\nimport { Loader2, UserCheck, UserPlus } from 'lucide-react';\nimport { useFollow, useIsFollowing, useUnfollow } from '../hooks/useCoolAndLovelyApi';\nimport { cn } from '../utils/cn';\n\ninterface FollowButtonProps {\n /** Actor id to follow/unfollow. */\n followeeId?: string | null;\n className?: string;\n size?: 'sm' | 'md';\n}\n\nexport const FollowButton: FC<FollowButtonProps> = ({ followeeId, className, size = 'md' }) => {\n const following = useIsFollowing(followeeId ?? undefined);\n const follow = useFollow();\n const unfollow = useUnfollow();\n\n if (!followeeId) return null;\n\n const isFollowing = following.data ?? false;\n const pending = follow.isPending || unfollow.isPending || following.isLoading;\n\n const handleClick = () => {\n if (!followeeId) return;\n if (isFollowing) {\n unfollow.mutate(followeeId);\n } else {\n follow.mutate(followeeId);\n }\n };\n\n const sizing = size === 'sm' ? 'px-2.5 py-1 text-xs' : 'px-3 py-2 text-sm';\n\n return (\n <button\n type=\"button\"\n onClick={handleClick}\n disabled={pending}\n aria-pressed={isFollowing}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-lg font-medium transition-colors disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]',\n sizing,\n isFollowing\n ? 'border border-border-seam text-text-primary hover:bg-bg-sunken'\n : 'bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90',\n className\n )}\n >\n {pending ? (\n <Loader2 className={cn('animate-spin', size === 'sm' ? 'h-3.5 w-3.5' : 'h-4 w-4')} />\n ) : isFollowing ? (\n <UserCheck className={size === 'sm' ? 'h-3.5 w-3.5' : 'h-4 w-4'} />\n ) : (\n <UserPlus className={size === 'sm' ? 'h-3.5 w-3.5' : 'h-4 w-4'} />\n )}\n {isFollowing ? 'Following' : 'Follow'}\n </button>\n );\n};\n"],"mappings":";;;;;AAsBA,IAAa,KAAuC,EAAE,eAAY,cAAW,UAAO,WAAW;CAC7F,IAAM,IAAY,EAAe,KAAc,KAAA,EAAU,EACnD,IAAS,GAAW,EACpB,IAAW,GAAa;AAE9B,KAAI,CAAC,EAAY,QAAO;CAExB,IAAM,IAAc,EAAU,QAAQ,IAChC,IAAU,EAAO,aAAa,EAAS,aAAa,EAAU;AAapE,QACE,kBAAC,UAAD;EACE,MAAK;EACL,eAdsB;AACnB,SACD,IACF,EAAS,OAAO,EAAW,GAE3B,EAAO,OAAO,EAAW;;EAUzB,UAAU;EACV,gBAAc;EACd,WAAW,EACT,8LATS,MAAS,OAAO,wBAAwB,qBAWjD,IACI,mEACA,+EACJ,EACD;YAZH,CAcG,IACC,kBAAC,GAAD,EAAS,WAAW,EAAG,gBAAgB,MAAS,OAAO,gBAAgB,UAAU,EAAI,CAAA,GAErF,EADE,IACD,IAEA,GAFD,EAAW,WAAW,MAAS,OAAO,gBAAgB,WAAa,CAED,EAEnE,IAAc,cAAc,SACtB"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { useCoolAndLovely as e } from "../context/CoolAndLovelyContext.js";
|
|
2
|
+
import { cn as t } from "../utils/cn.js";
|
|
3
|
+
import { uploadImageToFiles as n, useFileUrl as r } from "../hooks/useFiles.js";
|
|
4
|
+
import { FieldShell as i } from "./Field.js";
|
|
5
|
+
import { useId as a, useRef as o, useState as s } from "react";
|
|
6
|
+
import { ImagePlus as c, Loader2 as l, X as u } from "lucide-react";
|
|
7
|
+
import { jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
8
|
+
//#region src/coolandlovely/components/ImageUploadField.tsx
|
|
9
|
+
var p = ({ label: p, value: m, onChange: h, folder: g, hint: _, accept: v = "image/*", disabled: y = !1, className: b, aspect: x = "square" }) => {
|
|
10
|
+
let { apiGatewayUrl: S } = e(), C = o(null), w = a(), [T, E] = s(!1), [D, O] = s(), k = r(T ? void 0 : m || void 0).data ?? null, A = async (e) => {
|
|
11
|
+
let t = e.target.files?.[0];
|
|
12
|
+
if (e.target.value = "", t) {
|
|
13
|
+
E(!0), O(void 0);
|
|
14
|
+
try {
|
|
15
|
+
h((await n(t, S, g)).id);
|
|
16
|
+
} catch (e) {
|
|
17
|
+
O(e instanceof Error ? e.message : "Upload failed — please try again.");
|
|
18
|
+
} finally {
|
|
19
|
+
E(!1);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}, j = y || T;
|
|
23
|
+
return /* @__PURE__ */ f(i, {
|
|
24
|
+
label: p,
|
|
25
|
+
htmlFor: w,
|
|
26
|
+
hint: _,
|
|
27
|
+
error: D,
|
|
28
|
+
className: b,
|
|
29
|
+
children: [/* @__PURE__ */ f("div", {
|
|
30
|
+
className: "flex items-center gap-4",
|
|
31
|
+
children: [/* @__PURE__ */ d("div", {
|
|
32
|
+
className: t("relative flex shrink-0 items-center justify-center overflow-hidden rounded-lg border border-border-seam bg-bg-sunken", x === "wide" ? "h-20 w-32" : "h-20 w-20"),
|
|
33
|
+
children: k ? /* @__PURE__ */ d("img", {
|
|
34
|
+
src: k,
|
|
35
|
+
alt: "",
|
|
36
|
+
className: "h-full w-full object-cover"
|
|
37
|
+
}) : T ? /* @__PURE__ */ d(l, { className: "h-5 w-5 animate-spin text-text-secondary" }) : /* @__PURE__ */ d(c, {
|
|
38
|
+
className: "h-6 w-6 text-text-secondary",
|
|
39
|
+
"aria-hidden": "true"
|
|
40
|
+
})
|
|
41
|
+
}), /* @__PURE__ */ f("div", {
|
|
42
|
+
className: "flex items-center gap-2",
|
|
43
|
+
children: [/* @__PURE__ */ f("button", {
|
|
44
|
+
type: "button",
|
|
45
|
+
disabled: j,
|
|
46
|
+
onClick: () => C.current?.click(),
|
|
47
|
+
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 disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]",
|
|
48
|
+
children: [T ? /* @__PURE__ */ d(l, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ d(c, { className: "h-4 w-4" }), T ? "Uploading…" : m ? "Replace" : "Upload image"]
|
|
49
|
+
}), m && !T ? /* @__PURE__ */ f("button", {
|
|
50
|
+
type: "button",
|
|
51
|
+
disabled: y,
|
|
52
|
+
onClick: () => h(""),
|
|
53
|
+
className: "inline-flex items-center gap-1 rounded-lg px-2 py-2 text-sm text-text-secondary transition-colors hover:bg-status-error-bg-subtle hover:text-status-error-text disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]",
|
|
54
|
+
children: [/* @__PURE__ */ d(u, { className: "h-4 w-4" }), " Remove"]
|
|
55
|
+
}) : null]
|
|
56
|
+
})]
|
|
57
|
+
}), /* @__PURE__ */ d("input", {
|
|
58
|
+
ref: C,
|
|
59
|
+
id: w,
|
|
60
|
+
type: "file",
|
|
61
|
+
accept: v,
|
|
62
|
+
className: "sr-only",
|
|
63
|
+
"aria-label": p,
|
|
64
|
+
onChange: (e) => void A(e)
|
|
65
|
+
})]
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
//#endregion
|
|
69
|
+
export { p as ImageUploadField };
|
|
70
|
+
|
|
71
|
+
//# sourceMappingURL=ImageUploadField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageUploadField.js","names":[],"sources":["../../../src/coolandlovely/components/ImageUploadField.tsx"],"sourcesContent":["/**\n * ImageUploadField — image upload for a coo* entity, backed by wspace-files-svc.\n * @module coolandlovely/components\n *\n * Controlled by the current File id (`value`). On select it runs the shared\n * presigned upload (initiateUpload -> PUT -> completeUpload) and hands the new\n * File id back via `onChange`. The caller persists it (e.g. logoFileId). Preview\n * resolves the id -> presigned URL via useFileUrl. Clearing sends onChange('').\n *\n * Token-only, no Apollo, no toast dependency — surfaces its own error inline to\n * match the existing form error style.\n */\n\nimport type { FC } from 'react';\nimport { useId, useRef, useState } from 'react';\nimport { ImagePlus, Loader2, X } from 'lucide-react';\nimport { FieldShell } from './Field';\nimport { useCoolAndLovely } from '../context/CoolAndLovelyContext';\nimport { uploadImageToFiles, useFileUrl } from '../hooks/useFiles';\nimport { cn } from '../utils/cn';\n\ninterface ImageUploadFieldProps {\n label: string;\n /** Current wspace-files File id, or '' / undefined when none. */\n value?: string | null;\n /** Called with the new File id on success, or '' when cleared. */\n onChange: (fileId: string) => void;\n /** Destination folder in wspace-files (see COO_FILE_FOLDERS). */\n folder?: string;\n hint?: string;\n /** `accept` for the input; images only by default. */\n accept?: string;\n disabled?: boolean;\n className?: string;\n /** Aspect of the preview tile. */\n aspect?: 'square' | 'wide';\n}\n\nexport const ImageUploadField: FC<ImageUploadFieldProps> = ({\n label,\n value,\n onChange,\n folder,\n hint,\n accept = 'image/*',\n disabled = false,\n className,\n aspect = 'square',\n}) => {\n const { apiGatewayUrl } = useCoolAndLovely();\n const inputRef = useRef<HTMLInputElement | null>(null);\n const inputId = useId();\n const [uploading, setUploading] = useState(false);\n const [error, setError] = useState<string | undefined>();\n\n // While uploading we skip resolution; the preview shows after onChange lands.\n const resolved = useFileUrl(uploading ? undefined : value || undefined);\n const previewUrl = resolved.data ?? null;\n\n const handleSelect = async (event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0];\n event.target.value = ''; // allow re-selecting the same file\n if (!file) return;\n setUploading(true);\n setError(undefined);\n try {\n const uploaded = await uploadImageToFiles(file, apiGatewayUrl, folder);\n onChange(uploaded.id);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'Upload failed — please try again.');\n } finally {\n setUploading(false);\n }\n };\n\n const locked = disabled || uploading;\n\n return (\n <FieldShell label={label} htmlFor={inputId} hint={hint} error={error} className={className}>\n <div className=\"flex items-center gap-4\">\n <div\n className={cn(\n 'relative flex shrink-0 items-center justify-center overflow-hidden rounded-lg border border-border-seam bg-bg-sunken',\n aspect === 'wide' ? 'h-20 w-32' : 'h-20 w-20'\n )}\n >\n {previewUrl ? (\n <img src={previewUrl} alt=\"\" className=\"h-full w-full object-cover\" />\n ) : uploading ? (\n <Loader2 className=\"h-5 w-5 animate-spin text-text-secondary\" />\n ) : (\n <ImagePlus className=\"h-6 w-6 text-text-secondary\" aria-hidden=\"true\" />\n )}\n </div>\n\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n disabled={locked}\n onClick={() => inputRef.current?.click()}\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 disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n {uploading ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <ImagePlus className=\"h-4 w-4\" />\n )}\n {uploading ? 'Uploading…' : value ? 'Replace' : 'Upload image'}\n </button>\n {value && !uploading ? (\n <button\n type=\"button\"\n disabled={disabled}\n onClick={() => onChange('')}\n className=\"inline-flex items-center gap-1 rounded-lg px-2 py-2 text-sm text-text-secondary transition-colors hover:bg-status-error-bg-subtle hover:text-status-error-text disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <X className=\"h-4 w-4\" /> Remove\n </button>\n ) : null}\n </div>\n </div>\n <input\n ref={inputRef}\n id={inputId}\n type=\"file\"\n accept={accept}\n className=\"sr-only\"\n aria-label={label}\n onChange={(e) => void handleSelect(e)}\n />\n </FieldShell>\n );\n};\n"],"mappings":";;;;;;;;AAsCA,IAAa,KAA+C,EAC1D,UACA,UACA,aACA,WACA,SACA,YAAS,WACT,cAAW,IACX,cACA,YAAS,eACL;CACJ,IAAM,EAAE,qBAAkB,GAAkB,EACtC,IAAW,EAAgC,KAAK,EAChD,IAAU,GAAO,EACjB,CAAC,GAAW,KAAgB,EAAS,GAAM,EAC3C,CAAC,GAAO,KAAY,GAA8B,EAIlD,IADW,EAAW,IAAY,KAAA,IAAY,KAAS,KAAA,EAAU,CAC3C,QAAQ,MAE9B,IAAe,OAAO,MAA+C;EACzE,IAAM,IAAO,EAAM,OAAO,QAAQ;AAClC,QAAM,OAAO,QAAQ,IAChB,GAEL;GADA,EAAa,GAAK,EAClB,EAAS,KAAA,EAAU;AACnB,OAAI;AAEF,OADiB,MAAM,EAAmB,GAAM,GAAe,EAAO,EACpD,GAAG;YACd,GAAG;AACV,MAAS,aAAa,QAAQ,EAAE,UAAU,oCAAoC;aACtE;AACR,MAAa,GAAM;;;IAIjB,IAAS,KAAY;AAE3B,QACE,kBAAC,GAAD;EAAmB;EAAO,SAAS;EAAe;EAAa;EAAkB;YAAjF,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IACE,WAAW,EACT,wHACA,MAAW,SAAS,cAAc,YACnC;cAEA,IACC,kBAAC,OAAD;KAAK,KAAK;KAAY,KAAI;KAAG,WAAU;KAA+B,CAAA,GACpE,IACF,kBAAC,GAAD,EAAS,WAAU,4CAA6C,CAAA,GAEhE,kBAAC,GAAD;KAAW,WAAU;KAA8B,eAAY;KAAS,CAAA;IAEtE,CAAA,EAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,UAAD;KACE,MAAK;KACL,UAAU;KACV,eAAe,EAAS,SAAS,OAAO;KACxC,WAAU;eAJZ,CAMG,IACC,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,GAE5C,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA,EAElC,IAAY,eAAe,IAAQ,YAAY,eACzC;QACR,KAAS,CAAC,IACT,kBAAC,UAAD;KACE,MAAK;KACK;KACV,eAAe,EAAS,GAAG;KAC3B,WAAU;eAJZ,CAME,kBAAC,GAAD,EAAG,WAAU,WAAY,CAAA,EAAA,UAClB;SACP,KACA;MACF;MACN,kBAAC,SAAD;GACE,KAAK;GACL,IAAI;GACJ,MAAK;GACG;GACR,WAAU;GACV,cAAY;GACZ,WAAW,MAAM,KAAK,EAAa,EAAE;GACrC,CAAA,CACS"}
|