@vendure-platform/create 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (334) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +39 -39
  3. package/dist/add-to-existing.js +1 -1
  4. package/dist/assets/demo-data/initial-data.json +663 -663
  5. package/dist/assets/storefront/components.json +20 -20
  6. package/dist/assets/storefront/eslint.config.mjs +10 -10
  7. package/dist/assets/storefront/graphql.config.yml +1 -1
  8. package/dist/assets/storefront/messages/de.json +2 -0
  9. package/dist/assets/storefront/messages/en.json +2 -0
  10. package/dist/assets/storefront/next.config.ts +5 -4
  11. package/dist/assets/storefront/package.json +1 -1
  12. package/dist/assets/storefront/postcss.config.mjs +1 -1
  13. package/dist/assets/storefront/src/app/[locale]/(catalog)/catalog/page.tsx +17 -21
  14. package/dist/assets/storefront/src/app/[locale]/(catalog)/collection/[slug]/page.tsx +64 -42
  15. package/dist/assets/storefront/src/app/[locale]/(catalog)/layout.tsx +1 -5
  16. package/dist/assets/storefront/src/app/[locale]/(catalog)/product/[slug]/actions.ts +69 -65
  17. package/dist/assets/storefront/src/app/[locale]/(catalog)/product/[slug]/page.tsx +68 -37
  18. package/dist/assets/storefront/src/app/[locale]/(catalog)/search/page.tsx +24 -27
  19. package/dist/assets/storefront/src/app/[locale]/(catalog)/search/search-results.tsx +11 -9
  20. package/dist/assets/storefront/src/app/[locale]/(catalog)/search/search-term.tsx +4 -4
  21. package/dist/assets/storefront/src/app/[locale]/(scenario)/[...path]/page.tsx +1 -1
  22. package/dist/assets/storefront/src/app/[locale]/accept-invitation/[token]/accept-invitation-client.tsx +8 -13
  23. package/dist/assets/storefront/src/app/[locale]/accept-invitation/[token]/actions.ts +11 -14
  24. package/dist/assets/storefront/src/app/[locale]/accept-invitation/[token]/page.tsx +30 -33
  25. package/dist/assets/storefront/src/app/[locale]/accept-invitation/[token]/sign-out-and-retry.tsx +6 -6
  26. package/dist/assets/storefront/src/app/[locale]/account/addresses/actions.ts +30 -45
  27. package/dist/assets/storefront/src/app/[locale]/account/addresses/address-form.tsx +166 -159
  28. package/dist/assets/storefront/src/app/[locale]/account/addresses/addresses-client.tsx +35 -26
  29. package/dist/assets/storefront/src/app/[locale]/account/addresses/loading.tsx +1 -3
  30. package/dist/assets/storefront/src/app/[locale]/account/addresses/page.tsx +16 -18
  31. package/dist/assets/storefront/src/app/[locale]/account/approvals/actions.ts +5 -8
  32. package/dist/assets/storefront/src/app/[locale]/account/approvals/page.tsx +77 -69
  33. package/dist/assets/storefront/src/app/[locale]/account/company/actions.ts +10 -11
  34. package/dist/assets/storefront/src/app/[locale]/account/company/addresses/actions.ts +6 -15
  35. package/dist/assets/storefront/src/app/[locale]/account/company/addresses/address-form.tsx +57 -49
  36. package/dist/assets/storefront/src/app/[locale]/account/company/addresses/addresses-client.tsx +23 -25
  37. package/dist/assets/storefront/src/app/[locale]/account/company/addresses/page.tsx +30 -30
  38. package/dist/assets/storefront/src/app/[locale]/account/company/business-units/actions.ts +20 -29
  39. package/dist/assets/storefront/src/app/[locale]/account/company/business-units/business-unit-form.tsx +32 -37
  40. package/dist/assets/storefront/src/app/[locale]/account/company/business-units/business-units-client.tsx +32 -25
  41. package/dist/assets/storefront/src/app/[locale]/account/company/business-units/page.tsx +24 -24
  42. package/dist/assets/storefront/src/app/[locale]/account/company/business-units/types.ts +7 -4
  43. package/dist/assets/storefront/src/app/[locale]/account/company/edit-company-form.tsx +11 -21
  44. package/dist/assets/storefront/src/app/[locale]/account/company/page.tsx +23 -28
  45. package/dist/assets/storefront/src/app/[locale]/account/company/payment-methods/page.tsx +24 -24
  46. package/dist/assets/storefront/src/app/[locale]/account/company/payment-methods/payment-methods-client.tsx +5 -7
  47. package/dist/assets/storefront/src/app/[locale]/account/company/roles/page.tsx +22 -25
  48. package/dist/assets/storefront/src/app/[locale]/account/company/roles/roles-client.tsx +11 -26
  49. package/dist/assets/storefront/src/app/[locale]/account/company/tax-ids/actions.ts +6 -10
  50. package/dist/assets/storefront/src/app/[locale]/account/company/tax-ids/page.tsx +18 -16
  51. package/dist/assets/storefront/src/app/[locale]/account/company/tax-ids/tax-ids-client.tsx +74 -67
  52. package/dist/assets/storefront/src/app/[locale]/account/company/users/actions.ts +16 -25
  53. package/dist/assets/storefront/src/app/[locale]/account/company/users/edit-user-form.tsx +27 -25
  54. package/dist/assets/storefront/src/app/[locale]/account/company/users/invite-user-form.tsx +27 -19
  55. package/dist/assets/storefront/src/app/[locale]/account/company/users/page.tsx +32 -29
  56. package/dist/assets/storefront/src/app/[locale]/account/company/users/users-client.tsx +35 -38
  57. package/dist/assets/storefront/src/app/[locale]/account/error.tsx +1 -3
  58. package/dist/assets/storefront/src/app/[locale]/account/layout.tsx +16 -22
  59. package/dist/assets/storefront/src/app/[locale]/account/orders/[code]/page.tsx +151 -103
  60. package/dist/assets/storefront/src/app/[locale]/account/orders/[code]/reorder-actions.ts +6 -7
  61. package/dist/assets/storefront/src/app/[locale]/account/orders/[code]/reorder-dialog.tsx +22 -48
  62. package/dist/assets/storefront/src/app/[locale]/account/orders/loading.tsx +8 -8
  63. package/dist/assets/storefront/src/app/[locale]/account/orders/page.tsx +101 -61
  64. package/dist/assets/storefront/src/app/[locale]/account/profile/actions.ts +32 -23
  65. package/dist/assets/storefront/src/app/[locale]/account/profile/change-password-form.tsx +6 -16
  66. package/dist/assets/storefront/src/app/[locale]/account/profile/edit-email-form.tsx +7 -17
  67. package/dist/assets/storefront/src/app/[locale]/account/profile/edit-profile-form.tsx +6 -16
  68. package/dist/assets/storefront/src/app/[locale]/account/profile/loading.tsx +12 -18
  69. package/dist/assets/storefront/src/app/[locale]/account/profile/page.tsx +15 -15
  70. package/dist/assets/storefront/src/app/[locale]/account/quick-order/file-upload-tab.tsx +2 -6
  71. package/dist/assets/storefront/src/app/[locale]/account/quick-order/manual-entry-tab.tsx +18 -15
  72. package/dist/assets/storefront/src/app/[locale]/account/quick-order/page.tsx +3 -6
  73. package/dist/assets/storefront/src/app/[locale]/account/quick-order/preview-table.tsx +10 -2
  74. package/dist/assets/storefront/src/app/[locale]/account/quick-order/save-to-list-dialog.tsx +24 -26
  75. package/dist/assets/storefront/src/app/[locale]/account/quick-order/sku-autocomplete.tsx +19 -10
  76. package/dist/assets/storefront/src/app/[locale]/account/quick-order/text-input-tab.tsx +3 -8
  77. package/dist/assets/storefront/src/app/[locale]/account/quotes/[id]/page.tsx +86 -59
  78. package/dist/assets/storefront/src/app/[locale]/account/quotes/actions.ts +38 -45
  79. package/dist/assets/storefront/src/app/[locale]/account/quotes/new/new-quote-form.tsx +3 -5
  80. package/dist/assets/storefront/src/app/[locale]/account/quotes/new/page.tsx +6 -6
  81. package/dist/assets/storefront/src/app/[locale]/account/quotes/page.tsx +92 -59
  82. package/dist/assets/storefront/src/app/[locale]/account/shared-carts/[id]/activity-feed.tsx +152 -0
  83. package/dist/assets/storefront/src/app/[locale]/account/shared-carts/[id]/add-item-search.tsx +132 -0
  84. package/dist/assets/storefront/src/app/[locale]/account/shared-carts/[id]/add-member-form.tsx +140 -0
  85. package/dist/assets/storefront/src/app/[locale]/account/shared-carts/[id]/members-panel.tsx +259 -0
  86. package/dist/assets/storefront/src/app/[locale]/account/shared-carts/[id]/page.tsx +55 -0
  87. package/dist/assets/storefront/src/app/[locale]/account/shared-carts/[id]/rename-dialog.tsx +117 -0
  88. package/dist/assets/storefront/src/app/[locale]/account/shared-carts/[id]/shared-cart-detail-client.tsx +555 -0
  89. package/dist/assets/storefront/src/app/[locale]/account/shared-carts/actions.ts +305 -0
  90. package/dist/assets/storefront/src/app/[locale]/account/shared-carts/new/new-shared-cart-form.tsx +84 -0
  91. package/dist/assets/storefront/src/app/[locale]/account/shared-carts/new/page.tsx +47 -0
  92. package/dist/assets/storefront/src/app/[locale]/account/shared-carts/page.tsx +276 -0
  93. package/dist/assets/storefront/src/app/[locale]/account/shopping-lists/[id]/page.tsx +22 -8
  94. package/dist/assets/storefront/src/app/[locale]/account/shopping-lists/[id]/shopping-list-detail-client.tsx +62 -49
  95. package/dist/assets/storefront/src/app/[locale]/account/shopping-lists/actions.ts +9 -4
  96. package/dist/assets/storefront/src/app/[locale]/account/shopping-lists/create-shopping-list-form.tsx +5 -11
  97. package/dist/assets/storefront/src/app/[locale]/account/shopping-lists/loading.tsx +1 -1
  98. package/dist/assets/storefront/src/app/[locale]/account/shopping-lists/page.tsx +32 -42
  99. package/dist/assets/storefront/src/app/[locale]/account/store-credits/actions.ts +8 -8
  100. package/dist/assets/storefront/src/app/[locale]/account/store-credits/claim-form.tsx +6 -10
  101. package/dist/assets/storefront/src/app/[locale]/account/store-credits/page.tsx +76 -55
  102. package/dist/assets/storefront/src/app/[locale]/account/store-credits/reveal-code.tsx +10 -6
  103. package/dist/assets/storefront/src/app/[locale]/account/verify-email/page.tsx +32 -39
  104. package/dist/assets/storefront/src/app/[locale]/cart/actions.ts +29 -23
  105. package/dist/assets/storefront/src/app/[locale]/cart/cart-items.tsx +111 -40
  106. package/dist/assets/storefront/src/app/[locale]/cart/cart-line-actions.tsx +9 -9
  107. package/dist/assets/storefront/src/app/[locale]/cart/cart-quantity-control.tsx +61 -48
  108. package/dist/assets/storefront/src/app/[locale]/cart/cart-remove-button.tsx +15 -9
  109. package/dist/assets/storefront/src/app/[locale]/cart/cart.tsx +38 -41
  110. package/dist/assets/storefront/src/app/[locale]/cart/code-input.tsx +8 -8
  111. package/dist/assets/storefront/src/app/[locale]/cart/order-summary.tsx +70 -29
  112. package/dist/assets/storefront/src/app/[locale]/cart/page.tsx +11 -11
  113. package/dist/assets/storefront/src/app/[locale]/cart/promotion-code.tsx +48 -42
  114. package/dist/assets/storefront/src/app/[locale]/cart/reapproval-confirm.tsx +22 -20
  115. package/dist/assets/storefront/src/app/[locale]/cart/request-quote-button.tsx +7 -16
  116. package/dist/assets/storefront/src/app/[locale]/checkout/actions.ts +39 -65
  117. package/dist/assets/storefront/src/app/[locale]/checkout/cancel-quote-checkout-button.tsx +5 -5
  118. package/dist/assets/storefront/src/app/[locale]/checkout/checkout-flow.tsx +202 -202
  119. package/dist/assets/storefront/src/app/[locale]/checkout/checkout-provider.tsx +142 -142
  120. package/dist/assets/storefront/src/app/[locale]/checkout/order-summary.tsx +158 -139
  121. package/dist/assets/storefront/src/app/[locale]/checkout/page.tsx +56 -47
  122. package/dist/assets/storefront/src/app/[locale]/checkout/steps/delivery-step.tsx +86 -88
  123. package/dist/assets/storefront/src/app/[locale]/checkout/steps/payment-step.tsx +90 -91
  124. package/dist/assets/storefront/src/app/[locale]/checkout/steps/review-step.tsx +166 -161
  125. package/dist/assets/storefront/src/app/[locale]/checkout/steps/shipping-address-step.tsx +573 -494
  126. package/dist/assets/storefront/src/app/[locale]/error.tsx +3 -5
  127. package/dist/assets/storefront/src/app/[locale]/forgot-password/actions.ts +10 -7
  128. package/dist/assets/storefront/src/app/[locale]/forgot-password/forgot-password-form.tsx +10 -25
  129. package/dist/assets/storefront/src/app/[locale]/forgot-password/page.tsx +5 -5
  130. package/dist/assets/storefront/src/app/[locale]/globals.css +48 -48
  131. package/dist/assets/storefront/src/app/[locale]/layout.tsx +53 -47
  132. package/dist/assets/storefront/src/app/[locale]/not-found.tsx +3 -5
  133. package/dist/assets/storefront/src/app/[locale]/order-confirmation/[code]/order-confirmation.tsx +71 -44
  134. package/dist/assets/storefront/src/app/[locale]/order-confirmation/[code]/page.tsx +7 -5
  135. package/dist/assets/storefront/src/app/[locale]/page.tsx +33 -35
  136. package/dist/assets/storefront/src/app/[locale]/register/actions.ts +7 -7
  137. package/dist/assets/storefront/src/app/[locale]/register/page.tsx +22 -16
  138. package/dist/assets/storefront/src/app/[locale]/register/registration-form.tsx +22 -34
  139. package/dist/assets/storefront/src/app/[locale]/register-company/actions.ts +15 -19
  140. package/dist/assets/storefront/src/app/[locale]/register-company/page.tsx +17 -15
  141. package/dist/assets/storefront/src/app/[locale]/register-company/registration-form.tsx +9 -15
  142. package/dist/assets/storefront/src/app/[locale]/reset-password/actions.ts +7 -8
  143. package/dist/assets/storefront/src/app/[locale]/reset-password/page.tsx +18 -12
  144. package/dist/assets/storefront/src/app/[locale]/reset-password/reset-password-form.tsx +8 -16
  145. package/dist/assets/storefront/src/app/[locale]/shopping-list/[code]/add-to-cart-button.tsx +10 -4
  146. package/dist/assets/storefront/src/app/[locale]/shopping-list/[code]/page.tsx +62 -30
  147. package/dist/assets/storefront/src/app/[locale]/sign-in/actions.ts +14 -16
  148. package/dist/assets/storefront/src/app/[locale]/sign-in/login-form.tsx +17 -30
  149. package/dist/assets/storefront/src/app/[locale]/sign-in/page.tsx +35 -30
  150. package/dist/assets/storefront/src/app/[locale]/verify/actions.ts +8 -8
  151. package/dist/assets/storefront/src/app/[locale]/verify/page.tsx +18 -14
  152. package/dist/assets/storefront/src/app/[locale]/verify/verify-content.tsx +25 -27
  153. package/dist/assets/storefront/src/app/[locale]/verify-pending/page.tsx +23 -23
  154. package/dist/assets/storefront/src/app/api/clear-company-context/route.ts +6 -3
  155. package/dist/assets/storefront/src/app/api/revalidate/route.ts +19 -28
  156. package/dist/assets/storefront/src/app/api/search-analytics/route.ts +1 -1
  157. package/dist/assets/storefront/src/components/commerce/account-nav-items.ts +44 -17
  158. package/dist/assets/storefront/src/components/commerce/account-sidebar.tsx +30 -22
  159. package/dist/assets/storefront/src/components/commerce/active-filters.tsx +1 -1
  160. package/dist/assets/storefront/src/components/commerce/add-to-shared-cart.tsx +247 -0
  161. package/dist/assets/storefront/src/components/commerce/approval-decision-form.tsx +9 -13
  162. package/dist/assets/storefront/src/components/commerce/approval-status-badge.tsx +23 -15
  163. package/dist/assets/storefront/src/components/commerce/business-unit-switcher.tsx +11 -11
  164. package/dist/assets/storefront/src/components/commerce/catalog-search-bar.tsx +2 -2
  165. package/dist/assets/storefront/src/components/commerce/category-product-section.tsx +28 -16
  166. package/dist/assets/storefront/src/components/commerce/category-sidebar.tsx +31 -18
  167. package/dist/assets/storefront/src/components/commerce/collection-hero-banner.tsx +2 -4
  168. package/dist/assets/storefront/src/components/commerce/company-state-badge.tsx +10 -16
  169. package/dist/assets/storefront/src/components/commerce/company-switcher.tsx +12 -12
  170. package/dist/assets/storefront/src/components/commerce/configurator-form.tsx +188 -79
  171. package/dist/assets/storefront/src/components/commerce/facet-filters.tsx +29 -49
  172. package/dist/assets/storefront/src/components/commerce/gift-card-pdp.tsx +4 -4
  173. package/dist/assets/storefront/src/components/commerce/gift-card-product-info.tsx +20 -30
  174. package/dist/assets/storefront/src/components/commerce/instant-search-dropdown.tsx +258 -227
  175. package/dist/assets/storefront/src/components/commerce/order-status-badge.tsx +27 -19
  176. package/dist/assets/storefront/src/components/commerce/packaging-unit-selector.tsx +27 -26
  177. package/dist/assets/storefront/src/components/commerce/permission-gate.tsx +4 -4
  178. package/dist/assets/storefront/src/components/commerce/price-range-filter.tsx +5 -5
  179. package/dist/assets/storefront/src/components/commerce/price-tiers.tsx +38 -21
  180. package/dist/assets/storefront/src/components/commerce/price.tsx +3 -3
  181. package/dist/assets/storefront/src/components/commerce/product-card-list.tsx +35 -14
  182. package/dist/assets/storefront/src/components/commerce/product-card.tsx +31 -16
  183. package/dist/assets/storefront/src/components/commerce/product-carousel.tsx +20 -12
  184. package/dist/assets/storefront/src/components/commerce/product-grid.tsx +14 -13
  185. package/dist/assets/storefront/src/components/commerce/product-image-carousel.tsx +10 -8
  186. package/dist/assets/storefront/src/components/commerce/product-info.tsx +129 -72
  187. package/dist/assets/storefront/src/components/commerce/product-list-expansion.tsx +102 -74
  188. package/dist/assets/storefront/src/components/commerce/product-list-view.tsx +1 -1
  189. package/dist/assets/storefront/src/components/commerce/product-sidebar.tsx +36 -11
  190. package/dist/assets/storefront/src/components/commerce/product-view-switcher.tsx +33 -21
  191. package/dist/assets/storefront/src/components/commerce/quote-action-buttons.tsx +54 -36
  192. package/dist/assets/storefront/src/components/commerce/quote-activity-timeline.tsx +126 -50
  193. package/dist/assets/storefront/src/components/commerce/quote-comment-form.tsx +9 -13
  194. package/dist/assets/storefront/src/components/commerce/quote-csv-export-button.tsx +6 -6
  195. package/dist/assets/storefront/src/components/commerce/quote-product-search.tsx +22 -18
  196. package/dist/assets/storefront/src/components/commerce/quote-request-form.tsx +28 -27
  197. package/dist/assets/storefront/src/components/commerce/quote-status-badge.tsx +26 -18
  198. package/dist/assets/storefront/src/components/commerce/quote-validity-indicator.tsx +7 -7
  199. package/dist/assets/storefront/src/components/commerce/quote-version-comparison.tsx +50 -30
  200. package/dist/assets/storefront/src/components/commerce/related-products.tsx +37 -28
  201. package/dist/assets/storefront/src/components/commerce/shared-cart-role.ts +39 -0
  202. package/dist/assets/storefront/src/components/commerce/shared-cart-status-badge.tsx +28 -0
  203. package/dist/assets/storefront/src/components/commerce/shop-by-category.tsx +8 -7
  204. package/dist/assets/storefront/src/components/commerce/shopping-list-toggle.tsx +14 -20
  205. package/dist/assets/storefront/src/components/commerce/sort-dropdown.tsx +9 -16
  206. package/dist/assets/storefront/src/components/commerce/store-credit-summary.tsx +30 -32
  207. package/dist/assets/storefront/src/components/commerce/subcollection-tiles.tsx +11 -13
  208. package/dist/assets/storefront/src/components/layout/auth-page-shell.tsx +2 -2
  209. package/dist/assets/storefront/src/components/layout/draft-mode-indicator.tsx +2 -7
  210. package/dist/assets/storefront/src/components/layout/draft-mode-link.tsx +4 -2
  211. package/dist/assets/storefront/src/components/layout/footer.tsx +70 -28
  212. package/dist/assets/storefront/src/components/layout/navbar/actions.ts +9 -12
  213. package/dist/assets/storefront/src/components/layout/navbar/cart-icon.tsx +9 -9
  214. package/dist/assets/storefront/src/components/layout/navbar/channel-picker-wrapper.tsx +4 -9
  215. package/dist/assets/storefront/src/components/layout/navbar/channel-picker.tsx +13 -8
  216. package/dist/assets/storefront/src/components/layout/navbar/currency-picker-wrapper.tsx +4 -4
  217. package/dist/assets/storefront/src/components/layout/navbar/currency-picker.tsx +11 -6
  218. package/dist/assets/storefront/src/components/layout/navbar/language-picker.tsx +7 -7
  219. package/dist/assets/storefront/src/components/layout/navbar/login-button.tsx +24 -21
  220. package/dist/assets/storefront/src/components/layout/navbar/mobile-nav.tsx +8 -8
  221. package/dist/assets/storefront/src/components/layout/navbar/nav-menu-items.tsx +4 -6
  222. package/dist/assets/storefront/src/components/layout/navbar/navbar-cart.tsx +1 -1
  223. package/dist/assets/storefront/src/components/layout/navbar/navbar-collections.tsx +41 -42
  224. package/dist/assets/storefront/src/components/layout/navbar/navbar-link.tsx +7 -10
  225. package/dist/assets/storefront/src/components/layout/navbar/navbar-user.tsx +18 -23
  226. package/dist/assets/storefront/src/components/layout/navbar/theme-switcher.tsx +9 -9
  227. package/dist/assets/storefront/src/components/layout/navbar.tsx +36 -31
  228. package/dist/assets/storefront/src/components/layout/search-input.tsx +35 -32
  229. package/dist/assets/storefront/src/components/providers/awaiting-approval-banner.tsx +12 -10
  230. package/dist/assets/storefront/src/components/providers/cart-count-server.tsx +14 -12
  231. package/dist/assets/storefront/src/components/providers/company-context-guard.tsx +4 -4
  232. package/dist/assets/storefront/src/components/providers/company-permissions-server.tsx +2 -6
  233. package/dist/assets/storefront/src/components/providers/theme-provider.tsx +3 -3
  234. package/dist/assets/storefront/src/components/shared/breadcrumbs.tsx +4 -1
  235. package/dist/assets/storefront/src/components/shared/country-select.tsx +63 -67
  236. package/dist/assets/storefront/src/components/shared/pagination.tsx +15 -28
  237. package/dist/assets/storefront/src/components/shared/prefetch-link.tsx +1 -1
  238. package/dist/assets/storefront/src/components/shared/skeletons/search-input-skeleton.tsx +1 -3
  239. package/dist/assets/storefront/src/components/ui/accordion.tsx +46 -51
  240. package/dist/assets/storefront/src/components/ui/alert-dialog.tsx +94 -121
  241. package/dist/assets/storefront/src/components/ui/alert.tsx +48 -54
  242. package/dist/assets/storefront/src/components/ui/aspect-ratio.tsx +5 -7
  243. package/dist/assets/storefront/src/components/ui/avatar.tsx +29 -44
  244. package/dist/assets/storefront/src/components/ui/badge.tsx +28 -37
  245. package/dist/assets/storefront/src/components/ui/breadcrumb.tsx +82 -89
  246. package/dist/assets/storefront/src/components/ui/button.tsx +43 -49
  247. package/dist/assets/storefront/src/components/ui/calendar.tsx +157 -198
  248. package/dist/assets/storefront/src/components/ui/card.tsx +53 -76
  249. package/dist/assets/storefront/src/components/ui/carousel.tsx +194 -212
  250. package/dist/assets/storefront/src/components/ui/chart.tsx +274 -317
  251. package/dist/assets/storefront/src/components/ui/checkbox.tsx +24 -27
  252. package/dist/assets/storefront/src/components/ui/collapsible.tsx +9 -21
  253. package/dist/assets/storefront/src/components/ui/command.tsx +114 -159
  254. package/dist/assets/storefront/src/components/ui/context-menu.tsx +167 -199
  255. package/dist/assets/storefront/src/components/ui/dialog.tsx +97 -114
  256. package/dist/assets/storefront/src/components/ui/drawer.tsx +93 -107
  257. package/dist/assets/storefront/src/components/ui/dropdown-menu.tsx +171 -206
  258. package/dist/assets/storefront/src/components/ui/empty-state.tsx +5 -5
  259. package/dist/assets/storefront/src/components/ui/field.tsx +196 -208
  260. package/dist/assets/storefront/src/components/ui/form.tsx +115 -139
  261. package/dist/assets/storefront/src/components/ui/hover-card.tsx +27 -33
  262. package/dist/assets/storefront/src/components/ui/input-otp.tsx +50 -59
  263. package/dist/assets/storefront/src/components/ui/input.tsx +17 -17
  264. package/dist/assets/storefront/src/components/ui/kbd-shortcut.tsx +3 -1
  265. package/dist/assets/storefront/src/components/ui/label.tsx +16 -19
  266. package/dist/assets/storefront/src/components/ui/menubar.tsx +193 -223
  267. package/dist/assets/storefront/src/components/ui/navigation-menu.tsx +123 -130
  268. package/dist/assets/storefront/src/components/ui/pagination.tsx +87 -108
  269. package/dist/assets/storefront/src/components/ui/popover.tsx +29 -35
  270. package/dist/assets/storefront/src/components/ui/progress.tsx +19 -26
  271. package/dist/assets/storefront/src/components/ui/radio-group.tsx +32 -38
  272. package/dist/assets/storefront/src/components/ui/resizable.tsx +37 -42
  273. package/dist/assets/storefront/src/components/ui/scroll-area.tsx +41 -47
  274. package/dist/assets/storefront/src/components/ui/select.tsx +130 -152
  275. package/dist/assets/storefront/src/components/ui/separator.tsx +21 -21
  276. package/dist/assets/storefront/src/components/ui/sheet.tsx +90 -111
  277. package/dist/assets/storefront/src/components/ui/sidebar.tsx +611 -649
  278. package/dist/assets/storefront/src/components/ui/skeleton.tsx +10 -10
  279. package/dist/assets/storefront/src/components/ui/slider.tsx +50 -55
  280. package/dist/assets/storefront/src/components/ui/sonner.tsx +20 -20
  281. package/dist/assets/storefront/src/components/ui/switch.tsx +23 -26
  282. package/dist/assets/storefront/src/components/ui/table.tsx +65 -97
  283. package/dist/assets/storefront/src/components/ui/tabs.tsx +39 -55
  284. package/dist/assets/storefront/src/components/ui/textarea.tsx +14 -14
  285. package/dist/assets/storefront/src/components/ui/toggle-group.tsx +56 -62
  286. package/dist/assets/storefront/src/components/ui/toggle.tsx +37 -38
  287. package/dist/assets/storefront/src/components/ui/tooltip.tsx +38 -46
  288. package/dist/assets/storefront/src/config/scenario-meta.ts +2 -2
  289. package/dist/assets/storefront/src/config/scenario.ts +14 -16
  290. package/dist/assets/storefront/src/contexts/auth-context.tsx +5 -6
  291. package/dist/assets/storefront/src/graphql-env.d.ts +18 -5
  292. package/dist/assets/storefront/src/graphql.ts +5 -5
  293. package/dist/assets/storefront/src/hooks/use-mobile.ts +13 -13
  294. package/dist/assets/storefront/src/i18n/locale-utils.ts +3 -3
  295. package/dist/assets/storefront/src/i18n/navigation.ts +4 -4
  296. package/dist/assets/storefront/src/i18n/request.ts +4 -4
  297. package/dist/assets/storefront/src/i18n/routing.ts +5 -5
  298. package/dist/assets/storefront/src/i18n/server.ts +3 -3
  299. package/dist/assets/storefront/src/lib/actions/switch-channel.ts +9 -9
  300. package/dist/assets/storefront/src/lib/actions/switch-currency.ts +7 -7
  301. package/dist/assets/storefront/src/lib/auth.ts +1 -1
  302. package/dist/assets/storefront/src/lib/backend-health.ts +3 -3
  303. package/dist/assets/storefront/src/lib/cart-count-context.tsx +10 -4
  304. package/dist/assets/storefront/src/lib/cart-guard-utils.ts +2 -1
  305. package/dist/assets/storefront/src/lib/cart-guard.ts +7 -6
  306. package/dist/assets/storefront/src/lib/channel-server.ts +4 -4
  307. package/dist/assets/storefront/src/lib/company-context.ts +1 -1
  308. package/dist/assets/storefront/src/lib/company-permissions-context.tsx +1 -5
  309. package/dist/assets/storefront/src/lib/currency-server.ts +4 -4
  310. package/dist/assets/storefront/src/lib/format.ts +16 -7
  311. package/dist/assets/storefront/src/lib/metadata.ts +39 -44
  312. package/dist/assets/storefront/src/lib/quantity.ts +2 -4
  313. package/dist/assets/storefront/src/lib/search-bar-context.tsx +7 -4
  314. package/dist/assets/storefront/src/lib/use-recent-searches.ts +8 -6
  315. package/dist/assets/storefront/src/lib/utils.ts +3 -3
  316. package/dist/assets/storefront/src/lib/vendure/actions.ts +18 -16
  317. package/dist/assets/storefront/src/lib/vendure/api.ts +44 -27
  318. package/dist/assets/storefront/src/lib/vendure/approval-utils.ts +9 -8
  319. package/dist/assets/storefront/src/lib/vendure/cached.ts +20 -15
  320. package/dist/assets/storefront/src/lib/vendure/mutations.ts +96 -0
  321. package/dist/assets/storefront/src/lib/vendure/queries.ts +157 -5
  322. package/dist/assets/storefront/src/middleware.ts +1 -1
  323. package/dist/assets/storefront/src/scenarios/customer/hero.tsx +2 -2
  324. package/dist/assets/storefront/src/scenarios/customer/meta.ts +1 -1
  325. package/dist/assets/storefront/src/schema.graphql +134 -0
  326. package/dist/bin/run.js +1 -1
  327. package/dist/cli.d.ts +1 -1
  328. package/dist/index.d.ts +5 -5
  329. package/dist/index.js +1 -1
  330. package/dist/plugin-registry.js +1 -1
  331. package/dist/scaffold-storefront-only.js +1 -1
  332. package/dist/scaffolder.js +1 -1
  333. package/dist/template-renderer.js +1 -1
  334. package/package.json +2 -2
