@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.
Files changed (27) hide show
  1. package/dist/coolandlovely/CoolAndLovelyRoutes.js +1 -1
  2. package/dist/coolandlovely/CoolAndLovelyRoutes.js.map +1 -1
  3. package/dist/coolandlovely/context/CoolAndLovelyContext.js +1 -1
  4. package/dist/coolandlovely/context/CoolAndLovelyContext.js.map +1 -1
  5. package/dist/coolandlovely/pages/brands/BrandDetailPage.js +1 -1
  6. package/dist/coolandlovely/pages/brands/BrandDetailPage.js.map +1 -1
  7. package/dist/coolandlovely/pages/brands/BrandFormPage.js +1 -1
  8. package/dist/coolandlovely/pages/brands/BrandFormPage.js.map +1 -1
  9. package/dist/coolandlovely/pages/collaborations/CollaborationDetailPage.js +1 -1
  10. package/dist/coolandlovely/pages/collaborations/CollaborationDetailPage.js.map +1 -1
  11. package/dist/coolandlovely/pages/collaborations/CollaborationFormPage.js +1 -1
  12. package/dist/coolandlovely/pages/collaborations/CollaborationFormPage.js.map +1 -1
  13. package/dist/coolandlovely/pages/collections/CollectionDetailPage.js +1 -1
  14. package/dist/coolandlovely/pages/collections/CollectionDetailPage.js.map +1 -1
  15. package/dist/coolandlovely/pages/collections/CollectionFormPage.js +1 -1
  16. package/dist/coolandlovely/pages/collections/CollectionFormPage.js.map +1 -1
  17. package/dist/coolandlovely/pages/items/StyleItemDetailPage.js +1 -1
  18. package/dist/coolandlovely/pages/items/StyleItemDetailPage.js.map +1 -1
  19. package/dist/coolandlovely/pages/items/StyleItemFormPage.js +1 -1
  20. package/dist/coolandlovely/pages/items/StyleItemFormPage.js.map +1 -1
  21. package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js +1 -1
  22. package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js.map +1 -1
  23. package/dist/coolandlovely/pages/looks/LookDetailPage.js +1 -1
  24. package/dist/coolandlovely/pages/looks/LookDetailPage.js.map +1 -1
  25. package/dist/coolandlovely/pages/profile/StyleProfilePage.js +1 -1
  26. package/dist/coolandlovely/pages/profile/StyleProfilePage.js.map +1 -1
  27. package/package.json +7 -7
