@akinon/projectzero 2.0.0-beta.19 → 2.0.0-beta.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -7
- package/app-template/CHANGELOG.md +251 -204
- package/app-template/akinon.json +1 -1
- package/app-template/package.json +28 -28
- package/app-template/public/amex.svg +12 -0
- package/app-template/public/apple-pay.svg +16 -0
- package/app-template/public/assets/images/product-placeholder-1.jpg +0 -0
- package/app-template/public/assets/images/product-placeholder-2.jpg +0 -0
- package/app-template/public/assets/images/product-placeholder-3.jpg +0 -0
- package/app-template/public/assets/images/product-placeholder-4.jpg +0 -0
- package/app-template/public/google-pay.svg +16 -0
- package/app-template/public/locales/en/account.json +6 -3
- package/app-template/public/locales/en/auth.json +6 -7
- package/app-template/public/locales/en/basket.json +6 -6
- package/app-template/public/locales/en/blog.json +7 -0
- package/app-template/public/locales/en/category.json +3 -1
- package/app-template/public/locales/en/checkout.json +5 -4
- package/app-template/public/locales/en/common.json +11 -2
- package/app-template/public/locales/en/forgot_password.json +6 -7
- package/app-template/public/locales/en/product.json +4 -3
- package/app-template/public/locales/tr/account.json +6 -3
- package/app-template/public/locales/tr/auth.json +16 -17
- package/app-template/public/locales/tr/basket.json +4 -4
- package/app-template/public/locales/tr/blog.json +7 -0
- package/app-template/public/locales/tr/category.json +3 -1
- package/app-template/public/locales/tr/checkout.json +39 -38
- package/app-template/public/locales/tr/common.json +10 -1
- package/app-template/public/locales/tr/forgot_password.json +12 -13
- package/app-template/public/locales/tr/product.json +1 -0
- package/app-template/public/logo.svg +3 -27
- package/app-template/public/mastercard.svg +14 -0
- package/app-template/public/promotion-banner.jpg +0 -0
- package/app-template/public/shop-pay.svg +12 -0
- package/app-template/public/visa.svg +12 -0
- package/app-template/src/app/[commerce]/[locale]/[currency]/blog/[slug]/page.tsx +118 -0
- package/app-template/src/app/[commerce]/[locale]/[currency]/pages/[slug]/page.tsx +15 -0
- package/app-template/src/app/api/theme-settings/route.ts +12 -0
- package/app-template/src/assets/fonts/pz-icon.css +211 -49
- package/app-template/src/assets/fonts/pz-icon.eot +0 -0
- package/app-template/src/assets/fonts/pz-icon.html +486 -0
- package/app-template/src/assets/fonts/pz-icon.scss +373 -49
- package/app-template/src/assets/fonts/pz-icon.svg +215 -53
- package/app-template/src/assets/fonts/pz-icon.ttf +0 -0
- package/app-template/src/assets/fonts/pz-icon.woff +0 -0
- package/app-template/src/assets/fonts/pz-icon.woff2 +0 -0
- package/app-template/src/assets/globals.scss +4 -0
- package/app-template/src/assets/icons/arrow-right.svg +3 -0
- package/app-template/src/assets/icons/cart.svg +4 -12
- package/app-template/src/assets/icons/check.svg +2 -18
- package/app-template/src/assets/icons/chevron-down.svg +2 -7
- package/app-template/src/assets/icons/delete.svg +3 -0
- package/app-template/src/assets/icons/facebook.svg +2 -8
- package/app-template/src/assets/icons/fav-off.svg +5 -0
- package/app-template/src/assets/icons/fav-on.svg +5 -0
- package/app-template/src/assets/icons/filter-and-sort.svg +3 -0
- package/app-template/src/assets/icons/heart.svg +3 -0
- package/app-template/src/assets/icons/instagram.svg +2 -13
- package/app-template/src/assets/icons/materials.svg +3 -0
- package/app-template/src/assets/icons/person.svg +4 -0
- package/app-template/src/assets/icons/pinterest.svg +5 -11
- package/app-template/src/assets/icons/ruler.svg +3 -0
- package/app-template/src/assets/icons/search.svg +8 -11
- package/app-template/src/assets/icons/share.svg +2 -9
- package/app-template/src/assets/icons/snapchat.svg +3 -0
- package/app-template/src/assets/icons/tiktok.svg +3 -0
- package/app-template/src/assets/icons/tumblr.svg +6 -0
- package/app-template/src/assets/icons/twitter.svg +2 -10
- package/app-template/src/assets/icons/vimeo.svg +3 -0
- package/app-template/src/assets/icons/youtube.svg +3 -0
- package/app-template/src/assets/icons/zoom.svg +8 -0
- package/app-template/src/components/accordion.tsx +33 -11
- package/app-template/src/components/action-tooltip.tsx +160 -0
- package/app-template/src/components/currency-select.tsx +149 -4
- package/app-template/src/components/icon.tsx +5 -6
- package/app-template/src/components/index.ts +4 -1
- package/app-template/src/components/language-select.tsx +88 -2
- package/app-template/src/components/pagination.tsx +132 -20
- package/app-template/src/components/quantity-input.tsx +63 -0
- package/app-template/src/components/quantity-selector.tsx +203 -0
- package/app-template/src/components/route-handler.tsx +50 -0
- package/app-template/src/components/select.tsx +89 -69
- package/app-template/src/components/types/index.ts +26 -0
- package/app-template/src/components/widget-content.tsx +323 -0
- package/app-template/src/data/server/theme.ts +70 -0
- package/app-template/src/hooks/use-fav-button.tsx +5 -2
- package/app-template/src/hooks/use-product-cart.ts +11 -8
- package/app-template/src/hooks/use-theme-settings.ts +42 -0
- package/app-template/src/lib/fonts.ts +149 -0
- package/app-template/src/settings.js +2 -2
- package/app-template/src/types/hookform-resolvers-yup.d.ts +28 -0
- package/app-template/src/types/widget.ts +169 -0
- package/app-template/src/utils/formatDate.ts +48 -0
- package/app-template/src/utils/styles.ts +71 -0
- package/app-template/src/views/account/contact-form.tsx +147 -130
- package/app-template/src/views/basket/basket-item.tsx +691 -107
- package/app-template/src/views/basket/basket-summary-context.tsx +560 -0
- package/app-template/src/views/basket/designer-context.tsx +617 -0
- package/app-template/src/views/basket/index.ts +2 -0
- package/app-template/src/views/basket/summary.tsx +496 -75
- package/app-template/src/views/breadcrumb/breadcrumb-client.tsx +190 -0
- package/app-template/src/views/breadcrumb/breadcrumb-registrar.tsx +286 -0
- package/app-template/src/views/breadcrumb/constants.ts +15 -0
- package/app-template/src/views/breadcrumb/index.tsx +127 -0
- package/app-template/src/views/breadcrumb.tsx +13 -38
- package/app-template/src/views/category/category-banner.tsx +4 -23
- package/app-template/src/views/category/category-header.tsx +289 -66
- package/app-template/src/views/category/category-info.tsx +173 -24
- package/app-template/src/views/category/filters/filter-item.tsx +138 -42
- package/app-template/src/views/category/filters/index.tsx +208 -48
- package/app-template/src/views/category/layout.tsx +7 -4
- package/app-template/src/views/category/native-widget-context.tsx +257 -0
- package/app-template/src/views/category/product-list-registrar.tsx +665 -0
- package/app-template/src/views/checkout/auth.tsx +64 -40
- package/app-template/src/views/checkout/checkout-address-registrar.tsx +254 -0
- package/app-template/src/views/checkout/checkout-buttons-registrar.tsx +183 -0
- package/app-template/src/views/checkout/checkout-delivery-method-registrar.tsx +259 -0
- package/app-template/src/views/checkout/checkout-payment-options-registrar.tsx +253 -0
- package/app-template/src/views/checkout/checkout-summary-registrar.tsx +183 -0
- package/app-template/src/views/checkout/constants.ts +5 -0
- package/app-template/src/views/checkout/index.tsx +5 -0
- package/app-template/src/views/checkout/layout/header.tsx +9 -5
- package/app-template/src/views/checkout/steps/payment/index.tsx +5 -2
- package/app-template/src/views/checkout/steps/payment/options/credit-card/index.tsx +72 -1
- package/app-template/src/views/checkout/steps/payment/options/masterpass-rest.tsx +15 -0
- package/app-template/src/views/checkout/steps/payment/options/saved-card.tsx +18 -0
- package/app-template/src/views/checkout/steps/payment/payment-option-buttons.tsx +171 -40
- package/app-template/src/views/checkout/steps/shipping/address-box.tsx +74 -12
- package/app-template/src/views/checkout/steps/shipping/addresses.tsx +128 -45
- package/app-template/src/views/checkout/steps/shipping/shipping-options.tsx +232 -27
- package/app-template/src/views/checkout/summary.tsx +303 -29
- package/app-template/src/views/footer/footer-app-banner-context.tsx +326 -0
- package/app-template/src/views/footer/footer-bottom-context.tsx +215 -0
- package/app-template/src/views/footer/footer-bottom-wrapper.tsx +74 -0
- package/app-template/src/views/footer/footer-layout-constants.ts +35 -0
- package/app-template/src/views/footer/footer-layout-registrar.tsx +342 -0
- package/app-template/src/views/footer/footer-layout-switcher.tsx +110 -0
- package/app-template/src/views/footer/footer-menu-context.tsx +211 -0
- package/app-template/src/views/footer/footer-native-widgets.tsx +60 -0
- package/app-template/src/views/footer/footer-social-context.tsx +254 -0
- package/app-template/src/views/footer/footer-subscription-context.tsx +210 -0
- package/app-template/src/views/footer/footer-utils.ts +43 -0
- package/app-template/src/views/footer/footer-value-props-context.tsx +326 -0
- package/app-template/src/views/footer/logo-settings.ts +183 -0
- package/app-template/src/views/footer/native-widget-config.ts +262 -0
- package/app-template/src/views/footer/subscription-settings.ts +122 -0
- package/app-template/src/views/footer/use-footer-logo.ts +162 -0
- package/app-template/src/views/footer.tsx +415 -13
- package/app-template/src/views/guest-login/index.tsx +62 -58
- package/app-template/src/views/header/action-menu.tsx +277 -45
- package/app-template/src/views/header/band.tsx +6 -21
- package/app-template/src/views/header/designer-context.tsx +261 -0
- package/app-template/src/views/header/header-announcement-registrar.tsx +267 -0
- package/app-template/src/views/header/header-client-wrapper.tsx +496 -0
- package/app-template/src/views/header/header-content.tsx +1026 -0
- package/app-template/src/views/header/header-currency-registrar.tsx +348 -0
- package/app-template/src/views/header/header-icons-context.tsx +262 -0
- package/app-template/src/views/header/header-language-registrar.tsx +348 -0
- package/app-template/src/views/header/header-layout-context.tsx +143 -0
- package/app-template/src/views/header/header-layout-registrar.tsx +658 -0
- package/app-template/src/views/header/header-logo-context.tsx +228 -0
- package/app-template/src/views/header/header-logo.tsx +118 -0
- package/app-template/src/views/header/header-mini-basket-context.tsx +524 -0
- package/app-template/src/views/header/header-search-registrar.tsx +511 -0
- package/app-template/src/views/header/header-text-slider-registrar.tsx +382 -0
- package/app-template/src/views/header/index.tsx +109 -47
- package/app-template/src/views/header/inline-search.tsx +262 -0
- package/app-template/src/views/header/mini-basket.tsx +819 -44
- package/app-template/src/views/header/mobile-hamburger-button.tsx +5 -8
- package/app-template/src/views/header/mobile-menu.tsx +12 -0
- package/app-template/src/views/header/navbar-menu-context.tsx +219 -0
- package/app-template/src/views/header/navbar.tsx +178 -111
- package/app-template/src/views/header/search/index.tsx +71 -32
- package/app-template/src/views/header/search/results.tsx +127 -65
- package/app-template/src/views/header/search/search-input.tsx +61 -0
- package/app-template/src/views/header/server-settings-parser.ts +1105 -0
- package/app-template/src/views/header/use-header-icons.ts +241 -0
- package/app-template/src/views/header/use-header-logo.ts +213 -0
- package/app-template/src/views/header/use-navbar-menu.ts +179 -0
- package/app-template/src/views/login/index.tsx +54 -46
- package/app-template/src/views/product/accordion-section.tsx +61 -0
- package/app-template/src/views/product/accordion-wrapper.tsx +135 -43
- package/app-template/src/views/product/custom-button-group.tsx +69 -0
- package/app-template/src/views/product/favorites-button-section.tsx +69 -0
- package/app-template/src/views/product/find-in-store-section.tsx +60 -0
- package/app-template/src/views/product/index.ts +1 -0
- package/app-template/src/views/product/layout.tsx +6 -5
- package/app-template/src/views/product/misc-buttons.tsx +339 -25
- package/app-template/src/views/product/price-wrapper.tsx +3 -29
- package/app-template/src/views/product/product-actions.tsx +137 -8
- package/app-template/src/views/product/product-info-section.tsx +140 -0
- package/app-template/src/views/product/product-info.tsx +69 -31
- package/app-template/src/views/product/product-share.tsx +13 -8
- package/app-template/src/views/product/product-variants.tsx +2 -2
- package/app-template/src/views/product/quantity-section.tsx +73 -0
- package/app-template/src/views/product/sale-tag.tsx +10 -0
- package/app-template/src/views/product/share-section.tsx +357 -0
- package/app-template/src/views/product/slider.tsx +117 -79
- package/app-template/src/views/product/variant.tsx +69 -41
- package/app-template/src/views/product/variants-section.tsx +126 -0
- package/app-template/src/views/product-detail/constants.ts +272 -0
- package/app-template/src/views/product-detail/index.ts +10 -0
- package/app-template/src/views/product-detail/product-detail-registrar.tsx +616 -0
- package/app-template/src/views/product-item/index.tsx +119 -46
- package/app-template/src/views/register/index.tsx +14 -25
- package/app-template/src/views/share/index.tsx +9 -6
- package/app-template/src/views/widgets/home-hero-slider-content.tsx +41 -39
- package/app-template/src/widgets/flatpages/about-us/index.tsx +78 -0
- package/app-template/src/widgets/flatpages/blog-list/index.tsx +129 -0
- package/app-template/src/widgets/footer-app-banner.tsx +444 -0
- package/app-template/src/widgets/footer-bottom.tsx +127 -0
- package/app-template/src/widgets/footer-menu-compact.tsx +238 -0
- package/app-template/src/widgets/footer-menu-two.tsx +298 -0
- package/app-template/src/widgets/footer-social-client.tsx +251 -0
- package/app-template/src/widgets/footer-social.tsx +47 -16
- package/app-template/src/widgets/footer-subscription/footer-subscription-form.tsx +17 -14
- package/app-template/src/widgets/footer-subscription/index.tsx +183 -17
- package/app-template/src/widgets/footer-value-props.tsx +201 -0
- package/app-template/src/widgets/index.ts +7 -0
- package/app-template/src/widgets/schemas/about-us.json +46 -0
- package/app-template/src/widgets/schemas/blog-list.json +37 -0
- package/app-template/src/widgets/schemas/blog.json +29 -0
- package/app-template/tailwind.config.js +18 -2
- package/package.json +1 -1
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { CSSProperties } from 'react';
|
|
4
|
+
import clsx from 'clsx';
|
|
5
|
+
import { Link, Icon } from '@theme/components';
|
|
6
|
+
import { useDesignerFeatures } from '@akinon/next/components/theme-editor/hooks/use-designer-features';
|
|
7
|
+
import {
|
|
8
|
+
useFooterSocialDesigner,
|
|
9
|
+
FOOTER_SOCIAL_ICONS_BLOCK_ID,
|
|
10
|
+
type SocialIconItem
|
|
11
|
+
} from '../views/footer/footer-social-context';
|
|
12
|
+
import {
|
|
13
|
+
FOOTER_PLACEHOLDER_ID,
|
|
14
|
+
FOOTER_SOCIAL_SECTION_ID
|
|
15
|
+
} from '../views/footer/native-widget-config';
|
|
16
|
+
|
|
17
|
+
const kebabToCamel = (str: string): string =>
|
|
18
|
+
str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
19
|
+
|
|
20
|
+
const normalizeStyles = (
|
|
21
|
+
styles: Record<string, unknown> | undefined
|
|
22
|
+
): CSSProperties => {
|
|
23
|
+
if (!styles) return {};
|
|
24
|
+
|
|
25
|
+
const result: Record<string, string | number> = {};
|
|
26
|
+
|
|
27
|
+
for (const [key, value] of Object.entries(styles)) {
|
|
28
|
+
if (value == null) continue;
|
|
29
|
+
|
|
30
|
+
const camelKey = kebabToCamel(key);
|
|
31
|
+
let resolved: unknown = value;
|
|
32
|
+
|
|
33
|
+
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
34
|
+
const responsive = value as Record<string, unknown>;
|
|
35
|
+
resolved = responsive.desktop ?? responsive.mobile ?? responsive.tablet;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (resolved != null) {
|
|
39
|
+
if (typeof resolved === 'number') {
|
|
40
|
+
result[camelKey] = resolved;
|
|
41
|
+
} else if (typeof resolved === 'string') {
|
|
42
|
+
result[camelKey] = resolved;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return result as CSSProperties;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Legacy types for backwards compatibility
|
|
51
|
+
type SocialNetwork = {
|
|
52
|
+
key: string;
|
|
53
|
+
url: string;
|
|
54
|
+
icon: string;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
type SocialItem = {
|
|
58
|
+
value: {
|
|
59
|
+
icon_class?: string;
|
|
60
|
+
site_name: string;
|
|
61
|
+
redirect_url: string;
|
|
62
|
+
is_target_blank: string;
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
interface FooterSocialClientProps {
|
|
67
|
+
activeNetworks: SocialNetwork[];
|
|
68
|
+
socialItems: SocialItem[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Resolve responsive value to actual value
|
|
72
|
+
const resolveValue = <T,>(value: unknown, defaultValue: T): T => {
|
|
73
|
+
if (value == null) return defaultValue;
|
|
74
|
+
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
75
|
+
const responsive = value as Record<string, unknown>;
|
|
76
|
+
const resolved =
|
|
77
|
+
responsive.desktop ?? responsive.mobile ?? responsive.tablet;
|
|
78
|
+
return (resolved as T) ?? defaultValue;
|
|
79
|
+
}
|
|
80
|
+
return value as T;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export default function FooterSocialClient({
|
|
84
|
+
activeNetworks,
|
|
85
|
+
socialItems
|
|
86
|
+
}: FooterSocialClientProps) {
|
|
87
|
+
const { isDesigner, selectedBlockId, getBlock, blockVersion } =
|
|
88
|
+
useFooterSocialDesigner();
|
|
89
|
+
|
|
90
|
+
const iconsBlock =
|
|
91
|
+
blockVersion >= 0 ? getBlock(FOOTER_SOCIAL_ICONS_BLOCK_ID) : undefined;
|
|
92
|
+
const iconStylesRaw = iconsBlock?.styles as
|
|
93
|
+
| Record<string, unknown>
|
|
94
|
+
| undefined;
|
|
95
|
+
const iconStyles = normalizeStyles(iconStylesRaw);
|
|
96
|
+
|
|
97
|
+
// Get custom icons from properties (new social-icons-list format)
|
|
98
|
+
const blockProperties = iconsBlock?.properties as
|
|
99
|
+
| Record<string, unknown>
|
|
100
|
+
| undefined;
|
|
101
|
+
const customIcons = resolveValue<SocialIconItem[]>(
|
|
102
|
+
blockProperties?.icons,
|
|
103
|
+
[]
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// Gap between icons from styles
|
|
107
|
+
const iconGap = resolveValue<string | number>(iconStylesRaw?.gap, 16);
|
|
108
|
+
|
|
109
|
+
const { handleClick: handleIconsClick } = useDesignerFeatures({
|
|
110
|
+
blockId: FOOTER_SOCIAL_ICONS_BLOCK_ID,
|
|
111
|
+
placeholderId: FOOTER_PLACEHOLDER_ID,
|
|
112
|
+
sectionId: FOOTER_SOCIAL_SECTION_ID,
|
|
113
|
+
isDesigner,
|
|
114
|
+
blockInfo: {
|
|
115
|
+
id: FOOTER_SOCIAL_ICONS_BLOCK_ID,
|
|
116
|
+
type: 'social-icons-list',
|
|
117
|
+
label: 'Social Icons'
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const isIconsSelected = selectedBlockId === FOOTER_SOCIAL_ICONS_BLOCK_ID;
|
|
122
|
+
|
|
123
|
+
// Process SVG to apply color - replace fill/stroke attributes with the chosen color
|
|
124
|
+
const processSvgColor = (svgHtml: string, color: string | undefined) => {
|
|
125
|
+
if (!color) return svgHtml;
|
|
126
|
+
// Replace fill and stroke attributes (but not fill="none" which is used for transparency)
|
|
127
|
+
let processed = svgHtml
|
|
128
|
+
.replace(/fill="(?!none)[^"]*"/gi, `fill="${color}"`)
|
|
129
|
+
.replace(/stroke="(?!none)[^"]*"/gi, `stroke="${color}"`);
|
|
130
|
+
// If no fill attribute exists, add one to the svg element
|
|
131
|
+
if (!processed.includes('fill=')) {
|
|
132
|
+
processed = processed.replace('<svg', `<svg fill="${color}"`);
|
|
133
|
+
}
|
|
134
|
+
return processed;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Render custom icons from theme editor (new format)
|
|
138
|
+
if (customIcons.length > 0) {
|
|
139
|
+
return (
|
|
140
|
+
<div
|
|
141
|
+
className={clsx(
|
|
142
|
+
'flex items-center',
|
|
143
|
+
isDesigner && 'cursor-pointer',
|
|
144
|
+
isIconsSelected && 'ring-2 ring-blue-500 ring-offset-2 rounded'
|
|
145
|
+
)}
|
|
146
|
+
style={{ gap: `${iconGap}px` }}
|
|
147
|
+
data-block-id={isDesigner ? FOOTER_SOCIAL_ICONS_BLOCK_ID : undefined}
|
|
148
|
+
onClick={isDesigner ? handleIconsClick : undefined}
|
|
149
|
+
>
|
|
150
|
+
{customIcons.map((item, i) => (
|
|
151
|
+
<Link key={i} href={item.link} target="_blank">
|
|
152
|
+
<Icon
|
|
153
|
+
size={item.size || 24}
|
|
154
|
+
name={item.icon}
|
|
155
|
+
style={{ color: item.color }}
|
|
156
|
+
/>
|
|
157
|
+
</Link>
|
|
158
|
+
))}
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Fallback: Render legacy activeNetworks (SVG icons from theme-config)
|
|
164
|
+
if (activeNetworks.length > 0) {
|
|
165
|
+
// Legacy: use global color/size from styles
|
|
166
|
+
const legacyIconColor = iconStyles.color as string | undefined;
|
|
167
|
+
const legacyIconSize = resolveValue<number>(iconStylesRaw?.size, 18);
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<div
|
|
171
|
+
className={clsx(
|
|
172
|
+
'flex items-center',
|
|
173
|
+
isDesigner && 'cursor-pointer',
|
|
174
|
+
isIconsSelected && 'ring-2 ring-blue-500 ring-offset-2 rounded'
|
|
175
|
+
)}
|
|
176
|
+
style={{ gap: `${iconGap}px` }}
|
|
177
|
+
data-block-id={isDesigner ? FOOTER_SOCIAL_ICONS_BLOCK_ID : undefined}
|
|
178
|
+
onClick={isDesigner ? handleIconsClick : undefined}
|
|
179
|
+
>
|
|
180
|
+
{activeNetworks.map((network, i) => (
|
|
181
|
+
<Link key={i} href={network.url} target="_blank">
|
|
182
|
+
<div
|
|
183
|
+
className="flex items-center justify-center [&>svg]:w-full [&>svg]:h-full"
|
|
184
|
+
style={{
|
|
185
|
+
width: legacyIconSize,
|
|
186
|
+
height: legacyIconSize
|
|
187
|
+
}}
|
|
188
|
+
dangerouslySetInnerHTML={{
|
|
189
|
+
__html: processSvgColor(network.icon, legacyIconColor)
|
|
190
|
+
}}
|
|
191
|
+
/>
|
|
192
|
+
</Link>
|
|
193
|
+
))}
|
|
194
|
+
</div>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Fallback: Render legacy socialItems (font icons from footer-social widget)
|
|
199
|
+
if (socialItems.length > 0) {
|
|
200
|
+
const legacyIconColor = iconStyles.color as string | undefined;
|
|
201
|
+
const legacyIconSize = resolveValue<number>(iconStylesRaw?.size, 18);
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
<div
|
|
205
|
+
className={clsx(
|
|
206
|
+
'flex items-center',
|
|
207
|
+
isDesigner && 'cursor-pointer',
|
|
208
|
+
isIconsSelected && 'ring-2 ring-blue-500 ring-offset-2 rounded'
|
|
209
|
+
)}
|
|
210
|
+
style={{ gap: `${iconGap}px` }}
|
|
211
|
+
data-block-id={isDesigner ? FOOTER_SOCIAL_ICONS_BLOCK_ID : undefined}
|
|
212
|
+
onClick={isDesigner ? handleIconsClick : undefined}
|
|
213
|
+
>
|
|
214
|
+
{socialItems.map((item, i) => (
|
|
215
|
+
<Link
|
|
216
|
+
key={i}
|
|
217
|
+
href={item?.value?.redirect_url}
|
|
218
|
+
target={
|
|
219
|
+
item?.value?.is_target_blank === 'true' ? '_blank' : '_self'
|
|
220
|
+
}
|
|
221
|
+
style={{ color: legacyIconColor }}
|
|
222
|
+
>
|
|
223
|
+
<Icon
|
|
224
|
+
size={legacyIconSize}
|
|
225
|
+
name={item?.value?.icon_class?.replace('pz-icon-', '')}
|
|
226
|
+
/>
|
|
227
|
+
</Link>
|
|
228
|
+
))}
|
|
229
|
+
</div>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Designer mode: Show placeholder when no icons
|
|
234
|
+
if (isDesigner) {
|
|
235
|
+
return (
|
|
236
|
+
<div
|
|
237
|
+
className={clsx(
|
|
238
|
+
'flex items-center gap-4 py-2 px-4 border border-dashed border-gray-400 rounded text-gray-400 text-sm',
|
|
239
|
+
'cursor-pointer',
|
|
240
|
+
isIconsSelected && 'ring-2 ring-blue-500 ring-offset-2'
|
|
241
|
+
)}
|
|
242
|
+
data-block-id={FOOTER_SOCIAL_ICONS_BLOCK_ID}
|
|
243
|
+
onClick={handleIconsClick}
|
|
244
|
+
>
|
|
245
|
+
Click to add social icons
|
|
246
|
+
</div>
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import 'server-only';
|
|
2
2
|
|
|
3
|
-
import { Link, Icon } from '@theme/components';
|
|
4
3
|
import { getWidgetData } from '@akinon/next/data/server';
|
|
4
|
+
import FooterSocialClient from './footer-social-client';
|
|
5
|
+
|
|
6
|
+
type ThemeConfig = {
|
|
7
|
+
theme_settings: string | object;
|
|
8
|
+
};
|
|
5
9
|
|
|
6
10
|
type TargetBlank = {
|
|
7
11
|
value: string;
|
|
@@ -31,23 +35,50 @@ type FooterSocialType = {
|
|
|
31
35
|
};
|
|
32
36
|
|
|
33
37
|
export default async function FooterSocial() {
|
|
38
|
+
const themeConfig = await getWidgetData<ThemeConfig>({
|
|
39
|
+
slug: 'theme-config'
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
let socialNetworks = {};
|
|
43
|
+
try {
|
|
44
|
+
if (themeConfig?.attributes?.theme_settings) {
|
|
45
|
+
const settingsRaw = themeConfig.attributes.theme_settings;
|
|
46
|
+
let settings;
|
|
47
|
+
|
|
48
|
+
if (typeof settingsRaw === 'string') {
|
|
49
|
+
settings = JSON.parse(settingsRaw);
|
|
50
|
+
} else if (
|
|
51
|
+
typeof settingsRaw === 'object' &&
|
|
52
|
+
settingsRaw !== null &&
|
|
53
|
+
'value' in settingsRaw
|
|
54
|
+
) {
|
|
55
|
+
const value = (settingsRaw as any).value;
|
|
56
|
+
settings = typeof value === 'string' ? JSON.parse(value) : value;
|
|
57
|
+
} else {
|
|
58
|
+
settings = settingsRaw;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
socialNetworks = settings.socialNetworks || {};
|
|
62
|
+
}
|
|
63
|
+
} catch (e) {
|
|
64
|
+
console.error('Error parsing theme settings:', e);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const activeNetworks = Object.entries(socialNetworks)
|
|
68
|
+
.filter(([_, value]: [string, any]) => value.icon)
|
|
69
|
+
.map(([key, value]: [string, any]) => ({
|
|
70
|
+
key,
|
|
71
|
+
url: value.url || '#',
|
|
72
|
+
icon: value.icon
|
|
73
|
+
}));
|
|
74
|
+
|
|
34
75
|
const data = await getWidgetData<FooterSocialType>({ slug: 'footer-social' });
|
|
76
|
+
const socialItems = data?.attributes?.social_items || [];
|
|
35
77
|
|
|
36
78
|
return (
|
|
37
|
-
<
|
|
38
|
-
{
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
href={item?.value?.redirect_url}
|
|
42
|
-
className="p-2 border rounded-full border-gray md:mr-1 md:last:mr-0"
|
|
43
|
-
target={item?.value?.is_target_blank === 'true' ? '_blank' : '_self'}
|
|
44
|
-
>
|
|
45
|
-
<Icon
|
|
46
|
-
size={18}
|
|
47
|
-
name={item?.value?.icon_class?.replace('pz-icon-', '')}
|
|
48
|
-
/>
|
|
49
|
-
</Link>
|
|
50
|
-
))}
|
|
51
|
-
</div>
|
|
79
|
+
<FooterSocialClient
|
|
80
|
+
activeNetworks={activeNetworks}
|
|
81
|
+
socialItems={socialItems}
|
|
82
|
+
/>
|
|
52
83
|
);
|
|
53
84
|
}
|
|
@@ -9,6 +9,7 @@ import { useEmailSubscriptionMutation } from '@akinon/next/data/client/misc';
|
|
|
9
9
|
import { useGetWidgetQuery } from '@akinon/next/data/client/misc';
|
|
10
10
|
import { Input, Button, Checkbox, Modal } from '@theme/components';
|
|
11
11
|
import { useLocalization } from '@akinon/next/hooks';
|
|
12
|
+
import { Icon } from '@akinon/next/components';
|
|
12
13
|
|
|
13
14
|
const subscriptionFormSchema = (t: (key: string) => string) =>
|
|
14
15
|
yup.object().shape({
|
|
@@ -43,29 +44,31 @@ export default function FooterSubscriptionForm() {
|
|
|
43
44
|
|
|
44
45
|
return (
|
|
45
46
|
<div>
|
|
46
|
-
<div className="flex
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
47
|
+
<div className="flex items-center justify-between border border-[#7d7d7d] px-4 py-[14px] gap-4 w-[331px]">
|
|
48
|
+
<div className="flex-1">
|
|
49
|
+
<Input
|
|
50
|
+
id="footer-subscription-email"
|
|
51
|
+
type="email"
|
|
52
|
+
placeholder={t('common.newsletter_signup.email_address')}
|
|
53
|
+
className="border-0 h-auto p-0 flex-1"
|
|
54
|
+
{...register('email')}
|
|
55
|
+
error={errors.email}
|
|
56
|
+
data-testid="newsletter-input"
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
56
59
|
|
|
57
60
|
<Button
|
|
58
61
|
type="submit"
|
|
59
62
|
appearance="outlined"
|
|
60
|
-
className="
|
|
63
|
+
className="bg-transparent hover:bg-transparent p-0 h-auto"
|
|
61
64
|
data-testid="newsletter-submit"
|
|
62
65
|
onClick={handleSubmit(onSubmit)}
|
|
63
66
|
>
|
|
64
|
-
{
|
|
67
|
+
<Icon name="arrow-right" size={16} className="text-black" />
|
|
65
68
|
</Button>
|
|
66
69
|
</div>
|
|
67
70
|
|
|
68
|
-
<Checkbox
|
|
71
|
+
{/* <Checkbox
|
|
69
72
|
{...register('subscribe_contract')}
|
|
70
73
|
error={errors.subscribe_contract}
|
|
71
74
|
data-testid="newsletter-agreement-checkbox"
|
|
@@ -104,7 +107,7 @@ export default function FooterSubscriptionForm() {
|
|
|
104
107
|
{t('common.newsletter_signup.close')}
|
|
105
108
|
</Button>
|
|
106
109
|
</Modal>
|
|
107
|
-
</Checkbox>
|
|
110
|
+
</Checkbox> */}
|
|
108
111
|
</div>
|
|
109
112
|
);
|
|
110
113
|
}
|
|
@@ -1,30 +1,196 @@
|
|
|
1
|
-
|
|
1
|
+
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import type { CSSProperties } from 'react';
|
|
4
|
+
import clsx from 'clsx';
|
|
5
|
+
import { useDesignerFeatures } from '@akinon/next/components/theme-editor/hooks/use-designer-features';
|
|
4
6
|
|
|
5
7
|
import FooterSubscriptionForm from './footer-subscription-form';
|
|
8
|
+
import {
|
|
9
|
+
FOOTER_PLACEHOLDER_ID,
|
|
10
|
+
FOOTER_SUBSCRIPTION_SECTION_ID
|
|
11
|
+
} from '../../views/footer/native-widget-config';
|
|
12
|
+
import {
|
|
13
|
+
FOOTER_SUBSCRIPTION_BLOCKS,
|
|
14
|
+
FOOTER_SUBSCRIPTION_DEFAULT_CONTENT
|
|
15
|
+
} from '../../views/footer/subscription-settings';
|
|
16
|
+
import { useFooterSubscriptionDesigner } from '../../views/footer/footer-subscription-context';
|
|
6
17
|
|
|
7
|
-
type
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
type FooterSubscriptionProps = {
|
|
19
|
+
title?: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type FooterSubscriptionBlockConfig =
|
|
24
|
+
typeof FOOTER_SUBSCRIPTION_BLOCKS[keyof typeof FOOTER_SUBSCRIPTION_BLOCKS];
|
|
25
|
+
|
|
26
|
+
const extractTextValue = (value: unknown): string | undefined => {
|
|
27
|
+
if (value == null) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
32
|
+
return String(value);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (typeof value === 'object') {
|
|
36
|
+
const record = value as Record<string, unknown>;
|
|
37
|
+
const candidateKeys = [
|
|
38
|
+
'content',
|
|
39
|
+
'value',
|
|
40
|
+
'text',
|
|
41
|
+
'label',
|
|
42
|
+
'default',
|
|
43
|
+
'desktop',
|
|
44
|
+
'mobile',
|
|
45
|
+
'tablet'
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
for (const key of candidateKeys) {
|
|
49
|
+
const candidate = record[key];
|
|
50
|
+
if (typeof candidate === 'string' && candidate.trim().length > 0) {
|
|
51
|
+
return candidate;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (const objValue of Object.values(record)) {
|
|
56
|
+
if (typeof objValue === 'string' && objValue.trim().length > 0) {
|
|
57
|
+
return objValue;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return undefined;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const kebabToCamel = (str: string): string =>
|
|
66
|
+
str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
67
|
+
|
|
68
|
+
const normalizeStyles = (
|
|
69
|
+
styles: Record<string, unknown> | undefined
|
|
70
|
+
): CSSProperties => {
|
|
71
|
+
if (!styles) return {};
|
|
72
|
+
|
|
73
|
+
const result: Record<string, string | number> = {};
|
|
74
|
+
|
|
75
|
+
for (const [key, value] of Object.entries(styles)) {
|
|
76
|
+
if (value == null) continue;
|
|
77
|
+
|
|
78
|
+
const camelKey = kebabToCamel(key);
|
|
79
|
+
let resolved: unknown = value;
|
|
80
|
+
|
|
81
|
+
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
82
|
+
const responsive = value as Record<string, unknown>;
|
|
83
|
+
resolved = responsive.desktop ?? responsive.mobile ?? responsive.tablet;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (resolved != null) {
|
|
87
|
+
if (typeof resolved === 'number') {
|
|
88
|
+
result[camelKey] = resolved;
|
|
89
|
+
} else if (typeof resolved === 'string') {
|
|
90
|
+
result[camelKey] = resolved;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return result as CSSProperties;
|
|
14
96
|
};
|
|
15
97
|
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
98
|
+
function useSubscriptionBlock(blockConfig: FooterSubscriptionBlockConfig) {
|
|
99
|
+
const { isDesigner, selectedBlockId, getBlock, blockVersion } =
|
|
100
|
+
useFooterSubscriptionDesigner();
|
|
101
|
+
const blockState = blockVersion >= 0 ? getBlock(blockConfig.id) : undefined;
|
|
102
|
+
const styles = normalizeStyles(blockState?.styles as Record<string, unknown>);
|
|
103
|
+
const { handleClick } = useDesignerFeatures({
|
|
104
|
+
blockId: blockConfig.id,
|
|
105
|
+
placeholderId: FOOTER_PLACEHOLDER_ID,
|
|
106
|
+
sectionId: FOOTER_SUBSCRIPTION_SECTION_ID,
|
|
107
|
+
isDesigner,
|
|
108
|
+
blockInfo: {
|
|
109
|
+
id: blockConfig.id,
|
|
110
|
+
type: blockConfig.type,
|
|
111
|
+
label: blockConfig.label
|
|
112
|
+
}
|
|
19
113
|
});
|
|
20
114
|
|
|
115
|
+
return {
|
|
116
|
+
styles,
|
|
117
|
+
isDesigner,
|
|
118
|
+
isSelected: selectedBlockId === blockConfig.id,
|
|
119
|
+
onClick: isDesigner ? handleClick : undefined,
|
|
120
|
+
content: extractTextValue(blockState?.value)
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export default function FooterSubscription({
|
|
125
|
+
title,
|
|
126
|
+
description
|
|
127
|
+
}: FooterSubscriptionProps) {
|
|
128
|
+
const titleBlock = useSubscriptionBlock(FOOTER_SUBSCRIPTION_BLOCKS.TITLE);
|
|
129
|
+
const descriptionBlock = useSubscriptionBlock(
|
|
130
|
+
FOOTER_SUBSCRIPTION_BLOCKS.DESCRIPTION
|
|
131
|
+
);
|
|
132
|
+
const formBlock = useSubscriptionBlock(FOOTER_SUBSCRIPTION_BLOCKS.FORM);
|
|
133
|
+
const displayTitle =
|
|
134
|
+
titleBlock.content || title || FOOTER_SUBSCRIPTION_DEFAULT_CONTENT.title;
|
|
135
|
+
const displayDescription =
|
|
136
|
+
descriptionBlock.content ||
|
|
137
|
+
description ||
|
|
138
|
+
FOOTER_SUBSCRIPTION_DEFAULT_CONTENT.description;
|
|
139
|
+
const shouldRenderDescription =
|
|
140
|
+
descriptionBlock.isDesigner || Boolean(displayDescription);
|
|
141
|
+
|
|
21
142
|
return (
|
|
22
|
-
<div className="
|
|
23
|
-
<h3
|
|
24
|
-
{
|
|
143
|
+
<div className="w-full max-w-[331px]">
|
|
144
|
+
<h3
|
|
145
|
+
style={titleBlock.styles}
|
|
146
|
+
data-block-id={
|
|
147
|
+
titleBlock.isDesigner
|
|
148
|
+
? FOOTER_SUBSCRIPTION_BLOCKS.TITLE.id
|
|
149
|
+
: undefined
|
|
150
|
+
}
|
|
151
|
+
onClick={titleBlock.onClick}
|
|
152
|
+
className={clsx(
|
|
153
|
+
'mb-4 text-lg text-[#121212]',
|
|
154
|
+
titleBlock.isDesigner && 'cursor-pointer',
|
|
155
|
+
titleBlock.isSelected && 'ring-2 ring-blue-500 ring-offset-2 rounded'
|
|
156
|
+
)}
|
|
157
|
+
>
|
|
158
|
+
{displayTitle}
|
|
25
159
|
</h3>
|
|
26
|
-
|
|
27
|
-
|
|
160
|
+
|
|
161
|
+
{shouldRenderDescription && (
|
|
162
|
+
<p
|
|
163
|
+
style={descriptionBlock.styles}
|
|
164
|
+
data-block-id={
|
|
165
|
+
descriptionBlock.isDesigner
|
|
166
|
+
? FOOTER_SUBSCRIPTION_BLOCKS.DESCRIPTION.id
|
|
167
|
+
: undefined
|
|
168
|
+
}
|
|
169
|
+
onClick={descriptionBlock.onClick}
|
|
170
|
+
className={clsx(
|
|
171
|
+
'text-sm text-[#121212] text-opacity-75 mb-6 leading-relaxed',
|
|
172
|
+
descriptionBlock.isDesigner && 'cursor-pointer',
|
|
173
|
+
descriptionBlock.isSelected &&
|
|
174
|
+
'ring-2 ring-blue-500 ring-offset-2 rounded'
|
|
175
|
+
)}
|
|
176
|
+
>
|
|
177
|
+
{displayDescription}
|
|
178
|
+
</p>
|
|
179
|
+
)}
|
|
180
|
+
|
|
181
|
+
<div
|
|
182
|
+
style={formBlock.styles}
|
|
183
|
+
data-block-id={
|
|
184
|
+
formBlock.isDesigner ? FOOTER_SUBSCRIPTION_BLOCKS.FORM.id : undefined
|
|
185
|
+
}
|
|
186
|
+
onClick={formBlock.onClick}
|
|
187
|
+
className={clsx(
|
|
188
|
+
formBlock.isDesigner && 'cursor-pointer inline-block',
|
|
189
|
+
formBlock.isSelected && 'ring-2 ring-blue-500 ring-offset-2 rounded'
|
|
190
|
+
)}
|
|
191
|
+
>
|
|
192
|
+
<FooterSubscriptionForm />
|
|
193
|
+
</div>
|
|
28
194
|
</div>
|
|
29
195
|
);
|
|
30
196
|
}
|