@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,251 @@
1
+ 'use client';
2
+
3
+ import type { CSSProperties } from 'react';
4
+ import clsx from 'clsx';
5
+ import { Link, Icon } from '@theme/components';
6
+ import { useDesignerFeatures } from '@akinon/next/components/theme-editor/hooks/use-designer-features';
7
+ import {
8
+ useFooterSocialDesigner,
9
+ FOOTER_SOCIAL_ICONS_BLOCK_ID,
10
+ type SocialIconItem
11
+ } from '../views/footer/footer-social-context';
12
+ import {
13
+ FOOTER_PLACEHOLDER_ID,
14
+ FOOTER_SOCIAL_SECTION_ID
15
+ } from '../views/footer/native-widget-config';
16
+
17
+ const kebabToCamel = (str: string): string =>
18
+ str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
19
+
20
+ const normalizeStyles = (
21
+ styles: Record<string, unknown> | undefined
22
+ ): CSSProperties => {
23
+ if (!styles) return {};
24
+
25
+ const result: Record<string, string | number> = {};
26
+
27
+ for (const [key, value] of Object.entries(styles)) {
28
+ if (value == null) continue;
29
+
30
+ const camelKey = kebabToCamel(key);
31
+ let resolved: unknown = value;
32
+
33
+ if (typeof value === 'object' && !Array.isArray(value)) {
34
+ const responsive = value as Record<string, unknown>;
35
+ resolved = responsive.desktop ?? responsive.mobile ?? responsive.tablet;
36
+ }
37
+
38
+ if (resolved != null) {
39
+ if (typeof resolved === 'number') {
40
+ result[camelKey] = resolved;
41
+ } else if (typeof resolved === 'string') {
42
+ result[camelKey] = resolved;
43
+ }
44
+ }
45
+ }
46
+
47
+ return result as CSSProperties;
48
+ };
49
+
50
+ // Legacy types for backwards compatibility
51
+ type SocialNetwork = {
52
+ key: string;
53
+ url: string;
54
+ icon: string;
55
+ };
56
+
57
+ type SocialItem = {
58
+ value: {
59
+ icon_class?: string;
60
+ site_name: string;
61
+ redirect_url: string;
62
+ is_target_blank: string;
63
+ };
64
+ };
65
+
66
+ interface FooterSocialClientProps {
67
+ activeNetworks: SocialNetwork[];
68
+ socialItems: SocialItem[];
69
+ }
70
+
71
+ // Resolve responsive value to actual value
72
+ const resolveValue = <T,>(value: unknown, defaultValue: T): T => {
73
+ if (value == null) return defaultValue;
74
+ if (typeof value === 'object' && !Array.isArray(value)) {
75
+ const responsive = value as Record<string, unknown>;
76
+ const resolved =
77
+ responsive.desktop ?? responsive.mobile ?? responsive.tablet;
78
+ return (resolved as T) ?? defaultValue;
79
+ }
80
+ return value as T;
81
+ };
82
+
83
+ export default function FooterSocialClient({
84
+ activeNetworks,
85
+ socialItems
86
+ }: FooterSocialClientProps) {
87
+ const { isDesigner, selectedBlockId, getBlock, blockVersion } =
88
+ useFooterSocialDesigner();
89
+
90
+ const iconsBlock =
91
+ blockVersion >= 0 ? getBlock(FOOTER_SOCIAL_ICONS_BLOCK_ID) : undefined;
92
+ const iconStylesRaw = iconsBlock?.styles as
93
+ | Record<string, unknown>
94
+ | undefined;
95
+ const iconStyles = normalizeStyles(iconStylesRaw);
96
+
97
+ // Get custom icons from properties (new social-icons-list format)
98
+ const blockProperties = iconsBlock?.properties as
99
+ | Record<string, unknown>
100
+ | undefined;
101
+ const customIcons = resolveValue<SocialIconItem[]>(
102
+ blockProperties?.icons,
103
+ []
104
+ );
105
+
106
+ // Gap between icons from styles
107
+ const iconGap = resolveValue<string | number>(iconStylesRaw?.gap, 16);
108
+
109
+ const { handleClick: handleIconsClick } = useDesignerFeatures({
110
+ blockId: FOOTER_SOCIAL_ICONS_BLOCK_ID,
111
+ placeholderId: FOOTER_PLACEHOLDER_ID,
112
+ sectionId: FOOTER_SOCIAL_SECTION_ID,
113
+ isDesigner,
114
+ blockInfo: {
115
+ id: FOOTER_SOCIAL_ICONS_BLOCK_ID,
116
+ type: 'social-icons-list',
117
+ label: 'Social Icons'
118
+ }
119
+ });
120
+
121
+ const isIconsSelected = selectedBlockId === FOOTER_SOCIAL_ICONS_BLOCK_ID;
122
+
123
+ // Process SVG to apply color - replace fill/stroke attributes with the chosen color
124
+ const processSvgColor = (svgHtml: string, color: string | undefined) => {
125
+ if (!color) return svgHtml;
126
+ // Replace fill and stroke attributes (but not fill="none" which is used for transparency)
127
+ let processed = svgHtml
128
+ .replace(/fill="(?!none)[^"]*"/gi, `fill="${color}"`)
129
+ .replace(/stroke="(?!none)[^"]*"/gi, `stroke="${color}"`);
130
+ // If no fill attribute exists, add one to the svg element
131
+ if (!processed.includes('fill=')) {
132
+ processed = processed.replace('<svg', `<svg fill="${color}"`);
133
+ }
134
+ return processed;
135
+ };
136
+
137
+ // Render custom icons from theme editor (new format)
138
+ if (customIcons.length > 0) {
139
+ return (
140
+ <div
141
+ className={clsx(
142
+ 'flex items-center',
143
+ isDesigner && 'cursor-pointer',
144
+ isIconsSelected && 'ring-2 ring-blue-500 ring-offset-2 rounded'
145
+ )}
146
+ style={{ gap: `${iconGap}px` }}
147
+ data-block-id={isDesigner ? FOOTER_SOCIAL_ICONS_BLOCK_ID : undefined}
148
+ onClick={isDesigner ? handleIconsClick : undefined}
149
+ >
150
+ {customIcons.map((item, i) => (
151
+ <Link key={i} href={item.link} target="_blank">
152
+ <Icon
153
+ size={item.size || 24}
154
+ name={item.icon}
155
+ style={{ color: item.color }}
156
+ />
157
+ </Link>
158
+ ))}
159
+ </div>
160
+ );
161
+ }
162
+
163
+ // Fallback: Render legacy activeNetworks (SVG icons from theme-config)
164
+ if (activeNetworks.length > 0) {
165
+ // Legacy: use global color/size from styles
166
+ const legacyIconColor = iconStyles.color as string | undefined;
167
+ const legacyIconSize = resolveValue<number>(iconStylesRaw?.size, 18);
168
+
169
+ return (
170
+ <div
171
+ className={clsx(
172
+ 'flex items-center',
173
+ isDesigner && 'cursor-pointer',
174
+ isIconsSelected && 'ring-2 ring-blue-500 ring-offset-2 rounded'
175
+ )}
176
+ style={{ gap: `${iconGap}px` }}
177
+ data-block-id={isDesigner ? FOOTER_SOCIAL_ICONS_BLOCK_ID : undefined}
178
+ onClick={isDesigner ? handleIconsClick : undefined}
179
+ >
180
+ {activeNetworks.map((network, i) => (
181
+ <Link key={i} href={network.url} target="_blank">
182
+ <div
183
+ className="flex items-center justify-center [&>svg]:w-full [&>svg]:h-full"
184
+ style={{
185
+ width: legacyIconSize,
186
+ height: legacyIconSize
187
+ }}
188
+ dangerouslySetInnerHTML={{
189
+ __html: processSvgColor(network.icon, legacyIconColor)
190
+ }}
191
+ />
192
+ </Link>
193
+ ))}
194
+ </div>
195
+ );
196
+ }
197
+
198
+ // Fallback: Render legacy socialItems (font icons from footer-social widget)
199
+ if (socialItems.length > 0) {
200
+ const legacyIconColor = iconStyles.color as string | undefined;
201
+ const legacyIconSize = resolveValue<number>(iconStylesRaw?.size, 18);
202
+
203
+ return (
204
+ <div
205
+ className={clsx(
206
+ 'flex items-center',
207
+ isDesigner && 'cursor-pointer',
208
+ isIconsSelected && 'ring-2 ring-blue-500 ring-offset-2 rounded'
209
+ )}
210
+ style={{ gap: `${iconGap}px` }}
211
+ data-block-id={isDesigner ? FOOTER_SOCIAL_ICONS_BLOCK_ID : undefined}
212
+ onClick={isDesigner ? handleIconsClick : undefined}
213
+ >
214
+ {socialItems.map((item, i) => (
215
+ <Link
216
+ key={i}
217
+ href={item?.value?.redirect_url}
218
+ target={
219
+ item?.value?.is_target_blank === 'true' ? '_blank' : '_self'
220
+ }
221
+ style={{ color: legacyIconColor }}
222
+ >
223
+ <Icon
224
+ size={legacyIconSize}
225
+ name={item?.value?.icon_class?.replace('pz-icon-', '')}
226
+ />
227
+ </Link>
228
+ ))}
229
+ </div>
230
+ );
231
+ }
232
+
233
+ // Designer mode: Show placeholder when no icons
234
+ if (isDesigner) {
235
+ return (
236
+ <div
237
+ className={clsx(
238
+ 'flex items-center gap-4 py-2 px-4 border border-dashed border-gray-400 rounded text-gray-400 text-sm',
239
+ 'cursor-pointer',
240
+ isIconsSelected && 'ring-2 ring-blue-500 ring-offset-2'
241
+ )}
242
+ data-block-id={FOOTER_SOCIAL_ICONS_BLOCK_ID}
243
+ onClick={handleIconsClick}
244
+ >
245
+ Click to add social icons
246
+ </div>
247
+ );
248
+ }
249
+
250
+ return null;
251
+ }
@@ -1,7 +1,11 @@
1
1
  import 'server-only';
