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

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 (138) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/app-template/CHANGELOG.md +138 -0
  3. package/app-template/next.config.mjs +0 -1
  4. package/app-template/package.json +31 -30
  5. package/app-template/src/app/[pz]/[...prettyurl]/page.tsx +2 -2
  6. package/app-template/src/app/[pz]/account/layout.tsx +2 -1
  7. package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/blog/[slug]/page.tsx +4 -2
  8. package/app-template/src/app/[pz]/category/[pk]/page.tsx +11 -1
  9. package/app-template/src/app/[pz]/group-product/[pk]/page.tsx +2 -2
  10. package/app-template/src/app/[pz]/layout.tsx +3 -1
  11. package/app-template/src/app/[pz]/list/page.tsx +11 -1
  12. package/app-template/src/app/[pz]/page.tsx +13 -35
  13. package/app-template/src/app/[pz]/pages/[slug]/page.tsx +19 -0
  14. package/app-template/src/app/[pz]/product/[pk]/page.tsx +2 -2
  15. package/app-template/src/app/api/barcode-search/route.ts +1 -1
  16. package/app-template/src/app/api/cache/route.ts +1 -1
  17. package/app-template/src/app/api/image-proxy/route.ts +1 -1
  18. package/app-template/src/app/api/logout/route.ts +1 -1
  19. package/app-template/src/app/api/product-categories/route.ts +1 -1
  20. package/app-template/src/app/api/similar-product-list/route.ts +1 -1
  21. package/app-template/src/app/api/similar-products/route.ts +1 -1
  22. package/app-template/src/app/api/virtual-try-on/route.ts +1 -1
  23. package/app-template/src/app/api/web-vitals/route.ts +1 -1
  24. package/app-template/src/components/quantity-selector.tsx +16 -4
  25. package/app-template/src/components/widget-content.tsx +3 -3
  26. package/app-template/src/routes/index.ts +6 -6
  27. package/app-template/src/utils/__tests__/theme-page-context.test.ts +145 -0
  28. package/app-template/src/utils/theme-page-context.ts +309 -0
  29. package/app-template/src/views/basket/basket-item.tsx +107 -691
  30. package/app-template/src/views/basket/index.ts +0 -2
  31. package/app-template/src/views/basket/summary.tsx +75 -496
  32. package/app-template/src/views/breadcrumb.tsx +38 -13
  33. package/app-template/src/views/category/category-header.tsx +66 -289
  34. package/app-template/src/views/category/category-info.tsx +24 -173
  35. package/app-template/src/views/category/filters/index.tsx +48 -208
  36. package/app-template/src/views/category/layout.tsx +5 -7
  37. package/app-template/src/views/checkout/index.tsx +0 -5
  38. package/app-template/src/views/checkout/steps/payment/index.tsx +2 -5
  39. package/app-template/src/views/checkout/steps/payment/options/credit-card/index.tsx +1 -72
  40. package/app-template/src/views/checkout/steps/payment/payment-option-buttons.tsx +40 -171
  41. package/app-template/src/views/checkout/steps/shipping/address-box.tsx +12 -74
  42. package/app-template/src/views/checkout/steps/shipping/addresses.tsx +45 -128
  43. package/app-template/src/views/checkout/steps/shipping/shipping-options.tsx +27 -232
  44. package/app-template/src/views/checkout/summary.tsx +29 -303
  45. package/app-template/src/views/footer.tsx +13 -415
  46. package/app-template/src/views/guest-login/index.tsx +1 -1
  47. package/app-template/src/views/header/action-menu.tsx +45 -277
  48. package/app-template/src/views/header/band.tsx +21 -6
  49. package/app-template/src/views/header/index.tsx +47 -109
  50. package/app-template/src/views/header/mini-basket.tsx +45 -820
  51. package/app-template/src/views/header/navbar.tsx +111 -178
  52. package/app-template/src/views/header/search/index.tsx +32 -71
  53. package/app-template/src/views/header/search/results.tsx +65 -127
  54. package/app-template/src/views/product/accordion-wrapper.tsx +43 -135
  55. package/app-template/src/views/product/index.ts +1 -1
  56. package/app-template/src/views/product/layout.tsx +7 -2
  57. package/app-template/src/views/product/misc-buttons.tsx +25 -339
  58. package/app-template/src/views/product/product-actions.tsx +8 -137
  59. package/app-template/src/views/product/product-info.tsx +31 -69
  60. package/app-template/src/views/product/product-share.tsx +8 -11
  61. package/app-template/src/views/product/slider.tsx +79 -117
  62. package/app-template/src/views/product-item/index.tsx +46 -119
  63. package/app-template/src/widgets/footer-social.tsx +16 -47
  64. package/app-template/src/widgets/footer-subscription/index.tsx +17 -183
  65. package/dist/commands/plugins.js +23 -2
  66. package/package.json +1 -1
  67. package/app-template/src/app/[commerce]/[locale]/[currency]/pages/[slug]/page.tsx +0 -15
  68. package/app-template/src/views/basket/basket-summary-context.tsx +0 -560
  69. package/app-template/src/views/basket/designer-context.tsx +0 -617
  70. package/app-template/src/views/breadcrumb/breadcrumb-client.tsx +0 -190
  71. package/app-template/src/views/breadcrumb/breadcrumb-registrar.tsx +0 -286
  72. package/app-template/src/views/breadcrumb/constants.ts +0 -15
  73. package/app-template/src/views/breadcrumb/index.tsx +0 -127
  74. package/app-template/src/views/category/native-widget-context.tsx +0 -257
  75. package/app-template/src/views/category/product-list-registrar.tsx +0 -665
  76. package/app-template/src/views/checkout/checkout-address-registrar.tsx +0 -254
  77. package/app-template/src/views/checkout/checkout-buttons-registrar.tsx +0 -183
  78. package/app-template/src/views/checkout/checkout-delivery-method-registrar.tsx +0 -259
  79. package/app-template/src/views/checkout/checkout-payment-options-registrar.tsx +0 -253
  80. package/app-template/src/views/checkout/checkout-summary-registrar.tsx +0 -183
  81. package/app-template/src/views/checkout/constants.ts +0 -5
  82. package/app-template/src/views/checkout/steps/payment/options/masterpass-rest.tsx +0 -15
  83. package/app-template/src/views/checkout/steps/payment/options/saved-card.tsx +0 -18
  84. package/app-template/src/views/footer/footer-app-banner-context.tsx +0 -326
  85. package/app-template/src/views/footer/footer-bottom-context.tsx +0 -215
  86. package/app-template/src/views/footer/footer-bottom-wrapper.tsx +0 -74
  87. package/app-template/src/views/footer/footer-layout-constants.ts +0 -35
  88. package/app-template/src/views/footer/footer-layout-registrar.tsx +0 -342
  89. package/app-template/src/views/footer/footer-layout-switcher.tsx +0 -110
  90. package/app-template/src/views/footer/footer-menu-context.tsx +0 -211
  91. package/app-template/src/views/footer/footer-native-widgets.tsx +0 -60
  92. package/app-template/src/views/footer/footer-social-context.tsx +0 -254
  93. package/app-template/src/views/footer/footer-subscription-context.tsx +0 -210
  94. package/app-template/src/views/footer/footer-utils.ts +0 -43
  95. package/app-template/src/views/footer/footer-value-props-context.tsx +0 -326
  96. package/app-template/src/views/footer/logo-settings.ts +0 -183
  97. package/app-template/src/views/footer/native-widget-config.ts +0 -262
  98. package/app-template/src/views/footer/subscription-settings.ts +0 -122
  99. package/app-template/src/views/footer/use-footer-logo.ts +0 -162
  100. package/app-template/src/views/header/designer-context.tsx +0 -261
  101. package/app-template/src/views/header/header-announcement-registrar.tsx +0 -267
  102. package/app-template/src/views/header/header-client-wrapper.tsx +0 -496
  103. package/app-template/src/views/header/header-content.tsx +0 -1026
  104. package/app-template/src/views/header/header-currency-registrar.tsx +0 -348
  105. package/app-template/src/views/header/header-icons-context.tsx +0 -262
  106. package/app-template/src/views/header/header-language-registrar.tsx +0 -348
  107. package/app-template/src/views/header/header-layout-context.tsx +0 -143
  108. package/app-template/src/views/header/header-layout-registrar.tsx +0 -658
  109. package/app-template/src/views/header/header-logo-context.tsx +0 -228
  110. package/app-template/src/views/header/header-logo.tsx +0 -118
  111. package/app-template/src/views/header/header-mini-basket-context.tsx +0 -524
  112. package/app-template/src/views/header/header-search-registrar.tsx +0 -511
  113. package/app-template/src/views/header/header-text-slider-registrar.tsx +0 -382
  114. package/app-template/src/views/header/inline-search.tsx +0 -262
  115. package/app-template/src/views/header/navbar-menu-context.tsx +0 -219
  116. package/app-template/src/views/header/search/search-input.tsx +0 -61
  117. package/app-template/src/views/header/server-settings-parser.ts +0 -1105
  118. package/app-template/src/views/header/use-header-icons.ts +0 -241
  119. package/app-template/src/views/header/use-header-logo.ts +0 -213
  120. package/app-template/src/views/header/use-navbar-menu.ts +0 -179
  121. package/app-template/src/views/product/accordion-section.tsx +0 -61
  122. package/app-template/src/views/product/custom-button-group.tsx +0 -69
  123. package/app-template/src/views/product/favorites-button-section.tsx +0 -69
  124. package/app-template/src/views/product/find-in-store-section.tsx +0 -60
  125. package/app-template/src/views/product/product-info-section.tsx +0 -140
  126. package/app-template/src/views/product/quantity-section.tsx +0 -73
  127. package/app-template/src/views/product/sale-tag.tsx +0 -10
  128. package/app-template/src/views/product/share-section.tsx +0 -357
  129. package/app-template/src/views/product/variants-section.tsx +0 -126
  130. package/app-template/src/views/product-detail/constants.ts +0 -272
  131. package/app-template/src/views/product-detail/index.ts +0 -10
  132. package/app-template/src/views/product-detail/product-detail-registrar.tsx +0 -616
  133. package/app-template/src/widgets/footer-app-banner.tsx +0 -444
  134. package/app-template/src/widgets/footer-bottom.tsx +0 -127
  135. package/app-template/src/widgets/footer-menu-compact.tsx +0 -238
  136. package/app-template/src/widgets/footer-menu-two.tsx +0 -298
  137. package/app-template/src/widgets/footer-social-client.tsx +0 -251
  138. package/app-template/src/widgets/footer-value-props.tsx +0 -201
