@akinon/projectzero 2.0.0-beta.2 → 2.0.0-beta.20

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 (377) hide show
  1. package/CHANGELOG.md +147 -6
  2. package/app-template/.env.example +8 -0
  3. package/app-template/.github/instructions/account.instructions.md +749 -0
  4. package/app-template/.github/instructions/checkout.instructions.md +678 -0
  5. package/app-template/.github/instructions/default.instructions.md +279 -0
  6. package/app-template/.github/instructions/edge-cases.instructions.md +73 -0
  7. package/app-template/.github/instructions/routing.instructions.md +603 -0
  8. package/app-template/.github/instructions/settings.instructions.md +338 -0
  9. package/app-template/.gitignore +5 -0
  10. package/app-template/AGENTS.md +7 -0
  11. package/app-template/CHANGELOG.md +1645 -61
  12. package/app-template/Procfile +1 -1
  13. package/app-template/README.md +6 -0
  14. package/app-template/akinon.json +1 -4
  15. package/app-template/build.sh +10 -0
  16. package/app-template/config/prebuild-tests.json +5 -0
  17. package/app-template/docs/advanced-usage.md +111 -0
  18. package/app-template/docs/plugins.md +60 -25
  19. package/app-template/docs/sentry-usage.md +35 -0
  20. package/app-template/jest.config.ts +2 -2
  21. package/app-template/next-env.d.ts +1 -0
  22. package/app-template/next.config.mjs +8 -5
  23. package/app-template/package.json +60 -50
  24. package/app-template/postcss.config.mjs +5 -0
  25. package/app-template/public/amex.svg +12 -0
  26. package/app-template/public/apple-pay.svg +16 -0
  27. package/app-template/public/assets/images/product-placeholder-1.jpg +0 -0
  28. package/app-template/public/assets/images/product-placeholder-2.jpg +0 -0
  29. package/app-template/public/assets/images/product-placeholder-3.jpg +0 -0
  30. package/app-template/public/assets/images/product-placeholder-4.jpg +0 -0
  31. package/app-template/public/google-pay.svg +16 -0
  32. package/app-template/public/locales/en/account.json +13 -4
  33. package/app-template/public/locales/en/auth.json +6 -7
  34. package/app-template/public/locales/en/basket.json +6 -6
  35. package/app-template/public/locales/en/blog.json +7 -0
  36. package/app-template/public/locales/en/category.json +3 -1
  37. package/app-template/public/locales/en/checkout.json +17 -4
  38. package/app-template/public/locales/en/common.json +71 -3
  39. package/app-template/public/locales/en/forgot_password.json +6 -7
  40. package/app-template/public/locales/en/product.json +84 -4
  41. package/app-template/public/locales/tr/account.json +13 -4
  42. package/app-template/public/locales/tr/auth.json +16 -17
  43. package/app-template/public/locales/tr/basket.json +4 -4
  44. package/app-template/public/locales/tr/blog.json +7 -0
  45. package/app-template/public/locales/tr/category.json +3 -1
  46. package/app-template/public/locales/tr/checkout.json +48 -36
  47. package/app-template/public/locales/tr/common.json +70 -2
  48. package/app-template/public/locales/tr/forgot_password.json +12 -13
  49. package/app-template/public/locales/tr/product.json +82 -0
  50. package/app-template/public/logo.svg +3 -27
  51. package/app-template/public/mastercard.svg +14 -0
  52. package/app-template/public/masterpass-javascript-sdk-web.min.js +1 -0
  53. package/app-template/public/promotion-banner.jpg +0 -0
  54. package/app-template/public/shop-pay.svg +12 -0
  55. package/app-template/public/visa.svg +12 -0
  56. package/app-template/src/__tests__/middleware-matcher.test.ts +135 -0
  57. package/app-template/src/app/[commerce]/[locale]/[currency]/blog/[slug]/page.tsx +118 -0
  58. package/app-template/src/app/[commerce]/[locale]/[currency]/pages/[slug]/page.tsx +15 -0
  59. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/[...prettyurl]/page.tsx +9 -9
  60. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/layout.tsx +2 -2
  61. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/[id]/cancellation/page.tsx +105 -13
  62. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/[id]/page.tsx +136 -52
  63. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/profile/page.tsx +2 -2
  64. package/app-template/src/app/[pz]/category/[pk]/page.tsx +27 -0
  65. package/app-template/src/app/[pz]/error.tsx +17 -0
  66. package/app-template/src/app/[pz]/flat-page/[pk]/page.tsx +23 -0
  67. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/forms/[pk]/generate/page.tsx +1 -2
  68. package/app-template/src/app/[pz]/group-product/[pk]/page.tsx +93 -0
  69. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/landing-page/[pk]/page.tsx +2 -4
  70. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/layout.tsx +3 -10
  71. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/list/page.tsx +2 -4
  72. package/app-template/src/app/{[commerce]/[locale]/[currency]/pz-not-found/page.tsx → [pz]/not-found.tsx} +5 -7
  73. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/orders/checkout/page.tsx +7 -4
  74. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/orders/completed/[token]/page.tsx +6 -4
  75. package/app-template/src/app/[pz]/product/[pk]/page.tsx +102 -0
  76. package/app-template/src/app/[pz]/special-page/[pk]/page.tsx +35 -0
  77. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/email-set-primary/[[...id]]/page.tsx +3 -4
  78. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/registration/account-confirm-email/[[...id]]/page.tsx +3 -3
  79. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/reset/[[...id]]/page.tsx +41 -5
  80. package/app-template/src/app/[pz]/xml-sitemap/[node]/route.ts +73 -0
  81. package/app-template/src/app/api/auth/[...nextauth]/route.ts +3 -0
  82. package/app-template/src/app/api/barcode-search/route.ts +1 -0
  83. package/app-template/src/app/api/form/[...id]/route.ts +1 -7
  84. package/app-template/src/app/api/image-proxy/route.ts +1 -0
  85. package/app-template/src/app/api/product-categories/route.ts +1 -0
  86. package/app-template/src/app/api/similar-product-list/route.ts +1 -0
  87. package/app-template/src/app/api/similar-products/route.ts +1 -0
  88. package/app-template/src/app/api/theme-settings/route.ts +12 -0
  89. package/app-template/src/app/api/virtual-try-on/limited-categories/route.ts +1 -0
  90. package/app-template/src/app/api/virtual-try-on/route.ts +1 -0
  91. package/app-template/src/assets/fonts/pz-icon.css +211 -49
  92. package/app-template/src/assets/fonts/pz-icon.eot +0 -0
  93. package/app-template/src/assets/fonts/pz-icon.html +486 -0
  94. package/app-template/src/assets/fonts/pz-icon.scss +373 -49
  95. package/app-template/src/assets/fonts/pz-icon.svg +215 -53
  96. package/app-template/src/assets/fonts/pz-icon.ttf +0 -0
  97. package/app-template/src/assets/fonts/pz-icon.woff +0 -0
  98. package/app-template/src/assets/fonts/pz-icon.woff2 +0 -0
  99. package/app-template/src/assets/globals.scss +37 -34
  100. package/app-template/src/assets/icons/arrow-right.svg +3 -0
  101. package/app-template/src/assets/icons/cart.svg +4 -12
  102. package/app-template/src/assets/icons/check.svg +2 -18
  103. package/app-template/src/assets/icons/chevron-down.svg +2 -7
  104. package/app-template/src/assets/icons/delete.svg +3 -0
  105. package/app-template/src/assets/icons/facebook.svg +2 -8
  106. package/app-template/src/assets/icons/fav-off.svg +5 -0
  107. package/app-template/src/assets/icons/fav-on.svg +5 -0
  108. package/app-template/src/assets/icons/filter-and-sort.svg +3 -0
  109. package/app-template/src/assets/icons/heart.svg +3 -0
  110. package/app-template/src/assets/icons/instagram.svg +2 -13
  111. package/app-template/src/assets/icons/materials.svg +3 -0
  112. package/app-template/src/assets/icons/person.svg +4 -0
  113. package/app-template/src/assets/icons/pinterest.svg +5 -11
  114. package/app-template/src/assets/icons/ruler.svg +3 -0
  115. package/app-template/src/assets/icons/search.svg +8 -11
  116. package/app-template/src/assets/icons/share.svg +2 -9
  117. package/app-template/src/assets/icons/snapchat.svg +3 -0
  118. package/app-template/src/assets/icons/tiktok.svg +3 -0
  119. package/app-template/src/assets/icons/tumblr.svg +6 -0
  120. package/app-template/src/assets/icons/twitter.svg +2 -10
  121. package/app-template/src/assets/icons/vimeo.svg +3 -0
  122. package/app-template/src/assets/icons/youtube.svg +3 -0
  123. package/app-template/src/assets/icons/zoom.svg +8 -0
  124. package/app-template/src/auth.ts +3 -0
  125. package/app-template/src/components/__tests__/link.test.tsx +2 -0
  126. package/app-template/src/components/accordion.tsx +48 -23
  127. package/app-template/src/components/action-tooltip.tsx +160 -0
  128. package/app-template/src/components/button.tsx +50 -35
  129. package/app-template/src/components/carousel-core.tsx +4 -11
  130. package/app-template/src/components/checkbox.tsx +2 -1
  131. package/app-template/src/components/currency-select.tsx +150 -4
  132. package/app-template/src/components/file-input.tsx +64 -2
  133. package/app-template/src/components/generate-form-fields.tsx +49 -10
  134. package/app-template/src/components/icon.tsx +5 -6
  135. package/app-template/src/components/index.ts +4 -1
  136. package/app-template/src/components/input.tsx +8 -2
  137. package/app-template/src/components/language-select.tsx +88 -2
  138. package/app-template/src/components/modal.tsx +34 -16
  139. package/app-template/src/components/pagination.tsx +133 -20
  140. package/app-template/src/components/price.tsx +1 -1
  141. package/app-template/src/components/pwa-tags.tsx +1 -0
  142. package/app-template/src/components/quantity-input.tsx +63 -0
  143. package/app-template/src/components/quantity-selector.tsx +203 -0
  144. package/app-template/src/components/route-handler.tsx +50 -0
  145. package/app-template/src/components/select.tsx +86 -54
  146. package/app-template/src/components/tabs.tsx +2 -2
  147. package/app-template/src/components/types/index.ts +55 -2
  148. package/app-template/src/components/widget-content.tsx +323 -0
  149. package/app-template/src/data/server/theme.ts +70 -0
  150. package/app-template/src/hooks/use-fav-button.tsx +9 -10
  151. package/app-template/src/hooks/use-product-cart.ts +80 -0
  152. package/app-template/src/hooks/use-stock-alert.ts +74 -0
  153. package/app-template/src/hooks/use-theme-settings.ts +42 -0
  154. package/app-template/src/lib/fonts.ts +149 -0
  155. package/app-template/src/middleware.ts +1 -0
  156. package/app-template/src/plugins.js +13 -2
  157. package/app-template/src/redux/middlewares/category.ts +6 -5
  158. package/app-template/src/redux/reducers/category.ts +1 -1
  159. package/app-template/src/redux/store.ts +21 -1
  160. package/app-template/src/routes/index.ts +2 -1
  161. package/app-template/src/settings.js +5 -3
  162. package/app-template/src/types/hookform-resolvers-yup.d.ts +28 -0
  163. package/app-template/src/types/index.ts +74 -3
  164. package/app-template/src/types/next-auth.d.ts +2 -2
  165. package/app-template/src/types/widget.ts +169 -0
  166. package/app-template/src/utils/convert-facet-search-params.ts +1 -1
  167. package/app-template/src/utils/formatDate.ts +48 -0
  168. package/app-template/src/utils/styles.ts +71 -0
  169. package/app-template/src/utils/variant-validation.ts +41 -0
  170. package/app-template/src/views/account/address-form.tsx +8 -4
  171. package/app-template/src/views/account/contact-form.tsx +148 -136
  172. package/app-template/src/views/account/content-header.tsx +2 -2
  173. package/app-template/src/views/account/faq/faq-tabs.tsx +8 -2
  174. package/app-template/src/views/account/favorite-item.tsx +1 -1
  175. package/app-template/src/views/account/order.tsx +10 -8
  176. package/app-template/src/views/account/orders/order-cancellation-item.tsx +4 -3
  177. package/app-template/src/views/account/orders/order-detail-header.tsx +1 -1
  178. package/app-template/src/views/anonymous-tracking/order-detail/index.tsx +44 -37
  179. package/app-template/src/views/basket/basket-item.tsx +697 -107
  180. package/app-template/src/views/basket/basket-summary-context.tsx +560 -0
  181. package/app-template/src/views/basket/designer-context.tsx +617 -0
  182. package/app-template/src/views/basket/index.ts +2 -0
  183. package/app-template/src/views/basket/summary.tsx +497 -60
  184. package/app-template/src/views/breadcrumb/breadcrumb-client.tsx +190 -0
  185. package/app-template/src/views/breadcrumb/breadcrumb-registrar.tsx +286 -0
  186. package/app-template/src/views/breadcrumb/constants.ts +15 -0
  187. package/app-template/src/views/breadcrumb/index.tsx +127 -0
  188. package/app-template/src/views/breadcrumb.tsx +13 -38
  189. package/app-template/src/views/category/category-active-filters.tsx +1 -1
  190. package/app-template/src/views/category/category-banner.tsx +4 -23
  191. package/app-template/src/views/category/category-header.tsx +289 -60
  192. package/app-template/src/views/category/category-info.tsx +177 -27
  193. package/app-template/src/views/category/filters/filter-item.tsx +138 -42
  194. package/app-template/src/views/category/filters/index.tsx +209 -49
  195. package/app-template/src/views/category/layout.tsx +7 -4
  196. package/app-template/src/views/category/native-widget-context.tsx +257 -0
  197. package/app-template/src/views/category/product-list-registrar.tsx +665 -0
  198. package/app-template/src/views/checkout/auth.tsx +64 -40
  199. package/app-template/src/views/checkout/checkout-address-registrar.tsx +254 -0
  200. package/app-template/src/views/checkout/checkout-buttons-registrar.tsx +183 -0
  201. package/app-template/src/views/checkout/checkout-delivery-method-registrar.tsx +259 -0
  202. package/app-template/src/views/checkout/checkout-payment-options-registrar.tsx +253 -0
  203. package/app-template/src/views/checkout/checkout-summary-registrar.tsx +183 -0
  204. package/app-template/src/views/checkout/constants.ts +5 -0
  205. package/app-template/src/views/checkout/index.tsx +5 -0
  206. package/app-template/src/views/checkout/layout/header.tsx +9 -5
  207. package/app-template/src/views/checkout/steps/payment/index.tsx +5 -2
  208. package/app-template/src/views/checkout/steps/payment/options/credit-card/index.tsx +93 -6
  209. package/app-template/src/views/checkout/steps/payment/options/funds-transfer.tsx +25 -5
  210. package/app-template/src/views/checkout/steps/payment/options/loyalty.tsx +21 -2
  211. package/app-template/src/views/checkout/steps/payment/options/masterpass-rest.tsx +15 -0
  212. package/app-template/src/views/checkout/steps/payment/options/redirection.tsx +27 -5
  213. package/app-template/src/views/checkout/steps/payment/options/saved-card.tsx +18 -0
  214. package/app-template/src/views/checkout/steps/payment/options/store-credit.tsx +464 -0
  215. package/app-template/src/views/checkout/steps/payment/payment-option-buttons.tsx +171 -40
  216. package/app-template/src/views/checkout/steps/shipping/address-box.tsx +104 -29
  217. package/app-template/src/views/checkout/steps/shipping/addresses.tsx +129 -46
  218. package/app-template/src/views/checkout/steps/shipping/shipping-options.tsx +232 -27
  219. package/app-template/src/views/checkout/summary.tsx +310 -26
  220. package/app-template/src/views/find-in-store/index.tsx +2 -2
  221. package/app-template/src/views/footer/footer-app-banner-context.tsx +326 -0
  222. package/app-template/src/views/footer/footer-bottom-context.tsx +215 -0
  223. package/app-template/src/views/footer/footer-bottom-wrapper.tsx +74 -0
  224. package/app-template/src/views/footer/footer-layout-constants.ts +35 -0
  225. package/app-template/src/views/footer/footer-layout-registrar.tsx +342 -0
  226. package/app-template/src/views/footer/footer-layout-switcher.tsx +110 -0
  227. package/app-template/src/views/footer/footer-menu-context.tsx +211 -0
  228. package/app-template/src/views/footer/footer-native-widgets.tsx +60 -0
  229. package/app-template/src/views/footer/footer-social-context.tsx +254 -0
  230. package/app-template/src/views/footer/footer-subscription-context.tsx +210 -0
  231. package/app-template/src/views/footer/footer-utils.ts +43 -0
  232. package/app-template/src/views/footer/footer-value-props-context.tsx +326 -0
  233. package/app-template/src/views/footer/logo-settings.ts +183 -0
  234. package/app-template/src/views/footer/native-widget-config.ts +262 -0
  235. package/app-template/src/views/footer/subscription-settings.ts +122 -0
  236. package/app-template/src/views/footer/use-footer-logo.ts +162 -0
  237. package/app-template/src/views/footer.tsx +415 -13
  238. package/app-template/src/views/guest-login/index.tsx +62 -58
  239. package/app-template/src/views/header/action-menu.tsx +284 -49
  240. package/app-template/src/views/header/band.tsx +6 -21
  241. package/app-template/src/views/header/designer-context.tsx +261 -0
  242. package/app-template/src/views/header/header-announcement-registrar.tsx +267 -0
  243. package/app-template/src/views/header/header-client-wrapper.tsx +496 -0
  244. package/app-template/src/views/header/header-content.tsx +1026 -0
  245. package/app-template/src/views/header/header-currency-registrar.tsx +348 -0
  246. package/app-template/src/views/header/header-icons-context.tsx +262 -0
  247. package/app-template/src/views/header/header-language-registrar.tsx +348 -0
  248. package/app-template/src/views/header/header-layout-context.tsx +143 -0
  249. package/app-template/src/views/header/header-layout-registrar.tsx +658 -0
  250. package/app-template/src/views/header/header-logo-context.tsx +228 -0
  251. package/app-template/src/views/header/header-logo.tsx +118 -0
  252. package/app-template/src/views/header/header-mini-basket-context.tsx +524 -0
  253. package/app-template/src/views/header/header-search-registrar.tsx +511 -0
  254. package/app-template/src/views/header/header-text-slider-registrar.tsx +382 -0
  255. package/app-template/src/views/header/index.tsx +110 -48
  256. package/app-template/src/views/header/inline-search.tsx +262 -0
  257. package/app-template/src/views/header/mini-basket.tsx +832 -46
  258. package/app-template/src/views/header/mobile-hamburger-button.tsx +5 -8
  259. package/app-template/src/views/header/mobile-menu.tsx +12 -0
  260. package/app-template/src/views/header/navbar-menu-context.tsx +219 -0
  261. package/app-template/src/views/header/navbar.tsx +178 -111
  262. package/app-template/src/views/header/search/index.tsx +85 -24
  263. package/app-template/src/views/header/search/results.tsx +127 -65
  264. package/app-template/src/views/header/search/search-input.tsx +61 -0
  265. package/app-template/src/views/header/server-settings-parser.ts +1105 -0
  266. package/app-template/src/views/header/use-header-icons.ts +241 -0
  267. package/app-template/src/views/header/use-header-logo.ts +213 -0
  268. package/app-template/src/views/header/use-navbar-menu.ts +179 -0
  269. package/app-template/src/views/installment-options/index.tsx +1 -1
  270. package/app-template/src/views/login/index.tsx +89 -56
  271. package/app-template/src/views/otp-login/index.tsx +23 -20
  272. package/app-template/src/views/product/accordion-section.tsx +61 -0
  273. package/app-template/src/views/product/accordion-wrapper.tsx +135 -43
  274. package/app-template/src/views/product/custom-button-group.tsx +69 -0
  275. package/app-template/src/views/product/favorites-button-section.tsx +69 -0
  276. package/app-template/src/views/product/find-in-store-section.tsx +60 -0
  277. package/app-template/src/views/product/index.ts +1 -0
  278. package/app-template/src/views/product/layout.tsx +21 -6
  279. package/app-template/src/views/product/misc-buttons.tsx +339 -25
  280. package/app-template/src/views/product/price-wrapper.tsx +3 -24
  281. package/app-template/src/views/product/product-actions.tsx +294 -0
  282. package/app-template/src/views/product/product-info-section.tsx +140 -0
  283. package/app-template/src/views/product/product-info.tsx +130 -254
  284. package/app-template/src/views/product/product-share.tsx +61 -0
  285. package/app-template/src/views/product/product-variants.tsx +26 -0
  286. package/app-template/src/views/product/quantity-section.tsx +73 -0
  287. package/app-template/src/views/product/sale-tag.tsx +10 -0
  288. package/app-template/src/views/product/share-section.tsx +357 -0
  289. package/app-template/src/views/product/slider.tsx +135 -76
  290. package/app-template/src/views/product/variant.tsx +69 -41
  291. package/app-template/src/views/product/variants-section.tsx +126 -0
  292. package/app-template/src/views/product-detail/constants.ts +272 -0
  293. package/app-template/src/views/product-detail/index.ts +10 -0
  294. package/app-template/src/views/product-detail/product-detail-registrar.tsx +616 -0
  295. package/app-template/src/views/product-item/index.tsx +119 -46
  296. package/app-template/src/views/register/index.tsx +54 -44
  297. package/app-template/src/views/share/index.tsx +9 -6
  298. package/app-template/src/views/widgets/home-hero-slider-content.tsx +41 -39
  299. package/app-template/src/widgets/flatpages/about-us/index.tsx +78 -0
  300. package/app-template/src/widgets/flatpages/blog-list/index.tsx +129 -0
  301. package/app-template/src/widgets/footer-app-banner.tsx +444 -0
  302. package/app-template/src/widgets/footer-bottom.tsx +127 -0
  303. package/app-template/src/widgets/footer-menu-compact.tsx +238 -0
  304. package/app-template/src/widgets/footer-menu-two.tsx +298 -0
  305. package/app-template/src/widgets/footer-menu.tsx +6 -2
  306. package/app-template/src/widgets/footer-social-client.tsx +251 -0
  307. package/app-template/src/widgets/footer-social.tsx +47 -16
  308. package/app-template/src/widgets/footer-subscription/footer-subscription-form.tsx +17 -14
  309. package/app-template/src/widgets/footer-subscription/index.tsx +183 -17
  310. package/app-template/src/widgets/footer-value-props.tsx +201 -0
  311. package/app-template/src/widgets/home-stories-eng.tsx +42 -34
  312. package/app-template/src/widgets/index.ts +7 -0
  313. package/app-template/src/widgets/schemas/about-us.json +46 -0
  314. package/app-template/src/widgets/schemas/blog-list.json +37 -0
  315. package/app-template/src/widgets/schemas/blog.json +29 -0
  316. package/app-template/tailwind.config.js +19 -7
  317. package/app-template/tsconfig.json +29 -11
  318. package/codemods/migrate-segments/index.js +591 -0
  319. package/codemods/sentry-9/index.js +30 -0
  320. package/codemods/sentry-9/remove-sentry-configs.js +14 -0
  321. package/codemods/sentry-9/remove-sentry-dependency.js +25 -0
  322. package/codemods/sentry-9/replace-error-page.js +32 -0
  323. package/codemods/update-tailwind-config/index.js +30 -0
  324. package/codemods/update-tailwind-config/transform.js +102 -0
  325. package/commands/codemod.ts +17 -0
  326. package/commands/index.ts +3 -1
  327. package/commands/plugins.ts +115 -46
  328. package/dist/codemods/sentry-9/templates/error.js +14 -0
  329. package/dist/commands/codemod.js +15 -0
  330. package/dist/commands/index.js +3 -1
  331. package/dist/commands/plugins.js +85 -34
  332. package/package.json +3 -2
  333. package/app-template/postcss.config.js +0 -6
  334. package/app-template/sentry.client.config.ts +0 -16
  335. package/app-template/sentry.edge.config.ts +0 -3
  336. package/app-template/sentry.properties +0 -4
  337. package/app-template/sentry.server.config.ts +0 -3
  338. package/app-template/src/app/[commerce]/[locale]/[currency]/category/[pk]/page.tsx +0 -22
  339. package/app-template/src/app/[commerce]/[locale]/[currency]/error.tsx +0 -20
  340. package/app-template/src/app/[commerce]/[locale]/[currency]/flat-page/[pk]/page.tsx +0 -20
  341. package/app-template/src/app/[commerce]/[locale]/[currency]/group-product/[pk]/page.tsx +0 -74
  342. package/app-template/src/app/[commerce]/[locale]/[currency]/product/[pk]/loading.tsx +0 -67
  343. package/app-template/src/app/[commerce]/[locale]/[currency]/product/[pk]/page.tsx +0 -84
  344. package/app-template/src/app/[commerce]/[locale]/[currency]/special-page/[pk]/page.tsx +0 -27
  345. package/app-template/src/app/[commerce]/[locale]/[currency]/xml-sitemap/[node]/route.ts +0 -25
  346. package/app-template/src/pages/api/auth/[...nextauth].ts +0 -3
  347. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/address/page.tsx +0 -0
  348. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/change-email/page.tsx +0 -0
  349. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/change-password/page.tsx +0 -0
  350. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/contact/page.tsx +0 -0
  351. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/coupons/page.tsx +0 -0
  352. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/email-verification/page.tsx +0 -0
  353. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/faq/page.tsx +0 -0
  354. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/favourite-products/page.tsx +0 -0
  355. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/my-quotations/page.tsx +0 -0
  356. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/[id]/layout.tsx +0 -0
  357. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/page.tsx +0 -0
  358. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/page.tsx +0 -0
  359. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/address/stores/page.tsx +0 -0
  360. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/anonymous-tracking/page.tsx +0 -0
  361. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/auth/oauth-login/page.tsx +0 -0
  362. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/auth/page.tsx +0 -0
  363. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/basket/page.tsx +0 -0
  364. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/basket-b2b/page.tsx +0 -0
  365. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/category/[pk]/loading.tsx +0 -0
  366. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/client-root.tsx +0 -0
  367. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/contact-us/page.tsx +0 -0
  368. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/flat-page/[pk]/loading.tsx +0 -0
  369. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/group-product/[pk]/loading.tsx +0 -0
  370. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/landing-page/[pk]/loading.tsx +0 -0
  371. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/list/loading.tsx +0 -0
  372. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/orders/completed/[token]/layout.tsx +0 -0
  373. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/page.tsx +0 -0
  374. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/special-page/[pk]/loading.tsx +0 -0
  375. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/template.tsx +0 -0
  376. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/password/reset/page.tsx +0 -0
  377. /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/xml-sitemap/route.ts +0 -0
