@burdenoff/microfe-coolandlovely 2026.720.2 → 2026.801.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/coolandlovely/CoolAndLovelyRoutes.js +1 -1
- package/dist/coolandlovely/CoolAndLovelyRoutes.js.map +1 -1
- package/dist/coolandlovely/context/CoolAndLovelyContext.js +1 -1
- package/dist/coolandlovely/context/CoolAndLovelyContext.js.map +1 -1
- package/dist/coolandlovely/pages/brands/BrandDetailPage.js +1 -1
- package/dist/coolandlovely/pages/brands/BrandDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/brands/BrandFormPage.js +1 -1
- package/dist/coolandlovely/pages/brands/BrandFormPage.js.map +1 -1
- package/dist/coolandlovely/pages/collaborations/CollaborationDetailPage.js +1 -1
- package/dist/coolandlovely/pages/collaborations/CollaborationDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/collaborations/CollaborationFormPage.js +1 -1
- package/dist/coolandlovely/pages/collaborations/CollaborationFormPage.js.map +1 -1
- package/dist/coolandlovely/pages/collections/CollectionDetailPage.js +1 -1
- package/dist/coolandlovely/pages/collections/CollectionDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/collections/CollectionFormPage.js +1 -1
- package/dist/coolandlovely/pages/collections/CollectionFormPage.js.map +1 -1
- package/dist/coolandlovely/pages/items/StyleItemDetailPage.js +1 -1
- package/dist/coolandlovely/pages/items/StyleItemDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/items/StyleItemFormPage.js +1 -1
- package/dist/coolandlovely/pages/items/StyleItemFormPage.js.map +1 -1
- package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js +1 -1
- package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/looks/LookDetailPage.js +1 -1
- package/dist/coolandlovely/pages/looks/LookDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/profile/StyleProfilePage.js +1 -1
- package/dist/coolandlovely/pages/profile/StyleProfilePage.js.map +1 -1
- package/package.json +7 -7
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCoolAndLovely as e } from "./context/CoolAndLovelyContext.js";
|
|
2
2
|
import { HomePage as t } from "./pages/HomePage.js";
|
|
3
3
|
import { Suspense as n, lazy as r } from "react";
|
|
4
|
-
import { Navigate as i, Route as a, Routes as o, useLocation as s } from "react-router
|
|
4
|
+
import { Navigate as i, Route as a, Routes as o, useLocation as s } from "react-router";
|
|
5
5
|
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
6
6
|
//#region src/coolandlovely/CoolAndLovelyRoutes.tsx
|
|
7
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", {
|
|
@@ -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\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 <Route path=\"collaborations/:collaborationId/edit\" element={<CollaborationFormPage />} />\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) + public profiles */}\n <Route path=\"profile\" element={<StyleProfilePage />} />\n <Route path=\"profile/:userId\" 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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"}
|
|
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';\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 <Route path=\"collaborations/:collaborationId/edit\" element={<CollaborationFormPage />} />\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) + public profiles */}\n <Route path=\"profile\" element={<StyleProfilePage />} />\n <Route path=\"profile/:userId\" 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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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { joinPath as e } from "../utils/navigation.js";
|
|
2
2
|
import { createContext as t, useCallback as n, useContext as r, useMemo as i } from "react";
|
|
3
|
-
import { MemoryRouter as a, useNavigate as o } from "react-router
|
|
3
|
+
import { MemoryRouter as a, useNavigate as o } from "react-router";
|
|
4
4
|
import { jsx as s } from "react/jsx-runtime";
|
|
5
5
|
//#region src/coolandlovely/context/CoolAndLovelyContext.tsx
|
|
6
6
|
var c = t(null);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CoolAndLovelyContext.js","names":[],"sources":["../../../src/coolandlovely/context/CoolAndLovelyContext.tsx"],"sourcesContent":["/**\n * CoolAndLovely Context Provider\n * @module coolandlovely/context\n */\n\nimport type { FC, PropsWithChildren } from 'react';\nimport { createContext, useContext, useMemo, useCallback } from 'react';\nimport { MemoryRouter, useNavigate as useRouterNavigate } from 'react-router
|
|
1
|
+
{"version":3,"file":"CoolAndLovelyContext.js","names":[],"sources":["../../../src/coolandlovely/context/CoolAndLovelyContext.tsx"],"sourcesContent":["/**\n * CoolAndLovely Context Provider\n * @module coolandlovely/context\n */\n\nimport type { FC, PropsWithChildren } from 'react';\nimport { createContext, useContext, useMemo, useCallback } from 'react';\nimport { MemoryRouter, useNavigate as useRouterNavigate } from 'react-router';\nimport type { CoolAndLovelyRootProps } from '../types';\nimport { joinPath } from '../utils/navigation';\n\n// ============================================================================\n// Context\n// ============================================================================\n\ninterface CoolAndLovelyContextValue extends Omit<\n CoolAndLovelyRootProps,\n 'registerNavItems' | 'registerFooterItems' | 'navigate'\n> {\n isStandalone: boolean;\n /** Shell's navigate function (only available in integrated mode) */\n navigate?: (path: string) => void;\n /** Navigate to a path relative to basePath */\n navigateTo: (relativePath: string) => void;\n /** Get the full path for a relative path */\n getPath: (relativePath: string) => string;\n}\n\nconst CoolAndLovelyContext = createContext<CoolAndLovelyContextValue | null>(null);\n\n// ============================================================================\n// Hook\n// ============================================================================\n\nexport function useCoolAndLovely(): CoolAndLovelyContextValue {\n const context = useContext(CoolAndLovelyContext);\n if (!context) {\n throw new Error('useCoolAndLovely must be used within a CoolAndLovelyProvider');\n }\n return context;\n}\n\n// ============================================================================\n// Provider Component\n// ============================================================================\n\ntype ProviderProps = Omit<CoolAndLovelyRootProps, 'registerNavItems' | 'registerFooterItems'> & {\n isStandalone: boolean;\n};\n\n/** Provider for standalone mode (has access to MemoryRouter) */\nconst StandaloneProviderInner: FC<PropsWithChildren<ProviderProps>> = ({\n children,\n basePath,\n currentUser,\n tenantId,\n workspaceId,\n organizationId,\n apiGatewayUrl,\n authToken,\n workspaceToken,\n}) => {\n const routerNavigate = useRouterNavigate();\n const effectiveBasePath = basePath || '/';\n\n const getPath = useCallback(\n (relativePath: string) => joinPath(effectiveBasePath, relativePath),\n [effectiveBasePath]\n );\n\n const navigateTo = useCallback(\n (relativePath: string) => {\n const fullPath = joinPath(effectiveBasePath, relativePath);\n routerNavigate(fullPath);\n },\n [effectiveBasePath, routerNavigate]\n );\n\n const contextValue = useMemo<CoolAndLovelyContextValue>(\n () => ({\n basePath: effectiveBasePath,\n navigate: undefined,\n currentUser,\n tenantId,\n workspaceId,\n organizationId,\n apiGatewayUrl,\n authToken,\n workspaceToken,\n isStandalone: true,\n navigateTo,\n getPath,\n }),\n [\n effectiveBasePath,\n currentUser,\n tenantId,\n workspaceId,\n organizationId,\n apiGatewayUrl,\n authToken,\n workspaceToken,\n navigateTo,\n getPath,\n ]\n );\n\n return (\n <CoolAndLovelyContext.Provider value={contextValue}>{children}</CoolAndLovelyContext.Provider>\n );\n};\n\n/** Provider for integrated mode (shell provides navigate) */\nconst IntegratedProviderInner: FC<\n PropsWithChildren<ProviderProps & { navigate: (path: string) => void }>\n> = ({\n children,\n basePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n organizationId,\n apiGatewayUrl,\n authToken,\n workspaceToken,\n}) => {\n const effectiveBasePath = basePath || '/';\n\n const getPath = useCallback(\n (relativePath: string) => joinPath(effectiveBasePath, relativePath),\n [effectiveBasePath]\n );\n\n const navigateTo = useCallback(\n (relativePath: string) => {\n const fullPath = joinPath(effectiveBasePath, relativePath);\n navigate(fullPath);\n },\n [effectiveBasePath, navigate]\n );\n\n const contextValue = useMemo<CoolAndLovelyContextValue>(\n () => ({\n basePath: effectiveBasePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n organizationId,\n apiGatewayUrl,\n authToken,\n workspaceToken,\n isStandalone: false,\n navigateTo,\n getPath,\n }),\n [\n effectiveBasePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n organizationId,\n apiGatewayUrl,\n authToken,\n workspaceToken,\n navigateTo,\n getPath,\n ]\n );\n\n return (\n <CoolAndLovelyContext.Provider value={contextValue}>{children}</CoolAndLovelyContext.Provider>\n );\n};\n\nexport const CoolAndLovelyProvider: FC<PropsWithChildren<CoolAndLovelyRootProps>> = (props) => {\n const { children, navigate } = props;\n // Standalone mode: no navigate function provided (MFE manages its own router)\n // Integrated mode: navigate function provided by shell (shell provides router)\n const isStandalone = !navigate;\n\n if (isStandalone) {\n return (\n <MemoryRouter>\n <StandaloneProviderInner {...props} isStandalone={true}>\n {children}\n </StandaloneProviderInner>\n </MemoryRouter>\n );\n }\n\n return (\n <IntegratedProviderInner {...props} navigate={navigate} isStandalone={false}>\n {children}\n </IntegratedProviderInner>\n );\n};\n"],"mappings":";;;;;AA4BA,IAAM,IAAuB,EAAgD,KAAK;AAMlF,SAAgB,IAA8C;CAC5D,IAAM,IAAU,EAAW,EAAqB;AAChD,KAAI,CAAC,EACH,OAAU,MAAM,+DAA+D;AAEjF,QAAO;;AAYT,IAAM,KAAiE,EACrE,aACA,aACA,gBACA,aACA,gBACA,mBACA,kBACA,cACA,wBACI;CACJ,IAAM,IAAiB,GAAmB,EACpC,IAAoB,KAAY,KAEhC,IAAU,GACb,MAAyB,EAAS,GAAmB,EAAa,EACnE,CAAC,EAAkB,CACpB,EAEK,IAAa,GAChB,MAAyB;AAExB,IADiB,EAAS,GAAmB,EAAa,CAClC;IAE1B,CAAC,GAAmB,EAAe,CACpC,EAEK,IAAe,SACZ;EACL,UAAU;EACV,UAAU,KAAA;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA,cAAc;EACd;EACA;EACD,GACD;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CACF;AAED,QACE,kBAAC,EAAqB,UAAtB;EAA+B,OAAO;EAAe;EAAyC,CAAA;GAK5F,KAED,EACH,aACA,aACA,aACA,gBACA,aACA,gBACA,mBACA,kBACA,cACA,wBACI;CACJ,IAAM,IAAoB,KAAY,KAEhC,IAAU,GACb,MAAyB,EAAS,GAAmB,EAAa,EACnE,CAAC,EAAkB,CACpB,EAEK,IAAa,GAChB,MAAyB;AAExB,IADiB,EAAS,GAAmB,EAAa,CACxC;IAEpB,CAAC,GAAmB,EAAS,CAC9B,EAEK,IAAe,SACZ;EACL,UAAU;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,cAAc;EACd;EACA;EACD,GACD;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CACF;AAED,QACE,kBAAC,EAAqB,UAAtB;EAA+B,OAAO;EAAe;EAAyC,CAAA;GAIrF,KAAwE,MAAU;CAC7F,IAAM,EAAE,aAAU,gBAAa;AAe/B,QAZsB,IAapB,kBAAC,GAAD;EAAyB,GAAI;EAAiB;EAAU,cAAc;EACnE;EACuB,CAAA,GAXxB,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;EAAyB,GAAI;EAAO,cAAc;EAC/C;EACuB,CAAA,EACb,CAAA"}
|
|
@@ -12,7 +12,7 @@ import { BrandCover as m } from "../../components/BrandCover.js";
|
|
|
12
12
|
import { TagList as h } from "../../components/TagList.js";
|
|
13
13
|
import { Comments as g } from "../../components/Comments.js";
|
|
14
14
|
import { ArrowLeft as _, ExternalLink as v, Loader2 as y, Pencil as b, Plus as x, Shapes as S, Shirt as C, Store as w } from "lucide-react";
|
|
15
|
-
import { useParams as T } from "react-router
|
|
15
|
+
import { useParams as T } from "react-router";
|
|
16
16
|
import { Fragment as E, jsx as D, jsxs as O } from "react/jsx-runtime";
|
|
17
17
|
import { GlassCard as k } from "@burdenoff/fe-libs/ui";
|
|
18
18
|
//#region src/coolandlovely/pages/brands/BrandDetailPage.tsx
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BrandDetailPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/brands/BrandDetailPage.tsx"],"sourcesContent":["/**\n * Brand Detail Page\n * @module coolandlovely/pages/brands\n *\n * Renders a single brand storefront (`cooBrand`) with its publish-lifecycle\n * transitions (`cooTransitionBrand`), collections and style items.\n */\n\nimport { useParams } from 'react-router-dom';\nimport { Store, ExternalLink, Pencil, ArrowLeft, Shapes, Shirt, Loader2, Plus } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { BrandCover } from '../../components/BrandCover';\nimport { TagList } from '../../components/TagList';\nimport { Comments } from '../../components/Comments';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useBrand, useTransitionBrand, useUpdateBrand } from '../../hooks';\nimport { categoryIcon } from '../../utils/icons';\nimport { formatPrice, formatDate, titleCase } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport type { CooBrandStatus } from '../../types';\n\n/** Allowed next states per the svc brand state machine (UX guidance only;\n * the service is the authority and rejects illegal jumps). */\nconst NEXT_STATES: Record<CooBrandStatus, CooBrandStatus[]> = {\n DRAFT: ['PENDING_REVIEW', 'ARCHIVED'],\n PENDING_REVIEW: ['PUBLISHED', 'DRAFT', 'ARCHIVED'],\n PUBLISHED: ['SUSPENDED', 'ARCHIVED'],\n SUSPENDED: ['PUBLISHED', 'ARCHIVED'],\n ARCHIVED: [],\n};\n\nexport function BrandDetailPage() {\n const { brandId } = useParams<{ brandId: string }>();\n const { navigateTo } = useCoolAndLovely();\n const query = useBrand(brandId);\n const transition = useTransitionBrand();\n const updateBrand = useUpdateBrand();\n\n if (query.isLoading) {\n return (\n <PageLayout title=\"Brand\">\n <LoadingView label=\"Loading brand…\" />\n </PageLayout>\n );\n }\n\n if (query.isError || !query.data) {\n return (\n <PageLayout title=\"Brand\">\n <ErrorView\n title=\"Brand not found\"\n message=\"This storefront could not be loaded or does not exist.\"\n onRetry={() => void query.refetch()}\n />\n </PageLayout>\n );\n }\n\n const brand = query.data;\n const collections = brand.collections ?? [];\n const items = brand.items ?? [];\n const nextStates = NEXT_STATES[brand.status];\n // Only render a website link for safe http(s) URLs (block javascript:/data: sinks).\n const safeWebsiteUrl =\n brand.websiteUrl && /^https?:\\/\\//i.test(brand.websiteUrl) ? brand.websiteUrl : undefined;\n\n return (\n <PageLayout title={brand.name} description={`@${brand.handle}`}>\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Main column */}\n <div className=\"space-y-6 lg:col-span-2\">\n {/* Storefront hero — cover + logo + identity overlay, floating actions */}\n <BrandCover\n coverFileId={brand.cover?.id}\n logoFileId={brand.logo?.id}\n name={brand.name}\n seed={brand.id}\n className=\"overflow-hidden rounded-xl border border-border-seam shadow-sm\"\n overlay={\n <>\n <div className=\"absolute right-3 top-3 flex flex-wrap items-center justify-end gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/brands')}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-bg-surface/80 px-3 py-2 text-sm font-medium text-text-primary shadow-sm backdrop-blur transition-colors hover:bg-bg-surface\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/brands/${brand.id}/edit`)}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text shadow-sm transition-colors hover:bg-action-primary-bg/90\"\n >\n <Pencil className=\"h-4 w-4\" /> Edit\n </button>\n </div>\n <div className=\"absolute inset-x-0 bottom-0 flex items-end gap-3 p-4 sm:gap-4 sm:p-5\">\n <Thumbnail\n icon={<Store className=\"h-7 w-7\" />}\n seed={brand.id}\n fileId={brand.logo?.id}\n alt={`${brand.name} logo`}\n className=\"h-16 w-16 shrink-0 rounded-full shadow-md ring-2 ring-bg-surface\"\n />\n <div className=\"min-w-0 pb-1\">\n <div className=\"flex flex-wrap items-center gap-x-2 gap-y-0.5\">\n <h2 className=\"truncate text-xl font-semibold text-text-primary sm:text-2xl\">\n {brand.name}\n </h2>\n <span className=\"text-sm text-text-secondary\">@{brand.handle}</span>\n </div>\n {brand.tagline && (\n <p className=\"mt-1 line-clamp-2 text-sm text-text-primary\">{brand.tagline}</p>\n )}\n </div>\n </div>\n </>\n }\n />\n\n {/* Storefront meta — aesthetics, status, bio, tags, website */}\n <section className=\"space-y-3\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n {brand.aesthetics.length > 0 ? (\n <TagList tags={brand.aesthetics.map(titleCase)} tone=\"accent\" max={12} />\n ) : (\n <span />\n )}\n <StatusBadge status={brand.status} />\n </div>\n {brand.bio && <p className=\"text-sm text-text-secondary\">{brand.bio}</p>}\n {brand.tags.length > 0 && <TagList tags={brand.tags} max={12} />}\n {safeWebsiteUrl ? (\n <a\n href={safeWebsiteUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex max-w-full items-center gap-1.5 text-sm font-medium text-action-primary-bg hover:underline\"\n >\n <ExternalLink className=\"h-4 w-4 shrink-0\" />{' '}\n <span className=\"break-all\">{safeWebsiteUrl}</span>\n </a>\n ) : brand.websiteUrl ? (\n <p className=\"break-all text-sm text-text-secondary\">{brand.websiteUrl}</p>\n ) : null}\n </section>\n\n {/* Collections */}\n <section className=\"mt-6\">\n <div className=\"mb-3 flex items-center justify-between gap-2\">\n <h3 className=\"flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <Shapes className=\"h-5 w-5\" /> Collections ({collections.length})\n </h3>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/collections/new?brandId=${brand.id}`)}\n className=\"inline-flex shrink-0 items-center gap-1.5 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Plus className=\"h-4 w-4\" /> Add collection\n </button>\n </div>\n {collections.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n No collections yet.\n </p>\n ) : (\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n {collections.map((c) => (\n <button\n key={c.id}\n type=\"button\"\n onClick={() => navigateTo(`/collections/${c.id}`)}\n className=\"flex items-center gap-3 rounded-lg border border-border-seam bg-bg-surface p-3 text-left transition-colors hover:bg-bg-sunken/50\"\n >\n <Thumbnail\n icon={<Shapes className=\"h-5 w-5\" />}\n seed={c.id}\n fileId={c.cover?.id}\n alt={c.name}\n className=\"h-11 w-11 shrink-0\"\n />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{c.name}</p>\n <p className=\"text-xs text-text-secondary\">{titleCase(c.season)}</p>\n </div>\n <StatusBadge status={c.status} />\n </button>\n ))}\n </div>\n )}\n </section>\n\n {/* Items */}\n <section className=\"mt-6\">\n <h3 className=\"mb-3 flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <Shirt className=\"h-5 w-5\" /> Style items ({items.length})\n </h3>\n {items.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n No items yet.\n </p>\n ) : (\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-3\">\n {items.map((item) => (\n <button\n key={item.id}\n type=\"button\"\n onClick={() => navigateTo(`/items/${item.id}`)}\n className=\"overflow-hidden rounded-lg border border-border-seam bg-bg-surface text-left transition-all hover:border-accent-foreground/20 hover:shadow-md\"\n >\n <Thumbnail\n icon={categoryIcon(item.category, 24)}\n seed={item.id}\n fileId={item.image?.id}\n alt={item.name}\n className=\"h-24 w-full\"\n />\n <div className=\"p-3\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{item.name}</p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {formatPrice(item.price, item.currency)}\n </p>\n </div>\n </button>\n ))}\n </div>\n )}\n </section>\n </div>\n\n {/* Sidebar */}\n <div className=\"space-y-4\">\n <GlassCard treatment=\"insight\" className=\"p-5\">\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">Lifecycle</h3>\n <div className=\"mb-3 flex items-center gap-2\">\n <span className=\"text-sm text-text-secondary\">Current:</span>\n <StatusBadge status={brand.status} />\n </div>\n {nextStates.length === 0 ? (\n <p className=\"text-xs text-text-secondary\">\n This brand is archived (terminal state).\n </p>\n ) : (\n <div className=\"flex flex-col gap-2\">\n {nextStates.map((next) => (\n <button\n key={next}\n type=\"button\"\n disabled={transition.isPending}\n onClick={() => transition.mutate({ id: brand.id, status: next })}\n className={cn(\n 'inline-flex items-center justify-center gap-2 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'\n )}\n >\n {transition.isPending && <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />}\n Move to {titleCase(next)}\n </button>\n ))}\n </div>\n )}\n {transition.isError && (\n <p className=\"mt-2 text-xs text-status-error-text\">\n {transition.error instanceof Error ? transition.error.message : 'Transition failed'}\n </p>\n )}\n </GlassCard>\n\n <GlassCard treatment=\"plain\" className=\"p-5\">\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">Details</h3>\n <dl className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Handle</dt>\n <dd className=\"text-text-primary\">@{brand.handle}</dd>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-text-secondary\">Featured</dt>\n <dd className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={brand.isFeatured}\n aria-label={brand.isFeatured ? 'Unfeature this brand' : 'Feature this brand'}\n disabled={updateBrand.isPending}\n onClick={() =>\n updateBrand.mutate({\n id: brand.id,\n input: { isFeatured: !brand.isFeatured },\n })\n }\n className={cn(\n 'relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] disabled:opacity-60',\n brand.isFeatured ? 'bg-action-primary-bg' : 'bg-bg-sunken'\n )}\n >\n <span\n className={cn(\n 'inline-block h-4 w-4 rounded-full bg-bg-surface shadow transition-transform',\n brand.isFeatured ? 'translate-x-[1.125rem]' : 'translate-x-0.5'\n )}\n />\n </button>\n {updateBrand.isPending && (\n <Loader2 className=\"h-3.5 w-3.5 animate-spin text-text-secondary\" />\n )}\n </dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Created</dt>\n <dd className=\"text-text-primary\">\n {formatDate(brand.createdAt, { hour: undefined, minute: undefined })}\n </dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Updated</dt>\n <dd className=\"text-text-primary\">\n {formatDate(brand.updatedAt, { hour: undefined, minute: undefined })}\n </dd>\n </div>\n </dl>\n </GlassCard>\n\n {/* Reviews & feedback — moved into the sidebar to match the storefront layout */}\n <Comments entityType=\"CooBrand\" entityId={brand.id} className=\"mt-0\" />\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BrandDetailPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/brands/BrandDetailPage.tsx"],"sourcesContent":["/**\n * Brand Detail Page\n * @module coolandlovely/pages/brands\n *\n * Renders a single brand storefront (`cooBrand`) with its publish-lifecycle\n * transitions (`cooTransitionBrand`), collections and style items.\n */\n\nimport { useParams } from 'react-router';\nimport { Store, ExternalLink, Pencil, ArrowLeft, Shapes, Shirt, Loader2, Plus } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { BrandCover } from '../../components/BrandCover';\nimport { TagList } from '../../components/TagList';\nimport { Comments } from '../../components/Comments';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useBrand, useTransitionBrand, useUpdateBrand } from '../../hooks';\nimport { categoryIcon } from '../../utils/icons';\nimport { formatPrice, formatDate, titleCase } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport type { CooBrandStatus } from '../../types';\n\n/** Allowed next states per the svc brand state machine (UX guidance only;\n * the service is the authority and rejects illegal jumps). */\nconst NEXT_STATES: Record<CooBrandStatus, CooBrandStatus[]> = {\n DRAFT: ['PENDING_REVIEW', 'ARCHIVED'],\n PENDING_REVIEW: ['PUBLISHED', 'DRAFT', 'ARCHIVED'],\n PUBLISHED: ['SUSPENDED', 'ARCHIVED'],\n SUSPENDED: ['PUBLISHED', 'ARCHIVED'],\n ARCHIVED: [],\n};\n\nexport function BrandDetailPage() {\n const { brandId } = useParams<{ brandId: string }>();\n const { navigateTo } = useCoolAndLovely();\n const query = useBrand(brandId);\n const transition = useTransitionBrand();\n const updateBrand = useUpdateBrand();\n\n if (query.isLoading) {\n return (\n <PageLayout title=\"Brand\">\n <LoadingView label=\"Loading brand…\" />\n </PageLayout>\n );\n }\n\n if (query.isError || !query.data) {\n return (\n <PageLayout title=\"Brand\">\n <ErrorView\n title=\"Brand not found\"\n message=\"This storefront could not be loaded or does not exist.\"\n onRetry={() => void query.refetch()}\n />\n </PageLayout>\n );\n }\n\n const brand = query.data;\n const collections = brand.collections ?? [];\n const items = brand.items ?? [];\n const nextStates = NEXT_STATES[brand.status];\n // Only render a website link for safe http(s) URLs (block javascript:/data: sinks).\n const safeWebsiteUrl =\n brand.websiteUrl && /^https?:\\/\\//i.test(brand.websiteUrl) ? brand.websiteUrl : undefined;\n\n return (\n <PageLayout title={brand.name} description={`@${brand.handle}`}>\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Main column */}\n <div className=\"space-y-6 lg:col-span-2\">\n {/* Storefront hero — cover + logo + identity overlay, floating actions */}\n <BrandCover\n coverFileId={brand.cover?.id}\n logoFileId={brand.logo?.id}\n name={brand.name}\n seed={brand.id}\n className=\"overflow-hidden rounded-xl border border-border-seam shadow-sm\"\n overlay={\n <>\n <div className=\"absolute right-3 top-3 flex flex-wrap items-center justify-end gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/brands')}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-bg-surface/80 px-3 py-2 text-sm font-medium text-text-primary shadow-sm backdrop-blur transition-colors hover:bg-bg-surface\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/brands/${brand.id}/edit`)}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text shadow-sm transition-colors hover:bg-action-primary-bg/90\"\n >\n <Pencil className=\"h-4 w-4\" /> Edit\n </button>\n </div>\n <div className=\"absolute inset-x-0 bottom-0 flex items-end gap-3 p-4 sm:gap-4 sm:p-5\">\n <Thumbnail\n icon={<Store className=\"h-7 w-7\" />}\n seed={brand.id}\n fileId={brand.logo?.id}\n alt={`${brand.name} logo`}\n className=\"h-16 w-16 shrink-0 rounded-full shadow-md ring-2 ring-bg-surface\"\n />\n <div className=\"min-w-0 pb-1\">\n <div className=\"flex flex-wrap items-center gap-x-2 gap-y-0.5\">\n <h2 className=\"truncate text-xl font-semibold text-text-primary sm:text-2xl\">\n {brand.name}\n </h2>\n <span className=\"text-sm text-text-secondary\">@{brand.handle}</span>\n </div>\n {brand.tagline && (\n <p className=\"mt-1 line-clamp-2 text-sm text-text-primary\">{brand.tagline}</p>\n )}\n </div>\n </div>\n </>\n }\n />\n\n {/* Storefront meta — aesthetics, status, bio, tags, website */}\n <section className=\"space-y-3\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n {brand.aesthetics.length > 0 ? (\n <TagList tags={brand.aesthetics.map(titleCase)} tone=\"accent\" max={12} />\n ) : (\n <span />\n )}\n <StatusBadge status={brand.status} />\n </div>\n {brand.bio && <p className=\"text-sm text-text-secondary\">{brand.bio}</p>}\n {brand.tags.length > 0 && <TagList tags={brand.tags} max={12} />}\n {safeWebsiteUrl ? (\n <a\n href={safeWebsiteUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex max-w-full items-center gap-1.5 text-sm font-medium text-action-primary-bg hover:underline\"\n >\n <ExternalLink className=\"h-4 w-4 shrink-0\" />{' '}\n <span className=\"break-all\">{safeWebsiteUrl}</span>\n </a>\n ) : brand.websiteUrl ? (\n <p className=\"break-all text-sm text-text-secondary\">{brand.websiteUrl}</p>\n ) : null}\n </section>\n\n {/* Collections */}\n <section className=\"mt-6\">\n <div className=\"mb-3 flex items-center justify-between gap-2\">\n <h3 className=\"flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <Shapes className=\"h-5 w-5\" /> Collections ({collections.length})\n </h3>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/collections/new?brandId=${brand.id}`)}\n className=\"inline-flex shrink-0 items-center gap-1.5 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Plus className=\"h-4 w-4\" /> Add collection\n </button>\n </div>\n {collections.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n No collections yet.\n </p>\n ) : (\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n {collections.map((c) => (\n <button\n key={c.id}\n type=\"button\"\n onClick={() => navigateTo(`/collections/${c.id}`)}\n className=\"flex items-center gap-3 rounded-lg border border-border-seam bg-bg-surface p-3 text-left transition-colors hover:bg-bg-sunken/50\"\n >\n <Thumbnail\n icon={<Shapes className=\"h-5 w-5\" />}\n seed={c.id}\n fileId={c.cover?.id}\n alt={c.name}\n className=\"h-11 w-11 shrink-0\"\n />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{c.name}</p>\n <p className=\"text-xs text-text-secondary\">{titleCase(c.season)}</p>\n </div>\n <StatusBadge status={c.status} />\n </button>\n ))}\n </div>\n )}\n </section>\n\n {/* Items */}\n <section className=\"mt-6\">\n <h3 className=\"mb-3 flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <Shirt className=\"h-5 w-5\" /> Style items ({items.length})\n </h3>\n {items.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n No items yet.\n </p>\n ) : (\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-3\">\n {items.map((item) => (\n <button\n key={item.id}\n type=\"button\"\n onClick={() => navigateTo(`/items/${item.id}`)}\n className=\"overflow-hidden rounded-lg border border-border-seam bg-bg-surface text-left transition-all hover:border-accent-foreground/20 hover:shadow-md\"\n >\n <Thumbnail\n icon={categoryIcon(item.category, 24)}\n seed={item.id}\n fileId={item.image?.id}\n alt={item.name}\n className=\"h-24 w-full\"\n />\n <div className=\"p-3\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{item.name}</p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {formatPrice(item.price, item.currency)}\n </p>\n </div>\n </button>\n ))}\n </div>\n )}\n </section>\n </div>\n\n {/* Sidebar */}\n <div className=\"space-y-4\">\n <GlassCard treatment=\"insight\" className=\"p-5\">\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">Lifecycle</h3>\n <div className=\"mb-3 flex items-center gap-2\">\n <span className=\"text-sm text-text-secondary\">Current:</span>\n <StatusBadge status={brand.status} />\n </div>\n {nextStates.length === 0 ? (\n <p className=\"text-xs text-text-secondary\">\n This brand is archived (terminal state).\n </p>\n ) : (\n <div className=\"flex flex-col gap-2\">\n {nextStates.map((next) => (\n <button\n key={next}\n type=\"button\"\n disabled={transition.isPending}\n onClick={() => transition.mutate({ id: brand.id, status: next })}\n className={cn(\n 'inline-flex items-center justify-center gap-2 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'\n )}\n >\n {transition.isPending && <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />}\n Move to {titleCase(next)}\n </button>\n ))}\n </div>\n )}\n {transition.isError && (\n <p className=\"mt-2 text-xs text-status-error-text\">\n {transition.error instanceof Error ? transition.error.message : 'Transition failed'}\n </p>\n )}\n </GlassCard>\n\n <GlassCard treatment=\"plain\" className=\"p-5\">\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">Details</h3>\n <dl className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Handle</dt>\n <dd className=\"text-text-primary\">@{brand.handle}</dd>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-text-secondary\">Featured</dt>\n <dd className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={brand.isFeatured}\n aria-label={brand.isFeatured ? 'Unfeature this brand' : 'Feature this brand'}\n disabled={updateBrand.isPending}\n onClick={() =>\n updateBrand.mutate({\n id: brand.id,\n input: { isFeatured: !brand.isFeatured },\n })\n }\n className={cn(\n 'relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] disabled:opacity-60',\n brand.isFeatured ? 'bg-action-primary-bg' : 'bg-bg-sunken'\n )}\n >\n <span\n className={cn(\n 'inline-block h-4 w-4 rounded-full bg-bg-surface shadow transition-transform',\n brand.isFeatured ? 'translate-x-[1.125rem]' : 'translate-x-0.5'\n )}\n />\n </button>\n {updateBrand.isPending && (\n <Loader2 className=\"h-3.5 w-3.5 animate-spin text-text-secondary\" />\n )}\n </dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Created</dt>\n <dd className=\"text-text-primary\">\n {formatDate(brand.createdAt, { hour: undefined, minute: undefined })}\n </dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Updated</dt>\n <dd className=\"text-text-primary\">\n {formatDate(brand.updatedAt, { hour: undefined, minute: undefined })}\n </dd>\n </div>\n </dl>\n </GlassCard>\n\n {/* Reviews & feedback — moved into the sidebar to match the storefront layout */}\n <Comments entityType=\"CooBrand\" entityId={brand.id} className=\"mt-0\" />\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -9,7 +9,7 @@ import { TextAreaField as l, TextField as u, ToggleField as d } from "../../comp
|
|
|
9
9
|
import { ImageUploadField as f } from "../../components/ImageUploadField.js";
|
|
10
10
|
import { useEffect as p, useState as m } from "react";
|
|
11
11
|
import { Loader2 as h, Save as g, Store as _ } from "lucide-react";
|
|
12
|
-
import { useParams as v } from "react-router
|
|
12
|
+
import { useParams as v } from "react-router";
|
|
13
13
|
import { jsx as y, jsxs as b } from "react/jsx-runtime";
|
|
14
14
|
import { GlassCard as x } from "@burdenoff/fe-libs/ui";
|
|
15
15
|
//#region src/coolandlovely/pages/brands/BrandFormPage.tsx
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BrandFormPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/brands/BrandFormPage.tsx"],"sourcesContent":["/**\n * Brand Create / Edit Page\n * @module coolandlovely/pages/brands\n *\n * Drives `cooCreateBrand` (new) and `cooUpdateBrand` (edit). The route param\n * `brandId` distinguishes the two modes.\n */\n\nimport { useEffect, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { Loader2, Save, Store } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { TextField, TextAreaField, ToggleField } from '../../components/Field';\nimport { ImageUploadField } from '../../components/ImageUploadField';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useBrand, useCreateBrand, useUpdateBrand } from '../../hooks';\nimport { COO_FILE_FOLDERS } from '../../hooks/useFiles';\nimport { parseCsv } from '../../utils/formatters';\n\ninterface FormState {\n name: string;\n handle: string;\n tagline: string;\n bio: string;\n aesthetics: string;\n tags: string;\n websiteUrl: string;\n logoFileId: string;\n coverFileId: string;\n isFeatured: boolean;\n}\n\nconst EMPTY: FormState = {\n name: '',\n handle: '',\n tagline: '',\n bio: '',\n aesthetics: '',\n tags: '',\n websiteUrl: '',\n logoFileId: '',\n coverFileId: '',\n isFeatured: false,\n};\n\nexport function BrandFormPage() {\n const { brandId } = useParams<{ brandId: string }>();\n const isEdit = Boolean(brandId);\n const { navigateTo } = useCoolAndLovely();\n\n const existing = useBrand(isEdit ? brandId : undefined);\n const createBrand = useCreateBrand();\n const updateBrand = useUpdateBrand();\n\n const [form, setForm] = useState<FormState>(EMPTY);\n const [errors, setErrors] = useState<Partial<Record<keyof FormState, string>>>({});\n\n useEffect(() => {\n if (isEdit && existing.data) {\n const b = existing.data;\n setForm({\n name: b.name,\n handle: b.handle,\n tagline: b.tagline ?? '',\n bio: b.bio ?? '',\n aesthetics: b.aesthetics.join(', '),\n tags: b.tags.join(', '),\n websiteUrl: b.websiteUrl ?? '',\n logoFileId: b.logo?.id ?? '',\n coverFileId: b.cover?.id ?? '',\n isFeatured: b.isFeatured,\n });\n }\n }, [isEdit, existing.data]);\n\n const setField = <K extends keyof FormState>(key: K, value: FormState[K]) =>\n setForm((f) => ({ ...f, [key]: value }));\n\n const mutation = isEdit ? updateBrand : createBrand;\n\n const validate = (): boolean => {\n const next: Partial<Record<keyof FormState, string>> = {};\n if (!form.name.trim()) next.name = 'Name is required';\n if (!isEdit && !form.handle.trim()) next.handle = 'Handle is required';\n setErrors(next);\n return Object.keys(next).length === 0;\n };\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (!validate()) return;\n\n if (isEdit && brandId) {\n updateBrand.mutate(\n {\n id: brandId,\n input: {\n name: form.name.trim(),\n tagline: form.tagline.trim() || undefined,\n bio: form.bio.trim() || undefined,\n aesthetics: parseCsv(form.aesthetics),\n tags: parseCsv(form.tags),\n websiteUrl: form.websiteUrl.trim() || undefined,\n logoFileId: form.logoFileId || undefined,\n coverFileId: form.coverFileId || undefined,\n isFeatured: form.isFeatured,\n },\n },\n { onSuccess: () => navigateTo(`/brands/${brandId}`) }\n );\n } else {\n createBrand.mutate(\n {\n name: form.name.trim(),\n handle: form.handle.trim(),\n tagline: form.tagline.trim() || undefined,\n bio: form.bio.trim() || undefined,\n aesthetics: parseCsv(form.aesthetics),\n tags: parseCsv(form.tags),\n websiteUrl: form.websiteUrl.trim() || undefined,\n logoFileId: form.logoFileId || undefined,\n coverFileId: form.coverFileId || undefined,\n },\n {\n onSuccess: (brand) => {\n // `CooCreateBrandInput` has no `isFeatured`; if the creator toggled\n // Featured on, persist it with a follow-up `cooUpdateBrand` before\n // navigating (navigate regardless of that call's outcome).\n if (form.isFeatured) {\n updateBrand.mutate(\n { id: brand.id, input: { isFeatured: true } },\n { onSettled: () => navigateTo(`/brands/${brand.id}`) }\n );\n } else {\n navigateTo(`/brands/${brand.id}`);\n }\n },\n }\n );\n }\n };\n\n if (isEdit && existing.isLoading) {\n return (\n <PageLayout title=\"Edit brand\">\n <LoadingView label=\"Loading brand…\" />\n </PageLayout>\n );\n }\n\n if (isEdit && (existing.isError || !existing.data)) {\n return (\n <PageLayout title=\"Edit brand\">\n <ErrorView title=\"Brand not found\" onRetry={() => void existing.refetch()} />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={isEdit ? 'Edit brand' : 'New brand'}\n description={isEdit ? 'Update this storefront' : 'Launch a new storefront on the platform'}\n >\n <form onSubmit={handleSubmit} className=\"mx-auto max-w-2xl\">\n <GlassCard treatment=\"plain\" className=\"p-6\">\n <div className=\"mb-6 flex items-center gap-3\">\n <div className=\"flex h-11 w-11 items-center justify-center rounded-lg bg-accent-purple-subtle text-text-primary\">\n <Store className=\"h-5 w-5\" />\n </div>\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">Storefront details</h2>\n <p className=\"text-xs text-text-secondary\">\n Name your storefront and tell shoppers what your brand stands for.\n </p>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <TextField\n id=\"name\"\n label=\"Brand name\"\n value={form.name}\n onChange={(v) => setField('name', v)}\n placeholder=\"Aurora Atelier\"\n required\n error={errors.name}\n />\n <TextField\n id=\"handle\"\n label=\"Handle\"\n value={form.handle}\n onChange={(v) => setField('handle', v)}\n placeholder=\"aurora-atelier\"\n required={!isEdit}\n hint={isEdit ? 'Handle cannot be changed after creation' : 'Unique per workspace'}\n error={errors.handle}\n />\n </div>\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <TextField\n id=\"tagline\"\n label=\"Tagline\"\n value={form.tagline}\n onChange={(v) => setField('tagline', v)}\n placeholder=\"Quiet luxury, loud ideas\"\n />\n <TextAreaField\n id=\"bio\"\n label=\"Bio\"\n value={form.bio}\n onChange={(v) => setField('bio', v)}\n placeholder=\"Tell shoppers what your brand stands for…\"\n rows={4}\n />\n </div>\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <TextField\n id=\"aesthetics\"\n label=\"Aesthetics\"\n value={form.aesthetics}\n onChange={(v) => setField('aesthetics', v)}\n placeholder=\"minimal, Y2K, coastal\"\n hint=\"Comma-separated descriptors\"\n />\n <TextField\n id=\"tags\"\n label=\"Tags\"\n value={form.tags}\n onChange={(v) => setField('tags', v)}\n placeholder=\"sustainable, handmade\"\n hint=\"Comma-separated\"\n />\n </div>\n\n <TextField\n id=\"websiteUrl\"\n label=\"Website\"\n type=\"url\"\n value={form.websiteUrl}\n onChange={(v) => setField('websiteUrl', v)}\n placeholder=\"https://example.com\"\n className=\"mt-4\"\n />\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <ImageUploadField\n label=\"Logo\"\n value={form.logoFileId}\n onChange={(id) => setField('logoFileId', id)}\n folder={COO_FILE_FOLDERS.brands}\n hint=\"Square storefront logo\"\n />\n <ImageUploadField\n label=\"Cover\"\n aspect=\"wide\"\n value={form.coverFileId}\n onChange={(id) => setField('coverFileId', id)}\n folder={COO_FILE_FOLDERS.brands}\n hint=\"Wide storefront banner\"\n />\n </div>\n\n <ToggleField\n id=\"isFeatured\"\n label=\"Feature this brand\"\n checked={form.isFeatured}\n onChange={(v) => setField('isFeatured', v)}\n hint=\"Featured brands surface in the Featured filter and carry a star badge.\"\n className=\"mt-4\"\n />\n\n {mutation.isError && (\n <p className=\"mt-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {mutation.error instanceof Error ? mutation.error.message : 'Something went wrong'}\n </p>\n )}\n\n <div className=\"mt-6 flex justify-end gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo(isEdit && brandId ? `/brands/${brandId}` : '/brands')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={mutation.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {mutation.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n {isEdit ? 'Save changes' : 'Create brand'}\n </button>\n </div>\n </GlassCard>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;AAkCA,IAAM,IAAmB;CACvB,MAAM;CACN,QAAQ;CACR,SAAS;CACT,KAAK;CACL,YAAY;CACZ,MAAM;CACN,YAAY;CACZ,YAAY;CACZ,aAAa;CACb,YAAY;CACb;AAED,SAAgB,IAAgB;CAC9B,IAAM,EAAE,eAAY,GAAgC,EAC9C,IAAS,EAAQ,GACjB,EAAE,kBAAe,GAAkB,EAEnC,IAAW,EAAS,IAAS,IAAU,KAAA,EAAU,EACjD,IAAc,GAAgB,EAC9B,IAAc,GAAgB,EAE9B,CAAC,GAAM,KAAW,EAAoB,EAAM,EAC5C,CAAC,GAAQ,KAAa,EAAmD,EAAE,CAAC;AAElF,SAAgB;AACd,MAAI,KAAU,EAAS,MAAM;GAC3B,IAAM,IAAI,EAAS;AACnB,KAAQ;IACN,MAAM,EAAE;IACR,QAAQ,EAAE;IACV,SAAS,EAAE,WAAW;IACtB,KAAK,EAAE,OAAO;IACd,YAAY,EAAE,WAAW,KAAK,KAAK;IACnC,MAAM,EAAE,KAAK,KAAK,KAAK;IACvB,YAAY,EAAE,cAAc;IAC5B,YAAY,EAAE,MAAM,MAAM;IAC1B,aAAa,EAAE,OAAO,MAAM;IAC5B,YAAY,EAAE;IACf,CAAC;;IAEH,CAAC,GAAQ,EAAS,KAAK,CAAC;CAE3B,IAAM,KAAuC,GAAQ,MACnD,GAAS,OAAO;EAAE,GAAG;GAAI,IAAM;EAAO,EAAE,EAEpC,IAAW,IAAS,IAAc,GAElC,UAA0B;EAC9B,IAAM,IAAiD,EAAE;AAIzD,SAHK,EAAK,KAAK,MAAM,KAAE,EAAK,OAAO,qBAC/B,CAAC,KAAU,CAAC,EAAK,OAAO,MAAM,KAAE,EAAK,SAAS,uBAClD,EAAU,EAAK,EACR,OAAO,KAAK,EAAK,CAAC,WAAW;;AAyEtC,QAhBI,KAAU,EAAS,YAEnB,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD,EAAa,OAAM,kBAAmB,CAAA;EAC3B,CAAA,GAIb,MAAW,EAAS,WAAW,CAAC,EAAS,QAEzC,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD;GAAW,OAAM;GAAkB,eAAe,KAAK,EAAS,SAAS;GAAI,CAAA;EAClE,CAAA,GAKf,kBAAC,GAAD;EACE,OAAO,IAAS,eAAe;EAC/B,aAAa,IAAS,2BAA2B;YAEjD,kBAAC,QAAD;GAAM,WA3EY,MAAuB;AAC3C,MAAE,gBAAgB,EACb,GAAU,KAEX,KAAU,IACZ,EAAY,OACV;KACE,IAAI;KACJ,OAAO;MACL,MAAM,EAAK,KAAK,MAAM;MACtB,SAAS,EAAK,QAAQ,MAAM,IAAI,KAAA;MAChC,KAAK,EAAK,IAAI,MAAM,IAAI,KAAA;MACxB,YAAY,EAAS,EAAK,WAAW;MACrC,MAAM,EAAS,EAAK,KAAK;MACzB,YAAY,EAAK,WAAW,MAAM,IAAI,KAAA;MACtC,YAAY,EAAK,cAAc,KAAA;MAC/B,aAAa,EAAK,eAAe,KAAA;MACjC,YAAY,EAAK;MAClB;KACF,EACD,EAAE,iBAAiB,EAAW,WAAW,IAAU,EAAE,CACtD,GAED,EAAY,OACV;KACE,MAAM,EAAK,KAAK,MAAM;KACtB,QAAQ,EAAK,OAAO,MAAM;KAC1B,SAAS,EAAK,QAAQ,MAAM,IAAI,KAAA;KAChC,KAAK,EAAK,IAAI,MAAM,IAAI,KAAA;KACxB,YAAY,EAAS,EAAK,WAAW;KACrC,MAAM,EAAS,EAAK,KAAK;KACzB,YAAY,EAAK,WAAW,MAAM,IAAI,KAAA;KACtC,YAAY,EAAK,cAAc,KAAA;KAC/B,aAAa,EAAK,eAAe,KAAA;KAClC,EACD,EACE,YAAY,MAAU;AAIpB,KAAI,EAAK,aACP,EAAY,OACV;MAAE,IAAI,EAAM;MAAI,OAAO,EAAE,YAAY,IAAM;MAAE,EAC7C,EAAE,iBAAiB,EAAW,WAAW,EAAM,KAAK,EAAE,CACvD,GAED,EAAW,WAAW,EAAM,KAAK;OAGtC,CACF;;GAyB6B,WAAU;aACtC,kBAAC,GAAD;IAAW,WAAU;IAAQ,WAAU;cAAvC;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;OACzB,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;OAAI,WAAU;iBAA4C;OAAuB,CAAA,EACjF,kBAAC,KAAD;OAAG,WAAU;iBAA8B;OAEvC,CAAA,CACA,EAAA,CAAA,CACF;;KAEN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OACE,IAAG;OACH,OAAM;OACN,OAAO,EAAK;OACZ,WAAW,MAAM,EAAS,QAAQ,EAAE;OACpC,aAAY;OACZ,UAAA;OACA,OAAO,EAAO;OACd,CAAA,EACF,kBAAC,GAAD;OACE,IAAG;OACH,OAAM;OACN,OAAO,EAAK;OACZ,WAAW,MAAM,EAAS,UAAU,EAAE;OACtC,aAAY;OACZ,UAAU,CAAC;OACX,MAAM,IAAS,4CAA4C;OAC3D,OAAO,EAAO;OACd,CAAA,CACE;;KAEN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OACE,IAAG;OACH,OAAM;OACN,OAAO,EAAK;OACZ,WAAW,MAAM,EAAS,WAAW,EAAE;OACvC,aAAY;OACZ,CAAA,EACF,kBAAC,GAAD;OACE,IAAG;OACH,OAAM;OACN,OAAO,EAAK;OACZ,WAAW,MAAM,EAAS,OAAO,EAAE;OACnC,aAAY;OACZ,MAAM;OACN,CAAA,CACE;;KAEN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OACE,IAAG;OACH,OAAM;OACN,OAAO,EAAK;OACZ,WAAW,MAAM,EAAS,cAAc,EAAE;OAC1C,aAAY;OACZ,MAAK;OACL,CAAA,EACF,kBAAC,GAAD;OACE,IAAG;OACH,OAAM;OACN,OAAO,EAAK;OACZ,WAAW,MAAM,EAAS,QAAQ,EAAE;OACpC,aAAY;OACZ,MAAK;OACL,CAAA,CACE;;KAEN,kBAAC,GAAD;MACE,IAAG;MACH,OAAM;MACN,MAAK;MACL,OAAO,EAAK;MACZ,WAAW,MAAM,EAAS,cAAc,EAAE;MAC1C,aAAY;MACZ,WAAU;MACV,CAAA;KAEF,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OACE,OAAM;OACN,OAAO,EAAK;OACZ,WAAW,MAAO,EAAS,cAAc,EAAG;OAC5C,QAAQ,EAAiB;OACzB,MAAK;OACL,CAAA,EACF,kBAAC,GAAD;OACE,OAAM;OACN,QAAO;OACP,OAAO,EAAK;OACZ,WAAW,MAAO,EAAS,eAAe,EAAG;OAC7C,QAAQ,EAAiB;OACzB,MAAK;OACL,CAAA,CACE;;KAEN,kBAAC,GAAD;MACE,IAAG;MACH,OAAM;MACN,SAAS,EAAK;MACd,WAAW,MAAM,EAAS,cAAc,EAAE;MAC1C,MAAK;MACL,WAAU;MACV,CAAA;KAED,EAAS,WACR,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAS,iBAAiB,QAAQ,EAAS,MAAM,UAAU;MAC1D,CAAA;KAGN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAW,KAAU,IAAU,WAAW,MAAY,UAAU;OAC/E,WAAU;iBACX;OAEQ,CAAA,EACT,kBAAC,UAAD;OACE,MAAK;OACL,UAAU,EAAS;OACnB,WAAU;iBAHZ,CAKG,EAAS,YACR,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,GAE5C,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAE7B,IAAS,iBAAiB,eACpB;SACL;;KACI;;GACP,CAAA;EACI,CAAA"}
|
|
1
|
+
{"version":3,"file":"BrandFormPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/brands/BrandFormPage.tsx"],"sourcesContent":["/**\n * Brand Create / Edit Page\n * @module coolandlovely/pages/brands\n *\n * Drives `cooCreateBrand` (new) and `cooUpdateBrand` (edit). The route param\n * `brandId` distinguishes the two modes.\n */\n\nimport { useEffect, useState } from 'react';\nimport { useParams } from 'react-router';\nimport { Loader2, Save, Store } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { TextField, TextAreaField, ToggleField } from '../../components/Field';\nimport { ImageUploadField } from '../../components/ImageUploadField';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useBrand, useCreateBrand, useUpdateBrand } from '../../hooks';\nimport { COO_FILE_FOLDERS } from '../../hooks/useFiles';\nimport { parseCsv } from '../../utils/formatters';\n\ninterface FormState {\n name: string;\n handle: string;\n tagline: string;\n bio: string;\n aesthetics: string;\n tags: string;\n websiteUrl: string;\n logoFileId: string;\n coverFileId: string;\n isFeatured: boolean;\n}\n\nconst EMPTY: FormState = {\n name: '',\n handle: '',\n tagline: '',\n bio: '',\n aesthetics: '',\n tags: '',\n websiteUrl: '',\n logoFileId: '',\n coverFileId: '',\n isFeatured: false,\n};\n\nexport function BrandFormPage() {\n const { brandId } = useParams<{ brandId: string }>();\n const isEdit = Boolean(brandId);\n const { navigateTo } = useCoolAndLovely();\n\n const existing = useBrand(isEdit ? brandId : undefined);\n const createBrand = useCreateBrand();\n const updateBrand = useUpdateBrand();\n\n const [form, setForm] = useState<FormState>(EMPTY);\n const [errors, setErrors] = useState<Partial<Record<keyof FormState, string>>>({});\n\n useEffect(() => {\n if (isEdit && existing.data) {\n const b = existing.data;\n setForm({\n name: b.name,\n handle: b.handle,\n tagline: b.tagline ?? '',\n bio: b.bio ?? '',\n aesthetics: b.aesthetics.join(', '),\n tags: b.tags.join(', '),\n websiteUrl: b.websiteUrl ?? '',\n logoFileId: b.logo?.id ?? '',\n coverFileId: b.cover?.id ?? '',\n isFeatured: b.isFeatured,\n });\n }\n }, [isEdit, existing.data]);\n\n const setField = <K extends keyof FormState>(key: K, value: FormState[K]) =>\n setForm((f) => ({ ...f, [key]: value }));\n\n const mutation = isEdit ? updateBrand : createBrand;\n\n const validate = (): boolean => {\n const next: Partial<Record<keyof FormState, string>> = {};\n if (!form.name.trim()) next.name = 'Name is required';\n if (!isEdit && !form.handle.trim()) next.handle = 'Handle is required';\n setErrors(next);\n return Object.keys(next).length === 0;\n };\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (!validate()) return;\n\n if (isEdit && brandId) {\n updateBrand.mutate(\n {\n id: brandId,\n input: {\n name: form.name.trim(),\n tagline: form.tagline.trim() || undefined,\n bio: form.bio.trim() || undefined,\n aesthetics: parseCsv(form.aesthetics),\n tags: parseCsv(form.tags),\n websiteUrl: form.websiteUrl.trim() || undefined,\n logoFileId: form.logoFileId || undefined,\n coverFileId: form.coverFileId || undefined,\n isFeatured: form.isFeatured,\n },\n },\n { onSuccess: () => navigateTo(`/brands/${brandId}`) }\n );\n } else {\n createBrand.mutate(\n {\n name: form.name.trim(),\n handle: form.handle.trim(),\n tagline: form.tagline.trim() || undefined,\n bio: form.bio.trim() || undefined,\n aesthetics: parseCsv(form.aesthetics),\n tags: parseCsv(form.tags),\n websiteUrl: form.websiteUrl.trim() || undefined,\n logoFileId: form.logoFileId || undefined,\n coverFileId: form.coverFileId || undefined,\n },\n {\n onSuccess: (brand) => {\n // `CooCreateBrandInput` has no `isFeatured`; if the creator toggled\n // Featured on, persist it with a follow-up `cooUpdateBrand` before\n // navigating (navigate regardless of that call's outcome).\n if (form.isFeatured) {\n updateBrand.mutate(\n { id: brand.id, input: { isFeatured: true } },\n { onSettled: () => navigateTo(`/brands/${brand.id}`) }\n );\n } else {\n navigateTo(`/brands/${brand.id}`);\n }\n },\n }\n );\n }\n };\n\n if (isEdit && existing.isLoading) {\n return (\n <PageLayout title=\"Edit brand\">\n <LoadingView label=\"Loading brand…\" />\n </PageLayout>\n );\n }\n\n if (isEdit && (existing.isError || !existing.data)) {\n return (\n <PageLayout title=\"Edit brand\">\n <ErrorView title=\"Brand not found\" onRetry={() => void existing.refetch()} />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={isEdit ? 'Edit brand' : 'New brand'}\n description={isEdit ? 'Update this storefront' : 'Launch a new storefront on the platform'}\n >\n <form onSubmit={handleSubmit} className=\"mx-auto max-w-2xl\">\n <GlassCard treatment=\"plain\" className=\"p-6\">\n <div className=\"mb-6 flex items-center gap-3\">\n <div className=\"flex h-11 w-11 items-center justify-center rounded-lg bg-accent-purple-subtle text-text-primary\">\n <Store className=\"h-5 w-5\" />\n </div>\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">Storefront details</h2>\n <p className=\"text-xs text-text-secondary\">\n Name your storefront and tell shoppers what your brand stands for.\n </p>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <TextField\n id=\"name\"\n label=\"Brand name\"\n value={form.name}\n onChange={(v) => setField('name', v)}\n placeholder=\"Aurora Atelier\"\n required\n error={errors.name}\n />\n <TextField\n id=\"handle\"\n label=\"Handle\"\n value={form.handle}\n onChange={(v) => setField('handle', v)}\n placeholder=\"aurora-atelier\"\n required={!isEdit}\n hint={isEdit ? 'Handle cannot be changed after creation' : 'Unique per workspace'}\n error={errors.handle}\n />\n </div>\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <TextField\n id=\"tagline\"\n label=\"Tagline\"\n value={form.tagline}\n onChange={(v) => setField('tagline', v)}\n placeholder=\"Quiet luxury, loud ideas\"\n />\n <TextAreaField\n id=\"bio\"\n label=\"Bio\"\n value={form.bio}\n onChange={(v) => setField('bio', v)}\n placeholder=\"Tell shoppers what your brand stands for…\"\n rows={4}\n />\n </div>\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <TextField\n id=\"aesthetics\"\n label=\"Aesthetics\"\n value={form.aesthetics}\n onChange={(v) => setField('aesthetics', v)}\n placeholder=\"minimal, Y2K, coastal\"\n hint=\"Comma-separated descriptors\"\n />\n <TextField\n id=\"tags\"\n label=\"Tags\"\n value={form.tags}\n onChange={(v) => setField('tags', v)}\n placeholder=\"sustainable, handmade\"\n hint=\"Comma-separated\"\n />\n </div>\n\n <TextField\n id=\"websiteUrl\"\n label=\"Website\"\n type=\"url\"\n value={form.websiteUrl}\n onChange={(v) => setField('websiteUrl', v)}\n placeholder=\"https://example.com\"\n className=\"mt-4\"\n />\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <ImageUploadField\n label=\"Logo\"\n value={form.logoFileId}\n onChange={(id) => setField('logoFileId', id)}\n folder={COO_FILE_FOLDERS.brands}\n hint=\"Square storefront logo\"\n />\n <ImageUploadField\n label=\"Cover\"\n aspect=\"wide\"\n value={form.coverFileId}\n onChange={(id) => setField('coverFileId', id)}\n folder={COO_FILE_FOLDERS.brands}\n hint=\"Wide storefront banner\"\n />\n </div>\n\n <ToggleField\n id=\"isFeatured\"\n label=\"Feature this brand\"\n checked={form.isFeatured}\n onChange={(v) => setField('isFeatured', v)}\n hint=\"Featured brands surface in the Featured filter and carry a star badge.\"\n className=\"mt-4\"\n />\n\n {mutation.isError && (\n <p className=\"mt-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {mutation.error instanceof Error ? mutation.error.message : 'Something went wrong'}\n </p>\n )}\n\n <div className=\"mt-6 flex justify-end gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo(isEdit && brandId ? `/brands/${brandId}` : '/brands')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={mutation.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {mutation.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n {isEdit ? 'Save changes' : 'Create brand'}\n </button>\n </div>\n </GlassCard>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -11,7 +11,7 @@ import { ActorName as p } from "../../components/ActorName.js";
|
|
|
11
11
|
import { BrandCreatorAvatars as m } from "../../components/BrandCreatorAvatars.js";
|
|
12
12
|
import { StepTracker as h } from "../../components/StepTracker.js";
|
|
13
13
|
import { ArrowLeft as g, Loader2 as _, Pencil as v, Sparkles as y, Store as b, Trash2 as x } from "lucide-react";
|
|
14
|
-
import { useParams as S } from "react-router
|
|
14
|
+
import { useParams as S } from "react-router";
|
|
15
15
|
import { jsx as C, jsxs as w } from "react/jsx-runtime";
|
|
16
16
|
import { GlassCard as T } from "@burdenoff/fe-libs/ui";
|
|
17
17
|
//#region src/coolandlovely/pages/collaborations/CollaborationDetailPage.tsx
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CollaborationDetailPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/collaborations/CollaborationDetailPage.tsx"],"sourcesContent":["/**\n * Collaboration Detail Page\n * @module coolandlovely/pages/collaborations\n *\n * Single collaboration (`cooCollaboration`) with validated lifecycle\n * transitions (`cooTransitionCollaboration`), its brand and attributed looks.\n */\n\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, Loader2, Pencil, Trash2, Store, Sparkles } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { BrandCreatorAvatars } from '../../components/BrandCreatorAvatars';\nimport { ActorName } from '../../components/ActorName';\nimport { StepTracker } from '../../components/StepTracker';\nimport { TagList } from '../../components/TagList';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useCollaboration, useTransitionCollaboration, useDeleteCollaboration } from '../../hooks';\nimport { formatPrice, formatDate, titleCase } from '../../utils/formatters';\nimport type { CooCollaborationStatus } from '../../types';\n\n/** Allowed next states per the svc collaboration state machine (UX guidance;\n * the service is authoritative and rejects illegal jumps). */\nconst NEXT_STATES: Record<CooCollaborationStatus, CooCollaborationStatus[]> = {\n PROPOSED: ['ACCEPTED', 'DECLINED', 'CANCELLED'],\n ACCEPTED: ['IN_PROGRESS', 'CANCELLED'],\n IN_PROGRESS: ['PUBLISHED', 'CANCELLED'],\n PUBLISHED: [],\n DECLINED: [],\n CANCELLED: [],\n};\n\nconst LIFECYCLE_STEPS: CooCollaborationStatus[] = [\n 'PROPOSED',\n 'ACCEPTED',\n 'IN_PROGRESS',\n 'PUBLISHED',\n];\n\nexport function CollaborationDetailPage() {\n const { collaborationId } = useParams<{ collaborationId: string }>();\n const { navigateTo } = useCoolAndLovely();\n const query = useCollaboration(collaborationId);\n const transition = useTransitionCollaboration();\n const deleteCollaboration = useDeleteCollaboration();\n\n if (query.isLoading) {\n return (\n <PageLayout title=\"Collaboration\">\n <LoadingView label=\"Loading collaboration…\" />\n </PageLayout>\n );\n }\n\n if (query.isError || !query.data) {\n return (\n <PageLayout title=\"Collaboration\">\n <ErrorView title=\"Collaboration not found\" onRetry={() => void query.refetch()} />\n </PageLayout>\n );\n }\n\n const collab = query.data;\n const looks = collab.looks ?? [];\n const nextStates = NEXT_STATES[collab.status];\n const trackerSteps = LIFECYCLE_STEPS.includes(collab.status)\n ? LIFECYCLE_STEPS\n : (['PROPOSED', collab.status] as CooCollaborationStatus[]);\n\n return (\n <PageLayout\n title={collab.title}\n description={collab.brand?.name ?? undefined}\n actions={\n <div className=\"flex items-center gap-2\">\n {nextStates.length > 0 && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/collaborations/${collab.id}/edit`)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Pencil className=\"h-4 w-4\" /> Edit\n </button>\n )}\n {collab.status === 'PROPOSED' && (\n <button\n type=\"button\"\n disabled={deleteCollaboration.isPending}\n onClick={() =>\n deleteCollaboration.mutate(collab.id, {\n onSuccess: () => navigateTo('/collaborations'),\n })\n }\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-50\"\n >\n {deleteCollaboration.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Trash2 className=\"h-4 w-4\" />\n )}\n Delete\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => navigateTo('/collaborations')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n </div>\n }\n >\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"lg:col-span-2\">\n <GlassCard treatment=\"glass\" className=\"p-6\">\n <div className=\"flex items-center gap-3\">\n <BrandCreatorAvatars\n brandLogoFileId={collab.brand?.logo?.id}\n brandSeed={collab.brandId}\n creatorId={collab.creatorId}\n className=\"shrink-0\"\n />\n <div className=\"min-w-0 flex-1\">\n <h2 className=\"truncate text-xl font-semibold text-text-primary\">{collab.title}</h2>\n <p className=\"text-xs text-text-secondary\">\n {collab.brand?.name ?? 'Brand'} ×{' '}\n <button\n type=\"button\"\n onClick={() => navigateTo(`/profile/${collab.creatorId}`)}\n className=\"font-medium text-text-primary hover:underline focus-visible:outline-none\"\n >\n <ActorName id={collab.creatorId} />\n </button>{' '}\n · {titleCase(collab.role)} collaboration\n </p>\n </div>\n <StatusBadge status={collab.status} />\n </div>\n {collab.brief && <p className=\"mt-4 text-sm text-text-secondary\">{collab.brief}</p>}\n {collab.tags.length > 0 && (\n <TagList tags={collab.tags} className=\"mt-4\" tone=\"accent\" />\n )}\n </GlassCard>\n\n {/* Attributed looks */}\n <section className=\"mt-6\">\n <h3 className=\"mb-3 text-lg font-semibold text-text-primary\">\n Attributed looks ({looks.length})\n </h3>\n {looks.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n No looks attributed to this collaboration yet.\n </p>\n ) : (\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-3\">\n {looks.map((look) => (\n <button\n key={look.id}\n type=\"button\"\n onClick={() => navigateTo(`/looks/${look.id}`)}\n className=\"overflow-hidden rounded-lg border border-border-seam bg-bg-surface text-left transition-all hover:border-accent-foreground/20 hover:shadow-md\"\n >\n <Thumbnail\n icon={<Sparkles className=\"h-6 w-6\" />}\n seed={look.id}\n className=\"h-24 w-full\"\n />\n <div className=\"p-3\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{look.title}</p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {titleCase(look.occasion)}\n </p>\n </div>\n </button>\n ))}\n </div>\n )}\n </section>\n </div>\n\n <div className=\"space-y-4\">\n <GlassCard treatment=\"insight\" className=\"p-5\">\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">Lifecycle</h3>\n <StepTracker steps={trackerSteps} current={collab.status} className=\"mb-5\" />\n <div className=\"mb-3 flex items-center gap-2\">\n <span className=\"text-sm text-text-secondary\">Current:</span>\n <StatusBadge status={collab.status} />\n </div>\n {nextStates.length === 0 ? (\n <p className=\"text-xs text-text-secondary\">\n This collaboration is in a terminal state.\n </p>\n ) : (\n <div className=\"flex flex-col gap-2\">\n {nextStates.map((next) => (\n <button\n key={next}\n type=\"button\"\n disabled={transition.isPending}\n onClick={() => transition.mutate({ id: collab.id, status: next })}\n className=\"inline-flex items-center justify-center gap-2 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\"\n >\n {transition.isPending && <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />}\n Move to {titleCase(next)}\n </button>\n ))}\n </div>\n )}\n {transition.isError && (\n <p className=\"mt-2 text-xs text-status-error-text\">\n {transition.error instanceof Error ? transition.error.message : 'Transition failed'}\n </p>\n )}\n </GlassCard>\n\n <GlassCard treatment=\"plain\" className=\"p-5\">\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">Terms</h3>\n <dl className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Role</dt>\n <dd className=\"text-text-primary\">{titleCase(collab.role)}</dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Compensation</dt>\n <dd className=\"text-text-primary\">\n {formatPrice(collab.compensation, collab.currency)}\n </dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Starts</dt>\n <dd className=\"text-text-primary\">\n {collab.startDate\n ? formatDate(collab.startDate, { hour: undefined, minute: undefined })\n : '—'}\n </dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Ends</dt>\n <dd className=\"text-text-primary\">\n {collab.endDate\n ? formatDate(collab.endDate, { hour: undefined, minute: undefined })\n : '—'}\n </dd>\n </div>\n </dl>\n </GlassCard>\n\n {collab.brand && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/brands/${collab.brand?.id}`)}\n className=\"flex w-full items-center gap-3 rounded-lg border border-border-seam bg-bg-surface p-4 text-left transition-colors hover:bg-bg-sunken/50\"\n >\n <Thumbnail\n icon={<Store className=\"h-5 w-5\" />}\n seed={collab.brand.id}\n className=\"h-10 w-10 shrink-0\"\n />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {collab.brand.name}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">View brand</p>\n </div>\n </button>\n )}\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CollaborationDetailPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/collaborations/CollaborationDetailPage.tsx"],"sourcesContent":["/**\n * Collaboration Detail Page\n * @module coolandlovely/pages/collaborations\n *\n * Single collaboration (`cooCollaboration`) with validated lifecycle\n * transitions (`cooTransitionCollaboration`), its brand and attributed looks.\n */\n\nimport { useParams } from 'react-router';\nimport { ArrowLeft, Loader2, Pencil, Trash2, Store, Sparkles } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { BrandCreatorAvatars } from '../../components/BrandCreatorAvatars';\nimport { ActorName } from '../../components/ActorName';\nimport { StepTracker } from '../../components/StepTracker';\nimport { TagList } from '../../components/TagList';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useCollaboration, useTransitionCollaboration, useDeleteCollaboration } from '../../hooks';\nimport { formatPrice, formatDate, titleCase } from '../../utils/formatters';\nimport type { CooCollaborationStatus } from '../../types';\n\n/** Allowed next states per the svc collaboration state machine (UX guidance;\n * the service is authoritative and rejects illegal jumps). */\nconst NEXT_STATES: Record<CooCollaborationStatus, CooCollaborationStatus[]> = {\n PROPOSED: ['ACCEPTED', 'DECLINED', 'CANCELLED'],\n ACCEPTED: ['IN_PROGRESS', 'CANCELLED'],\n IN_PROGRESS: ['PUBLISHED', 'CANCELLED'],\n PUBLISHED: [],\n DECLINED: [],\n CANCELLED: [],\n};\n\nconst LIFECYCLE_STEPS: CooCollaborationStatus[] = [\n 'PROPOSED',\n 'ACCEPTED',\n 'IN_PROGRESS',\n 'PUBLISHED',\n];\n\nexport function CollaborationDetailPage() {\n const { collaborationId } = useParams<{ collaborationId: string }>();\n const { navigateTo } = useCoolAndLovely();\n const query = useCollaboration(collaborationId);\n const transition = useTransitionCollaboration();\n const deleteCollaboration = useDeleteCollaboration();\n\n if (query.isLoading) {\n return (\n <PageLayout title=\"Collaboration\">\n <LoadingView label=\"Loading collaboration…\" />\n </PageLayout>\n );\n }\n\n if (query.isError || !query.data) {\n return (\n <PageLayout title=\"Collaboration\">\n <ErrorView title=\"Collaboration not found\" onRetry={() => void query.refetch()} />\n </PageLayout>\n );\n }\n\n const collab = query.data;\n const looks = collab.looks ?? [];\n const nextStates = NEXT_STATES[collab.status];\n const trackerSteps = LIFECYCLE_STEPS.includes(collab.status)\n ? LIFECYCLE_STEPS\n : (['PROPOSED', collab.status] as CooCollaborationStatus[]);\n\n return (\n <PageLayout\n title={collab.title}\n description={collab.brand?.name ?? undefined}\n actions={\n <div className=\"flex items-center gap-2\">\n {nextStates.length > 0 && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/collaborations/${collab.id}/edit`)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Pencil className=\"h-4 w-4\" /> Edit\n </button>\n )}\n {collab.status === 'PROPOSED' && (\n <button\n type=\"button\"\n disabled={deleteCollaboration.isPending}\n onClick={() =>\n deleteCollaboration.mutate(collab.id, {\n onSuccess: () => navigateTo('/collaborations'),\n })\n }\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-50\"\n >\n {deleteCollaboration.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Trash2 className=\"h-4 w-4\" />\n )}\n Delete\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => navigateTo('/collaborations')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n </div>\n }\n >\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"lg:col-span-2\">\n <GlassCard treatment=\"glass\" className=\"p-6\">\n <div className=\"flex items-center gap-3\">\n <BrandCreatorAvatars\n brandLogoFileId={collab.brand?.logo?.id}\n brandSeed={collab.brandId}\n creatorId={collab.creatorId}\n className=\"shrink-0\"\n />\n <div className=\"min-w-0 flex-1\">\n <h2 className=\"truncate text-xl font-semibold text-text-primary\">{collab.title}</h2>\n <p className=\"text-xs text-text-secondary\">\n {collab.brand?.name ?? 'Brand'} ×{' '}\n <button\n type=\"button\"\n onClick={() => navigateTo(`/profile/${collab.creatorId}`)}\n className=\"font-medium text-text-primary hover:underline focus-visible:outline-none\"\n >\n <ActorName id={collab.creatorId} />\n </button>{' '}\n · {titleCase(collab.role)} collaboration\n </p>\n </div>\n <StatusBadge status={collab.status} />\n </div>\n {collab.brief && <p className=\"mt-4 text-sm text-text-secondary\">{collab.brief}</p>}\n {collab.tags.length > 0 && (\n <TagList tags={collab.tags} className=\"mt-4\" tone=\"accent\" />\n )}\n </GlassCard>\n\n {/* Attributed looks */}\n <section className=\"mt-6\">\n <h3 className=\"mb-3 text-lg font-semibold text-text-primary\">\n Attributed looks ({looks.length})\n </h3>\n {looks.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n No looks attributed to this collaboration yet.\n </p>\n ) : (\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-3\">\n {looks.map((look) => (\n <button\n key={look.id}\n type=\"button\"\n onClick={() => navigateTo(`/looks/${look.id}`)}\n className=\"overflow-hidden rounded-lg border border-border-seam bg-bg-surface text-left transition-all hover:border-accent-foreground/20 hover:shadow-md\"\n >\n <Thumbnail\n icon={<Sparkles className=\"h-6 w-6\" />}\n seed={look.id}\n className=\"h-24 w-full\"\n />\n <div className=\"p-3\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{look.title}</p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {titleCase(look.occasion)}\n </p>\n </div>\n </button>\n ))}\n </div>\n )}\n </section>\n </div>\n\n <div className=\"space-y-4\">\n <GlassCard treatment=\"insight\" className=\"p-5\">\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">Lifecycle</h3>\n <StepTracker steps={trackerSteps} current={collab.status} className=\"mb-5\" />\n <div className=\"mb-3 flex items-center gap-2\">\n <span className=\"text-sm text-text-secondary\">Current:</span>\n <StatusBadge status={collab.status} />\n </div>\n {nextStates.length === 0 ? (\n <p className=\"text-xs text-text-secondary\">\n This collaboration is in a terminal state.\n </p>\n ) : (\n <div className=\"flex flex-col gap-2\">\n {nextStates.map((next) => (\n <button\n key={next}\n type=\"button\"\n disabled={transition.isPending}\n onClick={() => transition.mutate({ id: collab.id, status: next })}\n className=\"inline-flex items-center justify-center gap-2 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\"\n >\n {transition.isPending && <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />}\n Move to {titleCase(next)}\n </button>\n ))}\n </div>\n )}\n {transition.isError && (\n <p className=\"mt-2 text-xs text-status-error-text\">\n {transition.error instanceof Error ? transition.error.message : 'Transition failed'}\n </p>\n )}\n </GlassCard>\n\n <GlassCard treatment=\"plain\" className=\"p-5\">\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">Terms</h3>\n <dl className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Role</dt>\n <dd className=\"text-text-primary\">{titleCase(collab.role)}</dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Compensation</dt>\n <dd className=\"text-text-primary\">\n {formatPrice(collab.compensation, collab.currency)}\n </dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Starts</dt>\n <dd className=\"text-text-primary\">\n {collab.startDate\n ? formatDate(collab.startDate, { hour: undefined, minute: undefined })\n : '—'}\n </dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Ends</dt>\n <dd className=\"text-text-primary\">\n {collab.endDate\n ? formatDate(collab.endDate, { hour: undefined, minute: undefined })\n : '—'}\n </dd>\n </div>\n </dl>\n </GlassCard>\n\n {collab.brand && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/brands/${collab.brand?.id}`)}\n className=\"flex w-full items-center gap-3 rounded-lg border border-border-seam bg-bg-surface p-4 text-left transition-colors hover:bg-bg-sunken/50\"\n >\n <Thumbnail\n icon={<Store className=\"h-5 w-5\" />}\n seed={collab.brand.id}\n className=\"h-10 w-10 shrink-0\"\n />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {collab.brand.name}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">View brand</p>\n </div>\n </button>\n )}\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -8,7 +8,7 @@ import { ActorName as u } from "../../components/ActorName.js";
|
|
|
8
8
|
import { SelectField as d, TextAreaField as f, TextField as p } from "../../components/Field.js";
|
|
9
9
|
import { useEffect as m, useMemo as h, useState as g } from "react";
|
|
10
10
|
import { ArrowLeft as _, Handshake as v, Loader2 as y, Save as b } from "lucide-react";
|
|
11
|
-
import { useParams as x } from "react-router
|
|
11
|
+
import { useParams as x } from "react-router";
|
|
12
12
|
import { jsx as S, jsxs as C } from "react/jsx-runtime";
|
|
13
13
|
import { GlassCard as w } from "@burdenoff/fe-libs/ui";
|
|
14
14
|
//#region src/coolandlovely/pages/collaborations/CollaborationFormPage.tsx
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CollaborationFormPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/collaborations/CollaborationFormPage.tsx"],"sourcesContent":["/**\n * Collaboration Create / Edit Page\n * @module coolandlovely/pages/collaborations\n *\n * Create mode drives `cooCreateCollaboration`. Edit mode (route\n * `/collaborations/:collaborationId/edit`) drives `cooUpdateCollaboration` —\n * identity (brand/creator) and status are immutable there, so those fields\n * render read-only and status transitions stay on CollaborationDetailPage.\n */\n\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, Loader2, Save, Handshake } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { TextField, TextAreaField, SelectField } from '../../components/Field';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { ActorName } from '../../components/ActorName';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport {\n useCreateCollaboration,\n useUpdateCollaboration,\n useCollaboration,\n useBrands,\n} from '../../hooks';\nimport { parseCsv, titleCase } from '../../utils/formatters';\nimport type { CooCollaboratorRole } from '../../types';\n\nconst ROLES: CooCollaboratorRole[] = ['CREATOR', 'BRAND', 'STYLIST', 'PHOTOGRAPHER'];\n\n/** Statuses past which a collaboration's terms may no longer be edited (mirrors the svc's TERMINAL_COLLABORATION_STATUSES). */\nconst TERMINAL_STATUSES = new Set(['PUBLISHED', 'DECLINED', 'CANCELLED']);\n\ninterface FormState {\n brandId: string;\n creatorId: string;\n title: string;\n brief: string;\n role: CooCollaboratorRole;\n compensation: string;\n currency: string;\n tags: string;\n startDate: string;\n endDate: string;\n}\n\nfunction toIso(date: string): string | undefined {\n if (!date) return undefined;\n const d = new Date(date);\n return Number.isNaN(d.getTime()) ? undefined : d.toISOString();\n}\n\n/** yyyy-MM-dd for a <input type=\"date\"> from an ISO string, or '' when unset. */\nfunction toDateInput(iso: string | null | undefined): string {\n if (!iso) return '';\n const d = new Date(iso);\n return Number.isNaN(d.getTime()) ? '' : d.toISOString().slice(0, 10);\n}\n\nconst emptyForm: FormState = {\n brandId: '',\n creatorId: '',\n title: '',\n brief: '',\n role: 'CREATOR',\n compensation: '',\n currency: 'USD',\n tags: '',\n startDate: '',\n endDate: '',\n};\n\nexport function CollaborationFormPage() {\n const { collaborationId } = useParams<{ collaborationId: string }>();\n const isEditMode = Boolean(collaborationId);\n const { navigateTo, currentUser } = useCoolAndLovely();\n const createCollaboration = useCreateCollaboration();\n const updateCollaboration = useUpdateCollaboration();\n const brands = useBrands({ pagination: { limit: 100, offset: 0 } });\n const existing = useCollaboration(isEditMode ? collaborationId : undefined);\n\n const [form, setForm] = useState<FormState>({ ...emptyForm, creatorId: currentUser?.id ?? '' });\n const [errors, setErrors] = useState<Partial<Record<keyof FormState, string>>>({});\n\n // Prefill the form once the existing collaboration loads (edit mode only).\n useEffect(() => {\n if (!isEditMode || !existing.data) return;\n const c = existing.data;\n setForm({\n brandId: c.brandId,\n creatorId: c.creatorId,\n title: c.title,\n brief: c.brief ?? '',\n role: c.role,\n compensation: c.compensation ? String(c.compensation) : '',\n currency: c.currency,\n tags: c.tags.join(', '),\n startDate: toDateInput(c.startDate),\n endDate: toDateInput(c.endDate),\n });\n }, [isEditMode, existing.data]);\n\n const setField = <K extends keyof FormState>(key: K, value: FormState[K]) =>\n setForm((f) => ({ ...f, [key]: value }));\n\n const brandOptions = useMemo(\n () => (brands.data?.items ?? []).map((b) => ({ value: b.id, label: b.name })),\n [brands.data]\n );\n const roleOptions = ROLES.map((r) => ({ value: r, label: titleCase(r) }));\n\n if (isEditMode && existing.isLoading) {\n return (\n <PageLayout title=\"Edit collaboration\">\n <LoadingView label=\"Loading collaboration…\" />\n </PageLayout>\n );\n }\n\n if (isEditMode && (existing.isError || !existing.data)) {\n return (\n <PageLayout title=\"Edit collaboration\">\n <ErrorView title=\"Collaboration not found\" onRetry={() => void existing.refetch()} />\n </PageLayout>\n );\n }\n\n const isTerminal =\n isEditMode && existing.data ? TERMINAL_STATUSES.has(existing.data.status) : false;\n\n const validate = (): boolean => {\n const next: Partial<Record<keyof FormState, string>> = {};\n if (!form.title.trim()) next.title = 'Title is required';\n if (!isEditMode) {\n if (!form.brandId) next.brandId = 'Brand is required';\n if (!form.creatorId.trim()) next.creatorId = 'Creator is required';\n }\n if (form.compensation && Number.isNaN(Number(form.compensation)))\n next.compensation = 'Must be a number';\n setErrors(next);\n return Object.keys(next).length === 0;\n };\n\n const mutation = isEditMode ? updateCollaboration : createCollaboration;\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (!validate()) return;\n\n if (isEditMode && collaborationId) {\n updateCollaboration.mutate(\n {\n id: collaborationId,\n input: {\n title: form.title.trim(),\n brief: form.brief.trim() || undefined,\n role: form.role,\n compensation: form.compensation ? Number(form.compensation) : undefined,\n currency: form.currency.trim() || undefined,\n tags: parseCsv(form.tags),\n startDate: toIso(form.startDate),\n endDate: toIso(form.endDate),\n },\n },\n { onSuccess: (c) => navigateTo(`/collaborations/${c.id}`) }\n );\n return;\n }\n\n createCollaboration.mutate(\n {\n brandId: form.brandId,\n creatorId: form.creatorId.trim(),\n title: form.title.trim(),\n brief: form.brief.trim() || undefined,\n role: form.role,\n compensation: form.compensation ? Number(form.compensation) : undefined,\n currency: form.currency.trim() || undefined,\n tags: parseCsv(form.tags),\n startDate: toIso(form.startDate),\n endDate: toIso(form.endDate),\n },\n { onSuccess: (c) => navigateTo(`/collaborations/${c.id}`) }\n );\n };\n\n return (\n <PageLayout\n title={isEditMode ? 'Edit collaboration' : 'New collaboration'}\n description={\n isEditMode ? 'Update the brief, terms and dates' : 'Propose a brand × creator partnership'\n }\n actions={\n <button\n type=\"button\"\n onClick={() =>\n navigateTo(\n isEditMode && collaborationId\n ? `/collaborations/${collaborationId}`\n : '/collaborations'\n )\n }\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Cancel\n </button>\n }\n >\n <form onSubmit={handleSubmit} className=\"mx-auto max-w-2xl\">\n <GlassCard treatment=\"plain\" className=\"p-6\">\n <div className=\"mb-6 flex items-center gap-3\">\n <div className=\"flex h-11 w-11 items-center justify-center rounded-lg bg-status-warning-bg-subtle text-status-warning-text\">\n <Handshake className=\"h-5 w-5\" />\n </div>\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">Collaboration details</h2>\n <p className=\"text-xs text-text-secondary\">\n {isEditMode\n ? 'Brand and creator cannot be changed after proposal.'\n : 'Created in the Proposed state.'}\n </p>\n </div>\n </div>\n\n {isTerminal && (\n <p className=\"mb-4 rounded-lg bg-status-warning-bg-subtle px-3 py-2 text-sm text-status-warning-text\">\n This collaboration is {existing.data?.status.toLowerCase()} and can no longer be\n edited.\n </p>\n )}\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n {isEditMode ? (\n <div>\n <span className=\"mb-1.5 block text-sm font-medium text-text-primary\">Brand</span>\n <p className=\"rounded-lg border border-border-seam bg-bg-sunken px-3 py-2 text-sm text-text-secondary\">\n {existing.data?.brand?.name ?? form.brandId}\n </p>\n </div>\n ) : (\n <SelectField\n id=\"brandId\"\n label=\"Brand\"\n value={form.brandId}\n onChange={(v) => setField('brandId', v)}\n options={brandOptions}\n required\n placeholder={brands.isLoading ? 'Loading brands…' : 'Select a brand'}\n error={errors.brandId}\n />\n )}\n <SelectField\n id=\"role\"\n label=\"Role\"\n value={form.role}\n onChange={(v) => setField('role', v as CooCollaboratorRole)}\n options={roleOptions}\n disabled={isTerminal}\n />\n </div>\n\n <TextField\n id=\"title\"\n label=\"Title\"\n value={form.title}\n onChange={(v) => setField('title', v)}\n placeholder=\"Summer capsule edit\"\n required\n error={errors.title}\n className=\"mt-4\"\n disabled={isTerminal}\n />\n\n {isEditMode ? (\n <div className=\"mt-4\">\n <span className=\"mb-1.5 block text-sm font-medium text-text-primary\">Creator</span>\n <p className=\"rounded-lg border border-border-seam bg-bg-sunken px-3 py-2 text-sm text-text-secondary\">\n <ActorName id={form.creatorId} />\n </p>\n </div>\n ) : (\n <TextField\n id=\"creatorId\"\n label=\"Creator ID\"\n value={form.creatorId}\n onChange={(v) => setField('creatorId', v)}\n placeholder=\"creator user id\"\n required\n hint=\"Defaults to you. Paste another user's id to credit a different creator.\"\n error={errors.creatorId}\n className=\"mt-4\"\n />\n )}\n\n <TextAreaField\n id=\"brief\"\n label=\"Brief\"\n value={form.brief}\n onChange={(v) => setField('brief', v)}\n placeholder=\"What is this collaboration about…\"\n rows={4}\n className=\"mt-4\"\n disabled={isTerminal}\n />\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <TextField\n id=\"compensation\"\n label=\"Compensation\"\n type=\"number\"\n value={form.compensation}\n onChange={(v) => setField('compensation', v)}\n placeholder=\"0.00\"\n error={errors.compensation}\n disabled={isTerminal}\n />\n <TextField\n id=\"currency\"\n label=\"Currency\"\n value={form.currency}\n onChange={(v) => setField('currency', v)}\n placeholder=\"USD\"\n disabled={isTerminal}\n />\n </div>\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <TextField\n id=\"startDate\"\n label=\"Start date\"\n type=\"date\"\n value={form.startDate}\n onChange={(v) => setField('startDate', v)}\n disabled={isTerminal}\n />\n <TextField\n id=\"endDate\"\n label=\"End date\"\n type=\"date\"\n value={form.endDate}\n onChange={(v) => setField('endDate', v)}\n disabled={isTerminal}\n />\n </div>\n\n <TextField\n id=\"tags\"\n label=\"Tags\"\n value={form.tags}\n onChange={(v) => setField('tags', v)}\n placeholder=\"capsule, sustainable\"\n hint=\"Comma-separated\"\n className=\"mt-4\"\n disabled={isTerminal}\n />\n\n {mutation.isError && (\n <p className=\"mt-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {mutation.error instanceof Error ? mutation.error.message : 'Something went wrong'}\n </p>\n )}\n\n <div className=\"mt-6 flex justify-end gap-2\">\n <button\n type=\"submit\"\n disabled={mutation.isPending || isTerminal}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {mutation.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n {isEditMode ? 'Save changes' : 'Create collaboration'}\n </button>\n </div>\n </GlassCard>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CollaborationFormPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/collaborations/CollaborationFormPage.tsx"],"sourcesContent":["/**\n * Collaboration Create / Edit Page\n * @module coolandlovely/pages/collaborations\n *\n * Create mode drives `cooCreateCollaboration`. Edit mode (route\n * `/collaborations/:collaborationId/edit`) drives `cooUpdateCollaboration` —\n * identity (brand/creator) and status are immutable there, so those fields\n * render read-only and status transitions stay on CollaborationDetailPage.\n */\n\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\nimport { ArrowLeft, Loader2, Save, Handshake } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { TextField, TextAreaField, SelectField } from '../../components/Field';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { ActorName } from '../../components/ActorName';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport {\n useCreateCollaboration,\n useUpdateCollaboration,\n useCollaboration,\n useBrands,\n} from '../../hooks';\nimport { parseCsv, titleCase } from '../../utils/formatters';\nimport type { CooCollaboratorRole } from '../../types';\n\nconst ROLES: CooCollaboratorRole[] = ['CREATOR', 'BRAND', 'STYLIST', 'PHOTOGRAPHER'];\n\n/** Statuses past which a collaboration's terms may no longer be edited (mirrors the svc's TERMINAL_COLLABORATION_STATUSES). */\nconst TERMINAL_STATUSES = new Set(['PUBLISHED', 'DECLINED', 'CANCELLED']);\n\ninterface FormState {\n brandId: string;\n creatorId: string;\n title: string;\n brief: string;\n role: CooCollaboratorRole;\n compensation: string;\n currency: string;\n tags: string;\n startDate: string;\n endDate: string;\n}\n\nfunction toIso(date: string): string | undefined {\n if (!date) return undefined;\n const d = new Date(date);\n return Number.isNaN(d.getTime()) ? undefined : d.toISOString();\n}\n\n/** yyyy-MM-dd for a <input type=\"date\"> from an ISO string, or '' when unset. */\nfunction toDateInput(iso: string | null | undefined): string {\n if (!iso) return '';\n const d = new Date(iso);\n return Number.isNaN(d.getTime()) ? '' : d.toISOString().slice(0, 10);\n}\n\nconst emptyForm: FormState = {\n brandId: '',\n creatorId: '',\n title: '',\n brief: '',\n role: 'CREATOR',\n compensation: '',\n currency: 'USD',\n tags: '',\n startDate: '',\n endDate: '',\n};\n\nexport function CollaborationFormPage() {\n const { collaborationId } = useParams<{ collaborationId: string }>();\n const isEditMode = Boolean(collaborationId);\n const { navigateTo, currentUser } = useCoolAndLovely();\n const createCollaboration = useCreateCollaboration();\n const updateCollaboration = useUpdateCollaboration();\n const brands = useBrands({ pagination: { limit: 100, offset: 0 } });\n const existing = useCollaboration(isEditMode ? collaborationId : undefined);\n\n const [form, setForm] = useState<FormState>({ ...emptyForm, creatorId: currentUser?.id ?? '' });\n const [errors, setErrors] = useState<Partial<Record<keyof FormState, string>>>({});\n\n // Prefill the form once the existing collaboration loads (edit mode only).\n useEffect(() => {\n if (!isEditMode || !existing.data) return;\n const c = existing.data;\n setForm({\n brandId: c.brandId,\n creatorId: c.creatorId,\n title: c.title,\n brief: c.brief ?? '',\n role: c.role,\n compensation: c.compensation ? String(c.compensation) : '',\n currency: c.currency,\n tags: c.tags.join(', '),\n startDate: toDateInput(c.startDate),\n endDate: toDateInput(c.endDate),\n });\n }, [isEditMode, existing.data]);\n\n const setField = <K extends keyof FormState>(key: K, value: FormState[K]) =>\n setForm((f) => ({ ...f, [key]: value }));\n\n const brandOptions = useMemo(\n () => (brands.data?.items ?? []).map((b) => ({ value: b.id, label: b.name })),\n [brands.data]\n );\n const roleOptions = ROLES.map((r) => ({ value: r, label: titleCase(r) }));\n\n if (isEditMode && existing.isLoading) {\n return (\n <PageLayout title=\"Edit collaboration\">\n <LoadingView label=\"Loading collaboration…\" />\n </PageLayout>\n );\n }\n\n if (isEditMode && (existing.isError || !existing.data)) {\n return (\n <PageLayout title=\"Edit collaboration\">\n <ErrorView title=\"Collaboration not found\" onRetry={() => void existing.refetch()} />\n </PageLayout>\n );\n }\n\n const isTerminal =\n isEditMode && existing.data ? TERMINAL_STATUSES.has(existing.data.status) : false;\n\n const validate = (): boolean => {\n const next: Partial<Record<keyof FormState, string>> = {};\n if (!form.title.trim()) next.title = 'Title is required';\n if (!isEditMode) {\n if (!form.brandId) next.brandId = 'Brand is required';\n if (!form.creatorId.trim()) next.creatorId = 'Creator is required';\n }\n if (form.compensation && Number.isNaN(Number(form.compensation)))\n next.compensation = 'Must be a number';\n setErrors(next);\n return Object.keys(next).length === 0;\n };\n\n const mutation = isEditMode ? updateCollaboration : createCollaboration;\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (!validate()) return;\n\n if (isEditMode && collaborationId) {\n updateCollaboration.mutate(\n {\n id: collaborationId,\n input: {\n title: form.title.trim(),\n brief: form.brief.trim() || undefined,\n role: form.role,\n compensation: form.compensation ? Number(form.compensation) : undefined,\n currency: form.currency.trim() || undefined,\n tags: parseCsv(form.tags),\n startDate: toIso(form.startDate),\n endDate: toIso(form.endDate),\n },\n },\n { onSuccess: (c) => navigateTo(`/collaborations/${c.id}`) }\n );\n return;\n }\n\n createCollaboration.mutate(\n {\n brandId: form.brandId,\n creatorId: form.creatorId.trim(),\n title: form.title.trim(),\n brief: form.brief.trim() || undefined,\n role: form.role,\n compensation: form.compensation ? Number(form.compensation) : undefined,\n currency: form.currency.trim() || undefined,\n tags: parseCsv(form.tags),\n startDate: toIso(form.startDate),\n endDate: toIso(form.endDate),\n },\n { onSuccess: (c) => navigateTo(`/collaborations/${c.id}`) }\n );\n };\n\n return (\n <PageLayout\n title={isEditMode ? 'Edit collaboration' : 'New collaboration'}\n description={\n isEditMode ? 'Update the brief, terms and dates' : 'Propose a brand × creator partnership'\n }\n actions={\n <button\n type=\"button\"\n onClick={() =>\n navigateTo(\n isEditMode && collaborationId\n ? `/collaborations/${collaborationId}`\n : '/collaborations'\n )\n }\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Cancel\n </button>\n }\n >\n <form onSubmit={handleSubmit} className=\"mx-auto max-w-2xl\">\n <GlassCard treatment=\"plain\" className=\"p-6\">\n <div className=\"mb-6 flex items-center gap-3\">\n <div className=\"flex h-11 w-11 items-center justify-center rounded-lg bg-status-warning-bg-subtle text-status-warning-text\">\n <Handshake className=\"h-5 w-5\" />\n </div>\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">Collaboration details</h2>\n <p className=\"text-xs text-text-secondary\">\n {isEditMode\n ? 'Brand and creator cannot be changed after proposal.'\n : 'Created in the Proposed state.'}\n </p>\n </div>\n </div>\n\n {isTerminal && (\n <p className=\"mb-4 rounded-lg bg-status-warning-bg-subtle px-3 py-2 text-sm text-status-warning-text\">\n This collaboration is {existing.data?.status.toLowerCase()} and can no longer be\n edited.\n </p>\n )}\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n {isEditMode ? (\n <div>\n <span className=\"mb-1.5 block text-sm font-medium text-text-primary\">Brand</span>\n <p className=\"rounded-lg border border-border-seam bg-bg-sunken px-3 py-2 text-sm text-text-secondary\">\n {existing.data?.brand?.name ?? form.brandId}\n </p>\n </div>\n ) : (\n <SelectField\n id=\"brandId\"\n label=\"Brand\"\n value={form.brandId}\n onChange={(v) => setField('brandId', v)}\n options={brandOptions}\n required\n placeholder={brands.isLoading ? 'Loading brands…' : 'Select a brand'}\n error={errors.brandId}\n />\n )}\n <SelectField\n id=\"role\"\n label=\"Role\"\n value={form.role}\n onChange={(v) => setField('role', v as CooCollaboratorRole)}\n options={roleOptions}\n disabled={isTerminal}\n />\n </div>\n\n <TextField\n id=\"title\"\n label=\"Title\"\n value={form.title}\n onChange={(v) => setField('title', v)}\n placeholder=\"Summer capsule edit\"\n required\n error={errors.title}\n className=\"mt-4\"\n disabled={isTerminal}\n />\n\n {isEditMode ? (\n <div className=\"mt-4\">\n <span className=\"mb-1.5 block text-sm font-medium text-text-primary\">Creator</span>\n <p className=\"rounded-lg border border-border-seam bg-bg-sunken px-3 py-2 text-sm text-text-secondary\">\n <ActorName id={form.creatorId} />\n </p>\n </div>\n ) : (\n <TextField\n id=\"creatorId\"\n label=\"Creator ID\"\n value={form.creatorId}\n onChange={(v) => setField('creatorId', v)}\n placeholder=\"creator user id\"\n required\n hint=\"Defaults to you. Paste another user's id to credit a different creator.\"\n error={errors.creatorId}\n className=\"mt-4\"\n />\n )}\n\n <TextAreaField\n id=\"brief\"\n label=\"Brief\"\n value={form.brief}\n onChange={(v) => setField('brief', v)}\n placeholder=\"What is this collaboration about…\"\n rows={4}\n className=\"mt-4\"\n disabled={isTerminal}\n />\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <TextField\n id=\"compensation\"\n label=\"Compensation\"\n type=\"number\"\n value={form.compensation}\n onChange={(v) => setField('compensation', v)}\n placeholder=\"0.00\"\n error={errors.compensation}\n disabled={isTerminal}\n />\n <TextField\n id=\"currency\"\n label=\"Currency\"\n value={form.currency}\n onChange={(v) => setField('currency', v)}\n placeholder=\"USD\"\n disabled={isTerminal}\n />\n </div>\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <TextField\n id=\"startDate\"\n label=\"Start date\"\n type=\"date\"\n value={form.startDate}\n onChange={(v) => setField('startDate', v)}\n disabled={isTerminal}\n />\n <TextField\n id=\"endDate\"\n label=\"End date\"\n type=\"date\"\n value={form.endDate}\n onChange={(v) => setField('endDate', v)}\n disabled={isTerminal}\n />\n </div>\n\n <TextField\n id=\"tags\"\n label=\"Tags\"\n value={form.tags}\n onChange={(v) => setField('tags', v)}\n placeholder=\"capsule, sustainable\"\n hint=\"Comma-separated\"\n className=\"mt-4\"\n disabled={isTerminal}\n />\n\n {mutation.isError && (\n <p className=\"mt-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {mutation.error instanceof Error ? mutation.error.message : 'Something went wrong'}\n </p>\n )}\n\n <div className=\"mt-6 flex justify-end gap-2\">\n <button\n type=\"submit\"\n disabled={mutation.isPending || isTerminal}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {mutation.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n {isEditMode ? 'Save changes' : 'Create collaboration'}\n </button>\n </div>\n </GlassCard>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -10,7 +10,7 @@ import { categoryIcon as f } from "../../utils/icons.js";
|
|
|
10
10
|
import { TagList as p } from "../../components/TagList.js";
|
|
11
11
|
import { ColourSwatchRow as m } from "../../components/ColourSwatchRow.js";
|
|
12
12
|
import { Archive as h, ArrowLeft as g, Loader2 as _, Pencil as v, Plus as y, Rocket as b, Shapes as x, Store as S } from "lucide-react";
|
|
13
|
-
import { useParams as C } from "react-router
|
|
13
|
+
import { useParams as C } from "react-router";
|
|
14
14
|
import { Fragment as w, jsx as T, jsxs as E } from "react/jsx-runtime";
|
|
15
15
|
import { GlassCard as D } from "@burdenoff/fe-libs/ui";
|
|
16
16
|
//#region src/coolandlovely/pages/collections/CollectionDetailPage.tsx
|