@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
@@ -1,18 +1,7 @@
1
- import type {Metadata} from 'next';
2
- import {Suspense} from 'react';
3
- import {client} from '@/lib/vendure/api';
4
- import {GetMyQuotesQuery} from '@/lib/vendure/queries';
5
- import {getActiveCustomer} from '@/lib/vendure/actions';
6
- import {getActiveChannelCached} from '@/lib/vendure/cached';
7
- import {getActiveChannelToken} from '@/lib/channel-server';
8
- import {
9
- Table,
10
- TableBody,
11
- TableCell,
12
- TableHead,
13
- TableHeader,
14
- TableRow,
15
- } from '@/components/ui/table';
1
+ import { Price } from '@/components/commerce/price';
2
+ import { QuoteStatusBadge } from '@/components/commerce/quote-status-badge';
3
+ import { ErrorState } from '@/components/shared/error-state';
4
+ import { Button } from '@/components/ui/button';
16
5
  import {
17
6
  Pagination,
18
7
  PaginationContent,
@@ -22,14 +11,18 @@ import {
22
11
  PaginationNext,
23
12
  PaginationPrevious,
24
13
  } from '@/components/ui/pagination';
25
- import {ErrorState} from '@/components/shared/error-state';
26
- import {ArrowRightIcon, Plus} from 'lucide-react';
27
- import {Button} from '@/components/ui/button';
28
- import {Price} from '@/components/commerce/price';
29
- import {QuoteStatusBadge} from '@/components/commerce/quote-status-badge';
30
- import {formatDate} from '@/lib/format';
31
- import {setRequestLocale, getTranslations} from 'next-intl/server';
32
- import {Link, redirect} from '@/i18n/navigation';
14
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
15
+ import { Link, redirect } from '@/i18n/navigation';
16
+ import { getActiveChannelToken } from '@/lib/channel-server';
17
+ import { formatDate } from '@/lib/format';
18
+ import { getActiveCustomer } from '@/lib/vendure/actions';
19
+ import { client } from '@/lib/vendure/api';
20
+ import { getActiveChannelCached } from '@/lib/vendure/cached';
21
+ import { GetMyQuotesQuery } from '@/lib/vendure/queries';
22
+ import { ArrowRightIcon, Plus } from 'lucide-react';
23
+ import type { Metadata } from 'next';
24
+ import { getTranslations, setRequestLocale } from 'next-intl/server';
25
+ import { Suspense } from 'react';
33
26
 
34
27
  export const metadata: Metadata = {
35
28
  title: 'Quotes',
@@ -49,7 +42,17 @@ type QuoteRow = {
49
42
 
50
43
  type QuotesTranslations = Awaited<ReturnType<typeof getTranslations<'Quotes'>>>;
51
44
 
52
- function QuoteTable({quotes, locale, currencyCode, t}: {quotes: QuoteRow[]; locale: string; currencyCode: string; t: QuotesTranslations}) {
45
+ function QuoteTable({
46
+ quotes,
47
+ locale,
48
+ currencyCode,
49
+ t,
50
+ }: {
51
+ quotes: QuoteRow[];
52
+ locale: string;
53
+ currencyCode: string;
54
+ t: QuotesTranslations;
55
+ }) {
53
56
  return (
54
57
  <div className="border rounded-lg">
55
58
  <Table>
@@ -69,22 +72,16 @@ function QuoteTable({quotes, locale, currencyCode, t}: {quotes: QuoteRow[]; loca
69
72
  <TableCell className="font-medium">
70
73
  <Button asChild variant="outline">
71
74
  <Link href={`/account/quotes/${quote.id}`}>
72
- {quote.quoteNumber} <ArrowRightIcon/>
75
+ {quote.quoteNumber} <ArrowRightIcon />
73
76
  </Link>
74
77
  </Button>
75
78
  </TableCell>
79
+ <TableCell>{formatDate(quote.createdAt, locale)}</TableCell>
80
+ <TableCell>{quote.requestedBy}</TableCell>
76
81
  <TableCell>
77
- {formatDate(quote.createdAt, locale)}
78
- </TableCell>
79
- <TableCell>
80
- {quote.requestedBy}
81
- </TableCell>
82
- <TableCell>
83
- <QuoteStatusBadge status={quote.status}/>
84
- </TableCell>
85
- <TableCell>
86
- {t('itemCount', {count: quote.itemCount})}
82
+ <QuoteStatusBadge status={quote.status} />
87
83
  </TableCell>
84
+ <TableCell>{t('itemCount', { count: quote.itemCount })}</TableCell>
88
85
  <TableCell className="text-right">
89
86
  {quote.total != null ? (
90
87
  <Price value={quote.total} currencyCode={currencyCode} taxLabel="excl" />
@@ -100,16 +97,34 @@ function QuoteTable({quotes, locale, currencyCode, t}: {quotes: QuoteRow[]; loca
100
97
  );
101
98
  }
102
99
 
103
- export default async function QuotesPage(props: {params: Promise<{locale: string}>; searchParams: Promise<Record<string, string | string[] | undefined>>}) {
100
+ export default async function QuotesPage(props: {
101
+ params: Promise<{ locale: string }>;
102
+ searchParams: Promise<Record<string, string | string[] | undefined>>;
103
+ }) {
104
104
  return (
105
- <Suspense fallback={<div><div className="flex items-center justify-between mb-6"><h1 className="text-3xl font-bold">Quotes</h1></div><p className="text-sm text-muted-foreground">Loading quotes...</p></div>}>
105
+ <Suspense
106
+ fallback={
107
+ <div>
108
+ <div className="flex items-center justify-between mb-6">
109
+ <h1 className="text-3xl font-bold">Quotes</h1>
110
+ </div>
111
+ <p className="text-sm text-muted-foreground">Loading quotes...</p>
112
+ </div>
113
+ }
114
+ >
106
115
  <QuotesContent params={props.params} searchParamsPromise={props.searchParams} />
107
116
  </Suspense>
108
117
  );
109
118
  }
110
119
 
111
- async function QuotesContent({params, searchParamsPromise}: {params: Promise<{locale: string}>; searchParamsPromise: Promise<Record<string, string | string[] | undefined>>}) {
112
- const {locale} = await params;
120
+ async function QuotesContent({
121
+ params,
122
+ searchParamsPromise,
123
+ }: {
124
+ params: Promise<{ locale: string }>;
125
+ searchParamsPromise: Promise<Record<string, string | string[] | undefined>>;
126
+ }) {
127
+ const { locale } = await params;
113
128
  setRequestLocale(locale);
114
129
  const channelToken = await getActiveChannelToken();
115
130
  const [t, searchParams, activeCustomer, channel] = await Promise.all([
@@ -124,16 +139,13 @@ async function QuotesContent({params, searchParamsPromise}: {params: Promise<{lo
124
139
  const skip = (currentPage - 1) * ITEMS_PER_PAGE;
125
140
  if (!activeCustomer) return await redirect('/sign-in');
126
141
 
127
- const [result, error] = await client.query(
128
- GetMyQuotesQuery,
129
- {
130
- options: {
131
- take: ITEMS_PER_PAGE,
132
- skip,
133
- sort: {createdAt: 'DESC' as any},
134
- },
142
+ const [result, error] = await client.query(GetMyQuotesQuery, {
143
+ options: {
144
+ take: ITEMS_PER_PAGE,
145
+ skip,
146
+ sort: { createdAt: 'DESC' as any },
135
147
  },
136
- );
148
+ });
137
149
  if (error) {
138
150
  return (
139
151
  <div>
@@ -158,9 +170,7 @@ async function QuotesContent({params, searchParamsPromise}: {params: Promise<{lo
158
170
  quoteNumber: quote.quoteNumber,
159
171
  createdAt: quote.createdAt,
160
172
  status: quote.status,
161
- requestedBy: quote.customer
162
- ? `${quote.customer.firstName} ${quote.customer.lastName}`
163
- : '—',
173
+ requestedBy: quote.customer ? `${quote.customer.firstName} ${quote.customer.lastName}` : '—',
164
174
  itemCount: quote.currentVersion?.lineItems.length ?? 0,
165
175
  total: quote.currentVersion?.total ?? null,
166
176
  };
@@ -192,14 +202,24 @@ async function QuotesContent({params, searchParamsPromise}: {params: Promise<{lo
192
202
  {myQuoteRows.length > 0 && (
193
203
  <section>
194
204
  <h2 className="text-2xl font-bold mb-4">{t('myQuotesHeading')}</h2>
195
- <QuoteTable quotes={myQuoteRows} locale={locale} currencyCode={currencyCode} t={t} />
205
+ <QuoteTable
206
+ quotes={myQuoteRows}
207
+ locale={locale}
208
+ currencyCode={currencyCode}
209
+ t={t}
210
+ />
196
211
  </section>
197
212
  )}
198
213
 
199
214
  {companyQuoteRows.length > 0 && (
200
215
  <section>
201
216
  <h2 className="text-2xl font-bold mb-4">{t('companyQuotesHeading')}</h2>
202
- <QuoteTable quotes={companyQuoteRows} locale={locale} currencyCode={currencyCode} t={t} />
217
+ <QuoteTable
218
+ quotes={companyQuoteRows}
219
+ locale={locale}
220
+ currencyCode={currencyCode}
221
+ t={t}
222
+ />
203
223
  </section>
204
224
  )}
205
225
  </div>
@@ -215,11 +235,18 @@ async function QuotesContent({params, searchParamsPromise}: {params: Promise<{lo
215
235
  className={currentPage === 1 ? 'pointer-events-none opacity-50' : ''}
216
236
  />
217
237
  </PaginationItem>
218
- {Array.from({length: totalPages}, (_, i) => i + 1).map((page) => {
219
- if (page === 1 || page === totalPages || (page >= currentPage - 1 && page <= currentPage + 1)) {
238
+ {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => {
239
+ if (
240
+ page === 1 ||
241
+ page === totalPages ||
242
+ (page >= currentPage - 1 && page <= currentPage + 1)
243
+ ) {
220
244
  return (
221
245
  <PaginationItem key={page}>
222
- <PaginationLink href={`/account/quotes?page=${page}`} isActive={page === currentPage}>
246
+ <PaginationLink
247
+ href={`/account/quotes?page=${page}`}
248
+ isActive={page === currentPage}
249
+ >
223
250
  {page}
224
251
  </PaginationLink>
225
252
  </PaginationItem>
@@ -227,7 +254,7 @@ async function QuotesContent({params, searchParamsPromise}: {params: Promise<{lo
227
254
  } else if (page === currentPage - 2 || page === currentPage + 2) {
228
255
  return (
229
256
  <PaginationItem key={page}>
230
- <PaginationEllipsis/>
257
+ <PaginationEllipsis />
231
258
  </PaginationItem>
232
259
  );
233
260
  }
@@ -235,8 +262,14 @@ async function QuotesContent({params, searchParamsPromise}: {params: Promise<{lo
235
262
  })}
236
263
  <PaginationItem>
237
264
  <PaginationNext
238
- href={currentPage < totalPages ? `/account/quotes?page=${currentPage + 1}` : '#'}
239
- className={currentPage === totalPages ? 'pointer-events-none opacity-50' : ''}
265
+ href={
266
+ currentPage < totalPages
267
+ ? `/account/quotes?page=${currentPage + 1}`
268
+ : '#'
269
+ }
270
+ className={
271
+ currentPage === totalPages ? 'pointer-events-none opacity-50' : ''
272
+ }
240
273
  />
241
274
  </PaginationItem>
242
275
  </PaginationContent>
@@ -0,0 +1,152 @@
1
+ 'use client';
2
+
3
+ import { roleLabel } from '@/components/commerce/shared-cart-role';
4
+ import { formatDateTime } from '@/lib/format';
5
+ import {
6
+ CircleDot,
7
+ CreditCard,
8
+ Crown,
9
+ Lock,
10
+ MinusCircle,
11
+ PencilLine,
12
+ PlusCircle,
13
+ ShieldCheck,
14
+ UserMinus,
15
+ UserPlus,
16
+ type LucideIcon,
17
+ } from 'lucide-react';
18
+ import type { SharedCartData } from '../actions';
19
+
20
+ type Activity = SharedCartData['activity'][number];
21
+ type Lines = SharedCartData['order']['lines'];
22
+
23
+ const ICONS: Record<string, LucideIcon> = {
24
+ 'cart-created': CircleDot,
25
+ 'line-added': PlusCircle,
26
+ 'line-removed': MinusCircle,
27
+ 'line-adjusted': PencilLine,
28
+ 'member-added': UserPlus,
29
+ 'member-removed': UserMinus,
30
+ 'role-changed': ShieldCheck,
31
+ 'ownership-transferred': Crown,
32
+ 'checkout-initiated': CreditCard,
33
+ 'cart-closed': Lock,
34
+ };
35
+
36
+ // This activity-describing logic has a near-twin in the dashboard's shared-cart-detail
37
+ // route. They're deliberately kept separate copies: dashboard code can't import from the
38
+ // storefront (different bundle, no server-side deps allowed), so there's no shared module
39
+ // to reach for. If you change how an activity reads here, mirror it there.
40
+ function actorName(activity: Activity): string {
41
+ return activity.actorLabel;
42
+ }
43
+
44
+ // The product an item entry refers to, resolved from the cart's current lines: the affected line
45
+ // for adds/adjusts, or the recorded variant id as a fallback. A removed line is gone, so those
46
+ // entries fall back to the generic "an item".
47
+ function lineName(activity: Activity, lines: Lines): string | null {
48
+ if (activity.targetLineId) {
49
+ const byLine = lines.find((l) => String(l.id) === String(activity.targetLineId));
50
+ if (byLine) return byLine.productVariant.name;
51
+ }
52
+ const variantId = (activity.detail as Record<string, unknown> | null)?.productVariantId;
53
+ if (variantId != null) {
54
+ const byVariant = lines.find((l) => String(l.productVariant.id) === String(variantId));
55
+ if (byVariant) return byVariant.productVariant.name;
56
+ }
57
+ return null;
58
+ }
59
+
60
+ function targetName(activity: Activity): string | null {
61
+ if (!activity.targetCustomer) return null;
62
+ return `${activity.targetCustomer.firstName} ${activity.targetCustomer.lastName}`.trim() || null;
63
+ }
64
+
65
+ function describe(activity: Activity, lines: Lines): string {
66
+ const detail = (activity.detail ?? {}) as Record<string, unknown>;
67
+ const qty = typeof detail.quantity === 'number' ? detail.quantity : undefined;
68
+ const item = lineName(activity, lines);
69
+ const who = targetName(activity);
70
+ switch (activity.action) {
71
+ case 'cart-created':
72
+ return 'created the cart';
73
+ case 'line-added':
74
+ if (item) return qty ? `added ${item} (×${qty})` : `added ${item}`;
75
+ return qty ? `added ${qty} × an item` : 'added an item';
76
+ case 'line-removed':
77
+ return item ? `removed ${item}` : 'removed an item';
78
+ case 'line-adjusted':
79
+ if (item) return qty != null ? `changed ${item} to qty ${qty}` : `changed ${item}`;
80
+ return qty != null ? `changed an item to qty ${qty}` : 'changed an item quantity';
81
+ case 'member-added':
82
+ if (who)
83
+ return detail.role ? `added ${who} as ${roleLabel(String(detail.role))}` : `added ${who}`;
84
+ return detail.role ? `added a member as ${roleLabel(String(detail.role))}` : 'added a member';
85
+ case 'member-removed':
86
+ return who ? `removed ${who}` : 'removed a member';
87
+ case 'role-changed':
88
+ if (who)
89
+ return detail.to
90
+ ? `changed ${who}'s role to ${roleLabel(String(detail.to))}`
91
+ : `changed ${who}'s role`;
92
+ return detail.to
93
+ ? `changed a member's role to ${roleLabel(String(detail.to))}`
94
+ : "changed a member's role";
95
+ case 'ownership-transferred':
96
+ return who ? `transferred ownership to ${who}` : 'transferred ownership';
97
+ case 'checkout-initiated':
98
+ return 'started checkout';
99
+ case 'cart-closed':
100
+ return 'closed the cart';
101
+ default:
102
+ return activity.action;
103
+ }
104
+ }
105
+
106
+ export function ActivityFeed({
107
+ activity,
108
+ lines,
109
+ locale,
110
+ }: {
111
+ activity: ReadonlyArray<Activity>;
112
+ lines: Lines;
113
+ locale: string;
114
+ }) {
115
+ // Newest first.
116
+ const items = [...activity].sort(
117
+ (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
118
+ );
119
+
120
+ return (
121
+ <section className="border rounded-lg">
122
+ <div className="px-4 py-3 border-b">
123
+ <h2 className="font-semibold">Activity</h2>
124
+ </div>
125
+ {items.length === 0 ? (
126
+ <p className="px-4 py-6 text-sm text-muted-foreground">No activity yet.</p>
127
+ ) : (
128
+ <ul className="divide-y">
129
+ {items.map((item) => {
130
+ const Icon = ICONS[item.action] ?? CircleDot;
131
+ return (
132
+ <li key={item.id} className="flex items-start gap-3 px-4 py-3">
133
+ <div className="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-muted">
134
+ <Icon className="h-3.5 w-3.5 text-muted-foreground" />
135
+ </div>
136
+ <div className="min-w-0 flex-1">
137
+ <p className="text-sm">
138
+ <span className="font-medium">{actorName(item)}</span>{' '}
139
+ <span className="text-muted-foreground">{describe(item, lines)}</span>
140
+ </p>
141
+ <p className="text-xs text-muted-foreground">
142
+ {formatDateTime(item.createdAt, locale)}
143
+ </p>
144
+ </div>
145
+ </li>
146
+ );
147
+ })}
148
+ </ul>
149
+ )}
150
+ </section>
151
+ );
152
+ }
@@ -0,0 +1,132 @@
1
+ 'use client';
2
+
3
+ import { Input } from '@/components/ui/input';
4
+ import { Loader2, Plus, Search } from 'lucide-react';
5
+ import Image from 'next/image';
6
+ import { useEffect, useRef, useState, useTransition } from 'react';
7
+ import { toast } from 'sonner';
8
+ import { addItemToSharedCart, searchVariantsForSharedCart, type VariantSearchResult } from '../actions';
9
+
10
+ export function AddItemSearch({ sharedCartId, onAdded }: { sharedCartId: string; onAdded: () => void }) {
11
+ const [term, setTerm] = useState('');
12
+ const [results, setResults] = useState<VariantSearchResult[]>([]);
13
+ const [open, setOpen] = useState(false);
14
+ const [isSearching, setIsSearching] = useState(false);
15
+ const [addingId, setAddingId] = useState<string | null>(null);
16
+ const [, startTransition] = useTransition();
17
+ const containerRef = useRef<HTMLDivElement>(null);
18
+ const debounceRef = useRef<ReturnType<typeof setTimeout>>(undefined);
19
+
20
+ // Debounce in the change handler (not an effect) to match the storefront's
21
+ // search convention and avoid cascading effect re-renders.
22
+ function onTermChange(value: string) {
23
+ setTerm(value);
24
+ clearTimeout(debounceRef.current);
25
+ const q = value.trim();
26
+ if (q.length < 2) {
27
+ setResults([]);
28
+ setOpen(false);
29
+ setIsSearching(false);
30
+ return;
31
+ }
32
+ setIsSearching(true);
33
+ debounceRef.current = setTimeout(async () => {
34
+ const items = await searchVariantsForSharedCart(q);
35
+ setResults(items);
36
+ setOpen(true);
37
+ setIsSearching(false);
38
+ }, 300);
39
+ }
40
+
41
+ useEffect(() => {
42
+ function onClickOutside(e: MouseEvent) {
43
+ if (containerRef.current && !containerRef.current.contains(e.target as Node)) setOpen(false);
44
+ }
45
+ document.addEventListener('mousedown', onClickOutside);
46
+ return () => {
47
+ document.removeEventListener('mousedown', onClickOutside);
48
+ clearTimeout(debounceRef.current);
49
+ };
50
+ }, []);
51
+
52
+ function add(item: VariantSearchResult) {
53
+ setAddingId(item.productVariantId);
54
+ startTransition(async () => {
55
+ const result = await addItemToSharedCart(sharedCartId, item.productVariantId, 1);
56
+ setAddingId(null);
57
+ if (!result.success) {
58
+ toast.error(result.message ?? 'Could not add item.');
59
+ return;
60
+ }
61
+ toast.success(`Added ${item.productName}`);
62
+ setTerm('');
63
+ setResults([]);
64
+ setOpen(false);
65
+ onAdded();
66
+ });
67
+ }
68
+
69
+ return (
70
+ <div className="relative" ref={containerRef}>
71
+ <div className="relative">
72
+ <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
73
+ <Input
74
+ value={term}
75
+ onChange={(e) => onTermChange(e.target.value)}
76
+ onFocus={() => results.length > 0 && setOpen(true)}
77
+ placeholder="Search products to add…"
78
+ className="pl-9"
79
+ />
80
+ {isSearching && (
81
+ <Loader2 className="absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 animate-spin text-muted-foreground" />
82
+ )}
83
+ </div>
84
+
85
+ {open && (
86
+ <div className="absolute z-10 mt-1 w-full rounded-lg border bg-background shadow-lg max-h-72 overflow-y-auto">
87
+ {results.length === 0 ? (
88
+ <p className="p-3 text-sm text-muted-foreground">No products found.</p>
89
+ ) : (
90
+ results.map((item) => (
91
+ <button
92
+ key={item.productVariantId}
93
+ type="button"
94
+ className="flex items-center gap-3 w-full p-3 hover:bg-muted/50 text-left disabled:opacity-60"
95
+ onClick={() => add(item)}
96
+ disabled={addingId !== null}
97
+ >
98
+ <div className="h-8 w-8 shrink-0 rounded border bg-muted overflow-hidden">
99
+ {item.imageUrl && (
100
+ <Image
101
+ src={`${item.imageUrl}?preset=tiny`}
102
+ alt={item.productName}
103
+ width={32}
104
+ height={32}
105
+ className="h-full w-full object-cover"
106
+ />
107
+ )}
108
+ </div>
109
+ <div className="flex-1 min-w-0">
110
+ <div className="flex items-center gap-1.5">
111
+ <p className="text-sm font-medium truncate">{item.productName}</p>
112
+ {!item.inStock && (
113
+ <span className="shrink-0 text-[10px] font-medium px-1 py-0.5 rounded bg-red-100 text-red-700">
114
+ Out of stock
115
+ </span>
116
+ )}
117
+ </div>
118
+ <p className="text-xs text-muted-foreground">{item.sku}</p>
119
+ </div>
120
+ {addingId === item.productVariantId ? (
121
+ <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
122
+ ) : (
123
+ <Plus className="h-4 w-4 text-muted-foreground" />
124
+ )}
125
+ </button>
126
+ ))
127
+ )}
128
+ </div>
129
+ )}
130
+ </div>
131
+ );
132
+ }
@@ -0,0 +1,140 @@
1
+ 'use client';
2
+
3
+ import { ROLE_OPTIONS } from '@/components/commerce/shared-cart-role';
4
+ import { Button } from '@/components/ui/button';
5
+ import {
6
+ Dialog,
7
+ DialogContent,
8
+ DialogDescription,
9
+ DialogFooter,
10
+ DialogHeader,
11
+ DialogTitle,
12
+ DialogTrigger,
13
+ } from '@/components/ui/dialog';
14
+ import { Label } from '@/components/ui/label';
15
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
16
+ import { Loader2, UserPlus } from 'lucide-react';
17
+ import { useState, useTransition } from 'react';
18
+ import { toast } from 'sonner';
19
+ import {
20
+ addSharedCartMember,
21
+ fetchCompanyMembers,
22
+ type CompanyMemberOption,
23
+ type SharedCartRole,
24
+ } from '../actions';
25
+
26
+ export function AddMemberDialog({
27
+ cartId,
28
+ excludeCustomerIds,
29
+ onAdded,
30
+ }: {
31
+ cartId: string;
32
+ excludeCustomerIds: Set<string>;
33
+ onAdded: () => void;
34
+ }) {
35
+ const [open, setOpen] = useState(false);
36
+ const [candidates, setCandidates] = useState<CompanyMemberOption[] | null>(null);
37
+ const [selected, setSelected] = useState<string>('');
38
+ const [role, setRole] = useState<SharedCartRole>('edit');
39
+ const [isPending, startTransition] = useTransition();
40
+
41
+ function loadOnOpen(next: boolean) {
42
+ setOpen(next);
43
+ if (next && candidates === null) {
44
+ fetchCompanyMembers().then(setCandidates);
45
+ }
46
+ }
47
+
48
+ const available = (candidates ?? []).filter((c) => !excludeCustomerIds.has(c.customerId));
49
+
50
+ function add() {
51
+ if (!selected) return;
52
+ startTransition(async () => {
53
+ const result = await addSharedCartMember(cartId, selected, role);
54
+ if (!result.success) {
55
+ toast.error(result.message ?? 'Could not add member.');
56
+ return;
57
+ }
58
+ toast.success('Member added.');
59
+ setSelected('');
60
+ setOpen(false);
61
+ onAdded();
62
+ });
63
+ }
64
+
65
+ return (
66
+ <Dialog open={open} onOpenChange={loadOnOpen}>
67
+ <DialogTrigger asChild>
68
+ <Button variant="outline" className="w-full">
69
+ <UserPlus className="h-4 w-4 mr-2" />
70
+ Add colleague
71
+ </Button>
72
+ </DialogTrigger>
73
+ <DialogContent>
74
+ <DialogHeader>
75
+ <DialogTitle>Add a colleague</DialogTitle>
76
+ <DialogDescription>
77
+ Choose someone from your company and the access they should have.
78
+ </DialogDescription>
79
+ </DialogHeader>
80
+
81
+ <div className="space-y-4 py-2">
82
+ <div className="space-y-2">
83
+ <Label>Colleague</Label>
84
+ {candidates === null ? (
85
+ <p className="text-sm text-muted-foreground">Loading colleagues…</p>
86
+ ) : available.length === 0 ? (
87
+ <p className="text-sm text-muted-foreground">
88
+ Everyone in your company is already a member.
89
+ </p>
90
+ ) : (
91
+ <Select value={selected} onValueChange={setSelected} disabled={isPending}>
92
+ <SelectTrigger className="w-full">
93
+ <SelectValue placeholder="Select a colleague" />
94
+ </SelectTrigger>
95
+ <SelectContent>
96
+ {available.map((c) => (
97
+ <SelectItem key={c.customerId} value={c.customerId}>
98
+ {c.firstName} {c.lastName} — {c.emailAddress}
99
+ </SelectItem>
100
+ ))}
101
+ </SelectContent>
102
+ </Select>
103
+ )}
104
+ </div>
105
+
106
+ <div className="space-y-2">
107
+ <Label>Role</Label>
108
+ <Select
109
+ value={role}
110
+ onValueChange={(v) => setRole(v as SharedCartRole)}
111
+ disabled={isPending}
112
+ >
113
+ <SelectTrigger className="w-full">
114
+ <SelectValue />
115
+ </SelectTrigger>
116
+ <SelectContent>
117
+ {ROLE_OPTIONS.map((o) => (
118
+ <SelectItem key={o.value} value={o.value}>
119
+ <span className="font-medium">{o.label}</span>
120
+ <span className="text-muted-foreground"> — {o.hint}</span>
121
+ </SelectItem>
122
+ ))}
123
+ </SelectContent>
124
+ </Select>
125
+ </div>
126
+ </div>
127
+
128
+ <DialogFooter>
129
+ <Button type="button" variant="ghost" onClick={() => setOpen(false)} disabled={isPending}>
130
+ Cancel
131
+ </Button>
132
+ <Button onClick={add} disabled={!selected || isPending}>
133
+ {isPending && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
134
+ Add colleague
135
+ </Button>
136
+ </DialogFooter>
137
+ </DialogContent>
138
+ </Dialog>
139
+ );
140
+ }