@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,1105 @@
1
+ /**
2
+ * Server-side parsers for header widget settings
3
+ * These parse widget data fetched on the server to provide initial values
4
+ * to client components, avoiding the flash of default styles.
5
+ *
6
+ * Note: This file exports types that are also used by client components,
7
+ * but the parser functions should only be called on the server.
8
+ */
9
+
10
+ // Block IDs matching header-icons-context.tsx
11
+ const ICON_BLOCK_IDS = {
12
+ search: 'header-icon-search',
13
+ profile: 'header-icon-profile',
14
+ cart: 'header-icon-cart'
15
+ } as const;
16
+
17
+ const LOGO_BLOCK_ID = 'header-logo-image';
18
+
19
+ // Default values
20
+ const DEFAULT_ICON_SIZE = 20;
21
+ const DEFAULT_ICON_COLOR = 'currentColor';
22
+ const DEFAULT_LOGO_WIDTH = 120;
23
+ const DEFAULT_LOGO_HEIGHT = 40;
24
+
25
+ export interface IconSettings {
26
+ size: number;
27
+ color: string;
28
+ }
29
+
30
+ export interface HeaderIconSettings {
31
+ search: IconSettings;
32
+ profile: IconSettings;
33
+ cart: IconSettings;
34
+ }
35
+
36
+ export interface LogoSettings {
37
+ src: string;
38
+ width: number | string;
39
+ height: number | string;
40
+ maxWidth?: string;
41
+ maxHeight?: string;
42
+ objectFit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';
43
+ }
44
+
45
+ const defaultIconSettings: HeaderIconSettings = {
46
+ search: { size: DEFAULT_ICON_SIZE, color: DEFAULT_ICON_COLOR },
47
+ profile: { size: DEFAULT_ICON_SIZE, color: DEFAULT_ICON_COLOR },
48
+ cart: { size: DEFAULT_ICON_SIZE, color: DEFAULT_ICON_COLOR }
49
+ };
50
+
51
+ const defaultLogoSettings: LogoSettings = {
52
+ src: '/logo.svg',
53
+ width: DEFAULT_LOGO_WIDTH,
54
+ height: DEFAULT_LOGO_HEIGHT,
55
+ objectFit: 'contain'
56
+ };
57
+
58
+ /**
59
+ * Parse icon settings from widget data (server-side)
60
+ */
61
+ export function parseServerIconSettings(
62
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
63
+ widgetData: any
64
+ ): HeaderIconSettings {
65
+ if (!widgetData?.attributes) {
66
+ return defaultIconSettings;
67
+ }
68
+
69
+ const result = { ...defaultIconSettings };
70
+ const attrs = widgetData.attributes;
71
+
72
+ Object.entries(ICON_BLOCK_IDS).forEach(([key, blockId]) => {
73
+ const attrData = attrs[blockId];
74
+ if (!attrData) return;
75
+
76
+ try {
77
+ const blockData =
78
+ typeof attrData === 'string'
79
+ ? JSON.parse(attrData)
80
+ : typeof attrData === 'object' && 'value' in attrData
81
+ ? JSON.parse((attrData as { value: string }).value)
82
+ : null;
83
+
84
+ if (blockData) {
85
+ // Support both old format (direct styles) and new format ({ styles, properties, value })
86
+ const styles = blockData.styles || blockData;
87
+ const properties = blockData.properties || {};
88
+
89
+ const color =
90
+ styles?.color?.desktop || styles?.color || DEFAULT_ICON_COLOR;
91
+ const sizeValue =
92
+ properties?.iconSize?.desktop ||
93
+ properties?.iconSize ||
94
+ DEFAULT_ICON_SIZE;
95
+ const size =
96
+ typeof sizeValue === 'string' ? parseInt(sizeValue, 10) : sizeValue;
97
+
98
+ result[key as keyof HeaderIconSettings] = {
99
+ size: isNaN(size) ? DEFAULT_ICON_SIZE : size,
100
+ color
101
+ };
102
+ }
103
+ } catch {
104
+ // Ignore parse errors
105
+ }
106
+ });
107
+
108
+ return result;
109
+ }
110
+
111
+ /**
112
+ * Parse logo settings from widget data (server-side)
113
+ */
114
+ export function parseServerLogoSettings(
115
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
116
+ widgetData: any,
117
+ fallbackSrc?: string
118
+ ): LogoSettings {
119
+ const result: LogoSettings = {
120
+ ...defaultLogoSettings,
121
+ src: fallbackSrc || defaultLogoSettings.src
122
+ };
123
+
124
+ if (!widgetData?.attributes) {
125
+ return result;
126
+ }
127
+
128
+ const blockData = widgetData.attributes[LOGO_BLOCK_ID];
129
+ if (!blockData) return result;
130
+
131
+ try {
132
+ const data =
133
+ typeof blockData === 'string'
134
+ ? JSON.parse(blockData)
135
+ : typeof blockData === 'object' && 'value' in blockData
136
+ ? JSON.parse((blockData as { value: string }).value)
137
+ : null;
138
+
139
+ if (!data) return result;
140
+
141
+ // Support both old format (direct styles) and new format ({ styles, properties, value })
142
+ const styles = data.styles || data;
143
+ const value = data.value;
144
+
145
+ // Get logo src from value
146
+ if (value) {
147
+ const src = typeof value === 'object' ? value.desktop : value;
148
+ if (src) result.src = src;
149
+ }
150
+
151
+ // Get styles
152
+ if (styles.width) {
153
+ result.width =
154
+ typeof styles.width === 'object' ? styles.width.desktop : styles.width;
155
+ }
156
+ if (styles.height) {
157
+ result.height =
158
+ typeof styles.height === 'object'
159
+ ? styles.height.desktop
160
+ : styles.height;
161
+ }
162
+ if (styles.maxWidth || styles['max-width']) {
163
+ const maxWidth = styles.maxWidth || styles['max-width'];
164
+ result.maxWidth =
165
+ typeof maxWidth === 'object' ? maxWidth.desktop : maxWidth;
166
+ }
167
+ if (styles.maxHeight || styles['max-height']) {
168
+ const maxHeight = styles.maxHeight || styles['max-height'];
169
+ result.maxHeight =
170
+ typeof maxHeight === 'object' ? maxHeight.desktop : maxHeight;
171
+ }
172
+ if (styles.objectFit || styles['object-fit']) {
173
+ const objectFit = styles.objectFit || styles['object-fit'];
174
+ result.objectFit =
175
+ typeof objectFit === 'object' ? objectFit.desktop : objectFit;
176
+ }
177
+ } catch {
178
+ // Ignore parse errors
179
+ }
180
+
181
+ return result;
182
+ }
183
+
184
+ // ============================================
185
+ // Navbar Menu Settings
186
+ // ============================================
187
+
188
+ const NAVBAR_MENU_BLOCK_ID = 'header-nav-menu-item';
189
+
190
+ // Default navbar menu values
191
+ const DEFAULT_FONT_SIZE = '14px';
192
+ const DEFAULT_NAV_COLOR = 'currentColor';
193
+ const DEFAULT_HOVER_COLOR = '';
194
+ const DEFAULT_FONT_WEIGHT = '400';
195
+
196
+ export interface NavbarMenuSettings {
197
+ fontSize: string;
198
+ color: string;
199
+ hoverColor: string;
200
+ fontWeight: string;
201
+ textTransform?: string;
202
+ letterSpacing?: string;
203
+ }
204
+
205
+ const defaultNavbarMenuSettings: NavbarMenuSettings = {
206
+ fontSize: DEFAULT_FONT_SIZE,
207
+ color: DEFAULT_NAV_COLOR,
208
+ hoverColor: DEFAULT_HOVER_COLOR,
209
+ fontWeight: DEFAULT_FONT_WEIGHT
210
+ };
211
+
212
+ /**
213
+ * Parse navbar menu settings from widget data (server-side)
214
+ */
215
+ export function parseServerNavbarMenuSettings(
216
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
217
+ widgetData: any
218
+ ): NavbarMenuSettings {
219
+ if (!widgetData?.attributes) {
220
+ return defaultNavbarMenuSettings;
221
+ }
222
+
223
+ const result = { ...defaultNavbarMenuSettings };
224
+ const blockData = widgetData.attributes[NAVBAR_MENU_BLOCK_ID];
225
+
226
+ if (!blockData) return result;
227
+
228
+ try {
229
+ const data =
230
+ typeof blockData === 'string'
231
+ ? JSON.parse(blockData)
232
+ : typeof blockData === 'object' && 'value' in blockData
233
+ ? JSON.parse((blockData as { value: string }).value)
234
+ : null;
235
+
236
+ if (!data) return result;
237
+
238
+ // Support both old format (direct styles) and new format ({ styles, properties })
239
+ const styles = data.styles || data;
240
+
241
+ // Font size
242
+ if (styles.fontSize || styles['font-size']) {
243
+ const fontSize = styles.fontSize || styles['font-size'];
244
+ result.fontSize =
245
+ typeof fontSize === 'object' ? fontSize.desktop : fontSize;
246
+ }
247
+
248
+ // Color
249
+ if (styles.color) {
250
+ result.color =
251
+ typeof styles.color === 'object' ? styles.color.desktop : styles.color;
252
+ }
253
+
254
+ // Hover color
255
+ if (styles.hoverColor || styles['hover-color']) {
256
+ const hoverColor = styles.hoverColor || styles['hover-color'];
257
+ result.hoverColor =
258
+ typeof hoverColor === 'object' ? hoverColor.desktop : hoverColor;
259
+ }
260
+
261
+ // Font weight
262
+ if (styles.fontWeight || styles['font-weight']) {
263
+ const fontWeight = styles.fontWeight || styles['font-weight'];
264
+ result.fontWeight =
265
+ typeof fontWeight === 'object' ? fontWeight.desktop : fontWeight;
266
+ }
267
+
268
+ // Text transform
269
+ if (styles.textTransform || styles['text-transform']) {
270
+ const textTransform = styles.textTransform || styles['text-transform'];
271
+ result.textTransform =
272
+ typeof textTransform === 'object'
273
+ ? textTransform.desktop
274
+ : textTransform;
275
+ }
276
+
277
+ // Letter spacing
278
+ if (styles.letterSpacing || styles['letter-spacing']) {
279
+ const letterSpacing = styles.letterSpacing || styles['letter-spacing'];
280
+ result.letterSpacing =
281
+ typeof letterSpacing === 'object'
282
+ ? letterSpacing.desktop
283
+ : letterSpacing;
284
+ }
285
+ } catch {
286
+ // Ignore parse errors
287
+ }
288
+
289
+ return result;
290
+ }
291
+
292
+ // ============================================
293
+ // Header Layout Settings
294
+ // ============================================
295
+
296
+ const HEADER_LAYOUT_SECTION_ID = 'header-layout';
297
+
298
+ // Block IDs for header layout rows
299
+ const HEADER_LAYOUT_BLOCK_IDS = {
300
+ UTILITY_ROW: 'header-utility-row',
301
+ MAIN_ROW: 'header-main-row',
302
+ TOP_ROW: 'header-top-row',
303
+ BOTTOM_ROW: 'header-bottom-row'
304
+ } as const;
305
+
306
+ export type HeaderLayoutType = 'default' | 'two-row';
307
+ export type MenuPositionType = 'left' | 'center' | 'right';
308
+ export type SearchPositionType = 'left' | 'center' | 'right';
309
+ export type UtilityPositionType = 'left' | 'right';
310
+
311
+ export interface BlockStylesMap {
312
+ [blockId: string]: Record<string, string>;
313
+ }
314
+
315
+ export interface HeaderLayoutSettings {
316
+ layout: HeaderLayoutType;
317
+ menuPosition: MenuPositionType;
318
+ searchPosition: SearchPositionType;
319
+ utilityPosition: UtilityPositionType;
320
+ sticky: boolean;
321
+ blockStyles: BlockStylesMap;
322
+ }
323
+
324
+ const defaultLayoutSettings: HeaderLayoutSettings = {
325
+ layout: 'default',
326
+ menuPosition: 'center',
327
+ searchPosition: 'center',
328
+ utilityPosition: 'right',
329
+ sticky: true,
330
+ blockStyles: {}
331
+ };
332
+
333
+ /**
334
+ * Parse header layout settings from widget data (server-side)
335
+ */
336
+ export function parseServerLayoutSettings(
337
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
338
+ widgetData: any
339
+ ): HeaderLayoutSettings {
340
+ if (!widgetData?.attributes) {
341
+ return defaultLayoutSettings;
342
+ }
343
+
344
+ const result: HeaderLayoutSettings = {
345
+ ...defaultLayoutSettings,
346
+ blockStyles: {}
347
+ };
348
+ const attrs = widgetData.attributes;
349
+
350
+ // Parse section-level properties
351
+ const sectionData = attrs[HEADER_LAYOUT_SECTION_ID];
352
+
353
+ if (sectionData) {
354
+ try {
355
+ const data =
356
+ typeof sectionData === 'string'
357
+ ? JSON.parse(sectionData)
358
+ : typeof sectionData === 'object' && 'value' in sectionData
359
+ ? JSON.parse((sectionData as { value: string }).value)
360
+ : null;
361
+
362
+ if (data?.properties) {
363
+ const properties = data.properties;
364
+
365
+ // Layout
366
+ if (properties.layout) {
367
+ const layout = properties.layout;
368
+ result.layout = (
369
+ typeof layout === 'object' ? layout.desktop : layout
370
+ ) as HeaderLayoutType;
371
+ }
372
+
373
+ // Menu position
374
+ if (properties.menuPosition) {
375
+ const menuPosition = properties.menuPosition;
376
+ result.menuPosition = (
377
+ typeof menuPosition === 'object'
378
+ ? menuPosition.desktop
379
+ : menuPosition
380
+ ) as MenuPositionType;
381
+ }
382
+
383
+ // Search position
384
+ if (properties.searchPosition) {
385
+ const searchPosition = properties.searchPosition;
386
+ result.searchPosition = (
387
+ typeof searchPosition === 'object'
388
+ ? searchPosition.desktop
389
+ : searchPosition
390
+ ) as SearchPositionType;
391
+ }
392
+
393
+ // Utility position (Language/Currency selects)
394
+ if (properties.utilityPosition) {
395
+ const utilityPosition = properties.utilityPosition;
396
+ result.utilityPosition = (
397
+ typeof utilityPosition === 'object'
398
+ ? utilityPosition.desktop
399
+ : utilityPosition
400
+ ) as UtilityPositionType;
401
+ }
402
+
403
+ // Sticky
404
+ if (properties.sticky !== undefined) {
405
+ const sticky = properties.sticky;
406
+ const stickyValue =
407
+ typeof sticky === 'object' ? sticky.desktop : sticky;
408
+ result.sticky = stickyValue === 'true' || stickyValue === true;
409
+ }
410
+ }
411
+ } catch {
412
+ // Ignore parse errors
413
+ }
414
+ }
415
+
416
+ // Parse block styles (TOP_ROW, BOTTOM_ROW, MAIN_ROW)
417
+ Object.values(HEADER_LAYOUT_BLOCK_IDS).forEach((blockId) => {
418
+ const blockData = attrs[blockId];
419
+ if (!blockData) return;
420
+
421
+ try {
422
+ const data =
423
+ typeof blockData === 'string'
424
+ ? JSON.parse(blockData)
425
+ : typeof blockData === 'object' && 'value' in blockData
426
+ ? JSON.parse((blockData as { value: string }).value)
427
+ : null;
428
+
429
+ if (data?.styles) {
430
+ // Convert responsive styles to flat styles (desktop first)
431
+ const flatStyles: Record<string, string> = {};
432
+ Object.entries(data.styles).forEach(([key, value]) => {
433
+ if (typeof value === 'object' && value !== null) {
434
+ const responsiveValue = value as Record<string, string>;
435
+ flatStyles[key] =
436
+ responsiveValue.desktop ||
437
+ responsiveValue.mobile ||
438
+ Object.values(responsiveValue)[0] ||
439
+ '';
440
+ } else if (typeof value === 'string') {
441
+ flatStyles[key] = value;
442
+ }
443
+ });
444
+
445
+ if (Object.keys(flatStyles).length > 0) {
446
+ result.blockStyles[blockId] = flatStyles;
447
+ }
448
+ }
449
+ } catch {
450
+ // Ignore parse errors
451
+ }
452
+ });
453
+
454
+ return result;
455
+ }
456
+
457
+ // ============================================================
458
+ // SEARCH SECTION SETTINGS PARSER
459
+ // ============================================================
460
+
461
+ const HEADER_SEARCH_SECTION_ID = 'header-search';
462
+ const HEADER_SEARCH_ICON_BLOCK_ID = 'header-search-icon';
463
+
464
+ export interface SearchSectionStyles {
465
+ width?: string | number;
466
+ maxWidth?: string;
467
+ height?: string | number;
468
+ backgroundColor?: string;
469
+ borderColor?: string;
470
+ borderWidth?: string;
471
+ borderRadius?: string;
472
+ fontSize?: string;
473
+ paddingLeft?: string;
474
+ paddingRight?: string;
475
+ }
476
+
477
+ export interface SearchIconStyles {
478
+ color?: string;
479
+ iconSize?: number;
480
+ }
481
+
482
+ export interface HeaderSearchSettings {
483
+ sectionStyles: SearchSectionStyles;
484
+ iconStyles: SearchIconStyles;
485
+ properties: {
486
+ placeholder?: string;
487
+ visible?: boolean;
488
+ width?: string | number;
489
+ };
490
+ }
491
+
492
+ const defaultSearchSettings: HeaderSearchSettings = {
493
+ sectionStyles: {},
494
+ iconStyles: {},
495
+ properties: {}
496
+ };
497
+
498
+ /**
499
+ * Helper to extract responsive value (desktop first)
500
+ */
501
+ function extractResponsiveValue(
502
+ value: unknown
503
+ ): string | number | boolean | undefined {
504
+ if (value === null || value === undefined) return undefined;
505
+ if (
506
+ typeof value === 'string' ||
507
+ typeof value === 'number' ||
508
+ typeof value === 'boolean'
509
+ )
510
+ return value;
511
+ if (typeof value === 'object' && value !== null) {
512
+ const obj = value as Record<string, string | number | boolean>;
513
+ // Use nullish coalescing to properly handle false values
514
+ if ('desktop' in obj) return obj.desktop;
515
+ if ('mobile' in obj) return obj.mobile;
516
+ return Object.values(obj)[0];
517
+ }
518
+ return undefined;
519
+ }
520
+
521
+ /**
522
+ * Parse search section settings from widget data (server-side)
523
+ */
524
+ export function parseServerSearchSettings(
525
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
526
+ widgetData: any
527
+ ): HeaderSearchSettings {
528
+ if (!widgetData?.attributes) {
529
+ return defaultSearchSettings;
530
+ }
531
+
532
+ const result: HeaderSearchSettings = {
533
+ sectionStyles: {},
534
+ iconStyles: {},
535
+ properties: {}
536
+ };
537
+
538
+ const attrs = widgetData.attributes;
539
+
540
+ // Parse section styles and properties
541
+ const sectionData = attrs[HEADER_SEARCH_SECTION_ID];
542
+ if (sectionData) {
543
+ try {
544
+ const data =
545
+ typeof sectionData === 'string'
546
+ ? JSON.parse(sectionData)
547
+ : typeof sectionData === 'object' && 'value' in sectionData
548
+ ? JSON.parse((sectionData as { value: string }).value)
549
+ : null;
550
+
551
+ if (data) {
552
+ // Extract properties
553
+ if (data.properties) {
554
+ const width = extractResponsiveValue(data.properties.width);
555
+ result.properties = {
556
+ placeholder: data.properties.placeholder,
557
+ visible: data.properties.visible,
558
+ width: typeof width === 'boolean' ? undefined : width
559
+ };
560
+ }
561
+
562
+ // Extract styles
563
+ if (data.styles) {
564
+ const styles = data.styles;
565
+ const sectionStyles: SearchSectionStyles = {};
566
+
567
+ // Map theme editor style keys to CSS properties
568
+ const styleKeys = [
569
+ 'width',
570
+ 'max-width',
571
+ 'height',
572
+ 'background-color',
573
+ 'border-color',
574
+ 'border-width',
575
+ 'border-radius',
576
+ 'font-size',
577
+ 'padding-left',
578
+ 'padding-right'
579
+ ];
580
+
581
+ styleKeys.forEach((key) => {
582
+ const value = extractResponsiveValue(styles[key]);
583
+ if (value !== undefined) {
584
+ // Convert kebab-case to camelCase for CSS-in-JS
585
+ const camelKey = key.replace(/-([a-z])/g, (_, letter) =>
586
+ letter.toUpperCase()
587
+ ) as keyof SearchSectionStyles;
588
+ // @ts-expect-error - dynamic assignment
589
+ sectionStyles[camelKey] = value;
590
+ }
591
+ });
592
+
593
+ result.sectionStyles = sectionStyles;
594
+ }
595
+ }
596
+ } catch {
597
+ // Ignore parse errors
598
+ }
599
+ }
600
+
601
+ // Parse icon block styles
602
+ const iconData = attrs[HEADER_SEARCH_ICON_BLOCK_ID];
603
+ if (iconData) {
604
+ try {
605
+ const data =
606
+ typeof iconData === 'string'
607
+ ? JSON.parse(iconData)
608
+ : typeof iconData === 'object' && 'value' in iconData
609
+ ? JSON.parse((iconData as { value: string }).value)
610
+ : null;
611
+
612
+ if (data) {
613
+ const iconStyles: SearchIconStyles = {};
614
+
615
+ // Get color from styles
616
+ if (data.styles?.color) {
617
+ const color = extractResponsiveValue(data.styles.color);
618
+ if (typeof color === 'string') {
619
+ iconStyles.color = color;
620
+ }
621
+ }
622
+
623
+ // Get iconSize from properties
624
+ if (data.properties?.iconSize) {
625
+ const size = extractResponsiveValue(data.properties.iconSize);
626
+ if (size !== undefined && typeof size !== 'boolean') {
627
+ iconStyles.iconSize =
628
+ typeof size === 'string' ? parseInt(size, 10) : size;
629
+ }
630
+ }
631
+
632
+ result.iconStyles = iconStyles;
633
+ }
634
+ } catch {
635
+ // Ignore parse errors
636
+ }
637
+ }
638
+
639
+ return result;
640
+ }
641
+
642
+ // ============================================
643
+ // Language Select Settings
644
+ // ============================================
645
+
646
+ const HEADER_LANGUAGE_SECTION_ID = 'header-language';
647
+
648
+ export interface LanguageSectionStyles {
649
+ [key: string]: unknown;
650
+ width?: string;
651
+ height?: string;
652
+ fontSize?: string;
653
+ color?: string;
654
+ backgroundColor?: string;
655
+ borderColor?: string;
656
+ borderWidth?: string;
657
+ borderRadius?: string;
658
+ paddingLeft?: string;
659
+ paddingRight?: string;
660
+ }
661
+
662
+ export interface HeaderLanguageSettings {
663
+ sectionStyles?: LanguageSectionStyles;
664
+ properties?: {
665
+ showIcon?: boolean;
666
+ visible?: boolean;
667
+ };
668
+ }
669
+
670
+ /**
671
+ * Parse language select settings from widget data (server-side)
672
+ */
673
+ export function parseServerLanguageSettings(
674
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
675
+ widgetData: any
676
+ ): HeaderLanguageSettings {
677
+ const result: HeaderLanguageSettings = {};
678
+
679
+ if (!widgetData?.attributes) {
680
+ return result;
681
+ }
682
+
683
+ const attrs = widgetData.attributes;
684
+
685
+ // Parse section-level styles and properties
686
+ const sectionData = attrs[HEADER_LANGUAGE_SECTION_ID];
687
+ if (sectionData) {
688
+ try {
689
+ const data =
690
+ typeof sectionData === 'string'
691
+ ? JSON.parse(sectionData)
692
+ : typeof sectionData === 'object' && 'value' in sectionData
693
+ ? JSON.parse((sectionData as { value: string }).value)
694
+ : null;
695
+
696
+ if (data) {
697
+ // Parse properties (showIcon, visible)
698
+ if (data.properties) {
699
+ const properties: HeaderLanguageSettings['properties'] = {};
700
+
701
+ if (data.properties.showIcon !== undefined) {
702
+ const showIcon = extractResponsiveValue(data.properties.showIcon);
703
+ properties.showIcon = showIcon === 'true' || Boolean(showIcon);
704
+ }
705
+
706
+ if (data.properties.visible !== undefined) {
707
+ const visible = extractResponsiveValue(data.properties.visible);
708
+ properties.visible = visible === 'true' || Boolean(visible);
709
+ }
710
+
711
+ result.properties = properties;
712
+ }
713
+
714
+ // Parse styles
715
+ if (data.styles) {
716
+ const styles = data.styles;
717
+ const sectionStyles: LanguageSectionStyles = {};
718
+
719
+ const styleKeys = [
720
+ 'width',
721
+ 'height',
722
+ 'min-width',
723
+ 'font-size',
724
+ 'color',
725
+ 'background-color',
726
+ 'border-color',
727
+ 'border-width',
728
+ 'border-radius',
729
+ 'padding-left',
730
+ 'padding-right'
731
+ ];
732
+
733
+ styleKeys.forEach((key) => {
734
+ const value = extractResponsiveValue(styles[key]);
735
+ if (value !== undefined) {
736
+ const camelKey = key.replace(/-([a-z])/g, (_, letter) =>
737
+ letter.toUpperCase()
738
+ ) as keyof LanguageSectionStyles;
739
+ sectionStyles[camelKey] = value;
740
+ }
741
+ });
742
+
743
+ result.sectionStyles = sectionStyles;
744
+ }
745
+ }
746
+ } catch {
747
+ // Ignore parse errors
748
+ }
749
+ }
750
+
751
+ return result;
752
+ }
753
+
754
+ // ============================================
755
+ // Currency Select Settings
756
+ // ============================================
757
+
758
+ const HEADER_CURRENCY_SECTION_ID = 'header-currency';
759
+
760
+ export interface CurrencySectionStyles {
761
+ [key: string]: unknown;
762
+ width?: string;
763
+ height?: string;
764
+ fontSize?: string;
765
+ color?: string;
766
+ backgroundColor?: string;
767
+ borderColor?: string;
768
+ borderWidth?: string;
769
+ borderRadius?: string;
770
+ paddingLeft?: string;
771
+ paddingRight?: string;
772
+ }
773
+
774
+ export interface HeaderCurrencySettings {
775
+ sectionStyles?: CurrencySectionStyles;
776
+ properties?: {
777
+ showIcon?: boolean;
778
+ visible?: boolean;
779
+ };
780
+ }
781
+
782
+ /**
783
+ * Parse currency select settings from widget data (server-side)
784
+ */
785
+ export function parseServerCurrencySettings(
786
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
787
+ widgetData: any
788
+ ): HeaderCurrencySettings {
789
+ const result: HeaderCurrencySettings = {};
790
+
791
+ if (!widgetData?.attributes) {
792
+ return result;
793
+ }
794
+
795
+ const attrs = widgetData.attributes;
796
+
797
+ // Parse section-level styles and properties
798
+ const sectionData = attrs[HEADER_CURRENCY_SECTION_ID];
799
+ if (sectionData) {
800
+ try {
801
+ const data =
802
+ typeof sectionData === 'string'
803
+ ? JSON.parse(sectionData)
804
+ : typeof sectionData === 'object' && 'value' in sectionData
805
+ ? JSON.parse((sectionData as { value: string }).value)
806
+ : null;
807
+
808
+ if (data) {
809
+ // Parse properties (showIcon, visible)
810
+ if (data.properties) {
811
+ const properties: HeaderCurrencySettings['properties'] = {};
812
+
813
+ if (data.properties.showIcon !== undefined) {
814
+ const showIcon = extractResponsiveValue(data.properties.showIcon);
815
+ properties.showIcon = showIcon === 'true' || Boolean(showIcon);
816
+ }
817
+
818
+ if (data.properties.visible !== undefined) {
819
+ const visible = extractResponsiveValue(data.properties.visible);
820
+ properties.visible = visible === 'true' || Boolean(visible);
821
+ }
822
+
823
+ result.properties = properties;
824
+ }
825
+
826
+ // Parse styles
827
+ if (data.styles) {
828
+ const styles = data.styles;
829
+ const sectionStyles: CurrencySectionStyles = {};
830
+
831
+ const styleKeys = [
832
+ 'width',
833
+ 'height',
834
+ 'font-size',
835
+ 'color',
836
+ 'background-color',
837
+ 'border-color',
838
+ 'border-width',
839
+ 'border-radius',
840
+ 'padding-left',
841
+ 'padding-right'
842
+ ];
843
+
844
+ styleKeys.forEach((key) => {
845
+ const value = extractResponsiveValue(styles[key]);
846
+ if (value !== undefined) {
847
+ const camelKey = key.replace(/-([a-z])/g, (_, letter) =>
848
+ letter.toUpperCase()
849
+ ) as keyof CurrencySectionStyles;
850
+ sectionStyles[camelKey] = value;
851
+ }
852
+ });
853
+
854
+ result.sectionStyles = sectionStyles;
855
+ }
856
+ }
857
+ } catch {
858
+ // Ignore parse errors
859
+ }
860
+ }
861
+
862
+ return result;
863
+ }
864
+
865
+ // Text Slider section ID
866
+ const HEADER_TEXT_SLIDER_SECTION_ID = 'header-text-slider';
867
+
868
+ export interface TextSliderItem {
869
+ text: string;
870
+ link?: string;
871
+ }
872
+
873
+ export interface TextSliderSectionStyles extends Record<string, unknown> {
874
+ fontSize?: string;
875
+ fontWeight?: string;
876
+ color?: string;
877
+ backgroundColor?: string;
878
+ }
879
+
880
+ export interface HeaderTextSliderSettings {
881
+ sectionStyles?: Record<string, unknown>;
882
+ properties?: {
883
+ visible?: boolean;
884
+ items?: TextSliderItem[];
885
+ autoPlay?: boolean;
886
+ autoPlayInterval?: number;
887
+ showArrows?: boolean;
888
+ };
889
+ }
890
+
891
+ /**
892
+ * Parse text slider settings from widget data (server-side)
893
+ */
894
+ export function parseServerTextSliderSettings(
895
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
896
+ widgetData: any
897
+ ): HeaderTextSliderSettings {
898
+ const result: HeaderTextSliderSettings = {};
899
+
900
+ if (!widgetData?.attributes) {
901
+ return result;
902
+ }
903
+
904
+ const attrs = widgetData.attributes;
905
+
906
+ // Parse section-level styles and properties
907
+ const sectionData = attrs[HEADER_TEXT_SLIDER_SECTION_ID];
908
+ if (sectionData) {
909
+ try {
910
+ const data =
911
+ typeof sectionData === 'string'
912
+ ? JSON.parse(sectionData)
913
+ : typeof sectionData === 'object' && 'value' in sectionData
914
+ ? JSON.parse((sectionData as { value: string }).value)
915
+ : null;
916
+
917
+ if (data) {
918
+ // Parse properties
919
+ if (data.properties) {
920
+ const properties: HeaderTextSliderSettings['properties'] = {};
921
+
922
+ if (data.properties.visible !== undefined) {
923
+ const visible = extractResponsiveValue(data.properties.visible);
924
+ properties.visible = visible === 'true' || Boolean(visible);
925
+ }
926
+
927
+ if (data.properties.items !== undefined) {
928
+ properties.items = data.properties.items;
929
+ }
930
+
931
+ if (data.properties.autoPlay !== undefined) {
932
+ const autoPlay = extractResponsiveValue(data.properties.autoPlay);
933
+ properties.autoPlay = autoPlay === 'true' || Boolean(autoPlay);
934
+ }
935
+
936
+ if (data.properties.autoPlayInterval !== undefined) {
937
+ const interval = extractResponsiveValue(
938
+ data.properties.autoPlayInterval
939
+ );
940
+ properties.autoPlayInterval =
941
+ typeof interval === 'number'
942
+ ? interval
943
+ : parseInt(String(interval), 10) || 3000;
944
+ }
945
+
946
+ if (data.properties.showArrows !== undefined) {
947
+ const showArrows = extractResponsiveValue(
948
+ data.properties.showArrows
949
+ );
950
+ properties.showArrows =
951
+ showArrows === 'true' || Boolean(showArrows);
952
+ }
953
+
954
+ result.properties = properties;
955
+ }
956
+
957
+ // Parse styles
958
+ if (data.styles) {
959
+ const styles = data.styles;
960
+ const sectionStyles: TextSliderSectionStyles = {};
961
+
962
+ const styleKeys = [
963
+ 'font-size',
964
+ 'font-weight',
965
+ 'color',
966
+ 'background-color'
967
+ ];
968
+
969
+ styleKeys.forEach((key) => {
970
+ const value = extractResponsiveValue(styles[key]);
971
+ if (value !== undefined) {
972
+ const camelKey = key.replace(/-([a-z])/g, (_, letter) =>
973
+ letter.toUpperCase()
974
+ ) as keyof TextSliderSectionStyles;
975
+ sectionStyles[camelKey] = value;
976
+ }
977
+ });
978
+
979
+ result.sectionStyles = sectionStyles;
980
+ }
981
+ }
982
+ } catch {
983
+ // Ignore parse errors
984
+ }
985
+ }
986
+
987
+ return result;
988
+ }
989
+
990
+ // ============================================
991
+ // Announcement Bar Settings
992
+ // ============================================
993
+
994
+ const HEADER_ANNOUNCEMENT_SECTION_ID = 'header-announcement-bar';
995
+
996
+ export interface HeaderAnnouncementSettings {
997
+ sectionStyles?: Record<string, unknown>;
998
+ properties?: {
999
+ visible?: boolean;
1000
+ text?: string;
1001
+ link?: string;
1002
+ target?: string;
1003
+ };
1004
+ }
1005
+
1006
+ /**
1007
+ * Parse announcement bar settings from widget data (server-side)
1008
+ */
1009
+ export function parseServerAnnouncementSettings(
1010
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1011
+ widgetData: any
1012
+ ): HeaderAnnouncementSettings {
1013
+ const result: HeaderAnnouncementSettings = {};
1014
+
1015
+ const parseAnnouncementData = (data: any) => {
1016
+ if (!data) {
1017
+ return;
1018
+ }
1019
+
1020
+ if (data.properties) {
1021
+ const properties: HeaderAnnouncementSettings['properties'] = {};
1022
+
1023
+ if (data.properties.visible !== undefined) {
1024
+ const visible = extractResponsiveValue(data.properties.visible);
1025
+ properties.visible = visible === 'true' || Boolean(visible);
1026
+ }
1027
+
1028
+ if (data.properties.text !== undefined) {
1029
+ const text = extractResponsiveValue(data.properties.text);
1030
+ if (typeof text === 'string') {
1031
+ properties.text = text;
1032
+ }
1033
+ }
1034
+
1035
+ if (data.properties.link !== undefined) {
1036
+ const link = extractResponsiveValue(data.properties.link);
1037
+ if (typeof link === 'string') {
1038
+ properties.link = link;
1039
+ }
1040
+ }
1041
+
1042
+ if (data.properties.target !== undefined) {
1043
+ const target = extractResponsiveValue(data.properties.target);
1044
+ if (typeof target === 'string') {
1045
+ properties.target = target;
1046
+ }
1047
+ }
1048
+
1049
+ result.properties = properties;
1050
+ }
1051
+
1052
+ if (data.styles) {
1053
+ const styles = data.styles as Record<string, unknown>;
1054
+ const sectionStyles: Record<string, unknown> = {};
1055
+
1056
+ const styleKeys = [
1057
+ 'background-color',
1058
+ 'color',
1059
+ 'font-size',
1060
+ 'font-weight',
1061
+ 'text-align',
1062
+ 'padding-top',
1063
+ 'padding-bottom',
1064
+ 'padding-left',
1065
+ 'padding-right'
1066
+ ];
1067
+
1068
+ styleKeys.forEach((key) => {
1069
+ const value = extractResponsiveValue(styles[key]);
1070
+ if (value !== undefined) {
1071
+ const camelKey = key.replace(/-([a-z])/g, (_, letter) =>
1072
+ letter.toUpperCase()
1073
+ );
1074
+ sectionStyles[camelKey] = value;
1075
+ }
1076
+ });
1077
+
1078
+ result.sectionStyles = sectionStyles;
1079
+ }
1080
+ };
1081
+
1082
+ const attrs = widgetData?.attributes;
1083
+ const sectionData = attrs?.[HEADER_ANNOUNCEMENT_SECTION_ID];
1084
+
1085
+ try {
1086
+ const parsedAttributeData =
1087
+ typeof sectionData === 'string'
1088
+ ? JSON.parse(sectionData)
1089
+ : typeof sectionData === 'object' && 'value' in sectionData
1090
+ ? JSON.parse((sectionData as { value: string }).value)
1091
+ : null;
1092
+ if (parsedAttributeData) {
1093
+ parseAnnouncementData(parsedAttributeData);
1094
+ return result;
1095
+ }
1096
+ } catch {
1097
+ // Ignore parse errors for attributes and continue with schema metadata fallback
1098
+ }
1099
+
1100
+ const sectionSchemaMetadata =
1101
+ widgetData?.widget_type?.schema?.[HEADER_ANNOUNCEMENT_SECTION_ID]?.metadata;
1102
+ parseAnnouncementData(sectionSchemaMetadata);
1103
+
1104
+ return result;
1105
+ }