@akinon/projectzero 2.0.0-beta.20 → 2.0.0-beta.22
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.
- package/CHANGELOG.md +14 -0
- package/app-template/CHANGELOG.md +170 -0
- package/app-template/next.config.mjs +0 -1
- package/app-template/package.json +31 -30
- package/app-template/src/app/[pz]/[...prettyurl]/page.tsx +2 -2
- package/app-template/src/app/[pz]/account/layout.tsx +2 -1
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/blog/[slug]/page.tsx +4 -2
- package/app-template/src/app/[pz]/category/[pk]/page.tsx +11 -1
- package/app-template/src/app/[pz]/group-product/[pk]/page.tsx +2 -2
- package/app-template/src/app/[pz]/layout.tsx +3 -1
- package/app-template/src/app/[pz]/list/page.tsx +11 -1
- package/app-template/src/app/[pz]/page.tsx +13 -35
- package/app-template/src/app/[pz]/pages/[slug]/page.tsx +19 -0
- package/app-template/src/app/[pz]/product/[pk]/page.tsx +2 -2
- package/app-template/src/app/api/barcode-search/route.ts +1 -1
- package/app-template/src/app/api/cache/route.ts +1 -1
- package/app-template/src/app/api/image-proxy/route.ts +1 -1
- package/app-template/src/app/api/logout/route.ts +1 -1
- package/app-template/src/app/api/product-categories/route.ts +1 -1
- package/app-template/src/app/api/similar-product-list/route.ts +1 -1
- package/app-template/src/app/api/similar-products/route.ts +1 -1
- package/app-template/src/app/api/virtual-try-on/route.ts +1 -1
- package/app-template/src/app/api/web-vitals/route.ts +1 -1
- package/app-template/src/components/quantity-selector.tsx +16 -4
- package/app-template/src/components/widget-content.tsx +3 -3
- package/app-template/src/routes/index.ts +6 -6
- package/app-template/src/utils/__tests__/theme-page-context.test.ts +145 -0
- package/app-template/src/utils/theme-page-context.ts +309 -0
- package/app-template/src/views/basket/basket-item.tsx +107 -691
- package/app-template/src/views/basket/index.ts +0 -2
- package/app-template/src/views/basket/summary.tsx +75 -496
- package/app-template/src/views/breadcrumb.tsx +38 -13
- package/app-template/src/views/category/category-header.tsx +66 -289
- package/app-template/src/views/category/category-info.tsx +24 -173
- package/app-template/src/views/category/filters/index.tsx +48 -208
- package/app-template/src/views/category/layout.tsx +5 -7
- package/app-template/src/views/checkout/index.tsx +0 -5
- package/app-template/src/views/checkout/steps/payment/index.tsx +2 -5
- package/app-template/src/views/checkout/steps/payment/options/credit-card/index.tsx +1 -72
- package/app-template/src/views/checkout/steps/payment/payment-option-buttons.tsx +40 -171
- package/app-template/src/views/checkout/steps/shipping/address-box.tsx +12 -74
- package/app-template/src/views/checkout/steps/shipping/addresses.tsx +45 -128
- package/app-template/src/views/checkout/steps/shipping/shipping-options.tsx +27 -232
- package/app-template/src/views/checkout/summary.tsx +29 -303
- package/app-template/src/views/footer.tsx +13 -415
- package/app-template/src/views/guest-login/index.tsx +1 -1
- package/app-template/src/views/header/action-menu.tsx +45 -277
- package/app-template/src/views/header/band.tsx +21 -6
- package/app-template/src/views/header/index.tsx +47 -109
- package/app-template/src/views/header/mini-basket.tsx +45 -820
- package/app-template/src/views/header/navbar.tsx +111 -178
- package/app-template/src/views/header/search/index.tsx +32 -71
- package/app-template/src/views/header/search/results.tsx +65 -127
- package/app-template/src/views/product/accordion-wrapper.tsx +43 -135
- package/app-template/src/views/product/index.ts +1 -1
- package/app-template/src/views/product/layout.tsx +7 -2
- package/app-template/src/views/product/misc-buttons.tsx +25 -339
- package/app-template/src/views/product/product-actions.tsx +8 -137
- package/app-template/src/views/product/product-info.tsx +31 -69
- package/app-template/src/views/product/product-share.tsx +8 -11
- package/app-template/src/views/product/slider.tsx +79 -117
- package/app-template/src/views/product-item/index.tsx +46 -119
- package/app-template/src/widgets/footer-social.tsx +16 -47
- package/app-template/src/widgets/footer-subscription/index.tsx +17 -183
- package/codemods/migrate-auth-v5/index.js +339 -0
- package/codemods/migrate-auth-v5/transform.js +86 -0
- package/dist/commands/plugins.js +23 -2
- package/package.json +1 -1
- package/app-template/src/app/[commerce]/[locale]/[currency]/pages/[slug]/page.tsx +0 -15
- package/app-template/src/views/basket/basket-summary-context.tsx +0 -560
- package/app-template/src/views/basket/designer-context.tsx +0 -617
- package/app-template/src/views/breadcrumb/breadcrumb-client.tsx +0 -190
- package/app-template/src/views/breadcrumb/breadcrumb-registrar.tsx +0 -286
- package/app-template/src/views/breadcrumb/constants.ts +0 -15
- package/app-template/src/views/breadcrumb/index.tsx +0 -127
- package/app-template/src/views/category/native-widget-context.tsx +0 -257
- package/app-template/src/views/category/product-list-registrar.tsx +0 -665
- package/app-template/src/views/checkout/checkout-address-registrar.tsx +0 -254
- package/app-template/src/views/checkout/checkout-buttons-registrar.tsx +0 -183
- package/app-template/src/views/checkout/checkout-delivery-method-registrar.tsx +0 -259
- package/app-template/src/views/checkout/checkout-payment-options-registrar.tsx +0 -253
- package/app-template/src/views/checkout/checkout-summary-registrar.tsx +0 -183
- package/app-template/src/views/checkout/constants.ts +0 -5
- package/app-template/src/views/checkout/steps/payment/options/masterpass-rest.tsx +0 -15
- package/app-template/src/views/checkout/steps/payment/options/saved-card.tsx +0 -18
- package/app-template/src/views/footer/footer-app-banner-context.tsx +0 -326
- package/app-template/src/views/footer/footer-bottom-context.tsx +0 -215
- package/app-template/src/views/footer/footer-bottom-wrapper.tsx +0 -74
- package/app-template/src/views/footer/footer-layout-constants.ts +0 -35
- package/app-template/src/views/footer/footer-layout-registrar.tsx +0 -342
- package/app-template/src/views/footer/footer-layout-switcher.tsx +0 -110
- package/app-template/src/views/footer/footer-menu-context.tsx +0 -211
- package/app-template/src/views/footer/footer-native-widgets.tsx +0 -60
- package/app-template/src/views/footer/footer-social-context.tsx +0 -254
- package/app-template/src/views/footer/footer-subscription-context.tsx +0 -210
- package/app-template/src/views/footer/footer-utils.ts +0 -43
- package/app-template/src/views/footer/footer-value-props-context.tsx +0 -326
- package/app-template/src/views/footer/logo-settings.ts +0 -183
- package/app-template/src/views/footer/native-widget-config.ts +0 -262
- package/app-template/src/views/footer/subscription-settings.ts +0 -122
- package/app-template/src/views/footer/use-footer-logo.ts +0 -162
- package/app-template/src/views/header/designer-context.tsx +0 -261
- package/app-template/src/views/header/header-announcement-registrar.tsx +0 -267
- package/app-template/src/views/header/header-client-wrapper.tsx +0 -496
- package/app-template/src/views/header/header-content.tsx +0 -1026
- package/app-template/src/views/header/header-currency-registrar.tsx +0 -348
- package/app-template/src/views/header/header-icons-context.tsx +0 -262
- package/app-template/src/views/header/header-language-registrar.tsx +0 -348
- package/app-template/src/views/header/header-layout-context.tsx +0 -143
- package/app-template/src/views/header/header-layout-registrar.tsx +0 -658
- package/app-template/src/views/header/header-logo-context.tsx +0 -228
- package/app-template/src/views/header/header-logo.tsx +0 -118
- package/app-template/src/views/header/header-mini-basket-context.tsx +0 -524
- package/app-template/src/views/header/header-search-registrar.tsx +0 -511
- package/app-template/src/views/header/header-text-slider-registrar.tsx +0 -382
- package/app-template/src/views/header/inline-search.tsx +0 -262
- package/app-template/src/views/header/navbar-menu-context.tsx +0 -219
- package/app-template/src/views/header/search/search-input.tsx +0 -61
- package/app-template/src/views/header/server-settings-parser.ts +0 -1105
- package/app-template/src/views/header/use-header-icons.ts +0 -241
- package/app-template/src/views/header/use-header-logo.ts +0 -213
- package/app-template/src/views/header/use-navbar-menu.ts +0 -179
- package/app-template/src/views/product/accordion-section.tsx +0 -61
- package/app-template/src/views/product/custom-button-group.tsx +0 -69
- package/app-template/src/views/product/favorites-button-section.tsx +0 -69
- package/app-template/src/views/product/find-in-store-section.tsx +0 -60
- package/app-template/src/views/product/product-info-section.tsx +0 -140
- package/app-template/src/views/product/quantity-section.tsx +0 -73
- package/app-template/src/views/product/sale-tag.tsx +0 -10
- package/app-template/src/views/product/share-section.tsx +0 -357
- package/app-template/src/views/product/variants-section.tsx +0 -126
- package/app-template/src/views/product-detail/constants.ts +0 -272
- package/app-template/src/views/product-detail/index.ts +0 -10
- package/app-template/src/views/product-detail/product-detail-registrar.tsx +0 -616
- package/app-template/src/widgets/footer-app-banner.tsx +0 -444
- package/app-template/src/widgets/footer-bottom.tsx +0 -127
- package/app-template/src/widgets/footer-menu-compact.tsx +0 -238
- package/app-template/src/widgets/footer-menu-two.tsx +0 -298
- package/app-template/src/widgets/footer-social-client.tsx +0 -251
- package/app-template/src/widgets/footer-value-props.tsx +0 -201
|
@@ -1,251 +0,0 @@
|
|
|
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,201 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useCallback, type CSSProperties } from 'react';
|
|
4
|
-
import clsx from 'clsx';
|
|
5
|
-
import { Icon } from '@theme/components';
|
|
6
|
-
import { useDesignerFeatures } from '@akinon/next/components/theme-editor/hooks/use-designer-features';
|
|
7
|
-
import {
|
|
8
|
-
useFooterValuePropsDesigner,
|
|
9
|
-
FOOTER_VALUE_PROPS_LIST_BLOCK_ID,
|
|
10
|
-
type ValuePropItem
|
|
11
|
-
} from '../views/footer/footer-value-props-context';
|
|
12
|
-
import {
|
|
13
|
-
FOOTER_PLACEHOLDER_ID,
|
|
14
|
-
FOOTER_VALUE_PROPS_SECTION_ID
|
|
15
|
-
} from '../views/footer/native-widget-config';
|
|
16
|
-
|
|
17
|
-
const resolveValue = <T,>(value: unknown, defaultValue: T): T => {
|
|
18
|
-
if (value == null) return defaultValue;
|
|
19
|
-
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
20
|
-
const responsive = value as Record<string, unknown>;
|
|
21
|
-
const resolved =
|
|
22
|
-
responsive.desktop ?? responsive.mobile ?? responsive.tablet;
|
|
23
|
-
return (resolved as T) ?? defaultValue;
|
|
24
|
-
}
|
|
25
|
-
return value as T;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const DEFAULT_VALUE_PROPS: ValuePropItem[] = [
|
|
29
|
-
{ icon: 'home', title: 'Free Shipping', description: 'On orders over $50' },
|
|
30
|
-
{ icon: 'add', title: 'Secure Payment', description: '256-bit SSL encryption' },
|
|
31
|
-
{ icon: 'search', title: 'Easy Returns', description: '30-day return policy' }
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
interface FooterValuePropsProps {
|
|
35
|
-
renderPosition?: 'footer-top' | 'footer-bottom';
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export default function FooterValueProps({ renderPosition }: FooterValuePropsProps) {
|
|
39
|
-
const {
|
|
40
|
-
isDesigner,
|
|
41
|
-
selectedSectionId,
|
|
42
|
-
selectedBlockId,
|
|
43
|
-
getBlock,
|
|
44
|
-
getSectionProperty,
|
|
45
|
-
getSectionStyle,
|
|
46
|
-
blockVersion
|
|
47
|
-
} = useFooterValuePropsDesigner();
|
|
48
|
-
|
|
49
|
-
const { handleClick: handleListClick } = useDesignerFeatures({
|
|
50
|
-
blockId: FOOTER_VALUE_PROPS_LIST_BLOCK_ID,
|
|
51
|
-
placeholderId: FOOTER_PLACEHOLDER_ID,
|
|
52
|
-
sectionId: FOOTER_VALUE_PROPS_SECTION_ID,
|
|
53
|
-
isDesigner,
|
|
54
|
-
blockInfo: {
|
|
55
|
-
id: FOOTER_VALUE_PROPS_LIST_BLOCK_ID,
|
|
56
|
-
type: 'value-props-list',
|
|
57
|
-
label: 'Value Props List'
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
const handleSectionClick = useCallback(
|
|
62
|
-
(e: React.MouseEvent) => {
|
|
63
|
-
if (isDesigner) {
|
|
64
|
-
e.stopPropagation();
|
|
65
|
-
if (window.parent) {
|
|
66
|
-
window.parent.postMessage(
|
|
67
|
-
{
|
|
68
|
-
type: 'SELECT_SECTION',
|
|
69
|
-
data: {
|
|
70
|
-
placeholderId: FOOTER_PLACEHOLDER_ID,
|
|
71
|
-
sectionId: FOOTER_VALUE_PROPS_SECTION_ID
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
'*'
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
[isDesigner]
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
const enabled = resolveValue<boolean>(getSectionProperty('enabled'), true);
|
|
83
|
-
const layout = resolveValue<string>(getSectionProperty('layout'), 'horizontal');
|
|
84
|
-
const position = resolveValue<string>(getSectionProperty('position'), 'footer-top');
|
|
85
|
-
|
|
86
|
-
if (renderPosition && position !== renderPosition) {
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (!enabled && !isDesigner) {
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const sectionStyles: CSSProperties = {
|
|
95
|
-
backgroundColor: resolveValue<string>(getSectionStyle('background-color'), '#ffffff'),
|
|
96
|
-
paddingTop: `${resolveValue<number>(getSectionStyle('padding-top'), 24)}px`,
|
|
97
|
-
paddingBottom: `${resolveValue<number>(getSectionStyle('padding-bottom'), 24)}px`,
|
|
98
|
-
borderTopWidth: `${resolveValue<number>(getSectionStyle('border-top-width'), 1)}px`,
|
|
99
|
-
borderTopStyle: 'solid',
|
|
100
|
-
borderTopColor: resolveValue<string>(getSectionStyle('border-top-color'), '#e5e7eb'),
|
|
101
|
-
borderBottomWidth: `${resolveValue<number>(getSectionStyle('border-bottom-width'), 0)}px`,
|
|
102
|
-
borderBottomStyle: 'solid',
|
|
103
|
-
borderBottomColor: resolveValue<string>(getSectionStyle('border-bottom-color'), '#e5e7eb')
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
const listBlock = blockVersion >= 0 ? getBlock(FOOTER_VALUE_PROPS_LIST_BLOCK_ID) : undefined;
|
|
107
|
-
const listStylesRaw = listBlock?.styles as Record<string, unknown> | undefined;
|
|
108
|
-
const listGap = resolveValue<number>(listStylesRaw?.gap, 32);
|
|
109
|
-
|
|
110
|
-
const blockProperties = listBlock?.properties as Record<string, unknown> | undefined;
|
|
111
|
-
const items = resolveValue<ValuePropItem[]>(blockProperties?.items, DEFAULT_VALUE_PROPS);
|
|
112
|
-
|
|
113
|
-
const isSectionSelected = selectedSectionId === FOOTER_VALUE_PROPS_SECTION_ID && !selectedBlockId;
|
|
114
|
-
|
|
115
|
-
if (!enabled && isDesigner) {
|
|
116
|
-
return (
|
|
117
|
-
<div
|
|
118
|
-
data-section-id={FOOTER_VALUE_PROPS_SECTION_ID}
|
|
119
|
-
className="theme-section relative cursor-pointer"
|
|
120
|
-
style={sectionStyles}
|
|
121
|
-
onClick={handleSectionClick}
|
|
122
|
-
>
|
|
123
|
-
<div
|
|
124
|
-
className={clsx(
|
|
125
|
-
'absolute inset-0 pointer-events-none z-0 border-2 transition-all',
|
|
126
|
-
isSectionSelected ? 'border-[#4482ff]' : 'border-transparent'
|
|
127
|
-
)}
|
|
128
|
-
/>
|
|
129
|
-
<div className="container py-6 text-center text-gray-400 border border-dashed border-gray-300 rounded relative z-10">
|
|
130
|
-
Value Propositions (disabled - click section to enable)
|
|
131
|
-
</div>
|
|
132
|
-
</div>
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const isListSelected = selectedBlockId === FOOTER_VALUE_PROPS_LIST_BLOCK_ID;
|
|
137
|
-
|
|
138
|
-
return (
|
|
139
|
-
<div
|
|
140
|
-
data-section-id={FOOTER_VALUE_PROPS_SECTION_ID}
|
|
141
|
-
className={clsx(
|
|
142
|
-
'theme-section relative',
|
|
143
|
-
isDesigner && 'cursor-pointer'
|
|
144
|
-
)}
|
|
145
|
-
style={sectionStyles}
|
|
146
|
-
onClick={isDesigner ? handleSectionClick : undefined}
|
|
147
|
-
>
|
|
148
|
-
{isDesigner && (
|
|
149
|
-
<div
|
|
150
|
-
className={clsx(
|
|
151
|
-
'absolute inset-0 pointer-events-none z-0 border-2 transition-all',
|
|
152
|
-
isSectionSelected ? 'border-[#4482ff]' : 'border-transparent'
|
|
153
|
-
)}
|
|
154
|
-
/>
|
|
155
|
-
)}
|
|
156
|
-
<div className="container relative z-10">
|
|
157
|
-
<div
|
|
158
|
-
className={clsx(
|
|
159
|
-
layout === 'horizontal'
|
|
160
|
-
? 'flex flex-wrap justify-center md:justify-between items-start'
|
|
161
|
-
: 'grid grid-cols-2 md:grid-cols-4',
|
|
162
|
-
isDesigner && 'cursor-pointer',
|
|
163
|
-
isListSelected && 'ring-2 ring-blue-500 ring-offset-2 rounded p-2'
|
|
164
|
-
)}
|
|
165
|
-
style={{ gap: `${listGap}px` }}
|
|
166
|
-
onClick={isDesigner ? handleListClick : undefined}
|
|
167
|
-
data-block-id={isDesigner ? FOOTER_VALUE_PROPS_LIST_BLOCK_ID : undefined}
|
|
168
|
-
>
|
|
169
|
-
{items.length > 0 ? (
|
|
170
|
-
items.map((item, index) => (
|
|
171
|
-
<div
|
|
172
|
-
key={index}
|
|
173
|
-
className="flex flex-col items-center text-center"
|
|
174
|
-
>
|
|
175
|
-
<div className="mb-2">
|
|
176
|
-
<Icon
|
|
177
|
-
name={item.icon}
|
|
178
|
-
size={32}
|
|
179
|
-
className="text-current"
|
|
180
|
-
/>
|
|
181
|
-
</div>
|
|
182
|
-
<h4 className="font-semibold text-base mb-1">
|
|
183
|
-
{item.title}
|
|
184
|
-
</h4>
|
|
185
|
-
{item.description && (
|
|
186
|
-
<p className="text-sm text-gray-600">
|
|
187
|
-
{item.description}
|
|
188
|
-
</p>
|
|
189
|
-
)}
|
|
190
|
-
</div>
|
|
191
|
-
))
|
|
192
|
-
) : isDesigner ? (
|
|
193
|
-
<div className="w-full py-4 text-center text-gray-400 border border-dashed border-gray-300 rounded">
|
|
194
|
-
Click to add value propositions
|
|
195
|
-
</div>
|
|
196
|
-
) : null}
|
|
197
|
-
</div>
|
|
198
|
-
</div>
|
|
199
|
-
</div>
|
|
200
|
-
);
|
|
201
|
-
}
|