@akinon/projectzero 2.0.0-beta.19 → 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 (223) hide show
  1. package/CHANGELOG.md +9 -7
  2. package/app-template/CHANGELOG.md +251 -204
  3. package/app-template/akinon.json +1 -1
  4. package/app-template/package.json +28 -28
  5. package/app-template/public/amex.svg +12 -0
  6. package/app-template/public/apple-pay.svg +16 -0
  7. package/app-template/public/assets/images/product-placeholder-1.jpg +0 -0
  8. package/app-template/public/assets/images/product-placeholder-2.jpg +0 -0
  9. package/app-template/public/assets/images/product-placeholder-3.jpg +0 -0
  10. package/app-template/public/assets/images/product-placeholder-4.jpg +0 -0
  11. package/app-template/public/google-pay.svg +16 -0
  12. package/app-template/public/locales/en/account.json +6 -3
  13. package/app-template/public/locales/en/auth.json +6 -7
  14. package/app-template/public/locales/en/basket.json +6 -6
  15. package/app-template/public/locales/en/blog.json +7 -0
  16. package/app-template/public/locales/en/category.json +3 -1
  17. package/app-template/public/locales/en/checkout.json +5 -4
  18. package/app-template/public/locales/en/common.json +11 -2
  19. package/app-template/public/locales/en/forgot_password.json +6 -7
  20. package/app-template/public/locales/en/product.json +4 -3
  21. package/app-template/public/locales/tr/account.json +6 -3
  22. package/app-template/public/locales/tr/auth.json +16 -17
  23. package/app-template/public/locales/tr/basket.json +4 -4
  24. package/app-template/public/locales/tr/blog.json +7 -0
  25. package/app-template/public/locales/tr/category.json +3 -1
  26. package/app-template/public/locales/tr/checkout.json +39 -38
  27. package/app-template/public/locales/tr/common.json +10 -1
  28. package/app-template/public/locales/tr/forgot_password.json +12 -13
  29. package/app-template/public/locales/tr/product.json +1 -0
  30. package/app-template/public/logo.svg +3 -27
  31. package/app-template/public/mastercard.svg +14 -0
  32. package/app-template/public/promotion-banner.jpg +0 -0
  33. package/app-template/public/shop-pay.svg +12 -0
  34. package/app-template/public/visa.svg +12 -0
  35. package/app-template/src/app/[commerce]/[locale]/[currency]/blog/[slug]/page.tsx +118 -0
  36. package/app-template/src/app/[commerce]/[locale]/[currency]/pages/[slug]/page.tsx +15 -0
  37. package/app-template/src/app/api/theme-settings/route.ts +12 -0
  38. package/app-template/src/assets/fonts/pz-icon.css +211 -49
  39. package/app-template/src/assets/fonts/pz-icon.eot +0 -0
  40. package/app-template/src/assets/fonts/pz-icon.html +486 -0
  41. package/app-template/src/assets/fonts/pz-icon.scss +373 -49
  42. package/app-template/src/assets/fonts/pz-icon.svg +215 -53
  43. package/app-template/src/assets/fonts/pz-icon.ttf +0 -0
  44. package/app-template/src/assets/fonts/pz-icon.woff +0 -0
  45. package/app-template/src/assets/fonts/pz-icon.woff2 +0 -0
  46. package/app-template/src/assets/globals.scss +4 -0
  47. package/app-template/src/assets/icons/arrow-right.svg +3 -0
  48. package/app-template/src/assets/icons/cart.svg +4 -12
  49. package/app-template/src/assets/icons/check.svg +2 -18
  50. package/app-template/src/assets/icons/chevron-down.svg +2 -7
  51. package/app-template/src/assets/icons/delete.svg +3 -0
  52. package/app-template/src/assets/icons/facebook.svg +2 -8
  53. package/app-template/src/assets/icons/fav-off.svg +5 -0
  54. package/app-template/src/assets/icons/fav-on.svg +5 -0
  55. package/app-template/src/assets/icons/filter-and-sort.svg +3 -0
  56. package/app-template/src/assets/icons/heart.svg +3 -0
  57. package/app-template/src/assets/icons/instagram.svg +2 -13
  58. package/app-template/src/assets/icons/materials.svg +3 -0
  59. package/app-template/src/assets/icons/person.svg +4 -0
  60. package/app-template/src/assets/icons/pinterest.svg +5 -11
  61. package/app-template/src/assets/icons/ruler.svg +3 -0
  62. package/app-template/src/assets/icons/search.svg +8 -11
  63. package/app-template/src/assets/icons/share.svg +2 -9
  64. package/app-template/src/assets/icons/snapchat.svg +3 -0
  65. package/app-template/src/assets/icons/tiktok.svg +3 -0
  66. package/app-template/src/assets/icons/tumblr.svg +6 -0
  67. package/app-template/src/assets/icons/twitter.svg +2 -10
  68. package/app-template/src/assets/icons/vimeo.svg +3 -0
  69. package/app-template/src/assets/icons/youtube.svg +3 -0
  70. package/app-template/src/assets/icons/zoom.svg +8 -0
  71. package/app-template/src/components/accordion.tsx +33 -11
  72. package/app-template/src/components/action-tooltip.tsx +160 -0
  73. package/app-template/src/components/currency-select.tsx +149 -4
  74. package/app-template/src/components/icon.tsx +5 -6
  75. package/app-template/src/components/index.ts +4 -1
  76. package/app-template/src/components/language-select.tsx +88 -2
  77. package/app-template/src/components/pagination.tsx +132 -20
  78. package/app-template/src/components/quantity-input.tsx +63 -0
  79. package/app-template/src/components/quantity-selector.tsx +203 -0
  80. package/app-template/src/components/route-handler.tsx +50 -0
  81. package/app-template/src/components/select.tsx +89 -69
  82. package/app-template/src/components/types/index.ts +26 -0
  83. package/app-template/src/components/widget-content.tsx +323 -0
  84. package/app-template/src/data/server/theme.ts +70 -0
  85. package/app-template/src/hooks/use-fav-button.tsx +5 -2
  86. package/app-template/src/hooks/use-product-cart.ts +11 -8
  87. package/app-template/src/hooks/use-theme-settings.ts +42 -0
  88. package/app-template/src/lib/fonts.ts +149 -0
  89. package/app-template/src/settings.js +2 -2
  90. package/app-template/src/types/hookform-resolvers-yup.d.ts +28 -0
  91. package/app-template/src/types/widget.ts +169 -0
  92. package/app-template/src/utils/formatDate.ts +48 -0
  93. package/app-template/src/utils/styles.ts +71 -0
  94. package/app-template/src/views/account/contact-form.tsx +147 -130
  95. package/app-template/src/views/basket/basket-item.tsx +691 -107
  96. package/app-template/src/views/basket/basket-summary-context.tsx +560 -0
  97. package/app-template/src/views/basket/designer-context.tsx +617 -0
  98. package/app-template/src/views/basket/index.ts +2 -0
  99. package/app-template/src/views/basket/summary.tsx +496 -75
  100. package/app-template/src/views/breadcrumb/breadcrumb-client.tsx +190 -0
  101. package/app-template/src/views/breadcrumb/breadcrumb-registrar.tsx +286 -0
  102. package/app-template/src/views/breadcrumb/constants.ts +15 -0
  103. package/app-template/src/views/breadcrumb/index.tsx +127 -0
  104. package/app-template/src/views/breadcrumb.tsx +13 -38
  105. package/app-template/src/views/category/category-banner.tsx +4 -23
  106. package/app-template/src/views/category/category-header.tsx +289 -66
  107. package/app-template/src/views/category/category-info.tsx +173 -24
  108. package/app-template/src/views/category/filters/filter-item.tsx +138 -42
  109. package/app-template/src/views/category/filters/index.tsx +208 -48
  110. package/app-template/src/views/category/layout.tsx +7 -4
  111. package/app-template/src/views/category/native-widget-context.tsx +257 -0
  112. package/app-template/src/views/category/product-list-registrar.tsx +665 -0
  113. package/app-template/src/views/checkout/auth.tsx +64 -40
  114. package/app-template/src/views/checkout/checkout-address-registrar.tsx +254 -0
  115. package/app-template/src/views/checkout/checkout-buttons-registrar.tsx +183 -0
  116. package/app-template/src/views/checkout/checkout-delivery-method-registrar.tsx +259 -0
  117. package/app-template/src/views/checkout/checkout-payment-options-registrar.tsx +253 -0
  118. package/app-template/src/views/checkout/checkout-summary-registrar.tsx +183 -0
  119. package/app-template/src/views/checkout/constants.ts +5 -0
  120. package/app-template/src/views/checkout/index.tsx +5 -0
  121. package/app-template/src/views/checkout/layout/header.tsx +9 -5
  122. package/app-template/src/views/checkout/steps/payment/index.tsx +5 -2
  123. package/app-template/src/views/checkout/steps/payment/options/credit-card/index.tsx +72 -1
  124. package/app-template/src/views/checkout/steps/payment/options/masterpass-rest.tsx +15 -0
  125. package/app-template/src/views/checkout/steps/payment/options/saved-card.tsx +18 -0
  126. package/app-template/src/views/checkout/steps/payment/payment-option-buttons.tsx +171 -40
  127. package/app-template/src/views/checkout/steps/shipping/address-box.tsx +74 -12
  128. package/app-template/src/views/checkout/steps/shipping/addresses.tsx +128 -45
  129. package/app-template/src/views/checkout/steps/shipping/shipping-options.tsx +232 -27
  130. package/app-template/src/views/checkout/summary.tsx +303 -29
  131. package/app-template/src/views/footer/footer-app-banner-context.tsx +326 -0
  132. package/app-template/src/views/footer/footer-bottom-context.tsx +215 -0
  133. package/app-template/src/views/footer/footer-bottom-wrapper.tsx +74 -0
  134. package/app-template/src/views/footer/footer-layout-constants.ts +35 -0
  135. package/app-template/src/views/footer/footer-layout-registrar.tsx +342 -0
  136. package/app-template/src/views/footer/footer-layout-switcher.tsx +110 -0
  137. package/app-template/src/views/footer/footer-menu-context.tsx +211 -0
  138. package/app-template/src/views/footer/footer-native-widgets.tsx +60 -0
  139. package/app-template/src/views/footer/footer-social-context.tsx +254 -0
  140. package/app-template/src/views/footer/footer-subscription-context.tsx +210 -0
  141. package/app-template/src/views/footer/footer-utils.ts +43 -0
  142. package/app-template/src/views/footer/footer-value-props-context.tsx +326 -0
  143. package/app-template/src/views/footer/logo-settings.ts +183 -0
  144. package/app-template/src/views/footer/native-widget-config.ts +262 -0
  145. package/app-template/src/views/footer/subscription-settings.ts +122 -0
  146. package/app-template/src/views/footer/use-footer-logo.ts +162 -0
  147. package/app-template/src/views/footer.tsx +415 -13
  148. package/app-template/src/views/guest-login/index.tsx +62 -58
  149. package/app-template/src/views/header/action-menu.tsx +277 -45
  150. package/app-template/src/views/header/band.tsx +6 -21
  151. package/app-template/src/views/header/designer-context.tsx +261 -0
  152. package/app-template/src/views/header/header-announcement-registrar.tsx +267 -0
  153. package/app-template/src/views/header/header-client-wrapper.tsx +496 -0
  154. package/app-template/src/views/header/header-content.tsx +1026 -0
  155. package/app-template/src/views/header/header-currency-registrar.tsx +348 -0
  156. package/app-template/src/views/header/header-icons-context.tsx +262 -0
  157. package/app-template/src/views/header/header-language-registrar.tsx +348 -0
  158. package/app-template/src/views/header/header-layout-context.tsx +143 -0
  159. package/app-template/src/views/header/header-layout-registrar.tsx +658 -0
  160. package/app-template/src/views/header/header-logo-context.tsx +228 -0
  161. package/app-template/src/views/header/header-logo.tsx +118 -0
  162. package/app-template/src/views/header/header-mini-basket-context.tsx +524 -0
  163. package/app-template/src/views/header/header-search-registrar.tsx +511 -0
  164. package/app-template/src/views/header/header-text-slider-registrar.tsx +382 -0
  165. package/app-template/src/views/header/index.tsx +109 -47
  166. package/app-template/src/views/header/inline-search.tsx +262 -0
  167. package/app-template/src/views/header/mini-basket.tsx +819 -44
  168. package/app-template/src/views/header/mobile-hamburger-button.tsx +5 -8
  169. package/app-template/src/views/header/mobile-menu.tsx +12 -0
  170. package/app-template/src/views/header/navbar-menu-context.tsx +219 -0
  171. package/app-template/src/views/header/navbar.tsx +178 -111
  172. package/app-template/src/views/header/search/index.tsx +71 -32
  173. package/app-template/src/views/header/search/results.tsx +127 -65
  174. package/app-template/src/views/header/search/search-input.tsx +61 -0
  175. package/app-template/src/views/header/server-settings-parser.ts +1105 -0
  176. package/app-template/src/views/header/use-header-icons.ts +241 -0
  177. package/app-template/src/views/header/use-header-logo.ts +213 -0
  178. package/app-template/src/views/header/use-navbar-menu.ts +179 -0
  179. package/app-template/src/views/login/index.tsx +54 -46
  180. package/app-template/src/views/product/accordion-section.tsx +61 -0
  181. package/app-template/src/views/product/accordion-wrapper.tsx +135 -43
  182. package/app-template/src/views/product/custom-button-group.tsx +69 -0
  183. package/app-template/src/views/product/favorites-button-section.tsx +69 -0
  184. package/app-template/src/views/product/find-in-store-section.tsx +60 -0
  185. package/app-template/src/views/product/index.ts +1 -0
  186. package/app-template/src/views/product/layout.tsx +6 -5
  187. package/app-template/src/views/product/misc-buttons.tsx +339 -25
  188. package/app-template/src/views/product/price-wrapper.tsx +3 -29
  189. package/app-template/src/views/product/product-actions.tsx +137 -8
  190. package/app-template/src/views/product/product-info-section.tsx +140 -0
  191. package/app-template/src/views/product/product-info.tsx +69 -31
  192. package/app-template/src/views/product/product-share.tsx +13 -8
  193. package/app-template/src/views/product/product-variants.tsx +2 -2
  194. package/app-template/src/views/product/quantity-section.tsx +73 -0
  195. package/app-template/src/views/product/sale-tag.tsx +10 -0
  196. package/app-template/src/views/product/share-section.tsx +357 -0
  197. package/app-template/src/views/product/slider.tsx +117 -79
  198. package/app-template/src/views/product/variant.tsx +69 -41
  199. package/app-template/src/views/product/variants-section.tsx +126 -0
  200. package/app-template/src/views/product-detail/constants.ts +272 -0
  201. package/app-template/src/views/product-detail/index.ts +10 -0
  202. package/app-template/src/views/product-detail/product-detail-registrar.tsx +616 -0
  203. package/app-template/src/views/product-item/index.tsx +119 -46
  204. package/app-template/src/views/register/index.tsx +14 -25
  205. package/app-template/src/views/share/index.tsx +9 -6
  206. package/app-template/src/views/widgets/home-hero-slider-content.tsx +41 -39
  207. package/app-template/src/widgets/flatpages/about-us/index.tsx +78 -0
  208. package/app-template/src/widgets/flatpages/blog-list/index.tsx +129 -0
  209. package/app-template/src/widgets/footer-app-banner.tsx +444 -0
  210. package/app-template/src/widgets/footer-bottom.tsx +127 -0
  211. package/app-template/src/widgets/footer-menu-compact.tsx +238 -0
  212. package/app-template/src/widgets/footer-menu-two.tsx +298 -0
  213. package/app-template/src/widgets/footer-social-client.tsx +251 -0
  214. package/app-template/src/widgets/footer-social.tsx +47 -16
  215. package/app-template/src/widgets/footer-subscription/footer-subscription-form.tsx +17 -14
  216. package/app-template/src/widgets/footer-subscription/index.tsx +183 -17
  217. package/app-template/src/widgets/footer-value-props.tsx +201 -0
  218. package/app-template/src/widgets/index.ts +7 -0
  219. package/app-template/src/widgets/schemas/about-us.json +46 -0
  220. package/app-template/src/widgets/schemas/blog-list.json +37 -0
  221. package/app-template/src/widgets/schemas/blog.json +29 -0
  222. package/app-template/tailwind.config.js +18 -2
  223. package/package.json +1 -1
