@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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { ReactNode, useMemo, useRef, useCallback } from 'react';
|
|
3
|
+
import { ReactNode, useMemo, useRef, useCallback, CSSProperties } from 'react';
|
|
4
|
+
import { useRouter } from '@akinon/next/hooks';
|
|
4
5
|
import { useGetMiniBasketQuery } from '@akinon/next/data/client/basket';
|
|
5
6
|
import { useAppDispatch, useAppSelector } from '@akinon/next/redux/hooks';
|
|
6
7
|
import {
|
|
@@ -14,8 +15,104 @@ import { ROUTES } from '@theme/routes';
|
|
|
14
15
|
import MiniBasket from './mini-basket';
|
|
15
16
|
import { Badge, Icon, Link } from '@theme/components';
|
|
16
17
|
import { useOnClickOutside } from '@akinon/next/hooks';
|
|
18
|
+
import { useSession } from 'next-auth/react';
|
|
19
|
+
import { useHeaderIconSettings } from './use-header-icons';
|
|
20
|
+
import { useDesignerFeatures } from '@akinon/next/components/theme-editor/hooks/use-designer-features';
|
|
21
|
+
import {
|
|
22
|
+
HeaderMiniBasketProvider,
|
|
23
|
+
useHeaderMiniBasket
|
|
24
|
+
} from './header-mini-basket-context';
|
|
25
|
+
import {
|
|
26
|
+
HeaderIconsProvider,
|
|
27
|
+
useHeaderIconsDesigner,
|
|
28
|
+
HEADER_ICONS_PLACEHOLDER_ID,
|
|
29
|
+
HEADER_ICONS_SECTION_ID,
|
|
30
|
+
HEADER_ICON_BLOCKS
|
|
31
|
+
} from './header-icons-context';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Convert block styles to CSS properties
|
|
35
|
+
*/
|
|
36
|
+
function convertBlockStylesToCSS(
|
|
37
|
+
styles: Record<string, unknown> | undefined
|
|
38
|
+
): CSSProperties {
|
|
39
|
+
if (!styles) return {};
|
|
40
|
+
|
|
41
|
+
const result: Record<string, string | number> = {};
|
|
42
|
+
|
|
43
|
+
const styleMap: Record<string, string> = {
|
|
44
|
+
'margin-top': 'marginTop',
|
|
45
|
+
'margin-right': 'marginRight',
|
|
46
|
+
'margin-bottom': 'marginBottom',
|
|
47
|
+
'margin-left': 'marginLeft',
|
|
48
|
+
'background-color': 'backgroundColor'
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
Object.entries(styles).forEach(([key, value]) => {
|
|
52
|
+
const cssKey = styleMap[key] || key;
|
|
53
|
+
// Extract desktop value if responsive
|
|
54
|
+
if (value && typeof value === 'object' && 'desktop' in value) {
|
|
55
|
+
result[cssKey] = (value as Record<string, string>).desktop;
|
|
56
|
+
} else if (typeof value === 'string' || typeof value === 'number') {
|
|
57
|
+
result[cssKey] = value;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return result as CSSProperties;
|
|
62
|
+
}
|
|
63
|
+
import { HeaderIconSettings } from './server-settings-parser';
|
|
64
|
+
import { useHeaderLayout } from './header-layout-registrar';
|
|
65
|
+
|
|
66
|
+
// Inline selectable wrapper for header icons - doesn't add extra wrapper div
|
|
67
|
+
function SelectableIcon({
|
|
68
|
+
blockId,
|
|
69
|
+
blockType,
|
|
70
|
+
blockLabel,
|
|
71
|
+
isDesigner,
|
|
72
|
+
isSelected,
|
|
73
|
+
children
|
|
74
|
+
}: {
|
|
75
|
+
blockId: string;
|
|
76
|
+
blockType: string;
|
|
77
|
+
blockLabel: string;
|
|
78
|
+
isDesigner: boolean;
|
|
79
|
+
isSelected: boolean;
|
|
80
|
+
children: React.ReactElement;
|
|
81
|
+
}) {
|
|
82
|
+
const { handleClick } = useDesignerFeatures({
|
|
83
|
+
blockId,
|
|
84
|
+
placeholderId: HEADER_ICONS_PLACEHOLDER_ID,
|
|
85
|
+
sectionId: HEADER_ICONS_SECTION_ID,
|
|
86
|
+
isDesigner,
|
|
87
|
+
blockInfo: {
|
|
88
|
+
id: blockId,
|
|
89
|
+
type: blockType,
|
|
90
|
+
label: blockLabel
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (!isDesigner) {
|
|
95
|
+
return children;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Clone child and inject onClick and selection styles
|
|
99
|
+
return (
|
|
100
|
+
<span
|
|
101
|
+
data-block-id={blockId}
|
|
102
|
+
onClick={handleClick}
|
|
103
|
+
className={clsx(
|
|
104
|
+
'relative cursor-pointer',
|
|
105
|
+
isSelected && 'ring-2 ring-blue-500 ring-offset-1 rounded'
|
|
106
|
+
)}
|
|
107
|
+
style={{ display: 'inline-flex', alignItems: 'center' }}
|
|
108
|
+
>
|
|
109
|
+
{children}
|
|
110
|
+
</span>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
17
113
|
|
|
18
114
|
interface MenuItem {
|
|
115
|
+
id: 'search' | 'profile' | 'cart';
|
|
19
116
|
label: string;
|
|
20
117
|
url?: string;
|
|
21
118
|
action?: () => void;
|
|
@@ -24,10 +121,49 @@ interface MenuItem {
|
|
|
24
121
|
badge?: ReactNode;
|
|
25
122
|
miniBasket?: ReactNode;
|
|
26
123
|
dataTestId?: string;
|
|
124
|
+
blockId: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
interface ActionMenuContentProps {
|
|
128
|
+
initialIconSettings?: HeaderIconSettings;
|
|
27
129
|
}
|
|
28
130
|
|
|
29
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Inner ActionMenu component that uses the designer context
|
|
133
|
+
*/
|
|
134
|
+
function ActionMenuContent({ initialIconSettings }: ActionMenuContentProps) {
|
|
135
|
+
const router = useRouter();
|
|
30
136
|
const dispatch = useAppDispatch();
|
|
137
|
+
const { status } = useSession();
|
|
138
|
+
const iconSettings = useHeaderIconSettings(initialIconSettings);
|
|
139
|
+
const { isDesigner, selectedBlockId, getBlockStyles, getBlockProperties } =
|
|
140
|
+
useHeaderIconsDesigner();
|
|
141
|
+
const { layout } = useHeaderLayout();
|
|
142
|
+
const { sectionProperties } = useHeaderMiniBasket();
|
|
143
|
+
|
|
144
|
+
// Extract visibility value from section properties (handles responsive format)
|
|
145
|
+
const extractVisibility = useCallback((visibilityProp: unknown): string => {
|
|
146
|
+
if (!visibilityProp) return 'show';
|
|
147
|
+
if (typeof visibilityProp === 'string') return visibilityProp;
|
|
148
|
+
if (typeof visibilityProp === 'object' && visibilityProp !== null) {
|
|
149
|
+
const obj = visibilityProp as Record<string, string>;
|
|
150
|
+
return obj.desktop || obj.mobile || Object.values(obj)[0] || 'show';
|
|
151
|
+
}
|
|
152
|
+
return 'show';
|
|
153
|
+
}, []);
|
|
154
|
+
|
|
155
|
+
// Check if mini basket should be shown based on theme editor settings
|
|
156
|
+
const shouldShowMiniBasket = useMemo(() => {
|
|
157
|
+
const miniBasketVisibility = extractVisibility(
|
|
158
|
+
sectionProperties?.visibility
|
|
159
|
+
);
|
|
160
|
+
const shouldShow = miniBasketVisibility === 'show';
|
|
161
|
+
|
|
162
|
+
return shouldShow;
|
|
163
|
+
}, [sectionProperties, extractVisibility]);
|
|
164
|
+
|
|
165
|
+
// Hide search icon when inline search is visible (two-row layout)
|
|
166
|
+
const hideSearchIcon = layout === 'two-row';
|
|
31
167
|
|
|
32
168
|
const { data: miniBasket } = useGetMiniBasketQuery();
|
|
33
169
|
const totalQuantity = useMemo(
|
|
@@ -46,75 +182,171 @@ export default function ActionMenu() {
|
|
|
46
182
|
|
|
47
183
|
const MenuItems: MenuItem[] = [
|
|
48
184
|
{
|
|
185
|
+
id: 'search',
|
|
186
|
+
blockId: HEADER_ICON_BLOCKS.SEARCH.id,
|
|
49
187
|
label: 'Search',
|
|
50
188
|
action: () => {
|
|
51
|
-
dispatch(openSearch());
|
|
189
|
+
if (!isDesigner) dispatch(openSearch());
|
|
52
190
|
},
|
|
53
191
|
icon: 'search',
|
|
54
|
-
className: 'sm:hidden',
|
|
55
192
|
dataTestId: 'header-search'
|
|
56
193
|
},
|
|
57
194
|
{
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
195
|
+
id: 'profile',
|
|
196
|
+
blockId: HEADER_ICON_BLOCKS.PROFILE.id,
|
|
197
|
+
label: 'Profile',
|
|
198
|
+
url: `${status === 'authenticated' ? ROUTES.ACCOUNT : ROUTES.AUTH}`,
|
|
199
|
+
icon: 'person',
|
|
200
|
+
dataTestId: 'header-profile'
|
|
62
201
|
},
|
|
63
202
|
{
|
|
203
|
+
id: 'cart',
|
|
204
|
+
blockId: HEADER_ICON_BLOCKS.CART.id,
|
|
64
205
|
label: 'Basket',
|
|
65
|
-
action() {
|
|
66
|
-
|
|
206
|
+
action: () => {
|
|
207
|
+
if (shouldShowMiniBasket) {
|
|
208
|
+
dispatch(toggleMiniBasket());
|
|
209
|
+
} else if (!isDesigner) {
|
|
210
|
+
// Only navigate to basket page when not in designer mode
|
|
211
|
+
router.push(ROUTES.BASKET);
|
|
212
|
+
}
|
|
67
213
|
},
|
|
68
214
|
icon: 'cart',
|
|
69
215
|
dataTestId: 'header-basket',
|
|
216
|
+
className: 'pl-2.5',
|
|
70
217
|
badge: (
|
|
71
218
|
<Badge
|
|
72
219
|
className={clsx(
|
|
73
|
-
'w-4'
|
|
74
|
-
totalQuantity === 0
|
|
75
|
-
? 'bg-primary text-gray-500'
|
|
76
|
-
: 'bg-secondary-500 text-white'
|
|
220
|
+
'w-4 bg-primary text-white -bottom-1 -right-1 top-auto'
|
|
77
221
|
)}
|
|
78
222
|
>
|
|
79
223
|
<span data-testid="header-basket-count">{totalQuantity}</span>
|
|
80
224
|
</Badge>
|
|
81
225
|
),
|
|
82
|
-
miniBasket: <MiniBasket />
|
|
226
|
+
miniBasket: shouldShowMiniBasket ? <MiniBasket /> : null
|
|
83
227
|
}
|
|
84
228
|
];
|
|
85
229
|
|
|
230
|
+
const renderIconContent = (
|
|
231
|
+
menu: MenuItem,
|
|
232
|
+
settings: { size: number; color: string }
|
|
233
|
+
) => {
|
|
234
|
+
// Get custom icon from block properties
|
|
235
|
+
const blockProps = getBlockProperties(menu.blockId);
|
|
236
|
+
const customIcon = blockProps?.icon as
|
|
237
|
+
| string
|
|
238
|
+
| { desktop?: string }
|
|
239
|
+
| undefined;
|
|
240
|
+
|
|
241
|
+
// Handle responsive object format
|
|
242
|
+
const iconValue =
|
|
243
|
+
typeof customIcon === 'object' && customIcon !== null
|
|
244
|
+
? customIcon.desktop
|
|
245
|
+
: customIcon;
|
|
246
|
+
|
|
247
|
+
// Check if we have a custom SVG icon
|
|
248
|
+
const hasCustomSvgIcon =
|
|
249
|
+
iconValue && typeof iconValue === 'string' && iconValue.includes('<svg');
|
|
250
|
+
|
|
251
|
+
return (
|
|
252
|
+
<>
|
|
253
|
+
{hasCustomSvgIcon ? (
|
|
254
|
+
<div
|
|
255
|
+
className="flex items-center justify-center"
|
|
256
|
+
style={{
|
|
257
|
+
width: settings.size,
|
|
258
|
+
height: settings.size,
|
|
259
|
+
color:
|
|
260
|
+
settings.color !== 'currentColor' ? settings.color : undefined
|
|
261
|
+
}}
|
|
262
|
+
dangerouslySetInnerHTML={{ __html: iconValue }}
|
|
263
|
+
/>
|
|
264
|
+
) : (
|
|
265
|
+
<Icon
|
|
266
|
+
name={menu.icon}
|
|
267
|
+
size={settings.size}
|
|
268
|
+
style={{
|
|
269
|
+
color:
|
|
270
|
+
settings.color !== 'currentColor' ? settings.color : undefined
|
|
271
|
+
}}
|
|
272
|
+
/>
|
|
273
|
+
)}
|
|
274
|
+
{menu.badge}
|
|
275
|
+
</>
|
|
276
|
+
);
|
|
277
|
+
};
|
|
278
|
+
|
|
86
279
|
return (
|
|
87
|
-
<ul className="flex items-center
|
|
88
|
-
{MenuItems.map((menu, index) =>
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
|
|
280
|
+
<ul className="flex items-center">
|
|
281
|
+
{MenuItems.map((menu, index) => {
|
|
282
|
+
// Hide search icon when inline search input is visible
|
|
283
|
+
if (menu.id === 'search' && hideSearchIcon) {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const settings = iconSettings[menu.id];
|
|
288
|
+
|
|
289
|
+
const iconElement = menu.action ? (
|
|
290
|
+
<button onClick={menu.action} data-testid={menu.dataTestId}>
|
|
291
|
+
{renderIconContent(menu, settings)}
|
|
292
|
+
</button>
|
|
293
|
+
) : (
|
|
294
|
+
<Link
|
|
295
|
+
href={isDesigner ? '#' : menu.url ?? '#'}
|
|
296
|
+
passHref={true}
|
|
297
|
+
onClick={(event) => {
|
|
298
|
+
if (isDesigner) {
|
|
299
|
+
event.preventDefault();
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
}}
|
|
303
|
+
data-testid={menu.dataTestId}
|
|
304
|
+
>
|
|
305
|
+
{renderIconContent(menu, settings)}
|
|
306
|
+
</Link>
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
// Get block styles for margins
|
|
310
|
+
const blockStyles = getBlockStyles(menu.blockId);
|
|
311
|
+
const iconContainerStyles = convertBlockStylesToCSS(blockStyles);
|
|
312
|
+
|
|
313
|
+
return (
|
|
314
|
+
<li
|
|
315
|
+
key={index}
|
|
316
|
+
className={clsx('flex items-center relative', menu.className)}
|
|
317
|
+
style={iconContainerStyles}
|
|
318
|
+
ref={menu.miniBasket ? miniBasketRef : null}
|
|
319
|
+
>
|
|
320
|
+
<SelectableIcon
|
|
321
|
+
blockId={menu.blockId}
|
|
322
|
+
blockType="icon-button"
|
|
323
|
+
blockLabel={menu.label}
|
|
324
|
+
isDesigner={isDesigner}
|
|
325
|
+
isSelected={selectedBlockId === menu.blockId}
|
|
110
326
|
>
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
))}
|
|
327
|
+
{iconElement}
|
|
328
|
+
</SelectableIcon>
|
|
329
|
+
{menu.miniBasket}
|
|
330
|
+
</li>
|
|
331
|
+
);
|
|
332
|
+
})}
|
|
118
333
|
</ul>
|
|
119
334
|
);
|
|
120
335
|
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* ActionMenu with HeaderIcons and HeaderMiniBasket Provider wrappers
|
|
339
|
+
*/
|
|
340
|
+
interface ActionMenuProps {
|
|
341
|
+
initialIconSettings?: HeaderIconSettings;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export default function ActionMenu({ initialIconSettings }: ActionMenuProps) {
|
|
345
|
+
return (
|
|
346
|
+
<HeaderIconsProvider>
|
|
347
|
+
<HeaderMiniBasketProvider>
|
|
348
|
+
<ActionMenuContent initialIconSettings={initialIconSettings} />
|
|
349
|
+
</HeaderMiniBasketProvider>
|
|
350
|
+
</HeaderIconsProvider>
|
|
351
|
+
);
|
|
352
|
+
}
|
|
@@ -6,27 +6,12 @@ import ActionMenu from './action-menu';
|
|
|
6
6
|
import HeaderBandText from '@theme/widgets/header-band-text';
|
|
7
7
|
import { LanguageSelect } from '@theme/components';
|
|
8
8
|
import { CurrencySelect } from 'components/currency-select';
|
|
9
|
+
import { HeaderIconSettings } from './server-settings-parser';
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
<div className="hidden justify-start sm:inline-flex sm:gap-x-5 sm:header-grid-area-band-l">
|
|
14
|
-
<LanguageSelect className="bg-transparent w-11 px-0 text-sm" />
|
|
15
|
-
<CurrencySelect className="bg-transparent w-12 px-0 text-sm" />
|
|
16
|
-
</div>
|
|
17
|
-
|
|
18
|
-
<div className="header-grid-area-nav bg-gray-100 h-auto p-3 sm:header-grid-area-band-m sm:h-9 sm:py-0">
|
|
19
|
-
<div className="text-center overflow-ellipsis line-clamp-2 h-full flex items-center justify-center">
|
|
20
|
-
<HeaderBandText />
|
|
21
|
-
</div>
|
|
22
|
-
</div>
|
|
11
|
+
interface HeaderBandProps {
|
|
12
|
+
initialIconSettings?: HeaderIconSettings;
|
|
13
|
+
}
|
|
23
14
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
<UserMenu isMobile={false} />
|
|
27
|
-
<ActionMenu />
|
|
28
|
-
</div>
|
|
29
|
-
</div>
|
|
30
|
-
</div>
|
|
31
|
-
);
|
|
15
|
+
export default function HeaderBand({ initialIconSettings }: HeaderBandProps) {
|
|
16
|
+
return <ActionMenu initialIconSettings={initialIconSettings} />;
|
|
32
17
|
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Header Designer Context
|
|
5
|
+
*
|
|
6
|
+
* Provides a lightweight native-widget bridge so Theme Editor can
|
|
7
|
+
* select header blocks without rewriting the header as a widget.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
createContext,
|
|
12
|
+
PropsWithChildren,
|
|
13
|
+
useCallback,
|
|
14
|
+
useContext,
|
|
15
|
+
useEffect,
|
|
16
|
+
useRef,
|
|
17
|
+
useState
|
|
18
|
+
} from 'react';
|
|
19
|
+
import { useExternalDesigner } from '@akinon/next/components/theme-editor/hooks/use-external-designer';
|
|
20
|
+
|
|
21
|
+
type BlockDefinition = {
|
|
22
|
+
id: string;
|
|
23
|
+
type: string;
|
|
24
|
+
label: string;
|
|
25
|
+
properties?: Record<string, unknown>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type ThemeBlock = {
|
|
29
|
+
id: string;
|
|
30
|
+
type: string;
|
|
31
|
+
label: string;
|
|
32
|
+
styles?: Record<string, unknown>;
|
|
33
|
+
properties?: Record<string, unknown>;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type ThemeSection = {
|
|
37
|
+
id: string;
|
|
38
|
+
blocks?: ThemeBlock[];
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
type ThemePlaceholder = {
|
|
42
|
+
slug: string;
|
|
43
|
+
sections?: ThemeSection[];
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
interface HeaderDesignerContextValue {
|
|
47
|
+
isDesigner: boolean;
|
|
48
|
+
selectedSectionId: string | null;
|
|
49
|
+
selectedBlockId: string | null;
|
|
50
|
+
getBlockStyles: (blockId: string) => Record<string, unknown> | undefined;
|
|
51
|
+
getBlockProperties: (blockId: string) => Record<string, unknown> | undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const HEADER_PLACEHOLDER_ID = 'header';
|
|
55
|
+
export const HEADER_SECTION_ID = 'header-structure';
|
|
56
|
+
|
|
57
|
+
// Block definitions used by WithDesignerFeatures wrappers
|
|
58
|
+
const headerBlocks = {
|
|
59
|
+
CONTAINER: {
|
|
60
|
+
id: 'header-container',
|
|
61
|
+
type: 'group',
|
|
62
|
+
label: 'Header Container'
|
|
63
|
+
},
|
|
64
|
+
LOGO: {
|
|
65
|
+
id: 'header-logo-image',
|
|
66
|
+
type: 'image',
|
|
67
|
+
label: 'Logo'
|
|
68
|
+
},
|
|
69
|
+
NAVBAR: {
|
|
70
|
+
id: 'header-navbar',
|
|
71
|
+
type: 'group',
|
|
72
|
+
label: 'Navigation Bar'
|
|
73
|
+
},
|
|
74
|
+
NAV_ITEM: {
|
|
75
|
+
id: 'header-nav-menu-item',
|
|
76
|
+
type: 'text',
|
|
77
|
+
label: 'Navigation Item'
|
|
78
|
+
},
|
|
79
|
+
SEARCH: {
|
|
80
|
+
id: 'header-icon-search',
|
|
81
|
+
type: 'icon-button',
|
|
82
|
+
label: 'Search Icon',
|
|
83
|
+
properties: {
|
|
84
|
+
icon: 'search',
|
|
85
|
+
iconSize: '20'
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
USER_MENU: {
|
|
89
|
+
id: 'header-icon-profile',
|
|
90
|
+
type: 'icon-button',
|
|
91
|
+
label: 'Profile Icon',
|
|
92
|
+
properties: {
|
|
93
|
+
icon: 'person',
|
|
94
|
+
iconSize: '20'
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
MINI_BASKET: {
|
|
98
|
+
id: 'header-icon-cart',
|
|
99
|
+
type: 'icon-button',
|
|
100
|
+
label: 'Cart Icon',
|
|
101
|
+
properties: {
|
|
102
|
+
icon: 'cart',
|
|
103
|
+
iconSize: '20'
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
MOBILE_MENU_BUTTON: {
|
|
107
|
+
id: 'header-mobile-menu-button',
|
|
108
|
+
type: 'icon-button',
|
|
109
|
+
label: 'Mobile Menu Button',
|
|
110
|
+
properties: {
|
|
111
|
+
icon: 'hamburger',
|
|
112
|
+
iconSize: '18'
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
} satisfies Record<string, BlockDefinition>;
|
|
116
|
+
|
|
117
|
+
export const HEADER_BLOCKS = headerBlocks;
|
|
118
|
+
|
|
119
|
+
type HeaderBlockDefinition =
|
|
120
|
+
(typeof HEADER_BLOCKS)[keyof typeof HEADER_BLOCKS];
|
|
121
|
+
type HeaderBlockId = HeaderBlockDefinition['id'];
|
|
122
|
+
|
|
123
|
+
const blockLookup = new Map<HeaderBlockId, BlockDefinition>(
|
|
124
|
+
Object.values(HEADER_BLOCKS).map((block) => [block.id, block as BlockDefinition])
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const HeaderDesignerContext = createContext<HeaderDesignerContextValue>({
|
|
128
|
+
isDesigner: false,
|
|
129
|
+
selectedSectionId: null,
|
|
130
|
+
selectedBlockId: null,
|
|
131
|
+
getBlockStyles: () => undefined,
|
|
132
|
+
getBlockProperties: () => undefined
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
export function HeaderDesignerProvider({
|
|
136
|
+
children
|
|
137
|
+
}: PropsWithChildren) {
|
|
138
|
+
const designerState = useExternalDesigner({
|
|
139
|
+
placeholderId: HEADER_PLACEHOLDER_ID
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const [themeBlocks, setThemeBlocks] = useState<Map<string, ThemeBlock>>(
|
|
143
|
+
new Map()
|
|
144
|
+
);
|
|
145
|
+
const themeBlocksRef = useRef<Map<string, ThemeBlock>>(new Map());
|
|
146
|
+
|
|
147
|
+
const registerNativeWidget = useCallback(() => {
|
|
148
|
+
if (typeof window === 'undefined') return;
|
|
149
|
+
const isInIframe = window.self !== window.top;
|
|
150
|
+
if (!isInIframe || !window.parent) return;
|
|
151
|
+
|
|
152
|
+
const sectionBlocks = Object.values(HEADER_BLOCKS).map((blockDef) => {
|
|
153
|
+
const block = blockDef as BlockDefinition;
|
|
154
|
+
const storedBlock = themeBlocksRef.current.get(block.id);
|
|
155
|
+
return {
|
|
156
|
+
id: block.id,
|
|
157
|
+
type: block.type,
|
|
158
|
+
label: block.label,
|
|
159
|
+
properties: storedBlock?.properties ?? block.properties ?? {},
|
|
160
|
+
styles: storedBlock?.styles ?? {}
|
|
161
|
+
};
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
window.parent.postMessage(
|
|
165
|
+
{
|
|
166
|
+
type: 'REGISTER_NATIVE_WIDGETS',
|
|
167
|
+
data: {
|
|
168
|
+
widgets: [
|
|
169
|
+
{
|
|
170
|
+
placeholderId: HEADER_PLACEHOLDER_ID,
|
|
171
|
+
section: {
|
|
172
|
+
id: HEADER_SECTION_ID,
|
|
173
|
+
type: 'native',
|
|
174
|
+
label: 'Header Layout',
|
|
175
|
+
blocks: sectionBlocks
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
'*'
|
|
182
|
+
);
|
|
183
|
+
}, []);
|
|
184
|
+
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
if (typeof window === 'undefined') return;
|
|
187
|
+
registerNativeWidget();
|
|
188
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
189
|
+
}, []); // Only register once
|
|
190
|
+
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
if (typeof window === 'undefined') return;
|
|
193
|
+
|
|
194
|
+
const handleMessage = (event: MessageEvent) => {
|
|
195
|
+
const { type, data } = event.data || {};
|
|
196
|
+
|
|
197
|
+
if (
|
|
198
|
+
(type === 'UPDATE_THEME' || type === 'LOAD_THEME') &&
|
|
199
|
+
data?.theme?.placeholders
|
|
200
|
+
) {
|
|
201
|
+
const placeholder = (data.theme.placeholders as ThemePlaceholder[]).find(
|
|
202
|
+
(p) => p.slug === HEADER_PLACEHOLDER_ID
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const headerSection = placeholder?.sections?.find(
|
|
206
|
+
(section) => section.id === HEADER_SECTION_ID
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
if (headerSection?.blocks) {
|
|
210
|
+
const incoming = new Map<string, ThemeBlock>();
|
|
211
|
+
headerSection.blocks.forEach((block) => {
|
|
212
|
+
incoming.set(block.id, block);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const merged = new Map(themeBlocksRef.current);
|
|
216
|
+
incoming.forEach((block, id) => {
|
|
217
|
+
merged.set(id, block);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
themeBlocksRef.current = merged;
|
|
221
|
+
setThemeBlocks(merged);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
window.addEventListener('message', handleMessage);
|
|
227
|
+
return () => window.removeEventListener('message', handleMessage);
|
|
228
|
+
}, []);
|
|
229
|
+
|
|
230
|
+
const getBlockStyles = useCallback(
|
|
231
|
+
(blockId: string) => {
|
|
232
|
+
return themeBlocks.get(blockId)?.styles;
|
|
233
|
+
},
|
|
234
|
+
[themeBlocks]
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
const getBlockProperties = useCallback(
|
|
238
|
+
(blockId: string) => {
|
|
239
|
+
const block = themeBlocks.get(blockId);
|
|
240
|
+
if (block?.properties) {
|
|
241
|
+
return block.properties;
|
|
242
|
+
}
|
|
243
|
+
return blockLookup.get(blockId as HeaderBlockId)?.properties;
|
|
244
|
+
},
|
|
245
|
+
[themeBlocks]
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
<HeaderDesignerContext.Provider
|
|
250
|
+
value={{
|
|
251
|
+
...designerState,
|
|
252
|
+
getBlockStyles,
|
|
253
|
+
getBlockProperties
|
|
254
|
+
}}
|
|
255
|
+
>
|
|
256
|
+
{children}
|
|
257
|
+
</HeaderDesignerContext.Provider>
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export const useHeaderDesigner = () => useContext(HeaderDesignerContext);
|