@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
@@ -1,7 +1,8 @@
1
1
  'use client';
2
2
 
3
- import { ReactNode, useMemo, useRef, useCallback } from 'react';
4
- import { useGetBasketQuery } from '@akinon/next/data/client/basket';
3
+ import { ReactNode, useMemo, useRef, useCallback, CSSProperties } from 'react';
4
+ import { useRouter } from '@akinon/next/hooks';
5
+ import { useGetMiniBasketQuery } from '@akinon/next/data/client/basket';
5
6
  import { useAppDispatch, useAppSelector } from '@akinon/next/redux/hooks';
6
7
  import {
7
8
  closeMiniBasket,
@@ -14,8 +15,104 @@ import { ROUTES } from '@theme/routes';
14
15
  import MiniBasket from './mini-basket';
15
16
  import { Badge, Icon, Link } from '@theme/components';
16
17
  import { useOnClickOutside } from '@akinon/next/hooks';
18
+ import { useSession } from 'next-auth/react';
19
+ import { useHeaderIconSettings } from './use-header-icons';
20
+ import { useDesignerFeatures } from '@akinon/next/components/theme-editor/hooks/use-designer-features';
21
+ import {
22
+ HeaderMiniBasketProvider,
23
+ useHeaderMiniBasket
24
+ } from './header-mini-basket-context';
25
+ import {
26
+ HeaderIconsProvider,
27
+ useHeaderIconsDesigner,
28
+ HEADER_ICONS_PLACEHOLDER_ID,
29
+ HEADER_ICONS_SECTION_ID,
30
+ HEADER_ICON_BLOCKS
31
+ } from './header-icons-context';
32
+
33
+ /**
34
+ * Convert block styles to CSS properties
35
+ */
36
+ function convertBlockStylesToCSS(
37
+ styles: Record<string, unknown> | undefined
38
+ ): CSSProperties {
39
+ if (!styles) return {};
40
+
41
+ const result: Record<string, string | number> = {};
42
+
43
+ const styleMap: Record<string, string> = {
44
+ 'margin-top': 'marginTop',
45
+ 'margin-right': 'marginRight',
46
+ 'margin-bottom': 'marginBottom',
47
+ 'margin-left': 'marginLeft',
48
+ 'background-color': 'backgroundColor'
49
+ };
50
+
51
+ Object.entries(styles).forEach(([key, value]) => {
52
+ const cssKey = styleMap[key] || key;
53
+ // Extract desktop value if responsive
54
+ if (value && typeof value === 'object' && 'desktop' in value) {
55
+ result[cssKey] = (value as Record<string, string>).desktop;
56
+ } else if (typeof value === 'string' || typeof value === 'number') {
57
+ result[cssKey] = value;
58
+ }
59
+ });
60
+
61
+ return result as CSSProperties;
62
+ }
63
+ import { HeaderIconSettings } from './server-settings-parser';
64
+ import { useHeaderLayout } from './header-layout-registrar';
65
+
66
+ // Inline selectable wrapper for header icons - doesn't add extra wrapper div
67
+ function SelectableIcon({
68
+ blockId,
69
+ blockType,
70
+ blockLabel,
71
+ isDesigner,
72
+ isSelected,
73
+ children
74
+ }: {
75
+ blockId: string;
76
+ blockType: string;
77
+ blockLabel: string;
78
+ isDesigner: boolean;
79
+ isSelected: boolean;
80
+ children: React.ReactElement;
81
+ }) {
82
+ const { handleClick } = useDesignerFeatures({
83
+ blockId,
84
+ placeholderId: HEADER_ICONS_PLACEHOLDER_ID,
85
+ sectionId: HEADER_ICONS_SECTION_ID,
86
+ isDesigner,
87
+ blockInfo: {
88
+ id: blockId,
89
+ type: blockType,
90
+ label: blockLabel
91
+ }
92
+ });
93
+
94
+ if (!isDesigner) {
95
+ return children;
96
+ }
97
+
98
+ // Clone child and inject onClick and selection styles
99
+ return (
100
+ <span
101
+ data-block-id={blockId}
102
+ onClick={handleClick}
103
+ className={clsx(
104
+ 'relative cursor-pointer',
105
+ isSelected && 'ring-2 ring-blue-500 ring-offset-1 rounded'
106
+ )}
107
+ style={{ display: 'inline-flex', alignItems: 'center' }}
108
+ >
109
+ {children}
110
+ </span>
111
+ );
112
+ }
17
113
 
18
114
  interface MenuItem {
115
+ id: 'search' | 'profile' | 'cart';
19
116
  label: string;
20
117
  url?: string;
21
118
  action?: () => void;
@@ -24,13 +121,55 @@ interface MenuItem {
24
121
  badge?: ReactNode;
25
122
  miniBasket?: ReactNode;
26
123
  dataTestId?: string;
124
+ blockId: string;
27
125
  }
28
126
 
29
- export default function ActionMenu() {
127
+ interface ActionMenuContentProps {
128
+ initialIconSettings?: HeaderIconSettings;
129
+ }
130
+
131
+ /**
132
+ * Inner ActionMenu component that uses the designer context
133
+ */
134
+ function ActionMenuContent({ initialIconSettings }: ActionMenuContentProps) {
135
+ const router = useRouter();
30
136
  const dispatch = useAppDispatch();
137
+ const { status } = useSession();
138
+ const iconSettings = useHeaderIconSettings(initialIconSettings);
139
+ const { isDesigner, selectedBlockId, getBlockStyles, getBlockProperties } =
140
+ useHeaderIconsDesigner();
141
+ const { layout } = useHeaderLayout();
142
+ const { sectionProperties } = useHeaderMiniBasket();
143
+
144
+ // Extract visibility value from section properties (handles responsive format)
145
+ const extractVisibility = useCallback((visibilityProp: unknown): string => {
146
+ if (!visibilityProp) return 'show';
147
+ if (typeof visibilityProp === 'string') return visibilityProp;
148
+ if (typeof visibilityProp === 'object' && visibilityProp !== null) {
149
+ const obj = visibilityProp as Record<string, string>;
150
+ return obj.desktop || obj.mobile || Object.values(obj)[0] || 'show';
151
+ }
152
+ return 'show';
153
+ }, []);
154
+
155
+ // Check if mini basket should be shown based on theme editor settings
156
+ const shouldShowMiniBasket = useMemo(() => {
157
+ const miniBasketVisibility = extractVisibility(
158
+ sectionProperties?.visibility
159
+ );
160
+ const shouldShow = miniBasketVisibility === 'show';
31
161
 
32
- const { data } = useGetBasketQuery();
33
- const totalQuantity = useMemo(() => data?.total_quantity ?? 0, [data]);
162
+ return shouldShow;
163
+ }, [sectionProperties, extractVisibility]);
164
+
165
+ // Hide search icon when inline search is visible (two-row layout)
166
+ const hideSearchIcon = layout === 'two-row';
167
+
168
+ const { data: miniBasket } = useGetMiniBasketQuery();
169
+ const totalQuantity = useMemo(
170
+ () => miniBasket?.total_quantity ?? 0,
171
+ [miniBasket]
172
+ );
34
173
 
35
174
  const { open: miniBasketOpen } = useAppSelector(
36
175
  (state) => state.root.miniBasket
@@ -43,75 +182,171 @@ export default function ActionMenu() {
43
182
 
44
183
  const MenuItems: MenuItem[] = [
45
184
  {
185
+ id: 'search',
186
+ blockId: HEADER_ICON_BLOCKS.SEARCH.id,
46
187
  label: 'Search',
47
188
  action: () => {
48
- dispatch(openSearch());
189
+ if (!isDesigner) dispatch(openSearch());
49
190
  },
50
191
  icon: 'search',
51
- className: 'sm:hidden',
52
192
  dataTestId: 'header-search'
53
193
  },
54
194
  {
55
- label: 'Favourite Products',
56
- url: ROUTES.ACCOUNT_WISHLIST,
57
- icon: 'heart-stroke',
58
- dataTestId: 'header-favourite'
195
+ id: 'profile',
196
+ blockId: HEADER_ICON_BLOCKS.PROFILE.id,
197
+ label: 'Profile',
198
+ url: `${status === 'authenticated' ? ROUTES.ACCOUNT : ROUTES.AUTH}`,
199
+ icon: 'person',
200
+ dataTestId: 'header-profile'
59
201
  },
60
202
  {
203
+ id: 'cart',
204
+ blockId: HEADER_ICON_BLOCKS.CART.id,
61
205
  label: 'Basket',
62
- action() {
63
- dispatch(toggleMiniBasket());
206
+ action: () => {
207
+ if (shouldShowMiniBasket) {
208
+ dispatch(toggleMiniBasket());
209
+ } else if (!isDesigner) {
210
+ // Only navigate to basket page when not in designer mode
211
+ router.push(ROUTES.BASKET);
212
+ }
64
213
  },
65
214
  icon: 'cart',
66
215
  dataTestId: 'header-basket',
216
+ className: 'pl-2.5',
67
217
  badge: (
68
218
  <Badge
69
219
  className={clsx(
70
- 'w-4',
71
- totalQuantity === 0
72
- ? 'bg-primary text-gray-500'
73
- : 'bg-secondary-500 text-white'
220
+ 'w-4 bg-primary text-white -bottom-1 -right-1 top-auto'
74
221
  )}
75
222
  >
76
- {totalQuantity}
223
+ <span data-testid="header-basket-count">{totalQuantity}</span>
77
224
  </Badge>
78
225
  ),
79
- miniBasket: <MiniBasket />
226
+ miniBasket: shouldShowMiniBasket ? <MiniBasket /> : null
80
227
  }
81
228
  ];
82
229
 
230
+ const renderIconContent = (
231
+ menu: MenuItem,
232
+ settings: { size: number; color: string }
233
+ ) => {
234
+ // Get custom icon from block properties
235
+ const blockProps = getBlockProperties(menu.blockId);
236
+ const customIcon = blockProps?.icon as
237
+ | string
238
+ | { desktop?: string }
239
+ | undefined;
240
+
241
+ // Handle responsive object format
242
+ const iconValue =
243
+ typeof customIcon === 'object' && customIcon !== null
244
+ ? customIcon.desktop
245
+ : customIcon;
246
+
247
+ // Check if we have a custom SVG icon
248
+ const hasCustomSvgIcon =
249
+ iconValue && typeof iconValue === 'string' && iconValue.includes('<svg');
250
+
251
+ return (
252
+ <>
253
+ {hasCustomSvgIcon ? (
254
+ <div
255
+ className="flex items-center justify-center"
256
+ style={{
257
+ width: settings.size,
258
+ height: settings.size,
259
+ color:
260
+ settings.color !== 'currentColor' ? settings.color : undefined
261
+ }}
262
+ dangerouslySetInnerHTML={{ __html: iconValue }}
263
+ />
264
+ ) : (
265
+ <Icon
266
+ name={menu.icon}
267
+ size={settings.size}
268
+ style={{
269
+ color:
270
+ settings.color !== 'currentColor' ? settings.color : undefined
271
+ }}
272
+ />
273
+ )}
274
+ {menu.badge}
275
+ </>
276
+ );
277
+ };
278
+
83
279
  return (
84
- <ul className="flex items-center space-x-3 lg:space-x-10">
85
- {MenuItems.map((menu, index) => (
86
- <li
87
- key={index}
88
- className={clsx('flex items-center relative', menu.className)}
89
- ref={menu.miniBasket ? miniBasketRef : null}
90
- >
91
- {menu.action ? (
92
- <button onClick={menu.action} data-testid={menu.dataTestId}>
93
- <Icon name={menu.icon} size={24} />
94
- {menu.badge}
95
- </button>
96
- ) : (
97
- <Link
98
- href={menu.url ?? '#'}
99
- passHref={true}
100
- onClick={(event) => {
101
- if (menu.action) {
102
- event.preventDefault();
103
- menu.action();
104
- }
105
- }}
106
- data-testid={menu.dataTestId}
280
+ <ul className="flex items-center">
281
+ {MenuItems.map((menu, index) => {
282
+ // Hide search icon when inline search input is visible
283
+ if (menu.id === 'search' && hideSearchIcon) {
284
+ return null;
285
+ }
286
+
287
+ const settings = iconSettings[menu.id];
288
+
289
+ const iconElement = menu.action ? (
290
+ <button onClick={menu.action} data-testid={menu.dataTestId}>
291
+ {renderIconContent(menu, settings)}
292
+ </button>
293
+ ) : (
294
+ <Link
295
+ href={isDesigner ? '#' : menu.url ?? '#'}
296
+ passHref={true}
297
+ onClick={(event) => {
298
+ if (isDesigner) {
299
+ event.preventDefault();
300
+ return;
301
+ }
302
+ }}
303
+ data-testid={menu.dataTestId}
304
+ >
305
+ {renderIconContent(menu, settings)}
306
+ </Link>
307
+ );
308
+
309
+ // Get block styles for margins
310
+ const blockStyles = getBlockStyles(menu.blockId);
311
+ const iconContainerStyles = convertBlockStylesToCSS(blockStyles);
312
+
313
+ return (
314
+ <li
315
+ key={index}
316
+ className={clsx('flex items-center relative', menu.className)}
317
+ style={iconContainerStyles}
318
+ ref={menu.miniBasket ? miniBasketRef : null}
319
+ >
320
+ <SelectableIcon
321
+ blockId={menu.blockId}
322
+ blockType="icon-button"
323
+ blockLabel={menu.label}
324
+ isDesigner={isDesigner}
325
+ isSelected={selectedBlockId === menu.blockId}
107
326
  >
108
- <Icon name={menu.icon} size={24} />
109
- {menu.badge}
110
- </Link>
111
- )}
112
- {menu.miniBasket}
113
- </li>
114
- ))}
327
+ {iconElement}
328
+ </SelectableIcon>
329
+ {menu.miniBasket}
330
+ </li>
331
+ );
332
+ })}
115
333
  </ul>
116
334
  );
117
335
  }
336
+
337
+ /**
338
+ * ActionMenu with HeaderIcons and HeaderMiniBasket Provider wrappers
339
+ */
340
+ interface ActionMenuProps {
341
+ initialIconSettings?: HeaderIconSettings;
342
+ }
343
+
344
+ export default function ActionMenu({ initialIconSettings }: ActionMenuProps) {
345
+ return (
346
+ <HeaderIconsProvider>
347
+ <HeaderMiniBasketProvider>
348
+ <ActionMenuContent initialIconSettings={initialIconSettings} />
349
+ </HeaderMiniBasketProvider>
350
+ </HeaderIconsProvider>
351
+ );
352
+ }
@@ -6,27 +6,12 @@ import ActionMenu from './action-menu';
6
6
  import HeaderBandText from '@theme/widgets/header-band-text';
7
7
  import { LanguageSelect } from '@theme/components';
8
8
  import { CurrencySelect } from 'components/currency-select';
9
+ import { HeaderIconSettings } from './server-settings-parser';
9
10
 
10
- export default function HeaderBand() {
11
- return (
12
- <div className="contents text-xs">
13
- <div className="hidden justify-start sm:inline-flex sm:gap-x-5 sm:header-grid-area-band-l">
14
- <LanguageSelect className="bg-transparent w-11 px-0 text-sm" />
15
- <CurrencySelect className="bg-transparent w-12 px-0 text-sm" />
16
- </div>
17
-
18
- <div className="header-grid-area-nav bg-gray-100 h-auto p-3 sm:header-grid-area-band-m sm:h-9 sm:py-0">
19
- <div className="text-center overflow-ellipsis line-clamp-2 h-full flex items-center justify-center">
20
- <HeaderBandText />
21
- </div>
22
- </div>
11
+ interface HeaderBandProps {
12
+ initialIconSettings?: HeaderIconSettings;
13
+ }
23
14
 
24
- <div className="header-grid-area-main-r h-full pr-4 sm:header-grid-area-band-r sm:pr-0">
25
- <div className="flex items-center justify-end h-full">
26
- <UserMenu isMobile={false} />
27
- <ActionMenu />
28
- </div>
29
- </div>
30
- </div>
31
- );
15
+ export default function HeaderBand({ initialIconSettings }: HeaderBandProps) {
16
+ return <ActionMenu initialIconSettings={initialIconSettings} />;
32
17
  }
@@ -0,0 +1,261 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Header Designer Context
5
+ *
6
+ * Provides a lightweight native-widget bridge so Theme Editor can
7
+ * select header blocks without rewriting the header as a widget.
8
+ */
9
+
10
+ import {
11
+ createContext,
12
+ PropsWithChildren,
13
+ useCallback,
14
+ useContext,
15
+ useEffect,
16
+ useRef,
17
+ useState
18
+ } from 'react';
19
+ import { useExternalDesigner } from '@akinon/next/components/theme-editor/hooks/use-external-designer';
20
+
21
+ type BlockDefinition = {
22
+ id: string;
23
+ type: string;
24
+ label: string;
25
+ properties?: Record<string, unknown>;
26
+ };
27
+
28
+ type ThemeBlock = {
29
+ id: string;
30
+ type: string;
31
+ label: string;
32
+ styles?: Record<string, unknown>;
33
+ properties?: Record<string, unknown>;
34
+ };
35
+
36
+ type ThemeSection = {
37
+ id: string;
38
+ blocks?: ThemeBlock[];
39
+ };
40
+
41
+ type ThemePlaceholder = {
42
+ slug: string;
43
+ sections?: ThemeSection[];
44
+ };
45
+
46
+ interface HeaderDesignerContextValue {
47
+ isDesigner: boolean;
48
+ selectedSectionId: string | null;
49
+ selectedBlockId: string | null;
50
+ getBlockStyles: (blockId: string) => Record<string, unknown> | undefined;
51
+ getBlockProperties: (blockId: string) => Record<string, unknown> | undefined;
52
+ }
53
+
54
+ export const HEADER_PLACEHOLDER_ID = 'header';
55
+ export const HEADER_SECTION_ID = 'header-structure';
56
+
57
+ // Block definitions used by WithDesignerFeatures wrappers
58
+ const headerBlocks = {
59
+ CONTAINER: {
60
+ id: 'header-container',
61
+ type: 'group',
62
+ label: 'Header Container'
63
+ },
64
+ LOGO: {
65
+ id: 'header-logo-image',
66
+ type: 'image',
67
+ label: 'Logo'
68
+ },
69
+ NAVBAR: {
70
+ id: 'header-navbar',
71
+ type: 'group',
72
+ label: 'Navigation Bar'
73
+ },
74
+ NAV_ITEM: {
75
+ id: 'header-nav-menu-item',
76
+ type: 'text',
77
+ label: 'Navigation Item'
78
+ },
79
+ SEARCH: {
80
+ id: 'header-icon-search',
81
+ type: 'icon-button',
82
+ label: 'Search Icon',
83
+ properties: {
84
+ icon: 'search',
85
+ iconSize: '20'
86
+ }
87
+ },
88
+ USER_MENU: {
89
+ id: 'header-icon-profile',
90
+ type: 'icon-button',
91
+ label: 'Profile Icon',
92
+ properties: {
93
+ icon: 'person',
94
+ iconSize: '20'
95
+ }
96
+ },
97
+ MINI_BASKET: {
98
+ id: 'header-icon-cart',
99
+ type: 'icon-button',
100
+ label: 'Cart Icon',
101
+ properties: {
102
+ icon: 'cart',
103
+ iconSize: '20'
104
+ }
105
+ },
106
+ MOBILE_MENU_BUTTON: {
107
+ id: 'header-mobile-menu-button',
108
+ type: 'icon-button',
109
+ label: 'Mobile Menu Button',
110
+ properties: {
111
+ icon: 'hamburger',
112
+ iconSize: '18'
113
+ }
114
+ }
115
+ } satisfies Record<string, BlockDefinition>;
116
+
117
+ export const HEADER_BLOCKS = headerBlocks;
118
+
119
+ type HeaderBlockDefinition =
120
+ (typeof HEADER_BLOCKS)[keyof typeof HEADER_BLOCKS];
121
+ type HeaderBlockId = HeaderBlockDefinition['id'];
122
+
123
+ const blockLookup = new Map<HeaderBlockId, BlockDefinition>(
124
+ Object.values(HEADER_BLOCKS).map((block) => [block.id, block as BlockDefinition])
125
+ );
126
+
127
+ const HeaderDesignerContext = createContext<HeaderDesignerContextValue>({
128
+ isDesigner: false,
129
+ selectedSectionId: null,
130
+ selectedBlockId: null,
131
+ getBlockStyles: () => undefined,
132
+ getBlockProperties: () => undefined
133
+ });
134
+
135
+ export function HeaderDesignerProvider({
136
+ children
137
+ }: PropsWithChildren) {
138
+ const designerState = useExternalDesigner({
139
+ placeholderId: HEADER_PLACEHOLDER_ID
140
+ });
141
+
142
+ const [themeBlocks, setThemeBlocks] = useState<Map<string, ThemeBlock>>(
143
+ new Map()
144
+ );
145
+ const themeBlocksRef = useRef<Map<string, ThemeBlock>>(new Map());
146
+
147
+ const registerNativeWidget = useCallback(() => {
148
+ if (typeof window === 'undefined') return;
149
+ const isInIframe = window.self !== window.top;
150
+ if (!isInIframe || !window.parent) return;
151
+
152
+ const sectionBlocks = Object.values(HEADER_BLOCKS).map((blockDef) => {
153
+ const block = blockDef as BlockDefinition;
154
+ const storedBlock = themeBlocksRef.current.get(block.id);
155
+ return {
156
+ id: block.id,
157
+ type: block.type,
158
+ label: block.label,
159
+ properties: storedBlock?.properties ?? block.properties ?? {},
160
+ styles: storedBlock?.styles ?? {}
161
+ };
162
+ });
163
+
164
+ window.parent.postMessage(
165
+ {
166
+ type: 'REGISTER_NATIVE_WIDGETS',
167
+ data: {
168
+ widgets: [
169
+ {
170
+ placeholderId: HEADER_PLACEHOLDER_ID,
171
+ section: {
172
+ id: HEADER_SECTION_ID,
173
+ type: 'native',
174
+ label: 'Header Layout',
175
+ blocks: sectionBlocks
176
+ }
177
+ }
178
+ ]
179
+ }
180
+ },
181
+ '*'
182
+ );
183
+ }, []);
184
+
185
+ useEffect(() => {
186
+ if (typeof window === 'undefined') return;
187
+ registerNativeWidget();
188
+ // eslint-disable-next-line react-hooks/exhaustive-deps
189
+ }, []); // Only register once
190
+
191
+ useEffect(() => {
192
+ if (typeof window === 'undefined') return;
193
+
194
+ const handleMessage = (event: MessageEvent) => {
195
+ const { type, data } = event.data || {};
196
+
197
+ if (
198
+ (type === 'UPDATE_THEME' || type === 'LOAD_THEME') &&
199
+ data?.theme?.placeholders
200
+ ) {
201
+ const placeholder = (data.theme.placeholders as ThemePlaceholder[]).find(
202
+ (p) => p.slug === HEADER_PLACEHOLDER_ID
203
+ );
204
+
205
+ const headerSection = placeholder?.sections?.find(
206
+ (section) => section.id === HEADER_SECTION_ID
207
+ );
208
+
209
+ if (headerSection?.blocks) {
210
+ const incoming = new Map<string, ThemeBlock>();
211
+ headerSection.blocks.forEach((block) => {
212
+ incoming.set(block.id, block);
213
+ });
214
+
215
+ const merged = new Map(themeBlocksRef.current);
216
+ incoming.forEach((block, id) => {
217
+ merged.set(id, block);
218
+ });
219
+
220
+ themeBlocksRef.current = merged;
221
+ setThemeBlocks(merged);
222
+ }
223
+ }
224
+ };
225
+
226
+ window.addEventListener('message', handleMessage);
227
+ return () => window.removeEventListener('message', handleMessage);
228
+ }, []);
229
+
230
+ const getBlockStyles = useCallback(
231
+ (blockId: string) => {
232
+ return themeBlocks.get(blockId)?.styles;
233
+ },
234
+ [themeBlocks]
235
+ );
236
+
237
+ const getBlockProperties = useCallback(
238
+ (blockId: string) => {
239
+ const block = themeBlocks.get(blockId);
240
+ if (block?.properties) {
241
+ return block.properties;
242
+ }
243
+ return blockLookup.get(blockId as HeaderBlockId)?.properties;
244
+ },
245
+ [themeBlocks]
246
+ );
247
+
248
+ return (
249
+ <HeaderDesignerContext.Provider
250
+ value={{
251
+ ...designerState,
252
+ getBlockStyles,
253
+ getBlockProperties
254
+ }}
255
+ >
256
+ {children}
257
+ </HeaderDesignerContext.Provider>
258
+ );
259
+ }
260
+
261
+ export const useHeaderDesigner = () => useContext(HeaderDesignerContext);