@@ -0,0 +1,560 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Basket Summary Designer Context
5
+ *
6
+ * Enables basket summary to be customized via Theme Editor.
7
+ * Uses the native widget pattern for live editing.
8
+ *
9
+ * @see .github/instructions/native-widget.instructions.md for implementation guide
10
+ */
11
+
12
+ import {
13
+ createContext,
14
+ PropsWithChildren,
15
+ useCallback,
16
+ useContext,
17
+ useEffect,
18
+ useMemo,
19
+ useRef,
20
+ useState
21
+ } from 'react';
22
+ import { useExternalDesigner } from '@akinon/next/components/theme-editor/hooks/use-external-designer';
23
+ import { useNativeWidgetData } from '@akinon/next/components/theme-editor/hooks/use-native-widget-data';
24
+
25
+ // Constants
26
+ export const BASKET_SUMMARY_PLACEHOLDER_ID = 'basket-page';
27
+ export const BASKET_SUMMARY_SECTION_ID = 'basket-summary-section';
28
+ export const BASKET_SUMMARY_WIDGET_SLUG = 'basket-summary-styles';
29
+
30
+ // Section-level default styles
31
+ export const BASKET_SUMMARY_SECTION_STYLES = {
32
+ 'background-color': '#f9fafb',
33
+ padding: '24px'
34
+ };
35
+
36
+ // Block definitions for basket summary elements
37
+ export const BASKET_SUMMARY_BLOCKS = {
38
+ TITLE: {
39
+ id: 'basket-summary-title',
40
+ type: 'text',
41
+ label: 'Summary Title',
42
+ properties: {},
43
+ styles: {}
44
+ },
45
+ VOUCHER_SECTION: {
46
+ id: 'basket-summary-voucher-section',
47
+ type: 'group',
48
+ label: 'Voucher Section',
49
+ properties: {},
50
+ styles: {}
51
+ },
52
+ VOUCHER_TITLE: {
53
+ id: 'basket-summary-voucher-title',
54
+ type: 'text',
55
+ label: 'Voucher Section Title',
56
+ properties: {},
57
+ styles: {}
58
+ },
59
+ VOUCHER_FORM: {
60
+ id: 'basket-summary-voucher-form',
61
+ type: 'group',
62
+ label: 'Voucher Form',
63
+ properties: {},
64
+ styles: {}
65
+ },
66
+ VOUCHER_INPUT: {
67
+ id: 'basket-summary-voucher-input',
68
+ type: 'text',
69
+ label: 'Voucher Input',
70
+ properties: {},
71
+ styles: {}
72
+ },
73
+ VOUCHER_BUTTON: {
74
+ id: 'basket-summary-voucher-button',
75
+ type: 'button',
76
+ label: 'Voucher Button',
77
+ properties: {},
78
+ styles: {}
79
+ },
80
+ PRICE_SECTION: {
81
+ id: 'basket-summary-price-section',
82
+ type: 'group',
83
+ label: 'Price Section',
84
+ properties: {},
85
+ styles: {}
86
+ },
87
+ SUBTOTAL_ROW: {
88
+ id: 'basket-summary-subtotal-row',
89
+ type: 'group',
90
+ label: 'Subtotal Row',
91
+ properties: {},
92
+ styles: {}
93
+ },
94
+ SUBTOTAL_LABEL: {
95
+ id: 'basket-summary-subtotal-label',
96
+ type: 'text',
97
+ label: 'Subtotal Label',
98
+ properties: {},
99
+ styles: {}
100
+ },
101
+ SUBTOTAL_PRICE: {
102
+ id: 'basket-summary-subtotal-price',
103
+ type: 'text',
104
+ label: 'Subtotal Price',
105
+ properties: {},
106
+ styles: {}
107
+ },
108
+ DISCOUNT_ITEM_ROW: {
109
+ id: 'basket-summary-discount-item-row',
110
+ type: 'group',
111
+ label: 'Discount Item Row',
112
+ properties: {},
113
+ styles: {}
114
+ },
115
+ DISCOUNT_ITEM_LABEL: {
116
+ id: 'basket-summary-discount-item-label',
117
+ type: 'text',
118
+ label: 'Discount Item Label',
119
+ properties: {},
120
+ styles: {}
121
+ },
122
+ DISCOUNT_ITEM_PRICE: {
123
+ id: 'basket-summary-discount-item-price',
124
+ type: 'text',
125
+ label: 'Discount Item Price',
126
+ properties: {},
127
+ styles: {}
128
+ },
129
+ DISCOUNTS_TOTAL_LABEL: {
130
+ id: 'basket-summary-discounts-total-label',
131
+ type: 'text',
132
+ label: 'Discounts Total Label',
133
+ properties: {},
134
+ styles: {}
135
+ },
136
+ DISCOUNTS_TOTAL_PRICE: {
137
+ id: 'basket-summary-discounts-total-price',
138
+ type: 'text',
139
+ label: 'Discounts Total Price',
140
+ properties: {},
141
+ styles: {}
142
+ },
143
+ DISCOUNTS_TOTAL_ROW: {
144
+ id: 'basket-summary-discounts-total-row',
145
+ type: 'group',
146
+ label: 'Discounts Total Row',
147
+ properties: {},
148
+ styles: {}
149
+ },
150
+ TOTAL_ROW: {
151
+ id: 'basket-summary-total-row',
152
+ type: 'group',
153
+ label: 'Total Row',
154
+ properties: {},
155
+ styles: {}
156
+ },
157
+ TOTAL_LABEL: {
158
+ id: 'basket-summary-total-label',
159
+ type: 'text',
160
+ label: 'Total Label',
161
+ properties: {},
162
+ styles: {}
163
+ },
164
+ TOTAL_PRICE: {
165
+ id: 'basket-summary-total-price',
166
+ type: 'text',
167
+ label: 'Total Price',
168
+ properties: {},
169
+ styles: {}
170
+ },
171
+ CHECKOUT_SECTION: {
172
+ id: 'basket-summary-checkout-section',
173
+ type: 'group',
174
+ label: 'Checkout Section',
175
+ properties: {},
176
+ styles: {}
177
+ },
178
+ CHECKOUT_BUTTON: {
179
+ id: 'basket-summary-checkout-button',
180
+ type: 'button',
181
+ label: 'Checkout Button',
182
+ properties: {},
183
+ styles: {}
184
+ }
185
+ } as const;
186
+
187
+ interface ThemeBlock {
188
+ id: string;
189
+ type: string;
190
+ label: string;
191
+ styles?: Record<string, unknown>;
192
+ properties?: Record<string, unknown>;
193
+ blocks?: ThemeBlock[];
194
+ }
195
+
196
+ interface ThemeSection {
197
+ id: string;
198
+ blocks: ThemeBlock[];
199
+ styles?: Record<string, unknown>;
200
+ }
201
+
202
+ interface ThemePlaceholder {
203
+ id: string;
204
+ sections: ThemeSection[];
205
+ }
206
+
207
+ interface BasketSummaryContextValue {
208
+ isDesigner: boolean;
209
+ isSectionSelected: boolean;
210
+ selectedBlockId: string;
211
+ getSectionStyles: () => Record<string, unknown> | undefined;
212
+ getBlockData: (blockId: string) => ThemeBlock | undefined;
213
+ }
214
+
215
+ const BasketSummaryContext = createContext<BasketSummaryContextValue>({
216
+ isDesigner: false,
217
+ isSectionSelected: false,
218
+ selectedBlockId: '',
219
+ getSectionStyles: () => undefined,
220
+ getBlockData: () => undefined
221
+ });
222
+
223
+ export const useBasketSummary = () => useContext(BasketSummaryContext);
224
+
225
+ export const BasketSummaryProvider = ({ children }: PropsWithChildren) => {
226
+ const designerState = useExternalDesigner({
227
+ placeholderId: BASKET_SUMMARY_PLACEHOLDER_ID
228
+ });
229
+
230
+ const [isSectionSelected, setIsSectionSelected] = useState(false);
231
+ const [selectedBlockId, setSelectedBlockId] = useState('');
232
+ const [themeBlocks, setThemeBlocks] = useState<Map<string, ThemeBlock>>(
233
+ new Map()
234
+ );
235
+ const [sectionStyles, setSectionStyles] = useState<
236
+ Record<string, unknown> | undefined
237
+ >(BASKET_SUMMARY_SECTION_STYLES);
238
+
239
+ const themeBlocksRef = useRef<Map<string, ThemeBlock>>(new Map());
240
+ const hasRegisteredNativeWidget = useRef(false);
241
+ const hasReceivedThemeStyles = useRef(false);
242
+
243
+ // Flatten block meta for useNativeWidgetData
244
+ const blockMeta = useMemo(() => {
245
+ return Object.values(BASKET_SUMMARY_BLOCKS).map((block) => ({
246
+ id: block.id,
247
+ type: block.type,
248
+ label: block.label
249
+ }));
250
+ }, []);
251
+
252
+ // Load saved widget data (styles from backend)
253
+ const widgetData = useNativeWidgetData({
254
+ widgetSlug: BASKET_SUMMARY_WIDGET_SLUG,
255
+ sectionId: BASKET_SUMMARY_SECTION_ID,
256
+ blockMeta
257
+ });
258
+
259
+ const createBlock = useCallback((blockId: string): ThemeBlock => {
260
+ const blockDef = Object.values(BASKET_SUMMARY_BLOCKS).find(
261
+ (block) => block.id === blockId
262
+ );
263
+
264
+ if (!blockDef) {
265
+ return {
266
+ id: blockId,
267
+ type: 'text',
268
+ label: blockId,
269
+ properties: {},
270
+ styles: {},
271
+ blocks: []
272
+ };
273
+ }
274
+
275
+ const existingBlock = themeBlocksRef.current.get(blockId);
276
+
277
+ return {
278
+ id: blockDef.id,
279
+ type: blockDef.type,
280
+ label: blockDef.label,
281
+ properties: existingBlock?.properties || blockDef.properties || {},
282
+ styles: existingBlock?.styles || blockDef.styles || {},
283
+ blocks: existingBlock?.blocks || []
284
+ };
285
+ }, []);
286
+
287
+ // Get default blocks with current styles
288
+ const getDefaultBlocks = useCallback(() => {
289
+ const voucherSection = createBlock(
290
+ BASKET_SUMMARY_BLOCKS.VOUCHER_SECTION.id
291
+ );
292
+ const voucherForm = createBlock(BASKET_SUMMARY_BLOCKS.VOUCHER_FORM.id);
293
+ const priceSection = createBlock(BASKET_SUMMARY_BLOCKS.PRICE_SECTION.id);
294
+ const subtotalRow = createBlock(BASKET_SUMMARY_BLOCKS.SUBTOTAL_ROW.id);
295
+ const discountItemRow = createBlock(
296
+ BASKET_SUMMARY_BLOCKS.DISCOUNT_ITEM_ROW.id
297
+ );
298
+ const discountsTotalRow = createBlock(
299
+ BASKET_SUMMARY_BLOCKS.DISCOUNTS_TOTAL_ROW.id
300
+ );
301
+ const totalRow = createBlock(BASKET_SUMMARY_BLOCKS.TOTAL_ROW.id);
302
+ const checkoutSection = createBlock(
303
+ BASKET_SUMMARY_BLOCKS.CHECKOUT_SECTION.id
304
+ );
305
+
306
+ return [
307
+ createBlock(BASKET_SUMMARY_BLOCKS.TITLE.id),
308
+ {
309
+ ...voucherSection,
310
+ blocks: [
311
+ createBlock(BASKET_SUMMARY_BLOCKS.VOUCHER_TITLE.id),
312
+ {
313
+ ...voucherForm,
314
+ blocks: [
315
+ createBlock(BASKET_SUMMARY_BLOCKS.VOUCHER_INPUT.id),
316
+ createBlock(BASKET_SUMMARY_BLOCKS.VOUCHER_BUTTON.id)
317
+ ]
318
+ }
319
+ ]
320
+ },
321
+ {
322
+ ...priceSection,
323
+ blocks: [
324
+ {
325
+ ...subtotalRow,
326
+ blocks: [
327
+ createBlock(BASKET_SUMMARY_BLOCKS.SUBTOTAL_LABEL.id),
328
+ createBlock(BASKET_SUMMARY_BLOCKS.SUBTOTAL_PRICE.id)
329
+ ]
330
+ },
331
+ {
332
+ ...discountItemRow,
333
+ blocks: [
334
+ createBlock(BASKET_SUMMARY_BLOCKS.DISCOUNT_ITEM_LABEL.id),
335
+ createBlock(BASKET_SUMMARY_BLOCKS.DISCOUNT_ITEM_PRICE.id)
336
+ ]
337
+ },
338
+ {
339
+ ...discountsTotalRow,
340
+ blocks: [
341
+ createBlock(BASKET_SUMMARY_BLOCKS.DISCOUNTS_TOTAL_LABEL.id),
342
+ createBlock(BASKET_SUMMARY_BLOCKS.DISCOUNTS_TOTAL_PRICE.id)
343
+ ]
344
+ },
345
+ {
346
+ ...totalRow,
347
+ blocks: [
348
+ createBlock(BASKET_SUMMARY_BLOCKS.TOTAL_LABEL.id),
349
+ createBlock(BASKET_SUMMARY_BLOCKS.TOTAL_PRICE.id)
350
+ ]
351
+ }
352
+ ]
353
+ },
354
+ {
355
+ ...checkoutSection,
356
+ blocks: [createBlock(BASKET_SUMMARY_BLOCKS.CHECKOUT_BUTTON.id)]
357
+ }
358
+ ];
359
+ }, [createBlock]);
360
+
361
+ const indexBlocksRecursively = useCallback(
362
+ (blocks: ThemeBlock[], targetMap: Map<string, ThemeBlock>) => {
363
+ blocks.forEach((block) => {
364
+ targetMap.set(block.id, block);
365
+
366
+ if (block.blocks?.length) {
367
+ indexBlocksRecursively(block.blocks, targetMap);
368
+ }
369
+ });
370
+ },
371
+ []
372
+ );
373
+
374
+ // Register native widget with Theme Editor (ONLY ONCE)
375
+ useEffect(() => {
376
+ const isInIframe =
377
+ typeof window !== 'undefined' && window.self !== window.top;
378
+ if (!isInIframe || !window.parent) {
379
+ return;
380
+ }
381
+
382
+ if (hasRegisteredNativeWidget.current) {
383
+ return;
384
+ }
385
+
386
+ const blocks = getDefaultBlocks();
387
+
388
+ window.parent.postMessage(
389
+ {
390
+ type: 'REGISTER_NATIVE_WIDGETS',
391
+ data: {
392
+ widgets: [
393
+ {
394
+ placeholderId: BASKET_SUMMARY_PLACEHOLDER_ID,
395
+ autoAdd: true,
396
+ section: {
397
+ id: BASKET_SUMMARY_SECTION_ID,
398
+ type: 'native',
399
+ label: 'Basket Summary',
400
+ blocks,
401
+ styles: BASKET_SUMMARY_SECTION_STYLES,
402
+ properties: {}
403
+ }
404
+ }
405
+ ]
406
+ }
407
+ },
408
+ '*'
409
+ );
410
+
411
+ hasRegisteredNativeWidget.current = true;
412
+ }, [getDefaultBlocks]);
413
+
414
+ // Load saved styles from widget (non-designer / standalone mode)
415
+ useEffect(() => {
416
+ if (designerState.isDesigner) return;
417
+ if (widgetData.isLoading) return;
418
+
419
+ if (
420
+ widgetData.sectionStyles &&
421
+ Object.keys(widgetData.sectionStyles).length > 0
422
+ ) {
423
+ setSectionStyles(widgetData.sectionStyles);
424
+ }
425
+
426
+ if (widgetData.blocks && widgetData.blocks.size > 0) {
427
+ const blockMap = new Map<string, ThemeBlock>();
428
+ widgetData.blocks.forEach((block) => {
429
+ blockMap.set(block.id, {
430
+ id: block.id,
431
+ type: block.type || 'text',
432
+ label: block.label || block.id,
433
+ styles: block.styles,
434
+ properties: block.properties
435
+ });
436
+ });
437
+
438
+ setThemeBlocks(blockMap);
439
+ themeBlocksRef.current = blockMap;
440
+ }
441
+ }, [
442
+ designerState.isDesigner,
443
+ widgetData.isLoading,
444
+ widgetData.sectionStyles,
445
+ widgetData.blocks
446
+ ]);
447
+
448
+ // Listen for theme updates from Theme Editor
449
+ useEffect(() => {
450
+ const handleMessage = (event: MessageEvent) => {
451
+ const { type, data } = event.data || {};
452
+
453
+ if (type === 'SELECT_SECTION') {
454
+ const isOurSection =
455
+ data?.placeholderId === BASKET_SUMMARY_PLACEHOLDER_ID &&
456
+ data?.sectionId === BASKET_SUMMARY_SECTION_ID;
457
+ setIsSectionSelected(isOurSection);
458
+ if (isOurSection) {
459
+ setSelectedBlockId('');
460
+ }
461
+ return;
462
+ }
463
+
464
+ if (type === 'SELECT_BLOCK') {
465
+ const isOurSectionBlock =
466
+ data?.placeholderId === BASKET_SUMMARY_PLACEHOLDER_ID &&
467
+ data?.sectionId === BASKET_SUMMARY_SECTION_ID;
468
+ if (isOurSectionBlock) {
469
+ setSelectedBlockId(data?.blockId || '');
470
+ setIsSectionSelected(false);
471
+ } else {
472
+ setSelectedBlockId('');
473
+ }
474
+ return;
475
+ }
476
+
477
+ if (!designerState.isDesigner) {
478
+ return;
479
+ }
480
+
481
+ if (
482
+ (type === 'UPDATE_THEME' || type === 'LOAD_THEME') &&
483
+ data?.theme?.placeholders
484
+ ) {
485
+ const placeholder = data.theme.placeholders.find(
486
+ (p: ThemePlaceholder) => p.id === BASKET_SUMMARY_PLACEHOLDER_ID
487
+ );
488
+
489
+ if (!placeholder) return;
490
+
491
+ const summarySection = placeholder.sections?.find(
492
+ (section: ThemeSection) => section.id === BASKET_SUMMARY_SECTION_ID
493
+ );
494
+
495
+ if (!summarySection) return;
496
+
497
+ // Update section styles
498
+ if (summarySection.styles) {
499
+ setSectionStyles(summarySection.styles);
500
+ }
501
+
502
+ // Update blocks map
503
+ const newBlocks = new Map<string, ThemeBlock>();
504
+ indexBlocksRecursively(summarySection.blocks || [], newBlocks);
505
+
506
+ // Merge: keep existing styles if incoming block has no styles
507
+ const currentBlocks = themeBlocksRef.current;
508
+ const mergedMap = new Map<string, ThemeBlock>();
509
+
510
+ currentBlocks.forEach((block, id) => mergedMap.set(id, block));
511
+
512
+ newBlocks.forEach((block, id) => {
513
+ const existingBlock = mergedMap.get(id);
514
+ const hasNewStyles =
515
+ block.styles && Object.keys(block.styles).length > 0;
516
+
517
+ if (hasNewStyles) {
518
+ mergedMap.set(id, block);
519
+ } else if (existingBlock) {
520
+ mergedMap.set(id, { ...block, styles: existingBlock.styles });
521
+ } else {
522
+ mergedMap.set(id, block);
523
+ }
524
+ });
525
+
526
+ themeBlocksRef.current = mergedMap;
527
+ setThemeBlocks(mergedMap);
528
+ hasReceivedThemeStyles.current = true;
529
+ }
530
+ };
531
+
532
+ window.addEventListener('message', handleMessage);
533
+ return () => window.removeEventListener('message', handleMessage);
534
+ }, [designerState.isDesigner, indexBlocksRecursively]);
535
+
536
+ const getSectionStyles = useCallback(() => {
537
+ return sectionStyles;
538
+ }, [sectionStyles]);
539
+
540
+ const getBlockData = useCallback(
541
+ (blockId: string) => {
542
+ return themeBlocks.get(blockId);
543
+ },
544
+ [themeBlocks]
545
+ );
546
+
547
+ const contextValue: BasketSummaryContextValue = {
548
+ isDesigner: designerState.isDesigner,
549
+ isSectionSelected,
550
+ selectedBlockId,
551
+ getSectionStyles,
552
+ getBlockData
553
+ };
554
+
555
+ return (
556
+ <BasketSummaryContext.Provider value={contextValue}>
557
+ {children}
558
+ </BasketSummaryContext.Provider>
559
+ );
560
+ };