@@ -4,8 +4,15 @@ import {
4
4
  } from '@akinon/next/data/client/basket';
5
5
  import { useAppDispatch } from '@akinon/next/redux/hooks';
6
6
  import { BasketItem as BasketItemType } from '@akinon/next/types';
7
- import { Price, Button, Icon, Modal, Select, Link } from '@theme/components';
8
- import { useState } from 'react';
7
+ import {
8
+ Price,
9
+ Button,
10
+ Icon,
11
+ LoaderSpinner,
12
+ Modal,
13
+ Link
14
+ } from '@theme/components';
15
+ import { ComponentProps, useState } from 'react';
9
16
  import { useAddFavoriteMutation } from '@akinon/next/data/client/wishlist';
10
17
  import {
11
18
  useCommonProductAttributes,
@@ -13,14 +20,89 @@ import {
13
20
  } from '@akinon/next/hooks';
14
21
  import PluginModule, { Component } from '@akinon/next/components/plugin-module';
15
22
  import { Image } from '@akinon/next/components/image';
16
- import clsx from 'clsx';
17
23
  import { pushRemoveFromCart } from '@theme/utils/gtm';
24
+ import { WithDesignerFeatures } from '@akinon/next/components/theme-editor/components/with-designer-features';
25
+ import {
26
+ BASKET_ITEM_BLOCKS,
27
+ BASKET_ITEMS_SECTION_ID,
28
+ BASKET_PLACEHOLDER_ID,
29
+ useBasketDesigner
30
+ } from './designer-context';
18
31
 
19
32
  interface Props {
20
33
  basketItem?: BasketItemType;
21
34
  namespace?: string;
22
35
  }
23
36
 
37
+ const convertStylesToCSS = (
38
+ styles: Record<string, unknown> | undefined
39
+ ): React.CSSProperties => {
40
+ if (!styles) return {};
41
+
42
+ const cssStyles: React.CSSProperties = {};
43
+
44
+ Object.entries(styles).forEach(([key, value]) => {
45
+ const rawValue =
46
+ typeof value === 'object' && value !== null && 'desktop' in value
47
+ ? (value as Record<string, unknown>).desktop
48
+ : value;
49
+
50
+ const cssValue =
51
+ typeof rawValue === 'number'
52
+ ? rawValue
53
+ : typeof rawValue === 'string'
54
+ ? rawValue
55
+ : '';
56
+
57
+ if (cssValue !== '' && cssValue !== null && cssValue !== undefined) {
58
+ const camelKey = key.replace(/-([a-z])/g, (_, letter) =>
59
+ letter.toUpperCase()
60
+ );
61
+ (cssStyles as Record<string, unknown>)[camelKey] = cssValue;
62
+ }
63
+ });
64
+
65
+ return cssStyles;
66
+ };
67
+
68
+ const pickStyles = (
69
+ styles: React.CSSProperties,
70
+ keys: Array<keyof React.CSSProperties>
71
+ ): React.CSSProperties => {
72
+ const picked: React.CSSProperties = {};
73
+
74
+ keys.forEach((key) => {
75
+ const value = styles[key];
76
+
77
+ if (value !== undefined && value !== null && value !== '') {
78
+ (picked as Record<string, unknown>)[key as string] = value;
79
+ }
80
+ });
81
+
82
+ return picked;
83
+ };
84
+
85
+ const omitStyles = (
86
+ styles: React.CSSProperties,
87
+ keys: Array<keyof React.CSSProperties>
88
+ ): React.CSSProperties => {
89
+ const omitted = { ...styles };
90
+
91
+ keys.forEach((key) => {
92
+ delete omitted[key];
93
+ });
94
+
95
+ return omitted;
96
+ };
97
+
98
+ const getResponsiveValue = (value: unknown) => {
99
+ if (typeof value === 'object' && value !== null && 'desktop' in value) {
100
+ return (value as Record<string, unknown>).desktop;
101
+ }
102
+
103
+ return value;
104
+ };
105
+
24
106
  export const BasketItem = (props: Props) => {
25
107
  const { t } = useLocalization();
26
108
  const { basketItem, namespace } = props;
@@ -29,17 +111,235 @@ export const BasketItem = (props: Props) => {
29
111
  const [isRemoveBasketModalOpen, setRemoveBasketModalOpen] = useState(false);
30
112
  const [addFavorite, { isLoading: addFavoriteLoading }] =
31
113
  useAddFavoriteMutation();
32
- const [updateQuantityLoading, setUpdateQuantityLoading] = useState(false);
33
114
  const commonProductAttributes = useCommonProductAttributes({
34
115
  attributes: basketItem.product.attributes_kwargs
35
116
  });
117
+ const [updateQuantityLoading, setUpdateQuantityLoading] = useState(false);
118
+ const { isDesigner, selectedBlockId, getBlockStyles, getBlockProperties } =
119
+ useBasketDesigner();
120
+ const rowStyles = convertStylesToCSS(
121
+ getBlockStyles(BASKET_ITEM_BLOCKS.ITEM_ROW.id)
122
+ );
123
+ const imageStyles = convertStylesToCSS(
124
+ getBlockStyles(BASKET_ITEM_BLOCKS.IMAGE.id)
125
+ );
126
+ const nameStyles = convertStylesToCSS(
127
+ getBlockStyles(BASKET_ITEM_BLOCKS.NAME.id)
128
+ );
129
+ const attributesStyles = convertStylesToCSS(
130
+ getBlockStyles(BASKET_ITEM_BLOCKS.ATTRIBUTES.id)
131
+ );
132
+ const attributeLabelStyles = convertStylesToCSS(
133
+ getBlockStyles(BASKET_ITEM_BLOCKS.ATTRIBUTE_LABEL.id)
134
+ );
135
+ const attributeValueStyles = convertStylesToCSS(
136
+ getBlockStyles(BASKET_ITEM_BLOCKS.ATTRIBUTE_VALUE.id)
137
+ );
138
+ const quantityStyles = convertStylesToCSS(
139
+ getBlockStyles(BASKET_ITEM_BLOCKS.QUANTITY.id)
140
+ );
141
+ const quantityWrapperStyles = convertStylesToCSS(
142
+ getBlockStyles(BASKET_ITEM_BLOCKS.QUANTITY_WRAPPER.id)
143
+ );
144
+ const quantityMinusButtonStyles = convertStylesToCSS(
145
+ getBlockStyles(BASKET_ITEM_BLOCKS.QUANTITY_MINUS_BUTTON.id)
146
+ );
147
+ const quantityMinusIconStyles = convertStylesToCSS(
148
+ getBlockStyles(BASKET_ITEM_BLOCKS.QUANTITY_MINUS_ICON.id)
149
+ );
150
+ const quantityValueStyles = convertStylesToCSS(
151
+ getBlockStyles(BASKET_ITEM_BLOCKS.QUANTITY_VALUE.id)
152
+ );
153
+ const quantityPlusButtonStyles = convertStylesToCSS(
154
+ getBlockStyles(BASKET_ITEM_BLOCKS.QUANTITY_PLUS_BUTTON.id)
155
+ );
156
+ const quantityPlusIconStyles = convertStylesToCSS(
157
+ getBlockStyles(BASKET_ITEM_BLOCKS.QUANTITY_PLUS_ICON.id)
158
+ );
159
+
160
+ const quantityMinusIconProps =
161
+ getBlockProperties(BASKET_ITEM_BLOCKS.QUANTITY_MINUS_ICON.id) || {};
162
+ const quantityPlusIconProps =
163
+ getBlockProperties(BASKET_ITEM_BLOCKS.QUANTITY_PLUS_ICON.id) || {};
164
+ const priceStyles = convertStylesToCSS(
165
+ getBlockStyles(BASKET_ITEM_BLOCKS.PRICE.id)
166
+ );
167
+ const priceTextBlockStyles = convertStylesToCSS(
168
+ getBlockStyles(BASKET_ITEM_BLOCKS.PRICE_TEXT.id)
169
+ );
170
+ const removeStyles = convertStylesToCSS(
171
+ getBlockStyles(BASKET_ITEM_BLOCKS.REMOVE.id)
172
+ );
173
+ const removeProperties = getBlockProperties(BASKET_ITEM_BLOCKS.REMOVE.id);
174
+ const removeIconRaw = getResponsiveValue(removeProperties?.icon);
175
+ const removeIconValue = removeIconRaw;
176
+ const hasCustomRemoveSvgIcon =
177
+ typeof removeIconValue === 'string' && removeIconValue.includes('<svg');
178
+ const removeIconName = (
179
+ typeof removeIconValue === 'string' ? removeIconValue : 'close'
180
+ ) as ComponentProps<typeof Icon>['name'];
181
+ const giftPackStyles = convertStylesToCSS(
182
+ getBlockStyles(BASKET_ITEM_BLOCKS.GIFT_PACK.id)
183
+ );
184
+
185
+ const imageWrapperKeys: Array<keyof React.CSSProperties> = [
186
+ 'width',
187
+ 'height',
188
+ 'marginRight',
189
+ 'marginLeft',
190
+ 'marginTop',
191
+ 'marginBottom',
192
+ 'alignSelf',
193
+ 'justifySelf',
194
+ 'flexGrow',
195
+ 'flexShrink'
196
+ ];
197
+ const priceWrapperKeys: Array<keyof React.CSSProperties> = [
198
+ 'gap',
199
+ 'marginRight',
200
+ 'marginLeft',
201
+ 'marginTop',
202
+ 'marginBottom',
203
+ 'alignSelf',
204
+ 'justifySelf',
205
+ 'justifyContent',
206
+ 'alignItems'
207
+ ];
208
+ const attributesWrapperKeys: Array<keyof React.CSSProperties> = [
209
+ 'gap',
210
+ 'marginRight',
211
+ 'marginLeft',
212
+ 'marginTop',
213
+ 'marginBottom',
214
+ 'alignSelf',
215
+ 'justifySelf',
216
+ 'justifyContent',
217
+ 'alignItems'
218
+ ];
219
+ const quantityContainerKeys: Array<keyof React.CSSProperties> = [
220
+ 'gap',
221
+ 'marginRight',
222
+ 'marginLeft',
223
+ 'marginTop',
224
+ 'marginBottom',
225
+ 'alignSelf',
226
+ 'justifySelf',
227
+ 'justifyContent',
228
+ 'alignItems'
229
+ ];
230
+
231
+ const imageWrapperStyles = pickStyles(imageStyles, imageWrapperKeys);
232
+ const imageElementStyles = {
233
+ ...omitStyles(imageStyles, [
234
+ 'marginRight',
235
+ 'marginLeft',
236
+ 'marginTop',
237
+ 'marginBottom',
238
+ 'alignSelf',
239
+ 'justifySelf',
240
+ 'flexGrow',
241
+ 'flexShrink'
242
+ ]),
243
+ width: imageStyles.width || undefined,
244
+ height: imageStyles.height || undefined
245
+ };
246
+ const priceWrapperStyles = pickStyles(priceStyles, priceWrapperKeys);
247
+ const priceTextStyles = priceTextBlockStyles;
248
+ const attributesWrapperStyles = pickStyles(
249
+ attributesStyles,
250
+ attributesWrapperKeys
251
+ );
252
+ const attributesTextStyles = omitStyles(
253
+ attributesStyles,
254
+ attributesWrapperKeys
255
+ );
256
+ const finalAttributeLabelStyles = {
257
+ ...attributesTextStyles,
258
+ ...attributeLabelStyles
259
+ };
260
+ const finalAttributeValueStyles = {
261
+ ...attributesTextStyles,
262
+ ...attributeValueStyles
263
+ };
264
+ const quantityContainerStyles = pickStyles(
265
+ quantityStyles,
266
+ quantityContainerKeys
267
+ );
268
+ const minusIconRaw = getResponsiveValue(quantityMinusIconProps.icon);
269
+ const plusIconRaw = getResponsiveValue(quantityPlusIconProps.icon);
270
+ const minusIcon = typeof minusIconRaw === 'string' ? minusIconRaw : 'minus';
271
+ const plusIcon = typeof plusIconRaw === 'string' ? plusIconRaw : 'plus';
272
+
273
+ const toNumber = (value: unknown, fallback = 12) => {
274
+ if (typeof value === 'number') return value;
275
+ if (typeof value === 'string') {
276
+ const parsed = parseInt(value, 10);
277
+ return Number.isNaN(parsed) ? fallback : parsed;
278
+ }
279
+
280
+ return fallback;
281
+ };
282
+
283
+ const minusIconSize = toNumber(
284
+ quantityMinusIconStyles.width ||
285
+ quantityMinusIconStyles.height ||
286
+ quantityMinusIconStyles.fontSize,
287
+ 12
288
+ );
289
+ const plusIconSize = toNumber(
290
+ quantityPlusIconStyles.width ||
291
+ quantityPlusIconStyles.height ||
292
+ quantityPlusIconStyles.fontSize,
293
+ 12
294
+ );
295
+
296
+ const isCustomMinusSvg =
297
+ typeof minusIcon === 'string' && minusIcon.includes('<svg');
298
+ const isCustomPlusSvg =
299
+ typeof plusIcon === 'string' && plusIcon.includes('<svg');
300
+
301
+ const handleDesignerClick = (event: React.MouseEvent) => {
302
+ if (isDesigner) {
303
+ event.preventDefault();
304
+ }
305
+ };
306
+
307
+ // Check if any instance of this block type is selected
308
+ const isBlockSelected = (
309
+ block: typeof BASKET_ITEM_BLOCKS[keyof typeof BASKET_ITEM_BLOCKS]
310
+ ) => {
311
+ if (selectedBlockId === block.id) return true;
312
+ // Match only numbered instances (e.g., basket-item-price-1), not sibling blocks
313
+ if (selectedBlockId?.match(new RegExp(`^${block.id}-\\d+$`))) return true;
314
+ return false;
315
+ };
316
+
317
+ // Block instance - uses base ID for styles (shared across all items)
318
+ const getBlockInstance = (
319
+ block: typeof BASKET_ITEM_BLOCKS[keyof typeof BASKET_ITEM_BLOCKS]
320
+ ) => {
321
+ // Always get styles from base block ID (shared across all items)
322
+ const styles = getBlockStyles(block.id);
323
+ return {
324
+ ...block,
325
+ // Use base ID so all items share the same block in theme editor
326
+ id: block.id,
327
+ styles
328
+ };
329
+ };
36
330
 
37
331
  const updateQuantity = async (
38
332
  productPk: number,
39
333
  quantity: number,
40
- attributes: object = {}
334
+ attributes: object = {},
335
+ namespace?: string
41
336
  ) => {
42
- const requestParams: any = {
337
+ const requestParams: {
338
+ product: number;
339
+ quantity: number;
340
+ attributes: object;
341
+ namespace?: string;
342
+ } = {
43
343
  product: productPk,
44
344
  quantity,
45
345
  attributes
@@ -49,9 +349,11 @@ export const BasketItem = (props: Props) => {
49
349
  requestParams.namespace = namespace;
50
350
  }
51
351
 
352
+ setUpdateQuantityLoading(true);
353
+
52
354
  await updateQuantityMutation(requestParams)
53
355
  .unwrap()
54
- .then((data) =>
356
+ .then((data) => {
55
357
  dispatch(
56
358
  basketApi.util.updateQueryData(
57
359
  'getBasket',
@@ -60,15 +362,34 @@ export const BasketItem = (props: Props) => {
60
362
  Object.assign(draftBasket, data.basket);
61
363
  }
62
364
  )
63
- )
64
- );
365
+ );
366
+ })
367
+ .catch((err) => {
368
+ const formattedError =
369
+ err?.data?.non_field_errors ||
370
+ Object.keys(err?.data || {}).map(
371
+ (key) => `${key}: ${err?.data[key].join(', ')}`
372
+ );
373
+
374
+ console.error('Error in operation:', formattedError);
375
+ })
376
+ .finally(() => {
377
+ setTimeout(() => {
378
+ setUpdateQuantityLoading(false);
379
+ }, 200);
380
+ });
65
381
  };
66
382
 
67
383
  const deleteProduct = async (productPk?: number) => {
68
384
  setUpdateQuantityLoading(true);
69
385
 
70
386
  try {
71
- await updateQuantity(basketItem.product.pk, 0, basketItem.attributes);
387
+ await updateQuantity(
388
+ basketItem.product.pk,
389
+ 0,
390
+ basketItem.attributes,
391
+ namespace
392
+ );
72
393
  pushRemoveFromCart(basketItem?.product);
73
394
 
74
395
  if (productPk) {
@@ -82,116 +403,385 @@ export const BasketItem = (props: Props) => {
82
403
  }
83
404
  };
84
405
 
406
+ const handleQuantityChange = (newQuantity: number) => {
407
+ if (newQuantity === 0) {
408
+ setRemoveBasketModalOpen(true);
409
+ } else {
410
+ updateQuantity(
411
+ basketItem.product.pk,
412
+ newQuantity,
413
+ basketItem.attributes,
414
+ namespace
415
+ );
416
+ }
417
+ };
418
+
419
+ const handleDecrease = () => {
420
+ if (isDesigner) return;
421
+ if (updateQuantityLoading) return;
422
+ handleQuantityChange(basketItem.quantity - 1);
423
+ };
424
+
425
+ const handleIncrease = () => {
426
+ if (isDesigner) return;
427
+ if (updateQuantityLoading) return;
428
+ if (basketItem.quantity >= 999) return;
429
+ handleQuantityChange(basketItem.quantity + 1);
430
+ };
431
+
85
432
  return (
86
433
  <>
87
- <li
88
- key={basketItem.id}
89
- className="flex border-b border-gray-200 py-3 relative"
90
- >
91
- <div className="w-20 lg:w-[105px] mr-4 shrink-0">
92
- <Link href={basketItem.product.absolute_url} passHref>
93
- <Image
94
- src={basketItem.product.productimage_set[0]?.image}
95
- alt={basketItem.product.name}
96
- width={80}
97
- height={128}
98
- className="md:hidden"
99
- />
100
-
101
- <Image
102
- src={basketItem.product.productimage_set[0]?.image}
103
- alt={basketItem.product.name}
104
- width={105}
105
- height={158}
106
- className="hidden md:block"
107
- />
108
- </Link>
109
- </div>
110
- <div className="w-full flex flex-col justify-between">
111
- <div className="flex h-full">
112
- <div className="flex flex-1 flex-col gap-3 sm:flex-row sm:gap-1">
113
- <div className="flex-1">
114
- <Link
115
- href={basketItem.product.absolute_url}
116
- data-testid="basket-product-name"
117
- passHref
118
- >
119
- <span className="text-xs">{basketItem.product.name}</span>
120
- </Link>
121
- <div className="flex flex-col gap-1">
122
- {commonProductAttributes.map((attribute, index) => (
123
- <span className="text-xs" key={index}>
124
- <span>{attribute.name}</span>:{' '}
125
- <span
126
- data-testid={`basket-item-${attribute.name.toLowerCase()}`}
434
+ <li key={basketItem.id} data-testid="basket-item">
435
+ <WithDesignerFeatures
436
+ block={getBlockInstance(BASKET_ITEM_BLOCKS.ITEM_ROW)}
437
+ placeholderId={BASKET_PLACEHOLDER_ID}
438
+ sectionId={BASKET_ITEMS_SECTION_ID}
439
+ isDesigner={isDesigner}
440
+ isSelected={isBlockSelected(BASKET_ITEM_BLOCKS.ITEM_ROW)}
441
+ className="flex border-b border-gray-200 py-3 relative flex-col gap-4 sm:flex-row"
442
+ style={rowStyles}
443
+ >
444
+ <WithDesignerFeatures
445
+ block={getBlockInstance(BASKET_ITEM_BLOCKS.IMAGE)}
446
+ placeholderId={BASKET_PLACEHOLDER_ID}
447
+ sectionId={BASKET_ITEMS_SECTION_ID}
448
+ isDesigner={isDesigner}
449
+ isSelected={isBlockSelected(BASKET_ITEM_BLOCKS.IMAGE)}
450
+ className="mr-4 shrink-0"
451
+ style={imageWrapperStyles}
452
+ >
453
+ <Link
454
+ href={basketItem.product.absolute_url}
455
+ passHref
456
+ onClick={handleDesignerClick}
457
+ >
458
+ <Image
459
+ src={basketItem.product.productimage_set[0]?.image}
460
+ alt={basketItem.product.name}
461
+ width={80}
462
+ height={128}
463
+ className="md:hidden"
464
+ style={imageElementStyles}
465
+ />
466
+
467
+ <Image
468
+ src={basketItem.product.productimage_set[0]?.image}
469
+ alt={basketItem.product.name}
470
+ width={105}
471
+ height={158}
472
+ className="hidden md:block"
473
+ style={imageElementStyles}
474
+ />
475
+ </Link>
476
+ </WithDesignerFeatures>
477
+ <div className="w-full flex flex-col justify-between">
478
+ <div className="flex h-full gap-4">
479
+ <div className="flex flex-1 flex-col gap-3 sm:flex-row sm:gap-4">
480
+ <div className="flex-1 space-y-2">
481
+ <WithDesignerFeatures
482
+ block={getBlockInstance(BASKET_ITEM_BLOCKS.NAME)}
483
+ placeholderId={BASKET_PLACEHOLDER_ID}
484
+ sectionId={BASKET_ITEMS_SECTION_ID}
485
+ isDesigner={isDesigner}
486
+ isSelected={isBlockSelected(BASKET_ITEM_BLOCKS.NAME)}
487
+ className="inline-block w-full text-xs"
488
+ style={nameStyles}
489
+ >
490
+ <Link
491
+ href={basketItem.product.absolute_url}
492
+ data-testid="basket-product-name"
493
+ passHref
494
+ onClick={handleDesignerClick}
495
+ >
496
+ <span>{basketItem.product.name}</span>
497
+ </Link>
498
+ </WithDesignerFeatures>
499
+ <WithDesignerFeatures
500
+ block={getBlockInstance(BASKET_ITEM_BLOCKS.ATTRIBUTES)}
501
+ placeholderId={BASKET_PLACEHOLDER_ID}
502
+ sectionId={BASKET_ITEMS_SECTION_ID}
503
+ isDesigner={isDesigner}
504
+ isSelected={isBlockSelected(BASKET_ITEM_BLOCKS.ATTRIBUTES)}
505
+ className="flex flex-col gap-1"
506
+ style={attributesWrapperStyles}
507
+ >
508
+ {commonProductAttributes.map((attribute, index) => (
509
+ <div
510
+ className="text-xs"
511
+ key={index}
512
+ style={attributesTextStyles}
127
513
  >
128
- {attribute.value}
129
- </span>
130
- </span>
131
- ))}
514
+ <WithDesignerFeatures
515
+ block={getBlockInstance(
516
+ BASKET_ITEM_BLOCKS.ATTRIBUTE_LABEL
517
+ )}
518
+ placeholderId={BASKET_PLACEHOLDER_ID}
519
+ sectionId={BASKET_ITEMS_SECTION_ID}
520
+ isDesigner={isDesigner}
521
+ isSelected={isBlockSelected(
522
+ BASKET_ITEM_BLOCKS.ATTRIBUTE_LABEL
523
+ )}
524
+ className="inline-block"
525
+ >
526
+ <span style={finalAttributeLabelStyles}>
527
+ {attribute.name}
528
+ </span>
529
+ </WithDesignerFeatures>
530
+ :{' '}
531
+ <WithDesignerFeatures
532
+ block={getBlockInstance(
533
+ BASKET_ITEM_BLOCKS.ATTRIBUTE_VALUE
534
+ )}
535
+ placeholderId={BASKET_PLACEHOLDER_ID}
536
+ sectionId={BASKET_ITEMS_SECTION_ID}
537
+ isDesigner={isDesigner}
538
+ isSelected={isBlockSelected(
539
+ BASKET_ITEM_BLOCKS.ATTRIBUTE_VALUE
540
+ )}
541
+ className="inline-block"
542
+ >
543
+ <span
544
+ style={finalAttributeValueStyles}
545
+ data-testid={`basket-item-${attribute.name.toLowerCase()}`}
546
+ >
547
+ {attribute.value}
548
+ </span>
549
+ </WithDesignerFeatures>
550
+ </div>
551
+ ))}
552
+ </WithDesignerFeatures>
132
553
  </div>
554
+ <WithDesignerFeatures
555
+ block={getBlockInstance(BASKET_ITEM_BLOCKS.QUANTITY)}
556
+ placeholderId={BASKET_PLACEHOLDER_ID}
557
+ sectionId={BASKET_ITEMS_SECTION_ID}
558
+ isDesigner={isDesigner}
559
+ isSelected={isBlockSelected(BASKET_ITEM_BLOCKS.QUANTITY)}
560
+ className="flex flex-col justify-center md:flex-row md:items-center"
561
+ style={quantityContainerStyles}
562
+ >
563
+ <WithDesignerFeatures
564
+ block={getBlockInstance(
565
+ BASKET_ITEM_BLOCKS.QUANTITY_WRAPPER
566
+ )}
567
+ placeholderId={BASKET_PLACEHOLDER_ID}
568
+ sectionId={BASKET_ITEMS_SECTION_ID}
569
+ isDesigner={isDesigner}
570
+ isSelected={isBlockSelected(
571
+ BASKET_ITEM_BLOCKS.QUANTITY_WRAPPER
572
+ )}
573
+ className="w-[138px] h-11 flex items-center justify-between border p-4"
574
+ style={quantityWrapperStyles}
575
+ >
576
+ <WithDesignerFeatures
577
+ block={getBlockInstance(
578
+ BASKET_ITEM_BLOCKS.QUANTITY_MINUS_BUTTON
579
+ )}
580
+ placeholderId={BASKET_PLACEHOLDER_ID}
581
+ sectionId={BASKET_ITEMS_SECTION_ID}
582
+ isDesigner={isDesigner}
583
+ isSelected={isBlockSelected(
584
+ BASKET_ITEM_BLOCKS.QUANTITY_MINUS_BUTTON
585
+ )}
586
+ className="inline-flex"
587
+ >
588
+ <Button
589
+ className="h-auto p-0 hover:bg-transparent hover:text-black"
590
+ appearance="ghost"
591
+ onClick={handleDecrease}
592
+ disabled={
593
+ updateQuantityLoading || basketItem.quantity <= 0
594
+ }
595
+ style={quantityMinusButtonStyles}
596
+ >
597
+ <WithDesignerFeatures
598
+ block={getBlockInstance(
599
+ BASKET_ITEM_BLOCKS.QUANTITY_MINUS_ICON
600
+ )}
601
+ placeholderId={BASKET_PLACEHOLDER_ID}
602
+ sectionId={BASKET_ITEMS_SECTION_ID}
603
+ isDesigner={isDesigner}
604
+ isSelected={isBlockSelected(
605
+ BASKET_ITEM_BLOCKS.QUANTITY_MINUS_ICON
606
+ )}
607
+ className="inline-flex"
608
+ style={quantityMinusIconStyles}
609
+ >
610
+ {isCustomMinusSvg ? (
611
+ <div
612
+ style={{
613
+ width: minusIconSize,
614
+ height: minusIconSize
615
+ }}
616
+ dangerouslySetInnerHTML={{ __html: minusIcon }}
617
+ />
618
+ ) : (
619
+ <Icon
620
+ name={
621
+ minusIcon as ComponentProps<typeof Icon>['name']
622
+ }
623
+ size={minusIconSize}
624
+ style={quantityMinusIconStyles}
625
+ />
626
+ )}
627
+ </WithDesignerFeatures>
628
+ </Button>
629
+ </WithDesignerFeatures>
630
+
631
+ <WithDesignerFeatures
632
+ block={getBlockInstance(
633
+ BASKET_ITEM_BLOCKS.QUANTITY_VALUE
634
+ )}
635
+ placeholderId={BASKET_PLACEHOLDER_ID}
636
+ sectionId={BASKET_ITEMS_SECTION_ID}
637
+ isDesigner={isDesigner}
638
+ isSelected={isBlockSelected(
639
+ BASKET_ITEM_BLOCKS.QUANTITY_VALUE
640
+ )}
641
+ className="inline-flex"
642
+ style={quantityValueStyles}
643
+ >
644
+ {updateQuantityLoading ? (
645
+ <LoaderSpinner className="w-4 h-4" />
646
+ ) : (
647
+ <span style={quantityValueStyles}>
648
+ {basketItem.quantity}
649
+ </span>
650
+ )}
651
+ </WithDesignerFeatures>
652
+
653
+ <WithDesignerFeatures
654
+ block={getBlockInstance(
655
+ BASKET_ITEM_BLOCKS.QUANTITY_PLUS_BUTTON
656
+ )}
657
+ placeholderId={BASKET_PLACEHOLDER_ID}
658
+ sectionId={BASKET_ITEMS_SECTION_ID}
659
+ isDesigner={isDesigner}
660
+ isSelected={isBlockSelected(
661
+ BASKET_ITEM_BLOCKS.QUANTITY_PLUS_BUTTON
662
+ )}
663
+ className="inline-flex"
664
+ >
665
+ <Button
666
+ className="h-auto p-0 hover:bg-transparent hover:text-black"
667
+ appearance="ghost"
668
+ onClick={handleIncrease}
669
+ disabled={
670
+ updateQuantityLoading || basketItem.quantity >= 999
671
+ }
672
+ style={quantityPlusButtonStyles}
673
+ >
674
+ <WithDesignerFeatures
675
+ block={getBlockInstance(
676
+ BASKET_ITEM_BLOCKS.QUANTITY_PLUS_ICON
677
+ )}
678
+ placeholderId={BASKET_PLACEHOLDER_ID}
679
+ sectionId={BASKET_ITEMS_SECTION_ID}
680
+ isDesigner={isDesigner}
681
+ isSelected={isBlockSelected(
682
+ BASKET_ITEM_BLOCKS.QUANTITY_PLUS_ICON
683
+ )}
684
+ className="inline-flex"
685
+ style={quantityPlusIconStyles}
686
+ >
687
+ {isCustomPlusSvg ? (
688
+ <div
689
+ style={{
690
+ width: plusIconSize,
691
+ height: plusIconSize
692
+ }}
693
+ dangerouslySetInnerHTML={{ __html: plusIcon }}
694
+ />
695
+ ) : (
696
+ <Icon
697
+ name={
698
+ plusIcon as ComponentProps<typeof Icon>['name']
699
+ }
700
+ size={plusIconSize}
701
+ style={quantityPlusIconStyles}
702
+ />
703
+ )}
704
+ </WithDesignerFeatures>
705
+ </Button>
706
+ </WithDesignerFeatures>
707
+ </WithDesignerFeatures>
708
+ </WithDesignerFeatures>
709
+ <WithDesignerFeatures
710
+ block={getBlockInstance(BASKET_ITEM_BLOCKS.PRICE)}
711
+ placeholderId={BASKET_PLACEHOLDER_ID}
712
+ sectionId={BASKET_ITEMS_SECTION_ID}
713
+ isDesigner={isDesigner}
714
+ isSelected={isBlockSelected(BASKET_ITEM_BLOCKS.PRICE)}
715
+ className="flex flex-col shrink-0 text-sm gap-2 items-start justify-center lg:flex-row lg:mr-6 lg:gap-6 sm:items-center lg:justify-start"
716
+ style={priceWrapperStyles}
717
+ >
718
+ <WithDesignerFeatures
719
+ block={getBlockInstance(BASKET_ITEM_BLOCKS.PRICE_TEXT)}
720
+ placeholderId={BASKET_PLACEHOLDER_ID}
721
+ sectionId={BASKET_ITEMS_SECTION_ID}
722
+ isDesigner={isDesigner}
723
+ isSelected={isBlockSelected(BASKET_ITEM_BLOCKS.PRICE_TEXT)}
724
+ className="inline-block"
725
+ >
726
+ <Price
727
+ value={basketItem.product.price}
728
+ data-testid="basket-product-price"
729
+ style={priceTextStyles}
730
+ />
731
+ </WithDesignerFeatures>
732
+ </WithDesignerFeatures>
133
733
  </div>
134
- <div className="flex flex-col justify-center md:flex-row md:items-center lg:w-52">
135
- <Select
136
- className="px-2"
137
- defaultValue={basketItem.quantity}
138
- onChange={(event) => {
139
- updateQuantity(
140
- basketItem.product.pk,
141
- Number(event.currentTarget.value)
142
- );
143
- }}
144
- options={[
145
- ...Array.from({ length: 10 }, (_, i) => i + 1),
146
- basketItem.quantity > 10 && basketItem.quantity
147
- ]
148
- .filter((i) => i)
149
- .map((i) => ({
150
- label: `${t('basket.card.qty')} ${i}`,
151
- value: `${i}`
152
- }))}
153
- data-testid="basket-product-quantity"
154
- ></Select>
155
- </div>
156
- <div className="flex flex-col shrink-0 text-sm gap-2 items-start justify-center w-48 lg:flex-row lg:mr-6 lg:gap-6 sm:items-center lg:justify-start">
157
- {parseFloat(basketItem.product.retail_price) >
158
- parseFloat(basketItem.product.price) && (
159
- <Price
160
- className="line-through"
161
- value={basketItem.product.retail_price}
734
+ <WithDesignerFeatures
735
+ block={getBlockInstance(BASKET_ITEM_BLOCKS.REMOVE)}
736
+ placeholderId={BASKET_PLACEHOLDER_ID}
737
+ sectionId={BASKET_ITEMS_SECTION_ID}
738
+ isDesigner={isDesigner}
739
+ isSelected={isBlockSelected(BASKET_ITEM_BLOCKS.REMOVE)}
740
+ className="self-center"
741
+ style={removeStyles}
742
+ >
743
+ {hasCustomRemoveSvgIcon ? (
744
+ <div
745
+ className="cursor-pointer"
746
+ style={{ width: 16, height: 16, ...removeStyles }}
747
+ dangerouslySetInnerHTML={{ __html: removeIconValue }}
748
+ onClick={() => setRemoveBasketModalOpen(true)}
749
+ data-testid="basket-product-remove"
750
+ />
751
+ ) : (
752
+ <Icon
753
+ name={removeIconName}
754
+ size={16}
755
+ className="cursor-pointer hover:fill-secondary-500"
756
+ style={removeStyles}
757
+ onClick={() => setRemoveBasketModalOpen(true)}
758
+ data-testid="basket-product-remove"
162
759
  />
163
760
  )}
164
- <Price
165
- className={clsx(
166
- parseFloat(basketItem.product.retail_price) >
167
- parseFloat(basketItem.product.price)
168
- ? 'text-secondary-500'
169
- : 'text-primary'
170
- )}
171
- value={basketItem.product.price}
172
- data-testid="basket-product-price"
173
- />
174
- </div>
761
+ </WithDesignerFeatures>
175
762
  </div>
176
- <Icon
177
- name="close"
178
- size={16}
179
- className="self-center cursor-pointer hover:fill-secondary-500" // TODO: Add hover color. Fill not working
180
- onClick={() => setRemoveBasketModalOpen(true)}
181
- data-testid="basket-product-remove"
182
- />
183
- </div>
184
763
 
185
- <PluginModule
186
- component={Component.BasketGiftPack}
187
- props={{ basketItem }}
188
- />
189
- </div>
764
+ <WithDesignerFeatures
765
+ block={getBlockInstance(BASKET_ITEM_BLOCKS.GIFT_PACK)}
766
+ placeholderId={BASKET_PLACEHOLDER_ID}
767
+ sectionId={BASKET_ITEMS_SECTION_ID}
768
+ isDesigner={isDesigner}
769
+ isSelected={isBlockSelected(BASKET_ITEM_BLOCKS.GIFT_PACK)}
770
+ className="mt-3"
771
+ style={giftPackStyles}
772
+ >
773
+ <PluginModule
774
+ component={Component.BasketGiftPack}
775
+ props={{ basketItem }}
776
+ />
777
+ </WithDesignerFeatures>
778
+ </div>
779
+ </WithDesignerFeatures>
190
780
  </li>
191
781
  <Modal
192
782
  portalId="remove-basket-item"
193
783
  title={t('basket.card.modal.title')}
194
- className="w-full sm:w-[28rem] max-h-[90vh] overflow-y-auto"
784
+ className="w-full md:w-[28rem] max-h-[90vh] overflow-y-auto"
195
785
  open={isRemoveBasketModalOpen}
196
786
  setOpen={setRemoveBasketModalOpen}
197
787
  >