@@ -1 +1 @@
1
- {"version":3,"file":"CollectionDetailPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/collections/CollectionDetailPage.tsx"],"sourcesContent":["/**\n * Collection Detail Page\n * @module coolandlovely/pages/collections\n *\n * Single collection (`cooCollection`) with publish (`cooPublishCollection`),\n * edit, its brand link and member items.\n */\n\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, Pencil, Rocket, Archive, Loader2, Shapes, Store, 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 { TagList } from '../../components/TagList';\nimport { ColourSwatchRow } from '../../components/ColourSwatchRow';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useCollection, usePublishCollection, useArchiveCollection } from '../../hooks';\nimport { categoryIcon } from '../../utils/icons';\nimport { formatPrice, formatDate, titleCase } from '../../utils/formatters';\n\nexport function CollectionDetailPage() {\n const { collectionId } = useParams<{ collectionId: string }>();\n const { navigateTo } = useCoolAndLovely();\n const query = useCollection(collectionId);\n const publish = usePublishCollection();\n const archive = useArchiveCollection();\n\n if (query.isLoading) {\n return (\n <PageLayout title=\"Collection\">\n <LoadingView label=\"Loading collection…\" />\n </PageLayout>\n );\n }\n\n if (query.isError || !query.data) {\n return (\n <PageLayout title=\"Collection\">\n <ErrorView title=\"Collection not found\" onRetry={() => void query.refetch()} />\n </PageLayout>\n );\n }\n\n const collection = query.data;\n const items = collection.items ?? [];\n const canPublish = collection.status === 'DRAFT' || collection.status === 'SCHEDULED';\n const isArchived = collection.status === 'ARCHIVED';\n\n return (\n <PageLayout\n title={collection.name}\n description={collection.brand?.name ?? undefined}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/collections')}\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 <button\n type=\"button\"\n onClick={() => navigateTo(`/collections/${collection.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 {canPublish && (\n <button\n type=\"button\"\n disabled={publish.isPending}\n onClick={() => publish.mutate(collection.id)}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {publish.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Rocket className=\"h-4 w-4\" />\n )}\n Publish\n </button>\n )}\n {!isArchived && (\n <button\n type=\"button\"\n disabled={archive.isPending}\n onClick={() =>\n archive.mutate(collection.id, { onSuccess: () => navigateTo('/collections') })\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 {archive.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Archive className=\"h-4 w-4\" />\n )}\n Archive\n </button>\n )}\n </div>\n }\n >\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"lg:col-span-2\">\n <GlassCard treatment=\"glass\" className=\"group overflow-hidden p-0\">\n <Thumbnail\n icon={<Shapes className=\"h-12 w-12\" />}\n seed={collection.id}\n fileId={collection.cover?.id}\n alt={collection.name}\n className=\"aspect-[16/9] w-full sm:aspect-[21/9]\"\n overlay={\n <>\n <div className=\"pointer-events-none absolute inset-0 bg-gradient-to-t from-bg-canvas via-bg-canvas/55 to-transparent\" />\n <div className=\"absolute inset-x-0 bottom-0 flex flex-wrap items-end justify-between gap-3 p-6\">\n <div className=\"min-w-0\">\n <h2 className=\"font-serif text-3xl font-semibold leading-tight text-text-primary\">\n {collection.name}\n </h2>\n <div className=\"mt-2 flex flex-wrap items-center gap-2 text-sm text-text-secondary\">\n {collection.brand?.name && <span>{collection.brand.name}</span>}\n {collection.brand?.name && (\n <span aria-hidden=\"true\" className=\"opacity-60\">\n ·\n </span>\n )}\n <span className=\"rounded-full border border-border-seam bg-bg-surface/80 px-2.5 py-0.5 text-xs font-medium text-text-primary backdrop-blur-sm\">\n {titleCase(collection.season)}\n </span>\n </div>\n </div>\n <StatusBadge status={collection.status} className=\"shrink-0 shadow-sm\" />\n </div>\n </>\n }\n />\n {(collection.description || collection.tags.length > 0) && (\n <div className=\"p-6\">\n {collection.description && (\n <p className=\"text-sm text-text-secondary\">{collection.description}</p>\n )}\n {collection.tags.length > 0 && (\n <TagList\n tags={collection.tags}\n className={collection.description ? 'mt-4' : undefined}\n />\n )}\n </div>\n )}\n </GlassCard>\n\n <section className=\"mt-6\">\n <h3 className=\"mb-3 text-lg font-semibold text-text-primary\">Items ({items.length})</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 in this collection 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=\"group 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, 28)}\n seed={item.id}\n fileId={item.image?.id}\n alt={item.name}\n className=\"aspect-[3/4] 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 font-semibold text-text-primary tabular-nums\">\n {formatPrice(item.price, item.currency)}\n </p>\n {item.colours.length > 0 && (\n <ColourSwatchRow colours={item.colours} max={5} className=\"mt-2\" />\n )}\n </div>\n </button>\n ))}\n </div>\n )}\n <button\n type=\"button\"\n onClick={() =>\n navigateTo(`/items/new?brandId=${collection.brandId}&collectionId=${collection.id}`)\n }\n className=\"mt-3 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 <Plus className=\"h-4 w-4\" /> Add item to this collection\n </button>\n </section>\n </div>\n\n <div className=\"space-y-4\">\n <GlassCard treatment=\"plain\" className=\"p-5\">\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">Details</h3>\n <dl className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Season</dt>\n <dd className=\"text-text-primary\">{titleCase(collection.season)}</dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Publish at</dt>\n <dd className=\"text-text-primary\">\n {collection.publishAt\n ? formatDate(collection.publishAt, { hour: undefined, minute: undefined })\n : '—'}\n </dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Ends at</dt>\n <dd className=\"text-text-primary\">\n {collection.endAt\n ? formatDate(collection.endAt, { hour: undefined, minute: undefined })\n : '—'}\n </dd>\n </div>\n </dl>\n </GlassCard>\n\n {collection.brand && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/brands/${collection.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={collection.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 {collection.brand.name}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">View brand</p>\n </div>\n </button>\n )}\n\n {publish.isError && (\n <p className=\"rounded-lg bg-status-error-bg-subtle px-3 py-2 text-xs text-status-error-text\">\n {publish.error instanceof Error ? publish.error.message : 'Publish failed'}\n </p>\n )}\n {archive.isError && (\n <p className=\"rounded-lg bg-status-error-bg-subtle px-3 py-2 text-xs text-status-error-text\">\n {archive.error instanceof Error ? archive.error.message : 'Archive failed'}\n </p>\n )}\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"CollectionDetailPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/collections/CollectionDetailPage.tsx"],"sourcesContent":["/**\n * Collection Detail Page\n * @module coolandlovely/pages/collections\n *\n * Single collection (`cooCollection`) with publish (`cooPublishCollection`),\n * edit, its brand link and member items.\n */\n\nimport { useParams } from 'react-router';\nimport { ArrowLeft, Pencil, Rocket, Archive, Loader2, Shapes, Store, 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 { TagList } from '../../components/TagList';\nimport { ColourSwatchRow } from '../../components/ColourSwatchRow';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useCollection, usePublishCollection, useArchiveCollection } from '../../hooks';\nimport { categoryIcon } from '../../utils/icons';\nimport { formatPrice, formatDate, titleCase } from '../../utils/formatters';\n\nexport function CollectionDetailPage() {\n const { collectionId } = useParams<{ collectionId: string }>();\n const { navigateTo } = useCoolAndLovely();\n const query = useCollection(collectionId);\n const publish = usePublishCollection();\n const archive = useArchiveCollection();\n\n if (query.isLoading) {\n return (\n <PageLayout title=\"Collection\">\n <LoadingView label=\"Loading collection…\" />\n </PageLayout>\n );\n }\n\n if (query.isError || !query.data) {\n return (\n <PageLayout title=\"Collection\">\n <ErrorView title=\"Collection not found\" onRetry={() => void query.refetch()} />\n </PageLayout>\n );\n }\n\n const collection = query.data;\n const items = collection.items ?? [];\n const canPublish = collection.status === 'DRAFT' || collection.status === 'SCHEDULED';\n const isArchived = collection.status === 'ARCHIVED';\n\n return (\n <PageLayout\n title={collection.name}\n description={collection.brand?.name ?? undefined}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/collections')}\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 <button\n type=\"button\"\n onClick={() => navigateTo(`/collections/${collection.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 {canPublish && (\n <button\n type=\"button\"\n disabled={publish.isPending}\n onClick={() => publish.mutate(collection.id)}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {publish.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Rocket className=\"h-4 w-4\" />\n )}\n Publish\n </button>\n )}\n {!isArchived && (\n <button\n type=\"button\"\n disabled={archive.isPending}\n onClick={() =>\n archive.mutate(collection.id, { onSuccess: () => navigateTo('/collections') })\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 {archive.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Archive className=\"h-4 w-4\" />\n )}\n Archive\n </button>\n )}\n </div>\n }\n >\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"lg:col-span-2\">\n <GlassCard treatment=\"glass\" className=\"group overflow-hidden p-0\">\n <Thumbnail\n icon={<Shapes className=\"h-12 w-12\" />}\n seed={collection.id}\n fileId={collection.cover?.id}\n alt={collection.name}\n className=\"aspect-[16/9] w-full sm:aspect-[21/9]\"\n overlay={\n <>\n <div className=\"pointer-events-none absolute inset-0 bg-gradient-to-t from-bg-canvas via-bg-canvas/55 to-transparent\" />\n <div className=\"absolute inset-x-0 bottom-0 flex flex-wrap items-end justify-between gap-3 p-6\">\n <div className=\"min-w-0\">\n <h2 className=\"font-serif text-3xl font-semibold leading-tight text-text-primary\">\n {collection.name}\n </h2>\n <div className=\"mt-2 flex flex-wrap items-center gap-2 text-sm text-text-secondary\">\n {collection.brand?.name && <span>{collection.brand.name}</span>}\n {collection.brand?.name && (\n <span aria-hidden=\"true\" className=\"opacity-60\">\n ·\n </span>\n )}\n <span className=\"rounded-full border border-border-seam bg-bg-surface/80 px-2.5 py-0.5 text-xs font-medium text-text-primary backdrop-blur-sm\">\n {titleCase(collection.season)}\n </span>\n </div>\n </div>\n <StatusBadge status={collection.status} className=\"shrink-0 shadow-sm\" />\n </div>\n </>\n }\n />\n {(collection.description || collection.tags.length > 0) && (\n <div className=\"p-6\">\n {collection.description && (\n <p className=\"text-sm text-text-secondary\">{collection.description}</p>\n )}\n {collection.tags.length > 0 && (\n <TagList\n tags={collection.tags}\n className={collection.description ? 'mt-4' : undefined}\n />\n )}\n </div>\n )}\n </GlassCard>\n\n <section className=\"mt-6\">\n <h3 className=\"mb-3 text-lg font-semibold text-text-primary\">Items ({items.length})</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 in this collection 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=\"group 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, 28)}\n seed={item.id}\n fileId={item.image?.id}\n alt={item.name}\n className=\"aspect-[3/4] 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 font-semibold text-text-primary tabular-nums\">\n {formatPrice(item.price, item.currency)}\n </p>\n {item.colours.length > 0 && (\n <ColourSwatchRow colours={item.colours} max={5} className=\"mt-2\" />\n )}\n </div>\n </button>\n ))}\n </div>\n )}\n <button\n type=\"button\"\n onClick={() =>\n navigateTo(`/items/new?brandId=${collection.brandId}&collectionId=${collection.id}`)\n }\n className=\"mt-3 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 <Plus className=\"h-4 w-4\" /> Add item to this collection\n </button>\n </section>\n </div>\n\n <div className=\"space-y-4\">\n <GlassCard treatment=\"plain\" className=\"p-5\">\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">Details</h3>\n <dl className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Season</dt>\n <dd className=\"text-text-primary\">{titleCase(collection.season)}</dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Publish at</dt>\n <dd className=\"text-text-primary\">\n {collection.publishAt\n ? formatDate(collection.publishAt, { hour: undefined, minute: undefined })\n : '—'}\n </dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Ends at</dt>\n <dd className=\"text-text-primary\">\n {collection.endAt\n ? formatDate(collection.endAt, { hour: undefined, minute: undefined })\n : '—'}\n </dd>\n </div>\n </dl>\n </GlassCard>\n\n {collection.brand && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/brands/${collection.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={collection.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 {collection.brand.name}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">View brand</p>\n </div>\n </button>\n )}\n\n {publish.isError && (\n <p className=\"rounded-lg bg-status-error-bg-subtle px-3 py-2 text-xs text-status-error-text\">\n {publish.error instanceof Error ? publish.error.message : 'Publish failed'}\n </p>\n )}\n {archive.isError && (\n <p className=\"rounded-lg bg-status-error-bg-subtle px-3 py-2 text-xs text-status-error-text\">\n {archive.error instanceof Error ? archive.error.message : 'Archive failed'}\n </p>\n )}\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -9,7 +9,7 @@ import { SelectField as d, TextAreaField as f, TextField as p } from "../../comp
9
9
  import { ImageUploadField as m } from "../../components/ImageUploadField.js";
10
10
  import { useEffect as h, useMemo as g, useState as _ } from "react";
11
11
  import { ArrowLeft as v, Loader2 as y, Save as b, Shapes as x } from "lucide-react";
12
- import { useParams as S, useSearchParams as C } from "react-router-dom";
12
+ import { useParams as S, useSearchParams as C } from "react-router";
13
13
  import { jsx as w, jsxs as T } from "react/jsx-runtime";
14
14
  import { GlassCard as E } from "@burdenoff/fe-libs/ui";
15
15
  //#region src/coolandlovely/pages/collections/CollectionFormPage.tsx
@@ -1 +1 @@
1
- {"version":3,"file":"CollectionFormPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/collections/CollectionFormPage.tsx"],"sourcesContent":["/**\n * Collection Create / Edit Page\n * @module coolandlovely/pages/collections\n *\n * Drives `cooCreateCollection` (new) and `cooUpdateCollection` (edit).\n * Brand selection is required on create and locked on edit.\n */\n\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams, useSearchParams } from 'react-router-dom';\nimport { ArrowLeft, Loader2, Save, Shapes } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { TextField, TextAreaField, SelectField } from '../../components/Field';\nimport { ImageUploadField } from '../../components/ImageUploadField';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useCollection, useCreateCollection, useUpdateCollection, useBrands } from '../../hooks';\nimport { COO_FILE_FOLDERS } from '../../hooks/useFiles';\nimport { parseCsv, titleCase } from '../../utils/formatters';\nimport type { CooSeason } from '../../types';\n\nconst SEASONS: CooSeason[] = ['SPRING', 'SUMMER', 'AUTUMN', 'WINTER', 'RESORT', 'ALL_SEASON'];\n\ninterface FormState {\n brandId: string;\n name: string;\n description: string;\n season: CooSeason;\n tags: string;\n publishAt: string;\n endAt: string;\n coverFileId: string;\n}\n\nconst EMPTY: FormState = {\n brandId: '',\n name: '',\n description: '',\n season: 'ALL_SEASON',\n tags: '',\n publishAt: '',\n endAt: '',\n coverFileId: '',\n};\n\n/** ISO timestamp <input type=date> values use yyyy-mm-dd; convert to ISO. */\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\nfunction toDateInput(iso: string | null): string {\n if (!iso) return '';\n const d = new Date(iso);\n return Number.isNaN(d.getTime()) ? '' : d.toISOString().slice(0, 10);\n}\n\nexport function CollectionFormPage() {\n const { collectionId } = useParams<{ collectionId: string }>();\n const [searchParams] = useSearchParams();\n const isEdit = Boolean(collectionId);\n const { navigateTo } = useCoolAndLovely();\n\n const existing = useCollection(isEdit ? collectionId : undefined);\n const brands = useBrands({ pagination: { limit: 100, offset: 0 } });\n const createCollection = useCreateCollection();\n const updateCollection = useUpdateCollection();\n\n const [form, setForm] = useState<FormState>(() => ({\n ...EMPTY,\n brandId: searchParams.get('brandId') ?? '',\n }));\n const [errors, setErrors] = useState<Partial<Record<keyof FormState, string>>>({});\n\n useEffect(() => {\n if (isEdit && existing.data) {\n const c = existing.data;\n setForm({\n brandId: c.brandId,\n name: c.name,\n description: c.description ?? '',\n season: c.season,\n tags: c.tags.join(', '),\n publishAt: toDateInput(c.publishAt),\n endAt: toDateInput(c.endAt),\n coverFileId: c.cover?.id ?? '',\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 ? updateCollection : createCollection;\n\n const brandOptions = useMemo(\n () => (brands.data?.items ?? []).map((b) => ({ value: b.id, label: b.name })),\n [brands.data]\n );\n const seasonOptions = SEASONS.map((s) => ({ value: s, label: titleCase(s) }));\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.brandId) next.brandId = 'Brand 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 && collectionId) {\n updateCollection.mutate(\n {\n id: collectionId,\n input: {\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n season: form.season,\n tags: parseCsv(form.tags),\n publishAt: toIso(form.publishAt),\n endAt: toIso(form.endAt),\n coverFileId: form.coverFileId || undefined,\n },\n },\n { onSuccess: () => navigateTo(`/collections/${collectionId}`) }\n );\n } else {\n createCollection.mutate(\n {\n brandId: form.brandId,\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n season: form.season,\n tags: parseCsv(form.tags),\n publishAt: toIso(form.publishAt),\n endAt: toIso(form.endAt),\n coverFileId: form.coverFileId || undefined,\n },\n { onSuccess: (c) => navigateTo(`/collections/${c.id}`) }\n );\n }\n };\n\n if (isEdit && existing.isLoading) {\n return (\n <PageLayout title=\"Edit collection\">\n <LoadingView label=\"Loading collection…\" />\n </PageLayout>\n );\n }\n\n if (isEdit && (existing.isError || !existing.data)) {\n return (\n <PageLayout title=\"Edit collection\">\n <ErrorView title=\"Collection not found\" onRetry={() => void existing.refetch()} />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={isEdit ? 'Edit collection' : 'New collection'}\n description={isEdit ? 'Update this seasonal drop' : 'Group pieces into a seasonal drop'}\n actions={\n <button\n type=\"button\"\n onClick={() =>\n navigateTo(isEdit && collectionId ? `/collections/${collectionId}` : '/collections')\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-accent-blue-subtle text-accent-blue\">\n <Shapes className=\"h-5 w-5\" />\n </div>\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">Collection details</h2>\n <p className=\"text-xs text-text-secondary\">A collection belongs to one brand.</p>\n </div>\n </div>\n\n {!isEdit && (\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 className=\"mb-4\"\n />\n )}\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <TextField\n id=\"name\"\n label=\"Name\"\n value={form.name}\n onChange={(v) => setField('name', v)}\n placeholder=\"Spring Awakening\"\n required\n error={errors.name}\n />\n <SelectField\n id=\"season\"\n label=\"Season\"\n value={form.season}\n onChange={(v) => setField('season', v as CooSeason)}\n options={seasonOptions}\n />\n </div>\n\n <TextAreaField\n id=\"description\"\n label=\"Description\"\n value={form.description}\n onChange={(v) => setField('description', v)}\n placeholder=\"Describe the drop…\"\n rows={3}\n className=\"mt-4\"\n />\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <TextField\n id=\"publishAt\"\n label=\"Publish date\"\n type=\"date\"\n value={form.publishAt}\n onChange={(v) => setField('publishAt', v)}\n />\n <TextField\n id=\"endAt\"\n label=\"End date\"\n type=\"date\"\n value={form.endAt}\n onChange={(v) => setField('endAt', v)}\n />\n </div>\n\n <TextField\n id=\"tags\"\n label=\"Tags\"\n value={form.tags}\n onChange={(v) => setField('tags', v)}\n placeholder=\"pastel, floral\"\n hint=\"Comma-separated\"\n className=\"mt-4\"\n />\n\n <ImageUploadField\n label=\"Cover image\"\n aspect=\"wide\"\n value={form.coverFileId}\n onChange={(id) => setField('coverFileId', id)}\n folder={COO_FILE_FOLDERS.collections}\n hint=\"Represents the drop on cards and the collection page\"\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=\"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 collection'}\n </button>\n </div>\n </GlassCard>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"CollectionFormPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/collections/CollectionFormPage.tsx"],"sourcesContent":["/**\n * Collection Create / Edit Page\n * @module coolandlovely/pages/collections\n *\n * Drives `cooCreateCollection` (new) and `cooUpdateCollection` (edit).\n * Brand selection is required on create and locked on edit.\n */\n\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams, useSearchParams } from 'react-router';\nimport { ArrowLeft, Loader2, Save, Shapes } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { TextField, TextAreaField, SelectField } from '../../components/Field';\nimport { ImageUploadField } from '../../components/ImageUploadField';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useCollection, useCreateCollection, useUpdateCollection, useBrands } from '../../hooks';\nimport { COO_FILE_FOLDERS } from '../../hooks/useFiles';\nimport { parseCsv, titleCase } from '../../utils/formatters';\nimport type { CooSeason } from '../../types';\n\nconst SEASONS: CooSeason[] = ['SPRING', 'SUMMER', 'AUTUMN', 'WINTER', 'RESORT', 'ALL_SEASON'];\n\ninterface FormState {\n brandId: string;\n name: string;\n description: string;\n season: CooSeason;\n tags: string;\n publishAt: string;\n endAt: string;\n coverFileId: string;\n}\n\nconst EMPTY: FormState = {\n brandId: '',\n name: '',\n description: '',\n season: 'ALL_SEASON',\n tags: '',\n publishAt: '',\n endAt: '',\n coverFileId: '',\n};\n\n/** ISO timestamp <input type=date> values use yyyy-mm-dd; convert to ISO. */\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\nfunction toDateInput(iso: string | null): string {\n if (!iso) return '';\n const d = new Date(iso);\n return Number.isNaN(d.getTime()) ? '' : d.toISOString().slice(0, 10);\n}\n\nexport function CollectionFormPage() {\n const { collectionId } = useParams<{ collectionId: string }>();\n const [searchParams] = useSearchParams();\n const isEdit = Boolean(collectionId);\n const { navigateTo } = useCoolAndLovely();\n\n const existing = useCollection(isEdit ? collectionId : undefined);\n const brands = useBrands({ pagination: { limit: 100, offset: 0 } });\n const createCollection = useCreateCollection();\n const updateCollection = useUpdateCollection();\n\n const [form, setForm] = useState<FormState>(() => ({\n ...EMPTY,\n brandId: searchParams.get('brandId') ?? '',\n }));\n const [errors, setErrors] = useState<Partial<Record<keyof FormState, string>>>({});\n\n useEffect(() => {\n if (isEdit && existing.data) {\n const c = existing.data;\n setForm({\n brandId: c.brandId,\n name: c.name,\n description: c.description ?? '',\n season: c.season,\n tags: c.tags.join(', '),\n publishAt: toDateInput(c.publishAt),\n endAt: toDateInput(c.endAt),\n coverFileId: c.cover?.id ?? '',\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 ? updateCollection : createCollection;\n\n const brandOptions = useMemo(\n () => (brands.data?.items ?? []).map((b) => ({ value: b.id, label: b.name })),\n [brands.data]\n );\n const seasonOptions = SEASONS.map((s) => ({ value: s, label: titleCase(s) }));\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.brandId) next.brandId = 'Brand 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 && collectionId) {\n updateCollection.mutate(\n {\n id: collectionId,\n input: {\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n season: form.season,\n tags: parseCsv(form.tags),\n publishAt: toIso(form.publishAt),\n endAt: toIso(form.endAt),\n coverFileId: form.coverFileId || undefined,\n },\n },\n { onSuccess: () => navigateTo(`/collections/${collectionId}`) }\n );\n } else {\n createCollection.mutate(\n {\n brandId: form.brandId,\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n season: form.season,\n tags: parseCsv(form.tags),\n publishAt: toIso(form.publishAt),\n endAt: toIso(form.endAt),\n coverFileId: form.coverFileId || undefined,\n },\n { onSuccess: (c) => navigateTo(`/collections/${c.id}`) }\n );\n }\n };\n\n if (isEdit && existing.isLoading) {\n return (\n <PageLayout title=\"Edit collection\">\n <LoadingView label=\"Loading collection…\" />\n </PageLayout>\n );\n }\n\n if (isEdit && (existing.isError || !existing.data)) {\n return (\n <PageLayout title=\"Edit collection\">\n <ErrorView title=\"Collection not found\" onRetry={() => void existing.refetch()} />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={isEdit ? 'Edit collection' : 'New collection'}\n description={isEdit ? 'Update this seasonal drop' : 'Group pieces into a seasonal drop'}\n actions={\n <button\n type=\"button\"\n onClick={() =>\n navigateTo(isEdit && collectionId ? `/collections/${collectionId}` : '/collections')\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-accent-blue-subtle text-accent-blue\">\n <Shapes className=\"h-5 w-5\" />\n </div>\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">Collection details</h2>\n <p className=\"text-xs text-text-secondary\">A collection belongs to one brand.</p>\n </div>\n </div>\n\n {!isEdit && (\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 className=\"mb-4\"\n />\n )}\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <TextField\n id=\"name\"\n label=\"Name\"\n value={form.name}\n onChange={(v) => setField('name', v)}\n placeholder=\"Spring Awakening\"\n required\n error={errors.name}\n />\n <SelectField\n id=\"season\"\n label=\"Season\"\n value={form.season}\n onChange={(v) => setField('season', v as CooSeason)}\n options={seasonOptions}\n />\n </div>\n\n <TextAreaField\n id=\"description\"\n label=\"Description\"\n value={form.description}\n onChange={(v) => setField('description', v)}\n placeholder=\"Describe the drop…\"\n rows={3}\n className=\"mt-4\"\n />\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <TextField\n id=\"publishAt\"\n label=\"Publish date\"\n type=\"date\"\n value={form.publishAt}\n onChange={(v) => setField('publishAt', v)}\n />\n <TextField\n id=\"endAt\"\n label=\"End date\"\n type=\"date\"\n value={form.endAt}\n onChange={(v) => setField('endAt', v)}\n />\n </div>\n\n <TextField\n id=\"tags\"\n label=\"Tags\"\n value={form.tags}\n onChange={(v) => setField('tags', v)}\n placeholder=\"pastel, floral\"\n hint=\"Comma-separated\"\n className=\"mt-4\"\n />\n\n <ImageUploadField\n label=\"Cover image\"\n aspect=\"wide\"\n value={form.coverFileId}\n onChange={(id) => setField('coverFileId', id)}\n folder={COO_FILE_FOLDERS.collections}\n hint=\"Represents the drop on cards and the collection page\"\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=\"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 collection'}\n </button>\n </div>\n </GlassCard>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -12,7 +12,7 @@ import { categoryIcon as m } from "../../utils/icons.js";
12
12
  import { TagList as h } from "../../components/TagList.js";
13
13
  import { ColourSwatchRow as g } from "../../components/ColourSwatchRow.js";
14
14
  import { Archive as _, ArrowLeft as v, ChevronRight as y, Loader2 as b, Pencil as x, Shapes as S } from "lucide-react";
15
- import { useParams as C } from "react-router-dom";
15
+ import { useParams as C } from "react-router";
16
16
  import { jsx as w, jsxs as T } from "react/jsx-runtime";
17
17
  import { GlassCard as E } from "@burdenoff/fe-libs/ui";
18
18
  //#region src/coolandlovely/pages/items/StyleItemDetailPage.tsx
@@ -1 +1 @@
1
- {"version":3,"file":"StyleItemDetailPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/items/StyleItemDetailPage.tsx"],"sourcesContent":["/**\n * Style Item Detail Page\n * @module coolandlovely/pages/items\n *\n * Renders a single catalogue piece (`cooStyleItem`) with edit + archive\n * (`cooArchiveStyleItem`) actions.\n */\n\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, Pencil, Archive, Loader2, ChevronRight, Shapes } 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 { LookCard } from '../../components/LookCard';\nimport { TagList } from '../../components/TagList';\nimport { ColourSwatchRow } from '../../components/ColourSwatchRow';\nimport { AddToLookbookButton } from '../../components/AddToLookbookButton';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useStyleItem, useArchiveStyleItem, useLooksWithItems } from '../../hooks';\nimport { categoryIcon } from '../../utils/icons';\nimport { formatPrice, formatDate, titleCase } from '../../utils/formatters';\n\nexport function StyleItemDetailPage() {\n const { itemId } = useParams<{ itemId: string }>();\n const { navigateTo } = useCoolAndLovely();\n const query = useStyleItem(itemId);\n const looksQuery = useLooksWithItems({ pagination: { limit: 100, offset: 0 } });\n const archive = useArchiveStyleItem();\n\n if (query.isLoading) {\n return (\n <PageLayout title=\"Style item\">\n <LoadingView label=\"Loading item…\" />\n </PageLayout>\n );\n }\n\n if (query.isError || !query.data) {\n return (\n <PageLayout title=\"Style item\">\n <ErrorView title=\"Item not found\" onRetry={() => void query.refetch()} />\n </PageLayout>\n );\n }\n\n const item = query.data;\n const isArchived = item.status === 'RETIRED';\n const appearsInLooks = (looksQuery.data?.items ?? [])\n .filter((look) => look.items?.some((lookItem) => lookItem.styleItemId === item.id))\n .slice(0, 4);\n\n return (\n <PageLayout\n title={item.name}\n description={item.brand?.name ?? undefined}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/items')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n <AddToLookbookButton styleItemId={item.id} />\n <button\n type=\"button\"\n onClick={() => navigateTo(`/items/${item.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 transition-colors hover:bg-action-primary-bg/90\"\n >\n <Pencil className=\"h-4 w-4\" /> Edit\n </button>\n {!isArchived && (\n <button\n type=\"button\"\n disabled={archive.isPending}\n onClick={() => archive.mutate(item.id, { onSuccess: () => navigateTo('/items') })}\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 {archive.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Archive className=\"h-4 w-4\" />\n )}\n Archive\n </button>\n )}\n </div>\n }\n >\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Product image */}\n <div className=\"lg:col-span-2\">\n <GlassCard treatment=\"glass\" className=\"group overflow-hidden p-0\">\n <Thumbnail\n icon={categoryIcon(item.category, 48)}\n seed={item.id}\n fileId={item.image?.id}\n alt={item.name}\n className=\"aspect-[4/5] w-full\"\n />\n {item.description && (\n <div className=\"p-6\">\n <p className=\"text-sm leading-relaxed text-text-secondary\">{item.description}</p>\n </div>\n )}\n </GlassCard>\n </div>\n\n {/* Product panel */}\n <div className=\"space-y-4\">\n <GlassCard treatment=\"glass\" className=\"space-y-5 p-6\">\n <div>\n <h2 className=\"text-2xl font-semibold leading-tight text-text-primary\">\n {item.name}\n </h2>\n {item.brand && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/brands/${item.brand?.id}`)}\n className=\"mt-1 inline-flex items-center gap-0.5 text-sm font-medium text-action-primary-bg hover:underline\"\n >\n {item.brand.name}\n <ChevronRight className=\"h-3.5 w-3.5\" />\n </button>\n )}\n <div className=\"mt-3 flex flex-wrap items-center justify-between gap-2\">\n <p className=\"text-3xl font-bold text-text-primary tabular-nums\">\n {formatPrice(item.price, item.currency)}\n <span className=\"ml-2 text-base font-medium text-text-secondary\">\n {item.currency}\n </span>\n </p>\n <StatusBadge status={item.status} />\n </div>\n </div>\n\n <div className=\"border-t border-border-seam\" />\n\n {item.colours.length > 0 && (\n <div>\n <p className=\"mb-2 text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Colours\n </p>\n <ColourSwatchRow colours={item.colours} max={12} />\n </div>\n )}\n\n {item.sizes.length > 0 && (\n <div>\n <p className=\"mb-2 text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Size\n </p>\n <div className=\"flex flex-wrap gap-2\">\n {item.sizes.map((size) => (\n <span\n key={size}\n className=\"inline-flex min-w-9 items-center justify-center rounded-full border border-border-seam px-3 py-1 text-sm font-medium text-text-primary\"\n >\n {size}\n </span>\n ))}\n </div>\n </div>\n )}\n\n {item.materials.length > 0 && (\n <div>\n <p className=\"mb-1 text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Materials\n </p>\n <p className=\"text-sm text-text-primary\">{item.materials.join(', ')}</p>\n </div>\n )}\n\n {item.tags.length > 0 && (\n <div>\n <p className=\"mb-2 text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Tags\n </p>\n <TagList tags={item.tags} max={12} tone=\"accent\" />\n </div>\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\">Category</dt>\n <dd className=\"text-text-primary\">{titleCase(item.category)}</dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">SKU</dt>\n <dd className=\"text-text-primary\">{item.sku ?? '—'}</dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Currency</dt>\n <dd className=\"text-text-primary\">{item.currency}</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(item.createdAt, { hour: undefined, minute: undefined })}\n </dd>\n </div>\n </dl>\n </GlassCard>\n\n {item.collection && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/collections/${item.collection?.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={<Shapes className=\"h-5 w-5\" />}\n seed={item.collection.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 {item.collection.name}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">View collection</p>\n </div>\n </button>\n )}\n </div>\n </div>\n\n <section className=\"mt-8\">\n <div className=\"mb-3 flex items-center justify-between gap-3\">\n <h3 className=\"text-lg font-semibold text-text-primary\">Appears in looks</h3>\n {appearsInLooks.length > 0 && (\n <button\n type=\"button\"\n onClick={() => navigateTo('/looks')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-action-primary-bg hover:underline\"\n >\n View all <ChevronRight className=\"h-3.5 w-3.5\" />\n </button>\n )}\n </div>\n {looksQuery.isLoading ? (\n <div className=\"flex items-center gap-2 rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n <Loader2 className=\"h-4 w-4 animate-spin\" /> Loading referenced looks…\n </div>\n ) : appearsInLooks.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n This piece has not been tagged in any looks yet.\n </p>\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n {appearsInLooks.map((look) => (\n <LookCard\n key={look.id}\n id={look.id}\n title={look.title}\n occasion={look.occasion}\n imageFileId={look.image?.id}\n likeCount={look.likeCount}\n isLikedByMe={look.isLikedByMe}\n isSavedByMe={look.isSavedByMe}\n onOpen={() => navigateTo(`/looks/${look.id}`)}\n showActions\n />\n ))}\n </div>\n )}\n </section>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"StyleItemDetailPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/items/StyleItemDetailPage.tsx"],"sourcesContent":["/**\n * Style Item Detail Page\n * @module coolandlovely/pages/items\n *\n * Renders a single catalogue piece (`cooStyleItem`) with edit + archive\n * (`cooArchiveStyleItem`) actions.\n */\n\nimport { useParams } from 'react-router';\nimport { ArrowLeft, Pencil, Archive, Loader2, ChevronRight, Shapes } 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 { LookCard } from '../../components/LookCard';\nimport { TagList } from '../../components/TagList';\nimport { ColourSwatchRow } from '../../components/ColourSwatchRow';\nimport { AddToLookbookButton } from '../../components/AddToLookbookButton';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useStyleItem, useArchiveStyleItem, useLooksWithItems } from '../../hooks';\nimport { categoryIcon } from '../../utils/icons';\nimport { formatPrice, formatDate, titleCase } from '../../utils/formatters';\n\nexport function StyleItemDetailPage() {\n const { itemId } = useParams<{ itemId: string }>();\n const { navigateTo } = useCoolAndLovely();\n const query = useStyleItem(itemId);\n const looksQuery = useLooksWithItems({ pagination: { limit: 100, offset: 0 } });\n const archive = useArchiveStyleItem();\n\n if (query.isLoading) {\n return (\n <PageLayout title=\"Style item\">\n <LoadingView label=\"Loading item…\" />\n </PageLayout>\n );\n }\n\n if (query.isError || !query.data) {\n return (\n <PageLayout title=\"Style item\">\n <ErrorView title=\"Item not found\" onRetry={() => void query.refetch()} />\n </PageLayout>\n );\n }\n\n const item = query.data;\n const isArchived = item.status === 'RETIRED';\n const appearsInLooks = (looksQuery.data?.items ?? [])\n .filter((look) => look.items?.some((lookItem) => lookItem.styleItemId === item.id))\n .slice(0, 4);\n\n return (\n <PageLayout\n title={item.name}\n description={item.brand?.name ?? undefined}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/items')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n <AddToLookbookButton styleItemId={item.id} />\n <button\n type=\"button\"\n onClick={() => navigateTo(`/items/${item.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 transition-colors hover:bg-action-primary-bg/90\"\n >\n <Pencil className=\"h-4 w-4\" /> Edit\n </button>\n {!isArchived && (\n <button\n type=\"button\"\n disabled={archive.isPending}\n onClick={() => archive.mutate(item.id, { onSuccess: () => navigateTo('/items') })}\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 {archive.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Archive className=\"h-4 w-4\" />\n )}\n Archive\n </button>\n )}\n </div>\n }\n >\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Product image */}\n <div className=\"lg:col-span-2\">\n <GlassCard treatment=\"glass\" className=\"group overflow-hidden p-0\">\n <Thumbnail\n icon={categoryIcon(item.category, 48)}\n seed={item.id}\n fileId={item.image?.id}\n alt={item.name}\n className=\"aspect-[4/5] w-full\"\n />\n {item.description && (\n <div className=\"p-6\">\n <p className=\"text-sm leading-relaxed text-text-secondary\">{item.description}</p>\n </div>\n )}\n </GlassCard>\n </div>\n\n {/* Product panel */}\n <div className=\"space-y-4\">\n <GlassCard treatment=\"glass\" className=\"space-y-5 p-6\">\n <div>\n <h2 className=\"text-2xl font-semibold leading-tight text-text-primary\">\n {item.name}\n </h2>\n {item.brand && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/brands/${item.brand?.id}`)}\n className=\"mt-1 inline-flex items-center gap-0.5 text-sm font-medium text-action-primary-bg hover:underline\"\n >\n {item.brand.name}\n <ChevronRight className=\"h-3.5 w-3.5\" />\n </button>\n )}\n <div className=\"mt-3 flex flex-wrap items-center justify-between gap-2\">\n <p className=\"text-3xl font-bold text-text-primary tabular-nums\">\n {formatPrice(item.price, item.currency)}\n <span className=\"ml-2 text-base font-medium text-text-secondary\">\n {item.currency}\n </span>\n </p>\n <StatusBadge status={item.status} />\n </div>\n </div>\n\n <div className=\"border-t border-border-seam\" />\n\n {item.colours.length > 0 && (\n <div>\n <p className=\"mb-2 text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Colours\n </p>\n <ColourSwatchRow colours={item.colours} max={12} />\n </div>\n )}\n\n {item.sizes.length > 0 && (\n <div>\n <p className=\"mb-2 text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Size\n </p>\n <div className=\"flex flex-wrap gap-2\">\n {item.sizes.map((size) => (\n <span\n key={size}\n className=\"inline-flex min-w-9 items-center justify-center rounded-full border border-border-seam px-3 py-1 text-sm font-medium text-text-primary\"\n >\n {size}\n </span>\n ))}\n </div>\n </div>\n )}\n\n {item.materials.length > 0 && (\n <div>\n <p className=\"mb-1 text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Materials\n </p>\n <p className=\"text-sm text-text-primary\">{item.materials.join(', ')}</p>\n </div>\n )}\n\n {item.tags.length > 0 && (\n <div>\n <p className=\"mb-2 text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Tags\n </p>\n <TagList tags={item.tags} max={12} tone=\"accent\" />\n </div>\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\">Category</dt>\n <dd className=\"text-text-primary\">{titleCase(item.category)}</dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">SKU</dt>\n <dd className=\"text-text-primary\">{item.sku ?? '—'}</dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Currency</dt>\n <dd className=\"text-text-primary\">{item.currency}</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(item.createdAt, { hour: undefined, minute: undefined })}\n </dd>\n </div>\n </dl>\n </GlassCard>\n\n {item.collection && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/collections/${item.collection?.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={<Shapes className=\"h-5 w-5\" />}\n seed={item.collection.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 {item.collection.name}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">View collection</p>\n </div>\n </button>\n )}\n </div>\n </div>\n\n <section className=\"mt-8\">\n <div className=\"mb-3 flex items-center justify-between gap-3\">\n <h3 className=\"text-lg font-semibold text-text-primary\">Appears in looks</h3>\n {appearsInLooks.length > 0 && (\n <button\n type=\"button\"\n onClick={() => navigateTo('/looks')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-action-primary-bg hover:underline\"\n >\n View all <ChevronRight className=\"h-3.5 w-3.5\" />\n </button>\n )}\n </div>\n {looksQuery.isLoading ? (\n <div className=\"flex items-center gap-2 rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n <Loader2 className=\"h-4 w-4 animate-spin\" /> Loading referenced looks…\n </div>\n ) : appearsInLooks.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n This piece has not been tagged in any looks yet.\n </p>\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n {appearsInLooks.map((look) => (\n <LookCard\n key={look.id}\n id={look.id}\n title={look.title}\n occasion={look.occasion}\n imageFileId={look.image?.id}\n likeCount={look.likeCount}\n isLikedByMe={look.isLikedByMe}\n isSavedByMe={look.isSavedByMe}\n onOpen={() => navigateTo(`/looks/${look.id}`)}\n showActions\n />\n ))}\n </div>\n )}\n </section>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -10,7 +10,7 @@ import { SelectField as p, TextAreaField as m, TextField as h } from "../../comp
10
10
  import { ImageUploadField as g } from "../../components/ImageUploadField.js";
11
11
  import { useEffect as _, useMemo as v, useState as y } from "react";
12
12
  import { ArrowLeft as b, Loader2 as x, Save as S, Shirt as C } from "lucide-react";
13
- import { useParams as w, useSearchParams as T } from "react-router-dom";
13
+ import { useParams as w, useSearchParams as T } from "react-router";
14
14
  import { jsx as E, jsxs as D } from "react/jsx-runtime";
15
15
  import { GlassCard as O } from "@burdenoff/fe-libs/ui";
16
16
  //#region src/coolandlovely/pages/items/StyleItemFormPage.tsx
@@ -1 +1 @@
1
- {"version":3,"file":"StyleItemFormPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/items/StyleItemFormPage.tsx"],"sourcesContent":["/**\n * Style Item Create / Edit Page\n * @module coolandlovely/pages/items\n *\n * Drives `cooCreateStyleItem` (new) and `cooUpdateStyleItem` (edit).\n * Brand selection is required on create (and locked on edit). The collection\n * select is scoped to the chosen brand.\n */\n\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams, useSearchParams } from 'react-router-dom';\nimport { ArrowLeft, Loader2, Save, Shirt } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { TextField, TextAreaField, SelectField } from '../../components/Field';\nimport { ImageUploadField } from '../../components/ImageUploadField';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport {\n useStyleItem,\n useCreateStyleItem,\n useUpdateStyleItem,\n useBrands,\n useCollections,\n} from '../../hooks';\nimport { COO_FILE_FOLDERS } from '../../hooks/useFiles';\nimport { STYLE_CATEGORIES } from '../../utils/icons';\nimport { parseCsv, titleCase } from '../../utils/formatters';\nimport type { CooStyleCategory } from '../../types';\n\ninterface FormState {\n brandId: string;\n collectionId: string;\n name: string;\n description: string;\n category: CooStyleCategory;\n sku: string;\n price: string;\n currency: string;\n colours: string;\n sizes: string;\n materials: string;\n tags: string;\n imageFileId: string;\n}\n\nconst EMPTY: FormState = {\n brandId: '',\n collectionId: '',\n name: '',\n description: '',\n category: 'OTHER',\n sku: '',\n price: '',\n currency: 'USD',\n colours: '',\n sizes: '',\n materials: '',\n tags: '',\n imageFileId: '',\n};\n\nexport function StyleItemFormPage() {\n const { itemId } = useParams<{ itemId: string }>();\n const [searchParams] = useSearchParams();\n const isEdit = Boolean(itemId);\n const { navigateTo } = useCoolAndLovely();\n\n const existing = useStyleItem(isEdit ? itemId : undefined);\n const brands = useBrands({ pagination: { limit: 100, offset: 0 } });\n const createItem = useCreateStyleItem();\n const updateItem = useUpdateStyleItem();\n\n const [form, setForm] = useState<FormState>(() => ({\n ...EMPTY,\n brandId: searchParams.get('brandId') ?? '',\n collectionId: searchParams.get('collectionId') ?? '',\n }));\n const [errors, setErrors] = useState<Partial<Record<keyof FormState, string>>>({});\n\n // Collections scoped to the selected brand (for the optional collection select)\n const collections = useCollections(form.brandId ? { brandId: form.brandId } : {});\n\n useEffect(() => {\n if (isEdit && existing.data) {\n const it = existing.data;\n setForm({\n brandId: it.brandId,\n collectionId: it.collectionId ?? '',\n name: it.name,\n description: it.description ?? '',\n category: it.category,\n sku: it.sku ?? '',\n price: String(it.price),\n currency: it.currency,\n colours: it.colours.join(', '),\n sizes: it.sizes.join(', '),\n materials: it.materials.join(', '),\n tags: it.tags.join(', '),\n imageFileId: it.image?.id ?? '',\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 ? updateItem : createItem;\n\n const brandOptions = useMemo(\n () => (brands.data?.items ?? []).map((b) => ({ value: b.id, label: b.name })),\n [brands.data]\n );\n const collectionOptions = useMemo(\n () => (collections.data ?? []).map((c) => ({ value: c.id, label: c.name })),\n [collections.data]\n );\n const categoryOptions = STYLE_CATEGORIES.map((c) => ({ value: c, label: titleCase(c) }));\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.brandId) next.brandId = 'Brand is required';\n if (form.price && Number.isNaN(Number(form.price))) next.price = 'Price must be a number';\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 const priceNum = form.price.trim() ? Number(form.price) : undefined;\n\n if (isEdit && itemId) {\n updateItem.mutate(\n {\n id: itemId,\n input: {\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n category: form.category,\n collectionId: form.collectionId || undefined,\n sku: form.sku.trim() || undefined,\n price: priceNum,\n currency: form.currency.trim() || undefined,\n colours: parseCsv(form.colours),\n sizes: parseCsv(form.sizes),\n materials: parseCsv(form.materials),\n tags: parseCsv(form.tags),\n imageFileId: form.imageFileId || undefined,\n },\n },\n { onSuccess: () => navigateTo(`/items/${itemId}`) }\n );\n } else {\n createItem.mutate(\n {\n brandId: form.brandId,\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n category: form.category,\n collectionId: form.collectionId || undefined,\n sku: form.sku.trim() || undefined,\n price: priceNum,\n currency: form.currency.trim() || undefined,\n colours: parseCsv(form.colours),\n sizes: parseCsv(form.sizes),\n materials: parseCsv(form.materials),\n tags: parseCsv(form.tags),\n imageFileId: form.imageFileId || undefined,\n },\n { onSuccess: (item) => navigateTo(`/items/${item.id}`) }\n );\n }\n };\n\n if (isEdit && existing.isLoading) {\n return (\n <PageLayout title=\"Edit item\">\n <LoadingView label=\"Loading item…\" />\n </PageLayout>\n );\n }\n\n if (isEdit && (existing.isError || !existing.data)) {\n return (\n <PageLayout title=\"Edit item\">\n <ErrorView title=\"Item not found\" onRetry={() => void existing.refetch()} />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={isEdit ? 'Edit item' : 'New style item'}\n description={isEdit ? 'Update this catalogue piece' : 'List a new shoppable piece'}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo(isEdit && itemId ? `/items/${itemId}` : '/items')}\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-[var(--color-accent-soft)] text-text-primary\">\n <Shirt className=\"h-5 w-5\" />\n </div>\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">Item details</h2>\n <p className=\"text-xs text-text-secondary\">\n A piece belongs to a brand and optionally a collection.\n </p>\n </div>\n </div>\n\n {!isEdit && (\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 className=\"mb-4\"\n />\n )}\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <TextField\n id=\"name\"\n label=\"Name\"\n value={form.name}\n onChange={(v) => setField('name', v)}\n placeholder=\"Silk slip dress\"\n required\n error={errors.name}\n />\n <SelectField\n id=\"category\"\n label=\"Category\"\n value={form.category}\n onChange={(v) => setField('category', v as CooStyleCategory)}\n options={categoryOptions}\n />\n </div>\n\n <TextAreaField\n id=\"description\"\n label=\"Description\"\n value={form.description}\n onChange={(v) => setField('description', v)}\n placeholder=\"Describe the piece…\"\n rows={3}\n className=\"mt-4\"\n />\n\n <ImageUploadField\n label=\"Product photo\"\n value={form.imageFileId}\n onChange={(id) => setField('imageFileId', id)}\n folder={COO_FILE_FOLDERS.items}\n hint=\"Shown across the catalogue and item page\"\n className=\"mt-4\"\n />\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <TextField\n id=\"price\"\n label=\"Price\"\n type=\"number\"\n value={form.price}\n onChange={(v) => setField('price', v)}\n placeholder=\"0.00\"\n error={errors.price}\n />\n <TextField\n id=\"currency\"\n label=\"Currency\"\n value={form.currency}\n onChange={(v) => setField('currency', v)}\n placeholder=\"USD\"\n />\n <TextField\n id=\"sku\"\n label=\"SKU\"\n value={form.sku}\n onChange={(v) => setField('sku', v)}\n placeholder=\"ABC-123\"\n />\n </div>\n\n <SelectField\n id=\"collectionId\"\n label=\"Collection (optional)\"\n value={form.collectionId}\n onChange={(v) => setField('collectionId', v)}\n options={collectionOptions}\n placeholder={\n !form.brandId\n ? 'Select a brand first'\n : collections.isLoading\n ? 'Loading collections…'\n : 'No collection'\n }\n className=\"mt-4\"\n />\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <TextField\n id=\"colours\"\n label=\"Colours\"\n value={form.colours}\n onChange={(v) => setField('colours', v)}\n placeholder=\"black, ivory\"\n hint=\"Comma-separated\"\n />\n <TextField\n id=\"sizes\"\n label=\"Sizes\"\n value={form.sizes}\n onChange={(v) => setField('sizes', v)}\n placeholder=\"XS, S, M, L\"\n hint=\"Comma-separated\"\n />\n <TextField\n id=\"materials\"\n label=\"Materials\"\n value={form.materials}\n onChange={(v) => setField('materials', v)}\n placeholder=\"silk, cotton\"\n hint=\"Comma-separated\"\n />\n <TextField\n id=\"tags\"\n label=\"Tags\"\n value={form.tags}\n onChange={(v) => setField('tags', v)}\n placeholder=\"summer, party\"\n hint=\"Comma-separated\"\n />\n </div>\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}\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 item'}\n </button>\n </div>\n </GlassCard>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"StyleItemFormPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/items/StyleItemFormPage.tsx"],"sourcesContent":["/**\n * Style Item Create / Edit Page\n * @module coolandlovely/pages/items\n *\n * Drives `cooCreateStyleItem` (new) and `cooUpdateStyleItem` (edit).\n * Brand selection is required on create (and locked on edit). The collection\n * select is scoped to the chosen brand.\n */\n\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams, useSearchParams } from 'react-router';\nimport { ArrowLeft, Loader2, Save, Shirt } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { TextField, TextAreaField, SelectField } from '../../components/Field';\nimport { ImageUploadField } from '../../components/ImageUploadField';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport {\n useStyleItem,\n useCreateStyleItem,\n useUpdateStyleItem,\n useBrands,\n useCollections,\n} from '../../hooks';\nimport { COO_FILE_FOLDERS } from '../../hooks/useFiles';\nimport { STYLE_CATEGORIES } from '../../utils/icons';\nimport { parseCsv, titleCase } from '../../utils/formatters';\nimport type { CooStyleCategory } from '../../types';\n\ninterface FormState {\n brandId: string;\n collectionId: string;\n name: string;\n description: string;\n category: CooStyleCategory;\n sku: string;\n price: string;\n currency: string;\n colours: string;\n sizes: string;\n materials: string;\n tags: string;\n imageFileId: string;\n}\n\nconst EMPTY: FormState = {\n brandId: '',\n collectionId: '',\n name: '',\n description: '',\n category: 'OTHER',\n sku: '',\n price: '',\n currency: 'USD',\n colours: '',\n sizes: '',\n materials: '',\n tags: '',\n imageFileId: '',\n};\n\nexport function StyleItemFormPage() {\n const { itemId } = useParams<{ itemId: string }>();\n const [searchParams] = useSearchParams();\n const isEdit = Boolean(itemId);\n const { navigateTo } = useCoolAndLovely();\n\n const existing = useStyleItem(isEdit ? itemId : undefined);\n const brands = useBrands({ pagination: { limit: 100, offset: 0 } });\n const createItem = useCreateStyleItem();\n const updateItem = useUpdateStyleItem();\n\n const [form, setForm] = useState<FormState>(() => ({\n ...EMPTY,\n brandId: searchParams.get('brandId') ?? '',\n collectionId: searchParams.get('collectionId') ?? '',\n }));\n const [errors, setErrors] = useState<Partial<Record<keyof FormState, string>>>({});\n\n // Collections scoped to the selected brand (for the optional collection select)\n const collections = useCollections(form.brandId ? { brandId: form.brandId } : {});\n\n useEffect(() => {\n if (isEdit && existing.data) {\n const it = existing.data;\n setForm({\n brandId: it.brandId,\n collectionId: it.collectionId ?? '',\n name: it.name,\n description: it.description ?? '',\n category: it.category,\n sku: it.sku ?? '',\n price: String(it.price),\n currency: it.currency,\n colours: it.colours.join(', '),\n sizes: it.sizes.join(', '),\n materials: it.materials.join(', '),\n tags: it.tags.join(', '),\n imageFileId: it.image?.id ?? '',\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 ? updateItem : createItem;\n\n const brandOptions = useMemo(\n () => (brands.data?.items ?? []).map((b) => ({ value: b.id, label: b.name })),\n [brands.data]\n );\n const collectionOptions = useMemo(\n () => (collections.data ?? []).map((c) => ({ value: c.id, label: c.name })),\n [collections.data]\n );\n const categoryOptions = STYLE_CATEGORIES.map((c) => ({ value: c, label: titleCase(c) }));\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.brandId) next.brandId = 'Brand is required';\n if (form.price && Number.isNaN(Number(form.price))) next.price = 'Price must be a number';\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 const priceNum = form.price.trim() ? Number(form.price) : undefined;\n\n if (isEdit && itemId) {\n updateItem.mutate(\n {\n id: itemId,\n input: {\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n category: form.category,\n collectionId: form.collectionId || undefined,\n sku: form.sku.trim() || undefined,\n price: priceNum,\n currency: form.currency.trim() || undefined,\n colours: parseCsv(form.colours),\n sizes: parseCsv(form.sizes),\n materials: parseCsv(form.materials),\n tags: parseCsv(form.tags),\n imageFileId: form.imageFileId || undefined,\n },\n },\n { onSuccess: () => navigateTo(`/items/${itemId}`) }\n );\n } else {\n createItem.mutate(\n {\n brandId: form.brandId,\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n category: form.category,\n collectionId: form.collectionId || undefined,\n sku: form.sku.trim() || undefined,\n price: priceNum,\n currency: form.currency.trim() || undefined,\n colours: parseCsv(form.colours),\n sizes: parseCsv(form.sizes),\n materials: parseCsv(form.materials),\n tags: parseCsv(form.tags),\n imageFileId: form.imageFileId || undefined,\n },\n { onSuccess: (item) => navigateTo(`/items/${item.id}`) }\n );\n }\n };\n\n if (isEdit && existing.isLoading) {\n return (\n <PageLayout title=\"Edit item\">\n <LoadingView label=\"Loading item…\" />\n </PageLayout>\n );\n }\n\n if (isEdit && (existing.isError || !existing.data)) {\n return (\n <PageLayout title=\"Edit item\">\n <ErrorView title=\"Item not found\" onRetry={() => void existing.refetch()} />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={isEdit ? 'Edit item' : 'New style item'}\n description={isEdit ? 'Update this catalogue piece' : 'List a new shoppable piece'}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo(isEdit && itemId ? `/items/${itemId}` : '/items')}\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-[var(--color-accent-soft)] text-text-primary\">\n <Shirt className=\"h-5 w-5\" />\n </div>\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">Item details</h2>\n <p className=\"text-xs text-text-secondary\">\n A piece belongs to a brand and optionally a collection.\n </p>\n </div>\n </div>\n\n {!isEdit && (\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 className=\"mb-4\"\n />\n )}\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <TextField\n id=\"name\"\n label=\"Name\"\n value={form.name}\n onChange={(v) => setField('name', v)}\n placeholder=\"Silk slip dress\"\n required\n error={errors.name}\n />\n <SelectField\n id=\"category\"\n label=\"Category\"\n value={form.category}\n onChange={(v) => setField('category', v as CooStyleCategory)}\n options={categoryOptions}\n />\n </div>\n\n <TextAreaField\n id=\"description\"\n label=\"Description\"\n value={form.description}\n onChange={(v) => setField('description', v)}\n placeholder=\"Describe the piece…\"\n rows={3}\n className=\"mt-4\"\n />\n\n <ImageUploadField\n label=\"Product photo\"\n value={form.imageFileId}\n onChange={(id) => setField('imageFileId', id)}\n folder={COO_FILE_FOLDERS.items}\n hint=\"Shown across the catalogue and item page\"\n className=\"mt-4\"\n />\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <TextField\n id=\"price\"\n label=\"Price\"\n type=\"number\"\n value={form.price}\n onChange={(v) => setField('price', v)}\n placeholder=\"0.00\"\n error={errors.price}\n />\n <TextField\n id=\"currency\"\n label=\"Currency\"\n value={form.currency}\n onChange={(v) => setField('currency', v)}\n placeholder=\"USD\"\n />\n <TextField\n id=\"sku\"\n label=\"SKU\"\n value={form.sku}\n onChange={(v) => setField('sku', v)}\n placeholder=\"ABC-123\"\n />\n </div>\n\n <SelectField\n id=\"collectionId\"\n label=\"Collection (optional)\"\n value={form.collectionId}\n onChange={(v) => setField('collectionId', v)}\n options={collectionOptions}\n placeholder={\n !form.brandId\n ? 'Select a brand first'\n : collections.isLoading\n ? 'Loading collections…'\n : 'No collection'\n }\n className=\"mt-4\"\n />\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <TextField\n id=\"colours\"\n label=\"Colours\"\n value={form.colours}\n onChange={(v) => setField('colours', v)}\n placeholder=\"black, ivory\"\n hint=\"Comma-separated\"\n />\n <TextField\n id=\"sizes\"\n label=\"Sizes\"\n value={form.sizes}\n onChange={(v) => setField('sizes', v)}\n placeholder=\"XS, S, M, L\"\n hint=\"Comma-separated\"\n />\n <TextField\n id=\"materials\"\n label=\"Materials\"\n value={form.materials}\n onChange={(v) => setField('materials', v)}\n placeholder=\"silk, cotton\"\n hint=\"Comma-separated\"\n />\n <TextField\n id=\"tags\"\n label=\"Tags\"\n value={form.tags}\n onChange={(v) => setField('tags', v)}\n placeholder=\"summer, party\"\n hint=\"Comma-separated\"\n />\n </div>\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}\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 item'}\n </button>\n </div>\n </GlassCard>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -10,7 +10,7 @@ import { SelectField as f, TextField as p } from "../../components/Field.js";
10
10
  import { VisibilityChip as m } from "../../components/VisibilityChip.js";
11
11
  import { useMemo as h, useState as g } from "react";
12
12
  import { ArrowLeft as _, BookHeart as v, Clock as y, Loader2 as b, Plus as x, Sparkles as S, Tag as C, Trash2 as w, X as T } from "lucide-react";
13
- import { useParams as E } from "react-router-dom";
13
+ import { useParams as E } from "react-router";
14
14
  import { jsx as D, jsxs as O } from "react/jsx-runtime";
15
15
  import { GlassCard as k } from "@burdenoff/fe-libs/ui";
16
16
  //#region src/coolandlovely/pages/lookbooks/LookbookDetailPage.tsx
@@ -1 +1 @@
1
- {"version":3,"file":"LookbookDetailPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/lookbooks/LookbookDetailPage.tsx"],"sourcesContent":["/**\n * Lookbook Detail Page\n * @module coolandlovely/pages/lookbooks\n *\n * Single lookbook (`cooLookbook`) rendered as a Pinterest-style masonry board.\n * Each entry's `lookId` / `styleItemId` is resolved client-side against the\n * already-fetched looks/items lists to a real title + image, with an optional\n * note badge and a hover X-remove (`cooRemoveLookbookEntry`). New entries are\n * added inline (`cooAddLookbookEntry`).\n */\n\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, BookHeart, Sparkles, Tag, Loader2, Plus, Trash2, X, Clock } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { TagList } from '../../components/TagList';\nimport { VisibilityChip } from '../../components/VisibilityChip';\nimport { SelectField, TextField } from '../../components/Field';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport {\n useLookbook,\n useRemoveLookbookEntry,\n useAddLookbookEntry,\n useDeleteLookbook,\n useLooks,\n useStyleItems,\n} from '../../hooks';\nimport { formatRelativeTime } from '../../utils/formatters';\n\ntype EntryKind = 'look' | 'item';\n\n/** Alternating aspect ratios give the board its masonry rhythm. */\nconst TILE_ASPECTS = ['aspect-[3/4]', 'aspect-square', 'aspect-[4/5]'];\n\nexport function LookbookDetailPage() {\n const { lookbookId } = useParams<{ lookbookId: string }>();\n const { navigateTo } = useCoolAndLovely();\n const query = useLookbook(lookbookId);\n const removeEntry = useRemoveLookbookEntry();\n const addEntry = useAddLookbookEntry();\n const deleteLookbook = useDeleteLookbook();\n\n const [adding, setAdding] = useState(false);\n const [entryKind, setEntryKind] = useState<EntryKind>('look');\n const [selectedId, setSelectedId] = useState('');\n const [note, setNote] = useState('');\n\n const looks = useLooks({ pagination: { limit: 100, offset: 0 } });\n const items = useStyleItems({ pagination: { limit: 100, offset: 0 } });\n\n const lookById = useMemo(\n () => new Map((looks.data?.items ?? []).map((l) => [l.id, l])),\n [looks.data]\n );\n const itemById = useMemo(\n () => new Map((items.data?.items ?? []).map((i) => [i.id, i])),\n [items.data]\n );\n const usableItems = useMemo(\n () => (items.data?.items ?? []).filter((i) => i.status !== 'RETIRED'),\n [items.data]\n );\n\n const lookOptions = useMemo(\n () => (looks.data?.items ?? []).map((l) => ({ value: l.id, label: l.title })),\n [looks.data]\n );\n const itemOptions = useMemo(\n () =>\n usableItems.map((i) => ({\n value: i.id,\n label: `${i.name}${i.brand?.name ? ` · ${i.brand.name}` : ''}`,\n })),\n [usableItems]\n );\n\n if (query.isLoading) {\n return (\n <PageLayout title=\"Lookbook\">\n <LoadingView label=\"Loading lookbook…\" />\n </PageLayout>\n );\n }\n\n if (query.isError || !query.data) {\n return (\n <PageLayout title=\"Lookbook\">\n <ErrorView title=\"Lookbook not found\" onRetry={() => void query.refetch()} />\n </PageLayout>\n );\n }\n\n const lookbook = query.data;\n const entries = [...(lookbook.entries ?? [])].sort((a, b) => a.position - b.position);\n\n const handleAddEntry = (e: React.FormEvent) => {\n e.preventDefault();\n if (!lookbookId || !selectedId) return;\n addEntry.mutate(\n {\n lookbookId,\n lookId: entryKind === 'look' ? selectedId : undefined,\n styleItemId: entryKind === 'item' ? selectedId : undefined,\n note: note.trim() || undefined,\n },\n {\n onSuccess: () => {\n setSelectedId('');\n setNote('');\n setAdding(false);\n },\n }\n );\n };\n\n return (\n <PageLayout\n title={lookbook.name}\n description={lookbook.description ?? undefined}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/lookbooks')}\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 <button\n type=\"button\"\n onClick={() => {\n setAdding((v) => !v);\n setSelectedId('');\n }}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n {adding ? <X className=\"h-4 w-4\" /> : <Plus className=\"h-4 w-4\" />}\n {adding ? 'Close' : 'Add entry'}\n </button>\n <button\n type=\"button\"\n disabled={deleteLookbook.isPending}\n onClick={() => {\n if (!lookbookId) return;\n deleteLookbook.mutate(lookbookId, { onSuccess: () => navigateTo('/lookbooks') });\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 {deleteLookbook.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Trash2 className=\"h-4 w-4\" />\n )}\n Delete\n </button>\n </div>\n }\n >\n <div className=\"mx-auto max-w-5xl\">\n <GlassCard treatment=\"glass\" className=\"p-6\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-12 w-12 shrink-0 items-center justify-center rounded-lg bg-accent-purple-subtle text-text-primary\">\n <BookHeart className=\"h-6 w-6\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <h2 className=\"truncate text-xl font-semibold text-text-primary\">{lookbook.name}</h2>\n <div className=\"mt-1.5 flex flex-wrap items-center gap-2\">\n <VisibilityChip visibility={lookbook.visibility} />\n <span className=\"inline-flex items-center gap-1 text-xs text-text-secondary\">\n <Clock className=\"h-3.5 w-3.5\" /> Updated {formatRelativeTime(lookbook.updatedAt)}\n </span>\n <span className=\"text-xs text-text-secondary tabular-nums\">\n · {entries.length} {entries.length === 1 ? 'item' : 'items'}\n </span>\n </div>\n </div>\n </div>\n {lookbook.tags.length > 0 && <TagList tags={lookbook.tags} className=\"mt-4\" />}\n </GlassCard>\n\n {adding && (\n <form onSubmit={handleAddEntry} className=\"mt-6\">\n <GlassCard treatment=\"plain\" className=\"p-5\">\n <h3 className=\"mb-4 flex items-center gap-2 text-base font-semibold text-text-primary\">\n <Plus className=\"h-5 w-5\" /> Add to this lookbook\n </h3>\n <div className=\"mb-4 flex flex-wrap gap-2\">\n {(['look', 'item'] as EntryKind[]).map((k) => (\n <button\n key={k}\n type=\"button\"\n onClick={() => {\n setEntryKind(k);\n setSelectedId('');\n }}\n className={\n entryKind === k\n ? 'rounded-full bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text'\n : 'rounded-full bg-bg-sunken px-3 py-1.5 text-sm font-medium text-text-secondary hover:text-text-primary'\n }\n >\n {k === 'look' ? 'Save a look' : 'Clip a piece'}\n </button>\n ))}\n </div>\n <SelectField\n label={entryKind === 'look' ? 'Look' : 'Style item'}\n value={selectedId}\n onChange={setSelectedId}\n options={entryKind === 'look' ? lookOptions : itemOptions}\n placeholder={\n entryKind === 'look'\n ? looks.isLoading\n ? 'Loading looks…'\n : 'Select a look'\n : items.isLoading\n ? 'Loading items…'\n : 'Select a style item'\n }\n />\n <TextField\n label=\"Note (optional)\"\n value={note}\n onChange={setNote}\n placeholder=\"Why you saved it…\"\n className=\"mt-4\"\n />\n {addEntry.isError && (\n <p className=\"mt-3 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {addEntry.error instanceof Error ? addEntry.error.message : 'Could not add entry'}\n </p>\n )}\n <div className=\"mt-4 flex justify-end\">\n <button\n type=\"submit\"\n disabled={!selectedId || addEntry.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 {addEntry.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Plus className=\"h-4 w-4\" />\n )}\n Add entry\n </button>\n </div>\n </GlassCard>\n </form>\n )}\n\n <section className=\"mt-6\">\n <h3 className=\"mb-3 text-lg font-semibold text-text-primary\">Board ({entries.length})</h3>\n {entries.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n This lookbook has no entries yet. Clip a catalogue piece or save a look to add one.\n </p>\n ) : (\n <div className=\"columns-2 gap-4 md:columns-3\">\n {entries.map((entry, idx) => {\n const isLook = Boolean(entry.lookId);\n const look = entry.lookId ? lookById.get(entry.lookId) : undefined;\n const item = entry.styleItemId ? itemById.get(entry.styleItemId) : undefined;\n const title = isLook\n ? (look?.title ?? 'Saved look')\n : (item?.name ?? 'Clipped piece');\n const imageFileId = isLook ? look?.image?.id : item?.image?.id;\n const removing = removeEntry.isPending && removeEntry.variables === entry.id;\n const onOpen = () => {\n if (isLook && entry.lookId) navigateTo(`/looks/${entry.lookId}`);\n else if (entry.styleItemId) navigateTo(`/items/${entry.styleItemId}`);\n };\n return (\n <div\n key={entry.id}\n className=\"group relative mb-4 break-inside-avoid overflow-hidden rounded-xl border border-border-seam bg-bg-surface shadow-sm transition-shadow hover:shadow-lg\"\n >\n <button\n type=\"button\"\n onClick={onOpen}\n aria-label={`Open ${title}`}\n className=\"block w-full text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <Thumbnail\n icon={\n isLook ? <Sparkles className=\"h-8 w-8\" /> : <Tag className=\"h-8 w-8\" />\n }\n seed={entry.id}\n fileId={imageFileId}\n alt={title}\n className={`w-full rounded-none ${TILE_ASPECTS[idx % TILE_ASPECTS.length]}`}\n overlay={\n <div className=\"pointer-events-none absolute inset-x-0 bottom-0 flex flex-col gap-1.5 bg-gradient-to-t from-bg-canvas/90 via-bg-canvas/40 to-transparent p-3 pt-10\">\n {entry.note && (\n <span className=\"inline-flex w-fit max-w-full items-center rounded-md bg-bg-canvas/80 px-2 py-0.5 text-[11px] font-medium text-text-primary backdrop-blur\">\n <span className=\"truncate\">{entry.note}</span>\n </span>\n )}\n <p className=\"truncate text-sm font-semibold text-text-primary\">\n {title}\n </p>\n </div>\n }\n />\n </button>\n <button\n type=\"button\"\n disabled={removeEntry.isPending}\n onClick={() => removeEntry.mutate(entry.id)}\n aria-label={`Remove ${title}`}\n className=\"absolute right-2 top-2 z-10 inline-flex h-7 w-7 items-center justify-center rounded-full bg-bg-surface/90 text-text-secondary opacity-100 shadow-sm backdrop-blur transition-opacity hover:text-status-error-text sm:opacity-0 sm:focus-visible:opacity-100 sm:group-hover:opacity-100 disabled:opacity-60\"\n >\n {removing ? (\n <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />\n ) : (\n <X className=\"h-3.5 w-3.5\" />\n )}\n </button>\n </div>\n );\n })}\n </div>\n )}\n </section>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"LookbookDetailPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/lookbooks/LookbookDetailPage.tsx"],"sourcesContent":["/**\n * Lookbook Detail Page\n * @module coolandlovely/pages/lookbooks\n *\n * Single lookbook (`cooLookbook`) rendered as a Pinterest-style masonry board.\n * Each entry's `lookId` / `styleItemId` is resolved client-side against the\n * already-fetched looks/items lists to a real title + image, with an optional\n * note badge and a hover X-remove (`cooRemoveLookbookEntry`). New entries are\n * added inline (`cooAddLookbookEntry`).\n */\n\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\nimport { ArrowLeft, BookHeart, Sparkles, Tag, Loader2, Plus, Trash2, X, Clock } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { TagList } from '../../components/TagList';\nimport { VisibilityChip } from '../../components/VisibilityChip';\nimport { SelectField, TextField } from '../../components/Field';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport {\n useLookbook,\n useRemoveLookbookEntry,\n useAddLookbookEntry,\n useDeleteLookbook,\n useLooks,\n useStyleItems,\n} from '../../hooks';\nimport { formatRelativeTime } from '../../utils/formatters';\n\ntype EntryKind = 'look' | 'item';\n\n/** Alternating aspect ratios give the board its masonry rhythm. */\nconst TILE_ASPECTS = ['aspect-[3/4]', 'aspect-square', 'aspect-[4/5]'];\n\nexport function LookbookDetailPage() {\n const { lookbookId } = useParams<{ lookbookId: string }>();\n const { navigateTo } = useCoolAndLovely();\n const query = useLookbook(lookbookId);\n const removeEntry = useRemoveLookbookEntry();\n const addEntry = useAddLookbookEntry();\n const deleteLookbook = useDeleteLookbook();\n\n const [adding, setAdding] = useState(false);\n const [entryKind, setEntryKind] = useState<EntryKind>('look');\n const [selectedId, setSelectedId] = useState('');\n const [note, setNote] = useState('');\n\n const looks = useLooks({ pagination: { limit: 100, offset: 0 } });\n const items = useStyleItems({ pagination: { limit: 100, offset: 0 } });\n\n const lookById = useMemo(\n () => new Map((looks.data?.items ?? []).map((l) => [l.id, l])),\n [looks.data]\n );\n const itemById = useMemo(\n () => new Map((items.data?.items ?? []).map((i) => [i.id, i])),\n [items.data]\n );\n const usableItems = useMemo(\n () => (items.data?.items ?? []).filter((i) => i.status !== 'RETIRED'),\n [items.data]\n );\n\n const lookOptions = useMemo(\n () => (looks.data?.items ?? []).map((l) => ({ value: l.id, label: l.title })),\n [looks.data]\n );\n const itemOptions = useMemo(\n () =>\n usableItems.map((i) => ({\n value: i.id,\n label: `${i.name}${i.brand?.name ? ` · ${i.brand.name}` : ''}`,\n })),\n [usableItems]\n );\n\n if (query.isLoading) {\n return (\n <PageLayout title=\"Lookbook\">\n <LoadingView label=\"Loading lookbook…\" />\n </PageLayout>\n );\n }\n\n if (query.isError || !query.data) {\n return (\n <PageLayout title=\"Lookbook\">\n <ErrorView title=\"Lookbook not found\" onRetry={() => void query.refetch()} />\n </PageLayout>\n );\n }\n\n const lookbook = query.data;\n const entries = [...(lookbook.entries ?? [])].sort((a, b) => a.position - b.position);\n\n const handleAddEntry = (e: React.FormEvent) => {\n e.preventDefault();\n if (!lookbookId || !selectedId) return;\n addEntry.mutate(\n {\n lookbookId,\n lookId: entryKind === 'look' ? selectedId : undefined,\n styleItemId: entryKind === 'item' ? selectedId : undefined,\n note: note.trim() || undefined,\n },\n {\n onSuccess: () => {\n setSelectedId('');\n setNote('');\n setAdding(false);\n },\n }\n );\n };\n\n return (\n <PageLayout\n title={lookbook.name}\n description={lookbook.description ?? undefined}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/lookbooks')}\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 <button\n type=\"button\"\n onClick={() => {\n setAdding((v) => !v);\n setSelectedId('');\n }}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n {adding ? <X className=\"h-4 w-4\" /> : <Plus className=\"h-4 w-4\" />}\n {adding ? 'Close' : 'Add entry'}\n </button>\n <button\n type=\"button\"\n disabled={deleteLookbook.isPending}\n onClick={() => {\n if (!lookbookId) return;\n deleteLookbook.mutate(lookbookId, { onSuccess: () => navigateTo('/lookbooks') });\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 {deleteLookbook.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Trash2 className=\"h-4 w-4\" />\n )}\n Delete\n </button>\n </div>\n }\n >\n <div className=\"mx-auto max-w-5xl\">\n <GlassCard treatment=\"glass\" className=\"p-6\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-12 w-12 shrink-0 items-center justify-center rounded-lg bg-accent-purple-subtle text-text-primary\">\n <BookHeart className=\"h-6 w-6\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <h2 className=\"truncate text-xl font-semibold text-text-primary\">{lookbook.name}</h2>\n <div className=\"mt-1.5 flex flex-wrap items-center gap-2\">\n <VisibilityChip visibility={lookbook.visibility} />\n <span className=\"inline-flex items-center gap-1 text-xs text-text-secondary\">\n <Clock className=\"h-3.5 w-3.5\" /> Updated {formatRelativeTime(lookbook.updatedAt)}\n </span>\n <span className=\"text-xs text-text-secondary tabular-nums\">\n · {entries.length} {entries.length === 1 ? 'item' : 'items'}\n </span>\n </div>\n </div>\n </div>\n {lookbook.tags.length > 0 && <TagList tags={lookbook.tags} className=\"mt-4\" />}\n </GlassCard>\n\n {adding && (\n <form onSubmit={handleAddEntry} className=\"mt-6\">\n <GlassCard treatment=\"plain\" className=\"p-5\">\n <h3 className=\"mb-4 flex items-center gap-2 text-base font-semibold text-text-primary\">\n <Plus className=\"h-5 w-5\" /> Add to this lookbook\n </h3>\n <div className=\"mb-4 flex flex-wrap gap-2\">\n {(['look', 'item'] as EntryKind[]).map((k) => (\n <button\n key={k}\n type=\"button\"\n onClick={() => {\n setEntryKind(k);\n setSelectedId('');\n }}\n className={\n entryKind === k\n ? 'rounded-full bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text'\n : 'rounded-full bg-bg-sunken px-3 py-1.5 text-sm font-medium text-text-secondary hover:text-text-primary'\n }\n >\n {k === 'look' ? 'Save a look' : 'Clip a piece'}\n </button>\n ))}\n </div>\n <SelectField\n label={entryKind === 'look' ? 'Look' : 'Style item'}\n value={selectedId}\n onChange={setSelectedId}\n options={entryKind === 'look' ? lookOptions : itemOptions}\n placeholder={\n entryKind === 'look'\n ? looks.isLoading\n ? 'Loading looks…'\n : 'Select a look'\n : items.isLoading\n ? 'Loading items…'\n : 'Select a style item'\n }\n />\n <TextField\n label=\"Note (optional)\"\n value={note}\n onChange={setNote}\n placeholder=\"Why you saved it…\"\n className=\"mt-4\"\n />\n {addEntry.isError && (\n <p className=\"mt-3 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {addEntry.error instanceof Error ? addEntry.error.message : 'Could not add entry'}\n </p>\n )}\n <div className=\"mt-4 flex justify-end\">\n <button\n type=\"submit\"\n disabled={!selectedId || addEntry.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 {addEntry.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Plus className=\"h-4 w-4\" />\n )}\n Add entry\n </button>\n </div>\n </GlassCard>\n </form>\n )}\n\n <section className=\"mt-6\">\n <h3 className=\"mb-3 text-lg font-semibold text-text-primary\">Board ({entries.length})</h3>\n {entries.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n This lookbook has no entries yet. Clip a catalogue piece or save a look to add one.\n </p>\n ) : (\n <div className=\"columns-2 gap-4 md:columns-3\">\n {entries.map((entry, idx) => {\n const isLook = Boolean(entry.lookId);\n const look = entry.lookId ? lookById.get(entry.lookId) : undefined;\n const item = entry.styleItemId ? itemById.get(entry.styleItemId) : undefined;\n const title = isLook\n ? (look?.title ?? 'Saved look')\n : (item?.name ?? 'Clipped piece');\n const imageFileId = isLook ? look?.image?.id : item?.image?.id;\n const removing = removeEntry.isPending && removeEntry.variables === entry.id;\n const onOpen = () => {\n if (isLook && entry.lookId) navigateTo(`/looks/${entry.lookId}`);\n else if (entry.styleItemId) navigateTo(`/items/${entry.styleItemId}`);\n };\n return (\n <div\n key={entry.id}\n className=\"group relative mb-4 break-inside-avoid overflow-hidden rounded-xl border border-border-seam bg-bg-surface shadow-sm transition-shadow hover:shadow-lg\"\n >\n <button\n type=\"button\"\n onClick={onOpen}\n aria-label={`Open ${title}`}\n className=\"block w-full text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <Thumbnail\n icon={\n isLook ? <Sparkles className=\"h-8 w-8\" /> : <Tag className=\"h-8 w-8\" />\n }\n seed={entry.id}\n fileId={imageFileId}\n alt={title}\n className={`w-full rounded-none ${TILE_ASPECTS[idx % TILE_ASPECTS.length]}`}\n overlay={\n <div className=\"pointer-events-none absolute inset-x-0 bottom-0 flex flex-col gap-1.5 bg-gradient-to-t from-bg-canvas/90 via-bg-canvas/40 to-transparent p-3 pt-10\">\n {entry.note && (\n <span className=\"inline-flex w-fit max-w-full items-center rounded-md bg-bg-canvas/80 px-2 py-0.5 text-[11px] font-medium text-text-primary backdrop-blur\">\n <span className=\"truncate\">{entry.note}</span>\n </span>\n )}\n <p className=\"truncate text-sm font-semibold text-text-primary\">\n {title}\n </p>\n </div>\n }\n />\n </button>\n <button\n type=\"button\"\n disabled={removeEntry.isPending}\n onClick={() => removeEntry.mutate(entry.id)}\n aria-label={`Remove ${title}`}\n className=\"absolute right-2 top-2 z-10 inline-flex h-7 w-7 items-center justify-center rounded-full bg-bg-surface/90 text-text-secondary opacity-100 shadow-sm backdrop-blur transition-opacity hover:text-status-error-text sm:opacity-0 sm:focus-visible:opacity-100 sm:group-hover:opacity-100 disabled:opacity-60\"\n >\n {removing ? (\n <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />\n ) : (\n <X className=\"h-3.5 w-3.5\" />\n )}\n </button>\n </div>\n );\n })}\n </div>\n )}\n </section>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAmCA,IAAM,IAAe;CAAC;CAAgB;CAAiB;CAAe;AAEtE,SAAgB,IAAqB;CACnC,IAAM,EAAE,kBAAe,GAAmC,EACpD,EAAE,kBAAe,GAAkB,EACnC,IAAQ,EAAY,EAAW,EAC/B,IAAc,GAAwB,EACtC,IAAW,GAAqB,EAChC,IAAiB,GAAmB,EAEpC,CAAC,GAAQ,KAAa,EAAS,GAAM,EACrC,CAAC,GAAW,KAAgB,EAAoB,OAAO,EACvD,CAAC,GAAY,KAAiB,EAAS,GAAG,EAC1C,CAAC,GAAM,KAAW,EAAS,GAAG,EAE9B,IAAQ,EAAS,EAAE,YAAY;EAAE,OAAO;EAAK,QAAQ;EAAG,EAAE,CAAC,EAC3D,IAAQ,GAAc,EAAE,YAAY;EAAE,OAAO;EAAK,QAAQ;EAAG,EAAE,CAAC,EAEhE,IAAW,QACT,IAAI,KAAK,EAAM,MAAM,SAAS,EAAE,EAAE,KAAK,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,EAC9D,CAAC,EAAM,KAAK,CACb,EACK,IAAW,QACT,IAAI,KAAK,EAAM,MAAM,SAAS,EAAE,EAAE,KAAK,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,EAC9D,CAAC,EAAM,KAAK,CACb,EACK,IAAc,SACX,EAAM,MAAM,SAAS,EAAE,EAAE,QAAQ,MAAM,EAAE,WAAW,UAAU,EACrE,CAAC,EAAM,KAAK,CACb,EAEK,IAAc,SACX,EAAM,MAAM,SAAS,EAAE,EAAE,KAAK,OAAO;EAAE,OAAO,EAAE;EAAI,OAAO,EAAE;EAAO,EAAE,EAC7E,CAAC,EAAM,KAAK,CACb,EACK,IAAc,QAEhB,EAAY,KAAK,OAAO;EACtB,OAAO,EAAE;EACT,OAAO,GAAG,EAAE,OAAO,EAAE,OAAO,OAAO,MAAM,EAAE,MAAM,SAAS;EAC3D,EAAE,EACL,CAAC,EAAY,CACd;AAED,KAAI,EAAM,UACR,QACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD,EAAa,OAAM,qBAAsB,CAAA;EAC9B,CAAA;AAIjB,KAAI,EAAM,WAAW,CAAC,EAAM,KAC1B,QACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD;GAAW,OAAM;GAAqB,eAAe,KAAK,EAAM,SAAS;GAAI,CAAA;EAClE,CAAA;CAIjB,IAAM,IAAW,EAAM,MACjB,IAAU,CAAC,GAAI,EAAS,WAAW,EAAE,CAAE,CAAC,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,SAAS;AAsBrF,QACE,kBAAC,GAAD;EACE,OAAO,EAAS;EAChB,aAAa,EAAS,eAAe,KAAA;EACrC,SACE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,aAAa;KACvC,WAAU;eAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA,EAAA,QAC1B;;IACT,kBAAC,UAAD;KACE,MAAK;KACL,eAAe;AAEb,MADA,GAAW,MAAM,CAAC,EAAE,EACpB,EAAc,GAAG;;KAEnB,WAAU;eANZ,CAQY,EAAT,IAAU,IAA4B,GAA7B,EAAG,WAAU,WAAY,CAA+B,EACjE,IAAS,UAAU,YACb;;IACT,kBAAC,UAAD;KACE,MAAK;KACL,UAAU,EAAe;KACzB,eAAe;AACR,WACL,EAAe,OAAO,GAAY,EAAE,iBAAiB,EAAW,aAAa,EAAE,CAAC;;KAElF,WAAU;eAPZ,CASG,EAAe,YACd,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,GAE5C,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA,EAC9B,SAEK;;IACL;;YAGR,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,GAAD;KAAW,WAAU;KAAQ,WAAU;eAAvC,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA;OAC7B,CAAA,EACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,MAAD;QAAI,WAAU;kBAAoD,EAAS;QAAU,CAAA,EACrF,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,GAAD,EAAgB,YAAY,EAAS,YAAc,CAAA;SACnD,kBAAC,QAAD;UAAM,WAAU;oBAAhB;WACE,kBAAC,GAAD,EAAO,WAAU,eAAgB,CAAA;;WAAU,EAAmB,EAAS,UAAU;WAC5E;;SACP,kBAAC,QAAD;UAAM,WAAU;oBAAhB;WAA2D;WACtD,EAAQ;WAAO;WAAE,EAAQ,WAAW,IAAI,SAAS;WAC/C;;SACH;UACF;SACF;SACL,EAAS,KAAK,SAAS,KAAK,kBAAC,GAAD;MAAS,MAAM,EAAS;MAAM,WAAU;MAAS,CAAA,CACpE;;IAEX,KACC,kBAAC,QAAD;KAAM,WAtFU,MAAuB;AAC7C,QAAE,gBAAgB,EACd,GAAC,KAAc,CAAC,MACpB,EAAS,OACP;OACE;OACA,QAAQ,MAAc,SAAS,IAAa,KAAA;OAC5C,aAAa,MAAc,SAAS,IAAa,KAAA;OACjD,MAAM,EAAK,MAAM,IAAI,KAAA;OACtB,EACD,EACE,iBAAiB;AAGf,OAFA,EAAc,GAAG,EACjB,EAAQ,GAAG,EACX,EAAU,GAAM;SAEnB,CACF;;KAqEqC,WAAU;eACxC,kBAAC,GAAD;MAAW,WAAU;MAAQ,WAAU;gBAAvC;OACE,kBAAC,MAAD;QAAI,WAAU;kBAAd,CACE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,wBACzB;;OACL,kBAAC,OAAD;QAAK,WAAU;kBACX,CAAC,QAAQ,OAAO,CAAiB,KAAK,MACtC,kBAAC,UAAD;SAEE,MAAK;SACL,eAAe;AAEb,UADA,EAAa,EAAE,EACf,EAAc,GAAG;;SAEnB,WACE,MAAc,IACV,+FACA;mBAGL,MAAM,SAAS,gBAAgB;SACzB,EAbF,EAaE,CACT;QACE,CAAA;OACN,kBAAC,GAAD;QACE,OAAO,MAAc,SAAS,SAAS;QACvC,OAAO;QACP,UAAU;QACV,SAAS,MAAc,SAAS,IAAc;QAC9C,aACE,MAAc,SACV,EAAM,YACJ,mBACA,kBACF,EAAM,YACJ,mBACA;QAER,CAAA;OACF,kBAAC,GAAD;QACE,OAAM;QACN,OAAO;QACP,UAAU;QACV,aAAY;QACZ,WAAU;QACV,CAAA;OACD,EAAS,WACR,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAS,iBAAiB,QAAQ,EAAS,MAAM,UAAU;QAC1D,CAAA;OAEN,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,UAAD;SACE,MAAK;SACL,UAAU,CAAC,KAAc,EAAS;SAClC,WAAU;mBAHZ,CAKG,EAAS,YACR,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,GAE5C,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAC5B,YAEK;;QACL,CAAA;OACI;;KACP,CAAA;IAGT,kBAAC,WAAD;KAAS,WAAU;eAAnB,CACE,kBAAC,MAAD;MAAI,WAAU;gBAAd;OAA6D;OAAQ,EAAQ;OAAO;OAAM;SACzF,EAAQ,WAAW,IAClB,kBAAC,KAAD;MAAG,WAAU;gBAAqF;MAE9F,CAAA,GAEJ,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAQ,KAAK,GAAO,MAAQ;OAC3B,IAAM,IAAS,EAAQ,EAAM,QACvB,IAAO,EAAM,SAAS,EAAS,IAAI,EAAM,OAAO,GAAG,KAAA,GACnD,IAAO,EAAM,cAAc,EAAS,IAAI,EAAM,YAAY,GAAG,KAAA,GAC7D,IAAQ,IACT,GAAM,SAAS,eACf,GAAM,QAAQ,iBACb,IAAc,IAAS,GAAM,OAAO,KAAK,GAAM,OAAO,IACtD,IAAW,EAAY,aAAa,EAAY,cAAc,EAAM;AAK1E,cACE,kBAAC,OAAD;QAEE,WAAU;kBAFZ,CAIE,kBAAC,UAAD;SACE,MAAK;SACL,eAXe;AACnB,UAAI,KAAU,EAAM,SAAQ,EAAW,UAAU,EAAM,SAAS,GACvD,EAAM,eAAa,EAAW,UAAU,EAAM,cAAc;;SAUjE,cAAY,QAAQ;SACpB,WAAU;mBAEV,kBAAC,GAAD;UACE,MACW,EAAT,IAAU,IAAmC,GAApC,EAAU,WAAU,WAAY,CAA8B;UAEzE,MAAM,EAAM;UACZ,QAAQ;UACR,KAAK;UACL,WAAW,uBAAuB,EAAa,IAAM,EAAa;UAClE,SACE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACG,EAAM,QACL,kBAAC,QAAD;YAAM,WAAU;sBACd,kBAAC,QAAD;aAAM,WAAU;uBAAY,EAAM;aAAY,CAAA;YACzC,CAAA,EAET,kBAAC,KAAD;YAAG,WAAU;sBACV;YACC,CAAA,CACA;;UAER,CAAA;SACK,CAAA,EACT,kBAAC,UAAD;SACE,MAAK;SACL,UAAU,EAAY;SACtB,eAAe,EAAY,OAAO,EAAM,GAAG;SAC3C,cAAY,UAAU;SACtB,WAAU;mBAET,IACC,kBAAC,GAAD,EAAS,WAAU,4BAA6B,CAAA,GAEhD,kBAAC,GAAD,EAAG,WAAU,eAAgB,CAAA;SAExB,CAAA,CACL;UA5CC,EAAM,GA4CP;QAER;MACE,CAAA,CAEA;;IACN;;EACK,CAAA"}
@@ -14,7 +14,7 @@ import { FollowButton as b } from "../../components/FollowButton.js";
14
14
  import { ShopItemCard as x } from "../../components/ShopItemCard.js";
15
15
  import { useMemo as S, useState as C } from "react";
16
16
  import { ArrowLeft as w, Bookmark as T, Heart as E, Loader2 as D, Rocket as O, Sparkles as k, Star as A, Trash2 as j } from "lucide-react";
17
- import { useParams as M } from "react-router-dom";
17
+ import { useParams as M } from "react-router";
18
18
  import { jsx as N, jsxs as P } from "react/jsx-runtime";
19
19
  import { GlassCard as F } from "@burdenoff/fe-libs/ui";
20
20
  import { useActorProfiles as I } from "@burdenoff/fe-libs/shared";