@@ -0,0 +1,555 @@
1
+ 'use client';
2
+
3
+ import { Price } from '@/components/commerce/price';
4
+ import { MANAGE_SHARED_CARTS_PERMISSION, roleAtLeast } from '@/components/commerce/shared-cart-role';
5
+ import { SharedCartStatusBadge } from '@/components/commerce/shared-cart-status-badge';
6
+ import {
7
+ AlertDialog,
8
+ AlertDialogAction,
9
+ AlertDialogCancel,
10
+ AlertDialogContent,
11
+ AlertDialogDescription,
12
+ AlertDialogFooter,
13
+ AlertDialogHeader,
14
+ AlertDialogTitle,
15
+ } from '@/components/ui/alert-dialog';
16
+ import { Button } from '@/components/ui/button';
17
+ import {
18
+ DropdownMenu,
19
+ DropdownMenuContent,
20
+ DropdownMenuItem,
21
+ DropdownMenuSeparator,
22
+ DropdownMenuTrigger,
23
+ } from '@/components/ui/dropdown-menu';
24
+ import { Input } from '@/components/ui/input';
25
+ import { useRouter } from '@/i18n/navigation';
26
+ import { useCompanyPermissions } from '@/lib/company-permissions-context';
27
+ import { MIN_ORDER_QUANTITY, normalizeQuantity, sanitizeQuantityInput } from '@/lib/quantity';
28
+ import { CreditCard, Loader2, Lock, Minus, MoreVertical, PackageOpen, Plus, Trash2 } from 'lucide-react';
29
+ import Image from 'next/image';
30
+ import { useCallback, useEffect, useRef, useState, useTransition } from 'react';
31
+ import { toast } from 'sonner';
32
+ import {
33
+ adjustSharedCartLine,
34
+ deleteSharedCart,
35
+ fetchSharedCart,
36
+ initiateSharedCartCheckout,
37
+ removeSharedCartLine,
38
+ type SharedCartData,
39
+ type SharedCartRole,
40
+ } from '../actions';
41
+ import { ActivityFeed } from './activity-feed';
42
+ import { AddItemSearch } from './add-item-search';
43
+ import { MembersPanel } from './members-panel';
44
+ import { RenameSharedCartDialog } from './rename-dialog';
45
+
46
+ const POLL_INTERVAL_MS = 7000;
47
+
48
+ interface SharedCartDetailClientProps {
49
+ initialCart: SharedCartData;
50
+ viewerId: string;
51
+ locale: string;
52
+ }
53
+
54
+ export function SharedCartDetailClient({ initialCart, viewerId, locale }: SharedCartDetailClientProps) {
55
+ const router = useRouter();
56
+ const [cart, setCart] = useState<SharedCartData>(initialCart);
57
+ const [isRefreshing, startRefresh] = useTransition();
58
+ const { hasPermission } = useCompanyPermissions();
59
+
60
+ const requestSeq = useRef(0);
61
+ const load = useCallback(async () => {
62
+ const seq = ++requestSeq.current;
63
+ const next = await fetchSharedCart(initialCart.id);
64
+ if (next && seq === requestSeq.current) setCart(next);
65
+ }, [initialCart.id]);
66
+
67
+ const refresh = useCallback(() => {
68
+ startRefresh(load);
69
+ }, [load]);
70
+
71
+ useEffect(() => {
72
+ let timer: ReturnType<typeof setInterval> | undefined;
73
+ const start = () => {
74
+ stop();
75
+ if (!document.hidden) void load();
76
+ timer = setInterval(() => {
77
+ if (!document.hidden) void load();
78
+ }, POLL_INTERVAL_MS);
79
+ };
80
+ const stop = () => timer && clearInterval(timer);
81
+ const onVisibility = () => (document.hidden ? stop() : start());
82
+ start();
83
+ document.addEventListener('visibilitychange', onVisibility);
84
+ return () => {
85
+ stop();
86
+ document.removeEventListener('visibilitychange', onVisibility);
87
+ };
88
+ }, [load]);
89
+
90
+ const isOwner = cart.owner.id === viewerId;
91
+ const isCompanyAdmin = hasPermission(MANAGE_SHARED_CARTS_PERMISSION);
92
+ const viewerRole: SharedCartRole | undefined = cart.members.find((m) => m.customer.id === viewerId)?.role;
93
+ const canManage = isOwner || isCompanyAdmin;
94
+ const canEdit = isOwner || roleAtLeast(viewerRole, 'edit');
95
+ const canCheckout = isOwner || roleAtLeast(viewerRole, 'checkout');
96
+ const isOpen = cart.status === 'Open';
97
+ const isLocked = cart.status === 'Locked';
98
+ const isClosed = cart.status === 'Closed';
99
+ const editable = isOpen && canEdit;
100
+ const isInitiator = cart.order.customer?.id === viewerId;
101
+
102
+ return (
103
+ <div className="space-y-6">
104
+ <Header
105
+ cart={cart}
106
+ canManage={canManage}
107
+ isOpen={isOpen}
108
+ isLocked={isLocked}
109
+ onRenamed={refresh}
110
+ onDeleted={() => router.push('/account/shared-carts')}
111
+ />
112
+
113
+ {isLocked && (
114
+ <LockedBanner
115
+ cart={cart}
116
+ isInitiator={isInitiator}
117
+ onResume={() => router.push('/checkout')}
118
+ />
119
+ )}
120
+ {isClosed && <ClosedBanner cart={cart} />}
121
+
122
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
123
+ <div className="lg:col-span-2 space-y-6">
124
+ <section className="border rounded-lg">
125
+ <div className="flex items-center justify-between px-4 py-3 border-b">
126
+ <h2 className="font-semibold">Items</h2>
127
+ <span className="text-sm text-muted-foreground tabular-nums">
128
+ {cart.order.totalQuantity} {cart.order.totalQuantity === 1 ? 'item' : 'items'}
129
+ </span>
130
+ </div>
131
+
132
+ {editable && (
133
+ <div className="px-4 py-3 border-b bg-muted/30">
134
+ <AddItemSearch sharedCartId={cart.id} onAdded={refresh} />
135
+ </div>
136
+ )}
137
+
138
+ {cart.order.lines.length === 0 ? (
139
+ <div className="px-4 py-12 text-center text-sm text-muted-foreground">
140
+ <PackageOpen className="h-6 w-6 mx-auto mb-2 opacity-60" />
141
+ No items yet.
142
+ {editable && ' Search above to add the first product.'}
143
+ </div>
144
+ ) : (
145
+ <ul className="divide-y">
146
+ {cart.order.lines.map((line) => (
147
+ <LineRow
148
+ key={line.id}
149
+ sharedCartId={cart.id}
150
+ line={line}
151
+ currencyCode={cart.order.currencyCode}
152
+ editable={editable}
153
+ onSettled={refresh}
154
+ />
155
+ ))}
156
+ </ul>
157
+ )}
158
+ </section>
159
+
160
+ <ActivityFeed activity={cart.activity} lines={cart.order.lines} locale={locale} />
161
+ </div>
162
+
163
+ <aside className="space-y-6">
164
+ <section className="border rounded-lg p-4 space-y-3">
165
+ <h2 className="font-semibold">Summary</h2>
166
+ <div className="flex justify-between text-sm">
167
+ <span className="text-muted-foreground">Subtotal</span>
168
+ <Price
169
+ value={cart.order.subTotalWithTax}
170
+ currencyCode={cart.order.currencyCode}
171
+ />
172
+ </div>
173
+ <div className="flex justify-between font-semibold pt-2 border-t">
174
+ <span>Total</span>
175
+ <Price
176
+ value={cart.order.totalWithTax}
177
+ currencyCode={cart.order.currencyCode}
178
+ taxLabel="incl"
179
+ />
180
+ </div>
181
+
182
+ {isOpen && canCheckout && (
183
+ <CheckoutButton
184
+ sharedCartId={cart.id}
185
+ disabled={cart.order.lines.length === 0}
186
+ onStarted={() => router.push('/checkout')}
187
+ />
188
+ )}
189
+ {isOpen && !canCheckout && (
190
+ <p className="text-xs text-muted-foreground pt-1">
191
+ A member with checkout access can place this order.
192
+ </p>
193
+ )}
194
+ </section>
195
+
196
+ <MembersPanel cart={cart} viewerId={viewerId} canManage={canManage} onChanged={refresh} />
197
+ </aside>
198
+ </div>
199
+
200
+ {isRefreshing && <span className="sr-only">Refreshing…</span>}
201
+ </div>
202
+ );
203
+ }
204
+
205
+ function Header({
206
+ cart,
207
+ canManage,
208
+ isOpen,
209
+ isLocked,
210
+ onRenamed,
211
+ onDeleted,
212
+ }: {
213
+ cart: SharedCartData;
214
+ canManage: boolean;
215
+ isOpen: boolean;
216
+ isLocked: boolean;
217
+ onRenamed: () => void;
218
+ onDeleted: () => void;
219
+ }) {
220
+ const [renameOpen, setRenameOpen] = useState(false);
221
+ const [deleteOpen, setDeleteOpen] = useState(false);
222
+ const [isPending, startTransition] = useTransition();
223
+
224
+ function handleDelete() {
225
+ startTransition(async () => {
226
+ const result = await deleteSharedCart(cart.id);
227
+ if (!result.success) {
228
+ toast.error(result.message ?? 'Could not delete the cart.');
229
+ return;
230
+ }
231
+ toast.success('Shared cart deleted.');
232
+ onDeleted();
233
+ });
234
+ }
235
+
236
+ return (
237
+ <div className="flex flex-wrap items-start justify-between gap-3">
238
+ <div className="min-w-0">
239
+ <div className="flex items-center gap-3 flex-wrap">
240
+ <h1 className="text-2xl font-bold truncate">{cart.name}</h1>
241
+ <SharedCartStatusBadge status={cart.status} />
242
+ </div>
243
+ {cart.description && (
244
+ <p className="text-sm text-muted-foreground mt-1 max-w-prose">{cart.description}</p>
245
+ )}
246
+ <p className="text-xs text-muted-foreground mt-1">
247
+ Owner: {cart.owner.firstName} {cart.owner.lastName}
248
+ </p>
249
+ </div>
250
+
251
+ {canManage && (
252
+ <>
253
+ <DropdownMenu>
254
+ <DropdownMenuTrigger asChild>
255
+ <Button variant="outline" size="icon" aria-label="Cart actions">
256
+ <MoreVertical className="h-4 w-4" />
257
+ </Button>
258
+ </DropdownMenuTrigger>
259
+ <DropdownMenuContent align="end">
260
+ <DropdownMenuItem onSelect={() => setRenameOpen(true)} disabled={!isOpen}>
261
+ Rename
262
+ </DropdownMenuItem>
263
+ <DropdownMenuSeparator />
264
+ <DropdownMenuItem
265
+ onSelect={() => setDeleteOpen(true)}
266
+ disabled={isLocked}
267
+ className="text-destructive focus:text-destructive"
268
+ >
269
+ Delete cart
270
+ </DropdownMenuItem>
271
+ </DropdownMenuContent>
272
+ </DropdownMenu>
273
+
274
+ <RenameSharedCartDialog
275
+ open={renameOpen}
276
+ onOpenChange={setRenameOpen}
277
+ cartId={cart.id}
278
+ currentName={cart.name}
279
+ currentDescription={cart.description ?? ''}
280
+ onRenamed={onRenamed}
281
+ />
282
+
283
+ <AlertDialog open={deleteOpen} onOpenChange={setDeleteOpen}>
284
+ <AlertDialogContent>
285
+ <AlertDialogHeader>
286
+ <AlertDialogTitle>Delete this shared cart?</AlertDialogTitle>
287
+ <AlertDialogDescription>
288
+ This permanently removes “{cart.name}” and its items for everyone. This
289
+ can&apos;t be undone.
290
+ </AlertDialogDescription>
291
+ </AlertDialogHeader>
292
+ <AlertDialogFooter>
293
+ <AlertDialogCancel disabled={isPending}>Cancel</AlertDialogCancel>
294
+ <AlertDialogAction
295
+ onClick={(e) => {
296
+ e.preventDefault();
297
+ handleDelete();
298
+ }}
299
+ disabled={isPending}
300
+ className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
301
+ >
302
+ {isPending && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
303
+ Delete
304
+ </AlertDialogAction>
305
+ </AlertDialogFooter>
306
+ </AlertDialogContent>
307
+ </AlertDialog>
308
+ </>
309
+ )}
310
+ </div>
311
+ );
312
+ }
313
+
314
+ function LockedBanner({
315
+ cart,
316
+ isInitiator,
317
+ onResume,
318
+ }: {
319
+ cart: SharedCartData;
320
+ isInitiator: boolean;
321
+ onResume: () => void;
322
+ }) {
323
+ const initiator = cart.order.customer ?? cart.owner;
324
+
325
+ return (
326
+ <div className="flex flex-wrap items-start gap-3 rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 text-amber-900">
327
+ <Lock className="h-5 w-5 mt-0.5 shrink-0" />
328
+ <div className="min-w-0 flex-1 text-sm">
329
+ <p className="font-medium">Checkout in progress</p>
330
+ <p className="text-amber-800">
331
+ {isInitiator ? (
332
+ <>You started checkout for this cart. Resume to finish placing the order.</>
333
+ ) : (
334
+ <>
335
+ {initiator.firstName} {initiator.lastName} is placing this order. The cart is
336
+ read-only until checkout completes.
337
+ </>
338
+ )}
339
+ </p>
340
+ </div>
341
+ {isInitiator && (
342
+ <Button size="sm" onClick={onResume} className="shrink-0">
343
+ <CreditCard className="mr-2 h-4 w-4" />
344
+ Resume checkout
345
+ </Button>
346
+ )}
347
+ </div>
348
+ );
349
+ }
350
+
351
+ function ClosedBanner({ cart }: { cart: SharedCartData }) {
352
+ return (
353
+ <div className="flex items-start gap-3 rounded-lg border bg-muted px-4 py-3">
354
+ <PackageOpen className="h-5 w-5 mt-0.5 shrink-0 text-muted-foreground" />
355
+ <div className="text-sm">
356
+ <p className="font-medium">This cart is closed</p>
357
+ <p className="text-muted-foreground">
358
+ {cart.order.code
359
+ ? `It was checked out as order ${cart.order.code}.`
360
+ : 'It has been checked out.'}
361
+ </p>
362
+ </div>
363
+ </div>
364
+ );
365
+ }
366
+
367
+ function LineRow({
368
+ sharedCartId,
369
+ line,
370
+ currencyCode,
371
+ editable,
372
+ onSettled,
373
+ }: {
374
+ sharedCartId: string;
375
+ line: SharedCartData['order']['lines'][number];
376
+ currencyCode: string;
377
+ editable: boolean;
378
+ onSettled: () => void;
379
+ }) {
380
+ const [isPending, startTransition] = useTransition();
381
+ const [quantityInput, setQuantityInput] = useState(String(line.quantity));
382
+ const debounceRef = useRef<ReturnType<typeof setTimeout>>(undefined);
383
+ const skipBlurSubmitRef = useRef(false);
384
+
385
+ // Resync the field when polling/refresh brings a new quantity (e.g. a
386
+ // colleague edited the same line) — adjust-during-render, no effect.
387
+ const [syncedQuantity, setSyncedQuantity] = useState(line.quantity);
388
+ if (line.quantity !== syncedQuantity) {
389
+ setSyncedQuantity(line.quantity);
390
+ setQuantityInput(String(line.quantity));
391
+ }
392
+
393
+ const normalizedQuantity = normalizeQuantity(quantityInput);
394
+
395
+ function submitQuantity(value: number | string) {
396
+ const next = normalizeQuantity(value);
397
+ setQuantityInput(String(next));
398
+ if (next === line.quantity) return;
399
+ if (debounceRef.current) clearTimeout(debounceRef.current);
400
+ debounceRef.current = setTimeout(() => {
401
+ startTransition(async () => {
402
+ const result = await adjustSharedCartLine(sharedCartId, line.id, next);
403
+ if (!result.success) {
404
+ setQuantityInput(String(line.quantity));
405
+ toast.error(result.message ?? 'Could not update quantity.');
406
+ }
407
+ onSettled();
408
+ });
409
+ }, 300);
410
+ }
411
+
412
+ useEffect(() => () => clearTimeout(debounceRef.current), []);
413
+
414
+ function remove() {
415
+ startTransition(async () => {
416
+ const result = await removeSharedCartLine(sharedCartId, line.id);
417
+ if (!result.success) toast.error(result.message ?? 'Could not remove item.');
418
+ onSettled();
419
+ });
420
+ }
421
+
422
+ return (
423
+ <li className="flex items-center gap-3 px-4 py-3">
424
+ <div className="h-12 w-12 shrink-0 rounded border bg-muted overflow-hidden">
425
+ {line.featuredAsset?.preview && (
426
+ <Image
427
+ src={`${line.featuredAsset.preview}?preset=tiny`}
428
+ alt={line.productVariant.name}
429
+ width={48}
430
+ height={48}
431
+ className="h-full w-full object-cover"
432
+ />
433
+ )}
434
+ </div>
435
+ <div className="min-w-0 flex-1">
436
+ <p className="text-sm font-medium truncate">{line.productVariant.name}</p>
437
+ <p className="text-xs text-muted-foreground">{line.productVariant.sku}</p>
438
+ </div>
439
+
440
+ {editable ? (
441
+ <div className="flex items-center border rounded-md">
442
+ <Button
443
+ type="button"
444
+ variant="ghost"
445
+ size="icon"
446
+ className="h-8 w-8 rounded-none"
447
+ onMouseDown={() => {
448
+ skipBlurSubmitRef.current = true;
449
+ }}
450
+ onClick={() => submitQuantity(normalizedQuantity - 1)}
451
+ disabled={isPending || normalizedQuantity <= MIN_ORDER_QUANTITY}
452
+ aria-label="Decrease quantity"
453
+ >
454
+ <Minus className="h-3 w-3" />
455
+ </Button>
456
+ <Input
457
+ aria-label="Quantity"
458
+ inputMode="numeric"
459
+ pattern="[0-9]*"
460
+ value={quantityInput}
461
+ onChange={(e) => setQuantityInput(sanitizeQuantityInput(e.target.value))}
462
+ onBlur={() => {
463
+ if (skipBlurSubmitRef.current) {
464
+ skipBlurSubmitRef.current = false;
465
+ return;
466
+ }
467
+ if (debounceRef.current) clearTimeout(debounceRef.current);
468
+ submitQuantity(quantityInput);
469
+ }}
470
+ onKeyDown={(e) => {
471
+ if (e.key === 'Enter') {
472
+ e.preventDefault();
473
+ if (debounceRef.current) clearTimeout(debounceRef.current);
474
+ submitQuantity(quantityInput);
475
+ } else if (e.key === 'Escape') {
476
+ if (debounceRef.current) clearTimeout(debounceRef.current);
477
+ setQuantityInput(String(line.quantity));
478
+ e.currentTarget.blur();
479
+ }
480
+ }}
481
+ disabled={isPending}
482
+ className="h-8 w-12 border-0 text-center shadow-none focus-visible:ring-0 tabular-nums"
483
+ />
484
+ <Button
485
+ type="button"
486
+ variant="ghost"
487
+ size="icon"
488
+ className="h-8 w-8 rounded-none"
489
+ onMouseDown={() => {
490
+ skipBlurSubmitRef.current = true;
491
+ }}
492
+ onClick={() => submitQuantity(normalizedQuantity + 1)}
493
+ disabled={isPending}
494
+ aria-label="Increase quantity"
495
+ >
496
+ <Plus className="h-3 w-3" />
497
+ </Button>
498
+ </div>
499
+ ) : (
500
+ <span className="text-sm text-muted-foreground tabular-nums">×{line.quantity}</span>
501
+ )}
502
+
503
+ <div className="w-24 text-right text-sm font-medium tabular-nums">
504
+ <Price value={line.linePriceWithTax} currencyCode={currencyCode} />
505
+ </div>
506
+
507
+ {editable && (
508
+ <Button
509
+ variant="ghost"
510
+ size="icon"
511
+ className="h-7 w-7 text-muted-foreground hover:text-destructive"
512
+ onClick={remove}
513
+ disabled={isPending}
514
+ aria-label="Remove item"
515
+ >
516
+ <Trash2 className="h-4 w-4" />
517
+ </Button>
518
+ )}
519
+ </li>
520
+ );
521
+ }
522
+
523
+ function CheckoutButton({
524
+ sharedCartId,
525
+ disabled,
526
+ onStarted,
527
+ }: {
528
+ sharedCartId: string;
529
+ disabled: boolean;
530
+ onStarted: () => void;
531
+ }) {
532
+ const [isPending, startTransition] = useTransition();
533
+
534
+ function handleClick() {
535
+ startTransition(async () => {
536
+ const result = await initiateSharedCartCheckout(sharedCartId);
537
+ if (!result.success) {
538
+ toast.error(result.message ?? 'Could not start checkout.');
539
+ return;
540
+ }
541
+ onStarted();
542
+ });
543
+ }
544
+
545
+ return (
546
+ <Button className="w-full" disabled={disabled || isPending} onClick={handleClick}>
547
+ {isPending ? (
548
+ <Loader2 className="h-4 w-4 mr-2 animate-spin" />
549
+ ) : (
550
+ <CreditCard className="h-4 w-4 mr-2" />
551
+ )}
552
+ Proceed to checkout
553
+ </Button>
554
+ );
555
+ }