2
2
 
3
- import { Link, Icon } from '@theme/components';
4
3
  import { getWidgetData } from '@akinon/next/data/server';
4
+ import FooterSocialClient from './footer-social-client';
5
+
6
+ type ThemeConfig = {
7
+ theme_settings: string | object;
8
+ };
5
9
 
6
10
  type TargetBlank = {
7
11
  value: string;
@@ -31,23 +35,50 @@ type FooterSocialType = {
31
35
  };
32
36
 
33
37
  export default async function FooterSocial() {
38
+ const themeConfig = await getWidgetData<ThemeConfig>({
39
+ slug: 'theme-config'
40
+ });
41
+
42
+ let socialNetworks = {};
43
+ try {
44
+ if (themeConfig?.attributes?.theme_settings) {
45
+ const settingsRaw = themeConfig.attributes.theme_settings;
46
+ let settings;
47
+
48
+ if (typeof settingsRaw === 'string') {
49
+ settings = JSON.parse(settingsRaw);
50
+ } else if (
51
+ typeof settingsRaw === 'object' &&
52
+ settingsRaw !== null &&
53
+ 'value' in settingsRaw
54
+ ) {
55
+ const value = (settingsRaw as any).value;
56
+ settings = typeof value === 'string' ? JSON.parse(value) : value;
57
+ } else {
58
+ settings = settingsRaw;
59
+ }
60
+
61
+ socialNetworks = settings.socialNetworks || {};
62
+ }
63
+ } catch (e) {
64
+ console.error('Error parsing theme settings:', e);
65
+ }
66
+
67
+ const activeNetworks = Object.entries(socialNetworks)
68
+ .filter(([_, value]: [string, any]) => value.icon)
69
+ .map(([key, value]: [string, any]) => ({
70
+ key,
71
+ url: value.url || '#',
72
+ icon: value.icon
73
+ }));
74
+
34
75
  const data = await getWidgetData<FooterSocialType>({ slug: 'footer-social' });
76
+ const socialItems = data?.attributes?.social_items || [];
35
77
 
36
78
  return (
37
- <div className="flex flex-wrap items-center justify-around py-4 border-t border-gray md:justify-center">
38
- {data?.attributes?.social_items?.map((item, i) => (
39
- <Link
40
- key={i}
41
- href={item?.value?.redirect_url}
42
- className="p-2 border rounded-full border-gray md:mr-1 md:last:mr-0"
43
- target={item?.value?.is_target_blank === 'true' ? '_blank' : '_self'}
44
- >
45
- <Icon
46
- size={18}
47
- name={item?.value?.icon_class?.replace('pz-icon-', '')}
48
- />
49
- </Link>
50
- ))}
51
- </div>
79
+ <FooterSocialClient
80
+ activeNetworks={activeNetworks}
81
+ socialItems={socialItems}
82
+ />
52
83
  );
53
84
  }
@@ -9,6 +9,7 @@ import { useEmailSubscriptionMutation } from '@akinon/next/data/client/misc';
9
9
  import { useGetWidgetQuery } from '@akinon/next/data/client/misc';
10
10
  import { Input, Button, Checkbox, Modal } from '@theme/components';
11
11
  import { useLocalization } from '@akinon/next/hooks';
12
+ import { Icon } from '@akinon/next/components';
12
13
 
13
14
  const subscriptionFormSchema = (t: (key: string) => string) =>
14
15
  yup.object().shape({
@@ -43,29 +44,31 @@ export default function FooterSubscriptionForm() {
43
44
 
44
45
  return (
45
46
  <div>
46
- <div className="flex mb-5">
47
- <Input
48
- id="footer-subscription-email"
49
- type="email"
50
- placeholder={t('common.newsletter_signup.email_address')}
51
- className="text-sm h-7 w-44"
52
- {...register('email')}
53
- error={errors.email}
54
- data-testid="newsletter-input"
55
- />
47
+ <div className="flex items-center justify-between border border-[#7d7d7d] px-4 py-[14px] gap-4 w-[331px]">
48
+ <div className="flex-1">
49
+ <Input
50
+ id="footer-subscription-email"
51
+ type="email"
52
+ placeholder={t('common.newsletter_signup.email_address')}
53
+ className="border-0 h-auto p-0 flex-1"
54
+ {...register('email')}
55
+ error={errors.email}
56
+ data-testid="newsletter-input"
57
+ />
58
+ </div>
56
59
 
57
60
  <Button
58
61
  type="submit"
59
62
  appearance="outlined"
60
- className="text-xs font-semibold h-7"
63
+ className="bg-transparent hover:bg-transparent p-0 h-auto"
61
64
  data-testid="newsletter-submit"
62
65
  onClick={handleSubmit(onSubmit)}
63
66
  >
64
- {t('common.newsletter_signup.submit')}
67
+ <Icon name="arrow-right" size={16} className="text-black" />
65
68
  </Button>
66
69
  </div>
67
70
 
68
- <Checkbox
71
+ {/* <Checkbox
69
72
  {...register('subscribe_contract')}
70
73
  error={errors.subscribe_contract}
71
74
  data-testid="newsletter-agreement-checkbox"
@@ -104,7 +107,7 @@ export default function FooterSubscriptionForm() {
104
107
  {t('common.newsletter_signup.close')}
105
108
  </Button>
106
109
  </Modal>
107
- </Checkbox>
110
+ </Checkbox> */}
108
111
  </div>
109
112
  );
110
113
  }
@@ -1,30 +1,196 @@
1
- import 'server-only';
1
+ 'use client';
2
2
 
3
- import { getWidgetData } from '@akinon/next/data/server';
3
+ import type { CSSProperties } from 'react';
4
+ import clsx from 'clsx';
5
+ import { useDesignerFeatures } from '@akinon/next/components/theme-editor/hooks/use-designer-features';
4
6
 
5
7
  import FooterSubscriptionForm from './footer-subscription-form';
8
+ import {
9
+ FOOTER_PLACEHOLDER_ID,
10
+ FOOTER_SUBSCRIPTION_SECTION_ID
11
+ } from '../../views/footer/native-widget-config';
12
+ import {
13
+ FOOTER_SUBSCRIPTION_BLOCKS,
14
+ FOOTER_SUBSCRIPTION_DEFAULT_CONTENT
15
+ } from '../../views/footer/subscription-settings';
16
+ import { useFooterSubscriptionDesigner } from '../../views/footer/footer-subscription-context';
6
17
 
7
- type FooterSubscriptionType = {
8
- description: {
9
- value: string;
10
- };
11
- title: {
12
- value: string;
13
- };
18
+ type FooterSubscriptionProps = {
19
+ title?: string;
20
+ description?: string;
21
+ };
22
+
23
+ type FooterSubscriptionBlockConfig =
24
+ typeof FOOTER_SUBSCRIPTION_BLOCKS[keyof typeof FOOTER_SUBSCRIPTION_BLOCKS];
25
+
26
+ const extractTextValue = (value: unknown): string | undefined => {
27
+ if (value == null) {
28
+ return undefined;
29
+ }
30
+
31
+ if (typeof value === 'string' || typeof value === 'number') {
32
+ return String(value);
33
+ }
34
+
35
+ if (typeof value === 'object') {
36
+ const record = value as Record<string, unknown>;
37
+ const candidateKeys = [
38
+ 'content',
39
+ 'value',
40
+ 'text',
41
+ 'label',
42
+ 'default',
43
+ 'desktop',
44
+ 'mobile',
45
+ 'tablet'
46
+ ];
47
+
48
+ for (const key of candidateKeys) {
49
+ const candidate = record[key];
50
+ if (typeof candidate === 'string' && candidate.trim().length > 0) {
51
+ return candidate;
52
+ }
53
+ }
54
+
55
+ for (const objValue of Object.values(record)) {
56
+ if (typeof objValue === 'string' && objValue.trim().length > 0) {
57
+ return objValue;
58
+ }
59
+ }
60
+ }
61
+
62
+ return undefined;
63
+ };
64
+
65
+ const kebabToCamel = (str: string): string =>
66
+ str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
67
+
68
+ const normalizeStyles = (
69
+ styles: Record<string, unknown> | undefined
70
+ ): CSSProperties => {
71
+ if (!styles) return {};
72
+
73
+ const result: Record<string, string | number> = {};
74
+
75
+ for (const [key, value] of Object.entries(styles)) {
76
+ if (value == null) continue;
77
+
78
+ const camelKey = kebabToCamel(key);
79
+ let resolved: unknown = value;
80
+
81
+ if (typeof value === 'object' && !Array.isArray(value)) {
82
+ const responsive = value as Record<string, unknown>;
83
+ resolved = responsive.desktop ?? responsive.mobile ?? responsive.tablet;
84
+ }
85
+
86
+ if (resolved != null) {
87
+ if (typeof resolved === 'number') {
88
+ result[camelKey] = resolved;
89
+ } else if (typeof resolved === 'string') {
90
+ result[camelKey] = resolved;
91
+ }
92
+ }
93
+ }
94
+
95
+ return result as CSSProperties;
14
96
  };
15
97
 
16
- export default async function FooterSubscription() {
17
- const data = await getWidgetData<FooterSubscriptionType>({
18
- slug: 'footer-subscription'
98
+ function useSubscriptionBlock(blockConfig: FooterSubscriptionBlockConfig) {
99
+ const { isDesigner, selectedBlockId, getBlock, blockVersion } =
100
+ useFooterSubscriptionDesigner();
101
+ const blockState = blockVersion >= 0 ? getBlock(blockConfig.id) : undefined;
102
+ const styles = normalizeStyles(blockState?.styles as Record<string, unknown>);
103
+ const { handleClick } = useDesignerFeatures({
104
+ blockId: blockConfig.id,
105
+ placeholderId: FOOTER_PLACEHOLDER_ID,
106
+ sectionId: FOOTER_SUBSCRIPTION_SECTION_ID,
107
+ isDesigner,
108
+ blockInfo: {
109
+ id: blockConfig.id,
110
+ type: blockConfig.type,
111
+ label: blockConfig.label
112
+ }
19
113
  });
20
114
 
115
+ return {
116
+ styles,
117
+ isDesigner,
118
+ isSelected: selectedBlockId === blockConfig.id,
119
+ onClick: isDesigner ? handleClick : undefined,
120
+ content: extractTextValue(blockState?.value)
121
+ };
122
+ }
123
+
124
+ export default function FooterSubscription({
125
+ title,
126
+ description
127
+ }: FooterSubscriptionProps) {
128
+ const titleBlock = useSubscriptionBlock(FOOTER_SUBSCRIPTION_BLOCKS.TITLE);
129
+ const descriptionBlock = useSubscriptionBlock(
130
+ FOOTER_SUBSCRIPTION_BLOCKS.DESCRIPTION
131
+ );
132
+ const formBlock = useSubscriptionBlock(FOOTER_SUBSCRIPTION_BLOCKS.FORM);
133
+ const displayTitle =
134
+ titleBlock.content || title || FOOTER_SUBSCRIPTION_DEFAULT_CONTENT.title;
135
+ const displayDescription =
136
+ descriptionBlock.content ||
137
+ description ||
138
+ FOOTER_SUBSCRIPTION_DEFAULT_CONTENT.description;
139
+ const shouldRenderDescription =
140
+ descriptionBlock.isDesigner || Boolean(displayDescription);
141
+
21
142
  return (
22
- <div className="py-4 border-t md:border-t-0 lg:pl-7">
23
- <h3 className="mb-1 text-xs font-medium">
24
- {data?.attributes?.title?.value}
143
+ <div className="w-full max-w-[331px]">
144
+ <h3
145
+ style={titleBlock.styles}
146
+ data-block-id={
147
+ titleBlock.isDesigner
148
+ ? FOOTER_SUBSCRIPTION_BLOCKS.TITLE.id
149
+ : undefined
150
+ }
151
+ onClick={titleBlock.onClick}
152
+ className={clsx(
153
+ 'mb-4 text-lg text-[#121212]',
154
+ titleBlock.isDesigner && 'cursor-pointer',
155
+ titleBlock.isSelected && 'ring-2 ring-blue-500 ring-offset-2 rounded'
156
+ )}
157
+ >
158
+ {displayTitle}
25
159
  </h3>
26
- <p className="mb-2 text-xs">{data?.attributes?.description?.value}</p>
27
- <FooterSubscriptionForm />
160
+
161
+ {shouldRenderDescription && (
162
+ <p
163
+ style={descriptionBlock.styles}
164
+ data-block-id={
165
+ descriptionBlock.isDesigner
166
+ ? FOOTER_SUBSCRIPTION_BLOCKS.DESCRIPTION.id
167
+ : undefined
168
+ }
169
+ onClick={descriptionBlock.onClick}
170
+ className={clsx(
171
+ 'text-sm text-[#121212] text-opacity-75 mb-6 leading-relaxed',
172
+ descriptionBlock.isDesigner && 'cursor-pointer',
173
+ descriptionBlock.isSelected &&
174
+ 'ring-2 ring-blue-500 ring-offset-2 rounded'
175
+ )}
176
+ >
177
+ {displayDescription}
178
+ </p>
179
+ )}
180
+
181
+ <div
182
+ style={formBlock.styles}
183
+ data-block-id={
184
+ formBlock.isDesigner ? FOOTER_SUBSCRIPTION_BLOCKS.FORM.id : undefined
185
+ }
186
+ onClick={formBlock.onClick}
187
+ className={clsx(
188
+ formBlock.isDesigner && 'cursor-pointer inline-block',
189
+ formBlock.isSelected && 'ring-2 ring-blue-500 ring-offset-2 rounded'
190
+ )}
191
+ >
192
+ <FooterSubscriptionForm />
193
+ </div>
28
194
  </div>
29
195
  );
30
196
  }