@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.
- package/CHANGELOG.md +9 -7
- package/app-template/CHANGELOG.md +251 -204
- package/app-template/akinon.json +1 -1
- package/app-template/package.json +28 -28
- package/app-template/public/amex.svg +12 -0
- package/app-template/public/apple-pay.svg +16 -0
- package/app-template/public/assets/images/product-placeholder-1.jpg +0 -0
- package/app-template/public/assets/images/product-placeholder-2.jpg +0 -0
- package/app-template/public/assets/images/product-placeholder-3.jpg +0 -0
- package/app-template/public/assets/images/product-placeholder-4.jpg +0 -0
- package/app-template/public/google-pay.svg +16 -0
- package/app-template/public/locales/en/account.json +6 -3
- package/app-template/public/locales/en/auth.json +6 -7
- package/app-template/public/locales/en/basket.json +6 -6
- package/app-template/public/locales/en/blog.json +7 -0
- package/app-template/public/locales/en/category.json +3 -1
- package/app-template/public/locales/en/checkout.json +5 -4
- package/app-template/public/locales/en/common.json +11 -2
- package/app-template/public/locales/en/forgot_password.json +6 -7
- package/app-template/public/locales/en/product.json +4 -3
- package/app-template/public/locales/tr/account.json +6 -3
- package/app-template/public/locales/tr/auth.json +16 -17
- package/app-template/public/locales/tr/basket.json +4 -4
- package/app-template/public/locales/tr/blog.json +7 -0
- package/app-template/public/locales/tr/category.json +3 -1
- package/app-template/public/locales/tr/checkout.json +39 -38
- package/app-template/public/locales/tr/common.json +10 -1
- package/app-template/public/locales/tr/forgot_password.json +12 -13
- package/app-template/public/locales/tr/product.json +1 -0
- package/app-template/public/logo.svg +3 -27
- package/app-template/public/mastercard.svg +14 -0
- package/app-template/public/promotion-banner.jpg +0 -0
- package/app-template/public/shop-pay.svg +12 -0
- package/app-template/public/visa.svg +12 -0
- package/app-template/src/app/[commerce]/[locale]/[currency]/blog/[slug]/page.tsx +118 -0
- package/app-template/src/app/[commerce]/[locale]/[currency]/pages/[slug]/page.tsx +15 -0
- package/app-template/src/app/api/theme-settings/route.ts +12 -0
- package/app-template/src/assets/fonts/pz-icon.css +211 -49
- package/app-template/src/assets/fonts/pz-icon.eot +0 -0
- package/app-template/src/assets/fonts/pz-icon.html +486 -0
- package/app-template/src/assets/fonts/pz-icon.scss +373 -49
- package/app-template/src/assets/fonts/pz-icon.svg +215 -53
- package/app-template/src/assets/fonts/pz-icon.ttf +0 -0
- package/app-template/src/assets/fonts/pz-icon.woff +0 -0
- package/app-template/src/assets/fonts/pz-icon.woff2 +0 -0
- package/app-template/src/assets/globals.scss +4 -0
- package/app-template/src/assets/icons/arrow-right.svg +3 -0
- package/app-template/src/assets/icons/cart.svg +4 -12
- package/app-template/src/assets/icons/check.svg +2 -18
- package/app-template/src/assets/icons/chevron-down.svg +2 -7
- package/app-template/src/assets/icons/delete.svg +3 -0
- package/app-template/src/assets/icons/facebook.svg +2 -8
- package/app-template/src/assets/icons/fav-off.svg +5 -0
- package/app-template/src/assets/icons/fav-on.svg +5 -0
- package/app-template/src/assets/icons/filter-and-sort.svg +3 -0
- package/app-template/src/assets/icons/heart.svg +3 -0
- package/app-template/src/assets/icons/instagram.svg +2 -13
- package/app-template/src/assets/icons/materials.svg +3 -0
- package/app-template/src/assets/icons/person.svg +4 -0
- package/app-template/src/assets/icons/pinterest.svg +5 -11
- package/app-template/src/assets/icons/ruler.svg +3 -0
- package/app-template/src/assets/icons/search.svg +8 -11
- package/app-template/src/assets/icons/share.svg +2 -9
- package/app-template/src/assets/icons/snapchat.svg +3 -0
- package/app-template/src/assets/icons/tiktok.svg +3 -0
- package/app-template/src/assets/icons/tumblr.svg +6 -0
- package/app-template/src/assets/icons/twitter.svg +2 -10
- package/app-template/src/assets/icons/vimeo.svg +3 -0
- package/app-template/src/assets/icons/youtube.svg +3 -0
- package/app-template/src/assets/icons/zoom.svg +8 -0
- package/app-template/src/components/accordion.tsx +33 -11
- package/app-template/src/components/action-tooltip.tsx +160 -0
- package/app-template/src/components/currency-select.tsx +149 -4
- package/app-template/src/components/icon.tsx +5 -6
- package/app-template/src/components/index.ts +4 -1
- package/app-template/src/components/language-select.tsx +88 -2
- package/app-template/src/components/pagination.tsx +132 -20
- package/app-template/src/components/quantity-input.tsx +63 -0
- package/app-template/src/components/quantity-selector.tsx +203 -0
- package/app-template/src/components/route-handler.tsx +50 -0
- package/app-template/src/components/select.tsx +89 -69
- package/app-template/src/components/types/index.ts +26 -0
- package/app-template/src/components/widget-content.tsx +323 -0
- package/app-template/src/data/server/theme.ts +70 -0
- package/app-template/src/hooks/use-fav-button.tsx +5 -2
- package/app-template/src/hooks/use-product-cart.ts +11 -8
- package/app-template/src/hooks/use-theme-settings.ts +42 -0
- package/app-template/src/lib/fonts.ts +149 -0
- package/app-template/src/settings.js +2 -2
- package/app-template/src/types/hookform-resolvers-yup.d.ts +28 -0
- package/app-template/src/types/widget.ts +169 -0
- package/app-template/src/utils/formatDate.ts +48 -0
- package/app-template/src/utils/styles.ts +71 -0
- package/app-template/src/views/account/contact-form.tsx +147 -130
- package/app-template/src/views/basket/basket-item.tsx +691 -107
- package/app-template/src/views/basket/basket-summary-context.tsx +560 -0
- package/app-template/src/views/basket/designer-context.tsx +617 -0
- package/app-template/src/views/basket/index.ts +2 -0
- package/app-template/src/views/basket/summary.tsx +496 -75
- package/app-template/src/views/breadcrumb/breadcrumb-client.tsx +190 -0
- package/app-template/src/views/breadcrumb/breadcrumb-registrar.tsx +286 -0
- package/app-template/src/views/breadcrumb/constants.ts +15 -0
- package/app-template/src/views/breadcrumb/index.tsx +127 -0
- package/app-template/src/views/breadcrumb.tsx +13 -38
- package/app-template/src/views/category/category-banner.tsx +4 -23
- package/app-template/src/views/category/category-header.tsx +289 -66
- package/app-template/src/views/category/category-info.tsx +173 -24
- package/app-template/src/views/category/filters/filter-item.tsx +138 -42
- package/app-template/src/views/category/filters/index.tsx +208 -48
- package/app-template/src/views/category/layout.tsx +7 -4
- package/app-template/src/views/category/native-widget-context.tsx +257 -0
- package/app-template/src/views/category/product-list-registrar.tsx +665 -0
- package/app-template/src/views/checkout/auth.tsx +64 -40
- package/app-template/src/views/checkout/checkout-address-registrar.tsx +254 -0
- package/app-template/src/views/checkout/checkout-buttons-registrar.tsx +183 -0
- package/app-template/src/views/checkout/checkout-delivery-method-registrar.tsx +259 -0
- package/app-template/src/views/checkout/checkout-payment-options-registrar.tsx +253 -0
- package/app-template/src/views/checkout/checkout-summary-registrar.tsx +183 -0
- package/app-template/src/views/checkout/constants.ts +5 -0
- package/app-template/src/views/checkout/index.tsx +5 -0
- package/app-template/src/views/checkout/layout/header.tsx +9 -5
- package/app-template/src/views/checkout/steps/payment/index.tsx +5 -2
- package/app-template/src/views/checkout/steps/payment/options/credit-card/index.tsx +72 -1
- package/app-template/src/views/checkout/steps/payment/options/masterpass-rest.tsx +15 -0
- package/app-template/src/views/checkout/steps/payment/options/saved-card.tsx +18 -0
- package/app-template/src/views/checkout/steps/payment/payment-option-buttons.tsx +171 -40
- package/app-template/src/views/checkout/steps/shipping/address-box.tsx +74 -12
- package/app-template/src/views/checkout/steps/shipping/addresses.tsx +128 -45
- package/app-template/src/views/checkout/steps/shipping/shipping-options.tsx +232 -27
- package/app-template/src/views/checkout/summary.tsx +303 -29
- package/app-template/src/views/footer/footer-app-banner-context.tsx +326 -0
- package/app-template/src/views/footer/footer-bottom-context.tsx +215 -0
- package/app-template/src/views/footer/footer-bottom-wrapper.tsx +74 -0
- package/app-template/src/views/footer/footer-layout-constants.ts +35 -0
- package/app-template/src/views/footer/footer-layout-registrar.tsx +342 -0
- package/app-template/src/views/footer/footer-layout-switcher.tsx +110 -0
- package/app-template/src/views/footer/footer-menu-context.tsx +211 -0
- package/app-template/src/views/footer/footer-native-widgets.tsx +60 -0
- package/app-template/src/views/footer/footer-social-context.tsx +254 -0
- package/app-template/src/views/footer/footer-subscription-context.tsx +210 -0
- package/app-template/src/views/footer/footer-utils.ts +43 -0
- package/app-template/src/views/footer/footer-value-props-context.tsx +326 -0
- package/app-template/src/views/footer/logo-settings.ts +183 -0
- package/app-template/src/views/footer/native-widget-config.ts +262 -0
- package/app-template/src/views/footer/subscription-settings.ts +122 -0
- package/app-template/src/views/footer/use-footer-logo.ts +162 -0
- package/app-template/src/views/footer.tsx +415 -13
- package/app-template/src/views/guest-login/index.tsx +62 -58
- package/app-template/src/views/header/action-menu.tsx +277 -45
- package/app-template/src/views/header/band.tsx +6 -21
- package/app-template/src/views/header/designer-context.tsx +261 -0
- package/app-template/src/views/header/header-announcement-registrar.tsx +267 -0
- package/app-template/src/views/header/header-client-wrapper.tsx +496 -0
- package/app-template/src/views/header/header-content.tsx +1026 -0
- package/app-template/src/views/header/header-currency-registrar.tsx +348 -0
- package/app-template/src/views/header/header-icons-context.tsx +262 -0
- package/app-template/src/views/header/header-language-registrar.tsx +348 -0
- package/app-template/src/views/header/header-layout-context.tsx +143 -0
- package/app-template/src/views/header/header-layout-registrar.tsx +658 -0
- package/app-template/src/views/header/header-logo-context.tsx +228 -0
- package/app-template/src/views/header/header-logo.tsx +118 -0
- package/app-template/src/views/header/header-mini-basket-context.tsx +524 -0
- package/app-template/src/views/header/header-search-registrar.tsx +511 -0
- package/app-template/src/views/header/header-text-slider-registrar.tsx +382 -0
- package/app-template/src/views/header/index.tsx +109 -47
- package/app-template/src/views/header/inline-search.tsx +262 -0
- package/app-template/src/views/header/mini-basket.tsx +819 -44
- package/app-template/src/views/header/mobile-hamburger-button.tsx +5 -8
- package/app-template/src/views/header/mobile-menu.tsx +12 -0
- package/app-template/src/views/header/navbar-menu-context.tsx +219 -0
- package/app-template/src/views/header/navbar.tsx +178 -111
- package/app-template/src/views/header/search/index.tsx +71 -32
- package/app-template/src/views/header/search/results.tsx +127 -65
- package/app-template/src/views/header/search/search-input.tsx +61 -0
- package/app-template/src/views/header/server-settings-parser.ts +1105 -0
- package/app-template/src/views/header/use-header-icons.ts +241 -0
- package/app-template/src/views/header/use-header-logo.ts +213 -0
- package/app-template/src/views/header/use-navbar-menu.ts +179 -0
- package/app-template/src/views/login/index.tsx +54 -46
- package/app-template/src/views/product/accordion-section.tsx +61 -0
- package/app-template/src/views/product/accordion-wrapper.tsx +135 -43
- package/app-template/src/views/product/custom-button-group.tsx +69 -0
- package/app-template/src/views/product/favorites-button-section.tsx +69 -0
- package/app-template/src/views/product/find-in-store-section.tsx +60 -0
- package/app-template/src/views/product/index.ts +1 -0
- package/app-template/src/views/product/layout.tsx +6 -5
- package/app-template/src/views/product/misc-buttons.tsx +339 -25
- package/app-template/src/views/product/price-wrapper.tsx +3 -29
- package/app-template/src/views/product/product-actions.tsx +137 -8
- package/app-template/src/views/product/product-info-section.tsx +140 -0
- package/app-template/src/views/product/product-info.tsx +69 -31
- package/app-template/src/views/product/product-share.tsx +13 -8
- package/app-template/src/views/product/product-variants.tsx +2 -2
- package/app-template/src/views/product/quantity-section.tsx +73 -0
- package/app-template/src/views/product/sale-tag.tsx +10 -0
- package/app-template/src/views/product/share-section.tsx +357 -0
- package/app-template/src/views/product/slider.tsx +117 -79
- package/app-template/src/views/product/variant.tsx +69 -41
- package/app-template/src/views/product/variants-section.tsx +126 -0
- package/app-template/src/views/product-detail/constants.ts +272 -0
- package/app-template/src/views/product-detail/index.ts +10 -0
- package/app-template/src/views/product-detail/product-detail-registrar.tsx +616 -0
- package/app-template/src/views/product-item/index.tsx +119 -46
- package/app-template/src/views/register/index.tsx +14 -25
- package/app-template/src/views/share/index.tsx +9 -6
- package/app-template/src/views/widgets/home-hero-slider-content.tsx +41 -39
- package/app-template/src/widgets/flatpages/about-us/index.tsx +78 -0
- package/app-template/src/widgets/flatpages/blog-list/index.tsx +129 -0
- package/app-template/src/widgets/footer-app-banner.tsx +444 -0
- package/app-template/src/widgets/footer-bottom.tsx +127 -0
- package/app-template/src/widgets/footer-menu-compact.tsx +238 -0
- package/app-template/src/widgets/footer-menu-two.tsx +298 -0
- package/app-template/src/widgets/footer-social-client.tsx +251 -0
- package/app-template/src/widgets/footer-social.tsx +47 -16
- package/app-template/src/widgets/footer-subscription/footer-subscription-form.tsx +17 -14
- package/app-template/src/widgets/footer-subscription/index.tsx +183 -17
- package/app-template/src/widgets/footer-value-props.tsx +201 -0
- package/app-template/src/widgets/index.ts +7 -0
- package/app-template/src/widgets/schemas/about-us.json +46 -0
- package/app-template/src/widgets/schemas/blog-list.json +37 -0
- package/app-template/src/widgets/schemas/blog.json +29 -0
- package/app-template/tailwind.config.js +18 -2
- package/package.json +1 -1
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Header Layout Section Registrar
|
|
5
|
+
*
|
|
6
|
+
* This component registers the "Layout" section for the header placeholder.
|
|
7
|
+
* It's a minimal client component that only handles native widget registration
|
|
8
|
+
* and applies selection highlight to the header when Layout section is selected.
|
|
9
|
+
*
|
|
10
|
+
* When the Layout section is selected in Theme Editor, the entire header
|
|
11
|
+
* gets highlighted.
|
|
12
|
+
*
|
|
13
|
+
* It also provides the current layout type to child components via context.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
createContext,
|
|
18
|
+
useContext,
|
|
19
|
+
useEffect,
|
|
20
|
+
useRef,
|
|
21
|
+
useState,
|
|
22
|
+
useCallback,
|
|
23
|
+
ReactNode
|
|
24
|
+
} from 'react';
|
|
25
|
+
|
|
26
|
+
// Constants
|
|
27
|
+
export const HEADER_LAYOUT_PLACEHOLDER_ID = 'header';
|
|
28
|
+
export const HEADER_LAYOUT_SECTION_ID = 'header-layout';
|
|
29
|
+
export const HEADER_LAYOUT_WIDGET_SLUG = 'header-layout-settings-2';
|
|
30
|
+
|
|
31
|
+
// Block definitions for row containers
|
|
32
|
+
export const HEADER_LAYOUT_BLOCKS = {
|
|
33
|
+
UTILITY_ROW: {
|
|
34
|
+
id: 'header-utility-row',
|
|
35
|
+
type: 'container',
|
|
36
|
+
label: 'Utility Row'
|
|
37
|
+
},
|
|
38
|
+
MAIN_ROW: {
|
|
39
|
+
id: 'header-main-row',
|
|
40
|
+
type: 'container',
|
|
41
|
+
label: 'Main Row'
|
|
42
|
+
},
|
|
43
|
+
TOP_ROW: {
|
|
44
|
+
id: 'header-top-row',
|
|
45
|
+
type: 'container',
|
|
46
|
+
label: 'Top Row'
|
|
47
|
+
},
|
|
48
|
+
BOTTOM_ROW: {
|
|
49
|
+
id: 'header-bottom-row',
|
|
50
|
+
type: 'container',
|
|
51
|
+
label: 'Bottom Row'
|
|
52
|
+
}
|
|
53
|
+
} as const;
|
|
54
|
+
|
|
55
|
+
// Layout types
|
|
56
|
+
export type HeaderLayoutType = 'default' | 'two-row';
|
|
57
|
+
|
|
58
|
+
// Global flag to track if registration has been done (survives component remount)
|
|
59
|
+
declare global {
|
|
60
|
+
interface Window {
|
|
61
|
+
__headerLayoutRegistered?: boolean;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Check if running inside designer iframe
|
|
67
|
+
*/
|
|
68
|
+
function isInDesignerMode(): boolean {
|
|
69
|
+
if (typeof window === 'undefined') return false;
|
|
70
|
+
return window.self !== window.top;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type MenuPositionType = 'left' | 'center' | 'right';
|
|
74
|
+
export type SearchPositionType = 'left' | 'center' | 'right';
|
|
75
|
+
export type UtilityPositionType = 'left' | 'right';
|
|
76
|
+
|
|
77
|
+
export interface HeaderLayoutProperties {
|
|
78
|
+
layout?: HeaderLayoutType | Record<string, string>;
|
|
79
|
+
menuPosition?: MenuPositionType | Record<string, string>;
|
|
80
|
+
searchPosition?: SearchPositionType | Record<string, string>;
|
|
81
|
+
utilityPosition?: UtilityPositionType | Record<string, string>;
|
|
82
|
+
sticky?: boolean | string | Record<string, string>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Block styles interface
|
|
86
|
+
interface BlockStyles {
|
|
87
|
+
[key: string]: unknown;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Context for sharing layout type with other components
|
|
91
|
+
interface HeaderLayoutContextValue {
|
|
92
|
+
layout: HeaderLayoutType;
|
|
93
|
+
menuPosition: MenuPositionType;
|
|
94
|
+
searchPosition: SearchPositionType;
|
|
95
|
+
utilityPosition: UtilityPositionType;
|
|
96
|
+
sticky: boolean;
|
|
97
|
+
isDesigner: boolean;
|
|
98
|
+
selectedBlockId: string | null;
|
|
99
|
+
getBlockStyles: (blockId: string) => BlockStyles | undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const HeaderLayoutContext = createContext<HeaderLayoutContextValue>({
|
|
103
|
+
layout: 'default',
|
|
104
|
+
menuPosition: 'center',
|
|
105
|
+
searchPosition: 'center',
|
|
106
|
+
utilityPosition: 'right',
|
|
107
|
+
sticky: true,
|
|
108
|
+
isDesigner: false,
|
|
109
|
+
selectedBlockId: null,
|
|
110
|
+
getBlockStyles: () => undefined
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
export const useHeaderLayout = () => useContext(HeaderLayoutContext);
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* HeaderLayoutRegistrar
|
|
117
|
+
*
|
|
118
|
+
* Registers the Layout native section with Theme Editor so it appears in the sidebar,
|
|
119
|
+
* handles header highlight when Layout section is selected, and provides
|
|
120
|
+
* layout type to children via context.
|
|
121
|
+
*/
|
|
122
|
+
interface HeaderLayoutRegistrarProps {
|
|
123
|
+
children?: ReactNode;
|
|
124
|
+
/** Initial layout settings from server to avoid layout flash */
|
|
125
|
+
initialLayout?: HeaderLayoutType;
|
|
126
|
+
initialMenuPosition?: MenuPositionType;
|
|
127
|
+
initialSearchPosition?: SearchPositionType;
|
|
128
|
+
initialUtilityPosition?: UtilityPositionType;
|
|
129
|
+
initialSticky?: boolean;
|
|
130
|
+
/** Initial block styles from server (for standalone mode) */
|
|
131
|
+
initialBlockStyles?: Record<string, Record<string, string>>;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export default function HeaderLayoutRegistrar({
|
|
135
|
+
children,
|
|
136
|
+
initialLayout = 'default',
|
|
137
|
+
initialMenuPosition = 'center',
|
|
138
|
+
initialSearchPosition = 'center',
|
|
139
|
+
initialUtilityPosition = 'right',
|
|
140
|
+
initialSticky = true,
|
|
141
|
+
initialBlockStyles = {}
|
|
142
|
+
}: HeaderLayoutRegistrarProps) {
|
|
143
|
+
const isDesignerRef = useRef(false);
|
|
144
|
+
// Initialize with server-provided values to avoid flash
|
|
145
|
+
const [sectionProperties, setSectionProperties] =
|
|
146
|
+
useState<HeaderLayoutProperties>({
|
|
147
|
+
layout: initialLayout,
|
|
148
|
+
menuPosition: initialMenuPosition,
|
|
149
|
+
searchPosition: initialSearchPosition,
|
|
150
|
+
utilityPosition: initialUtilityPosition,
|
|
151
|
+
sticky: initialSticky
|
|
152
|
+
});
|
|
153
|
+
const [isLayoutSelected, setIsLayoutSelected] = useState(false);
|
|
154
|
+
const [selectedBlockId, setSelectedBlockId] = useState<string | null>(null);
|
|
155
|
+
// Initialize block styles from server-provided values
|
|
156
|
+
const [blockStyles, setBlockStyles] = useState<Map<string, BlockStyles>>(
|
|
157
|
+
() => {
|
|
158
|
+
const initialMap = new Map<string, BlockStyles>();
|
|
159
|
+
Object.entries(initialBlockStyles).forEach(([blockId, styles]) => {
|
|
160
|
+
initialMap.set(blockId, styles);
|
|
161
|
+
});
|
|
162
|
+
return initialMap;
|
|
163
|
+
}
|
|
164
|
+
);
|
|
165
|
+
const blockStylesRef = useRef<Map<string, BlockStyles>>(
|
|
166
|
+
(() => {
|
|
167
|
+
const initialMap = new Map<string, BlockStyles>();
|
|
168
|
+
Object.entries(initialBlockStyles).forEach(([blockId, styles]) => {
|
|
169
|
+
initialMap.set(blockId, styles);
|
|
170
|
+
});
|
|
171
|
+
return initialMap;
|
|
172
|
+
})()
|
|
173
|
+
);
|
|
174
|
+
const hasReceivedThemeProps = useRef(false);
|
|
175
|
+
const previousLayoutRef = useRef<HeaderLayoutType | null>(initialLayout);
|
|
176
|
+
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
isDesignerRef.current = isInDesignerMode();
|
|
179
|
+
}, []);
|
|
180
|
+
|
|
181
|
+
const isDesigner = isDesignerRef.current;
|
|
182
|
+
|
|
183
|
+
// Helper to get block styles
|
|
184
|
+
const getBlockStyles = useCallback(
|
|
185
|
+
(blockId: string) => blockStyles.get(blockId),
|
|
186
|
+
[blockStyles]
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
// Helper to get blocks based on current layout
|
|
190
|
+
const getBlocksForLayout = useCallback((layout: HeaderLayoutType) => {
|
|
191
|
+
// UTILITY_ROW is always included - it's used for Language/Currency selects
|
|
192
|
+
const utilityRowBlock = {
|
|
193
|
+
id: HEADER_LAYOUT_BLOCKS.UTILITY_ROW.id,
|
|
194
|
+
type: HEADER_LAYOUT_BLOCKS.UTILITY_ROW.type,
|
|
195
|
+
label: HEADER_LAYOUT_BLOCKS.UTILITY_ROW.label
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
if (layout === 'two-row') {
|
|
199
|
+
return [
|
|
200
|
+
utilityRowBlock,
|
|
201
|
+
{
|
|
202
|
+
id: HEADER_LAYOUT_BLOCKS.TOP_ROW.id,
|
|
203
|
+
type: HEADER_LAYOUT_BLOCKS.TOP_ROW.type,
|
|
204
|
+
label: HEADER_LAYOUT_BLOCKS.TOP_ROW.label
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: HEADER_LAYOUT_BLOCKS.BOTTOM_ROW.id,
|
|
208
|
+
type: HEADER_LAYOUT_BLOCKS.BOTTOM_ROW.type,
|
|
209
|
+
label: HEADER_LAYOUT_BLOCKS.BOTTOM_ROW.label
|
|
210
|
+
}
|
|
211
|
+
];
|
|
212
|
+
}
|
|
213
|
+
// Default layout - utility row + single main row
|
|
214
|
+
return [
|
|
215
|
+
utilityRowBlock,
|
|
216
|
+
{
|
|
217
|
+
id: HEADER_LAYOUT_BLOCKS.MAIN_ROW.id,
|
|
218
|
+
type: HEADER_LAYOUT_BLOCKS.MAIN_ROW.type,
|
|
219
|
+
label: HEADER_LAYOUT_BLOCKS.MAIN_ROW.label
|
|
220
|
+
}
|
|
221
|
+
];
|
|
222
|
+
}, []);
|
|
223
|
+
|
|
224
|
+
// Register native widget with Theme Editor
|
|
225
|
+
// Properties are managed by Theme Editor, not sent with registration
|
|
226
|
+
useEffect(() => {
|
|
227
|
+
const isInIframe =
|
|
228
|
+
typeof window !== 'undefined' && window.self !== window.top;
|
|
229
|
+
if (!isInIframe || !window.parent) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const blocks = getBlocksForLayout(initialLayout);
|
|
234
|
+
|
|
235
|
+
// If already registered, just update blocks (in case layout changed)
|
|
236
|
+
if (typeof window !== 'undefined' && window.__headerLayoutRegistered) {
|
|
237
|
+
window.parent.postMessage(
|
|
238
|
+
{
|
|
239
|
+
type: 'UPDATE_SECTION_BLOCKS',
|
|
240
|
+
data: {
|
|
241
|
+
placeholderId: HEADER_LAYOUT_PLACEHOLDER_ID,
|
|
242
|
+
sectionId: HEADER_LAYOUT_SECTION_ID,
|
|
243
|
+
blocks
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
'*'
|
|
247
|
+
);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Send native widget registration to Theme Editor
|
|
252
|
+
// Include blocks for row containers
|
|
253
|
+
const nativeWidgetConfig = {
|
|
254
|
+
placeholderId: HEADER_LAYOUT_PLACEHOLDER_ID,
|
|
255
|
+
section: {
|
|
256
|
+
id: HEADER_LAYOUT_SECTION_ID,
|
|
257
|
+
type: 'native',
|
|
258
|
+
label: 'Layout',
|
|
259
|
+
blocks
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
window.parent.postMessage(
|
|
264
|
+
{
|
|
265
|
+
type: 'REGISTER_NATIVE_WIDGETS',
|
|
266
|
+
data: { widgets: [nativeWidgetConfig] }
|
|
267
|
+
},
|
|
268
|
+
'*'
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
// Mark as registered in window to survive component remount
|
|
272
|
+
window.__headerLayoutRegistered = true;
|
|
273
|
+
}, [initialLayout, getBlocksForLayout]);
|
|
274
|
+
|
|
275
|
+
// Apply highlight style to header when Layout section is selected
|
|
276
|
+
useEffect(() => {
|
|
277
|
+
if (typeof window === 'undefined') return;
|
|
278
|
+
|
|
279
|
+
const headerElement = document.querySelector('header');
|
|
280
|
+
if (!headerElement) return;
|
|
281
|
+
|
|
282
|
+
if (isLayoutSelected) {
|
|
283
|
+
// Apply selection highlight
|
|
284
|
+
headerElement.style.outline = '2px solid #3b82f6';
|
|
285
|
+
headerElement.style.outlineOffset = '-2px';
|
|
286
|
+
} else {
|
|
287
|
+
// Remove selection highlight
|
|
288
|
+
headerElement.style.outline = '';
|
|
289
|
+
headerElement.style.outlineOffset = '';
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return () => {
|
|
293
|
+
// Cleanup on unmount
|
|
294
|
+
headerElement.style.outline = '';
|
|
295
|
+
headerElement.style.outlineOffset = '';
|
|
296
|
+
};
|
|
297
|
+
}, [isLayoutSelected]);
|
|
298
|
+
|
|
299
|
+
// Listen for theme updates and selection changes from Theme Editor
|
|
300
|
+
useEffect(() => {
|
|
301
|
+
if (typeof window === 'undefined') return;
|
|
302
|
+
|
|
303
|
+
const handleMessage = (event: MessageEvent) => {
|
|
304
|
+
const { type, data } = event.data || {};
|
|
305
|
+
|
|
306
|
+
// Handle theme updates
|
|
307
|
+
if (
|
|
308
|
+
(type === 'UPDATE_THEME' || type === 'LOAD_THEME') &&
|
|
309
|
+
data?.theme?.placeholders
|
|
310
|
+
) {
|
|
311
|
+
const placeholder = data.theme.placeholders?.find(
|
|
312
|
+
(p: { slug: string }) => p.slug === HEADER_LAYOUT_PLACEHOLDER_ID
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
const layoutSection = placeholder?.sections?.find(
|
|
316
|
+
(s: { id: string }) => s.id === HEADER_LAYOUT_SECTION_ID
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
if (layoutSection) {
|
|
320
|
+
hasReceivedThemeProps.current = true;
|
|
321
|
+
if (layoutSection.properties) {
|
|
322
|
+
setSectionProperties(layoutSection.properties);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Extract block styles from theme update
|
|
326
|
+
if (layoutSection.blocks && Array.isArray(layoutSection.blocks)) {
|
|
327
|
+
setBlockStyles((prev) => {
|
|
328
|
+
const newMap = new Map(prev);
|
|
329
|
+
layoutSection.blocks.forEach(
|
|
330
|
+
(block: { id: string; styles?: Record<string, unknown> }) => {
|
|
331
|
+
if (block.id && block.styles) {
|
|
332
|
+
// Convert responsive styles to flat styles for current breakpoint
|
|
333
|
+
// Block styles format: { 'background-color': { desktop: '#ff0000', mobile: '#00ff00' } }
|
|
334
|
+
const flatStyles: Record<string, string> = {};
|
|
335
|
+
Object.entries(block.styles).forEach(([key, value]) => {
|
|
336
|
+
if (typeof value === 'object' && value !== null) {
|
|
337
|
+
// Get desktop value first, fallback to any available value
|
|
338
|
+
const responsiveValue = value as Record<string, string>;
|
|
339
|
+
flatStyles[key] =
|
|
340
|
+
responsiveValue.desktop ||
|
|
341
|
+
responsiveValue.mobile ||
|
|
342
|
+
Object.values(responsiveValue)[0] ||
|
|
343
|
+
'';
|
|
344
|
+
} else if (typeof value === 'string') {
|
|
345
|
+
flatStyles[key] = value;
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
if (Object.keys(flatStyles).length > 0) {
|
|
349
|
+
newMap.set(block.id, flatStyles);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
);
|
|
354
|
+
blockStylesRef.current = newMap;
|
|
355
|
+
return newMap;
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Handle property updates (when user changes dropdown in Theme Editor)
|
|
362
|
+
if (type === 'UPDATE_SECTION_PROPERTY' || type === 'UPDATE_PROPERTY') {
|
|
363
|
+
const { sectionId, placeholderId, key, value, properties } = data || {};
|
|
364
|
+
|
|
365
|
+
// Check if this is for our section
|
|
366
|
+
if (
|
|
367
|
+
sectionId === HEADER_LAYOUT_SECTION_ID ||
|
|
368
|
+
placeholderId === HEADER_LAYOUT_PLACEHOLDER_ID
|
|
369
|
+
) {
|
|
370
|
+
// If we get individual key/value
|
|
371
|
+
if (key && value !== undefined) {
|
|
372
|
+
setSectionProperties((prev) => ({
|
|
373
|
+
...prev,
|
|
374
|
+
[key]: value
|
|
375
|
+
}));
|
|
376
|
+
}
|
|
377
|
+
// If we get full properties object
|
|
378
|
+
if (properties) {
|
|
379
|
+
setSectionProperties(properties);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Handle block style updates
|
|
385
|
+
if (type === 'UPDATE_BLOCK_STYLE') {
|
|
386
|
+
const { sectionId, blockId, key, value, styles } = data || {};
|
|
387
|
+
|
|
388
|
+
if (sectionId === HEADER_LAYOUT_SECTION_ID && blockId) {
|
|
389
|
+
setBlockStyles((prev) => {
|
|
390
|
+
const newMap = new Map(prev);
|
|
391
|
+
const existingStyles = newMap.get(blockId) || {};
|
|
392
|
+
|
|
393
|
+
if (key && value !== undefined) {
|
|
394
|
+
newMap.set(blockId, { ...existingStyles, [key]: value });
|
|
395
|
+
}
|
|
396
|
+
if (styles) {
|
|
397
|
+
newMap.set(blockId, { ...existingStyles, ...styles });
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
blockStylesRef.current = newMap;
|
|
401
|
+
return newMap;
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Handle selection changes
|
|
407
|
+
if (type === 'SELECT_SECTION') {
|
|
408
|
+
const { placeholderId, sectionId } = data || {};
|
|
409
|
+
|
|
410
|
+
// Check if Layout section is selected
|
|
411
|
+
const isSelected =
|
|
412
|
+
placeholderId === HEADER_LAYOUT_PLACEHOLDER_ID &&
|
|
413
|
+
sectionId === HEADER_LAYOUT_SECTION_ID;
|
|
414
|
+
|
|
415
|
+
setIsLayoutSelected(isSelected);
|
|
416
|
+
if (!isSelected) {
|
|
417
|
+
setSelectedBlockId(null);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Handle block selection
|
|
422
|
+
if (type === 'SELECT_BLOCK') {
|
|
423
|
+
const { sectionId, blockId } = data || {};
|
|
424
|
+
if (sectionId === HEADER_LAYOUT_SECTION_ID) {
|
|
425
|
+
setSelectedBlockId(blockId || null);
|
|
426
|
+
setIsLayoutSelected(true);
|
|
427
|
+
} else {
|
|
428
|
+
setIsLayoutSelected(false);
|
|
429
|
+
setSelectedBlockId(null);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Handle deselection
|
|
434
|
+
if (type === 'DESELECT' || type === 'CLEAR_SELECTION') {
|
|
435
|
+
setIsLayoutSelected(false);
|
|
436
|
+
setSelectedBlockId(null);
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
window.addEventListener('message', handleMessage);
|
|
441
|
+
return () => window.removeEventListener('message', handleMessage);
|
|
442
|
+
}, []);
|
|
443
|
+
|
|
444
|
+
// Helper to extract layout value from potentially responsive property
|
|
445
|
+
const extractLayoutValue = (layoutProp: unknown): HeaderLayoutType => {
|
|
446
|
+
if (!layoutProp) return 'default';
|
|
447
|
+
|
|
448
|
+
// If it's a direct string value
|
|
449
|
+
if (typeof layoutProp === 'string') {
|
|
450
|
+
return layoutProp as HeaderLayoutType;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// If it's a responsive object (e.g., { desktop: 'two-row' })
|
|
454
|
+
if (typeof layoutProp === 'object' && layoutProp !== null) {
|
|
455
|
+
const obj = layoutProp as Record<string, string>;
|
|
456
|
+
// Try desktop first, then mobile, then any first value
|
|
457
|
+
return (obj.desktop ||
|
|
458
|
+
obj.mobile ||
|
|
459
|
+
Object.values(obj)[0] ||
|
|
460
|
+
'default') as HeaderLayoutType;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return 'default';
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
// Get the current layout type
|
|
467
|
+
const currentLayout: HeaderLayoutType = extractLayoutValue(
|
|
468
|
+
sectionProperties.layout
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
// Helper to extract menu position value from potentially responsive property
|
|
472
|
+
const extractMenuPositionValue = (menuPosProp: unknown): MenuPositionType => {
|
|
473
|
+
if (!menuPosProp) return 'center';
|
|
474
|
+
|
|
475
|
+
if (typeof menuPosProp === 'string') {
|
|
476
|
+
return menuPosProp as MenuPositionType;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (typeof menuPosProp === 'object' && menuPosProp !== null) {
|
|
480
|
+
const obj = menuPosProp as Record<string, string>;
|
|
481
|
+
return (obj.desktop ||
|
|
482
|
+
obj.mobile ||
|
|
483
|
+
Object.values(obj)[0] ||
|
|
484
|
+
'center') as MenuPositionType;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return 'center';
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
// Get the current menu position
|
|
491
|
+
const currentMenuPosition: MenuPositionType = extractMenuPositionValue(
|
|
492
|
+
sectionProperties.menuPosition
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
// Helper to extract search position value from potentially responsive property
|
|
496
|
+
const extractSearchPositionValue = (
|
|
497
|
+
searchPosProp: unknown
|
|
498
|
+
): SearchPositionType => {
|
|
499
|
+
if (!searchPosProp) return 'center';
|
|
500
|
+
|
|
501
|
+
if (typeof searchPosProp === 'string') {
|
|
502
|
+
return searchPosProp as SearchPositionType;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (typeof searchPosProp === 'object' && searchPosProp !== null) {
|
|
506
|
+
const obj = searchPosProp as Record<string, string>;
|
|
507
|
+
return (obj.desktop ||
|
|
508
|
+
obj.mobile ||
|
|
509
|
+
Object.values(obj)[0] ||
|
|
510
|
+
'center') as SearchPositionType;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return 'center';
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
// Get the current search position
|
|
517
|
+
const currentSearchPosition: SearchPositionType = extractSearchPositionValue(
|
|
518
|
+
sectionProperties.searchPosition
|
|
519
|
+
);
|
|
520
|
+
|
|
521
|
+
// Helper to extract utility position value from potentially responsive property
|
|
522
|
+
const extractUtilityPositionValue = (
|
|
523
|
+
utilityPosProp: unknown
|
|
524
|
+
): UtilityPositionType => {
|
|
525
|
+
if (!utilityPosProp) return 'right';
|
|
526
|
+
|
|
527
|
+
if (typeof utilityPosProp === 'string') {
|
|
528
|
+
return utilityPosProp as UtilityPositionType;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (typeof utilityPosProp === 'object' && utilityPosProp !== null) {
|
|
532
|
+
const obj = utilityPosProp as Record<string, string>;
|
|
533
|
+
return (obj.desktop ||
|
|
534
|
+
obj.mobile ||
|
|
535
|
+
Object.values(obj)[0] ||
|
|
536
|
+
'right') as UtilityPositionType;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return 'right';
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
// Get the current utility position (Language/Currency selects position)
|
|
543
|
+
const currentUtilityPosition: UtilityPositionType =
|
|
544
|
+
extractUtilityPositionValue(sectionProperties.utilityPosition);
|
|
545
|
+
|
|
546
|
+
// Helper to extract sticky value from potentially responsive property
|
|
547
|
+
const extractStickyValue = (stickyProp: unknown): boolean => {
|
|
548
|
+
if (stickyProp === undefined || stickyProp === null) return true;
|
|
549
|
+
|
|
550
|
+
if (typeof stickyProp === 'boolean') {
|
|
551
|
+
return stickyProp;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (typeof stickyProp === 'string') {
|
|
555
|
+
return stickyProp === 'true';
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
if (typeof stickyProp === 'object' && stickyProp !== null) {
|
|
559
|
+
const obj = stickyProp as Record<string, string | boolean>;
|
|
560
|
+
const value = obj.desktop ?? obj.mobile ?? Object.values(obj)[0];
|
|
561
|
+
return value === 'true' || value === true;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
return true;
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
// Get the current sticky value
|
|
568
|
+
const currentSticky: boolean = extractStickyValue(sectionProperties.sticky);
|
|
569
|
+
|
|
570
|
+
// Apply sticky styles to header element when sticky value changes
|
|
571
|
+
useEffect(() => {
|
|
572
|
+
if (typeof window === 'undefined') return;
|
|
573
|
+
|
|
574
|
+
const headerElement = document.querySelector('header');
|
|
575
|
+
if (!headerElement) return;
|
|
576
|
+
|
|
577
|
+
if (currentSticky) {
|
|
578
|
+
headerElement.style.position = 'sticky';
|
|
579
|
+
headerElement.style.top = '0';
|
|
580
|
+
headerElement.style.zIndex = '40';
|
|
581
|
+
} else {
|
|
582
|
+
headerElement.style.position = 'relative';
|
|
583
|
+
headerElement.style.top = 'auto';
|
|
584
|
+
headerElement.style.zIndex = 'auto';
|
|
585
|
+
}
|
|
586
|
+
}, [currentSticky]);
|
|
587
|
+
|
|
588
|
+
// Handle layout changes - unregister search section when switching to default
|
|
589
|
+
// and update blocks in Theme Editor
|
|
590
|
+
useEffect(() => {
|
|
591
|
+
const isInIframe =
|
|
592
|
+
typeof window !== 'undefined' && window.self !== window.top;
|
|
593
|
+
if (!isInIframe || !window.parent) {
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const previousLayout = previousLayoutRef.current;
|
|
598
|
+
|
|
599
|
+
// If layout changed from two-row to default, remove search section
|
|
600
|
+
if (previousLayout === 'two-row' && currentLayout === 'default') {
|
|
601
|
+
window.parent.postMessage(
|
|
602
|
+
{
|
|
603
|
+
type: 'UNREGISTER_NATIVE_SECTION',
|
|
604
|
+
data: {
|
|
605
|
+
placeholderId: 'header',
|
|
606
|
+
sectionId: 'header-search'
|
|
607
|
+
}
|
|
608
|
+
},
|
|
609
|
+
'*'
|
|
610
|
+
);
|
|
611
|
+
// Reset registration flags
|
|
612
|
+
if (typeof window !== 'undefined') {
|
|
613
|
+
window.__headerSearchRegistered = false;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Update blocks when layout changes
|
|
618
|
+
if (previousLayout !== currentLayout && previousLayout !== null) {
|
|
619
|
+
window.parent.postMessage(
|
|
620
|
+
{
|
|
621
|
+
type: 'UPDATE_SECTION_BLOCKS',
|
|
622
|
+
data: {
|
|
623
|
+
placeholderId: HEADER_LAYOUT_PLACEHOLDER_ID,
|
|
624
|
+
sectionId: HEADER_LAYOUT_SECTION_ID,
|
|
625
|
+
blocks: getBlocksForLayout(currentLayout)
|
|
626
|
+
}
|
|
627
|
+
},
|
|
628
|
+
'*'
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Update previous layout ref
|
|
633
|
+
previousLayoutRef.current = currentLayout;
|
|
634
|
+
}, [currentLayout, getBlocksForLayout]);
|
|
635
|
+
|
|
636
|
+
// If no children, just return null (registration-only mode)
|
|
637
|
+
if (!children) {
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// Provide layout context to children
|
|
642
|
+
return (
|
|
643
|
+
<HeaderLayoutContext.Provider
|
|
644
|
+
value={{
|
|
645
|
+
layout: currentLayout,
|
|
646
|
+
menuPosition: currentMenuPosition,
|
|
647
|
+
searchPosition: currentSearchPosition,
|
|
648
|
+
utilityPosition: currentUtilityPosition,
|
|
649
|
+
sticky: currentSticky,
|
|
650
|
+
isDesigner,
|
|
651
|
+
selectedBlockId,
|
|
652
|
+
getBlockStyles
|
|
653
|
+
}}
|
|
654
|
+
>
|
|
655
|
+
{children}
|
|
656
|
+
</HeaderLayoutContext.Provider>
|
|
657
|
+
);
|
|
658
|
+
}
|