@burdenoff/microfe-coolandlovely 2026.1031.2 → 2026.1101.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/coolandlovely/CoolAndLovelyRoot.d.ts.map +1 -1
- package/dist/coolandlovely/CoolAndLovelyRoot.js +16 -4
- package/dist/coolandlovely/CoolAndLovelyRoot.js.map +1 -1
- package/dist/coolandlovely/CoolAndLovelyRoutes.d.ts.map +1 -1
- package/dist/coolandlovely/CoolAndLovelyRoutes.js +19 -15
- package/dist/coolandlovely/CoolAndLovelyRoutes.js.map +1 -1
- package/dist/coolandlovely/components/ActivityTrail.js +1 -1
- package/dist/coolandlovely/components/ActivityTrail.js.map +1 -1
- package/dist/coolandlovely/components/ActorName.js.map +1 -1
- package/dist/coolandlovely/components/AddToCartPanel.js.map +1 -1
- package/dist/coolandlovely/components/AddToLookbookButton.js.map +1 -1
- package/dist/coolandlovely/components/BrandCard.js.map +1 -1
- package/dist/coolandlovely/components/BrandCover.js.map +1 -1
- package/dist/coolandlovely/components/BrandCreatorAvatars.js.map +1 -1
- package/dist/coolandlovely/components/CollaborationFeeCard.js.map +1 -1
- package/dist/coolandlovely/components/CollectionCard.js.map +1 -1
- package/dist/coolandlovely/components/ColourSwatchRow.js.map +1 -1
- package/dist/coolandlovely/components/Comments.js.map +1 -1
- package/dist/coolandlovely/components/EmptyState.js.map +1 -1
- package/dist/coolandlovely/components/FeedbackWidget.js.map +1 -1
- package/dist/coolandlovely/components/Field.js.map +1 -1
- package/dist/coolandlovely/components/FileImage.js.map +1 -1
- package/dist/coolandlovely/components/FollowButton.js.map +1 -1
- package/dist/coolandlovely/components/FulfilmentTimeline.js.map +1 -1
- package/dist/coolandlovely/components/ImageUploadField.js +3 -1
- package/dist/coolandlovely/components/ImageUploadField.js.map +1 -1
- package/dist/coolandlovely/components/LookCard.js.map +1 -1
- package/dist/coolandlovely/components/LookbookCard.js.map +1 -1
- package/dist/coolandlovely/components/MetricsCard.js.map +1 -1
- package/dist/coolandlovely/components/PageLayout.js.map +1 -1
- package/dist/coolandlovely/components/PayForOrder.js.map +1 -1
- package/dist/coolandlovely/components/RefundPanel.js +1 -1
- package/dist/coolandlovely/components/RefundPanel.js.map +1 -1
- package/dist/coolandlovely/components/ReturnRequests.js +1 -1
- package/dist/coolandlovely/components/ReturnRequests.js.map +1 -1
- package/dist/coolandlovely/components/ShopItemCard.js.map +1 -1
- package/dist/coolandlovely/components/StateViews.js.map +1 -1
- package/dist/coolandlovely/components/StatusBadge.js +1 -1
- package/dist/coolandlovely/components/StatusBadge.js.map +1 -1
- package/dist/coolandlovely/components/StepTracker.js.map +1 -1
- package/dist/coolandlovely/components/StockEditor.js.map +1 -1
- package/dist/coolandlovely/components/StoreListingCard.js.map +1 -1
- package/dist/coolandlovely/components/TagList.js.map +1 -1
- package/dist/coolandlovely/components/Thumbnail.js +1 -1
- package/dist/coolandlovely/components/Thumbnail.js.map +1 -1
- package/dist/coolandlovely/components/VisibilityChip.js.map +1 -1
- package/dist/coolandlovely/components/WardrobeItemCard.js.map +1 -1
- package/dist/coolandlovely/components/index.js +2 -1
- package/dist/coolandlovely/context/CoolAndLovelyContext.js +2 -1
- package/dist/coolandlovely/context/CoolAndLovelyContext.js.map +1 -1
- package/dist/coolandlovely/context/index.js +2 -1
- package/dist/coolandlovely/hooks/index.js +4 -3
- package/dist/coolandlovely/hooks/useActivity.d.ts +1 -1
- package/dist/coolandlovely/hooks/useActivity.d.ts.map +1 -1
- package/dist/coolandlovely/hooks/useActivity.js.map +1 -1
- package/dist/coolandlovely/hooks/useAssistantEntity.js.map +1 -1
- package/dist/coolandlovely/hooks/useBrandOrdersApi.d.ts +4 -4
- package/dist/coolandlovely/hooks/useBrandOrdersApi.d.ts.map +1 -1
- package/dist/coolandlovely/hooks/useBrandOrdersApi.js.map +1 -1
- package/dist/coolandlovely/hooks/useCatalogImport.js.map +1 -1
- package/dist/coolandlovely/hooks/useComments.d.ts +1 -1
- package/dist/coolandlovely/hooks/useComments.d.ts.map +1 -1
- package/dist/coolandlovely/hooks/useComments.js.map +1 -1
- package/dist/coolandlovely/hooks/useCommerceApi.d.ts +7 -7
- package/dist/coolandlovely/hooks/useCommerceApi.d.ts.map +1 -1
- package/dist/coolandlovely/hooks/useCommerceApi.js.map +1 -1
- package/dist/coolandlovely/hooks/useCoolAndLovelyApi.d.ts +24 -24
- package/dist/coolandlovely/hooks/useCoolAndLovelyApi.d.ts.map +1 -1
- package/dist/coolandlovely/hooks/useCoolAndLovelyApi.js.map +1 -1
- package/dist/coolandlovely/hooks/useEditorialApi.d.ts +95 -0
- package/dist/coolandlovely/hooks/useEditorialApi.d.ts.map +1 -0
- package/dist/coolandlovely/hooks/useEditorialApi.js +107 -0
- package/dist/coolandlovely/hooks/useEditorialApi.js.map +1 -0
- package/dist/coolandlovely/hooks/useFiles.js.map +1 -1
- package/dist/coolandlovely/hooks/useInventoryApi.d.ts +1 -1
- package/dist/coolandlovely/hooks/useInventoryApi.d.ts.map +1 -1
- package/dist/coolandlovely/hooks/useInventoryApi.js.map +1 -1
- package/dist/coolandlovely/hooks/useTr.js.map +1 -1
- package/dist/coolandlovely/index.js +6 -5
- package/dist/coolandlovely/pages/HomePage.js.map +1 -1
- package/dist/coolandlovely/pages/admin/AdminBrandsPage.js.map +1 -1
- package/dist/coolandlovely/pages/admin/AdminCollaborationsPage.js.map +1 -1
- package/dist/coolandlovely/pages/admin/AdminFeaturedItemsPage.js.map +1 -1
- package/dist/coolandlovely/pages/brands/BrandDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/brands/BrandFormPage.js.map +1 -1
- package/dist/coolandlovely/pages/brands/BrandsListPage.js.map +1 -1
- package/dist/coolandlovely/pages/brands/ImportCatalogPage.js.map +1 -1
- package/dist/coolandlovely/pages/collaborations/CollaborationDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/collaborations/CollaborationFormPage.js +1 -1
- package/dist/coolandlovely/pages/collaborations/CollaborationFormPage.js.map +1 -1
- package/dist/coolandlovely/pages/collaborations/CollaborationsListPage.js.map +1 -1
- package/dist/coolandlovely/pages/collections/CollectionDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/collections/CollectionFormPage.js.map +1 -1
- package/dist/coolandlovely/pages/collections/CollectionsListPage.js.map +1 -1
- package/dist/coolandlovely/pages/commerce/BrandOrdersPage.js.map +1 -1
- package/dist/coolandlovely/pages/commerce/CartPage.js.map +1 -1
- package/dist/coolandlovely/pages/commerce/CreatorEarningsPage.js.map +1 -1
- package/dist/coolandlovely/pages/commerce/OrdersPage.js.map +1 -1
- package/dist/coolandlovely/pages/discover/DiscoverPage.js +1 -1
- package/dist/coolandlovely/pages/discover/DiscoverPage.js.map +1 -1
- package/dist/coolandlovely/pages/editorial/EditorialPage.d.ts +2 -0
- package/dist/coolandlovely/pages/editorial/EditorialPage.d.ts.map +1 -0
- package/dist/coolandlovely/pages/editorial/EditorialPage.js +486 -0
- package/dist/coolandlovely/pages/editorial/EditorialPage.js.map +1 -0
- package/dist/coolandlovely/pages/items/StoreListedPieceView.js.map +1 -1
- package/dist/coolandlovely/pages/items/StyleItemDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/items/StyleItemFormPage.js.map +1 -1
- package/dist/coolandlovely/pages/items/StyleItemsListPage.js.map +1 -1
- package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/lookbooks/LookbooksListPage.js.map +1 -1
- package/dist/coolandlovely/pages/looks/LookDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/looks/LookFormPage.js.map +1 -1
- package/dist/coolandlovely/pages/looks/LooksListPage.js.map +1 -1
- package/dist/coolandlovely/pages/profile/StyleProfilePage.js +1 -1
- package/dist/coolandlovely/pages/profile/StyleProfilePage.js.map +1 -1
- package/dist/coolandlovely/pages/trends/TrendsPage.js.map +1 -1
- package/dist/coolandlovely/pages/wardrobe/WardrobeFormPage.js.map +1 -1
- package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.js.map +1 -1
- package/dist/coolandlovely/store/coolandlovelyStore.js.map +1 -1
- package/dist/coolandlovely/store/index.js +2 -1
- package/dist/coolandlovely/types/commerce.d.ts.map +1 -1
- package/dist/coolandlovely/types/index.d.ts.map +1 -1
- package/dist/coolandlovely/utils/cn.js +1 -1
- package/dist/coolandlovely/utils/cn.js.map +1 -1
- package/dist/coolandlovely/utils/formatters.js.map +1 -1
- package/dist/coolandlovely/utils/icons.js.map +1 -1
- package/dist/coolandlovely/utils/index.js +2 -1
- package/dist/coolandlovely/utils/navigation.js.map +1 -1
- package/dist/coolandlovely/utils/paymentProviders.js +22 -22
- package/dist/coolandlovely/utils/paymentProviders.js.map +1 -1
- package/dist/coolandlovely/utils/sparkline.js +13 -15
- package/dist/coolandlovely/utils/sparkline.js.map +1 -1
- package/dist/node_modules/clsx/dist/clsx.js +1 -1
- package/dist/node_modules/clsx/dist/clsx.js.map +1 -1
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +225 -146
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -1
- package/dist/services/createSandboxAssistantTransport.js.map +1 -1
- package/dist/stories/DiscoverPage.stories.d.ts.map +1 -1
- package/dist/utils/pageContext.js +1 -1
- package/dist/utils/pageContext.js.map +1 -1
- package/package.json +6 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CollaborationDetailPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/collaborations/CollaborationDetailPage.tsx"],"sourcesContent":["/**\n * Collaboration Detail Page\n * @module coolandlovely/pages/collaborations\n *\n * Single collaboration (`cooCollaboration`) with validated lifecycle\n * transitions (`cooTransitionCollaboration`), its brand and attributed looks.\n */\n\nimport { useParams } from 'react-router';\nimport { ArrowLeft, Loader2, Pencil, Trash2, Store, Sparkles } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { BrandCreatorAvatars } from '../../components/BrandCreatorAvatars';\nimport { ActorName } from '../../components/ActorName';\nimport { StepTracker } from '../../components/StepTracker';\nimport { TagList } from '../../components/TagList';\nimport { CollaborationFeeCard } from '../../components/CollaborationFeeCard';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport {\n useAssistantEntityName,\n useCollaboration,\n useTransitionCollaboration,\n useDeleteCollaboration,\n} from '../../hooks';\nimport { formatPrice, formatDate, titleCase } from '../../utils/formatters';\nimport type { CooCollaborationStatus } from '../../types';\n\n/** Allowed next states per the svc collaboration state machine (UX guidance;\n * the service is authoritative and rejects illegal jumps). */\nconst NEXT_STATES: Record<CooCollaborationStatus, CooCollaborationStatus[]> = {\n PROPOSED: ['ACCEPTED', 'DECLINED', 'CANCELLED'],\n ACCEPTED: ['IN_PROGRESS', 'CANCELLED'],\n IN_PROGRESS: ['PUBLISHED', 'CANCELLED'],\n PUBLISHED: [],\n DECLINED: [],\n CANCELLED: [],\n};\n\nconst LIFECYCLE_STEPS: CooCollaborationStatus[] = [\n 'PROPOSED',\n 'ACCEPTED',\n 'IN_PROGRESS',\n 'PUBLISHED',\n];\n\nexport function CollaborationDetailPage() {\n const { collaborationId } = useParams<{ collaborationId: string }>();\n const { navigateTo } = useCoolAndLovely();\n const query = useCollaboration(collaborationId);\n const transition = useTransitionCollaboration();\n const deleteCollaboration = useDeleteCollaboration();\n // Name the collaboration for the assistant's screen context.\n useAssistantEntityName(collaborationId, query.data?.title);\n\n if (query.isLoading) {\n return (\n <PageLayout title=\"Collaboration\">\n <LoadingView label=\"Loading collaboration…\" />\n </PageLayout>\n );\n }\n\n if (query.isError || !query.data) {\n return (\n <PageLayout title=\"Collaboration\">\n <ErrorView title=\"Collaboration not found\" onRetry={() => void query.refetch()} />\n </PageLayout>\n );\n }\n\n const collab = query.data;\n const looks = collab.looks ?? [];\n const nextStates = NEXT_STATES[collab.status];\n const trackerSteps = LIFECYCLE_STEPS.includes(collab.status)\n ? LIFECYCLE_STEPS\n : (['PROPOSED', collab.status] as CooCollaborationStatus[]);\n\n return (\n <PageLayout\n title={collab.title}\n description={collab.brand?.name ?? undefined}\n actions={\n <div className=\"flex items-center gap-2\">\n {nextStates.length > 0 && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/collaborations/${collab.id}/edit`)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Pencil className=\"h-4 w-4\" /> Edit\n </button>\n )}\n {collab.status === 'PROPOSED' && (\n <button\n type=\"button\"\n disabled={deleteCollaboration.isPending}\n onClick={() =>\n deleteCollaboration.mutate(collab.id, {\n onSuccess: () => navigateTo('/collaborations'),\n })\n }\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-50\"\n >\n {deleteCollaboration.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Trash2 className=\"h-4 w-4\" />\n )}\n Delete\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => navigateTo('/collaborations')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n </div>\n }\n >\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"lg:col-span-2\">\n <GlassCard treatment=\"glass\" className=\"p-6\">\n <div className=\"flex items-center gap-3\">\n <BrandCreatorAvatars\n brandLogoFileId={collab.brand?.logo?.id}\n brandSeed={collab.brandId}\n creatorId={collab.creatorId}\n className=\"shrink-0\"\n />\n <div className=\"min-w-0 flex-1\">\n <h2 className=\"truncate text-xl font-semibold text-text-primary\">{collab.title}</h2>\n <p className=\"text-xs text-text-secondary\">\n {collab.brand?.name ?? 'Brand'} ×{' '}\n <button\n type=\"button\"\n onClick={() => navigateTo(`/profile/${collab.creatorId}`)}\n className=\"font-medium text-text-primary hover:underline focus-visible:outline-none\"\n >\n <ActorName id={collab.creatorId} />\n </button>{' '}\n · {titleCase(collab.role)} collaboration\n </p>\n </div>\n <StatusBadge status={collab.status} />\n </div>\n {collab.brief && <p className=\"mt-4 text-sm text-text-secondary\">{collab.brief}</p>}\n {collab.tags.length > 0 && (\n <TagList tags={collab.tags} className=\"mt-4\" tone=\"accent\" />\n )}\n </GlassCard>\n\n {/* Attributed looks */}\n <section className=\"mt-6\">\n <h3 className=\"mb-3 text-lg font-semibold text-text-primary\">\n Attributed looks ({looks.length})\n </h3>\n {looks.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n No looks attributed to this collaboration yet.\n </p>\n ) : (\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-3\">\n {looks.map((look) => (\n <button\n key={look.id}\n type=\"button\"\n onClick={() => navigateTo(`/looks/${look.id}`)}\n className=\"overflow-hidden rounded-lg border border-border-seam bg-bg-surface text-left transition-all hover:border-accent-foreground/20 hover:shadow-md\"\n >\n <Thumbnail\n icon={<Sparkles className=\"h-6 w-6\" />}\n seed={look.id}\n className=\"h-24 w-full\"\n />\n <div className=\"p-3\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{look.title}</p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {titleCase(look.occasion)}\n </p>\n </div>\n </button>\n ))}\n </div>\n )}\n </section>\n </div>\n\n <div className=\"space-y-4\">\n <GlassCard treatment=\"insight\" className=\"p-5\">\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">Lifecycle</h3>\n <StepTracker steps={trackerSteps} current={collab.status} className=\"mb-5\" />\n <div className=\"mb-3 flex items-center gap-2\">\n <span className=\"text-sm text-text-secondary\">Current:</span>\n <StatusBadge status={collab.status} />\n </div>\n {nextStates.length === 0 ? (\n <p className=\"text-xs text-text-secondary\">\n This collaboration is in a terminal state.\n </p>\n ) : (\n <div className=\"flex flex-col gap-2\">\n {nextStates.map((next) => (\n <button\n key={next}\n type=\"button\"\n disabled={transition.isPending}\n onClick={() => transition.mutate({ id: collab.id, status: next })}\n className=\"inline-flex items-center justify-center gap-2 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-50\"\n >\n {transition.isPending && <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />}\n Move to {titleCase(next)}\n </button>\n ))}\n </div>\n )}\n {transition.isError && (\n <p className=\"mt-2 text-xs text-status-error-text\">\n {transition.error instanceof Error ? transition.error.message : 'Transition failed'}\n </p>\n )}\n </GlassCard>\n\n <GlassCard treatment=\"plain\" className=\"p-5\">\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">Terms</h3>\n <dl className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Role</dt>\n <dd className=\"text-text-primary\">{titleCase(collab.role)}</dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Compensation</dt>\n <dd className=\"text-text-primary\">\n {formatPrice(collab.compensation, collab.currency)}\n </dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Starts</dt>\n <dd className=\"text-text-primary\">\n {collab.startDate\n ? formatDate(collab.startDate, { hour: undefined, minute: undefined })\n : '—'}\n </dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Ends</dt>\n <dd className=\"text-text-primary\">\n {collab.endDate\n ? formatDate(collab.endDate, { hour: undefined, minute: undefined })\n : '—'}\n </dd>\n </div>\n </dl>\n </GlassCard>\n\n <CollaborationFeeCard collaboration={collab} />\n\n {collab.brand && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/brands/${collab.brand?.id}`)}\n className=\"flex w-full items-center gap-3 rounded-lg border border-border-seam bg-bg-surface p-4 text-left transition-colors hover:bg-bg-sunken/50\"\n >\n <Thumbnail\n icon={<Store className=\"h-5 w-5\" />}\n seed={collab.brand.id}\n className=\"h-10 w-10 shrink-0\"\n />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {collab.brand.name}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">View brand</p>\n </div>\n </button>\n )}\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CollaborationDetailPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/collaborations/CollaborationDetailPage.tsx"],"sourcesContent":["/**\n * Collaboration Detail Page\n * @module coolandlovely/pages/collaborations\n *\n * Single collaboration (`cooCollaboration`) with validated lifecycle\n * transitions (`cooTransitionCollaboration`), its brand and attributed looks.\n */\n\nimport { useParams } from 'react-router';\nimport { ArrowLeft, Loader2, Pencil, Trash2, Store, Sparkles } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { BrandCreatorAvatars } from '../../components/BrandCreatorAvatars';\nimport { ActorName } from '../../components/ActorName';\nimport { StepTracker } from '../../components/StepTracker';\nimport { TagList } from '../../components/TagList';\nimport { CollaborationFeeCard } from '../../components/CollaborationFeeCard';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport {\n useAssistantEntityName,\n useCollaboration,\n useTransitionCollaboration,\n useDeleteCollaboration,\n} from '../../hooks';\nimport { formatPrice, formatDate, titleCase } from '../../utils/formatters';\nimport type { CooCollaborationStatus } from '../../types';\n\n/** Allowed next states per the svc collaboration state machine (UX guidance;\n * the service is authoritative and rejects illegal jumps). */\nconst NEXT_STATES: Record<CooCollaborationStatus, CooCollaborationStatus[]> = {\n PROPOSED: ['ACCEPTED', 'DECLINED', 'CANCELLED'],\n ACCEPTED: ['IN_PROGRESS', 'CANCELLED'],\n IN_PROGRESS: ['PUBLISHED', 'CANCELLED'],\n PUBLISHED: [],\n DECLINED: [],\n CANCELLED: [],\n};\n\nconst LIFECYCLE_STEPS: CooCollaborationStatus[] = [\n 'PROPOSED',\n 'ACCEPTED',\n 'IN_PROGRESS',\n 'PUBLISHED',\n];\n\nexport function CollaborationDetailPage() {\n const { collaborationId } = useParams<{ collaborationId: string }>();\n const { navigateTo } = useCoolAndLovely();\n const query = useCollaboration(collaborationId);\n const transition = useTransitionCollaboration();\n const deleteCollaboration = useDeleteCollaboration();\n // Name the collaboration for the assistant's screen context.\n useAssistantEntityName(collaborationId, query.data?.title);\n\n if (query.isLoading) {\n return (\n <PageLayout title=\"Collaboration\">\n <LoadingView label=\"Loading collaboration…\" />\n </PageLayout>\n );\n }\n\n if (query.isError || !query.data) {\n return (\n <PageLayout title=\"Collaboration\">\n <ErrorView title=\"Collaboration not found\" onRetry={() => void query.refetch()} />\n </PageLayout>\n );\n }\n\n const collab = query.data;\n const looks = collab.looks ?? [];\n const nextStates = NEXT_STATES[collab.status];\n const trackerSteps = LIFECYCLE_STEPS.includes(collab.status)\n ? LIFECYCLE_STEPS\n : (['PROPOSED', collab.status] as CooCollaborationStatus[]);\n\n return (\n <PageLayout\n title={collab.title}\n description={collab.brand?.name ?? undefined}\n actions={\n <div className=\"flex items-center gap-2\">\n {nextStates.length > 0 && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/collaborations/${collab.id}/edit`)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Pencil className=\"h-4 w-4\" /> Edit\n </button>\n )}\n {collab.status === 'PROPOSED' && (\n <button\n type=\"button\"\n disabled={deleteCollaboration.isPending}\n onClick={() =>\n deleteCollaboration.mutate(collab.id, {\n onSuccess: () => navigateTo('/collaborations'),\n })\n }\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-50\"\n >\n {deleteCollaboration.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Trash2 className=\"h-4 w-4\" />\n )}\n Delete\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => navigateTo('/collaborations')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n </div>\n }\n >\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"lg:col-span-2\">\n <GlassCard treatment=\"glass\" className=\"p-6\">\n <div className=\"flex items-center gap-3\">\n <BrandCreatorAvatars\n brandLogoFileId={collab.brand?.logo?.id}\n brandSeed={collab.brandId}\n creatorId={collab.creatorId}\n className=\"shrink-0\"\n />\n <div className=\"min-w-0 flex-1\">\n <h2 className=\"truncate text-xl font-semibold text-text-primary\">{collab.title}</h2>\n <p className=\"text-xs text-text-secondary\">\n {collab.brand?.name ?? 'Brand'} ×{' '}\n <button\n type=\"button\"\n onClick={() => navigateTo(`/profile/${collab.creatorId}`)}\n className=\"font-medium text-text-primary hover:underline focus-visible:outline-none\"\n >\n <ActorName id={collab.creatorId} />\n </button>{' '}\n · {titleCase(collab.role)} collaboration\n </p>\n </div>\n <StatusBadge status={collab.status} />\n </div>\n {collab.brief && <p className=\"mt-4 text-sm text-text-secondary\">{collab.brief}</p>}\n {collab.tags.length > 0 && (\n <TagList tags={collab.tags} className=\"mt-4\" tone=\"accent\" />\n )}\n </GlassCard>\n\n {/* Attributed looks */}\n <section className=\"mt-6\">\n <h3 className=\"mb-3 text-lg font-semibold text-text-primary\">\n Attributed looks ({looks.length})\n </h3>\n {looks.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n No looks attributed to this collaboration yet.\n </p>\n ) : (\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-3\">\n {looks.map((look) => (\n <button\n key={look.id}\n type=\"button\"\n onClick={() => navigateTo(`/looks/${look.id}`)}\n className=\"overflow-hidden rounded-lg border border-border-seam bg-bg-surface text-left transition-all hover:border-accent-foreground/20 hover:shadow-md\"\n >\n <Thumbnail\n icon={<Sparkles className=\"h-6 w-6\" />}\n seed={look.id}\n className=\"h-24 w-full\"\n />\n <div className=\"p-3\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{look.title}</p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {titleCase(look.occasion)}\n </p>\n </div>\n </button>\n ))}\n </div>\n )}\n </section>\n </div>\n\n <div className=\"space-y-4\">\n <GlassCard treatment=\"insight\" className=\"p-5\">\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">Lifecycle</h3>\n <StepTracker steps={trackerSteps} current={collab.status} className=\"mb-5\" />\n <div className=\"mb-3 flex items-center gap-2\">\n <span className=\"text-sm text-text-secondary\">Current:</span>\n <StatusBadge status={collab.status} />\n </div>\n {nextStates.length === 0 ? (\n <p className=\"text-xs text-text-secondary\">\n This collaboration is in a terminal state.\n </p>\n ) : (\n <div className=\"flex flex-col gap-2\">\n {nextStates.map((next) => (\n <button\n key={next}\n type=\"button\"\n disabled={transition.isPending}\n onClick={() => transition.mutate({ id: collab.id, status: next })}\n className=\"inline-flex items-center justify-center gap-2 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-50\"\n >\n {transition.isPending && <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />}\n Move to {titleCase(next)}\n </button>\n ))}\n </div>\n )}\n {transition.isError && (\n <p className=\"mt-2 text-xs text-status-error-text\">\n {transition.error instanceof Error ? transition.error.message : 'Transition failed'}\n </p>\n )}\n </GlassCard>\n\n <GlassCard treatment=\"plain\" className=\"p-5\">\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">Terms</h3>\n <dl className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Role</dt>\n <dd className=\"text-text-primary\">{titleCase(collab.role)}</dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Compensation</dt>\n <dd className=\"text-text-primary\">\n {formatPrice(collab.compensation, collab.currency)}\n </dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Starts</dt>\n <dd className=\"text-text-primary\">\n {collab.startDate\n ? formatDate(collab.startDate, { hour: undefined, minute: undefined })\n : '—'}\n </dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Ends</dt>\n <dd className=\"text-text-primary\">\n {collab.endDate\n ? formatDate(collab.endDate, { hour: undefined, minute: undefined })\n : '—'}\n </dd>\n </div>\n </dl>\n </GlassCard>\n\n <CollaborationFeeCard collaboration={collab} />\n\n {collab.brand && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/brands/${collab.brand?.id}`)}\n className=\"flex w-full items-center gap-3 rounded-lg border border-border-seam bg-bg-surface p-4 text-left transition-colors hover:bg-bg-sunken/50\"\n >\n <Thumbnail\n icon={<Store className=\"h-5 w-5\" />}\n seed={collab.brand.id}\n className=\"h-10 w-10 shrink-0\"\n />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {collab.brand.name}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">View brand</p>\n </div>\n </button>\n )}\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CollaborationFormPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/collaborations/CollaborationFormPage.tsx"],"sourcesContent":["/**\n * Collaboration Create / Edit Page\n * @module coolandlovely/pages/collaborations\n *\n * Create mode drives `cooCreateCollaboration`. Edit mode (route\n * `/collaborations/:collaborationId/edit`) drives `cooUpdateCollaboration` —\n * identity (brand/creator) and status are immutable there, so those fields\n * render read-only and status transitions stay on CollaborationDetailPage.\n */\n\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\nimport { ArrowLeft, Loader2, Save, Handshake } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { TextField, TextAreaField, SelectField } from '../../components/Field';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { ActorName } from '../../components/ActorName';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport {\n useCreateCollaboration,\n useUpdateCollaboration,\n useCollaboration,\n useBrands,\n} from '../../hooks';\nimport { parseCsv, titleCase } from '../../utils/formatters';\nimport type { CooCollaboratorRole } from '../../types';\n\nconst ROLES: CooCollaboratorRole[] = ['CREATOR', 'BRAND', 'STYLIST', 'PHOTOGRAPHER'];\n\n/** Statuses past which a collaboration's terms may no longer be edited (mirrors the svc's TERMINAL_COLLABORATION_STATUSES). */\nconst TERMINAL_STATUSES = new Set(['PUBLISHED', 'DECLINED', 'CANCELLED']);\n\ninterface FormState {\n brandId: string;\n creatorId: string;\n title: string;\n brief: string;\n role: CooCollaboratorRole;\n compensation: string;\n currency: string;\n tags: string;\n startDate: string;\n endDate: string;\n}\n\nfunction toIso(date: string): string | undefined {\n if (!date) return undefined;\n const d = new Date(date);\n return Number.isNaN(d.getTime()) ? undefined : d.toISOString();\n}\n\n/** yyyy-MM-dd for a <input type=\"date\"> from an ISO string, or '' when unset. */\nfunction toDateInput(iso: string | null | undefined): string {\n if (!iso) return '';\n const d = new Date(iso);\n return Number.isNaN(d.getTime()) ? '' : d.toISOString().slice(0, 10);\n}\n\nconst emptyForm: FormState = {\n brandId: '',\n creatorId: '',\n title: '',\n brief: '',\n role: 'CREATOR',\n compensation: '',\n currency: 'USD',\n tags: '',\n startDate: '',\n endDate: '',\n};\n\nexport function CollaborationFormPage() {\n const { collaborationId } = useParams<{ collaborationId: string }>();\n const isEditMode = Boolean(collaborationId);\n const { navigateTo, currentUser } = useCoolAndLovely();\n const createCollaboration = useCreateCollaboration();\n const updateCollaboration = useUpdateCollaboration();\n const brands = useBrands({ pagination: { limit: 100, offset: 0 } });\n const existing = useCollaboration(isEditMode ? collaborationId : undefined);\n\n const [form, setForm] = useState<FormState>({ ...emptyForm, creatorId: currentUser?.id ?? '' });\n const [errors, setErrors] = useState<Partial<Record<keyof FormState, string>>>({});\n\n // Prefill the form once the existing collaboration loads (edit mode only).\n useEffect(() => {\n if (!isEditMode || !existing.data) return;\n const c = existing.data;\n setForm({\n brandId: c.brandId,\n creatorId: c.creatorId,\n title: c.title,\n brief: c.brief ?? '',\n role: c.role,\n compensation: c.compensation ? String(c.compensation) : '',\n currency: c.currency,\n tags: c.tags.join(', '),\n startDate: toDateInput(c.startDate),\n endDate: toDateInput(c.endDate),\n });\n }, [isEditMode, existing.data]);\n\n const setField = <K extends keyof FormState>(key: K, value: FormState[K]) =>\n setForm((f) => ({ ...f, [key]: value }));\n\n const brandOptions = useMemo(\n () => (brands.data?.items ?? []).map((b) => ({ value: b.id, label: b.name })),\n [brands.data]\n );\n const roleOptions = ROLES.map((r) => ({ value: r, label: titleCase(r) }));\n\n if (isEditMode && existing.isLoading) {\n return (\n <PageLayout title=\"Edit collaboration\">\n <LoadingView label=\"Loading collaboration…\" />\n </PageLayout>\n );\n }\n\n if (isEditMode && (existing.isError || !existing.data)) {\n return (\n <PageLayout title=\"Edit collaboration\">\n <ErrorView title=\"Collaboration not found\" onRetry={() => void existing.refetch()} />\n </PageLayout>\n );\n }\n\n const isTerminal =\n isEditMode && existing.data ? TERMINAL_STATUSES.has(existing.data.status) : false;\n\n const validate = (): boolean => {\n const next: Partial<Record<keyof FormState, string>> = {};\n if (!form.title.trim()) next.title = 'Title is required';\n if (!isEditMode) {\n if (!form.brandId) next.brandId = 'Brand is required';\n if (!form.creatorId.trim()) next.creatorId = 'Creator is required';\n }\n if (form.compensation && Number.isNaN(Number(form.compensation)))\n next.compensation = 'Must be a number';\n setErrors(next);\n return Object.keys(next).length === 0;\n };\n\n const mutation = isEditMode ? updateCollaboration : createCollaboration;\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (!validate()) return;\n\n if (isEditMode && collaborationId) {\n updateCollaboration.mutate(\n {\n id: collaborationId,\n input: {\n title: form.title.trim(),\n brief: form.brief.trim() || undefined,\n role: form.role,\n compensation: form.compensation ? Number(form.compensation) : undefined,\n currency: form.currency.trim() || undefined,\n tags: parseCsv(form.tags),\n startDate: toIso(form.startDate),\n endDate: toIso(form.endDate),\n },\n },\n { onSuccess: (c) => navigateTo(`/collaborations/${c.id}`) }\n );\n return;\n }\n\n createCollaboration.mutate(\n {\n brandId: form.brandId,\n creatorId: form.creatorId.trim(),\n title: form.title.trim(),\n brief: form.brief.trim() || undefined,\n role: form.role,\n compensation: form.compensation ? Number(form.compensation) : undefined,\n currency: form.currency.trim() || undefined,\n tags: parseCsv(form.tags),\n startDate: toIso(form.startDate),\n endDate: toIso(form.endDate),\n },\n { onSuccess: (c) => navigateTo(`/collaborations/${c.id}`) }\n );\n };\n\n return (\n <PageLayout\n title={isEditMode ? 'Edit collaboration' : 'New collaboration'}\n description={\n isEditMode ? 'Update the brief, terms and dates' : 'Propose a brand × creator partnership'\n }\n actions={\n <button\n type=\"button\"\n onClick={() =>\n navigateTo(\n isEditMode && collaborationId\n ? `/collaborations/${collaborationId}`\n : '/collaborations'\n )\n }\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Cancel\n </button>\n }\n >\n <form onSubmit={handleSubmit} className=\"mx-auto max-w-2xl\">\n <GlassCard treatment=\"plain\" className=\"p-6\">\n <div className=\"mb-6 flex items-center gap-3\">\n <div className=\"flex h-11 w-11 items-center justify-center rounded-lg bg-status-warning-bg-subtle text-status-warning-text\">\n <Handshake className=\"h-5 w-5\" />\n </div>\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">Collaboration details</h2>\n <p className=\"text-xs text-text-secondary\">\n {isEditMode\n ? 'Brand and creator cannot be changed after proposal.'\n : 'Created in the Proposed state.'}\n </p>\n </div>\n </div>\n\n {isTerminal && (\n <p className=\"mb-4 rounded-lg bg-status-warning-bg-subtle px-3 py-2 text-sm text-status-warning-text\">\n This collaboration is {existing.data?.status.toLowerCase()} and can no longer be\n edited.\n </p>\n )}\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n {isEditMode ? (\n <div>\n <span className=\"mb-1.5 block text-sm font-medium text-text-primary\">Brand</span>\n <p className=\"rounded-lg border border-border-seam bg-bg-sunken px-3 py-2 text-sm text-text-secondary\">\n {existing.data?.brand?.name ?? form.brandId}\n </p>\n </div>\n ) : (\n <SelectField\n id=\"brandId\"\n label=\"Brand\"\n value={form.brandId}\n onChange={(v) => setField('brandId', v)}\n options={brandOptions}\n required\n placeholder={brands.isLoading ? 'Loading brands…' : 'Select a brand'}\n error={errors.brandId}\n />\n )}\n <SelectField\n id=\"role\"\n label=\"Role\"\n value={form.role}\n onChange={(v) => setField('role', v as CooCollaboratorRole)}\n options={roleOptions}\n disabled={isTerminal}\n />\n </div>\n\n <TextField\n id=\"title\"\n label=\"Title\"\n value={form.title}\n onChange={(v) => setField('title', v)}\n placeholder=\"Summer capsule edit\"\n required\n error={errors.title}\n className=\"mt-4\"\n disabled={isTerminal}\n />\n\n {isEditMode ? (\n <div className=\"mt-4\">\n <span className=\"mb-1.5 block text-sm font-medium text-text-primary\">Creator</span>\n <p className=\"rounded-lg border border-border-seam bg-bg-sunken px-3 py-2 text-sm text-text-secondary\">\n <ActorName id={form.creatorId} />\n </p>\n </div>\n ) : (\n <TextField\n id=\"creatorId\"\n label=\"Creator ID\"\n value={form.creatorId}\n onChange={(v) => setField('creatorId', v)}\n placeholder=\"creator user id\"\n required\n hint=\"Defaults to you. Paste another user's id to credit a different creator.\"\n error={errors.creatorId}\n className=\"mt-4\"\n />\n )}\n\n <TextAreaField\n id=\"brief\"\n label=\"Brief\"\n value={form.brief}\n onChange={(v) => setField('brief', v)}\n placeholder=\"What is this collaboration about…\"\n rows={4}\n className=\"mt-4\"\n disabled={isTerminal}\n />\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <TextField\n id=\"compensation\"\n label=\"Compensation\"\n type=\"number\"\n value={form.compensation}\n onChange={(v) => setField('compensation', v)}\n placeholder=\"0.00\"\n error={errors.compensation}\n disabled={isTerminal}\n />\n <TextField\n id=\"currency\"\n label=\"Currency\"\n value={form.currency}\n onChange={(v) => setField('currency', v)}\n placeholder=\"USD\"\n disabled={isTerminal}\n />\n </div>\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <TextField\n id=\"startDate\"\n label=\"Start date\"\n type=\"date\"\n value={form.startDate}\n onChange={(v) => setField('startDate', v)}\n disabled={isTerminal}\n />\n <TextField\n id=\"endDate\"\n label=\"End date\"\n type=\"date\"\n value={form.endDate}\n onChange={(v) => setField('endDate', v)}\n disabled={isTerminal}\n />\n </div>\n\n <TextField\n id=\"tags\"\n label=\"Tags\"\n value={form.tags}\n onChange={(v) => setField('tags', v)}\n placeholder=\"capsule, sustainable\"\n hint=\"Comma-separated\"\n className=\"mt-4\"\n disabled={isTerminal}\n />\n\n {mutation.isError && (\n <p className=\"mt-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {mutation.error instanceof Error ? mutation.error.message : 'Something went wrong'}\n </p>\n )}\n\n <div className=\"mt-6 flex justify-end gap-2\">\n <button\n type=\"submit\"\n disabled={mutation.isPending || isTerminal}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {mutation.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n {isEditMode ? 'Save changes' : 'Create collaboration'}\n </button>\n </div>\n </GlassCard>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CollaborationFormPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/collaborations/CollaborationFormPage.tsx"],"sourcesContent":["/**\n * Collaboration Create / Edit Page\n * @module coolandlovely/pages/collaborations\n *\n * Create mode drives `cooCreateCollaboration`. Edit mode (route\n * `/collaborations/:collaborationId/edit`) drives `cooUpdateCollaboration` —\n * identity (brand/creator) and status are immutable there, so those fields\n * render read-only and status transitions stay on CollaborationDetailPage.\n */\n\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\nimport { ArrowLeft, Loader2, Save, Handshake } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { TextField, TextAreaField, SelectField } from '../../components/Field';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { ActorName } from '../../components/ActorName';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport {\n useCreateCollaboration,\n useUpdateCollaboration,\n useCollaboration,\n useBrands,\n} from '../../hooks';\nimport { parseCsv, titleCase } from '../../utils/formatters';\nimport type { CooCollaboratorRole } from '../../types';\n\nconst ROLES: CooCollaboratorRole[] = ['CREATOR', 'BRAND', 'STYLIST', 'PHOTOGRAPHER'];\n\n/** Statuses past which a collaboration's terms may no longer be edited (mirrors the svc's TERMINAL_COLLABORATION_STATUSES). */\nconst TERMINAL_STATUSES = new Set(['PUBLISHED', 'DECLINED', 'CANCELLED']);\n\ninterface FormState {\n brandId: string;\n creatorId: string;\n title: string;\n brief: string;\n role: CooCollaboratorRole;\n compensation: string;\n currency: string;\n tags: string;\n startDate: string;\n endDate: string;\n}\n\nfunction toIso(date: string): string | undefined {\n if (!date) return undefined;\n const d = new Date(date);\n return Number.isNaN(d.getTime()) ? undefined : d.toISOString();\n}\n\n/** yyyy-MM-dd for a <input type=\"date\"> from an ISO string, or '' when unset. */\nfunction toDateInput(iso: string | null | undefined): string {\n if (!iso) return '';\n const d = new Date(iso);\n return Number.isNaN(d.getTime()) ? '' : d.toISOString().slice(0, 10);\n}\n\nconst emptyForm: FormState = {\n brandId: '',\n creatorId: '',\n title: '',\n brief: '',\n role: 'CREATOR',\n compensation: '',\n currency: 'USD',\n tags: '',\n startDate: '',\n endDate: '',\n};\n\nexport function CollaborationFormPage() {\n const { collaborationId } = useParams<{ collaborationId: string }>();\n const isEditMode = Boolean(collaborationId);\n const { navigateTo, currentUser } = useCoolAndLovely();\n const createCollaboration = useCreateCollaboration();\n const updateCollaboration = useUpdateCollaboration();\n const brands = useBrands({ pagination: { limit: 100, offset: 0 } });\n const existing = useCollaboration(isEditMode ? collaborationId : undefined);\n\n const [form, setForm] = useState<FormState>({ ...emptyForm, creatorId: currentUser?.id ?? '' });\n const [errors, setErrors] = useState<Partial<Record<keyof FormState, string>>>({});\n\n // Prefill the form once the existing collaboration loads (edit mode only).\n useEffect(() => {\n if (!isEditMode || !existing.data) return;\n const c = existing.data;\n setForm({\n brandId: c.brandId,\n creatorId: c.creatorId,\n title: c.title,\n brief: c.brief ?? '',\n role: c.role,\n compensation: c.compensation ? String(c.compensation) : '',\n currency: c.currency,\n tags: c.tags.join(', '),\n startDate: toDateInput(c.startDate),\n endDate: toDateInput(c.endDate),\n });\n }, [isEditMode, existing.data]);\n\n const setField = <K extends keyof FormState>(key: K, value: FormState[K]) =>\n setForm((f) => ({ ...f, [key]: value }));\n\n const brandOptions = useMemo(\n () => (brands.data?.items ?? []).map((b) => ({ value: b.id, label: b.name })),\n [brands.data]\n );\n const roleOptions = ROLES.map((r) => ({ value: r, label: titleCase(r) }));\n\n if (isEditMode && existing.isLoading) {\n return (\n <PageLayout title=\"Edit collaboration\">\n <LoadingView label=\"Loading collaboration…\" />\n </PageLayout>\n );\n }\n\n if (isEditMode && (existing.isError || !existing.data)) {\n return (\n <PageLayout title=\"Edit collaboration\">\n <ErrorView title=\"Collaboration not found\" onRetry={() => void existing.refetch()} />\n </PageLayout>\n );\n }\n\n const isTerminal =\n isEditMode && existing.data ? TERMINAL_STATUSES.has(existing.data.status) : false;\n\n const validate = (): boolean => {\n const next: Partial<Record<keyof FormState, string>> = {};\n if (!form.title.trim()) next.title = 'Title is required';\n if (!isEditMode) {\n if (!form.brandId) next.brandId = 'Brand is required';\n if (!form.creatorId.trim()) next.creatorId = 'Creator is required';\n }\n if (form.compensation && Number.isNaN(Number(form.compensation)))\n next.compensation = 'Must be a number';\n setErrors(next);\n return Object.keys(next).length === 0;\n };\n\n const mutation = isEditMode ? updateCollaboration : createCollaboration;\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (!validate()) return;\n\n if (isEditMode && collaborationId) {\n updateCollaboration.mutate(\n {\n id: collaborationId,\n input: {\n title: form.title.trim(),\n brief: form.brief.trim() || undefined,\n role: form.role,\n compensation: form.compensation ? Number(form.compensation) : undefined,\n currency: form.currency.trim() || undefined,\n tags: parseCsv(form.tags),\n startDate: toIso(form.startDate),\n endDate: toIso(form.endDate),\n },\n },\n { onSuccess: (c) => navigateTo(`/collaborations/${c.id}`) }\n );\n return;\n }\n\n createCollaboration.mutate(\n {\n brandId: form.brandId,\n creatorId: form.creatorId.trim(),\n title: form.title.trim(),\n brief: form.brief.trim() || undefined,\n role: form.role,\n compensation: form.compensation ? Number(form.compensation) : undefined,\n currency: form.currency.trim() || undefined,\n tags: parseCsv(form.tags),\n startDate: toIso(form.startDate),\n endDate: toIso(form.endDate),\n },\n { onSuccess: (c) => navigateTo(`/collaborations/${c.id}`) }\n );\n };\n\n return (\n <PageLayout\n title={isEditMode ? 'Edit collaboration' : 'New collaboration'}\n description={\n isEditMode ? 'Update the brief, terms and dates' : 'Propose a brand × creator partnership'\n }\n actions={\n <button\n type=\"button\"\n onClick={() =>\n navigateTo(\n isEditMode && collaborationId\n ? `/collaborations/${collaborationId}`\n : '/collaborations'\n )\n }\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Cancel\n </button>\n }\n >\n <form onSubmit={handleSubmit} className=\"mx-auto max-w-2xl\">\n <GlassCard treatment=\"plain\" className=\"p-6\">\n <div className=\"mb-6 flex items-center gap-3\">\n <div className=\"flex h-11 w-11 items-center justify-center rounded-lg bg-status-warning-bg-subtle text-status-warning-text\">\n <Handshake className=\"h-5 w-5\" />\n </div>\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">Collaboration details</h2>\n <p className=\"text-xs text-text-secondary\">\n {isEditMode\n ? 'Brand and creator cannot be changed after proposal.'\n : 'Created in the Proposed state.'}\n </p>\n </div>\n </div>\n\n {isTerminal && (\n <p className=\"mb-4 rounded-lg bg-status-warning-bg-subtle px-3 py-2 text-sm text-status-warning-text\">\n This collaboration is {existing.data?.status.toLowerCase()} and can no longer be\n edited.\n </p>\n )}\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n {isEditMode ? (\n <div>\n <span className=\"mb-1.5 block text-sm font-medium text-text-primary\">Brand</span>\n <p className=\"rounded-lg border border-border-seam bg-bg-sunken px-3 py-2 text-sm text-text-secondary\">\n {existing.data?.brand?.name ?? form.brandId}\n </p>\n </div>\n ) : (\n <SelectField\n id=\"brandId\"\n label=\"Brand\"\n value={form.brandId}\n onChange={(v) => setField('brandId', v)}\n options={brandOptions}\n required\n placeholder={brands.isLoading ? 'Loading brands…' : 'Select a brand'}\n error={errors.brandId}\n />\n )}\n <SelectField\n id=\"role\"\n label=\"Role\"\n value={form.role}\n onChange={(v) => setField('role', v as CooCollaboratorRole)}\n options={roleOptions}\n disabled={isTerminal}\n />\n </div>\n\n <TextField\n id=\"title\"\n label=\"Title\"\n value={form.title}\n onChange={(v) => setField('title', v)}\n placeholder=\"Summer capsule edit\"\n required\n error={errors.title}\n className=\"mt-4\"\n disabled={isTerminal}\n />\n\n {isEditMode ? (\n <div className=\"mt-4\">\n <span className=\"mb-1.5 block text-sm font-medium text-text-primary\">Creator</span>\n <p className=\"rounded-lg border border-border-seam bg-bg-sunken px-3 py-2 text-sm text-text-secondary\">\n <ActorName id={form.creatorId} />\n </p>\n </div>\n ) : (\n <TextField\n id=\"creatorId\"\n label=\"Creator ID\"\n value={form.creatorId}\n onChange={(v) => setField('creatorId', v)}\n placeholder=\"creator user id\"\n required\n hint=\"Defaults to you. Paste another user's id to credit a different creator.\"\n error={errors.creatorId}\n className=\"mt-4\"\n />\n )}\n\n <TextAreaField\n id=\"brief\"\n label=\"Brief\"\n value={form.brief}\n onChange={(v) => setField('brief', v)}\n placeholder=\"What is this collaboration about…\"\n rows={4}\n className=\"mt-4\"\n disabled={isTerminal}\n />\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <TextField\n id=\"compensation\"\n label=\"Compensation\"\n type=\"number\"\n value={form.compensation}\n onChange={(v) => setField('compensation', v)}\n placeholder=\"0.00\"\n error={errors.compensation}\n disabled={isTerminal}\n />\n <TextField\n id=\"currency\"\n label=\"Currency\"\n value={form.currency}\n onChange={(v) => setField('currency', v)}\n placeholder=\"USD\"\n disabled={isTerminal}\n />\n </div>\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <TextField\n id=\"startDate\"\n label=\"Start date\"\n type=\"date\"\n value={form.startDate}\n onChange={(v) => setField('startDate', v)}\n disabled={isTerminal}\n />\n <TextField\n id=\"endDate\"\n label=\"End date\"\n type=\"date\"\n value={form.endDate}\n onChange={(v) => setField('endDate', v)}\n disabled={isTerminal}\n />\n </div>\n\n <TextField\n id=\"tags\"\n label=\"Tags\"\n value={form.tags}\n onChange={(v) => setField('tags', v)}\n placeholder=\"capsule, sustainable\"\n hint=\"Comma-separated\"\n className=\"mt-4\"\n disabled={isTerminal}\n />\n\n {mutation.isError && (\n <p className=\"mt-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {mutation.error instanceof Error ? mutation.error.message : 'Something went wrong'}\n </p>\n )}\n\n <div className=\"mt-6 flex justify-end gap-2\">\n <button\n type=\"submit\"\n disabled={mutation.isPending || isTerminal}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {mutation.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n {isEditMode ? 'Save changes' : 'Create collaboration'}\n </button>\n </div>\n </GlassCard>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CollaborationsListPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/collaborations/CollaborationsListPage.tsx"],"sourcesContent":["/**\n * Collaborations List Page\n * @module coolandlovely/pages/collaborations\n *\n * Lists brand × creator partnerships (`cooListCollaborations`) with status\n * filter.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Plus, Handshake } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { BrandCreatorAvatars } from '../../components/BrandCreatorAvatars';\nimport { ActorName } from '../../components/ActorName';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useCollaborations, useTr } from '../../hooks';\nimport { formatPrice, formatDate, titleCase } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport type { CooCollaborationStatus } from '../../types';\n\nconst STATUS_ORDER: CooCollaborationStatus[] = [\n 'PROPOSED',\n 'ACCEPTED',\n 'IN_PROGRESS',\n 'PUBLISHED',\n 'DECLINED',\n 'CANCELLED',\n];\n\nfunction formatStatusLabel(status: string): string {\n return status.replace(/_/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\nexport function CollaborationsListPage() {\n const { navigateTo } = useCoolAndLovely();\n const tr = useTr();\n const [status, setStatus] = useState<CooCollaborationStatus | 'ALL'>('ALL');\n\n const statusOptions = useMemo(\n () => [\n { value: 'ALL' as const, label: tr('coolandlovely.filters.all', 'All') },\n ...STATUS_ORDER.map((value) => ({\n value,\n label: tr(`coolandlovely.status.${value.toLowerCase()}`, formatStatusLabel(value)),\n })),\n ],\n [tr]\n );\n\n const query = useCollaborations({ status: status === 'ALL' ? undefined : status });\n const collaborations = query.data ?? [];\n\n return (\n <PageLayout\n title={tr('coolandlovely.page.collaborationsTitle', 'Collaborations')}\n description={tr(\n 'coolandlovely.page.collaborationsSubtitle',\n 'Brand × creator partnerships with a real lifecycle'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/collaborations/new')}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />{' '}\n {tr('coolandlovely.actions.newCollaboration', 'New collaboration')}\n </button>\n }\n >\n <div className=\"mb-6 flex flex-wrap items-center gap-2\">\n {statusOptions.map((opt) => (\n <button\n key={opt.value}\n type=\"button\"\n onClick={() => setStatus(opt.value)}\n className={cn(\n 'rounded-full px-3 py-1.5 text-sm font-medium transition-colors',\n status === opt.value\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n {opt.label}\n </button>\n ))}\n </div>\n\n {query.isLoading ? (\n <LoadingView\n label={tr('coolandlovely.loading.collaborations', 'Loading collaborations…')}\n />\n ) : query.isError ? (\n <ErrorView onRetry={() => void query.refetch()} />\n ) : collaborations.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Handshake className=\"h-10 w-10\" />}\n title={tr('coolandlovely.empty.collaborationsTitle', 'No collaborations yet')}\n description={tr(\n 'coolandlovely.empty.collaborationsDescription',\n 'Propose a brand × creator partnership to get started.'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/collaborations/new')}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text\"\n >\n <Plus className=\"h-4 w-4\" />{' '}\n {tr('coolandlovely.actions.newCollaboration', 'New collaboration')}\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {collaborations.map((c) => (\n <button\n key={c.id}\n type=\"button\"\n onClick={() => navigateTo(`/collaborations/${c.id}`)}\n className=\"flex flex-col rounded-lg border border-border-seam bg-bg-surface p-4 text-left shadow-sm transition-all hover:border-accent-foreground/20 hover:shadow-md\"\n >\n <div className=\"flex items-start gap-3\">\n <BrandCreatorAvatars\n brandLogoFileId={c.brand?.logo?.id}\n brandSeed={c.brandId}\n creatorId={c.creatorId}\n size=\"sm\"\n className=\"shrink-0\"\n />\n <div className=\"min-w-0 flex-1\">\n <h3 className=\"truncate text-base font-semibold text-text-primary\">{c.title}</h3>\n <p className=\"truncate text-xs text-text-secondary\">\n {c.brand?.name ?? tr('coolandlovely.common.brand', 'Brand')} ×{' '}\n <ActorName id={c.creatorId} />\n </p>\n </div>\n <StatusBadge status={c.status} />\n </div>\n {c.brief && (\n <p className=\"mt-3 line-clamp-2 text-sm text-text-secondary\">{c.brief}</p>\n )}\n <div className=\"mt-3 flex items-center justify-between text-xs text-text-secondary\">\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5\">\n {tr(`coolandlovely.role.${c.role.toLowerCase()}`, titleCase(c.role))}\n </span>\n <span>{formatPrice(c.compensation, c.currency)}</span>\n </div>\n {c.startDate && (\n <p className=\"mt-2 text-xs text-text-secondary\">\n {tr('coolandlovely.common.starts', 'Starts')}{' '}\n {formatDate(c.startDate, { hour: undefined, minute: undefined })}\n </p>\n )}\n </button>\n ))}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAsBA,IAAM,IAAyC;CAC7C;CACA;CACA;CACA;CACA;CACA;
|
|
1
|
+
{"version":3,"file":"CollaborationsListPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/collaborations/CollaborationsListPage.tsx"],"sourcesContent":["/**\n * Collaborations List Page\n * @module coolandlovely/pages/collaborations\n *\n * Lists brand × creator partnerships (`cooListCollaborations`) with status\n * filter.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Plus, Handshake } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { BrandCreatorAvatars } from '../../components/BrandCreatorAvatars';\nimport { ActorName } from '../../components/ActorName';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useCollaborations, useTr } from '../../hooks';\nimport { formatPrice, formatDate, titleCase } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport type { CooCollaborationStatus } from '../../types';\n\nconst STATUS_ORDER: CooCollaborationStatus[] = [\n 'PROPOSED',\n 'ACCEPTED',\n 'IN_PROGRESS',\n 'PUBLISHED',\n 'DECLINED',\n 'CANCELLED',\n];\n\nfunction formatStatusLabel(status: string): string {\n return status.replace(/_/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\nexport function CollaborationsListPage() {\n const { navigateTo } = useCoolAndLovely();\n const tr = useTr();\n const [status, setStatus] = useState<CooCollaborationStatus | 'ALL'>('ALL');\n\n const statusOptions = useMemo(\n () => [\n { value: 'ALL' as const, label: tr('coolandlovely.filters.all', 'All') },\n ...STATUS_ORDER.map((value) => ({\n value,\n label: tr(`coolandlovely.status.${value.toLowerCase()}`, formatStatusLabel(value)),\n })),\n ],\n [tr]\n );\n\n const query = useCollaborations({ status: status === 'ALL' ? undefined : status });\n const collaborations = query.data ?? [];\n\n return (\n <PageLayout\n title={tr('coolandlovely.page.collaborationsTitle', 'Collaborations')}\n description={tr(\n 'coolandlovely.page.collaborationsSubtitle',\n 'Brand × creator partnerships with a real lifecycle'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/collaborations/new')}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />{' '}\n {tr('coolandlovely.actions.newCollaboration', 'New collaboration')}\n </button>\n }\n >\n <div className=\"mb-6 flex flex-wrap items-center gap-2\">\n {statusOptions.map((opt) => (\n <button\n key={opt.value}\n type=\"button\"\n onClick={() => setStatus(opt.value)}\n className={cn(\n 'rounded-full px-3 py-1.5 text-sm font-medium transition-colors',\n status === opt.value\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n {opt.label}\n </button>\n ))}\n </div>\n\n {query.isLoading ? (\n <LoadingView\n label={tr('coolandlovely.loading.collaborations', 'Loading collaborations…')}\n />\n ) : query.isError ? (\n <ErrorView onRetry={() => void query.refetch()} />\n ) : collaborations.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Handshake className=\"h-10 w-10\" />}\n title={tr('coolandlovely.empty.collaborationsTitle', 'No collaborations yet')}\n description={tr(\n 'coolandlovely.empty.collaborationsDescription',\n 'Propose a brand × creator partnership to get started.'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/collaborations/new')}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text\"\n >\n <Plus className=\"h-4 w-4\" />{' '}\n {tr('coolandlovely.actions.newCollaboration', 'New collaboration')}\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {collaborations.map((c) => (\n <button\n key={c.id}\n type=\"button\"\n onClick={() => navigateTo(`/collaborations/${c.id}`)}\n className=\"flex flex-col rounded-lg border border-border-seam bg-bg-surface p-4 text-left shadow-sm transition-all hover:border-accent-foreground/20 hover:shadow-md\"\n >\n <div className=\"flex items-start gap-3\">\n <BrandCreatorAvatars\n brandLogoFileId={c.brand?.logo?.id}\n brandSeed={c.brandId}\n creatorId={c.creatorId}\n size=\"sm\"\n className=\"shrink-0\"\n />\n <div className=\"min-w-0 flex-1\">\n <h3 className=\"truncate text-base font-semibold text-text-primary\">{c.title}</h3>\n <p className=\"truncate text-xs text-text-secondary\">\n {c.brand?.name ?? tr('coolandlovely.common.brand', 'Brand')} ×{' '}\n <ActorName id={c.creatorId} />\n </p>\n </div>\n <StatusBadge status={c.status} />\n </div>\n {c.brief && (\n <p className=\"mt-3 line-clamp-2 text-sm text-text-secondary\">{c.brief}</p>\n )}\n <div className=\"mt-3 flex items-center justify-between text-xs text-text-secondary\">\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5\">\n {tr(`coolandlovely.role.${c.role.toLowerCase()}`, titleCase(c.role))}\n </span>\n <span>{formatPrice(c.compensation, c.currency)}</span>\n </div>\n {c.startDate && (\n <p className=\"mt-2 text-xs text-text-secondary\">\n {tr('coolandlovely.common.starts', 'Starts')}{' '}\n {formatDate(c.startDate, { hour: undefined, minute: undefined })}\n </p>\n )}\n </button>\n ))}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -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';\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 {\n useAssistantEntityName,\n useCollection,\n usePublishCollection,\n useArchiveCollection,\n} 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 // Name the collection for the assistant's screen context.\n useAssistantEntityName(collectionId, query.data?.name);\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":";;;;;;;;;;;;;;;;;AA2BA,SAAgB,IAAuB;CACrC,IAAM,EAAE,oBAAiB,GAAqC,EACxD,EAAE,kBAAe,GAAkB,EACnC,IAAQ,EAAc,EAAa,EACnC,IAAU,GAAsB,EAChC,IAAU,GAAsB;AAItC,KAFA,EAAuB,GAAc,EAAM,MAAM,KAAK,EAElD,EAAM,UACR,QACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD,EAAa,OAAM,uBAAwB,CAAA;EAChC,CAAA;AAIjB,KAAI,EAAM,WAAW,CAAC,EAAM,KAC1B,QACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD;GAAW,OAAM;GAAuB,eAAe,KAAK,EAAM,SAAS;GAAI,CAAA;EACpE,CAAA;CAIjB,IAAM,IAAa,EAAM,MACnB,IAAQ,EAAW,SAAS,EAAE,EAC9B,IAAa,EAAW,WAAW,WAAW,EAAW,WAAW,aACpE,IAAa,EAAW,WAAW;AAEzC,QACE,kBAAC,GAAD;EACE,OAAO,EAAW;EAClB,aAAa,EAAW,OAAO,QAAQ,KAAA;EACvC,SACE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,eAAe;KACzC,WAAU;eAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA,EAAA,QAC1B;;IACT,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,gBAAgB,EAAW,GAAG,OAAO;KAC/D,WAAU;eAHZ,CAKE,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA,EAAA,QACvB;;IACR,KACC,kBAAC,UAAD;KACE,MAAK;KACL,UAAU,EAAQ;KAClB,eAAe,EAAQ,OAAO,EAAW,GAAG;KAC5C,WAAU;eAJZ,CAMG,EAAQ,YACP,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,GAE5C,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA,EAC9B,UAEK;;IAEV,CAAC,KACA,kBAAC,UAAD;KACE,MAAK;KACL,UAAU,EAAQ;KAClB,eACE,EAAQ,OAAO,EAAW,IAAI,EAAE,iBAAiB,EAAW,eAAe,EAAE,CAAC;KAEhF,WAAU;eANZ,CAQG,EAAQ,YACP,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,GAE5C,kBAAC,GAAD,EAAS,WAAU,WAAY,CAAA,EAC/B,UAEK;;IAEP;;YAGR,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAW,WAAU;KAAQ,WAAU;eAAvC,CACE,kBAAC,GAAD;MACE,MAAM,kBAAC,GAAD,EAAQ,WAAU,aAAc,CAAA;MACtC,MAAM,EAAW;MACjB,QAAQ,EAAW,OAAO;MAC1B,KAAK,EAAW;MAChB,WAAU;MACV,SACE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD,EAAK,WAAU,wGAAyG,CAAA,EACxH,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAW;SACT,CAAA,EACL,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACG,EAAW,OAAO,QAAQ,kBAAC,QAAD,EAAA,UAAO,EAAW,MAAM,MAAY,CAAA;UAC9D,EAAW,OAAO,QACjB,kBAAC,QAAD;WAAM,eAAY;WAAO,WAAU;qBAAa;WAEzC,CAAA;UAET,kBAAC,QAAD;WAAM,WAAU;qBACb,EAAU,EAAW,OAAO;WACxB,CAAA;UACH;WACF;WACN,kBAAC,GAAD;QAAa,QAAQ,EAAW;QAAQ,WAAU;QAAuB,CAAA,CACrE;SACL,EAAA,CAAA;MAEL,CAAA,GACA,EAAW,eAAe,EAAW,KAAK,SAAS,MACnD,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACG,EAAW,eACV,kBAAC,KAAD;OAAG,WAAU;iBAA+B,EAAW;OAAgB,CAAA,EAExE,EAAW,KAAK,SAAS,KACxB,kBAAC,GAAD;OACE,MAAM,EAAW;OACjB,WAAW,EAAW,cAAc,SAAS,KAAA;OAC7C,CAAA,CAEA;QAEE;QAEZ,kBAAC,WAAD;KAAS,WAAU;eAAnB;MACE,kBAAC,MAAD;OAAI,WAAU;iBAAd;QAA6D;QAAQ,EAAM;QAAO;QAAM;;MACvF,EAAM,WAAW,IAChB,kBAAC,KAAD;OAAG,WAAU;iBAAqF;OAE9F,CAAA,GAEJ,kBAAC,OAAD;OAAK,WAAU;iBACZ,EAAM,KAAK,MACV,kBAAC,UAAD;QAEE,MAAK;QACL,eAAe,EAAW,UAAU,EAAK,KAAK;QAC9C,WAAU;kBAJZ,CAME,kBAAC,GAAD;SACE,MAAM,EAAa,EAAK,UAAU,GAAG;SACrC,MAAM,EAAK;SACX,QAAQ,EAAK,OAAO;SACpB,KAAK,EAAK;SACV,WAAU;SACV,CAAA,EACF,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,KAAD;WAAG,WAAU;qBAAkD,EAAK;WAAS,CAAA;UAC7E,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAY,EAAK,OAAO,EAAK,SAAS;WACrC,CAAA;UACH,EAAK,QAAQ,SAAS,KACrB,kBAAC,GAAD;WAAiB,SAAS,EAAK;WAAS,KAAK;WAAG,WAAU;WAAS,CAAA;UAEjE;WACC;UArBF,EAAK,GAqBH,CACT;OACE,CAAA;MAER,kBAAC,UAAD;OACE,MAAK;OACL,eACE,EAAW,sBAAsB,EAAW,QAAQ,gBAAgB,EAAW,KAAK;OAEtF,WAAU;iBALZ,CAOE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,+BACrB;;MACD;OACN;OAEN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MAAW,WAAU;MAAQ,WAAU;gBAAvC,CACE,kBAAC,MAAD;OAAI,WAAU;iBAA+C;OAAY,CAAA,EACzE,kBAAC,MAAD;OAAI,WAAU;iBAAd;QACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,MAAD;UAAI,WAAU;oBAAsB;UAAW,CAAA,EAC/C,kBAAC,MAAD;UAAI,WAAU;oBAAqB,EAAU,EAAW,OAAO;UAAM,CAAA,CACjE;;QACN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,MAAD;UAAI,WAAU;oBAAsB;UAAe,CAAA,EACnD,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAW,YACR,EAAW,EAAW,WAAW;WAAE,MAAM,KAAA;WAAW,QAAQ,KAAA;WAAW,CAAC,GACxE;UACD,CAAA,CACD;;QACN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,MAAD;UAAI,WAAU;oBAAsB;UAAY,CAAA,EAChD,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAW,QACR,EAAW,EAAW,OAAO;WAAE,MAAM,KAAA;WAAW,QAAQ,KAAA;WAAW,CAAC,GACpE;UACD,CAAA,CACD;;QACH;SACK;;KAEX,EAAW,SACV,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAW,WAAW,EAAW,OAAO,KAAK;MAC5D,WAAU;gBAHZ,CAKE,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;OACnC,MAAM,EAAW,MAAM;OACvB,WAAU;OACV,CAAA,EACF,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAW,MAAM;QAChB,CAAA,EACJ,kBAAC,KAAD;QAAG,WAAU;kBAAuC;QAAc,CAAA,CAC9D;SACC;;KAGV,EAAQ,WACP,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAQ,iBAAiB,QAAQ,EAAQ,MAAM,UAAU;MACxD,CAAA;KAEL,EAAQ,WACP,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAQ,iBAAiB,QAAQ,EAAQ,MAAM,UAAU;MACxD,CAAA;KAEF;MACF;;EACK,CAAA"}
|
|
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 {\n useAssistantEntityName,\n useCollection,\n usePublishCollection,\n useArchiveCollection,\n} 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 // Name the collection for the assistant's screen context.\n useAssistantEntityName(collectionId, query.data?.name);\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 +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';\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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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CollectionsListPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/collections/CollectionsListPage.tsx"],"sourcesContent":["/**\n * Collections List Page\n * @module coolandlovely/pages/collections\n *\n * Lists seasonal collections (`cooListCollections`) with status filter.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Plus, Shapes } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { CollectionCard } from '../../components/CollectionCard';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useCollections, useTr } from '../../hooks';\nimport { cn } from '../../utils/cn';\nimport type { CooCollectionStatus } from '../../types';\n\nconst STATUS_ORDER: CooCollectionStatus[] = ['LIVE', 'SCHEDULED', 'DRAFT', 'ENDED', 'ARCHIVED'];\n\nfunction formatStatusLabel(status: string): string {\n return status.replace(/_/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\nexport function CollectionsListPage() {\n const { navigateTo } = useCoolAndLovely();\n const tr = useTr();\n const [status, setStatus] = useState<CooCollectionStatus | 'ALL'>('ALL');\n\n const statusOptions = useMemo(\n () => [\n { value: 'ALL' as const, label: tr('coolandlovely.filters.all', 'All') },\n ...STATUS_ORDER.map((value) => ({\n value,\n label: tr(`coolandlovely.status.${value.toLowerCase()}`, formatStatusLabel(value)),\n })),\n ],\n [tr]\n );\n\n const query = useCollections({ status: status === 'ALL' ? undefined : status });\n const collections = query.data ?? [];\n\n return (\n <PageLayout\n title={tr('coolandlovely.page.collectionsTitle', 'Collections')}\n description={tr(\n 'coolandlovely.page.collectionsSubtitle',\n 'Seasonal drops and curated edits grouped by brand'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/collections/new')}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" /> {tr('coolandlovely.actions.newCollection', 'New collection')}\n </button>\n }\n >\n <div className=\"mb-6 flex flex-wrap items-center gap-2\">\n {statusOptions.map((opt) => (\n <button\n key={opt.value}\n type=\"button\"\n onClick={() => setStatus(opt.value)}\n className={cn(\n 'rounded-full px-3 py-1.5 text-sm font-medium transition-colors',\n status === opt.value\n ? 'bg-accent-purple-subtle text-text-primary'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n {opt.label}\n </button>\n ))}\n </div>\n\n {query.isLoading ? (\n <LoadingView label={tr('coolandlovely.loading.collections', 'Loading collections…')} />\n ) : query.isError ? (\n <ErrorView onRetry={() => void query.refetch()} />\n ) : collections.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Shapes className=\"h-10 w-10\" />}\n title={tr('coolandlovely.empty.collectionsTitle', 'No collections yet')}\n description={tr(\n 'coolandlovely.empty.collectionsDescription',\n 'Group pieces into a seasonal drop to start a collection.'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/collections/new')}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text\"\n >\n <Plus className=\"h-4 w-4\" />{' '}\n {tr('coolandlovely.actions.newCollection', 'New collection')}\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {collections.map((collection) => (\n <CollectionCard\n key={collection.id}\n collection={collection}\n onClick={() => navigateTo(`/collections/${collection.id}`)}\n />\n ))}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAkBA,IAAM,IAAsC;CAAC;CAAQ;CAAa;CAAS;CAAS;
|
|
1
|
+
{"version":3,"file":"CollectionsListPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/collections/CollectionsListPage.tsx"],"sourcesContent":["/**\n * Collections List Page\n * @module coolandlovely/pages/collections\n *\n * Lists seasonal collections (`cooListCollections`) with status filter.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Plus, Shapes } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { CollectionCard } from '../../components/CollectionCard';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useCollections, useTr } from '../../hooks';\nimport { cn } from '../../utils/cn';\nimport type { CooCollectionStatus } from '../../types';\n\nconst STATUS_ORDER: CooCollectionStatus[] = ['LIVE', 'SCHEDULED', 'DRAFT', 'ENDED', 'ARCHIVED'];\n\nfunction formatStatusLabel(status: string): string {\n return status.replace(/_/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\nexport function CollectionsListPage() {\n const { navigateTo } = useCoolAndLovely();\n const tr = useTr();\n const [status, setStatus] = useState<CooCollectionStatus | 'ALL'>('ALL');\n\n const statusOptions = useMemo(\n () => [\n { value: 'ALL' as const, label: tr('coolandlovely.filters.all', 'All') },\n ...STATUS_ORDER.map((value) => ({\n value,\n label: tr(`coolandlovely.status.${value.toLowerCase()}`, formatStatusLabel(value)),\n })),\n ],\n [tr]\n );\n\n const query = useCollections({ status: status === 'ALL' ? undefined : status });\n const collections = query.data ?? [];\n\n return (\n <PageLayout\n title={tr('coolandlovely.page.collectionsTitle', 'Collections')}\n description={tr(\n 'coolandlovely.page.collectionsSubtitle',\n 'Seasonal drops and curated edits grouped by brand'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/collections/new')}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" /> {tr('coolandlovely.actions.newCollection', 'New collection')}\n </button>\n }\n >\n <div className=\"mb-6 flex flex-wrap items-center gap-2\">\n {statusOptions.map((opt) => (\n <button\n key={opt.value}\n type=\"button\"\n onClick={() => setStatus(opt.value)}\n className={cn(\n 'rounded-full px-3 py-1.5 text-sm font-medium transition-colors',\n status === opt.value\n ? 'bg-accent-purple-subtle text-text-primary'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n {opt.label}\n </button>\n ))}\n </div>\n\n {query.isLoading ? (\n <LoadingView label={tr('coolandlovely.loading.collections', 'Loading collections…')} />\n ) : query.isError ? (\n <ErrorView onRetry={() => void query.refetch()} />\n ) : collections.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Shapes className=\"h-10 w-10\" />}\n title={tr('coolandlovely.empty.collectionsTitle', 'No collections yet')}\n description={tr(\n 'coolandlovely.empty.collectionsDescription',\n 'Group pieces into a seasonal drop to start a collection.'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/collections/new')}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text\"\n >\n <Plus className=\"h-4 w-4\" />{' '}\n {tr('coolandlovely.actions.newCollection', 'New collection')}\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {collections.map((collection) => (\n <CollectionCard\n key={collection.id}\n collection={collection}\n onClick={() => navigateTo(`/collections/${collection.id}`)}\n />\n ))}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAkBA,IAAM,IAAsC;CAAC;CAAQ;CAAa;CAAS;CAAS;AAAU;AAE9F,SAAS,EAAkB,GAAwB;CACjD,OAAO,EAAO,QAAQ,MAAM,GAAG,CAAC,CAAC,QAAQ,UAAU,MAAM,EAAE,YAAY,CAAC;AAC1E;AAEA,SAAgB,IAAsB;CACpC,IAAM,EAAE,kBAAe,EAAiB,GAClC,IAAK,EAAM,GACX,CAAC,GAAQ,KAAa,EAAsC,KAAK,GAEjE,IAAgB,QACd,CACJ;EAAE,OAAO;EAAgB,OAAO,EAAG,6BAA6B,KAAK;CAAE,GACvE,GAAG,EAAa,KAAK,OAAW;EAC9B;EACA,OAAO,EAAG,wBAAwB,EAAM,YAAY,KAAK,EAAkB,CAAK,CAAC;CACnF,EAAE,CACJ,GACA,CAAC,CAAE,CACL,GAEM,IAAQ,EAAe,EAAE,QAAQ,MAAW,QAAQ,KAAA,IAAY,EAAO,CAAC,GACxE,IAAc,EAAM,QAAQ,CAAC;CAEnC,OACE,kBAAC,GAAD;EACE,OAAO,EAAG,uCAAuC,aAAa;EAC9D,aAAa,EACX,0CACA,mDACF;EACA,SACE,kBAAC,UAAD;GACE,MAAK;GACL,eAAe,EAAW,kBAAkB;GAC5C,WAAU;aAHZ;IAKE,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA;IAAC;IAAE,EAAG,uCAAuC,gBAAgB;GAClF;;YAbZ,CAgBE,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAc,KAAK,MAClB,kBAAC,UAAD;IAEE,MAAK;IACL,eAAe,EAAU,EAAI,KAAK;IAClC,WAAW,EACT,kEACA,MAAW,EAAI,QACX,8CACA,0DACN;cAEC,EAAI;GACC,GAXD,EAAI,KAWH,CACT;EACE,CAAA,GAEJ,EAAM,YACL,kBAAC,GAAD,EAAa,OAAO,EAAG,qCAAqC,sBAAsB,EAAI,CAAA,IACpF,EAAM,UACR,kBAAC,GAAD,EAAW,eAAe,KAAK,EAAM,QAAQ,EAAI,CAAA,IAC/C,EAAY,WAAW,IACzB,kBAAC,GAAD;GACE,cAAa;GACb,MAAM,kBAAC,GAAD,EAAQ,WAAU,YAAa,CAAA;GACrC,OAAO,EAAG,wCAAwC,oBAAoB;GACtE,aAAa,EACX,8CACA,0DACF;GACA,QACE,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,EAAW,kBAAkB;IAC5C,WAAU;cAHZ;KAKE,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA;KAAE;KAC5B,EAAG,uCAAuC,gBAAgB;IACrD;;EAEX,CAAA,IAED,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAY,KAAK,MAChB,kBAAC,GAAD;IAEc;IACZ,eAAe,EAAW,gBAAgB,EAAW,IAAI;GAC1D,GAHM,EAAW,EAGjB,CACF;EACE,CAAA,CAEG;;AAEhB"}
|