@akinon/projectzero 2.0.0-beta.2 → 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 +147 -6
- package/app-template/.env.example +8 -0
- package/app-template/.github/instructions/account.instructions.md +749 -0
- package/app-template/.github/instructions/checkout.instructions.md +678 -0
- package/app-template/.github/instructions/default.instructions.md +279 -0
- package/app-template/.github/instructions/edge-cases.instructions.md +73 -0
- package/app-template/.github/instructions/routing.instructions.md +603 -0
- package/app-template/.github/instructions/settings.instructions.md +338 -0
- package/app-template/.gitignore +5 -0
- package/app-template/AGENTS.md +7 -0
- package/app-template/CHANGELOG.md +1645 -61
- package/app-template/Procfile +1 -1
- package/app-template/README.md +6 -0
- package/app-template/akinon.json +1 -4
- package/app-template/build.sh +10 -0
- package/app-template/config/prebuild-tests.json +5 -0
- package/app-template/docs/advanced-usage.md +111 -0
- package/app-template/docs/plugins.md +60 -25
- package/app-template/docs/sentry-usage.md +35 -0
- package/app-template/jest.config.ts +2 -2
- package/app-template/next-env.d.ts +1 -0
- package/app-template/next.config.mjs +8 -5
- package/app-template/package.json +60 -50
- package/app-template/postcss.config.mjs +5 -0
- 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 +13 -4
- 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 +17 -4
- package/app-template/public/locales/en/common.json +71 -3
- package/app-template/public/locales/en/forgot_password.json +6 -7
- package/app-template/public/locales/en/product.json +84 -4
- package/app-template/public/locales/tr/account.json +13 -4
- 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 +48 -36
- package/app-template/public/locales/tr/common.json +70 -2
- package/app-template/public/locales/tr/forgot_password.json +12 -13
- package/app-template/public/locales/tr/product.json +82 -0
- package/app-template/public/logo.svg +3 -27
- package/app-template/public/mastercard.svg +14 -0
- package/app-template/public/masterpass-javascript-sdk-web.min.js +1 -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/__tests__/middleware-matcher.test.ts +135 -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/{[commerce]/[locale]/[currency] → [pz]}/[...prettyurl]/page.tsx +9 -9
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/layout.tsx +2 -2
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/[id]/cancellation/page.tsx +105 -13
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/[id]/page.tsx +136 -52
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/profile/page.tsx +2 -2
- package/app-template/src/app/[pz]/category/[pk]/page.tsx +27 -0
- package/app-template/src/app/[pz]/error.tsx +17 -0
- package/app-template/src/app/[pz]/flat-page/[pk]/page.tsx +23 -0
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/forms/[pk]/generate/page.tsx +1 -2
- package/app-template/src/app/[pz]/group-product/[pk]/page.tsx +93 -0
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/landing-page/[pk]/page.tsx +2 -4
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/layout.tsx +3 -10
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/list/page.tsx +2 -4
- package/app-template/src/app/{[commerce]/[locale]/[currency]/pz-not-found/page.tsx → [pz]/not-found.tsx} +5 -7
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/orders/checkout/page.tsx +7 -4
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/orders/completed/[token]/page.tsx +6 -4
- package/app-template/src/app/[pz]/product/[pk]/page.tsx +102 -0
- package/app-template/src/app/[pz]/special-page/[pk]/page.tsx +35 -0
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/email-set-primary/[[...id]]/page.tsx +3 -4
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/registration/account-confirm-email/[[...id]]/page.tsx +3 -3
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/reset/[[...id]]/page.tsx +41 -5
- package/app-template/src/app/[pz]/xml-sitemap/[node]/route.ts +73 -0
- package/app-template/src/app/api/auth/[...nextauth]/route.ts +3 -0
- package/app-template/src/app/api/barcode-search/route.ts +1 -0
- package/app-template/src/app/api/form/[...id]/route.ts +1 -7
- package/app-template/src/app/api/image-proxy/route.ts +1 -0
- package/app-template/src/app/api/product-categories/route.ts +1 -0
- package/app-template/src/app/api/similar-product-list/route.ts +1 -0
- package/app-template/src/app/api/similar-products/route.ts +1 -0
- package/app-template/src/app/api/theme-settings/route.ts +12 -0
- package/app-template/src/app/api/virtual-try-on/limited-categories/route.ts +1 -0
- package/app-template/src/app/api/virtual-try-on/route.ts +1 -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 +37 -34
- 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/auth.ts +3 -0
- package/app-template/src/components/__tests__/link.test.tsx +2 -0
- package/app-template/src/components/accordion.tsx +48 -23
- package/app-template/src/components/action-tooltip.tsx +160 -0
- package/app-template/src/components/button.tsx +50 -35
- package/app-template/src/components/carousel-core.tsx +4 -11
- package/app-template/src/components/checkbox.tsx +2 -1
- package/app-template/src/components/currency-select.tsx +150 -4
- package/app-template/src/components/file-input.tsx +64 -2
- package/app-template/src/components/generate-form-fields.tsx +49 -10
- package/app-template/src/components/icon.tsx +5 -6
- package/app-template/src/components/index.ts +4 -1
- package/app-template/src/components/input.tsx +8 -2
- package/app-template/src/components/language-select.tsx +88 -2
- package/app-template/src/components/modal.tsx +34 -16
- package/app-template/src/components/pagination.tsx +133 -20
- package/app-template/src/components/price.tsx +1 -1
- package/app-template/src/components/pwa-tags.tsx +1 -0
- 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 +86 -54
- package/app-template/src/components/tabs.tsx +2 -2
- package/app-template/src/components/types/index.ts +55 -2
- 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 +9 -10
- package/app-template/src/hooks/use-product-cart.ts +80 -0
- package/app-template/src/hooks/use-stock-alert.ts +74 -0
- package/app-template/src/hooks/use-theme-settings.ts +42 -0
- package/app-template/src/lib/fonts.ts +149 -0
- package/app-template/src/middleware.ts +1 -0
- package/app-template/src/plugins.js +13 -2
- package/app-template/src/redux/middlewares/category.ts +6 -5
- package/app-template/src/redux/reducers/category.ts +1 -1
- package/app-template/src/redux/store.ts +21 -1
- package/app-template/src/routes/index.ts +2 -1
- package/app-template/src/settings.js +5 -3
- package/app-template/src/types/hookform-resolvers-yup.d.ts +28 -0
- package/app-template/src/types/index.ts +74 -3
- package/app-template/src/types/next-auth.d.ts +2 -2
- package/app-template/src/types/widget.ts +169 -0
- package/app-template/src/utils/convert-facet-search-params.ts +1 -1
- package/app-template/src/utils/formatDate.ts +48 -0
- package/app-template/src/utils/styles.ts +71 -0
- package/app-template/src/utils/variant-validation.ts +41 -0
- package/app-template/src/views/account/address-form.tsx +8 -4
- package/app-template/src/views/account/contact-form.tsx +148 -136
- package/app-template/src/views/account/content-header.tsx +2 -2
- package/app-template/src/views/account/faq/faq-tabs.tsx +8 -2
- package/app-template/src/views/account/favorite-item.tsx +1 -1
- package/app-template/src/views/account/order.tsx +10 -8
- package/app-template/src/views/account/orders/order-cancellation-item.tsx +4 -3
- package/app-template/src/views/account/orders/order-detail-header.tsx +1 -1
- package/app-template/src/views/anonymous-tracking/order-detail/index.tsx +44 -37
- package/app-template/src/views/basket/basket-item.tsx +697 -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 +497 -60
- 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-active-filters.tsx +1 -1
- package/app-template/src/views/category/category-banner.tsx +4 -23
- package/app-template/src/views/category/category-header.tsx +289 -60
- package/app-template/src/views/category/category-info.tsx +177 -27
- package/app-template/src/views/category/filters/filter-item.tsx +138 -42
- package/app-template/src/views/category/filters/index.tsx +209 -49
- 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 +93 -6
- package/app-template/src/views/checkout/steps/payment/options/funds-transfer.tsx +25 -5
- package/app-template/src/views/checkout/steps/payment/options/loyalty.tsx +21 -2
- package/app-template/src/views/checkout/steps/payment/options/masterpass-rest.tsx +15 -0
- package/app-template/src/views/checkout/steps/payment/options/redirection.tsx +27 -5
- package/app-template/src/views/checkout/steps/payment/options/saved-card.tsx +18 -0
- package/app-template/src/views/checkout/steps/payment/options/store-credit.tsx +464 -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 +104 -29
- package/app-template/src/views/checkout/steps/shipping/addresses.tsx +129 -46
- package/app-template/src/views/checkout/steps/shipping/shipping-options.tsx +232 -27
- package/app-template/src/views/checkout/summary.tsx +310 -26
- package/app-template/src/views/find-in-store/index.tsx +2 -2
- 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 +284 -49
- 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 +110 -48
- package/app-template/src/views/header/inline-search.tsx +262 -0
- package/app-template/src/views/header/mini-basket.tsx +832 -46
- 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 +85 -24
- 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/installment-options/index.tsx +1 -1
- package/app-template/src/views/login/index.tsx +89 -56
- package/app-template/src/views/otp-login/index.tsx +23 -20
- 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 +21 -6
- package/app-template/src/views/product/misc-buttons.tsx +339 -25
- package/app-template/src/views/product/price-wrapper.tsx +3 -24
- package/app-template/src/views/product/product-actions.tsx +294 -0
- package/app-template/src/views/product/product-info-section.tsx +140 -0
- package/app-template/src/views/product/product-info.tsx +130 -254
- package/app-template/src/views/product/product-share.tsx +61 -0
- package/app-template/src/views/product/product-variants.tsx +26 -0
- 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 +135 -76
- 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 +54 -44
- 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-menu.tsx +6 -2
- 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/home-stories-eng.tsx +42 -34
- 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 +19 -7
- package/app-template/tsconfig.json +29 -11
- package/codemods/migrate-segments/index.js +591 -0
- package/codemods/sentry-9/index.js +30 -0
- package/codemods/sentry-9/remove-sentry-configs.js +14 -0
- package/codemods/sentry-9/remove-sentry-dependency.js +25 -0
- package/codemods/sentry-9/replace-error-page.js +32 -0
- package/codemods/update-tailwind-config/index.js +30 -0
- package/codemods/update-tailwind-config/transform.js +102 -0
- package/commands/codemod.ts +17 -0
- package/commands/index.ts +3 -1
- package/commands/plugins.ts +115 -46
- package/dist/codemods/sentry-9/templates/error.js +14 -0
- package/dist/commands/codemod.js +15 -0
- package/dist/commands/index.js +3 -1
- package/dist/commands/plugins.js +85 -34
- package/package.json +3 -2
- package/app-template/postcss.config.js +0 -6
- package/app-template/sentry.client.config.ts +0 -16
- package/app-template/sentry.edge.config.ts +0 -3
- package/app-template/sentry.properties +0 -4
- package/app-template/sentry.server.config.ts +0 -3
- package/app-template/src/app/[commerce]/[locale]/[currency]/category/[pk]/page.tsx +0 -22
- package/app-template/src/app/[commerce]/[locale]/[currency]/error.tsx +0 -20
- package/app-template/src/app/[commerce]/[locale]/[currency]/flat-page/[pk]/page.tsx +0 -20
- package/app-template/src/app/[commerce]/[locale]/[currency]/group-product/[pk]/page.tsx +0 -74
- package/app-template/src/app/[commerce]/[locale]/[currency]/product/[pk]/loading.tsx +0 -67
- package/app-template/src/app/[commerce]/[locale]/[currency]/product/[pk]/page.tsx +0 -84
- package/app-template/src/app/[commerce]/[locale]/[currency]/special-page/[pk]/page.tsx +0 -27
- package/app-template/src/app/[commerce]/[locale]/[currency]/xml-sitemap/[node]/route.ts +0 -25
- package/app-template/src/pages/api/auth/[...nextauth].ts +0 -3
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/address/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/change-email/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/change-password/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/contact/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/coupons/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/email-verification/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/faq/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/favourite-products/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/my-quotations/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/[id]/layout.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/address/stores/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/anonymous-tracking/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/auth/oauth-login/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/auth/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/basket/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/basket-b2b/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/category/[pk]/loading.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/client-root.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/contact-us/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/flat-page/[pk]/loading.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/group-product/[pk]/loading.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/landing-page/[pk]/loading.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/list/loading.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/orders/completed/[token]/layout.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/special-page/[pk]/loading.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/template.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/password/reset/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/xml-sitemap/route.ts +0 -0
|
@@ -0,0 +1,1026 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Header Content Component
|
|
5
|
+
*
|
|
6
|
+
* Client component that renders the header content based on the selected layout.
|
|
7
|
+
* Uses the HeaderLayoutContext to determine which layout to display.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
ReactNode,
|
|
12
|
+
useCallback,
|
|
13
|
+
useMemo,
|
|
14
|
+
useState,
|
|
15
|
+
useEffect,
|
|
16
|
+
useRef
|
|
17
|
+
} from 'react';
|
|
18
|
+
import clsx from 'clsx';
|
|
19
|
+
import {
|
|
20
|
+
useHeaderLayout,
|
|
21
|
+
HeaderLayoutType,
|
|
22
|
+
MenuPositionType,
|
|
23
|
+
SearchPositionType,
|
|
24
|
+
HEADER_LAYOUT_BLOCKS,
|
|
25
|
+
HEADER_LAYOUT_PLACEHOLDER_ID,
|
|
26
|
+
HEADER_LAYOUT_SECTION_ID
|
|
27
|
+
} from './header-layout-registrar';
|
|
28
|
+
import HeaderSearchRegistrar, {
|
|
29
|
+
useHeaderSearch,
|
|
30
|
+
InitialSearchSettings
|
|
31
|
+
} from './header-search-registrar';
|
|
32
|
+
import HeaderLanguageRegistrar, {
|
|
33
|
+
useHeaderLanguage
|
|
34
|
+
} from './header-language-registrar';
|
|
35
|
+
import HeaderCurrencyRegistrar, {
|
|
36
|
+
useHeaderCurrency
|
|
37
|
+
} from './header-currency-registrar';
|
|
38
|
+
import HeaderTextSliderRegistrar, {
|
|
39
|
+
useHeaderTextSlider
|
|
40
|
+
} from './header-text-slider-registrar';
|
|
41
|
+
import HeaderAnnouncementRegistrar, {
|
|
42
|
+
useHeaderAnnouncement
|
|
43
|
+
} from './header-announcement-registrar';
|
|
44
|
+
import InlineSearch from './inline-search';
|
|
45
|
+
import { LanguageSelect } from '@theme/components/language-select';
|
|
46
|
+
import { CurrencySelect } from '@theme/components/currency-select';
|
|
47
|
+
import { useDesignerFeatures } from '@akinon/next/components/theme-editor/hooks/use-designer-features';
|
|
48
|
+
import type {
|
|
49
|
+
HeaderLanguageSettings,
|
|
50
|
+
HeaderCurrencySettings,
|
|
51
|
+
HeaderTextSliderSettings,
|
|
52
|
+
HeaderAnnouncementSettings
|
|
53
|
+
} from './server-settings-parser';
|
|
54
|
+
|
|
55
|
+
interface HeaderContentProps {
|
|
56
|
+
logo: ReactNode;
|
|
57
|
+
navbar: ReactNode;
|
|
58
|
+
icons: ReactNode;
|
|
59
|
+
mobileHamburger: ReactNode;
|
|
60
|
+
mobileMenu: ReactNode;
|
|
61
|
+
className?: string;
|
|
62
|
+
menuPosition?: MenuPositionType;
|
|
63
|
+
searchPosition?: SearchPositionType;
|
|
64
|
+
/** Initial search settings from server-side parsing (to avoid flash) */
|
|
65
|
+
initialSearchSettings?: InitialSearchSettings;
|
|
66
|
+
/** Initial language settings from server-side parsing (to avoid flash) */
|
|
67
|
+
initialLanguageSettings?: HeaderLanguageSettings;
|
|
68
|
+
/** Initial currency settings from server-side parsing (to avoid flash) */
|
|
69
|
+
initialCurrencySettings?: HeaderCurrencySettings;
|
|
70
|
+
/** Initial text slider settings from server-side parsing (to avoid flash) */
|
|
71
|
+
initialTextSliderSettings?: HeaderTextSliderSettings;
|
|
72
|
+
/** Initial announcement bar settings from server-side parsing (to avoid flash) */
|
|
73
|
+
initialAnnouncementSettings?: HeaderAnnouncementSettings;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Convert block styles to CSS properties
|
|
78
|
+
*/
|
|
79
|
+
function convertBlockStyles(
|
|
80
|
+
styles: Record<string, unknown> | undefined
|
|
81
|
+
): React.CSSProperties {
|
|
82
|
+
if (!styles) return {};
|
|
83
|
+
|
|
84
|
+
const result: Record<string, unknown> = {};
|
|
85
|
+
|
|
86
|
+
// Map theme editor keys to CSS property names
|
|
87
|
+
const styleMap: Record<string, string> = {
|
|
88
|
+
'background-color': 'backgroundColor',
|
|
89
|
+
'border-color': 'borderColor',
|
|
90
|
+
'border-width': 'borderWidth',
|
|
91
|
+
'border-radius': 'borderRadius',
|
|
92
|
+
'border-style': 'borderStyle',
|
|
93
|
+
'padding-top': 'paddingTop',
|
|
94
|
+
'padding-bottom': 'paddingBottom',
|
|
95
|
+
'padding-left': 'paddingLeft',
|
|
96
|
+
'padding-right': 'paddingRight',
|
|
97
|
+
'margin-top': 'marginTop',
|
|
98
|
+
'margin-bottom': 'marginBottom',
|
|
99
|
+
'margin-left': 'marginLeft',
|
|
100
|
+
'margin-right': 'marginRight',
|
|
101
|
+
'min-width': 'minWidth',
|
|
102
|
+
'max-width': 'maxWidth',
|
|
103
|
+
'font-size': 'fontSize',
|
|
104
|
+
'font-weight': 'fontWeight',
|
|
105
|
+
'font-family': 'fontFamily',
|
|
106
|
+
'line-height': 'lineHeight',
|
|
107
|
+
'text-align': 'textAlign',
|
|
108
|
+
// Theme editor background properties
|
|
109
|
+
bgColor: 'backgroundColor',
|
|
110
|
+
BgOpacity: null // Skip, already applied via background
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// Keys to skip (helper properties that shouldn't be applied directly)
|
|
114
|
+
const skipKeys = new Set(['bgColor', 'BgOpacity']);
|
|
115
|
+
|
|
116
|
+
Object.entries(styles).forEach(([key, value]) => {
|
|
117
|
+
// Skip helper keys
|
|
118
|
+
if (skipKeys.has(key)) return;
|
|
119
|
+
|
|
120
|
+
const cssKey = styleMap[key] || key;
|
|
121
|
+
// Skip if explicitly mapped to null
|
|
122
|
+
if (cssKey === null) return;
|
|
123
|
+
|
|
124
|
+
// Extract desktop value if responsive
|
|
125
|
+
if (value && typeof value === 'object' && 'desktop' in value) {
|
|
126
|
+
result[cssKey] = (value as Record<string, unknown>).desktop;
|
|
127
|
+
} else {
|
|
128
|
+
result[cssKey] = value;
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Auto-add border-style: solid if border-width is set
|
|
133
|
+
if (result.borderWidth && !result.borderStyle) {
|
|
134
|
+
result.borderStyle = 'solid';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return result as React.CSSProperties;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Selectable Row Container
|
|
142
|
+
* Wraps row content with theme editor selection support
|
|
143
|
+
*/
|
|
144
|
+
interface SelectableRowProps {
|
|
145
|
+
blockId: string;
|
|
146
|
+
blockLabel: string;
|
|
147
|
+
children: ReactNode;
|
|
148
|
+
className?: string;
|
|
149
|
+
style?: React.CSSProperties;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function SelectableRow({
|
|
153
|
+
blockId,
|
|
154
|
+
blockLabel,
|
|
155
|
+
children,
|
|
156
|
+
className,
|
|
157
|
+
style
|
|
158
|
+
}: SelectableRowProps) {
|
|
159
|
+
const { isDesigner, selectedBlockId, getBlockStyles } = useHeaderLayout();
|
|
160
|
+
|
|
161
|
+
const { handleClick } = useDesignerFeatures({
|
|
162
|
+
blockId,
|
|
163
|
+
placeholderId: HEADER_LAYOUT_PLACEHOLDER_ID,
|
|
164
|
+
sectionId: HEADER_LAYOUT_SECTION_ID,
|
|
165
|
+
isDesigner,
|
|
166
|
+
blockInfo: {
|
|
167
|
+
id: blockId,
|
|
168
|
+
type: 'container',
|
|
169
|
+
label: blockLabel
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const isSelected = selectedBlockId === blockId;
|
|
174
|
+
const blockStyles = getBlockStyles(blockId);
|
|
175
|
+
const computedStyles = useMemo(
|
|
176
|
+
() => convertBlockStyles(blockStyles),
|
|
177
|
+
[blockStyles]
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
const handleContainerClick = useCallback(
|
|
181
|
+
(e: React.MouseEvent) => {
|
|
182
|
+
if (isDesigner) {
|
|
183
|
+
e.preventDefault();
|
|
184
|
+
e.stopPropagation();
|
|
185
|
+
handleClick(e);
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
[isDesigner, handleClick]
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<div
|
|
193
|
+
data-block-id={blockId}
|
|
194
|
+
onClick={handleContainerClick}
|
|
195
|
+
className={clsx(
|
|
196
|
+
className,
|
|
197
|
+
isDesigner && 'cursor-pointer',
|
|
198
|
+
isSelected && 'ring-2 ring-blue-500 ring-inset'
|
|
199
|
+
)}
|
|
200
|
+
style={{ ...style, ...computedStyles }}
|
|
201
|
+
>
|
|
202
|
+
{children}
|
|
203
|
+
</div>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Search Input Wrapper
|
|
209
|
+
* Wraps the InlineSearch component with theme editor support
|
|
210
|
+
*/
|
|
211
|
+
function SearchInputWrapper() {
|
|
212
|
+
const {
|
|
213
|
+
isSectionVisible,
|
|
214
|
+
properties,
|
|
215
|
+
sectionStyles,
|
|
216
|
+
getBlockStyles,
|
|
217
|
+
getBlockProperties,
|
|
218
|
+
isDesigner,
|
|
219
|
+
selectedBlockId
|
|
220
|
+
} = useHeaderSearch();
|
|
221
|
+
|
|
222
|
+
// Merge width (and potential responsive widths) from properties into styles
|
|
223
|
+
// Must be called before early return to satisfy React hooks rules
|
|
224
|
+
const mergedSectionStyles = useMemo(() => {
|
|
225
|
+
const result = { ...(sectionStyles as Record<string, unknown>) };
|
|
226
|
+
|
|
227
|
+
const resolveValue = (value: unknown) => {
|
|
228
|
+
if (value === null || value === undefined) return undefined;
|
|
229
|
+
if (typeof value === 'string' || typeof value === 'number') return value;
|
|
230
|
+
if (typeof value === 'object') {
|
|
231
|
+
const obj = value as Record<string, string | number>;
|
|
232
|
+
return obj.desktop ?? obj.mobile ?? Object.values(obj)[0];
|
|
233
|
+
}
|
|
234
|
+
return undefined;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const width = resolveValue(properties.width);
|
|
238
|
+
if (width !== undefined) {
|
|
239
|
+
result.width = width;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return result as React.CSSProperties;
|
|
243
|
+
}, [properties.width, sectionStyles]);
|
|
244
|
+
|
|
245
|
+
if (!isSectionVisible) {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const placeholder =
|
|
250
|
+
typeof properties.placeholder === 'object'
|
|
251
|
+
? (properties.placeholder as Record<string, string>).desktop ||
|
|
252
|
+
'Search products...'
|
|
253
|
+
: properties.placeholder || 'Search products...';
|
|
254
|
+
|
|
255
|
+
// Merge block styles and properties for icon
|
|
256
|
+
const iconBlockStyles = getBlockStyles('header-search-icon') || {};
|
|
257
|
+
const iconBlockProps = getBlockProperties('header-search-icon') || {};
|
|
258
|
+
const iconStyles = { ...iconBlockStyles, ...iconBlockProps };
|
|
259
|
+
|
|
260
|
+
// Check if custom width is set to avoid w-full overriding it
|
|
261
|
+
const hasCustomWidth = mergedSectionStyles.width !== undefined;
|
|
262
|
+
|
|
263
|
+
return (
|
|
264
|
+
<div data-section-id="header-search">
|
|
265
|
+
<InlineSearch
|
|
266
|
+
placeholder={placeholder}
|
|
267
|
+
className={hasCustomWidth ? undefined : 'w-full'}
|
|
268
|
+
style={mergedSectionStyles}
|
|
269
|
+
iconStyles={iconStyles}
|
|
270
|
+
blockId="header-search-icon"
|
|
271
|
+
isDesigner={isDesigner}
|
|
272
|
+
isSelected={selectedBlockId === 'header-search-icon'}
|
|
273
|
+
/>
|
|
274
|
+
</div>
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Language Select Wrapper
|
|
280
|
+
* Wraps the LanguageSelect component with theme editor support
|
|
281
|
+
*/
|
|
282
|
+
function LanguageSelectWrapper() {
|
|
283
|
+
const {
|
|
284
|
+
isSectionVisible,
|
|
285
|
+
properties,
|
|
286
|
+
sectionStyles,
|
|
287
|
+
isDesigner,
|
|
288
|
+
isLanguageSectionSelected
|
|
289
|
+
} = useHeaderLanguage();
|
|
290
|
+
|
|
291
|
+
// Convert styles for CSS using shared function
|
|
292
|
+
const computedStyles = useMemo(
|
|
293
|
+
() => convertBlockStyles(sectionStyles),
|
|
294
|
+
[sectionStyles]
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
// Handle click to select section in designer mode
|
|
298
|
+
const handleClick = useCallback(
|
|
299
|
+
(e: React.MouseEvent) => {
|
|
300
|
+
if (isDesigner && window.parent) {
|
|
301
|
+
e.preventDefault();
|
|
302
|
+
e.stopPropagation();
|
|
303
|
+
window.parent.postMessage(
|
|
304
|
+
{
|
|
305
|
+
type: 'SELECT_SECTION',
|
|
306
|
+
data: {
|
|
307
|
+
placeholderId: 'header',
|
|
308
|
+
sectionId: 'header-language'
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
'*'
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
[isDesigner]
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
if (!isSectionVisible) {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const showIcon =
|
|
323
|
+
typeof properties.showIcon === 'object'
|
|
324
|
+
? (properties.showIcon as Record<string, boolean>).desktop ?? true
|
|
325
|
+
: properties.showIcon ?? true;
|
|
326
|
+
|
|
327
|
+
const labelFormat =
|
|
328
|
+
typeof properties.labelFormat === 'object'
|
|
329
|
+
? (properties.labelFormat as Record<string, string>).desktop ?? 'full'
|
|
330
|
+
: properties.labelFormat ?? 'full';
|
|
331
|
+
|
|
332
|
+
// Custom icon from properties - handle responsive format
|
|
333
|
+
const customIcon =
|
|
334
|
+
typeof properties.icon === 'object'
|
|
335
|
+
? (properties.icon as Record<string, string>).desktop ?? ''
|
|
336
|
+
: (properties.icon as string) ?? '';
|
|
337
|
+
|
|
338
|
+
return (
|
|
339
|
+
<div
|
|
340
|
+
data-section-id="header-language"
|
|
341
|
+
style={computedStyles}
|
|
342
|
+
onClick={handleClick}
|
|
343
|
+
className={clsx(
|
|
344
|
+
isDesigner && 'cursor-pointer',
|
|
345
|
+
isLanguageSectionSelected && 'ring-2 ring-blue-500 ring-inset'
|
|
346
|
+
)}
|
|
347
|
+
>
|
|
348
|
+
<div style={{ pointerEvents: isDesigner ? 'none' : 'auto' }}>
|
|
349
|
+
<LanguageSelect
|
|
350
|
+
showIcon={showIcon}
|
|
351
|
+
labelFormat={labelFormat as 'full' | 'short' | 'code'}
|
|
352
|
+
customIcon={customIcon}
|
|
353
|
+
/>
|
|
354
|
+
</div>
|
|
355
|
+
</div>
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Currency Select Wrapper
|
|
361
|
+
* Wraps the CurrencySelect component with theme editor support
|
|
362
|
+
*/
|
|
363
|
+
function CurrencySelectWrapper() {
|
|
364
|
+
const {
|
|
365
|
+
isSectionVisible,
|
|
366
|
+
properties,
|
|
367
|
+
sectionStyles,
|
|
368
|
+
isDesigner,
|
|
369
|
+
isCurrencySectionSelected
|
|
370
|
+
} = useHeaderCurrency();
|
|
371
|
+
|
|
372
|
+
// Convert styles for CSS using shared function
|
|
373
|
+
const computedStyles = useMemo(
|
|
374
|
+
() => convertBlockStyles(sectionStyles),
|
|
375
|
+
[sectionStyles]
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
// Handle click to select section in designer mode
|
|
379
|
+
const handleClick = useCallback(
|
|
380
|
+
(e: React.MouseEvent) => {
|
|
381
|
+
if (isDesigner && window.parent) {
|
|
382
|
+
e.preventDefault();
|
|
383
|
+
e.stopPropagation();
|
|
384
|
+
window.parent.postMessage(
|
|
385
|
+
{
|
|
386
|
+
type: 'SELECT_SECTION',
|
|
387
|
+
data: {
|
|
388
|
+
placeholderId: 'header',
|
|
389
|
+
sectionId: 'header-currency'
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
'*'
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
[isDesigner]
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
if (!isSectionVisible) {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const showIcon =
|
|
404
|
+
typeof properties.showIcon === 'object'
|
|
405
|
+
? (properties.showIcon as Record<string, boolean>).desktop ?? true
|
|
406
|
+
: properties.showIcon ?? true;
|
|
407
|
+
|
|
408
|
+
const labelFormat =
|
|
409
|
+
typeof properties.labelFormat === 'object'
|
|
410
|
+
? (properties.labelFormat as Record<string, string>).desktop ?? 'full'
|
|
411
|
+
: properties.labelFormat ?? 'full';
|
|
412
|
+
|
|
413
|
+
// Custom icon from properties - handle responsive format
|
|
414
|
+
const customIcon =
|
|
415
|
+
typeof properties.icon === 'object'
|
|
416
|
+
? (properties.icon as Record<string, string>).desktop ?? ''
|
|
417
|
+
: (properties.icon as string) ?? '';
|
|
418
|
+
|
|
419
|
+
return (
|
|
420
|
+
<div
|
|
421
|
+
data-section-id="header-currency"
|
|
422
|
+
style={computedStyles}
|
|
423
|
+
onClick={handleClick}
|
|
424
|
+
className={clsx(
|
|
425
|
+
isDesigner && 'cursor-pointer',
|
|
426
|
+
isCurrencySectionSelected && 'ring-2 ring-blue-500 ring-inset'
|
|
427
|
+
)}
|
|
428
|
+
>
|
|
429
|
+
<div style={{ pointerEvents: isDesigner ? 'none' : 'auto' }}>
|
|
430
|
+
<CurrencySelect
|
|
431
|
+
showIcon={showIcon}
|
|
432
|
+
labelFormat={labelFormat as 'full' | 'symbol' | 'code'}
|
|
433
|
+
customIcon={customIcon}
|
|
434
|
+
/>
|
|
435
|
+
</div>
|
|
436
|
+
</div>
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Text Slider Wrapper
|
|
442
|
+
* Wraps the Text Slider component with theme editor support
|
|
443
|
+
* Positioned absolutely centered in the utility bar
|
|
444
|
+
*/
|
|
445
|
+
const DEFAULT_ITEMS: Array<{ text: string; link?: string }> = [
|
|
446
|
+
{ text: 'Welcome to our store!' }
|
|
447
|
+
];
|
|
448
|
+
|
|
449
|
+
function TextSliderWrapper() {
|
|
450
|
+
const {
|
|
451
|
+
isSectionVisible,
|
|
452
|
+
properties,
|
|
453
|
+
sectionStyles,
|
|
454
|
+
isDesigner,
|
|
455
|
+
isTextSliderSectionSelected
|
|
456
|
+
} = useHeaderTextSlider();
|
|
457
|
+
|
|
458
|
+
const [currentIndex, setCurrentIndex] = useState(0);
|
|
459
|
+
const [prevIndex, setPrevIndex] = useState<number | null>(null);
|
|
460
|
+
const [slideDirection, setSlideDirection] = useState<'left' | 'right'>(
|
|
461
|
+
'left'
|
|
462
|
+
);
|
|
463
|
+
const [isAnimating, setIsAnimating] = useState(false);
|
|
464
|
+
|
|
465
|
+
// Convert styles for CSS using shared function
|
|
466
|
+
const computedStyles = useMemo(
|
|
467
|
+
() => convertBlockStyles(sectionStyles),
|
|
468
|
+
[sectionStyles]
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
// Handle click to select section in designer mode
|
|
472
|
+
const handleClick = useCallback(
|
|
473
|
+
(e: React.MouseEvent) => {
|
|
474
|
+
if (isDesigner && window.parent) {
|
|
475
|
+
e.preventDefault();
|
|
476
|
+
e.stopPropagation();
|
|
477
|
+
window.parent.postMessage(
|
|
478
|
+
{
|
|
479
|
+
type: 'SELECT_SECTION',
|
|
480
|
+
data: {
|
|
481
|
+
placeholderId: 'header',
|
|
482
|
+
sectionId: 'header-text-slider'
|
|
483
|
+
}
|
|
484
|
+
},
|
|
485
|
+
'*'
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
},
|
|
489
|
+
[isDesigner]
|
|
490
|
+
);
|
|
491
|
+
|
|
492
|
+
// Stable reference to items using JSON comparison
|
|
493
|
+
const itemsJsonRef = useRef<string>('');
|
|
494
|
+
const itemsRef = useRef(DEFAULT_ITEMS);
|
|
495
|
+
|
|
496
|
+
// Only update items ref when the actual data changes
|
|
497
|
+
const propItemsJson = JSON.stringify(properties.items);
|
|
498
|
+
if (propItemsJson !== itemsJsonRef.current) {
|
|
499
|
+
itemsJsonRef.current = propItemsJson;
|
|
500
|
+
const propItems = properties.items;
|
|
501
|
+
if (Array.isArray(propItems) && propItems.length > 0) {
|
|
502
|
+
itemsRef.current = propItems;
|
|
503
|
+
} else {
|
|
504
|
+
itemsRef.current = DEFAULT_ITEMS;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const items = itemsRef.current;
|
|
509
|
+
const autoPlay = properties.autoPlay !== false;
|
|
510
|
+
const autoPlayInterval = properties.autoPlayInterval || 3000;
|
|
511
|
+
const showArrows = properties.showArrows !== false;
|
|
512
|
+
|
|
513
|
+
// Refs for values needed in effects/intervals
|
|
514
|
+
const autoPlayRef = useRef(autoPlay);
|
|
515
|
+
const autoPlayIntervalRef = useRef(autoPlayInterval);
|
|
516
|
+
const itemsLengthRef = useRef(items.length);
|
|
517
|
+
const isAnimatingRef = useRef(isAnimating);
|
|
518
|
+
const currentIndexRef = useRef(currentIndex);
|
|
519
|
+
|
|
520
|
+
// Update refs on each render
|
|
521
|
+
autoPlayRef.current = autoPlay;
|
|
522
|
+
autoPlayIntervalRef.current = autoPlayInterval;
|
|
523
|
+
itemsLengthRef.current = items.length;
|
|
524
|
+
isAnimatingRef.current = isAnimating;
|
|
525
|
+
currentIndexRef.current = currentIndex;
|
|
526
|
+
|
|
527
|
+
// Reset current index if out of bounds
|
|
528
|
+
useEffect(() => {
|
|
529
|
+
if (currentIndex >= itemsLengthRef.current) {
|
|
530
|
+
setCurrentIndex(0);
|
|
531
|
+
}
|
|
532
|
+
}, [currentIndex]);
|
|
533
|
+
|
|
534
|
+
// Handle slide transition
|
|
535
|
+
const performSlide = useCallback(
|
|
536
|
+
(direction: 'left' | 'right', newIndex: number) => {
|
|
537
|
+
setIsAnimating(true);
|
|
538
|
+
setSlideDirection(direction);
|
|
539
|
+
setPrevIndex(currentIndexRef.current);
|
|
540
|
+
setCurrentIndex(newIndex);
|
|
541
|
+
setTimeout(() => {
|
|
542
|
+
setPrevIndex(null);
|
|
543
|
+
setIsAnimating(false);
|
|
544
|
+
}, 400);
|
|
545
|
+
},
|
|
546
|
+
[]
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
// Auto-play functionality
|
|
550
|
+
useEffect(() => {
|
|
551
|
+
if (!autoPlayRef.current || itemsLengthRef.current <= 1) return;
|
|
552
|
+
|
|
553
|
+
const interval = setInterval(() => {
|
|
554
|
+
if (
|
|
555
|
+
!autoPlayRef.current ||
|
|
556
|
+
itemsLengthRef.current <= 1 ||
|
|
557
|
+
isAnimatingRef.current
|
|
558
|
+
)
|
|
559
|
+
return;
|
|
560
|
+
|
|
561
|
+
const newIndex = (currentIndexRef.current + 1) % itemsLengthRef.current;
|
|
562
|
+
performSlide('left', newIndex);
|
|
563
|
+
}, autoPlayIntervalRef.current);
|
|
564
|
+
|
|
565
|
+
return () => clearInterval(interval);
|
|
566
|
+
}, [performSlide]);
|
|
567
|
+
|
|
568
|
+
// Navigation handlers
|
|
569
|
+
const goToPrev = useCallback(
|
|
570
|
+
(e: React.MouseEvent) => {
|
|
571
|
+
e.stopPropagation();
|
|
572
|
+
if (isAnimating) return;
|
|
573
|
+
const newIndex = (currentIndex - 1 + items.length) % items.length;
|
|
574
|
+
performSlide('right', newIndex);
|
|
575
|
+
},
|
|
576
|
+
[currentIndex, items.length, isAnimating, performSlide]
|
|
577
|
+
);
|
|
578
|
+
|
|
579
|
+
const goToNext = useCallback(
|
|
580
|
+
(e: React.MouseEvent) => {
|
|
581
|
+
e.stopPropagation();
|
|
582
|
+
if (isAnimating) return;
|
|
583
|
+
const newIndex = (currentIndex + 1) % items.length;
|
|
584
|
+
performSlide('left', newIndex);
|
|
585
|
+
},
|
|
586
|
+
[currentIndex, items.length, isAnimating, performSlide]
|
|
587
|
+
);
|
|
588
|
+
|
|
589
|
+
if (!isSectionVisible) {
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const currentItem = items[currentIndex];
|
|
594
|
+
|
|
595
|
+
return (
|
|
596
|
+
<div
|
|
597
|
+
data-section-id="header-text-slider"
|
|
598
|
+
onClick={handleClick}
|
|
599
|
+
className={clsx(
|
|
600
|
+
'absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2',
|
|
601
|
+
'flex items-center gap-2',
|
|
602
|
+
isDesigner && 'cursor-pointer',
|
|
603
|
+
isTextSliderSectionSelected && 'ring-2 ring-blue-500 ring-inset'
|
|
604
|
+
)}
|
|
605
|
+
style={{
|
|
606
|
+
...computedStyles,
|
|
607
|
+
pointerEvents: isDesigner ? 'auto' : undefined
|
|
608
|
+
}}
|
|
609
|
+
>
|
|
610
|
+
{/* Previous Arrow */}
|
|
611
|
+
{showArrows && items.length > 1 && (
|
|
612
|
+
<button
|
|
613
|
+
onClick={goToPrev}
|
|
614
|
+
className="flex items-center justify-center w-6 h-6 text-current hover:opacity-70 transition-opacity"
|
|
615
|
+
style={{ pointerEvents: isDesigner ? 'none' : 'auto' }}
|
|
616
|
+
>
|
|
617
|
+
<svg
|
|
618
|
+
width="16"
|
|
619
|
+
height="16"
|
|
620
|
+
viewBox="0 0 24 24"
|
|
621
|
+
fill="none"
|
|
622
|
+
stroke="currentColor"
|
|
623
|
+
strokeWidth="2"
|
|
624
|
+
>
|
|
625
|
+
<polyline points="15,18 9,12 15,6" />
|
|
626
|
+
</svg>
|
|
627
|
+
</button>
|
|
628
|
+
)}
|
|
629
|
+
|
|
630
|
+
{/* Text Content with Slide Animation */}
|
|
631
|
+
{/* Container uses grid to size based on longest item */}
|
|
632
|
+
<div className="relative overflow-hidden">
|
|
633
|
+
{/* Hidden items to establish container width - all invisible */}
|
|
634
|
+
<div className="grid invisible" aria-hidden="true">
|
|
635
|
+
{items.map((item, index) => (
|
|
636
|
+
<span
|
|
637
|
+
key={index}
|
|
638
|
+
className="col-start-1 row-start-1 whitespace-nowrap"
|
|
639
|
+
>
|
|
640
|
+
{item.text}
|
|
641
|
+
</span>
|
|
642
|
+
))}
|
|
643
|
+
</div>
|
|
644
|
+
{/* Exiting item (slides out) */}
|
|
645
|
+
{prevIndex !== null && (
|
|
646
|
+
<span
|
|
647
|
+
key={`prev-${prevIndex}`}
|
|
648
|
+
className={clsx(
|
|
649
|
+
'absolute inset-0 whitespace-nowrap text-center',
|
|
650
|
+
slideDirection === 'left'
|
|
651
|
+
? 'animate-slide-out'
|
|
652
|
+
: 'animate-slide-out [animation-direction:reverse]'
|
|
653
|
+
)}
|
|
654
|
+
>
|
|
655
|
+
{items[prevIndex]?.link ? (
|
|
656
|
+
<a href={items[prevIndex].link} className="hover:underline">
|
|
657
|
+
{items[prevIndex].text}
|
|
658
|
+
</a>
|
|
659
|
+
) : (
|
|
660
|
+
items[prevIndex]?.text
|
|
661
|
+
)}
|
|
662
|
+
</span>
|
|
663
|
+
)}
|
|
664
|
+
{/* Entering item (slides in) */}
|
|
665
|
+
<span
|
|
666
|
+
key={`current-${currentIndex}-${
|
|
667
|
+
prevIndex !== null ? 'animating' : 'static'
|
|
668
|
+
}`}
|
|
669
|
+
className={clsx(
|
|
670
|
+
'absolute inset-0 whitespace-nowrap text-center',
|
|
671
|
+
prevIndex !== null &&
|
|
672
|
+
(slideDirection === 'left'
|
|
673
|
+
? 'animate-slide-in'
|
|
674
|
+
: 'animate-slide-in [animation-direction:reverse]')
|
|
675
|
+
)}
|
|
676
|
+
>
|
|
677
|
+
{currentItem?.link ? (
|
|
678
|
+
<a
|
|
679
|
+
href={currentItem.link}
|
|
680
|
+
className="hover:underline"
|
|
681
|
+
style={{ pointerEvents: isDesigner ? 'none' : 'auto' }}
|
|
682
|
+
>
|
|
683
|
+
{currentItem.text}
|
|
684
|
+
</a>
|
|
685
|
+
) : (
|
|
686
|
+
currentItem?.text
|
|
687
|
+
)}
|
|
688
|
+
</span>
|
|
689
|
+
</div>
|
|
690
|
+
|
|
691
|
+
{/* Next Arrow */}
|
|
692
|
+
{showArrows && items.length > 1 && (
|
|
693
|
+
<button
|
|
694
|
+
onClick={goToNext}
|
|
695
|
+
className="flex items-center justify-center w-6 h-6 text-current hover:opacity-70 transition-opacity"
|
|
696
|
+
style={{ pointerEvents: isDesigner ? 'none' : 'auto' }}
|
|
697
|
+
>
|
|
698
|
+
<svg
|
|
699
|
+
width="16"
|
|
700
|
+
height="16"
|
|
701
|
+
viewBox="0 0 24 24"
|
|
702
|
+
fill="none"
|
|
703
|
+
stroke="currentColor"
|
|
704
|
+
strokeWidth="2"
|
|
705
|
+
>
|
|
706
|
+
<polyline points="9,6 15,12 9,18" />
|
|
707
|
+
</svg>
|
|
708
|
+
</button>
|
|
709
|
+
)}
|
|
710
|
+
</div>
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function AnnouncementBarWrapper() {
|
|
715
|
+
const {
|
|
716
|
+
isSectionVisible,
|
|
717
|
+
properties,
|
|
718
|
+
sectionStyles,
|
|
719
|
+
isDesigner,
|
|
720
|
+
isAnnouncementSectionSelected
|
|
721
|
+
} = useHeaderAnnouncement();
|
|
722
|
+
|
|
723
|
+
const computedStyles = useMemo(
|
|
724
|
+
() => convertBlockStyles(sectionStyles),
|
|
725
|
+
[sectionStyles]
|
|
726
|
+
);
|
|
727
|
+
|
|
728
|
+
const resolvePropValue = (value: unknown, fallback = ''): string => {
|
|
729
|
+
if (typeof value === 'string') return value;
|
|
730
|
+
if (typeof value === 'object' && value !== null) {
|
|
731
|
+
const responsiveValue = value as Record<string, string>;
|
|
732
|
+
return (
|
|
733
|
+
responsiveValue.desktop ||
|
|
734
|
+
responsiveValue.mobile ||
|
|
735
|
+
Object.values(responsiveValue)[0] ||
|
|
736
|
+
fallback
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
return fallback;
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
const text = resolvePropValue(properties.text, '');
|
|
743
|
+
const link = resolvePropValue(properties.link, '');
|
|
744
|
+
const target = resolvePropValue(properties.target, '_self');
|
|
745
|
+
|
|
746
|
+
const handleClick = useCallback(
|
|
747
|
+
(e: React.MouseEvent) => {
|
|
748
|
+
if (isDesigner && window.parent) {
|
|
749
|
+
e.preventDefault();
|
|
750
|
+
e.stopPropagation();
|
|
751
|
+
window.parent.postMessage(
|
|
752
|
+
{
|
|
753
|
+
type: 'SELECT_SECTION',
|
|
754
|
+
data: {
|
|
755
|
+
placeholderId: 'header',
|
|
756
|
+
sectionId: 'header-announcement-bar'
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
'*'
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
},
|
|
763
|
+
[isDesigner]
|
|
764
|
+
);
|
|
765
|
+
|
|
766
|
+
if (!isSectionVisible || !text) {
|
|
767
|
+
return null;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
return (
|
|
771
|
+
<div
|
|
772
|
+
data-section-id="header-announcement-bar"
|
|
773
|
+
onClick={handleClick}
|
|
774
|
+
className={clsx(
|
|
775
|
+
'w-full',
|
|
776
|
+
isDesigner && 'cursor-pointer',
|
|
777
|
+
isAnnouncementSectionSelected && 'ring-2 ring-blue-500 ring-inset'
|
|
778
|
+
)}
|
|
779
|
+
style={computedStyles}
|
|
780
|
+
>
|
|
781
|
+
<div className="container px-2.5 lg:px-5 xl:px-2.5 text-center">
|
|
782
|
+
{link ? (
|
|
783
|
+
<a
|
|
784
|
+
href={link}
|
|
785
|
+
target={target}
|
|
786
|
+
rel={target === '_blank' ? 'noopener noreferrer' : undefined}
|
|
787
|
+
className="hover:underline"
|
|
788
|
+
style={{ pointerEvents: isDesigner ? 'none' : 'auto' }}
|
|
789
|
+
>
|
|
790
|
+
{text}
|
|
791
|
+
</a>
|
|
792
|
+
) : (
|
|
793
|
+
<span>{text}</span>
|
|
794
|
+
)}
|
|
795
|
+
</div>
|
|
796
|
+
</div>
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Utility Row Component
|
|
802
|
+
* Shows Language and Currency selects when added via Theme Editor
|
|
803
|
+
* Only renders when at least one of them is visible
|
|
804
|
+
*/
|
|
805
|
+
function UtilityRow() {
|
|
806
|
+
const { isSectionVisible: isLanguageVisible } = useHeaderLanguage();
|
|
807
|
+
const { isSectionVisible: isCurrencyVisible } = useHeaderCurrency();
|
|
808
|
+
const { isSectionVisible: isTextSliderVisible } = useHeaderTextSlider();
|
|
809
|
+
const { getBlockStyles, utilityPosition } = useHeaderLayout();
|
|
810
|
+
|
|
811
|
+
// Get block styles for utility row
|
|
812
|
+
const blockStyles = getBlockStyles(HEADER_LAYOUT_BLOCKS.UTILITY_ROW.id);
|
|
813
|
+
const computedStyles = useMemo(
|
|
814
|
+
() => convertBlockStyles(blockStyles),
|
|
815
|
+
[blockStyles]
|
|
816
|
+
);
|
|
817
|
+
|
|
818
|
+
// Don't render if nothing is visible
|
|
819
|
+
if (!isLanguageVisible && !isCurrencyVisible && !isTextSliderVisible) {
|
|
820
|
+
return null;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Determine justify class based on utility position
|
|
824
|
+
const justifyClass =
|
|
825
|
+
utilityPosition === 'left' ? 'justify-start' : 'justify-end';
|
|
826
|
+
|
|
827
|
+
// Check if custom padding is set to avoid Tailwind class overriding it
|
|
828
|
+
const hasCustomPaddingY =
|
|
829
|
+
computedStyles.paddingTop !== undefined ||
|
|
830
|
+
computedStyles.paddingBottom !== undefined;
|
|
831
|
+
|
|
832
|
+
return (
|
|
833
|
+
<SelectableRow
|
|
834
|
+
blockId={HEADER_LAYOUT_BLOCKS.UTILITY_ROW.id}
|
|
835
|
+
blockLabel={HEADER_LAYOUT_BLOCKS.UTILITY_ROW.label}
|
|
836
|
+
className="w-full bg-gray-100"
|
|
837
|
+
style={computedStyles}
|
|
838
|
+
>
|
|
839
|
+
<div
|
|
840
|
+
className={clsx(
|
|
841
|
+
'container relative flex items-center gap-4 px-2.5 lg:px-5 xl:px-2.5',
|
|
842
|
+
justifyClass
|
|
843
|
+
)}
|
|
844
|
+
style={
|
|
845
|
+
hasCustomPaddingY
|
|
846
|
+
? {
|
|
847
|
+
paddingTop: computedStyles.paddingTop,
|
|
848
|
+
paddingBottom: computedStyles.paddingBottom
|
|
849
|
+
}
|
|
850
|
+
: undefined
|
|
851
|
+
}
|
|
852
|
+
>
|
|
853
|
+
{/* Text Slider - Absolute positioned in center */}
|
|
854
|
+
<TextSliderWrapper />
|
|
855
|
+
|
|
856
|
+
{/* Language and Currency - positioned based on utilityPosition */}
|
|
857
|
+
<LanguageSelectWrapper />
|
|
858
|
+
<CurrencySelectWrapper />
|
|
859
|
+
</div>
|
|
860
|
+
</SelectableRow>
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* Default Layout
|
|
866
|
+
* Single row: [Logo + Navbar] | [Icons]
|
|
867
|
+
*/
|
|
868
|
+
function DefaultLayout({
|
|
869
|
+
logo,
|
|
870
|
+
navbar,
|
|
871
|
+
icons,
|
|
872
|
+
mobileHamburger,
|
|
873
|
+
mobileMenu,
|
|
874
|
+
initialAnnouncementSettings,
|
|
875
|
+
initialLanguageSettings,
|
|
876
|
+
initialCurrencySettings,
|
|
877
|
+
initialTextSliderSettings
|
|
878
|
+
}: HeaderContentProps) {
|
|
879
|
+
return (
|
|
880
|
+
<>
|
|
881
|
+
<HeaderAnnouncementRegistrar initialSettings={initialAnnouncementSettings}>
|
|
882
|
+
<AnnouncementBarWrapper />
|
|
883
|
+
</HeaderAnnouncementRegistrar>
|
|
884
|
+
|
|
885
|
+
{/* Utility Row: Language & Currency Selects & Text Slider */}
|
|
886
|
+
<HeaderTextSliderRegistrar initialSettings={initialTextSliderSettings}>
|
|
887
|
+
<HeaderLanguageRegistrar initialSettings={initialLanguageSettings}>
|
|
888
|
+
<HeaderCurrencyRegistrar initialSettings={initialCurrencySettings}>
|
|
889
|
+
<UtilityRow />
|
|
890
|
+
</HeaderCurrencyRegistrar>
|
|
891
|
+
</HeaderLanguageRegistrar>
|
|
892
|
+
</HeaderTextSliderRegistrar>
|
|
893
|
+
|
|
894
|
+
<SelectableRow
|
|
895
|
+
blockId={HEADER_LAYOUT_BLOCKS.MAIN_ROW.id}
|
|
896
|
+
blockLabel={HEADER_LAYOUT_BLOCKS.MAIN_ROW.label}
|
|
897
|
+
className="w-full"
|
|
898
|
+
>
|
|
899
|
+
<div className="container flex items-center justify-between px-2.5 lg:px-5 xl:px-2.5 py-5 sm:py-0">
|
|
900
|
+
{mobileHamburger}
|
|
901
|
+
<div className="flex items-center sm:gap-16">
|
|
902
|
+
{logo}
|
|
903
|
+
{navbar}
|
|
904
|
+
</div>
|
|
905
|
+
{icons}
|
|
906
|
+
{mobileMenu}
|
|
907
|
+
</div>
|
|
908
|
+
</SelectableRow>
|
|
909
|
+
</>
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* Two Row Layout
|
|
915
|
+
* Row 1: Logo | Search Input | Icons
|
|
916
|
+
* Row 2: Navigation Menu (full width, position configurable)
|
|
917
|
+
*/
|
|
918
|
+
function TwoRowLayout({
|
|
919
|
+
logo,
|
|
920
|
+
navbar,
|
|
921
|
+
icons,
|
|
922
|
+
mobileHamburger,
|
|
923
|
+
mobileMenu,
|
|
924
|
+
menuPosition = 'center',
|
|
925
|
+
searchPosition = 'center',
|
|
926
|
+
initialSearchSettings,
|
|
927
|
+
initialAnnouncementSettings,
|
|
928
|
+
initialLanguageSettings,
|
|
929
|
+
initialCurrencySettings,
|
|
930
|
+
initialTextSliderSettings
|
|
931
|
+
}: HeaderContentProps) {
|
|
932
|
+
// Map menu position to justify class
|
|
933
|
+
const menuPositionClass = {
|
|
934
|
+
left: 'justify-start',
|
|
935
|
+
center: 'justify-center',
|
|
936
|
+
right: 'justify-end'
|
|
937
|
+
}[menuPosition];
|
|
938
|
+
|
|
939
|
+
const searchPositionClass = {
|
|
940
|
+
left: 'justify-start',
|
|
941
|
+
center: 'justify-center',
|
|
942
|
+
right: 'justify-end'
|
|
943
|
+
}[searchPosition];
|
|
944
|
+
|
|
945
|
+
return (
|
|
946
|
+
<>
|
|
947
|
+
<HeaderAnnouncementRegistrar initialSettings={initialAnnouncementSettings}>
|
|
948
|
+
<AnnouncementBarWrapper />
|
|
949
|
+
</HeaderAnnouncementRegistrar>
|
|
950
|
+
|
|
951
|
+
{/* Utility Row: Language & Currency Selects & Text Slider */}
|
|
952
|
+
<HeaderTextSliderRegistrar initialSettings={initialTextSliderSettings}>
|
|
953
|
+
<HeaderLanguageRegistrar initialSettings={initialLanguageSettings}>
|
|
954
|
+
<HeaderCurrencyRegistrar initialSettings={initialCurrencySettings}>
|
|
955
|
+
<UtilityRow />
|
|
956
|
+
</HeaderCurrencyRegistrar>
|
|
957
|
+
</HeaderLanguageRegistrar>
|
|
958
|
+
</HeaderTextSliderRegistrar>
|
|
959
|
+
|
|
960
|
+
{/* Row 1: Logo + Search + Icons */}
|
|
961
|
+
<SelectableRow
|
|
962
|
+
blockId={HEADER_LAYOUT_BLOCKS.TOP_ROW.id}
|
|
963
|
+
blockLabel={HEADER_LAYOUT_BLOCKS.TOP_ROW.label}
|
|
964
|
+
className="w-full"
|
|
965
|
+
>
|
|
966
|
+
<div className="container flex items-center justify-between px-2.5 sm:px-0">
|
|
967
|
+
{mobileHamburger}
|
|
968
|
+
<div className="flex items-center">{logo}</div>
|
|
969
|
+
<div className={`hidden sm:flex flex-1 ${searchPositionClass} mx-8`}>
|
|
970
|
+
<HeaderSearchRegistrar
|
|
971
|
+
autoRegister
|
|
972
|
+
initialSettings={initialSearchSettings}
|
|
973
|
+
>
|
|
974
|
+
<SearchInputWrapper />
|
|
975
|
+
</HeaderSearchRegistrar>
|
|
976
|
+
</div>
|
|
977
|
+
{icons}
|
|
978
|
+
</div>
|
|
979
|
+
</SelectableRow>
|
|
980
|
+
|
|
981
|
+
{/* Row 2: Navigation Menu */}
|
|
982
|
+
<SelectableRow
|
|
983
|
+
blockId={HEADER_LAYOUT_BLOCKS.BOTTOM_ROW.id}
|
|
984
|
+
blockLabel={HEADER_LAYOUT_BLOCKS.BOTTOM_ROW.label}
|
|
985
|
+
className="hidden sm:block w-full border-t border-gray-200"
|
|
986
|
+
>
|
|
987
|
+
<div className={`container flex ${menuPositionClass} px-2.5 sm:px-0`}>
|
|
988
|
+
{navbar}
|
|
989
|
+
</div>
|
|
990
|
+
</SelectableRow>
|
|
991
|
+
|
|
992
|
+
{mobileMenu}
|
|
993
|
+
</>
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
/**
|
|
998
|
+
* Layout Map
|
|
999
|
+
*/
|
|
1000
|
+
const LAYOUTS: Record<HeaderLayoutType, React.FC<HeaderContentProps>> = {
|
|
1001
|
+
default: DefaultLayout,
|
|
1002
|
+
'two-row': TwoRowLayout
|
|
1003
|
+
};
|
|
1004
|
+
|
|
1005
|
+
/**
|
|
1006
|
+
* HeaderContent
|
|
1007
|
+
*
|
|
1008
|
+
* Reads the current layout from context and renders appropriate layout.
|
|
1009
|
+
*/
|
|
1010
|
+
export default function HeaderContent(props: HeaderContentProps) {
|
|
1011
|
+
const { layout, menuPosition, searchPosition } = useHeaderLayout();
|
|
1012
|
+
const LayoutComponent = LAYOUTS[layout] || LAYOUTS.default;
|
|
1013
|
+
|
|
1014
|
+
return (
|
|
1015
|
+
<LayoutComponent
|
|
1016
|
+
{...props}
|
|
1017
|
+
menuPosition={menuPosition}
|
|
1018
|
+
searchPosition={searchPosition}
|
|
1019
|
+
initialSearchSettings={props.initialSearchSettings}
|
|
1020
|
+
initialAnnouncementSettings={props.initialAnnouncementSettings}
|
|
1021
|
+
initialLanguageSettings={props.initialLanguageSettings}
|
|
1022
|
+
initialCurrencySettings={props.initialCurrencySettings}
|
|
1023
|
+
initialTextSliderSettings={props.initialTextSliderSettings}
|
|
1024
|
+
/>
|
|
1025
|
+
);
|
|
1026
|
+
}
|