@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.
Files changed (141) hide show
  1. package/dist/coolandlovely/CoolAndLovelyRoot.d.ts.map +1 -1
  2. package/dist/coolandlovely/CoolAndLovelyRoot.js +16 -4
  3. package/dist/coolandlovely/CoolAndLovelyRoot.js.map +1 -1
  4. package/dist/coolandlovely/CoolAndLovelyRoutes.d.ts.map +1 -1
  5. package/dist/coolandlovely/CoolAndLovelyRoutes.js +19 -15
  6. package/dist/coolandlovely/CoolAndLovelyRoutes.js.map +1 -1
  7. package/dist/coolandlovely/components/ActivityTrail.js +1 -1
  8. package/dist/coolandlovely/components/ActivityTrail.js.map +1 -1
  9. package/dist/coolandlovely/components/ActorName.js.map +1 -1
  10. package/dist/coolandlovely/components/AddToCartPanel.js.map +1 -1
  11. package/dist/coolandlovely/components/AddToLookbookButton.js.map +1 -1
  12. package/dist/coolandlovely/components/BrandCard.js.map +1 -1
  13. package/dist/coolandlovely/components/BrandCover.js.map +1 -1
  14. package/dist/coolandlovely/components/BrandCreatorAvatars.js.map +1 -1
  15. package/dist/coolandlovely/components/CollaborationFeeCard.js.map +1 -1
  16. package/dist/coolandlovely/components/CollectionCard.js.map +1 -1
  17. package/dist/coolandlovely/components/ColourSwatchRow.js.map +1 -1
  18. package/dist/coolandlovely/components/Comments.js.map +1 -1
  19. package/dist/coolandlovely/components/EmptyState.js.map +1 -1
  20. package/dist/coolandlovely/components/FeedbackWidget.js.map +1 -1
  21. package/dist/coolandlovely/components/Field.js.map +1 -1
  22. package/dist/coolandlovely/components/FileImage.js.map +1 -1
  23. package/dist/coolandlovely/components/FollowButton.js.map +1 -1
  24. package/dist/coolandlovely/components/FulfilmentTimeline.js.map +1 -1
  25. package/dist/coolandlovely/components/ImageUploadField.js +3 -1
  26. package/dist/coolandlovely/components/ImageUploadField.js.map +1 -1
  27. package/dist/coolandlovely/components/LookCard.js.map +1 -1
  28. package/dist/coolandlovely/components/LookbookCard.js.map +1 -1
  29. package/dist/coolandlovely/components/MetricsCard.js.map +1 -1
  30. package/dist/coolandlovely/components/PageLayout.js.map +1 -1
  31. package/dist/coolandlovely/components/PayForOrder.js.map +1 -1
  32. package/dist/coolandlovely/components/RefundPanel.js +1 -1
  33. package/dist/coolandlovely/components/RefundPanel.js.map +1 -1
  34. package/dist/coolandlovely/components/ReturnRequests.js +1 -1
  35. package/dist/coolandlovely/components/ReturnRequests.js.map +1 -1
  36. package/dist/coolandlovely/components/ShopItemCard.js.map +1 -1
  37. package/dist/coolandlovely/components/StateViews.js.map +1 -1
  38. package/dist/coolandlovely/components/StatusBadge.js +1 -1
  39. package/dist/coolandlovely/components/StatusBadge.js.map +1 -1
  40. package/dist/coolandlovely/components/StepTracker.js.map +1 -1
  41. package/dist/coolandlovely/components/StockEditor.js.map +1 -1
  42. package/dist/coolandlovely/components/StoreListingCard.js.map +1 -1
  43. package/dist/coolandlovely/components/TagList.js.map +1 -1
  44. package/dist/coolandlovely/components/Thumbnail.js +1 -1
  45. package/dist/coolandlovely/components/Thumbnail.js.map +1 -1
  46. package/dist/coolandlovely/components/VisibilityChip.js.map +1 -1
  47. package/dist/coolandlovely/components/WardrobeItemCard.js.map +1 -1
  48. package/dist/coolandlovely/components/index.js +2 -1
  49. package/dist/coolandlovely/context/CoolAndLovelyContext.js +2 -1
  50. package/dist/coolandlovely/context/CoolAndLovelyContext.js.map +1 -1
  51. package/dist/coolandlovely/context/index.js +2 -1
  52. package/dist/coolandlovely/hooks/index.js +4 -3
  53. package/dist/coolandlovely/hooks/useActivity.d.ts +1 -1
  54. package/dist/coolandlovely/hooks/useActivity.d.ts.map +1 -1
  55. package/dist/coolandlovely/hooks/useActivity.js.map +1 -1
  56. package/dist/coolandlovely/hooks/useAssistantEntity.js.map +1 -1
  57. package/dist/coolandlovely/hooks/useBrandOrdersApi.d.ts +4 -4
  58. package/dist/coolandlovely/hooks/useBrandOrdersApi.d.ts.map +1 -1
  59. package/dist/coolandlovely/hooks/useBrandOrdersApi.js.map +1 -1
  60. package/dist/coolandlovely/hooks/useCatalogImport.js.map +1 -1
  61. package/dist/coolandlovely/hooks/useComments.d.ts +1 -1
  62. package/dist/coolandlovely/hooks/useComments.d.ts.map +1 -1
  63. package/dist/coolandlovely/hooks/useComments.js.map +1 -1
  64. package/dist/coolandlovely/hooks/useCommerceApi.d.ts +7 -7
  65. package/dist/coolandlovely/hooks/useCommerceApi.d.ts.map +1 -1
  66. package/dist/coolandlovely/hooks/useCommerceApi.js.map +1 -1
  67. package/dist/coolandlovely/hooks/useCoolAndLovelyApi.d.ts +24 -24
  68. package/dist/coolandlovely/hooks/useCoolAndLovelyApi.d.ts.map +1 -1
  69. package/dist/coolandlovely/hooks/useCoolAndLovelyApi.js.map +1 -1
  70. package/dist/coolandlovely/hooks/useEditorialApi.d.ts +95 -0
  71. package/dist/coolandlovely/hooks/useEditorialApi.d.ts.map +1 -0
  72. package/dist/coolandlovely/hooks/useEditorialApi.js +107 -0
  73. package/dist/coolandlovely/hooks/useEditorialApi.js.map +1 -0
  74. package/dist/coolandlovely/hooks/useFiles.js.map +1 -1
  75. package/dist/coolandlovely/hooks/useInventoryApi.d.ts +1 -1
  76. package/dist/coolandlovely/hooks/useInventoryApi.d.ts.map +1 -1
  77. package/dist/coolandlovely/hooks/useInventoryApi.js.map +1 -1
  78. package/dist/coolandlovely/hooks/useTr.js.map +1 -1
  79. package/dist/coolandlovely/index.js +6 -5
  80. package/dist/coolandlovely/pages/HomePage.js.map +1 -1
  81. package/dist/coolandlovely/pages/admin/AdminBrandsPage.js.map +1 -1
  82. package/dist/coolandlovely/pages/admin/AdminCollaborationsPage.js.map +1 -1
  83. package/dist/coolandlovely/pages/admin/AdminFeaturedItemsPage.js.map +1 -1
  84. package/dist/coolandlovely/pages/brands/BrandDetailPage.js.map +1 -1
  85. package/dist/coolandlovely/pages/brands/BrandFormPage.js.map +1 -1
  86. package/dist/coolandlovely/pages/brands/BrandsListPage.js.map +1 -1
  87. package/dist/coolandlovely/pages/brands/ImportCatalogPage.js.map +1 -1
  88. package/dist/coolandlovely/pages/collaborations/CollaborationDetailPage.js.map +1 -1
  89. package/dist/coolandlovely/pages/collaborations/CollaborationFormPage.js +1 -1
  90. package/dist/coolandlovely/pages/collaborations/CollaborationFormPage.js.map +1 -1
  91. package/dist/coolandlovely/pages/collaborations/CollaborationsListPage.js.map +1 -1
  92. package/dist/coolandlovely/pages/collections/CollectionDetailPage.js.map +1 -1
  93. package/dist/coolandlovely/pages/collections/CollectionFormPage.js.map +1 -1
  94. package/dist/coolandlovely/pages/collections/CollectionsListPage.js.map +1 -1
  95. package/dist/coolandlovely/pages/commerce/BrandOrdersPage.js.map +1 -1
  96. package/dist/coolandlovely/pages/commerce/CartPage.js.map +1 -1
  97. package/dist/coolandlovely/pages/commerce/CreatorEarningsPage.js.map +1 -1
  98. package/dist/coolandlovely/pages/commerce/OrdersPage.js.map +1 -1
  99. package/dist/coolandlovely/pages/discover/DiscoverPage.js +1 -1
  100. package/dist/coolandlovely/pages/discover/DiscoverPage.js.map +1 -1
  101. package/dist/coolandlovely/pages/editorial/EditorialPage.d.ts +2 -0
  102. package/dist/coolandlovely/pages/editorial/EditorialPage.d.ts.map +1 -0
  103. package/dist/coolandlovely/pages/editorial/EditorialPage.js +486 -0
  104. package/dist/coolandlovely/pages/editorial/EditorialPage.js.map +1 -0
  105. package/dist/coolandlovely/pages/items/StoreListedPieceView.js.map +1 -1
  106. package/dist/coolandlovely/pages/items/StyleItemDetailPage.js.map +1 -1
  107. package/dist/coolandlovely/pages/items/StyleItemFormPage.js.map +1 -1
  108. package/dist/coolandlovely/pages/items/StyleItemsListPage.js.map +1 -1
  109. package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js.map +1 -1
  110. package/dist/coolandlovely/pages/lookbooks/LookbooksListPage.js.map +1 -1
  111. package/dist/coolandlovely/pages/looks/LookDetailPage.js.map +1 -1
  112. package/dist/coolandlovely/pages/looks/LookFormPage.js.map +1 -1
  113. package/dist/coolandlovely/pages/looks/LooksListPage.js.map +1 -1
  114. package/dist/coolandlovely/pages/profile/StyleProfilePage.js +1 -1
  115. package/dist/coolandlovely/pages/profile/StyleProfilePage.js.map +1 -1
  116. package/dist/coolandlovely/pages/trends/TrendsPage.js.map +1 -1
  117. package/dist/coolandlovely/pages/wardrobe/WardrobeFormPage.js.map +1 -1
  118. package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.js.map +1 -1
  119. package/dist/coolandlovely/store/coolandlovelyStore.js.map +1 -1
  120. package/dist/coolandlovely/store/index.js +2 -1
  121. package/dist/coolandlovely/types/commerce.d.ts.map +1 -1
  122. package/dist/coolandlovely/types/index.d.ts.map +1 -1
  123. package/dist/coolandlovely/utils/cn.js +1 -1
  124. package/dist/coolandlovely/utils/cn.js.map +1 -1
  125. package/dist/coolandlovely/utils/formatters.js.map +1 -1
  126. package/dist/coolandlovely/utils/icons.js.map +1 -1
  127. package/dist/coolandlovely/utils/index.js +2 -1
  128. package/dist/coolandlovely/utils/navigation.js.map +1 -1
  129. package/dist/coolandlovely/utils/paymentProviders.js +22 -22
  130. package/dist/coolandlovely/utils/paymentProviders.js.map +1 -1
  131. package/dist/coolandlovely/utils/sparkline.js +13 -15
  132. package/dist/coolandlovely/utils/sparkline.js.map +1 -1
  133. package/dist/node_modules/clsx/dist/clsx.js +1 -1
  134. package/dist/node_modules/clsx/dist/clsx.js.map +1 -1
  135. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +225 -146
  136. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -1
  137. package/dist/services/createSandboxAssistantTransport.js.map +1 -1
  138. package/dist/stories/DiscoverPage.stories.d.ts.map +1 -1
  139. package/dist/utils/pageContext.js +1 -1
  140. package/dist/utils/pageContext.js.map +1 -1
  141. package/package.json +6 -4
