@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.
- package/CHANGELOG.md +8 -0
- package/app-template/CHANGELOG.md +138 -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/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,254 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Checkout Address Section Registrar
|
|
5
|
-
*
|
|
6
|
-
* This component registers the "Address" section for the checkout placeholder.
|
|
7
|
-
* It provides the current address view type to child components via context.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
createContext,
|
|
12
|
-
useContext,
|
|
13
|
-
useEffect,
|
|
14
|
-
useRef,
|
|
15
|
-
useState,
|
|
16
|
-
ReactNode
|
|
17
|
-
} from 'react';
|
|
18
|
-
|
|
19
|
-
import {
|
|
20
|
-
CHECKOUT_PLACEHOLDER_ID,
|
|
21
|
-
CHECKOUT_ADDRESS_SECTION_ID
|
|
22
|
-
} from './constants';
|
|
23
|
-
|
|
24
|
-
// View types
|
|
25
|
-
export type AddressViewType = 'box' | 'dropdown';
|
|
26
|
-
|
|
27
|
-
// Global flag to track if registration has been done (survives component remount)
|
|
28
|
-
declare global {
|
|
29
|
-
interface Window {
|
|
30
|
-
__checkoutAddressRegistered?: boolean;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Check if running inside designer iframe
|
|
36
|
-
*/
|
|
37
|
-
function isInDesignerMode(): boolean {
|
|
38
|
-
if (typeof window === 'undefined') return false;
|
|
39
|
-
return window.self !== window.top;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface CheckoutAddressProperties {
|
|
43
|
-
viewType?: AddressViewType | Record<string, string>;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface CheckoutAddressStyles {
|
|
47
|
-
[key: string]: string | number;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Context for sharing view type with other components
|
|
51
|
-
interface CheckoutAddressContextValue {
|
|
52
|
-
viewType: AddressViewType;
|
|
53
|
-
isDesigner: boolean;
|
|
54
|
-
sectionStyles: CheckoutAddressStyles;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const CheckoutAddressContext = createContext<CheckoutAddressContextValue>({
|
|
58
|
-
viewType: 'box',
|
|
59
|
-
isDesigner: false,
|
|
60
|
-
sectionStyles: {}
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
export const useCheckoutAddress = () => useContext(CheckoutAddressContext);
|
|
64
|
-
|
|
65
|
-
interface CheckoutAddressRegistrarProps {
|
|
66
|
-
children?: ReactNode;
|
|
67
|
-
initialViewType?: AddressViewType;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function CheckoutAddressRegistrar({
|
|
71
|
-
children,
|
|
72
|
-
initialViewType = 'box'
|
|
73
|
-
}: CheckoutAddressRegistrarProps) {
|
|
74
|
-
const isDesignerRef = useRef(false);
|
|
75
|
-
|
|
76
|
-
const [sectionProperties, setSectionProperties] =
|
|
77
|
-
useState<CheckoutAddressProperties>({
|
|
78
|
-
viewType: initialViewType
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
const [sectionStyles, setSectionStyles] = useState<CheckoutAddressStyles>({});
|
|
82
|
-
|
|
83
|
-
const [isSectionSelected, setIsSectionSelected] = useState(false);
|
|
84
|
-
|
|
85
|
-
useEffect(() => {
|
|
86
|
-
isDesignerRef.current = isInDesignerMode();
|
|
87
|
-
}, []);
|
|
88
|
-
|
|
89
|
-
const isDesigner = isDesignerRef.current;
|
|
90
|
-
|
|
91
|
-
// Register native widget with Theme Editor
|
|
92
|
-
useEffect(() => {
|
|
93
|
-
const isInIframe =
|
|
94
|
-
typeof window !== 'undefined' && window.self !== window.top;
|
|
95
|
-
if (!isInIframe || !window.parent) {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// If already registered, skip
|
|
100
|
-
if (typeof window !== 'undefined' && window.__checkoutAddressRegistered) {
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const nativeWidgetConfig = {
|
|
105
|
-
placeholderId: CHECKOUT_PLACEHOLDER_ID,
|
|
106
|
-
section: {
|
|
107
|
-
id: CHECKOUT_ADDRESS_SECTION_ID,
|
|
108
|
-
type: 'native',
|
|
109
|
-
label: 'Addresses',
|
|
110
|
-
blocks: []
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
window.parent.postMessage(
|
|
115
|
-
{
|
|
116
|
-
type: 'REGISTER_NATIVE_WIDGETS',
|
|
117
|
-
data: { widgets: [nativeWidgetConfig] }
|
|
118
|
-
},
|
|
119
|
-
'*'
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
window.__checkoutAddressRegistered = true;
|
|
123
|
-
}, []);
|
|
124
|
-
|
|
125
|
-
// Listen for theme updates and selection changes from Theme Editor
|
|
126
|
-
useEffect(() => {
|
|
127
|
-
if (typeof window === 'undefined') return;
|
|
128
|
-
|
|
129
|
-
const handleMessage = (event: MessageEvent) => {
|
|
130
|
-
const { type, data } = event.data || {};
|
|
131
|
-
|
|
132
|
-
// Handle theme updates
|
|
133
|
-
if (
|
|
134
|
-
(type === 'UPDATE_THEME' || type === 'LOAD_THEME') &&
|
|
135
|
-
data?.theme?.placeholders
|
|
136
|
-
) {
|
|
137
|
-
const placeholder = data.theme.placeholders?.find(
|
|
138
|
-
(p: { slug: string }) => p.slug === CHECKOUT_PLACEHOLDER_ID
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
const section = placeholder?.sections?.find(
|
|
142
|
-
(s: { id: string }) => s.id === CHECKOUT_ADDRESS_SECTION_ID
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
if (section) {
|
|
146
|
-
if (section.properties) {
|
|
147
|
-
setSectionProperties(section.properties);
|
|
148
|
-
}
|
|
149
|
-
if (section.styles) {
|
|
150
|
-
// Flatten responsive styles to current breakpoint (desktop by default)
|
|
151
|
-
const flatStyles: CheckoutAddressStyles = {};
|
|
152
|
-
Object.entries(section.styles).forEach(([key, value]) => {
|
|
153
|
-
if (typeof value === 'object' && value !== null) {
|
|
154
|
-
// Get desktop value first, fallback to any available value
|
|
155
|
-
const responsiveValue = value as Record<
|
|
156
|
-
string,
|
|
157
|
-
string | number
|
|
158
|
-
>;
|
|
159
|
-
flatStyles[key] =
|
|
160
|
-
responsiveValue.desktop ||
|
|
161
|
-
responsiveValue.mobile ||
|
|
162
|
-
responsiveValue.tablet ||
|
|
163
|
-
Object.values(responsiveValue)[0] ||
|
|
164
|
-
'';
|
|
165
|
-
} else {
|
|
166
|
-
if (typeof value === 'string' || typeof value === 'number') {
|
|
167
|
-
flatStyles[key] = value;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
setSectionStyles(flatStyles);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Handle property updates
|
|
177
|
-
if (type === 'UPDATE_SECTION_PROPERTY' || type === 'UPDATE_PROPERTY') {
|
|
178
|
-
const { sectionId, placeholderId, key, value, properties } = data || {};
|
|
179
|
-
|
|
180
|
-
if (
|
|
181
|
-
sectionId === CHECKOUT_ADDRESS_SECTION_ID ||
|
|
182
|
-
placeholderId === CHECKOUT_PLACEHOLDER_ID
|
|
183
|
-
) {
|
|
184
|
-
if (key && value !== undefined) {
|
|
185
|
-
setSectionProperties((prev) => ({
|
|
186
|
-
...prev,
|
|
187
|
-
[key]: value
|
|
188
|
-
}));
|
|
189
|
-
}
|
|
190
|
-
if (properties) {
|
|
191
|
-
setSectionProperties(properties);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Handle selection
|
|
197
|
-
if (type === 'SELECT_SECTION') {
|
|
198
|
-
const { placeholderId, sectionId } = data || {};
|
|
199
|
-
setIsSectionSelected(
|
|
200
|
-
placeholderId === CHECKOUT_PLACEHOLDER_ID &&
|
|
201
|
-
sectionId === CHECKOUT_ADDRESS_SECTION_ID
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
if (type === 'DESELECT' || type === 'CLEAR_SELECTION') {
|
|
206
|
-
setIsSectionSelected(false);
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
window.addEventListener('message', handleMessage);
|
|
211
|
-
return () => window.removeEventListener('message', handleMessage);
|
|
212
|
-
}, []);
|
|
213
|
-
|
|
214
|
-
// Extraction helper
|
|
215
|
-
const extractViewType = (prop: unknown): AddressViewType => {
|
|
216
|
-
if (!prop) return 'box';
|
|
217
|
-
|
|
218
|
-
if (typeof prop === 'string') {
|
|
219
|
-
return prop as AddressViewType;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
if (typeof prop === 'object' && prop !== null) {
|
|
223
|
-
const obj = prop as Record<string, string>;
|
|
224
|
-
return (obj.desktop ||
|
|
225
|
-
obj.mobile ||
|
|
226
|
-
Object.values(obj)[0] ||
|
|
227
|
-
'box') as AddressViewType;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return 'box';
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
const currentViewType = extractViewType(sectionProperties.viewType);
|
|
234
|
-
|
|
235
|
-
return (
|
|
236
|
-
<CheckoutAddressContext.Provider
|
|
237
|
-
value={{
|
|
238
|
-
viewType: currentViewType,
|
|
239
|
-
isDesigner,
|
|
240
|
-
sectionStyles
|
|
241
|
-
}}
|
|
242
|
-
>
|
|
243
|
-
<div
|
|
244
|
-
style={
|
|
245
|
-
isSectionSelected
|
|
246
|
-
? { outline: '2px solid #3b82f6', outlineOffset: '2px' }
|
|
247
|
-
: undefined
|
|
248
|
-
}
|
|
249
|
-
>
|
|
250
|
-
{children}
|
|
251
|
-
</div>
|
|
252
|
-
</CheckoutAddressContext.Provider>
|
|
253
|
-
);
|
|
254
|
-
}
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Checkout Buttons Section Registrar
|
|
5
|
-
*
|
|
6
|
-
* This component registers the "Buttons" section for the checkout placeholder.
|
|
7
|
-
* It provides button styles to child components via context.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
createContext,
|
|
12
|
-
useContext,
|
|
13
|
-
useEffect,
|
|
14
|
-
useRef,
|
|
15
|
-
useState,
|
|
16
|
-
ReactNode
|
|
17
|
-
} from 'react';
|
|
18
|
-
|
|
19
|
-
import {
|
|
20
|
-
CHECKOUT_PLACEHOLDER_ID,
|
|
21
|
-
CHECKOUT_BUTTONS_SECTION_ID
|
|
22
|
-
} from './constants';
|
|
23
|
-
|
|
24
|
-
// Global flag to track if registration has been done (survives component remount)
|
|
25
|
-
declare global {
|
|
26
|
-
interface Window {
|
|
27
|
-
__checkoutButtonsRegistered?: boolean;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Check if running inside designer iframe
|
|
33
|
-
*/
|
|
34
|
-
function isInDesignerMode(): boolean {
|
|
35
|
-
if (typeof window === 'undefined') return false;
|
|
36
|
-
return window.self !== window.top;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface CheckoutButtonsStyles {
|
|
40
|
-
[key: string]: string | number;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Context for sharing styles with other components
|
|
44
|
-
interface CheckoutButtonsContextValue {
|
|
45
|
-
isDesigner: boolean;
|
|
46
|
-
sectionStyles: CheckoutButtonsStyles;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const CheckoutButtonsContext = createContext<CheckoutButtonsContextValue>({
|
|
50
|
-
isDesigner: false,
|
|
51
|
-
sectionStyles: {}
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
export const useCheckoutButtons = () => useContext(CheckoutButtonsContext);
|
|
55
|
-
|
|
56
|
-
interface CheckoutButtonsRegistrarProps {
|
|
57
|
-
children?: ReactNode;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function CheckoutButtonsRegistrar({
|
|
61
|
-
children
|
|
62
|
-
}: CheckoutButtonsRegistrarProps) {
|
|
63
|
-
const isDesignerRef = useRef(false);
|
|
64
|
-
const [sectionStyles, setSectionStyles] = useState<CheckoutButtonsStyles>({});
|
|
65
|
-
const [isSectionSelected, setIsSectionSelected] = useState(false);
|
|
66
|
-
|
|
67
|
-
useEffect(() => {
|
|
68
|
-
isDesignerRef.current = isInDesignerMode();
|
|
69
|
-
}, []);
|
|
70
|
-
|
|
71
|
-
const isDesigner = isDesignerRef.current;
|
|
72
|
-
|
|
73
|
-
// Register native widget with Theme Editor
|
|
74
|
-
useEffect(() => {
|
|
75
|
-
const isInIframe =
|
|
76
|
-
typeof window !== 'undefined' && window.self !== window.top;
|
|
77
|
-
if (!isInIframe || !window.parent) {
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// If already registered, skip
|
|
82
|
-
if (typeof window !== 'undefined' && window.__checkoutButtonsRegistered) {
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const nativeWidgetConfig = {
|
|
87
|
-
placeholderId: CHECKOUT_PLACEHOLDER_ID,
|
|
88
|
-
section: {
|
|
89
|
-
id: CHECKOUT_BUTTONS_SECTION_ID,
|
|
90
|
-
type: 'native',
|
|
91
|
-
label: 'Buttons',
|
|
92
|
-
blocks: []
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
window.parent.postMessage(
|
|
97
|
-
{
|
|
98
|
-
type: 'REGISTER_NATIVE_WIDGETS',
|
|
99
|
-
data: { widgets: [nativeWidgetConfig] }
|
|
100
|
-
},
|
|
101
|
-
'*'
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
window.__checkoutButtonsRegistered = true;
|
|
105
|
-
}, []);
|
|
106
|
-
|
|
107
|
-
// Listen for theme updates and selection changes from Theme Editor
|
|
108
|
-
useEffect(() => {
|
|
109
|
-
if (typeof window === 'undefined') return;
|
|
110
|
-
|
|
111
|
-
const handleMessage = (event: MessageEvent) => {
|
|
112
|
-
const { type, data } = event.data || {};
|
|
113
|
-
|
|
114
|
-
// Handle theme updates
|
|
115
|
-
if (
|
|
116
|
-
(type === 'UPDATE_THEME' || type === 'LOAD_THEME') &&
|
|
117
|
-
data?.theme?.placeholders
|
|
118
|
-
) {
|
|
119
|
-
const placeholder = data.theme.placeholders?.find(
|
|
120
|
-
(p: { slug: string }) => p.slug === CHECKOUT_PLACEHOLDER_ID
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
const section = placeholder?.sections?.find(
|
|
124
|
-
(s: { id: string }) => s.id === CHECKOUT_BUTTONS_SECTION_ID
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
if (section && section.styles) {
|
|
128
|
-
// Flatten responsive styles to current breakpoint (desktop by default)
|
|
129
|
-
const flatStyles: CheckoutButtonsStyles = {};
|
|
130
|
-
Object.entries(section.styles).forEach(([key, value]) => {
|
|
131
|
-
if (typeof value === 'object' && value !== null) {
|
|
132
|
-
const responsiveValue = value as Record<string, string | number>;
|
|
133
|
-
flatStyles[key] =
|
|
134
|
-
responsiveValue.desktop ||
|
|
135
|
-
responsiveValue.mobile ||
|
|
136
|
-
responsiveValue.tablet ||
|
|
137
|
-
Object.values(responsiveValue)[0] ||
|
|
138
|
-
'';
|
|
139
|
-
} else if (typeof value === 'string' || typeof value === 'number') {
|
|
140
|
-
flatStyles[key] = value;
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
setSectionStyles(flatStyles);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Handle section selection
|
|
148
|
-
if (type === 'SECTION_SELECTED') {
|
|
149
|
-
const isSelected =
|
|
150
|
-
data?.placeholderId === CHECKOUT_PLACEHOLDER_ID &&
|
|
151
|
-
data?.sectionId === CHECKOUT_BUTTONS_SECTION_ID;
|
|
152
|
-
setIsSectionSelected(isSelected);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if (type === 'SECTION_DESELECTED') {
|
|
156
|
-
setIsSectionSelected(false);
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
window.addEventListener('message', handleMessage);
|
|
161
|
-
return () => window.removeEventListener('message', handleMessage);
|
|
162
|
-
}, []);
|
|
163
|
-
|
|
164
|
-
return (
|
|
165
|
-
<CheckoutButtonsContext.Provider
|
|
166
|
-
value={{
|
|
167
|
-
isDesigner,
|
|
168
|
-
sectionStyles
|
|
169
|
-
}}
|
|
170
|
-
>
|
|
171
|
-
<div
|
|
172
|
-
data-native-section={CHECKOUT_BUTTONS_SECTION_ID}
|
|
173
|
-
data-placeholder={CHECKOUT_PLACEHOLDER_ID}
|
|
174
|
-
style={{
|
|
175
|
-
outline: isSectionSelected ? '2px solid #2359C4' : 'none',
|
|
176
|
-
outlineOffset: '2px'
|
|
177
|
-
}}
|
|
178
|
-
>
|
|
179
|
-
{children}
|
|
180
|
-
</div>
|
|
181
|
-
</CheckoutButtonsContext.Provider>
|
|
182
|
-
);
|
|
183
|
-
}
|
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Checkout Delivery Method Section Registrar
|
|
5
|
-
*
|
|
6
|
-
* This component registers the "Delivery Method" section for the checkout placeholder.
|
|
7
|
-
* It provides the current view type and styles to child components via context.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
createContext,
|
|
12
|
-
useContext,
|
|
13
|
-
useEffect,
|
|
14
|
-
useRef,
|
|
15
|
-
useState,
|
|
16
|
-
ReactNode
|
|
17
|
-
} from 'react';
|
|
18
|
-
|
|
19
|
-
// Constants
|
|
20
|
-
export const CHECKOUT_PLACEHOLDER_ID = 'checkout-page';
|
|
21
|
-
export const CHECKOUT_DELIVERY_METHOD_SECTION_ID = 'checkout-delivery-method';
|
|
22
|
-
|
|
23
|
-
// View types
|
|
24
|
-
export type DeliveryMethodViewType = 'radio' | 'dropdown';
|
|
25
|
-
|
|
26
|
-
// Global flag to track if registration has been done (survives component remount)
|
|
27
|
-
declare global {
|
|
28
|
-
interface Window {
|
|
29
|
-
__checkoutDeliveryMethodRegistered?: boolean;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Check if running inside designer iframe
|
|
35
|
-
*/
|
|
36
|
-
function isInDesignerMode(): boolean {
|
|
37
|
-
if (typeof window === 'undefined') return false;
|
|
38
|
-
return window.self !== window.top;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface CheckoutDeliveryMethodProperties {
|
|
42
|
-
viewType?: DeliveryMethodViewType | Record<string, string>;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface CheckoutDeliveryMethodStyles {
|
|
46
|
-
[key: string]: string | number;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Context for sharing view type with other components
|
|
50
|
-
interface CheckoutDeliveryMethodContextValue {
|
|
51
|
-
viewType: DeliveryMethodViewType;
|
|
52
|
-
isDesigner: boolean;
|
|
53
|
-
sectionStyles: CheckoutDeliveryMethodStyles;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const CheckoutDeliveryMethodContext =
|
|
57
|
-
createContext<CheckoutDeliveryMethodContextValue>({
|
|
58
|
-
viewType: 'radio',
|
|
59
|
-
isDesigner: false,
|
|
60
|
-
sectionStyles: {}
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
export const useCheckoutDeliveryMethod = () =>
|
|
64
|
-
useContext(CheckoutDeliveryMethodContext);
|
|
65
|
-
|
|
66
|
-
interface CheckoutDeliveryMethodRegistrarProps {
|
|
67
|
-
children?: ReactNode;
|
|
68
|
-
initialViewType?: DeliveryMethodViewType;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function CheckoutDeliveryMethodRegistrar({
|
|
72
|
-
children,
|
|
73
|
-
initialViewType = 'radio'
|
|
74
|
-
}: CheckoutDeliveryMethodRegistrarProps) {
|
|
75
|
-
const isDesignerRef = useRef(false);
|
|
76
|
-
|
|
77
|
-
const [sectionProperties, setSectionProperties] =
|
|
78
|
-
useState<CheckoutDeliveryMethodProperties>({
|
|
79
|
-
viewType: initialViewType
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
const [sectionStyles, setSectionStyles] =
|
|
83
|
-
useState<CheckoutDeliveryMethodStyles>({});
|
|
84
|
-
|
|
85
|
-
const [isSectionSelected, setIsSectionSelected] = useState(false);
|
|
86
|
-
|
|
87
|
-
useEffect(() => {
|
|
88
|
-
isDesignerRef.current = isInDesignerMode();
|
|
89
|
-
}, []);
|
|
90
|
-
|
|
91
|
-
const isDesigner = isDesignerRef.current;
|
|
92
|
-
|
|
93
|
-
// Register native widget with Theme Editor
|
|
94
|
-
useEffect(() => {
|
|
95
|
-
const isInIframe =
|
|
96
|
-
typeof window !== 'undefined' && window.self !== window.top;
|
|
97
|
-
if (!isInIframe || !window.parent) {
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// If already registered, skip
|
|
102
|
-
if (
|
|
103
|
-
typeof window !== 'undefined' &&
|
|
104
|
-
window.__checkoutDeliveryMethodRegistered
|
|
105
|
-
) {
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const nativeWidgetConfig = {
|
|
110
|
-
placeholderId: CHECKOUT_PLACEHOLDER_ID,
|
|
111
|
-
section: {
|
|
112
|
-
id: CHECKOUT_DELIVERY_METHOD_SECTION_ID,
|
|
113
|
-
type: 'native',
|
|
114
|
-
label: 'Delivery Method',
|
|
115
|
-
blocks: []
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
window.parent.postMessage(
|
|
120
|
-
{
|
|
121
|
-
type: 'REGISTER_NATIVE_WIDGETS',
|
|
122
|
-
data: { widgets: [nativeWidgetConfig] }
|
|
123
|
-
},
|
|
124
|
-
'*'
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
window.__checkoutDeliveryMethodRegistered = true;
|
|
128
|
-
}, []);
|
|
129
|
-
|
|
130
|
-
// Listen for theme updates and selection changes from Theme Editor
|
|
131
|
-
useEffect(() => {
|
|
132
|
-
if (typeof window === 'undefined') return;
|
|
133
|
-
|
|
134
|
-
const handleMessage = (event: MessageEvent) => {
|
|
135
|
-
const { type, data } = event.data || {};
|
|
136
|
-
|
|
137
|
-
// Handle theme updates
|
|
138
|
-
if (
|
|
139
|
-
(type === 'UPDATE_THEME' || type === 'LOAD_THEME') &&
|
|
140
|
-
data?.theme?.placeholders
|
|
141
|
-
) {
|
|
142
|
-
const placeholder = data.theme.placeholders?.find(
|
|
143
|
-
(p: { slug: string }) => p.slug === CHECKOUT_PLACEHOLDER_ID
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
const section = placeholder?.sections?.find(
|
|
147
|
-
(s: { id: string }) => s.id === CHECKOUT_DELIVERY_METHOD_SECTION_ID
|
|
148
|
-
);
|
|
149
|
-
|
|
150
|
-
if (section) {
|
|
151
|
-
if (section.properties) {
|
|
152
|
-
setSectionProperties(section.properties);
|
|
153
|
-
}
|
|
154
|
-
if (section.styles) {
|
|
155
|
-
// Flatten responsive styles to current breakpoint (desktop by default)
|
|
156
|
-
const flatStyles: CheckoutDeliveryMethodStyles = {};
|
|
157
|
-
Object.entries(section.styles).forEach(([key, value]) => {
|
|
158
|
-
if (typeof value === 'object' && value !== null) {
|
|
159
|
-
const responsiveValue = value as Record<
|
|
160
|
-
string,
|
|
161
|
-
string | number
|
|
162
|
-
>;
|
|
163
|
-
flatStyles[key] =
|
|
164
|
-
responsiveValue.desktop ||
|
|
165
|
-
responsiveValue.mobile ||
|
|
166
|
-
responsiveValue.tablet ||
|
|
167
|
-
Object.values(responsiveValue)[0] ||
|
|
168
|
-
'';
|
|
169
|
-
} else if (
|
|
170
|
-
typeof value === 'string' ||
|
|
171
|
-
typeof value === 'number'
|
|
172
|
-
) {
|
|
173
|
-
flatStyles[key] = value;
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
setSectionStyles(flatStyles);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Handle property updates
|
|
182
|
-
if (type === 'UPDATE_SECTION_PROPERTY' || type === 'UPDATE_PROPERTY') {
|
|
183
|
-
const { sectionId, placeholderId, key, value, properties } = data || {};
|
|
184
|
-
|
|
185
|
-
if (
|
|
186
|
-
sectionId === CHECKOUT_DELIVERY_METHOD_SECTION_ID ||
|
|
187
|
-
placeholderId === CHECKOUT_PLACEHOLDER_ID
|
|
188
|
-
) {
|
|
189
|
-
if (key && value !== undefined) {
|
|
190
|
-
setSectionProperties((prev) => ({
|
|
191
|
-
...prev,
|
|
192
|
-
[key]: value
|
|
193
|
-
}));
|
|
194
|
-
}
|
|
195
|
-
if (properties) {
|
|
196
|
-
setSectionProperties(properties);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Handle selection
|
|
202
|
-
if (type === 'SELECT_SECTION') {
|
|
203
|
-
const { placeholderId, sectionId } = data || {};
|
|
204
|
-
setIsSectionSelected(
|
|
205
|
-
placeholderId === CHECKOUT_PLACEHOLDER_ID &&
|
|
206
|
-
sectionId === CHECKOUT_DELIVERY_METHOD_SECTION_ID
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (type === 'DESELECT' || type === 'CLEAR_SELECTION') {
|
|
211
|
-
setIsSectionSelected(false);
|
|
212
|
-
}
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
window.addEventListener('message', handleMessage);
|
|
216
|
-
return () => window.removeEventListener('message', handleMessage);
|
|
217
|
-
}, []);
|
|
218
|
-
|
|
219
|
-
// Extraction helper
|
|
220
|
-
const extractViewType = (prop: unknown): DeliveryMethodViewType => {
|
|
221
|
-
if (!prop) return 'radio';
|
|
222
|
-
|
|
223
|
-
if (typeof prop === 'string') {
|
|
224
|
-
return prop as DeliveryMethodViewType;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
if (typeof prop === 'object' && prop !== null) {
|
|
228
|
-
const obj = prop as Record<string, string>;
|
|
229
|
-
return (obj.desktop ||
|
|
230
|
-
obj.mobile ||
|
|
231
|
-
Object.values(obj)[0] ||
|
|
232
|
-
'radio') as DeliveryMethodViewType;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
return 'radio';
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
const currentViewType = extractViewType(sectionProperties.viewType);
|
|
239
|
-
|
|
240
|
-
return (
|
|
241
|
-
<CheckoutDeliveryMethodContext.Provider
|
|
242
|
-
value={{
|
|
243
|
-
viewType: currentViewType,
|
|
244
|
-
isDesigner,
|
|
245
|
-
sectionStyles
|
|
246
|
-
}}
|
|
247
|
-
>
|
|
248
|
-
<div
|
|
249
|
-
style={
|
|
250
|
-
isSectionSelected
|
|
251
|
-
? { outline: '2px solid #3b82f6', outlineOffset: '2px' }
|
|
252
|
-
: undefined
|
|
253
|
-
}
|
|
254
|
-
>
|
|
255
|
-
{children}
|
|
256
|
-
</div>
|
|
257
|
-
</CheckoutDeliveryMethodContext.Provider>
|
|
258
|
-
);
|
|
259
|
-
}
|