@@ -1,241 +0,0 @@
1
- 'use client';
2
-
3
- import { useEffect, useState, useRef } from 'react';
4
- import { useNativeWidgetData } from '@akinon/next/components/theme-editor/hooks/use-native-widget-data';
5
- import { useGetWidgetQuery } from '@akinon/next/data/client/misc';
6
-
7
- const THEME_CONFIG_SLUG = 'theme-config';
8
- const HEADER_ICONS_WIDGET_SLUG = 'header-icon-settings-2';
9
- const HEADER_PLACEHOLDER_ID = 'header';
10
- const HEADER_ICONS_SECTION_ID = 'header-icons';
11
-
12
- const BLOCK_IDS = {
13
- search: 'header-icon-search',
14
- profile: 'header-icon-profile',
15
- cart: 'header-icon-cart'
16
- } as const;
17
-
18
- const BLOCK_META = [
19
- { id: BLOCK_IDS.search, type: 'icon-button', label: 'Search Icon' },
20
- { id: BLOCK_IDS.profile, type: 'icon-button', label: 'Profile Icon' },
21
- { id: BLOCK_IDS.cart, type: 'icon-button', label: 'Cart Icon' }
22
- ];
23
-
24
- const DEFAULT_ICON_SIZE = 20;
25
- const DEFAULT_ICON_COLOR = 'currentColor';
26
-
27
- interface IconSettings {
28
- size: number;
29
- color: string;
30
- }
31
-
32
- interface HeaderIconSettings {
33
- search: IconSettings;
34
- profile: IconSettings;
35
- cart: IconSettings;
36
- }
37
-
38
- interface ThemeSettings {
39
- headerIcons?: {
40
- search?: { size?: number; color?: string };
41
- profile?: { size?: number; color?: string };
42
- cart?: { size?: number; color?: string };
43
- };
44
- }
45
-
46
- const defaultSettings: HeaderIconSettings = {
47
- search: { size: DEFAULT_ICON_SIZE, color: DEFAULT_ICON_COLOR },
48
- profile: { size: DEFAULT_ICON_SIZE, color: DEFAULT_ICON_COLOR },
49
- cart: { size: DEFAULT_ICON_SIZE, color: DEFAULT_ICON_COLOR }
50
- };
51
-
52
- function parseHeaderIconSettings(
53
- themeSettings?: ThemeSettings
54
- ): HeaderIconSettings {
55
- if (!themeSettings?.headerIcons) {
56
- return defaultSettings;
57
- }
58
-
59
- const { headerIcons } = themeSettings;
60
-
61
- return {
62
- search: {
63
- size: headerIcons.search?.size ?? DEFAULT_ICON_SIZE,
64
- color: headerIcons.search?.color ?? DEFAULT_ICON_COLOR
65
- },
66
- profile: {
67
- size: headerIcons.profile?.size ?? DEFAULT_ICON_SIZE,
68
- color: headerIcons.profile?.color ?? DEFAULT_ICON_COLOR
69
- },
70
- cart: {
71
- size: headerIcons.cart?.size ?? DEFAULT_ICON_SIZE,
72
- color: headerIcons.cart?.color ?? DEFAULT_ICON_COLOR
73
- }
74
- };
75
- }
76
-
77
- function parseBlockStyles(
78
- block:
79
- | { styles?: Record<string, unknown>; properties?: Record<string, unknown> }
80
- | undefined
81
- ): IconSettings | null {
82
- if (!block) return null;
83
-
84
- const hasColor = block.styles?.color;
85
- const hasSize = block.properties?.iconSize;
86
-
87
- if (!hasColor && !hasSize) return null;
88
-
89
- let color: string | undefined;
90
- if (hasColor) {
91
- const colorValue = block.styles!.color as string | { desktop: string };
92
- color = typeof colorValue === 'object' ? colorValue.desktop : colorValue;
93
- }
94
-
95
- let size: number | undefined;
96
- if (hasSize) {
97
- const sizeValue = block.properties!.iconSize as
98
- | number
99
- | string
100
- | { desktop: number | string };
101
- const rawSize =
102
- typeof sizeValue === 'object' ? sizeValue.desktop : sizeValue;
103
- const parsed =
104
- typeof rawSize === 'string' ? parseInt(rawSize, 10) : rawSize;
105
- if (!isNaN(parsed)) {
106
- size = parsed;
107
- }
108
- }
109
-
110
- if (color === undefined && size === undefined) return null;
111
-
112
- return {
113
- size: size ?? DEFAULT_ICON_SIZE,
114
- color: color ?? DEFAULT_ICON_COLOR
115
- };
116
- }
117
-
118
- export function useHeaderIconSettings(
119
- initialSettings?: HeaderIconSettings
120
- ): HeaderIconSettings {
121
- const isDesignerRef = useRef(false);
122
- const [isDesignerChecked, setIsDesignerChecked] = useState(false);
123
-
124
- useEffect(() => {
125
- if (typeof window === 'undefined') return;
126
- isDesignerRef.current = window.self !== window.top;
127
- setIsDesignerChecked(true);
128
- }, []);
129
-
130
- const isDesigner = isDesignerRef.current;
131
-
132
- const { data: themeConfigData } = useGetWidgetQuery(THEME_CONFIG_SLUG, {
133
- skip: !isDesignerChecked || isDesigner
134
- });
135
-
136
- const widgetData = useNativeWidgetData({
137
- widgetSlug: HEADER_ICONS_WIDGET_SLUG,
138
- sectionId: HEADER_ICONS_SECTION_ID,
139
- skip: !isDesignerChecked || isDesigner,
140
- blockMeta: BLOCK_META
141
- });
142
-
143
- const [settings, setSettings] = useState<HeaderIconSettings>(
144
- initialSettings || defaultSettings
145
- );
146
- const hasInitialSettings = !!initialSettings;
147
-
148
- useEffect(() => {
149
- if (!isDesignerChecked) return;
150
- if (isDesigner) return;
151
- if (hasInitialSettings) return;
152
- if (widgetData.isLoading && !themeConfigData) return;
153
-
154
- let newSettings = { ...defaultSettings };
155
-
156
- if (themeConfigData?.attributes?.theme_settings?.value) {
157
- try {
158
- const themeSettings: ThemeSettings = JSON.parse(
159
- themeConfigData.attributes.theme_settings.value
160
- );
161
- newSettings = parseHeaderIconSettings(themeSettings);
162
- } catch {
163
- // ignore
164
- }
165
- }
166
-
167
- Object.keys(BLOCK_IDS).forEach((key) => {
168
- const blockId = BLOCK_IDS[key as keyof typeof BLOCK_IDS];
169
- const block = widgetData.getBlock(blockId);
170
- const parsed = parseBlockStyles(block);
171
-
172
- if (parsed) {
173
- newSettings[key as keyof HeaderIconSettings] = parsed;
174
- }
175
- });
176
-
177
- setSettings(newSettings);
178
- }, [
179
- themeConfigData,
180
- widgetData,
181
- isDesignerChecked,
182
- isDesigner,
183
- hasInitialSettings
184
- ]);
185
-
186
- useEffect(() => {
187
- const handleMessage = (event: MessageEvent) => {
188
- const { type, data } = event.data || {};
189
-
190
- if (type === 'UPDATE_THEME_SETTINGS' && data?.settings?.headerIcons) {
191
- setSettings(parseHeaderIconSettings(data.settings));
192
- return;
193
- }
194
-
195
- if (
196
- (type === 'UPDATE_THEME' || type === 'LOAD_THEME') &&
197
- data?.theme?.placeholders
198
- ) {
199
- const placeholder = data.theme.placeholders?.find(
200
- (p: { slug: string }) => p.slug === HEADER_PLACEHOLDER_ID
201
- );
202
-
203
- const headerIconsSection = placeholder?.sections?.find(
204
- (s: { id: string }) => s.id === HEADER_ICONS_SECTION_ID
205
- );
206
-
207
- if (headerIconsSection?.blocks) {
208
- const blocks = headerIconsSection.blocks;
209
-
210
- setSettings((prev) => {
211
- const newSettings = { ...prev };
212
- let hasUpdates = false;
213
-
214
- Object.keys(BLOCK_IDS).forEach((key) => {
215
- const blockId = BLOCK_IDS[key as keyof typeof BLOCK_IDS];
216
- const block = blocks.find(
217
- (b: { id: string }) => b.id === blockId
218
- );
219
- const parsed = parseBlockStyles(block);
220
-
221
- if (parsed) {
222
- newSettings[key as keyof HeaderIconSettings] = parsed;
223
- hasUpdates = true;
224
- }
225
- });
226
-
227
- return hasUpdates ? newSettings : prev;
228
- });
229
- }
230
- }
231
- };
232
-
233
- window.addEventListener('message', handleMessage);
234
- return () => window.removeEventListener('message', handleMessage);
235
- }, []);
236
-
237
- return settings;
238
- }
239
-
240
- export { DEFAULT_ICON_SIZE, DEFAULT_ICON_COLOR };
241
- export type { IconSettings, HeaderIconSettings };
@@ -1,213 +0,0 @@
1
- 'use client';
2
-
3
- import { useEffect, useState, useRef } from 'react';
4
- import { useNativeWidgetData } from '@akinon/next/components/theme-editor/hooks/use-native-widget-data';
5
- import { useGetWidgetQuery } from '@akinon/next/data/client/misc';
6
-
7
- const THEME_CONFIG_SLUG = 'theme-config';
8
- const HEADER_LOGO_WIDGET_SLUG = 'header-logo-settings-2';
9
- const HEADER_PLACEHOLDER_ID = 'header';
10
- const HEADER_LOGO_SECTION_ID = 'header-logo';
11
- const HEADER_LOGO_BLOCK_ID = 'header-logo-image';
12
-
13
- const DEFAULT_LOGO_SRC = '/logo.svg';
14
- const DEFAULT_LOGO_WIDTH = 120;
15
- const DEFAULT_LOGO_HEIGHT = 40;
16
-
17
- const BLOCK_META = [
18
- { id: HEADER_LOGO_BLOCK_ID, type: 'image', label: 'Logo Image' }
19
- ];
20
-
21
- interface LogoSettings {
22
- src: string;
23
- width: number | string;
24
- height: number | string;
25
- maxWidth?: string;
26
- maxHeight?: string;
27
- objectFit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';
28
- }
29
-
30
- const defaultSettings: LogoSettings = {
31
- src: DEFAULT_LOGO_SRC,
32
- width: DEFAULT_LOGO_WIDTH,
33
- height: DEFAULT_LOGO_HEIGHT,
34
- objectFit: 'contain'
35
- };
36
-
37
- function parseThemeConfigLogo(themeSettings?: {
38
- logo?: string;
39
- }): Partial<LogoSettings> {
40
- if (!themeSettings?.logo) {
41
- return {};
42
- }
43
- return { src: themeSettings.logo };
44
- }
45
-
46
- function parseBlockStyles(
47
- block: { styles?: Record<string, unknown>; value?: unknown } | undefined
48
- ): Partial<LogoSettings> {
49
- if (!block) return {};
50
-
51
- const result: Partial<LogoSettings> = {};
52
-
53
- if (block.value) {
54
- const value =
55
- typeof block.value === 'object'
56
- ? (block.value as { desktop?: string }).desktop
57
- : block.value;
58
- if (value) {
59
- result.src = value as string;
60
- }
61
- }
62
-
63
- if (block.styles) {
64
- const styles = block.styles;
65
-
66
- if (styles.width) {
67
- const width =
68
- typeof styles.width === 'object'
69
- ? (styles.width as { desktop: string | number }).desktop
70
- : styles.width;
71
- result.width = width as string | number;
72
- }
73
-
74
- if (styles.height) {
75
- const height =
76
- typeof styles.height === 'object'
77
- ? (styles.height as { desktop: string | number }).desktop
78
- : styles.height;
79
- result.height = height as string | number;
80
- }
81
-
82
- if (styles.maxWidth || styles['max-width']) {
83
- const maxWidth = (styles.maxWidth || styles['max-width']) as
84
- | string
85
- | { desktop: string };
86
- result.maxWidth =
87
- typeof maxWidth === 'object' ? maxWidth.desktop : maxWidth;
88
- }
89
-
90
- if (styles.maxHeight || styles['max-height']) {
91
- const maxHeight = (styles.maxHeight || styles['max-height']) as
92
- | string
93
- | { desktop: string };
94
- result.maxHeight =
95
- typeof maxHeight === 'object' ? maxHeight.desktop : maxHeight;
96
- }
97
-
98
- if (styles.objectFit || styles['object-fit']) {
99
- const objectFit = (styles.objectFit || styles['object-fit']) as
100
- | string
101
- | { desktop: string };
102
- result.objectFit = (
103
- typeof objectFit === 'object' ? objectFit.desktop : objectFit
104
- ) as LogoSettings['objectFit'];
105
- }
106
- }
107
-
108
- return result;
109
- }
110
-
111
- export function useHeaderLogoSettings(
112
- initialSettings?: LogoSettings
113
- ): LogoSettings {
114
- const isDesignerRef = useRef(false);
115
- const [isDesignerChecked, setIsDesignerChecked] = useState(false);
116
-
117
- useEffect(() => {
118
- if (typeof window === 'undefined') return;
119
- isDesignerRef.current = window.self !== window.top;
120
- setIsDesignerChecked(true);
121
- }, []);
122
-
123
- const isDesigner = isDesignerRef.current;
124
-
125
- const { data: themeConfigData } = useGetWidgetQuery(THEME_CONFIG_SLUG, {
126
- skip: !isDesignerChecked || isDesigner
127
- });
128
-
129
- const widgetData = useNativeWidgetData({
130
- widgetSlug: HEADER_LOGO_WIDGET_SLUG,
131
- sectionId: HEADER_LOGO_SECTION_ID,
132
- skip: !isDesignerChecked || isDesigner,
133
- blockMeta: BLOCK_META
134
- });
135
-
136
- const [settings, setSettings] = useState<LogoSettings>(
137
- initialSettings || defaultSettings
138
- );
139
- const hasInitialSettings = !!initialSettings;
140
-
141
- useEffect(() => {
142
- if (!isDesignerChecked) return;
143
- if (isDesigner) return;
144
- if (hasInitialSettings) return;
145
- if (widgetData.isLoading && !themeConfigData) return;
146
-
147
- let newSettings = { ...defaultSettings };
148
-
149
- if (themeConfigData?.attributes?.theme_settings?.value) {
150
- try {
151
- const themeSettings = JSON.parse(
152
- themeConfigData.attributes.theme_settings.value
153
- );
154
- const themeConfigLogo = parseThemeConfigLogo(themeSettings);
155
- newSettings = { ...newSettings, ...themeConfigLogo };
156
- } catch {
157
- // ignore
158
- }
159
- }
160
-
161
- const block = widgetData.getBlock(HEADER_LOGO_BLOCK_ID);
162
- if (block) {
163
- const parsed = parseBlockStyles(block);
164
- newSettings = { ...newSettings, ...parsed };
165
- }
166
-
167
- setSettings(newSettings);
168
- }, [
169
- themeConfigData,
170
- widgetData,
171
- isDesignerChecked,
172
- isDesigner,
173
- hasInitialSettings
174
- ]);
175
-
176
- useEffect(() => {
177
- const handleMessage = (event: MessageEvent) => {
178
- const { type, data } = event.data || {};
179
-
180
- if (
181
- (type === 'UPDATE_THEME' || type === 'LOAD_THEME') &&
182
- data?.theme?.placeholders
183
- ) {
184
- const placeholder = data.theme.placeholders?.find(
185
- (p: { slug: string }) => p.slug === HEADER_PLACEHOLDER_ID
186
- );
187
-
188
- const headerLogoSection = placeholder?.sections?.find(
189
- (s: { id: string }) => s.id === HEADER_LOGO_SECTION_ID
190
- );
191
-
192
- if (headerLogoSection?.blocks) {
193
- const block = headerLogoSection.blocks.find(
194
- (b: { id: string }) => b.id === HEADER_LOGO_BLOCK_ID
195
- );
196
- const parsed = parseBlockStyles(block);
197
-
198
- if (Object.keys(parsed).length > 0) {
199
- setSettings((prev) => ({ ...prev, ...parsed }));
200
- }
201
- }
202
- }
203
- };
204
-
205
- window.addEventListener('message', handleMessage);
206
- return () => window.removeEventListener('message', handleMessage);
207
- }, []);
208
-
209
- return settings;
210
- }
211
-
212
- export { DEFAULT_LOGO_SRC, DEFAULT_LOGO_WIDTH, DEFAULT_LOGO_HEIGHT };
213
- export type { LogoSettings };
@@ -1,179 +0,0 @@
1
- 'use client';
2
-
3
- import { useEffect, useState, useRef } from 'react';
4
- import { useNativeWidgetData } from '@akinon/next/components/theme-editor/hooks/use-native-widget-data';
5
-
6
- const NAVBAR_MENU_WIDGET_SLUG = 'header-nav-settings-2';
7
- const HEADER_PLACEHOLDER_ID = 'header';
8
- const NAVBAR_MENU_SECTION_ID = 'header-nav';
9
- const NAVBAR_MENU_BLOCK_ID = 'header-nav-menu-item';
10
-
11
- const DEFAULT_FONT_SIZE = '14px';
12
- const DEFAULT_COLOR = 'currentColor';
13
- const DEFAULT_HOVER_COLOR = '';
14
- const DEFAULT_FONT_WEIGHT = '400';
15
-
16
- const BLOCK_META = [
17
- { id: NAVBAR_MENU_BLOCK_ID, type: 'text', label: 'Menu Items' }
18
- ];
19
-
20
- export interface NavbarMenuSettings {
21
- fontSize: string;
22
- color: string;
23
- hoverColor: string;
24
- fontWeight: string;
25
- textTransform?: string;
26
- letterSpacing?: string;
27
- }
28
-
29
- const defaultSettings: NavbarMenuSettings = {
30
- fontSize: DEFAULT_FONT_SIZE,
31
- color: DEFAULT_COLOR,
32
- hoverColor: DEFAULT_HOVER_COLOR,
33
- fontWeight: DEFAULT_FONT_WEIGHT
34
- };
35
-
36
- function parseBlockStyles(
37
- blockData:
38
- | { styles?: Record<string, unknown>; properties?: Record<string, unknown> }
39
- | undefined
40
- ): Partial<NavbarMenuSettings> {
41
- if (!blockData) return {};
42
-
43
- const styles = blockData.styles || {};
44
- const result: Partial<NavbarMenuSettings> = {};
45
-
46
- if (styles.fontSize || styles['font-size']) {
47
- const fontSize = (styles.fontSize || styles['font-size']) as
48
- | string
49
- | { desktop: string };
50
- result.fontSize =
51
- typeof fontSize === 'object' ? fontSize.desktop : fontSize;
52
- }
53
-
54
- if (styles.color) {
55
- const color = styles.color as string | { desktop: string };
56
- result.color = typeof color === 'object' ? color.desktop : color;
57
- }
58
-
59
- if (styles.hoverColor || styles['hover-color']) {
60
- const hoverColor = (styles.hoverColor || styles['hover-color']) as
61
- | string
62
- | { desktop: string };
63
- result.hoverColor =
64
- typeof hoverColor === 'object' ? hoverColor.desktop : hoverColor;
65
- }
66
-
67
- if (styles.fontWeight || styles['font-weight']) {
68
- const fontWeight = (styles.fontWeight || styles['font-weight']) as
69
- | string
70
- | { desktop: string };
71
- result.fontWeight =
72
- typeof fontWeight === 'object' ? fontWeight.desktop : fontWeight;
73
- }
74
-
75
- if (styles.textTransform || styles['text-transform']) {
76
- const textTransform = (styles.textTransform || styles['text-transform']) as
77
- | string
78
- | { desktop: string };
79
- result.textTransform =
80
- typeof textTransform === 'object' ? textTransform.desktop : textTransform;
81
- }
82
-
83
- if (styles.letterSpacing || styles['letter-spacing']) {
84
- const letterSpacing = (styles.letterSpacing || styles['letter-spacing']) as
85
- | string
86
- | { desktop: string };
87
- result.letterSpacing =
88
- typeof letterSpacing === 'object' ? letterSpacing.desktop : letterSpacing;
89
- }
90
-
91
- return result;
92
- }
93
-
94
- export function useNavbarMenuSettings(
95
- initialSettings?: NavbarMenuSettings
96
- ): NavbarMenuSettings {
97
- const isDesignerRef = useRef(false);
98
- const [isDesignerChecked, setIsDesignerChecked] = useState(false);
99
-
100
- useEffect(() => {
101
- if (typeof window === 'undefined') return;
102
- isDesignerRef.current = window.self !== window.top;
103
- setIsDesignerChecked(true);
104
- }, []);
105
-
106
- const isDesigner = isDesignerRef.current;
107
-
108
- const widgetData = useNativeWidgetData({
109
- widgetSlug: NAVBAR_MENU_WIDGET_SLUG,
110
- sectionId: NAVBAR_MENU_SECTION_ID,
111
- skip: !isDesignerChecked || isDesigner,
112
- blockMeta: BLOCK_META
113
- });
114
-
115
- const [settings, setSettings] = useState<NavbarMenuSettings>(
116
- initialSettings || defaultSettings
117
- );
118
- const hasInitialSettings = !!initialSettings;
119
-
120
- useEffect(() => {
121
- if (!isDesignerChecked) return;
122
- if (isDesigner) return;
123
- if (hasInitialSettings) return;
124
- if (widgetData.isLoading) return;
125
-
126
- const block = widgetData.getBlock(NAVBAR_MENU_BLOCK_ID);
127
- if (block) {
128
- const parsed = parseBlockStyles(block);
129
- if (Object.keys(parsed).length > 0) {
130
- setSettings((prev) => ({ ...prev, ...parsed }));
131
- }
132
- }
133
- }, [widgetData, isDesignerChecked, isDesigner, hasInitialSettings]);
134
-
135
- useEffect(() => {
136
- const handleMessage = (event: MessageEvent) => {
137
- const { type, data } = event.data || {};
138
-
139
- if (
140
- (type === 'UPDATE_THEME' || type === 'LOAD_THEME') &&
141
- data?.theme?.placeholders
142
- ) {
143
- const placeholder = data.theme.placeholders?.find(
144
- (p: { slug: string }) => p.slug === HEADER_PLACEHOLDER_ID
145
- );
146
-
147
- const navSection = placeholder?.sections?.find(
148
- (s: { id: string }) => s.id === NAVBAR_MENU_SECTION_ID
149
- );
150
-
151
- if (navSection?.blocks) {
152
- const block = navSection.blocks.find(
153
- (b: { id: string }) => b.id === NAVBAR_MENU_BLOCK_ID
154
- );
155
-
156
- if (block) {
157
- const parsed = parseBlockStyles(block);
158
- if (Object.keys(parsed).length > 0) {
159
- setSettings((prev) => ({ ...prev, ...parsed }));
160
- }
161
- }
162
- }
163
- }
164
- };
165
-
166
- window.addEventListener('message', handleMessage);
167
- return () => window.removeEventListener('message', handleMessage);
168
- }, []);
169
-
170
- return settings;
171
- }
172
-
173
- export { defaultSettings as defaultNavbarMenuSettings };
174
- export {
175
- DEFAULT_FONT_SIZE,
176
- DEFAULT_COLOR,
177
- DEFAULT_HOVER_COLOR,
178
- DEFAULT_FONT_WEIGHT
179
- };
@@ -1,61 +0,0 @@
1
- 'use client';
2
-
3
- /**
4
- * Accordion Section Wrapper
5
- *
6
- * Wraps accordion components with Theme Editor styling support.
7
- * Applies CSS variables for customization via Theme Editor.
8
- */
9
-
10
- import { PropsWithChildren, CSSProperties, useMemo } from 'react';
11
- import clsx from 'clsx';
12
- import {
13
- useProductDetail,
14
- ACCORDION_SECTION_ID
15
- } from '@theme/views/product-detail';
16
-
17
- export default function AccordionSection({ children }: PropsWithChildren) {
18
- const { isDesigner, selectedSectionId, accordionStyles } = useProductDetail();
19
-
20
- const isSelected = isDesigner && selectedSectionId === ACCORDION_SECTION_ID;
21
-
22
- const cssVariables = useMemo((): CSSProperties => {
23
- if (!accordionStyles) return {};
24
-
25
- const vars: Record<string, string> = {};
26
- Object.entries(accordionStyles).forEach(([key, value]) => {
27
- if (value) {
28
- vars[`--accordion-${key}`] = value;
29
- }
30
- });
31
- return vars as CSSProperties;
32
- }, [accordionStyles]);
33
-
34
- const handleClick = (e: React.MouseEvent) => {
35
- if (isDesigner) {
36
- e.stopPropagation();
37
- window.parent?.postMessage(
38
- {
39
- type: 'SELECT_SECTION',
40
- data: {
41
- placeholderId: 'product-detail',
42
- sectionId: ACCORDION_SECTION_ID
43
- }
44
- },
45
- '*'
46
- );
47
- }
48
- };
49
-
50
- return (
51
- <div
52
- className={clsx('accordion-section-wrapper', {
53
- 'ring-2 ring-blue-500 ring-offset-2': isSelected
54
- })}
55
- style={cssVariables}
56
- onClick={handleClick}
57
- >
58
- {children}
59
- </div>
60
- );
61
- }