@@ -1 +1 @@
1
- {"version":3,"file":"BrandOrdersPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/commerce/BrandOrdersPage.tsx"],"sourcesContent":["/**\n * Brand orders + brand order detail (BOFF-9636)\n * @module coolandlovely/pages/commerce\n *\n * The brand side of CoolAndLovely orders: the paid orders that contain a brand's pieces\n * (`cooBrandOrders`, only that brand's lines), counts per fulfilment state\n * (`cooBrandOrderSummary`), and one order's fulfilment — confirm it, ship it with tracking,\n * mark it delivered (`cooUpdateOrderFulfilment`). Fulfilment only moves forward, and the\n * buyer is notified of every change. A brand can also refund all of its pieces of an order\n * (`cooRefundBrandOrder`, BOFF-9702); refunded pieces are no longer fulfilled. Shoppers' return\n * requests (BOFF-9900) are listed while pending, and the brand approves (which refunds) or\n * declines each one on the order detail.\n */\n\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams, useSearchParams } from 'react-router';\nimport {\n ArrowLeft,\n CheckCircle2,\n ChevronRight,\n Loader2,\n Package,\n PackageCheck,\n Truck,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\n\nimport { EmptyState } from '../../components/EmptyState';\nimport { FulfilmentTimeline } from '../../components/FulfilmentTimeline';\nimport { SelectField, TextAreaField, TextField } from '../../components/Field';\nimport { PageLayout } from '../../components/PageLayout';\nimport { RefundPanel } from '../../components/RefundPanel';\nimport { ReturnRequestPanel } from '../../components/ReturnRequests';\nimport { ErrorView, LoadingView } from '../../components/StateViews';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useBrands, useTr } from '../../hooks';\nimport {\n useBrandOrder,\n useBrandOrderSummary,\n useBrandOrders,\n useBrandReturnRequests,\n useUpdateOrderFulfilment,\n} from '../../hooks/useBrandOrdersApi';\nimport type { CooBrandOrder, CooBrandOrderSummary, CooFulfilmentStatus } from '../../types';\nimport { cn } from '../../utils/cn';\nimport { formatDate, formatPrice } from '../../utils/formatters';\n\nconst FILTERS: Array<{\n value: CooFulfilmentStatus | undefined;\n key: string;\n label: string;\n count: (summary: CooBrandOrderSummary) => number;\n}> = [\n {\n value: undefined,\n key: 'coolandlovely.brandOrders.filterAll',\n label: 'All',\n count: (s) => s.total,\n },\n {\n value: 'UNFULFILLED',\n key: 'coolandlovely.brandOrders.filterUnfulfilled',\n label: 'To confirm',\n count: (s) => s.unfulfilled,\n },\n {\n value: 'CONFIRMED',\n key: 'coolandlovely.brandOrders.filterConfirmed',\n label: 'Confirmed',\n count: (s) => s.confirmed,\n },\n {\n value: 'SHIPPED',\n key: 'coolandlovely.brandOrders.filterShipped',\n label: 'Shipped',\n count: (s) => s.shipped,\n },\n {\n value: 'DELIVERED',\n key: 'coolandlovely.brandOrders.filterDelivered',\n label: 'Delivered',\n count: (s) => s.delivered,\n },\n];\n\nconst secondaryButton =\n 'inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-50';\nconst primaryButton =\n 'inline-flex w-full items-center justify-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2.5 text-sm font-semibold text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50';\n\nexport function BrandOrdersPage() {\n const tr = useTr();\n const { navigateTo } = useCoolAndLovely();\n const [searchParams, setSearchParams] = useSearchParams();\n const brands = useBrands({ pagination: { limit: 100, offset: 0 } });\n const brandOptions = useMemo(\n () => (brands.data?.items ?? []).map((brand) => ({ value: brand.id, label: brand.name })),\n [brands.data]\n );\n const brandId = searchParams.get('brand') ?? brandOptions[0]?.value;\n const [status, setStatus] = useState<CooFulfilmentStatus | undefined>(undefined);\n const summary = useBrandOrderSummary(brandId);\n const orders = useBrandOrders(brandId, status);\n const pendingReturns = useBrandReturnRequests(brandId, 'REQUESTED');\n const title = tr('coolandlovely.brandOrders.title', 'Brand orders');\n\n useEffect(() => setStatus(undefined), [brandId]);\n\n const selectBrand = (value: string) => {\n const next = new URLSearchParams(searchParams);\n next.set('brand', value);\n setSearchParams(next, { replace: true });\n };\n\n return (\n <PageLayout\n title={title}\n description={tr(\n 'coolandlovely.brandOrders.description',\n 'Paid orders for your pieces. Confirm them, ship them with tracking and mark them delivered.'\n )}\n >\n {brands.isLoading ? (\n <LoadingView />\n ) : brandOptions.length === 0 ? (\n <EmptyState\n icon={<Package className=\"h-8 w-8\" />}\n title={tr('coolandlovely.brandOrders.noBrandsTitle', 'No brands yet')}\n description={tr(\n 'coolandlovely.brandOrders.noBrandsDescription',\n 'Create a brand storefront to start selling pieces.'\n )}\n />\n ) : (\n <>\n <div className=\"mb-4 max-w-sm\">\n <SelectField\n id=\"coo-brand-orders-brand\"\n label={tr('coolandlovely.brandOrders.brand', 'Brand')}\n value={brandId ?? ''}\n onChange={selectBrand}\n options={brandOptions}\n />\n </div>\n {(pendingReturns.data?.items.length ?? 0) > 0 && (\n <GlassCard\n treatment=\"plain\"\n className=\"mb-4 space-y-2 p-4\"\n data-testid=\"brand-pending-returns\"\n >\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n {tr('coolandlovely.returns.pendingTitle', 'Return requests waiting for you')}\n </p>\n {pendingReturns.data?.items.map((request) => (\n <button\n key={request.id}\n type=\"button\"\n onClick={() => navigateTo(`/brand-orders/${request.brandId}/${request.orderId}`)}\n className=\"flex w-full items-center gap-3 rounded-lg px-2 py-1.5 text-left transition-colors hover:bg-bg-sunken/50\"\n data-testid=\"brand-pending-return\"\n >\n <span className=\"font-mono text-sm text-text-primary\">\n {request.orderNumber ?? request.orderId}\n </span>\n <span className=\"min-w-0 flex-1 truncate text-xs text-text-secondary\">\n {request.reason}\n </span>\n <span className=\"text-xs text-text-secondary\">\n {formatDate(request.createdAt)}\n </span>\n <ChevronRight className=\"h-4 w-4 text-text-secondary\" />\n </button>\n ))}\n </GlassCard>\n )}\n <div className=\"mb-4 flex flex-wrap gap-2\" role=\"tablist\">\n {FILTERS.map((filter) => (\n <button\n key={filter.key}\n type=\"button\"\n role=\"tab\"\n aria-selected={status === filter.value}\n onClick={() => setStatus(filter.value)}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-sm font-medium transition-colors',\n status === filter.value\n ? 'border-action-primary-bg bg-action-primary-bg text-action-primary-text'\n : 'border-border-seam text-text-primary hover:bg-bg-sunken'\n )}\n >\n {tr(filter.key, filter.label)}\n {summary.data && (\n <span className=\"tabular-nums opacity-80\">{filter.count(summary.data)}</span>\n )}\n </button>\n ))}\n </div>\n {orders.isLoading ? (\n <LoadingView label={tr('coolandlovely.brandOrders.loading', 'Loading orders…')} />\n ) : orders.isError || !orders.data ? (\n <ErrorView message={orders.error?.message} onRetry={() => void orders.refetch()} />\n ) : orders.data.items.length === 0 ? (\n <EmptyState\n icon={<Package className=\"h-8 w-8\" />}\n title={tr('coolandlovely.brandOrders.emptyTitle', 'No orders here')}\n description={tr(\n 'coolandlovely.brandOrders.emptyDescription',\n 'Paid orders for this brand’s pieces appear here.'\n )}\n />\n ) : (\n <div className=\"space-y-3\" data-testid=\"brand-orders-list\">\n {orders.data.items.map((order) => (\n <button\n key={order.id}\n type=\"button\"\n onClick={() => navigateTo(`/brand-orders/${order.brandId}/${order.id}`)}\n className=\"flex w-full items-center gap-4 rounded-xl border border-border-seam bg-bg-surface p-4 text-left transition-colors hover:bg-bg-sunken/50\"\n >\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-mono text-sm font-medium text-text-primary\">\n {order.orderNumber}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">\n {formatDate(order.paidAt ?? order.createdAt)} · {order.shippingName ?? '—'} ·{' '}\n {tr('coolandlovely.orders.pieces', '{{count}} pieces', {\n count: order.itemCount,\n })}\n </p>\n </div>\n {order.returnRequest?.status === 'REQUESTED' && (\n <StatusBadge status=\"REQUESTED\" />\n )}\n <StatusBadge\n status={order.fulfilment.refunded ? 'REFUNDED' : order.fulfilment.status}\n />\n <span className=\"w-28 text-right text-sm font-semibold tabular-nums text-text-primary\">\n {formatPrice(order.brandSubtotal, order.currency)}\n </span>\n <ChevronRight className=\"h-4 w-4 text-text-secondary\" />\n </button>\n ))}\n </div>\n )}\n </>\n )}\n </PageLayout>\n );\n}\n\nfunction FulfilmentForm({ order }: { order: CooBrandOrder }) {\n const tr = useTr();\n const update = useUpdateOrderFulfilment();\n const current = order.fulfilment;\n const [carrier, setCarrier] = useState(current.carrier ?? '');\n const [trackingNumber, setTrackingNumber] = useState(current.trackingNumber ?? '');\n const [trackingUrl, setTrackingUrl] = useState(current.trackingUrl ?? '');\n const [note, setNote] = useState(current.note ?? '');\n\n if (current.status === 'DELIVERED') {\n return (\n <p className=\"text-sm text-status-success-text\">\n {tr('coolandlovely.fulfilment.complete', 'Delivered. Nothing left to do for this order.')}\n </p>\n );\n }\n\n const details = {\n brandId: order.brandId,\n orderId: order.id,\n carrier: carrier.trim() || null,\n trackingNumber: trackingNumber.trim() || null,\n trackingUrl: trackingUrl.trim() || null,\n note: note.trim() || null,\n };\n const canShip = !!details.carrier && !!details.trackingNumber;\n\n return (\n <div className=\"space-y-3 border-t border-border-seam pt-4\">\n <TextField\n id=\"coo-fulfilment-carrier\"\n label={tr('coolandlovely.fulfilment.carrier', 'Carrier')}\n value={carrier}\n onChange={setCarrier}\n placeholder=\"Delhivery\"\n />\n <TextField\n id=\"coo-fulfilment-tracking\"\n label={tr('coolandlovely.fulfilment.trackingNumber', 'Tracking number')}\n value={trackingNumber}\n onChange={setTrackingNumber}\n />\n <TextField\n id=\"coo-fulfilment-tracking-url\"\n type=\"url\"\n label={tr('coolandlovely.fulfilment.trackingUrl', 'Tracking link (optional)')}\n value={trackingUrl}\n onChange={setTrackingUrl}\n placeholder=\"https://\"\n />\n <TextAreaField\n id=\"coo-fulfilment-note\"\n label={tr('coolandlovely.fulfilment.note', 'Note for the buyer (optional)')}\n value={note}\n onChange={setNote}\n rows={2}\n />\n {update.error && <p className=\"text-sm text-status-error-text\">{update.error.message}</p>}\n <div className=\"space-y-2\">\n {current.status === 'UNFULFILLED' && (\n <button\n type=\"button\"\n disabled={update.isPending}\n onClick={() => update.mutate({ ...details, status: 'CONFIRMED' })}\n className={cn(secondaryButton, 'w-full justify-center')}\n >\n <CheckCircle2 className=\"h-4 w-4\" />\n {tr('coolandlovely.fulfilment.confirm', 'Confirm order')}\n </button>\n )}\n {(current.status === 'UNFULFILLED' || current.status === 'CONFIRMED') && (\n <button\n type=\"button\"\n disabled={update.isPending || !canShip}\n onClick={() => update.mutate({ ...details, status: 'SHIPPED' })}\n className={primaryButton}\n >\n {update.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Truck className=\"h-4 w-4\" />\n )}\n {tr('coolandlovely.fulfilment.markShipped', 'Mark shipped')}\n </button>\n )}\n {current.status === 'SHIPPED' && (\n <>\n <button\n type=\"button\"\n disabled={update.isPending || !canShip}\n onClick={() => update.mutate({ ...details, status: 'SHIPPED' })}\n className={cn(secondaryButton, 'w-full justify-center')}\n >\n {tr('coolandlovely.fulfilment.saveTracking', 'Save tracking details')}\n </button>\n <button\n type=\"button\"\n disabled={update.isPending}\n onClick={() => update.mutate({ ...details, status: 'DELIVERED' })}\n className={primaryButton}\n >\n {update.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <PackageCheck className=\"h-4 w-4\" />\n )}\n {tr('coolandlovely.fulfilment.markDelivered', 'Mark delivered')}\n </button>\n </>\n )}\n {!canShip && current.status !== 'SHIPPED' && (\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'coolandlovely.fulfilment.trackingHint',\n 'Add a carrier and tracking number to mark the order shipped.'\n )}\n </p>\n )}\n </div>\n </div>\n );\n}\n\nexport function BrandOrderDetailPage() {\n const tr = useTr();\n const { brandId, orderId } = useParams<{ brandId: string; orderId: string }>();\n const { navigateTo } = useCoolAndLovely();\n const order = useBrandOrder(brandId, orderId);\n const title = tr('coolandlovely.brandOrders.detailTitle', 'Order');\n const back = () => navigateTo(`/brand-orders?brand=${brandId ?? ''}`);\n\n if (order.isLoading) {\n return (\n <PageLayout title={title}>\n <LoadingView />\n </PageLayout>\n );\n }\n if (order.isError || !order.data) {\n return (\n <PageLayout title={title}>\n <ErrorView\n title={tr('coolandlovely.orders.notFound', 'Order not found')}\n message={order.error?.message}\n onRetry={() => void order.refetch()}\n />\n </PageLayout>\n );\n }\n\n const data = order.data;\n // Pieces the brand refunded, or is refunding, are not shipped.\n const refundBlocksFulfilment =\n data.fulfilment.refunded ||\n data.refund?.status === 'PENDING' ||\n data.refund?.status === 'COMPLETED';\n return (\n <PageLayout\n title={data.orderNumber}\n description={tr('coolandlovely.brandOrders.paidOn', 'Paid {{date}}', {\n date: formatDate(data.paidAt ?? data.createdAt),\n })}\n actions={\n <button type=\"button\" onClick={back} className={secondaryButton}>\n <ArrowLeft className=\"h-4 w-4\" /> {tr('coolandlovely.common.back', 'Back')}\n </button>\n }\n >\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"space-y-3 lg:col-span-2\">\n {data.items.map((item) => (\n <GlassCard key={item.id} treatment=\"plain\" className=\"flex items-center gap-4 p-4\">\n <Thumbnail\n icon={<Package className=\"h-5 w-5\" />}\n seed={item.styleItemId}\n fileId={item.image?.id}\n alt={item.name}\n className=\"h-14 w-14 shrink-0\"\n />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{item.name}</p>\n <p className=\"truncate text-xs text-text-secondary\">\n {[item.sku, item.size, item.colour, `× ${item.quantity}`]\n .filter(Boolean)\n .join(' · ')}\n </p>\n </div>\n <span className=\"text-sm font-semibold tabular-nums text-text-primary\">\n {formatPrice(item.lineTotal, data.currency)}\n </span>\n </GlassCard>\n ))}\n <GlassCard treatment=\"plain\" className=\"space-y-1 p-4 text-sm\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n {tr('coolandlovely.orders.shipTo', 'Ship to')}\n </p>\n <p className=\"text-text-primary\">{data.shippingName}</p>\n <p className=\"whitespace-pre-line text-text-secondary\">{data.shippingAddress}</p>\n {data.buyerNote && (\n <p className=\"pt-2 text-text-secondary\">\n {tr('coolandlovely.brandOrders.buyerNote', 'Buyer note: {{note}}', {\n note: data.buyerNote,\n })}\n </p>\n )}\n </GlassCard>\n </div>\n <GlassCard treatment=\"glass\" className=\"h-fit space-y-4 p-6\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm text-text-secondary\">\n {tr('coolandlovely.fulfilment.status', 'Fulfilment')}\n </span>\n <span data-testid=\"fulfilment-status\">\n <StatusBadge\n status={data.fulfilment.refunded ? 'REFUNDED' : data.fulfilment.status}\n />\n </span>\n </div>\n <div className=\"flex justify-between text-sm\">\n <span className=\"text-text-secondary\">\n {tr('coolandlovely.brandOrders.brandSubtotal', 'Your pieces')}\n </span>\n <span className=\"font-semibold tabular-nums text-text-primary\">\n {formatPrice(data.brandSubtotal, data.currency)}\n </span>\n </div>\n <FulfilmentTimeline fulfilment={data.fulfilment} />\n {!refundBlocksFulfilment && (\n <FulfilmentForm key={data.fulfilment.updatedAt ?? 'new'} order={data} />\n )}\n <ReturnRequestPanel order={data} />\n <RefundPanel order={data} />\n </GlassCard>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"BrandOrdersPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/commerce/BrandOrdersPage.tsx"],"sourcesContent":["/**\n * Brand orders + brand order detail (BOFF-9636)\n * @module coolandlovely/pages/commerce\n *\n * The brand side of CoolAndLovely orders: the paid orders that contain a brand's pieces\n * (`cooBrandOrders`, only that brand's lines), counts per fulfilment state\n * (`cooBrandOrderSummary`), and one order's fulfilment — confirm it, ship it with tracking,\n * mark it delivered (`cooUpdateOrderFulfilment`). Fulfilment only moves forward, and the\n * buyer is notified of every change. A brand can also refund all of its pieces of an order\n * (`cooRefundBrandOrder`, BOFF-9702); refunded pieces are no longer fulfilled. Shoppers' return\n * requests (BOFF-9900) are listed while pending, and the brand approves (which refunds) or\n * declines each one on the order detail.\n */\n\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams, useSearchParams } from 'react-router';\nimport {\n ArrowLeft,\n CheckCircle2,\n ChevronRight,\n Loader2,\n Package,\n PackageCheck,\n Truck,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\n\nimport { EmptyState } from '../../components/EmptyState';\nimport { FulfilmentTimeline } from '../../components/FulfilmentTimeline';\nimport { SelectField, TextAreaField, TextField } from '../../components/Field';\nimport { PageLayout } from '../../components/PageLayout';\nimport { RefundPanel } from '../../components/RefundPanel';\nimport { ReturnRequestPanel } from '../../components/ReturnRequests';\nimport { ErrorView, LoadingView } from '../../components/StateViews';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useBrands, useTr } from '../../hooks';\nimport {\n useBrandOrder,\n useBrandOrderSummary,\n useBrandOrders,\n useBrandReturnRequests,\n useUpdateOrderFulfilment,\n} from '../../hooks/useBrandOrdersApi';\nimport type { CooBrandOrder, CooBrandOrderSummary, CooFulfilmentStatus } from '../../types';\nimport { cn } from '../../utils/cn';\nimport { formatDate, formatPrice } from '../../utils/formatters';\n\nconst FILTERS: Array<{\n value: CooFulfilmentStatus | undefined;\n key: string;\n label: string;\n count: (summary: CooBrandOrderSummary) => number;\n}> = [\n {\n value: undefined,\n key: 'coolandlovely.brandOrders.filterAll',\n label: 'All',\n count: (s) => s.total,\n },\n {\n value: 'UNFULFILLED',\n key: 'coolandlovely.brandOrders.filterUnfulfilled',\n label: 'To confirm',\n count: (s) => s.unfulfilled,\n },\n {\n value: 'CONFIRMED',\n key: 'coolandlovely.brandOrders.filterConfirmed',\n label: 'Confirmed',\n count: (s) => s.confirmed,\n },\n {\n value: 'SHIPPED',\n key: 'coolandlovely.brandOrders.filterShipped',\n label: 'Shipped',\n count: (s) => s.shipped,\n },\n {\n value: 'DELIVERED',\n key: 'coolandlovely.brandOrders.filterDelivered',\n label: 'Delivered',\n count: (s) => s.delivered,\n },\n];\n\nconst secondaryButton =\n 'inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-50';\nconst primaryButton =\n 'inline-flex w-full items-center justify-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2.5 text-sm font-semibold text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50';\n\nexport function BrandOrdersPage() {\n const tr = useTr();\n const { navigateTo } = useCoolAndLovely();\n const [searchParams, setSearchParams] = useSearchParams();\n const brands = useBrands({ pagination: { limit: 100, offset: 0 } });\n const brandOptions = useMemo(\n () => (brands.data?.items ?? []).map((brand) => ({ value: brand.id, label: brand.name })),\n [brands.data]\n );\n const brandId = searchParams.get('brand') ?? brandOptions[0]?.value;\n const [status, setStatus] = useState<CooFulfilmentStatus | undefined>(undefined);\n const summary = useBrandOrderSummary(brandId);\n const orders = useBrandOrders(brandId, status);\n const pendingReturns = useBrandReturnRequests(brandId, 'REQUESTED');\n const title = tr('coolandlovely.brandOrders.title', 'Brand orders');\n\n useEffect(() => setStatus(undefined), [brandId]);\n\n const selectBrand = (value: string) => {\n const next = new URLSearchParams(searchParams);\n next.set('brand', value);\n setSearchParams(next, { replace: true });\n };\n\n return (\n <PageLayout\n title={title}\n description={tr(\n 'coolandlovely.brandOrders.description',\n 'Paid orders for your pieces. Confirm them, ship them with tracking and mark them delivered.'\n )}\n >\n {brands.isLoading ? (\n <LoadingView />\n ) : brandOptions.length === 0 ? (\n <EmptyState\n icon={<Package className=\"h-8 w-8\" />}\n title={tr('coolandlovely.brandOrders.noBrandsTitle', 'No brands yet')}\n description={tr(\n 'coolandlovely.brandOrders.noBrandsDescription',\n 'Create a brand storefront to start selling pieces.'\n )}\n />\n ) : (\n <>\n <div className=\"mb-4 max-w-sm\">\n <SelectField\n id=\"coo-brand-orders-brand\"\n label={tr('coolandlovely.brandOrders.brand', 'Brand')}\n value={brandId ?? ''}\n onChange={selectBrand}\n options={brandOptions}\n />\n </div>\n {(pendingReturns.data?.items.length ?? 0) > 0 && (\n <GlassCard\n treatment=\"plain\"\n className=\"mb-4 space-y-2 p-4\"\n data-testid=\"brand-pending-returns\"\n >\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n {tr('coolandlovely.returns.pendingTitle', 'Return requests waiting for you')}\n </p>\n {pendingReturns.data?.items.map((request) => (\n <button\n key={request.id}\n type=\"button\"\n onClick={() => navigateTo(`/brand-orders/${request.brandId}/${request.orderId}`)}\n className=\"flex w-full items-center gap-3 rounded-lg px-2 py-1.5 text-left transition-colors hover:bg-bg-sunken/50\"\n data-testid=\"brand-pending-return\"\n >\n <span className=\"font-mono text-sm text-text-primary\">\n {request.orderNumber ?? request.orderId}\n </span>\n <span className=\"min-w-0 flex-1 truncate text-xs text-text-secondary\">\n {request.reason}\n </span>\n <span className=\"text-xs text-text-secondary\">\n {formatDate(request.createdAt)}\n </span>\n <ChevronRight className=\"h-4 w-4 text-text-secondary\" />\n </button>\n ))}\n </GlassCard>\n )}\n <div className=\"mb-4 flex flex-wrap gap-2\" role=\"tablist\">\n {FILTERS.map((filter) => (\n <button\n key={filter.key}\n type=\"button\"\n role=\"tab\"\n aria-selected={status === filter.value}\n onClick={() => setStatus(filter.value)}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-sm font-medium transition-colors',\n status === filter.value\n ? 'border-action-primary-bg bg-action-primary-bg text-action-primary-text'\n : 'border-border-seam text-text-primary hover:bg-bg-sunken'\n )}\n >\n {tr(filter.key, filter.label)}\n {summary.data && (\n <span className=\"tabular-nums opacity-80\">{filter.count(summary.data)}</span>\n )}\n </button>\n ))}\n </div>\n {orders.isLoading ? (\n <LoadingView label={tr('coolandlovely.brandOrders.loading', 'Loading orders…')} />\n ) : orders.isError || !orders.data ? (\n <ErrorView message={orders.error?.message} onRetry={() => void orders.refetch()} />\n ) : orders.data.items.length === 0 ? (\n <EmptyState\n icon={<Package className=\"h-8 w-8\" />}\n title={tr('coolandlovely.brandOrders.emptyTitle', 'No orders here')}\n description={tr(\n 'coolandlovely.brandOrders.emptyDescription',\n 'Paid orders for this brand’s pieces appear here.'\n )}\n />\n ) : (\n <div className=\"space-y-3\" data-testid=\"brand-orders-list\">\n {orders.data.items.map((order) => (\n <button\n key={order.id}\n type=\"button\"\n onClick={() => navigateTo(`/brand-orders/${order.brandId}/${order.id}`)}\n className=\"flex w-full items-center gap-4 rounded-xl border border-border-seam bg-bg-surface p-4 text-left transition-colors hover:bg-bg-sunken/50\"\n >\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-mono text-sm font-medium text-text-primary\">\n {order.orderNumber}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">\n {formatDate(order.paidAt ?? order.createdAt)} · {order.shippingName ?? '—'} ·{' '}\n {tr('coolandlovely.orders.pieces', '{{count}} pieces', {\n count: order.itemCount,\n })}\n </p>\n </div>\n {order.returnRequest?.status === 'REQUESTED' && (\n <StatusBadge status=\"REQUESTED\" />\n )}\n <StatusBadge\n status={order.fulfilment.refunded ? 'REFUNDED' : order.fulfilment.status}\n />\n <span className=\"w-28 text-right text-sm font-semibold tabular-nums text-text-primary\">\n {formatPrice(order.brandSubtotal, order.currency)}\n </span>\n <ChevronRight className=\"h-4 w-4 text-text-secondary\" />\n </button>\n ))}\n </div>\n )}\n </>\n )}\n </PageLayout>\n );\n}\n\nfunction FulfilmentForm({ order }: { order: CooBrandOrder }) {\n const tr = useTr();\n const update = useUpdateOrderFulfilment();\n const current = order.fulfilment;\n const [carrier, setCarrier] = useState(current.carrier ?? '');\n const [trackingNumber, setTrackingNumber] = useState(current.trackingNumber ?? '');\n const [trackingUrl, setTrackingUrl] = useState(current.trackingUrl ?? '');\n const [note, setNote] = useState(current.note ?? '');\n\n if (current.status === 'DELIVERED') {\n return (\n <p className=\"text-sm text-status-success-text\">\n {tr('coolandlovely.fulfilment.complete', 'Delivered. Nothing left to do for this order.')}\n </p>\n );\n }\n\n const details = {\n brandId: order.brandId,\n orderId: order.id,\n carrier: carrier.trim() || null,\n trackingNumber: trackingNumber.trim() || null,\n trackingUrl: trackingUrl.trim() || null,\n note: note.trim() || null,\n };\n const canShip = !!details.carrier && !!details.trackingNumber;\n\n return (\n <div className=\"space-y-3 border-t border-border-seam pt-4\">\n <TextField\n id=\"coo-fulfilment-carrier\"\n label={tr('coolandlovely.fulfilment.carrier', 'Carrier')}\n value={carrier}\n onChange={setCarrier}\n placeholder=\"Delhivery\"\n />\n <TextField\n id=\"coo-fulfilment-tracking\"\n label={tr('coolandlovely.fulfilment.trackingNumber', 'Tracking number')}\n value={trackingNumber}\n onChange={setTrackingNumber}\n />\n <TextField\n id=\"coo-fulfilment-tracking-url\"\n type=\"url\"\n label={tr('coolandlovely.fulfilment.trackingUrl', 'Tracking link (optional)')}\n value={trackingUrl}\n onChange={setTrackingUrl}\n placeholder=\"https://\"\n />\n <TextAreaField\n id=\"coo-fulfilment-note\"\n label={tr('coolandlovely.fulfilment.note', 'Note for the buyer (optional)')}\n value={note}\n onChange={setNote}\n rows={2}\n />\n {update.error && <p className=\"text-sm text-status-error-text\">{update.error.message}</p>}\n <div className=\"space-y-2\">\n {current.status === 'UNFULFILLED' && (\n <button\n type=\"button\"\n disabled={update.isPending}\n onClick={() => update.mutate({ ...details, status: 'CONFIRMED' })}\n className={cn(secondaryButton, 'w-full justify-center')}\n >\n <CheckCircle2 className=\"h-4 w-4\" />\n {tr('coolandlovely.fulfilment.confirm', 'Confirm order')}\n </button>\n )}\n {(current.status === 'UNFULFILLED' || current.status === 'CONFIRMED') && (\n <button\n type=\"button\"\n disabled={update.isPending || !canShip}\n onClick={() => update.mutate({ ...details, status: 'SHIPPED' })}\n className={primaryButton}\n >\n {update.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Truck className=\"h-4 w-4\" />\n )}\n {tr('coolandlovely.fulfilment.markShipped', 'Mark shipped')}\n </button>\n )}\n {current.status === 'SHIPPED' && (\n <>\n <button\n type=\"button\"\n disabled={update.isPending || !canShip}\n onClick={() => update.mutate({ ...details, status: 'SHIPPED' })}\n className={cn(secondaryButton, 'w-full justify-center')}\n >\n {tr('coolandlovely.fulfilment.saveTracking', 'Save tracking details')}\n </button>\n <button\n type=\"button\"\n disabled={update.isPending}\n onClick={() => update.mutate({ ...details, status: 'DELIVERED' })}\n className={primaryButton}\n >\n {update.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <PackageCheck className=\"h-4 w-4\" />\n )}\n {tr('coolandlovely.fulfilment.markDelivered', 'Mark delivered')}\n </button>\n </>\n )}\n {!canShip && current.status !== 'SHIPPED' && (\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'coolandlovely.fulfilment.trackingHint',\n 'Add a carrier and tracking number to mark the order shipped.'\n )}\n </p>\n )}\n </div>\n </div>\n );\n}\n\nexport function BrandOrderDetailPage() {\n const tr = useTr();\n const { brandId, orderId } = useParams<{ brandId: string; orderId: string }>();\n const { navigateTo } = useCoolAndLovely();\n const order = useBrandOrder(brandId, orderId);\n const title = tr('coolandlovely.brandOrders.detailTitle', 'Order');\n const back = () => navigateTo(`/brand-orders?brand=${brandId ?? ''}`);\n\n if (order.isLoading) {\n return (\n <PageLayout title={title}>\n <LoadingView />\n </PageLayout>\n );\n }\n if (order.isError || !order.data) {\n return (\n <PageLayout title={title}>\n <ErrorView\n title={tr('coolandlovely.orders.notFound', 'Order not found')}\n message={order.error?.message}\n onRetry={() => void order.refetch()}\n />\n </PageLayout>\n );\n }\n\n const data = order.data;\n // Pieces the brand refunded, or is refunding, are not shipped.\n const refundBlocksFulfilment =\n data.fulfilment.refunded ||\n data.refund?.status === 'PENDING' ||\n data.refund?.status === 'COMPLETED';\n return (\n <PageLayout\n title={data.orderNumber}\n description={tr('coolandlovely.brandOrders.paidOn', 'Paid {{date}}', {\n date: formatDate(data.paidAt ?? data.createdAt),\n })}\n actions={\n <button type=\"button\" onClick={back} className={secondaryButton}>\n <ArrowLeft className=\"h-4 w-4\" /> {tr('coolandlovely.common.back', 'Back')}\n </button>\n }\n >\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"space-y-3 lg:col-span-2\">\n {data.items.map((item) => (\n <GlassCard key={item.id} treatment=\"plain\" className=\"flex items-center gap-4 p-4\">\n <Thumbnail\n icon={<Package className=\"h-5 w-5\" />}\n seed={item.styleItemId}\n fileId={item.image?.id}\n alt={item.name}\n className=\"h-14 w-14 shrink-0\"\n />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{item.name}</p>\n <p className=\"truncate text-xs text-text-secondary\">\n {[item.sku, item.size, item.colour, `× ${item.quantity}`]\n .filter(Boolean)\n .join(' · ')}\n </p>\n </div>\n <span className=\"text-sm font-semibold tabular-nums text-text-primary\">\n {formatPrice(item.lineTotal, data.currency)}\n </span>\n </GlassCard>\n ))}\n <GlassCard treatment=\"plain\" className=\"space-y-1 p-4 text-sm\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n {tr('coolandlovely.orders.shipTo', 'Ship to')}\n </p>\n <p className=\"text-text-primary\">{data.shippingName}</p>\n <p className=\"whitespace-pre-line text-text-secondary\">{data.shippingAddress}</p>\n {data.buyerNote && (\n <p className=\"pt-2 text-text-secondary\">\n {tr('coolandlovely.brandOrders.buyerNote', 'Buyer note: {{note}}', {\n note: data.buyerNote,\n })}\n </p>\n )}\n </GlassCard>\n </div>\n <GlassCard treatment=\"glass\" className=\"h-fit space-y-4 p-6\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm text-text-secondary\">\n {tr('coolandlovely.fulfilment.status', 'Fulfilment')}\n </span>\n <span data-testid=\"fulfilment-status\">\n <StatusBadge\n status={data.fulfilment.refunded ? 'REFUNDED' : data.fulfilment.status}\n />\n </span>\n </div>\n <div className=\"flex justify-between text-sm\">\n <span className=\"text-text-secondary\">\n {tr('coolandlovely.brandOrders.brandSubtotal', 'Your pieces')}\n </span>\n <span className=\"font-semibold tabular-nums text-text-primary\">\n {formatPrice(data.brandSubtotal, data.currency)}\n </span>\n </div>\n <FulfilmentTimeline fulfilment={data.fulfilment} />\n {!refundBlocksFulfilment && (\n <FulfilmentForm key={data.fulfilment.updatedAt ?? 'new'} order={data} />\n )}\n <ReturnRequestPanel order={data} />\n <RefundPanel order={data} />\n </GlassCard>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CartPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/commerce/CartPage.tsx"],"sourcesContent":["/**\n * Cart Page (BOFF-3370)\n * @module coolandlovely/pages/commerce\n *\n * The shopper's cart (`cooMyCart`), quantity edits, and checkout. Checkout snapshots the cart\n * into an order (`cooCheckout`), then the shopper pays through the provider global-billing\n * chose; the order is only marked paid once the backend re-reads billing's record.\n */\n\nimport { useState } from 'react';\nimport { ArrowLeft, Loader2, Minus, Plus, ShoppingBag, Sparkles, Trash2 } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\n\nimport { EmptyState } from '../../components/EmptyState';\nimport { TextAreaField, TextField } from '../../components/Field';\nimport { PageLayout } from '../../components/PageLayout';\nimport { PayForOrder } from '../../components/PayForOrder';\nimport { ErrorView, LoadingView } from '../../components/StateViews';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useTr } from '../../hooks';\nimport {\n useCheckout,\n useMyCart,\n useRemoveCartItem,\n useSyncOrderPayment,\n useUpdateCartItem,\n} from '../../hooks/useCommerceApi';\nimport type { CooOrderCheckout } from '../../types';\nimport { formatPrice } from '../../utils/formatters';\n\nconst MAX_QUANTITY = 10;\n\nexport function CartPage() {\n const tr = useTr();\n const { navigateTo } = useCoolAndLovely();\n const cart = useMyCart();\n const updateItem = useUpdateCartItem();\n const removeItem = useRemoveCartItem();\n const checkout = useCheckout();\n const sync = useSyncOrderPayment();\n\n const [shippingName, setShippingName] = useState('');\n const [shippingAddress, setShippingAddress] = useState('');\n const [buyerNote, setBuyerNote] = useState('');\n const [session, setSession] = useState<CooOrderCheckout | null>(null);\n\n const title = tr('coolandlovely.cart.title', 'Your cart');\n\n if (cart.isLoading) {\n return (\n <PageLayout title={title}>\n <LoadingView label={tr('coolandlovely.cart.loading', 'Loading your cart…')} />\n </PageLayout>\n );\n }\n if (cart.isError || !cart.data) {\n return (\n <PageLayout title={title}>\n <ErrorView onRetry={() => void cart.refetch()} />\n </PageLayout>\n );\n }\n\n // After checkout: the order exists, the cart is empty, and the shopper pays.\n if (session) {\n const settle = async () => {\n await sync.mutateAsync(session.order.id).catch(() => undefined);\n navigateTo(`/orders/${session.order.id}`);\n };\n return (\n <PageLayout\n title={tr('coolandlovely.checkout.payTitle', 'Pay for your order')}\n description={session.order.orderNumber}\n >\n <div className=\"mx-auto max-w-lg space-y-4\">\n <GlassCard treatment=\"glass\" className=\"space-y-4 p-6\">\n <dl className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between\">\n <dt className=\"text-text-secondary\">\n {tr('coolandlovely.checkout.subtotal', 'Subtotal')}\n </dt>\n <dd className=\"tabular-nums text-text-primary\">\n {formatPrice(session.subtotal, session.currency)}\n </dd>\n </div>\n <div className=\"flex justify-between\">\n <dt className=\"text-text-secondary\">{tr('coolandlovely.checkout.tax', 'Tax')}</dt>\n <dd className=\"tabular-nums text-text-primary\">\n {formatPrice(session.taxAmount, session.currency)}\n </dd>\n </div>\n <div className=\"flex justify-between border-t border-border-seam pt-2 text-base font-semibold\">\n <dt className=\"text-text-primary\">{tr('coolandlovely.checkout.total', 'Total')}</dt>\n <dd className=\"tabular-nums text-text-primary\" data-testid=\"checkout-total\">\n {formatPrice(session.total, session.currency)}\n </dd>\n </div>\n </dl>\n <PayForOrder checkout={session} onSettle={settle} />\n <button\n type=\"button\"\n onClick={() => navigateTo(`/orders/${session.order.id}`)}\n className=\"w-full text-center text-sm font-medium text-text-secondary hover:underline\"\n >\n {tr('coolandlovely.checkout.payLater', 'Pay later from My Orders')}\n </button>\n </GlassCard>\n </div>\n </PageLayout>\n );\n }\n\n const data = cart.data;\n const busy = updateItem.isPending || removeItem.isPending;\n const canSubmit = data.canCheckout && shippingName.trim() !== '' && shippingAddress.trim() !== '';\n\n const submit = () => {\n checkout.mutate(\n {\n shippingName: shippingName.trim(),\n shippingAddress: shippingAddress.trim(),\n buyerNote: buyerNote.trim() || null,\n },\n { onSuccess: (result) => setSession(result) }\n );\n };\n\n return (\n <PageLayout\n title={title}\n description={tr('coolandlovely.cart.description', 'Pieces you are about to buy.')}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/items')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> {tr('coolandlovely.cart.keepShopping', 'Keep shopping')}\n </button>\n }\n >\n {data.items.length === 0 ? (\n <EmptyState\n icon={<ShoppingBag className=\"h-8 w-8\" />}\n title={tr('coolandlovely.cart.emptyTitle', 'Your cart is empty')}\n description={tr(\n 'coolandlovely.cart.emptyDescription',\n 'Add pieces from the catalogue or from a look.'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/items')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text\"\n >\n {tr('coolandlovely.cart.browse', 'Browse pieces')}\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"space-y-3 lg:col-span-2\" data-testid=\"cart-lines\">\n {data.items.map((line) => (\n <GlassCard key={line.id} treatment=\"plain\" className=\"flex items-center gap-4 p-4\">\n <Thumbnail\n icon={<ShoppingBag className=\"h-5 w-5\" />}\n seed={line.styleItemId}\n fileId={line.styleItem?.image?.id}\n alt={line.styleItem?.name ?? ''}\n className=\"h-16 w-16 shrink-0\"\n />\n <div className=\"min-w-0 flex-1\">\n <button\n type=\"button\"\n onClick={() => navigateTo(`/items/${line.styleItemId}`)}\n className=\"truncate text-left text-sm font-medium text-text-primary hover:underline\"\n >\n {line.styleItem?.name ??\n tr('coolandlovely.cart.unknownPiece', 'Catalogue piece')}\n </button>\n <p className=\"truncate text-xs text-text-secondary\">\n {[line.styleItem?.brand?.name, line.size, line.colour]\n .filter(Boolean)\n .join(' · ')}\n </p>\n {line.lookId && (\n <p className=\"mt-0.5 inline-flex items-center gap-1 text-xs text-accent-primary\">\n <Sparkles className=\"h-3 w-3\" />{' '}\n {tr('coolandlovely.cart.fromLook', 'From a look')}\n </p>\n )}\n {!line.isPurchasable &&\n (line.availableQuantity !== null &&\n line.availableQuantity > 0 &&\n line.styleItem?.status === 'ACTIVE' ? (\n <p className=\"mt-0.5 text-xs text-status-error-text\">\n {tr(\n 'coolandlovely.cart.reduceToStock',\n 'Only {{count}} left — lower the quantity to check out',\n { count: line.availableQuantity }\n )}\n </p>\n ) : (\n <p className=\"mt-0.5 text-xs text-status-error-text\">\n {tr('coolandlovely.cart.unavailable', 'No longer available')}\n </p>\n ))}\n </div>\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n aria-label={tr('coolandlovely.cart.decrease', 'Decrease quantity')}\n disabled={busy}\n onClick={() => updateItem.mutate({ id: line.id, quantity: line.quantity - 1 })}\n className=\"rounded-md border border-border-seam p-1.5 text-text-primary hover:bg-bg-sunken disabled:opacity-50\"\n >\n <Minus className=\"h-3.5 w-3.5\" />\n </button>\n <span\n className=\"w-6 text-center text-sm tabular-nums text-text-primary\"\n data-testid=\"cart-line-quantity\"\n >\n {line.quantity}\n </span>\n <button\n type=\"button\"\n aria-label={tr('coolandlovely.cart.increase', 'Increase quantity')}\n disabled={\n busy ||\n line.quantity >= MAX_QUANTITY ||\n (line.availableQuantity !== null && line.quantity >= line.availableQuantity)\n }\n onClick={() => updateItem.mutate({ id: line.id, quantity: line.quantity + 1 })}\n className=\"rounded-md border border-border-seam p-1.5 text-text-primary hover:bg-bg-sunken disabled:opacity-50\"\n >\n <Plus className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n <p className=\"w-24 text-right text-sm font-semibold tabular-nums text-text-primary\">\n {formatPrice(line.lineTotal, line.currency)}\n </p>\n <button\n type=\"button\"\n aria-label={tr('coolandlovely.cart.remove', 'Remove from cart')}\n disabled={busy}\n onClick={() => removeItem.mutate({ id: line.id })}\n className=\"rounded-md p-1.5 text-status-error-text hover:bg-status-error-bg-subtle disabled:opacity-50\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </GlassCard>\n ))}\n {(updateItem.error || removeItem.error) && (\n <p className=\"text-sm text-status-error-text\">\n {(updateItem.error ?? removeItem.error)?.message}\n </p>\n )}\n </div>\n\n <GlassCard treatment=\"glass\" className=\"h-fit space-y-4 p-6\">\n <div className=\"flex items-baseline justify-between\">\n <span className=\"text-sm text-text-secondary\">\n {tr('coolandlovely.cart.subtotalItems', 'Subtotal ({{count}} items)', {\n count: data.itemCount,\n })}\n </span>\n <span\n className=\"text-xl font-bold tabular-nums text-text-primary\"\n data-testid=\"cart-subtotal\"\n >\n {formatPrice(data.subtotal, data.currency ?? 'USD')}\n </span>\n </div>\n <p className=\"text-xs text-text-secondary\">\n {tr('coolandlovely.cart.taxNote', 'Tax is calculated when you place the order.')}\n </p>\n {data.blockedReason && (\n <p className=\"text-sm text-status-error-text\">{data.blockedReason}</p>\n )}\n <div className=\"space-y-3 border-t border-border-seam pt-4\">\n <TextField\n id=\"coo-shipping-name\"\n label={tr('coolandlovely.checkout.shippingName', 'Full name')}\n value={shippingName}\n onChange={setShippingName}\n required\n />\n <TextAreaField\n id=\"coo-shipping-address\"\n label={tr('coolandlovely.checkout.shippingAddress', 'Shipping address')}\n value={shippingAddress}\n onChange={setShippingAddress}\n rows={3}\n required\n />\n <TextField\n id=\"coo-buyer-note\"\n label={tr('coolandlovely.checkout.note', 'Note for the brand (optional)')}\n value={buyerNote}\n onChange={setBuyerNote}\n />\n </div>\n {checkout.error && (\n <p className=\"text-sm text-status-error-text\">{checkout.error.message}</p>\n )}\n <button\n type=\"button\"\n disabled={!canSubmit || checkout.isPending}\n onClick={submit}\n className=\"inline-flex w-full items-center justify-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2.5 text-sm font-semibold text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {checkout.isPending && <Loader2 className=\"h-4 w-4 animate-spin\" />}\n {tr('coolandlovely.checkout.placeOrder', 'Place order')}\n </button>\n </GlassCard>\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"CartPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/commerce/CartPage.tsx"],"sourcesContent":["/**\n * Cart Page (BOFF-3370)\n * @module coolandlovely/pages/commerce\n *\n * The shopper's cart (`cooMyCart`), quantity edits, and checkout. Checkout snapshots the cart\n * into an order (`cooCheckout`), then the shopper pays through the provider global-billing\n * chose; the order is only marked paid once the backend re-reads billing's record.\n */\n\nimport { useState } from 'react';\nimport { ArrowLeft, Loader2, Minus, Plus, ShoppingBag, Sparkles, Trash2 } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\n\nimport { EmptyState } from '../../components/EmptyState';\nimport { TextAreaField, TextField } from '../../components/Field';\nimport { PageLayout } from '../../components/PageLayout';\nimport { PayForOrder } from '../../components/PayForOrder';\nimport { ErrorView, LoadingView } from '../../components/StateViews';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useTr } from '../../hooks';\nimport {\n useCheckout,\n useMyCart,\n useRemoveCartItem,\n useSyncOrderPayment,\n useUpdateCartItem,\n} from '../../hooks/useCommerceApi';\nimport type { CooOrderCheckout } from '../../types';\nimport { formatPrice } from '../../utils/formatters';\n\nconst MAX_QUANTITY = 10;\n\nexport function CartPage() {\n const tr = useTr();\n const { navigateTo } = useCoolAndLovely();\n const cart = useMyCart();\n const updateItem = useUpdateCartItem();\n const removeItem = useRemoveCartItem();\n const checkout = useCheckout();\n const sync = useSyncOrderPayment();\n\n const [shippingName, setShippingName] = useState('');\n const [shippingAddress, setShippingAddress] = useState('');\n const [buyerNote, setBuyerNote] = useState('');\n const [session, setSession] = useState<CooOrderCheckout | null>(null);\n\n const title = tr('coolandlovely.cart.title', 'Your cart');\n\n if (cart.isLoading) {\n return (\n <PageLayout title={title}>\n <LoadingView label={tr('coolandlovely.cart.loading', 'Loading your cart…')} />\n </PageLayout>\n );\n }\n if (cart.isError || !cart.data) {\n return (\n <PageLayout title={title}>\n <ErrorView onRetry={() => void cart.refetch()} />\n </PageLayout>\n );\n }\n\n // After checkout: the order exists, the cart is empty, and the shopper pays.\n if (session) {\n const settle = async () => {\n await sync.mutateAsync(session.order.id).catch(() => undefined);\n navigateTo(`/orders/${session.order.id}`);\n };\n return (\n <PageLayout\n title={tr('coolandlovely.checkout.payTitle', 'Pay for your order')}\n description={session.order.orderNumber}\n >\n <div className=\"mx-auto max-w-lg space-y-4\">\n <GlassCard treatment=\"glass\" className=\"space-y-4 p-6\">\n <dl className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between\">\n <dt className=\"text-text-secondary\">\n {tr('coolandlovely.checkout.subtotal', 'Subtotal')}\n </dt>\n <dd className=\"tabular-nums text-text-primary\">\n {formatPrice(session.subtotal, session.currency)}\n </dd>\n </div>\n <div className=\"flex justify-between\">\n <dt className=\"text-text-secondary\">{tr('coolandlovely.checkout.tax', 'Tax')}</dt>\n <dd className=\"tabular-nums text-text-primary\">\n {formatPrice(session.taxAmount, session.currency)}\n </dd>\n </div>\n <div className=\"flex justify-between border-t border-border-seam pt-2 text-base font-semibold\">\n <dt className=\"text-text-primary\">{tr('coolandlovely.checkout.total', 'Total')}</dt>\n <dd className=\"tabular-nums text-text-primary\" data-testid=\"checkout-total\">\n {formatPrice(session.total, session.currency)}\n </dd>\n </div>\n </dl>\n <PayForOrder checkout={session} onSettle={settle} />\n <button\n type=\"button\"\n onClick={() => navigateTo(`/orders/${session.order.id}`)}\n className=\"w-full text-center text-sm font-medium text-text-secondary hover:underline\"\n >\n {tr('coolandlovely.checkout.payLater', 'Pay later from My Orders')}\n </button>\n </GlassCard>\n </div>\n </PageLayout>\n );\n }\n\n const data = cart.data;\n const busy = updateItem.isPending || removeItem.isPending;\n const canSubmit = data.canCheckout && shippingName.trim() !== '' && shippingAddress.trim() !== '';\n\n const submit = () => {\n checkout.mutate(\n {\n shippingName: shippingName.trim(),\n shippingAddress: shippingAddress.trim(),\n buyerNote: buyerNote.trim() || null,\n },\n { onSuccess: (result) => setSession(result) }\n );\n };\n\n return (\n <PageLayout\n title={title}\n description={tr('coolandlovely.cart.description', 'Pieces you are about to buy.')}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/items')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> {tr('coolandlovely.cart.keepShopping', 'Keep shopping')}\n </button>\n }\n >\n {data.items.length === 0 ? (\n <EmptyState\n icon={<ShoppingBag className=\"h-8 w-8\" />}\n title={tr('coolandlovely.cart.emptyTitle', 'Your cart is empty')}\n description={tr(\n 'coolandlovely.cart.emptyDescription',\n 'Add pieces from the catalogue or from a look.'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/items')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text\"\n >\n {tr('coolandlovely.cart.browse', 'Browse pieces')}\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"space-y-3 lg:col-span-2\" data-testid=\"cart-lines\">\n {data.items.map((line) => (\n <GlassCard key={line.id} treatment=\"plain\" className=\"flex items-center gap-4 p-4\">\n <Thumbnail\n icon={<ShoppingBag className=\"h-5 w-5\" />}\n seed={line.styleItemId}\n fileId={line.styleItem?.image?.id}\n alt={line.styleItem?.name ?? ''}\n className=\"h-16 w-16 shrink-0\"\n />\n <div className=\"min-w-0 flex-1\">\n <button\n type=\"button\"\n onClick={() => navigateTo(`/items/${line.styleItemId}`)}\n className=\"truncate text-left text-sm font-medium text-text-primary hover:underline\"\n >\n {line.styleItem?.name ??\n tr('coolandlovely.cart.unknownPiece', 'Catalogue piece')}\n </button>\n <p className=\"truncate text-xs text-text-secondary\">\n {[line.styleItem?.brand?.name, line.size, line.colour]\n .filter(Boolean)\n .join(' · ')}\n </p>\n {line.lookId && (\n <p className=\"mt-0.5 inline-flex items-center gap-1 text-xs text-accent-primary\">\n <Sparkles className=\"h-3 w-3\" />{' '}\n {tr('coolandlovely.cart.fromLook', 'From a look')}\n </p>\n )}\n {!line.isPurchasable &&\n (line.availableQuantity !== null &&\n line.availableQuantity > 0 &&\n line.styleItem?.status === 'ACTIVE' ? (\n <p className=\"mt-0.5 text-xs text-status-error-text\">\n {tr(\n 'coolandlovely.cart.reduceToStock',\n 'Only {{count}} left — lower the quantity to check out',\n { count: line.availableQuantity }\n )}\n </p>\n ) : (\n <p className=\"mt-0.5 text-xs text-status-error-text\">\n {tr('coolandlovely.cart.unavailable', 'No longer available')}\n </p>\n ))}\n </div>\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n aria-label={tr('coolandlovely.cart.decrease', 'Decrease quantity')}\n disabled={busy}\n onClick={() => updateItem.mutate({ id: line.id, quantity: line.quantity - 1 })}\n className=\"rounded-md border border-border-seam p-1.5 text-text-primary hover:bg-bg-sunken disabled:opacity-50\"\n >\n <Minus className=\"h-3.5 w-3.5\" />\n </button>\n <span\n className=\"w-6 text-center text-sm tabular-nums text-text-primary\"\n data-testid=\"cart-line-quantity\"\n >\n {line.quantity}\n </span>\n <button\n type=\"button\"\n aria-label={tr('coolandlovely.cart.increase', 'Increase quantity')}\n disabled={\n busy ||\n line.quantity >= MAX_QUANTITY ||\n (line.availableQuantity !== null && line.quantity >= line.availableQuantity)\n }\n onClick={() => updateItem.mutate({ id: line.id, quantity: line.quantity + 1 })}\n className=\"rounded-md border border-border-seam p-1.5 text-text-primary hover:bg-bg-sunken disabled:opacity-50\"\n >\n <Plus className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n <p className=\"w-24 text-right text-sm font-semibold tabular-nums text-text-primary\">\n {formatPrice(line.lineTotal, line.currency)}\n </p>\n <button\n type=\"button\"\n aria-label={tr('coolandlovely.cart.remove', 'Remove from cart')}\n disabled={busy}\n onClick={() => removeItem.mutate({ id: line.id })}\n className=\"rounded-md p-1.5 text-status-error-text hover:bg-status-error-bg-subtle disabled:opacity-50\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </GlassCard>\n ))}\n {(updateItem.error || removeItem.error) && (\n <p className=\"text-sm text-status-error-text\">\n {(updateItem.error ?? removeItem.error)?.message}\n </p>\n )}\n </div>\n\n <GlassCard treatment=\"glass\" className=\"h-fit space-y-4 p-6\">\n <div className=\"flex items-baseline justify-between\">\n <span className=\"text-sm text-text-secondary\">\n {tr('coolandlovely.cart.subtotalItems', 'Subtotal ({{count}} items)', {\n count: data.itemCount,\n })}\n </span>\n <span\n className=\"text-xl font-bold tabular-nums text-text-primary\"\n data-testid=\"cart-subtotal\"\n >\n {formatPrice(data.subtotal, data.currency ?? 'USD')}\n </span>\n </div>\n <p className=\"text-xs text-text-secondary\">\n {tr('coolandlovely.cart.taxNote', 'Tax is calculated when you place the order.')}\n </p>\n {data.blockedReason && (\n <p className=\"text-sm text-status-error-text\">{data.blockedReason}</p>\n )}\n <div className=\"space-y-3 border-t border-border-seam pt-4\">\n <TextField\n id=\"coo-shipping-name\"\n label={tr('coolandlovely.checkout.shippingName', 'Full name')}\n value={shippingName}\n onChange={setShippingName}\n required\n />\n <TextAreaField\n id=\"coo-shipping-address\"\n label={tr('coolandlovely.checkout.shippingAddress', 'Shipping address')}\n value={shippingAddress}\n onChange={setShippingAddress}\n rows={3}\n required\n />\n <TextField\n id=\"coo-buyer-note\"\n label={tr('coolandlovely.checkout.note', 'Note for the brand (optional)')}\n value={buyerNote}\n onChange={setBuyerNote}\n />\n </div>\n {checkout.error && (\n <p className=\"text-sm text-status-error-text\">{checkout.error.message}</p>\n )}\n <button\n type=\"button\"\n disabled={!canSubmit || checkout.isPending}\n onClick={submit}\n className=\"inline-flex w-full items-center justify-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2.5 text-sm font-semibold text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {checkout.isPending && <Loader2 className=\"h-4 w-4 animate-spin\" />}\n {tr('coolandlovely.checkout.placeOrder', 'Place order')}\n </button>\n </GlassCard>\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CreatorEarningsPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/commerce/CreatorEarningsPage.tsx"],"sourcesContent":["/**\n * Creator Earnings Page (BOFF-3370)\n * @module coolandlovely/pages/commerce\n *\n * A creator's look commissions: every piece bought from one of their published looks earns\n * them a commission once the order is paid. Balances come from global-billing's earnings\n * ledger (`cooMyCreatorEarningsSummary`); a creator can ask for a payout of the available\n * balance (`cooRequestCreatorPayout`), which billing queues for payout review.\n *\n * Collaboration fees (BOFF-9637): when a brand pays the agreed fee of a published collaboration,\n * it lands here too (`cooMyCollaborationPayouts`) and counts towards the same payable balance.\n */\n\nimport { useState } from 'react';\nimport { Loader2, Wallet } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\n\nimport { EmptyState } from '../../components/EmptyState';\nimport { TextField } from '../../components/Field';\nimport { MetricsCard } from '../../components/MetricsCard';\nimport { PageLayout } from '../../components/PageLayout';\nimport { ErrorView, LoadingView } from '../../components/StateViews';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { useTr } from '../../hooks';\nimport {\n useMyCollaborationPayouts,\n useMyCreatorEarnings,\n useMyCreatorEarningsSummary,\n useRequestCreatorPayout,\n} from '../../hooks/useCommerceApi';\nimport { cn } from '../../utils/cn';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { formatDate, formatPrice } from '../../utils/formatters';\n\nexport function CreatorEarningsPage() {\n const tr = useTr();\n const summary = useMyCreatorEarningsSummary();\n const earnings = useMyCreatorEarnings();\n const fees = useMyCollaborationPayouts();\n const payout = useRequestCreatorPayout();\n const { navigateTo } = useCoolAndLovely();\n const [amount, setAmount] = useState('');\n const title = tr('coolandlovely.earnings.title', 'Creator earnings');\n\n if (summary.isLoading || earnings.isLoading) {\n return (\n <PageLayout title={title}>\n <LoadingView />\n </PageLayout>\n );\n }\n if (summary.isError || earnings.isError || !summary.data || !earnings.data) {\n return (\n <PageLayout title={title}>\n <ErrorView\n onRetry={() => {\n void summary.refetch();\n void earnings.refetch();\n }}\n />\n </PageLayout>\n );\n }\n\n const s = summary.data;\n const currency = s.currency ?? 'USD';\n const requested = Number(amount);\n const canRequest = s.currency !== null && requested > 0 && requested <= s.availableAmount;\n\n return (\n <PageLayout\n title={title}\n description={tr(\n 'coolandlovely.earnings.description',\n 'You earn {{rate}}% of every piece bought from your looks, and the agreed fee of every collaboration a brand pays. Commissions become available {{days}} days after payment.',\n { rate: Math.round(s.commissionRate * 100), days: s.holdDays }\n )}\n >\n <div\n className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\"\n data-testid=\"earnings-summary\"\n >\n <MetricsCard\n icon={<Wallet className=\"h-5 w-5\" />}\n label={tr('coolandlovely.earnings.available', 'Available')}\n value={formatPrice(s.availableAmount, currency)}\n />\n <MetricsCard\n icon={<Wallet className=\"h-5 w-5\" />}\n label={tr('coolandlovely.earnings.pending', 'In return window')}\n value={formatPrice(s.pendingAmount, currency)}\n />\n <MetricsCard\n icon={<Wallet className=\"h-5 w-5\" />}\n label={tr('coolandlovely.earnings.reserved', 'Payout requested')}\n value={formatPrice(s.reservedAmount, currency)}\n />\n <MetricsCard\n icon={<Wallet className=\"h-5 w-5\" />}\n label={tr('coolandlovely.earnings.paid', 'Paid out')}\n value={formatPrice(s.paidAmount, currency)}\n />\n </div>\n {!s.billingAvailable && (\n <p className=\"mt-3 text-sm text-status-warning-text\">\n {tr(\n 'coolandlovely.earnings.billingUnavailable',\n 'Balances could not be confirmed with billing just now; the figures above come from your commission history.'\n )}\n </p>\n )}\n\n <div className=\"mt-6 grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <section className=\"lg:col-span-2\">\n <h3 className=\"mb-3 text-lg font-semibold text-text-primary\">\n {tr('coolandlovely.earnings.history', 'Commission history')}\n </h3>\n {earnings.data.items.length === 0 ? (\n <EmptyState\n icon={<Wallet className=\"h-8 w-8\" />}\n title={tr('coolandlovely.earnings.emptyTitle', 'No commissions yet')}\n description={tr(\n 'coolandlovely.earnings.emptyDescription',\n 'Publish looks with catalogue pieces. When someone buys from your look, the commission shows up here.'\n )}\n />\n ) : (\n <div className=\"overflow-x-auto rounded-xl border border-border-seam\">\n <table className=\"w-full text-sm\">\n <thead className=\"bg-bg-sunken text-left text-xs uppercase tracking-wide text-text-secondary\">\n <tr>\n <th className=\"px-4 py-2\">{tr('coolandlovely.earnings.order', 'Order')}</th>\n <th className=\"px-4 py-2 text-right\">\n {tr('coolandlovely.earnings.sale', 'Sale')}\n </th>\n <th className=\"px-4 py-2 text-right\">\n {tr('coolandlovely.earnings.commission', 'Commission')}\n </th>\n <th className=\"px-4 py-2\">\n {tr('coolandlovely.earnings.availableOn', 'Available on')}\n </th>\n <th className=\"px-4 py-2\">{tr('coolandlovely.earnings.ledger', 'Ledger')}</th>\n </tr>\n </thead>\n <tbody>\n {earnings.data.items.map((earning) => (\n <tr key={earning.id} className=\"border-t border-border-seam\">\n <td className=\"px-4 py-2 font-mono text-text-primary\">\n {earning.orderNumber ?? '—'}\n </td>\n <td className=\"px-4 py-2 text-right tabular-nums text-text-secondary\">\n {formatPrice(earning.saleAmount, earning.currency)}\n </td>\n <td\n className={cn(\n 'px-4 py-2 text-right font-semibold tabular-nums text-text-primary',\n earning.reversalStatus === 'REVERSED' &&\n 'text-text-secondary line-through'\n )}\n >\n {formatPrice(earning.amount, earning.currency)}\n </td>\n <td className=\"px-4 py-2 text-text-secondary\">\n {formatDate(earning.availableAt, { hour: undefined, minute: undefined })}\n </td>\n <td className=\"px-4 py-2\" data-testid=\"earning-ledger-status\">\n {/* A refunded line's commission is reversed (BOFF-9702). */}\n <StatusBadge\n status={\n earning.reversalStatus === 'REVERSED' ||\n earning.reversalStatus === 'NOT_REVERSIBLE'\n ? earning.reversalStatus\n : earning.billingSyncStatus\n }\n />\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </section>\n\n <GlassCard treatment=\"glass\" className=\"h-fit space-y-4 p-6\">\n <h3 className=\"text-base font-semibold text-text-primary\">\n {tr('coolandlovely.earnings.requestTitle', 'Request a payout')}\n </h3>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'coolandlovely.earnings.requestHint',\n 'Payouts are reviewed by the platform and sent to the payout account on your billing profile.'\n )}\n </p>\n <TextField\n id=\"coo-payout-amount\"\n type=\"number\"\n label={tr('coolandlovely.earnings.amount', 'Amount ({{currency}})', { currency })}\n value={amount}\n onChange={setAmount}\n hint={tr('coolandlovely.earnings.maxHint', 'Up to {{amount}}', {\n amount: formatPrice(s.availableAmount, currency),\n })}\n />\n {payout.error && <p className=\"text-sm text-status-error-text\">{payout.error.message}</p>}\n {payout.data && (\n <p className=\"text-sm text-status-success-text\">\n {tr(\n 'coolandlovely.earnings.requested',\n 'Payout of {{amount}} requested ({{status}}).',\n {\n amount: formatPrice(payout.data.requestedAmount, payout.data.currency),\n status: payout.data.status,\n }\n )}\n </p>\n )}\n <button\n type=\"button\"\n disabled={!canRequest || payout.isPending}\n onClick={() =>\n payout.mutate({ amount: requested, currency }, { onSuccess: () => setAmount('') })\n }\n className=\"inline-flex w-full items-center justify-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2.5 text-sm font-semibold text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {payout.isPending && <Loader2 className=\"h-4 w-4 animate-spin\" />}\n {tr('coolandlovely.earnings.requestButton', 'Request payout')}\n </button>\n </GlassCard>\n </div>\n\n <section className=\"mt-8\" data-testid=\"collaboration-fees\">\n <h3 className=\"mb-3 text-lg font-semibold text-text-primary\">\n {tr('coolandlovely.earnings.feesTitle', 'Collaboration fees')}\n </h3>\n {fees.isError ? (\n <p className=\"text-sm text-status-error-text\">\n {tr('coolandlovely.earnings.feesError', 'Collaboration fees could not be loaded.')}\n </p>\n ) : !fees.data || fees.data.items.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n {fees.isLoading\n ? tr('coolandlovely.earnings.feesLoading', 'Loading collaboration fees…')\n : tr(\n 'coolandlovely.earnings.feesEmpty',\n 'When a brand pays the agreed fee of a published collaboration, it shows up here.'\n )}\n </p>\n ) : (\n <div className=\"overflow-x-auto rounded-xl border border-border-seam\">\n <table className=\"w-full text-sm\">\n <thead className=\"bg-bg-sunken text-left text-xs uppercase tracking-wide text-text-secondary\">\n <tr>\n <th className=\"px-4 py-2\">\n {tr('coolandlovely.earnings.collaboration', 'Collaboration')}\n </th>\n <th className=\"px-4 py-2 text-right\">\n {tr('coolandlovely.earnings.fee', 'Fee')}\n </th>\n <th className=\"px-4 py-2\">\n {tr('coolandlovely.earnings.brandPayment', 'Brand payment')}\n </th>\n <th className=\"px-4 py-2\">\n {tr('coolandlovely.earnings.availableOn', 'Available on')}\n </th>\n <th className=\"px-4 py-2\">{tr('coolandlovely.earnings.ledger', 'Ledger')}</th>\n </tr>\n </thead>\n <tbody>\n {fees.data.items.map((fee) => (\n <tr key={fee.id} className=\"border-t border-border-seam\">\n <td className=\"px-4 py-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo(`/collaborations/${fee.collaborationId}`)}\n className=\"text-left font-medium text-text-primary hover:underline\"\n >\n {fee.collaborationTitle ?? '—'}\n </button>\n </td>\n <td className=\"px-4 py-2 text-right font-semibold tabular-nums text-text-primary\">\n {formatPrice(fee.amount, fee.currency)}\n </td>\n <td className=\"px-4 py-2\">\n <StatusBadge status={fee.status} />\n </td>\n <td className=\"px-4 py-2 text-text-secondary\">\n {fee.earningAvailableAt\n ? formatDate(fee.earningAvailableAt, { hour: undefined, minute: undefined })\n : '—'}\n </td>\n <td className=\"px-4 py-2\">\n {fee.earningSyncStatus ? <StatusBadge status={fee.earningSyncStatus} /> : '—'}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </section>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"CreatorEarningsPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/commerce/CreatorEarningsPage.tsx"],"sourcesContent":["/**\n * Creator Earnings Page (BOFF-3370)\n * @module coolandlovely/pages/commerce\n *\n * A creator's look commissions: every piece bought from one of their published looks earns\n * them a commission once the order is paid. Balances come from global-billing's earnings\n * ledger (`cooMyCreatorEarningsSummary`); a creator can ask for a payout of the available\n * balance (`cooRequestCreatorPayout`), which billing queues for payout review.\n *\n * Collaboration fees (BOFF-9637): when a brand pays the agreed fee of a published collaboration,\n * it lands here too (`cooMyCollaborationPayouts`) and counts towards the same payable balance.\n */\n\nimport { useState } from 'react';\nimport { Loader2, Wallet } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\n\nimport { EmptyState } from '../../components/EmptyState';\nimport { TextField } from '../../components/Field';\nimport { MetricsCard } from '../../components/MetricsCard';\nimport { PageLayout } from '../../components/PageLayout';\nimport { ErrorView, LoadingView } from '../../components/StateViews';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { useTr } from '../../hooks';\nimport {\n useMyCollaborationPayouts,\n useMyCreatorEarnings,\n useMyCreatorEarningsSummary,\n useRequestCreatorPayout,\n} from '../../hooks/useCommerceApi';\nimport { cn } from '../../utils/cn';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { formatDate, formatPrice } from '../../utils/formatters';\n\nexport function CreatorEarningsPage() {\n const tr = useTr();\n const summary = useMyCreatorEarningsSummary();\n const earnings = useMyCreatorEarnings();\n const fees = useMyCollaborationPayouts();\n const payout = useRequestCreatorPayout();\n const { navigateTo } = useCoolAndLovely();\n const [amount, setAmount] = useState('');\n const title = tr('coolandlovely.earnings.title', 'Creator earnings');\n\n if (summary.isLoading || earnings.isLoading) {\n return (\n <PageLayout title={title}>\n <LoadingView />\n </PageLayout>\n );\n }\n if (summary.isError || earnings.isError || !summary.data || !earnings.data) {\n return (\n <PageLayout title={title}>\n <ErrorView\n onRetry={() => {\n void summary.refetch();\n void earnings.refetch();\n }}\n />\n </PageLayout>\n );\n }\n\n const s = summary.data;\n const currency = s.currency ?? 'USD';\n const requested = Number(amount);\n const canRequest = s.currency !== null && requested > 0 && requested <= s.availableAmount;\n\n return (\n <PageLayout\n title={title}\n description={tr(\n 'coolandlovely.earnings.description',\n 'You earn {{rate}}% of every piece bought from your looks, and the agreed fee of every collaboration a brand pays. Commissions become available {{days}} days after payment.',\n { rate: Math.round(s.commissionRate * 100), days: s.holdDays }\n )}\n >\n <div\n className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\"\n data-testid=\"earnings-summary\"\n >\n <MetricsCard\n icon={<Wallet className=\"h-5 w-5\" />}\n label={tr('coolandlovely.earnings.available', 'Available')}\n value={formatPrice(s.availableAmount, currency)}\n />\n <MetricsCard\n icon={<Wallet className=\"h-5 w-5\" />}\n label={tr('coolandlovely.earnings.pending', 'In return window')}\n value={formatPrice(s.pendingAmount, currency)}\n />\n <MetricsCard\n icon={<Wallet className=\"h-5 w-5\" />}\n label={tr('coolandlovely.earnings.reserved', 'Payout requested')}\n value={formatPrice(s.reservedAmount, currency)}\n />\n <MetricsCard\n icon={<Wallet className=\"h-5 w-5\" />}\n label={tr('coolandlovely.earnings.paid', 'Paid out')}\n value={formatPrice(s.paidAmount, currency)}\n />\n </div>\n {!s.billingAvailable && (\n <p className=\"mt-3 text-sm text-status-warning-text\">\n {tr(\n 'coolandlovely.earnings.billingUnavailable',\n 'Balances could not be confirmed with billing just now; the figures above come from your commission history.'\n )}\n </p>\n )}\n\n <div className=\"mt-6 grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <section className=\"lg:col-span-2\">\n <h3 className=\"mb-3 text-lg font-semibold text-text-primary\">\n {tr('coolandlovely.earnings.history', 'Commission history')}\n </h3>\n {earnings.data.items.length === 0 ? (\n <EmptyState\n icon={<Wallet className=\"h-8 w-8\" />}\n title={tr('coolandlovely.earnings.emptyTitle', 'No commissions yet')}\n description={tr(\n 'coolandlovely.earnings.emptyDescription',\n 'Publish looks with catalogue pieces. When someone buys from your look, the commission shows up here.'\n )}\n />\n ) : (\n <div className=\"overflow-x-auto rounded-xl border border-border-seam\">\n <table className=\"w-full text-sm\">\n <thead className=\"bg-bg-sunken text-left text-xs uppercase tracking-wide text-text-secondary\">\n <tr>\n <th className=\"px-4 py-2\">{tr('coolandlovely.earnings.order', 'Order')}</th>\n <th className=\"px-4 py-2 text-right\">\n {tr('coolandlovely.earnings.sale', 'Sale')}\n </th>\n <th className=\"px-4 py-2 text-right\">\n {tr('coolandlovely.earnings.commission', 'Commission')}\n </th>\n <th className=\"px-4 py-2\">\n {tr('coolandlovely.earnings.availableOn', 'Available on')}\n </th>\n <th className=\"px-4 py-2\">{tr('coolandlovely.earnings.ledger', 'Ledger')}</th>\n </tr>\n </thead>\n <tbody>\n {earnings.data.items.map((earning) => (\n <tr key={earning.id} className=\"border-t border-border-seam\">\n <td className=\"px-4 py-2 font-mono text-text-primary\">\n {earning.orderNumber ?? '—'}\n </td>\n <td className=\"px-4 py-2 text-right tabular-nums text-text-secondary\">\n {formatPrice(earning.saleAmount, earning.currency)}\n </td>\n <td\n className={cn(\n 'px-4 py-2 text-right font-semibold tabular-nums text-text-primary',\n earning.reversalStatus === 'REVERSED' &&\n 'text-text-secondary line-through'\n )}\n >\n {formatPrice(earning.amount, earning.currency)}\n </td>\n <td className=\"px-4 py-2 text-text-secondary\">\n {formatDate(earning.availableAt, { hour: undefined, minute: undefined })}\n </td>\n <td className=\"px-4 py-2\" data-testid=\"earning-ledger-status\">\n {/* A refunded line's commission is reversed (BOFF-9702). */}\n <StatusBadge\n status={\n earning.reversalStatus === 'REVERSED' ||\n earning.reversalStatus === 'NOT_REVERSIBLE'\n ? earning.reversalStatus\n : earning.billingSyncStatus\n }\n />\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </section>\n\n <GlassCard treatment=\"glass\" className=\"h-fit space-y-4 p-6\">\n <h3 className=\"text-base font-semibold text-text-primary\">\n {tr('coolandlovely.earnings.requestTitle', 'Request a payout')}\n </h3>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'coolandlovely.earnings.requestHint',\n 'Payouts are reviewed by the platform and sent to the payout account on your billing profile.'\n )}\n </p>\n <TextField\n id=\"coo-payout-amount\"\n type=\"number\"\n label={tr('coolandlovely.earnings.amount', 'Amount ({{currency}})', { currency })}\n value={amount}\n onChange={setAmount}\n hint={tr('coolandlovely.earnings.maxHint', 'Up to {{amount}}', {\n amount: formatPrice(s.availableAmount, currency),\n })}\n />\n {payout.error && <p className=\"text-sm text-status-error-text\">{payout.error.message}</p>}\n {payout.data && (\n <p className=\"text-sm text-status-success-text\">\n {tr(\n 'coolandlovely.earnings.requested',\n 'Payout of {{amount}} requested ({{status}}).',\n {\n amount: formatPrice(payout.data.requestedAmount, payout.data.currency),\n status: payout.data.status,\n }\n )}\n </p>\n )}\n <button\n type=\"button\"\n disabled={!canRequest || payout.isPending}\n onClick={() =>\n payout.mutate({ amount: requested, currency }, { onSuccess: () => setAmount('') })\n }\n className=\"inline-flex w-full items-center justify-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2.5 text-sm font-semibold text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {payout.isPending && <Loader2 className=\"h-4 w-4 animate-spin\" />}\n {tr('coolandlovely.earnings.requestButton', 'Request payout')}\n </button>\n </GlassCard>\n </div>\n\n <section className=\"mt-8\" data-testid=\"collaboration-fees\">\n <h3 className=\"mb-3 text-lg font-semibold text-text-primary\">\n {tr('coolandlovely.earnings.feesTitle', 'Collaboration fees')}\n </h3>\n {fees.isError ? (\n <p className=\"text-sm text-status-error-text\">\n {tr('coolandlovely.earnings.feesError', 'Collaboration fees could not be loaded.')}\n </p>\n ) : !fees.data || fees.data.items.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n {fees.isLoading\n ? tr('coolandlovely.earnings.feesLoading', 'Loading collaboration fees…')\n : tr(\n 'coolandlovely.earnings.feesEmpty',\n 'When a brand pays the agreed fee of a published collaboration, it shows up here.'\n )}\n </p>\n ) : (\n <div className=\"overflow-x-auto rounded-xl border border-border-seam\">\n <table className=\"w-full text-sm\">\n <thead className=\"bg-bg-sunken text-left text-xs uppercase tracking-wide text-text-secondary\">\n <tr>\n <th className=\"px-4 py-2\">\n {tr('coolandlovely.earnings.collaboration', 'Collaboration')}\n </th>\n <th className=\"px-4 py-2 text-right\">\n {tr('coolandlovely.earnings.fee', 'Fee')}\n </th>\n <th className=\"px-4 py-2\">\n {tr('coolandlovely.earnings.brandPayment', 'Brand payment')}\n </th>\n <th className=\"px-4 py-2\">\n {tr('coolandlovely.earnings.availableOn', 'Available on')}\n </th>\n <th className=\"px-4 py-2\">{tr('coolandlovely.earnings.ledger', 'Ledger')}</th>\n </tr>\n </thead>\n <tbody>\n {fees.data.items.map((fee) => (\n <tr key={fee.id} className=\"border-t border-border-seam\">\n <td className=\"px-4 py-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo(`/collaborations/${fee.collaborationId}`)}\n className=\"text-left font-medium text-text-primary hover:underline\"\n >\n {fee.collaborationTitle ?? '—'}\n </button>\n </td>\n <td className=\"px-4 py-2 text-right font-semibold tabular-nums text-text-primary\">\n {formatPrice(fee.amount, fee.currency)}\n </td>\n <td className=\"px-4 py-2\">\n <StatusBadge status={fee.status} />\n </td>\n <td className=\"px-4 py-2 text-text-secondary\">\n {fee.earningAvailableAt\n ? formatDate(fee.earningAvailableAt, { hour: undefined, minute: undefined })\n : '—'}\n </td>\n <td className=\"px-4 py-2\">\n {fee.earningSyncStatus ? <StatusBadge status={fee.earningSyncStatus} /> : '—'}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </section>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"OrdersPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/commerce/OrdersPage.tsx"],"sourcesContent":["/**\n * My Orders + Order detail (BOFF-3370)\n * @module coolandlovely/pages/commerce\n *\n * The shopper's own orders (`cooMyOrders` / `cooMyOrder`). An unpaid order can be paid\n * (`cooResumeOrderPayment` re-uses its billing order), re-checked against billing\n * (`cooSyncOrderPayment`) or cancelled (`cooCancelOrder`). Brand refunds (BOFF-9702) show on\n * the order with their amount and reason. Inside the return window the shopper can ask a brand to\n * take back its pieces (`cooRequestReturn`, BOFF-9900) and sees the brand's decision.\n */\n\nimport { useState } from 'react';\nimport { useParams } from 'react-router';\nimport {\n ArrowLeft,\n ChevronRight,\n Loader2,\n Package,\n RefreshCw,\n Sparkles,\n XCircle,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\n\nimport { EmptyState } from '../../components/EmptyState';\nimport { FulfilmentTimeline } from '../../components/FulfilmentTimeline';\nimport { PageLayout } from '../../components/PageLayout';\nimport { PayForOrder } from '../../components/PayForOrder';\nimport { OrderReturns } from '../../components/ReturnRequests';\nimport { ErrorView, LoadingView } from '../../components/StateViews';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useTr } from '../../hooks';\nimport {\n useCancelOrder,\n useMyOrder,\n useMyOrders,\n useResumeOrderPayment,\n useSyncOrderPayment,\n} from '../../hooks/useCommerceApi';\nimport type { CooOrderCheckout, CooOrderStatus } from '../../types';\nimport { cn } from '../../utils/cn';\nimport { formatDate, formatPrice } from '../../utils/formatters';\n\nconst FILTERS: Array<{ value: CooOrderStatus | undefined; key: string; label: string }> = [\n { value: undefined, key: 'coolandlovely.orders.filterAll', label: 'All' },\n {\n value: 'PENDING_PAYMENT',\n key: 'coolandlovely.orders.filterPending',\n label: 'Awaiting payment',\n },\n { value: 'PAID', key: 'coolandlovely.orders.filterPaid', label: 'Paid' },\n {\n value: 'PARTIALLY_REFUNDED',\n key: 'coolandlovely.orders.filterPartiallyRefunded',\n label: 'Partly refunded',\n },\n { value: 'REFUNDED', key: 'coolandlovely.orders.filterRefunded', label: 'Refunded' },\n { value: 'CANCELLED', key: 'coolandlovely.orders.filterCancelled', label: 'Cancelled' },\n];\n\nconst secondaryButton =\n 'inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-50';\n\nexport function OrdersPage() {\n const tr = useTr();\n const { navigateTo } = useCoolAndLovely();\n const [status, setStatus] = useState<CooOrderStatus | undefined>(undefined);\n const orders = useMyOrders(status);\n const title = tr('coolandlovely.orders.title', 'My orders');\n\n return (\n <PageLayout\n title={title}\n description={tr('coolandlovely.orders.description', 'Everything you have ordered.')}\n >\n <div className=\"mb-4 flex flex-wrap gap-2\" role=\"tablist\">\n {FILTERS.map((filter) => (\n <button\n key={filter.key}\n type=\"button\"\n role=\"tab\"\n aria-selected={status === filter.value}\n onClick={() => setStatus(filter.value)}\n className={cn(\n 'rounded-full border px-3 py-1 text-sm font-medium transition-colors',\n status === filter.value\n ? 'border-action-primary-bg bg-action-primary-bg text-action-primary-text'\n : 'border-border-seam text-text-primary hover:bg-bg-sunken'\n )}\n >\n {tr(filter.key, filter.label)}\n </button>\n ))}\n </div>\n {orders.isLoading ? (\n <LoadingView label={tr('coolandlovely.orders.loading', 'Loading orders…')} />\n ) : orders.isError || !orders.data ? (\n <ErrorView onRetry={() => void orders.refetch()} />\n ) : orders.data.items.length === 0 ? (\n <EmptyState\n icon={<Package className=\"h-8 w-8\" />}\n title={tr('coolandlovely.orders.emptyTitle', 'No orders yet')}\n description={tr('coolandlovely.orders.emptyDescription', 'Orders you place appear here.')}\n />\n ) : (\n <div className=\"space-y-3\" data-testid=\"orders-list\">\n {orders.data.items.map((order) => (\n <button\n key={order.id}\n type=\"button\"\n onClick={() => navigateTo(`/orders/${order.id}`)}\n className=\"flex w-full items-center gap-4 rounded-xl border border-border-seam bg-bg-surface p-4 text-left transition-colors hover:bg-bg-sunken/50\"\n >\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-mono text-sm font-medium text-text-primary\">\n {order.orderNumber}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">\n {formatDate(order.createdAt)} ·{' '}\n {tr('coolandlovely.orders.pieces', '{{count}} pieces', {\n count: order.itemCount,\n })}\n </p>\n </div>\n <StatusBadge status={order.status} />\n <span className=\"w-28 text-right text-sm font-semibold tabular-nums text-text-primary\">\n {formatPrice(order.total, order.currency)}\n </span>\n <ChevronRight className=\"h-4 w-4 text-text-secondary\" />\n </button>\n ))}\n </div>\n )}\n </PageLayout>\n );\n}\n\nexport function OrderDetailPage() {\n const tr = useTr();\n const { orderId } = useParams<{ orderId: string }>();\n const { navigateTo } = useCoolAndLovely();\n const order = useMyOrder(orderId);\n const resume = useResumeOrderPayment();\n const sync = useSyncOrderPayment();\n const cancel = useCancelOrder();\n const [session, setSession] = useState<CooOrderCheckout | null>(null);\n const title = tr('coolandlovely.orders.detailTitle', 'Order');\n\n if (order.isLoading) {\n return (\n <PageLayout title={title}>\n <LoadingView />\n </PageLayout>\n );\n }\n if (order.isError || !order.data) {\n return (\n <PageLayout title={title}>\n <ErrorView\n title={tr('coolandlovely.orders.notFound', 'Order not found')}\n onRetry={() => void order.refetch()}\n />\n </PageLayout>\n );\n }\n\n const data = order.data;\n const pending = data.status === 'PENDING_PAYMENT';\n const actionError = resume.error ?? sync.error ?? cancel.error;\n\n const settle = async () => {\n await sync.mutateAsync(data.id).catch(() => undefined);\n setSession(null);\n };\n\n return (\n <PageLayout\n title={data.orderNumber}\n description={formatDate(data.createdAt)}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button type=\"button\" onClick={() => navigateTo('/orders')} className={secondaryButton}>\n <ArrowLeft className=\"h-4 w-4\" /> {tr('coolandlovely.common.back', 'Back')}\n </button>\n {pending && (\n <>\n <button\n type=\"button\"\n disabled={sync.isPending}\n onClick={() => sync.mutate(data.id)}\n className={secondaryButton}\n >\n {sync.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <RefreshCw className=\"h-4 w-4\" />\n )}\n {tr('coolandlovely.orders.checkPayment', 'Check payment')}\n </button>\n <button\n type=\"button\"\n disabled={cancel.isPending}\n onClick={() => cancel.mutate(data.id)}\n className={cn(\n secondaryButton,\n 'text-status-error-text hover:bg-status-error-bg-subtle'\n )}\n >\n <XCircle className=\"h-4 w-4\" /> {tr('coolandlovely.orders.cancel', 'Cancel order')}\n </button>\n </>\n )}\n </div>\n }\n >\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"space-y-3 lg:col-span-2\">\n {(data.refunds?.length ?? 0) > 0 && (\n <GlassCard treatment=\"plain\" className=\"space-y-3 p-4\" data-testid=\"order-refunds\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n {tr('coolandlovely.orders.refunds', 'Refunds')}\n </p>\n {data.refunds?.map((refund) => (\n <div key={refund.id} className=\"space-y-1\" data-testid=\"order-refund\">\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"text-sm font-medium text-text-primary\">\n {refund.brandName ?? tr('coolandlovely.orders.brand', 'Brand')}\n </span>\n <span className=\"flex items-center gap-2\">\n <span className=\"text-sm font-semibold tabular-nums text-text-primary\">\n {formatPrice(refund.amount, refund.currency)}\n </span>\n <StatusBadge status={refund.status} />\n </span>\n </div>\n <p className=\"text-xs text-text-secondary\">{refund.reason}</p>\n {refund.status === 'COMPLETED' && (\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'coolandlovely.orders.refundedToMethod',\n 'Refunded {{date}} to your original payment method.',\n { date: formatDate(refund.refundedAt ?? refund.createdAt) }\n )}\n </p>\n )}\n </div>\n ))}\n </GlassCard>\n )}\n <OrderReturns order={data} />\n {(data.status === 'PAID' || data.status === 'PARTIALLY_REFUNDED') &&\n (data.fulfilments?.length ?? 0) > 0 && (\n <GlassCard treatment=\"plain\" className=\"space-y-4 p-4\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n {tr('coolandlovely.orders.delivery', 'Delivery')}\n </p>\n {data.fulfilments?.map((fulfilment) => (\n <div\n key={fulfilment.brandId}\n className=\"space-y-2\"\n data-testid=\"order-fulfilment\"\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"text-sm font-medium text-text-primary\">\n {fulfilment.brandName ?? tr('coolandlovely.orders.brand', 'Brand')}\n </span>\n <StatusBadge status={fulfilment.refunded ? 'REFUNDED' : fulfilment.status} />\n </div>\n {fulfilment.refunded ? (\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'coolandlovely.orders.brandRefunded',\n 'The brand refunded these pieces, so they will not be shipped.'\n )}\n </p>\n ) : fulfilment.status === 'UNFULFILLED' ? (\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'coolandlovely.orders.awaitingBrand',\n 'Waiting for the brand to confirm your pieces.'\n )}\n </p>\n ) : (\n <FulfilmentTimeline fulfilment={fulfilment} />\n )}\n </div>\n ))}\n </GlassCard>\n )}\n {data.items.map((item) => (\n <GlassCard key={item.id} treatment=\"plain\" className=\"flex items-center gap-4 p-4\">\n <Thumbnail\n icon={<Package className=\"h-5 w-5\" />}\n seed={item.styleItemId}\n fileId={item.image?.id}\n alt={item.name}\n className=\"h-14 w-14 shrink-0\"\n />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{item.name}</p>\n <p className=\"truncate text-xs text-text-secondary\">\n {[item.size, item.colour, `× ${item.quantity}`].filter(Boolean).join(' · ')}\n </p>\n {item.isCreatorAttributed && (\n <p className=\"mt-0.5 inline-flex items-center gap-1 text-xs text-accent-primary\">\n <Sparkles className=\"h-3 w-3\" />\n {tr('coolandlovely.orders.supportsCreator', 'Supports the creator of the look')}\n </p>\n )}\n </div>\n <span className=\"text-sm font-semibold tabular-nums text-text-primary\">\n {formatPrice(item.lineTotal, data.currency)}\n </span>\n </GlassCard>\n ))}\n </div>\n <GlassCard treatment=\"glass\" className=\"h-fit space-y-4 p-6\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm text-text-secondary\">\n {tr('coolandlovely.orders.status', 'Status')}\n </span>\n <span data-testid=\"order-status\">\n <StatusBadge status={data.status} />\n </span>\n </div>\n <dl className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between\">\n <dt className=\"text-text-secondary\">\n {tr('coolandlovely.checkout.subtotal', 'Subtotal')}\n </dt>\n <dd className=\"tabular-nums text-text-primary\">\n {formatPrice(data.subtotal, data.currency)}\n </dd>\n </div>\n <div className=\"flex justify-between\">\n <dt className=\"text-text-secondary\">{tr('coolandlovely.checkout.tax', 'Tax')}</dt>\n <dd className=\"tabular-nums text-text-primary\">\n {formatPrice(data.taxAmount, data.currency)}\n </dd>\n </div>\n <div className=\"flex justify-between border-t border-border-seam pt-2 text-base font-semibold\">\n <dt className=\"text-text-primary\">{tr('coolandlovely.checkout.total', 'Total')}</dt>\n <dd className=\"tabular-nums text-text-primary\">\n {formatPrice(data.total, data.currency)}\n </dd>\n </div>\n {data.refundedAmount > 0 && (\n <div className=\"flex justify-between\" data-testid=\"order-refunded-amount\">\n <dt className=\"text-text-secondary\">\n {tr('coolandlovely.orders.refunded', 'Refunded')}\n </dt>\n <dd className=\"tabular-nums text-text-primary\">\n − {formatPrice(data.refundedAmount, data.currency)}\n </dd>\n </div>\n )}\n </dl>\n {data.shippingAddress && (\n <div className=\"border-t border-border-seam pt-3 text-sm\">\n <p className=\"mb-1 text-xs font-medium uppercase tracking-wide text-text-secondary\">\n {tr('coolandlovely.orders.shipTo', 'Ship to')}\n </p>\n <p className=\"text-text-primary\">{data.shippingName}</p>\n <p className=\"whitespace-pre-line text-text-secondary\">{data.shippingAddress}</p>\n </div>\n )}\n {data.paidAt && (\n <p className=\"text-xs text-text-secondary\">\n {tr('coolandlovely.orders.paidOn', 'Paid {{date}} via {{provider}}', {\n date: formatDate(data.paidAt),\n provider: data.paymentProvider ?? '—',\n })}\n </p>\n )}\n {actionError && <p className=\"text-sm text-status-error-text\">{actionError.message}</p>}\n {pending &&\n (session ? (\n <PayForOrder checkout={session} onSettle={settle} />\n ) : (\n <button\n type=\"button\"\n disabled={resume.isPending}\n onClick={() =>\n resume.mutate(data.id, { onSuccess: (result) => setSession(result) })\n }\n className=\"inline-flex w-full items-center justify-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2.5 text-sm font-semibold text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {resume.isPending && <Loader2 className=\"h-4 w-4 animate-spin\" />}\n {tr('coolandlovely.orders.payNow', 'Pay now')}\n </button>\n ))}\n </GlassCard>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"OrdersPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/commerce/OrdersPage.tsx"],"sourcesContent":["/**\n * My Orders + Order detail (BOFF-3370)\n * @module coolandlovely/pages/commerce\n *\n * The shopper's own orders (`cooMyOrders` / `cooMyOrder`). An unpaid order can be paid\n * (`cooResumeOrderPayment` re-uses its billing order), re-checked against billing\n * (`cooSyncOrderPayment`) or cancelled (`cooCancelOrder`). Brand refunds (BOFF-9702) show on\n * the order with their amount and reason. Inside the return window the shopper can ask a brand to\n * take back its pieces (`cooRequestReturn`, BOFF-9900) and sees the brand's decision.\n */\n\nimport { useState } from 'react';\nimport { useParams } from 'react-router';\nimport {\n ArrowLeft,\n ChevronRight,\n Loader2,\n Package,\n RefreshCw,\n Sparkles,\n XCircle,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\n\nimport { EmptyState } from '../../components/EmptyState';\nimport { FulfilmentTimeline } from '../../components/FulfilmentTimeline';\nimport { PageLayout } from '../../components/PageLayout';\nimport { PayForOrder } from '../../components/PayForOrder';\nimport { OrderReturns } from '../../components/ReturnRequests';\nimport { ErrorView, LoadingView } from '../../components/StateViews';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useTr } from '../../hooks';\nimport {\n useCancelOrder,\n useMyOrder,\n useMyOrders,\n useResumeOrderPayment,\n useSyncOrderPayment,\n} from '../../hooks/useCommerceApi';\nimport type { CooOrderCheckout, CooOrderStatus } from '../../types';\nimport { cn } from '../../utils/cn';\nimport { formatDate, formatPrice } from '../../utils/formatters';\n\nconst FILTERS: Array<{ value: CooOrderStatus | undefined; key: string; label: string }> = [\n { value: undefined, key: 'coolandlovely.orders.filterAll', label: 'All' },\n {\n value: 'PENDING_PAYMENT',\n key: 'coolandlovely.orders.filterPending',\n label: 'Awaiting payment',\n },\n { value: 'PAID', key: 'coolandlovely.orders.filterPaid', label: 'Paid' },\n {\n value: 'PARTIALLY_REFUNDED',\n key: 'coolandlovely.orders.filterPartiallyRefunded',\n label: 'Partly refunded',\n },\n { value: 'REFUNDED', key: 'coolandlovely.orders.filterRefunded', label: 'Refunded' },\n { value: 'CANCELLED', key: 'coolandlovely.orders.filterCancelled', label: 'Cancelled' },\n];\n\nconst secondaryButton =\n 'inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-50';\n\nexport function OrdersPage() {\n const tr = useTr();\n const { navigateTo } = useCoolAndLovely();\n const [status, setStatus] = useState<CooOrderStatus | undefined>(undefined);\n const orders = useMyOrders(status);\n const title = tr('coolandlovely.orders.title', 'My orders');\n\n return (\n <PageLayout\n title={title}\n description={tr('coolandlovely.orders.description', 'Everything you have ordered.')}\n >\n <div className=\"mb-4 flex flex-wrap gap-2\" role=\"tablist\">\n {FILTERS.map((filter) => (\n <button\n key={filter.key}\n type=\"button\"\n role=\"tab\"\n aria-selected={status === filter.value}\n onClick={() => setStatus(filter.value)}\n className={cn(\n 'rounded-full border px-3 py-1 text-sm font-medium transition-colors',\n status === filter.value\n ? 'border-action-primary-bg bg-action-primary-bg text-action-primary-text'\n : 'border-border-seam text-text-primary hover:bg-bg-sunken'\n )}\n >\n {tr(filter.key, filter.label)}\n </button>\n ))}\n </div>\n {orders.isLoading ? (\n <LoadingView label={tr('coolandlovely.orders.loading', 'Loading orders…')} />\n ) : orders.isError || !orders.data ? (\n <ErrorView onRetry={() => void orders.refetch()} />\n ) : orders.data.items.length === 0 ? (\n <EmptyState\n icon={<Package className=\"h-8 w-8\" />}\n title={tr('coolandlovely.orders.emptyTitle', 'No orders yet')}\n description={tr('coolandlovely.orders.emptyDescription', 'Orders you place appear here.')}\n />\n ) : (\n <div className=\"space-y-3\" data-testid=\"orders-list\">\n {orders.data.items.map((order) => (\n <button\n key={order.id}\n type=\"button\"\n onClick={() => navigateTo(`/orders/${order.id}`)}\n className=\"flex w-full items-center gap-4 rounded-xl border border-border-seam bg-bg-surface p-4 text-left transition-colors hover:bg-bg-sunken/50\"\n >\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-mono text-sm font-medium text-text-primary\">\n {order.orderNumber}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">\n {formatDate(order.createdAt)} ·{' '}\n {tr('coolandlovely.orders.pieces', '{{count}} pieces', {\n count: order.itemCount,\n })}\n </p>\n </div>\n <StatusBadge status={order.status} />\n <span className=\"w-28 text-right text-sm font-semibold tabular-nums text-text-primary\">\n {formatPrice(order.total, order.currency)}\n </span>\n <ChevronRight className=\"h-4 w-4 text-text-secondary\" />\n </button>\n ))}\n </div>\n )}\n </PageLayout>\n );\n}\n\nexport function OrderDetailPage() {\n const tr = useTr();\n const { orderId } = useParams<{ orderId: string }>();\n const { navigateTo } = useCoolAndLovely();\n const order = useMyOrder(orderId);\n const resume = useResumeOrderPayment();\n const sync = useSyncOrderPayment();\n const cancel = useCancelOrder();\n const [session, setSession] = useState<CooOrderCheckout | null>(null);\n const title = tr('coolandlovely.orders.detailTitle', 'Order');\n\n if (order.isLoading) {\n return (\n <PageLayout title={title}>\n <LoadingView />\n </PageLayout>\n );\n }\n if (order.isError || !order.data) {\n return (\n <PageLayout title={title}>\n <ErrorView\n title={tr('coolandlovely.orders.notFound', 'Order not found')}\n onRetry={() => void order.refetch()}\n />\n </PageLayout>\n );\n }\n\n const data = order.data;\n const pending = data.status === 'PENDING_PAYMENT';\n const actionError = resume.error ?? sync.error ?? cancel.error;\n\n const settle = async () => {\n await sync.mutateAsync(data.id).catch(() => undefined);\n setSession(null);\n };\n\n return (\n <PageLayout\n title={data.orderNumber}\n description={formatDate(data.createdAt)}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button type=\"button\" onClick={() => navigateTo('/orders')} className={secondaryButton}>\n <ArrowLeft className=\"h-4 w-4\" /> {tr('coolandlovely.common.back', 'Back')}\n </button>\n {pending && (\n <>\n <button\n type=\"button\"\n disabled={sync.isPending}\n onClick={() => sync.mutate(data.id)}\n className={secondaryButton}\n >\n {sync.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <RefreshCw className=\"h-4 w-4\" />\n )}\n {tr('coolandlovely.orders.checkPayment', 'Check payment')}\n </button>\n <button\n type=\"button\"\n disabled={cancel.isPending}\n onClick={() => cancel.mutate(data.id)}\n className={cn(\n secondaryButton,\n 'text-status-error-text hover:bg-status-error-bg-subtle'\n )}\n >\n <XCircle className=\"h-4 w-4\" /> {tr('coolandlovely.orders.cancel', 'Cancel order')}\n </button>\n </>\n )}\n </div>\n }\n >\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"space-y-3 lg:col-span-2\">\n {(data.refunds?.length ?? 0) > 0 && (\n <GlassCard treatment=\"plain\" className=\"space-y-3 p-4\" data-testid=\"order-refunds\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n {tr('coolandlovely.orders.refunds', 'Refunds')}\n </p>\n {data.refunds?.map((refund) => (\n <div key={refund.id} className=\"space-y-1\" data-testid=\"order-refund\">\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"text-sm font-medium text-text-primary\">\n {refund.brandName ?? tr('coolandlovely.orders.brand', 'Brand')}\n </span>\n <span className=\"flex items-center gap-2\">\n <span className=\"text-sm font-semibold tabular-nums text-text-primary\">\n {formatPrice(refund.amount, refund.currency)}\n </span>\n <StatusBadge status={refund.status} />\n </span>\n </div>\n <p className=\"text-xs text-text-secondary\">{refund.reason}</p>\n {refund.status === 'COMPLETED' && (\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'coolandlovely.orders.refundedToMethod',\n 'Refunded {{date}} to your original payment method.',\n { date: formatDate(refund.refundedAt ?? refund.createdAt) }\n )}\n </p>\n )}\n </div>\n ))}\n </GlassCard>\n )}\n <OrderReturns order={data} />\n {(data.status === 'PAID' || data.status === 'PARTIALLY_REFUNDED') &&\n (data.fulfilments?.length ?? 0) > 0 && (\n <GlassCard treatment=\"plain\" className=\"space-y-4 p-4\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n {tr('coolandlovely.orders.delivery', 'Delivery')}\n </p>\n {data.fulfilments?.map((fulfilment) => (\n <div\n key={fulfilment.brandId}\n className=\"space-y-2\"\n data-testid=\"order-fulfilment\"\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"text-sm font-medium text-text-primary\">\n {fulfilment.brandName ?? tr('coolandlovely.orders.brand', 'Brand')}\n </span>\n <StatusBadge status={fulfilment.refunded ? 'REFUNDED' : fulfilment.status} />\n </div>\n {fulfilment.refunded ? (\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'coolandlovely.orders.brandRefunded',\n 'The brand refunded these pieces, so they will not be shipped.'\n )}\n </p>\n ) : fulfilment.status === 'UNFULFILLED' ? (\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'coolandlovely.orders.awaitingBrand',\n 'Waiting for the brand to confirm your pieces.'\n )}\n </p>\n ) : (\n <FulfilmentTimeline fulfilment={fulfilment} />\n )}\n </div>\n ))}\n </GlassCard>\n )}\n {data.items.map((item) => (\n <GlassCard key={item.id} treatment=\"plain\" className=\"flex items-center gap-4 p-4\">\n <Thumbnail\n icon={<Package className=\"h-5 w-5\" />}\n seed={item.styleItemId}\n fileId={item.image?.id}\n alt={item.name}\n className=\"h-14 w-14 shrink-0\"\n />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{item.name}</p>\n <p className=\"truncate text-xs text-text-secondary\">\n {[item.size, item.colour, `× ${item.quantity}`].filter(Boolean).join(' · ')}\n </p>\n {item.isCreatorAttributed && (\n <p className=\"mt-0.5 inline-flex items-center gap-1 text-xs text-accent-primary\">\n <Sparkles className=\"h-3 w-3\" />\n {tr('coolandlovely.orders.supportsCreator', 'Supports the creator of the look')}\n </p>\n )}\n </div>\n <span className=\"text-sm font-semibold tabular-nums text-text-primary\">\n {formatPrice(item.lineTotal, data.currency)}\n </span>\n </GlassCard>\n ))}\n </div>\n <GlassCard treatment=\"glass\" className=\"h-fit space-y-4 p-6\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm text-text-secondary\">\n {tr('coolandlovely.orders.status', 'Status')}\n </span>\n <span data-testid=\"order-status\">\n <StatusBadge status={data.status} />\n </span>\n </div>\n <dl className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between\">\n <dt className=\"text-text-secondary\">\n {tr('coolandlovely.checkout.subtotal', 'Subtotal')}\n </dt>\n <dd className=\"tabular-nums text-text-primary\">\n {formatPrice(data.subtotal, data.currency)}\n </dd>\n </div>\n <div className=\"flex justify-between\">\n <dt className=\"text-text-secondary\">{tr('coolandlovely.checkout.tax', 'Tax')}</dt>\n <dd className=\"tabular-nums text-text-primary\">\n {formatPrice(data.taxAmount, data.currency)}\n </dd>\n </div>\n <div className=\"flex justify-between border-t border-border-seam pt-2 text-base font-semibold\">\n <dt className=\"text-text-primary\">{tr('coolandlovely.checkout.total', 'Total')}</dt>\n <dd className=\"tabular-nums text-text-primary\">\n {formatPrice(data.total, data.currency)}\n </dd>\n </div>\n {data.refundedAmount > 0 && (\n <div className=\"flex justify-between\" data-testid=\"order-refunded-amount\">\n <dt className=\"text-text-secondary\">\n {tr('coolandlovely.orders.refunded', 'Refunded')}\n </dt>\n <dd className=\"tabular-nums text-text-primary\">\n − {formatPrice(data.refundedAmount, data.currency)}\n </dd>\n </div>\n )}\n </dl>\n {data.shippingAddress && (\n <div className=\"border-t border-border-seam pt-3 text-sm\">\n <p className=\"mb-1 text-xs font-medium uppercase tracking-wide text-text-secondary\">\n {tr('coolandlovely.orders.shipTo', 'Ship to')}\n </p>\n <p className=\"text-text-primary\">{data.shippingName}</p>\n <p className=\"whitespace-pre-line text-text-secondary\">{data.shippingAddress}</p>\n </div>\n )}\n {data.paidAt && (\n <p className=\"text-xs text-text-secondary\">\n {tr('coolandlovely.orders.paidOn', 'Paid {{date}} via {{provider}}', {\n date: formatDate(data.paidAt),\n provider: data.paymentProvider ?? '—',\n })}\n </p>\n )}\n {actionError && <p className=\"text-sm text-status-error-text\">{actionError.message}</p>}\n {pending &&\n (session ? (\n <PayForOrder checkout={session} onSettle={settle} />\n ) : (\n <button\n type=\"button\"\n disabled={resume.isPending}\n onClick={() =>\n resume.mutate(data.id, { onSuccess: (result) => setSession(result) })\n }\n className=\"inline-flex w-full items-center justify-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2.5 text-sm font-semibold text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {resume.isPending && <Loader2 className=\"h-4 w-4 animate-spin\" />}\n {tr('coolandlovely.orders.payNow', 'Pay now')}\n </button>\n ))}\n </GlassCard>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -178,7 +178,7 @@ function M() {
178
178
  ...s ? [s] : []
179
179
  ]
180
180
  };
181
- }, c = i.size > 0, l = t.map(o).sort((e, t) => c ? t.score - e.score : t.row.likeCount + t.row.saveCount - (e.row.likeCount + e.row.saveCount)).slice(0, 8), u = n.map(s).filter((e) => c ? e.score > 0 : !0).sort((e, t) => t.score - e.score).slice(0, 8), d = t.filter((e) => r.has(e.authorId)).sort((e, t) => t.likeCount + t.saveCount - (e.likeCount + e.saveCount)).slice(0, 8);
181
+ }, c = i.size > 0, l = t.map(o).sort((e, t) => c ? t.score - e.score : t.row.likeCount + t.row.saveCount - (e.row.likeCount + e.row.saveCount)).slice(0, 8), u = n.map(s).filter((e) => !c || e.score > 0).sort((e, t) => t.score - e.score).slice(0, 8), d = t.filter((e) => r.has(e.authorId)).sort((e, t) => t.likeCount + t.saveCount - (e.likeCount + e.saveCount)).slice(0, 8);
182
182
  return {
183
183
  hasProfile: !!e,
184
184
  hasTaste: c,