@shopgate/engage 7.27.3-alpha.18 → 7.27.3-alpha.19
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/components/ResponsiveContainer/breakpoints.js +2 -2
- package/components/View/components/Content/style.js +1 -1
- package/components/View/context.js +1 -1
- package/core/constants/index.js +7 -1
- package/core/contexts/ThemeResourcesContext.d.ts +10 -1
- package/core/contexts/ThemeResourcesContext.js +1 -1
- package/core/helpers/scrollContainer.js +2 -2
- package/core/hocs/withThemeResources.js +4 -1
- package/core/hooks/events/index.js +1 -1
- package/core/hooks/events/usePressHandler.js +38 -0
- package/core/hooks/useThemeResources.js +6 -5
- package/core/providers/ThemeResourcesProvider.js +9 -5
- package/locations/subscriptions.js +2 -2
- package/package.json +9 -7
- package/page/action-creators/index.js +22 -0
- package/page/components/Widgets/Overlay.js +51 -0
- package/page/components/Widgets/Tooltip.js +22 -0
- package/page/components/Widgets/Widget.js +15 -0
- package/page/components/Widgets/WidgetContext.d.ts +42 -0
- package/page/components/Widgets/WidgetContext.js +9 -0
- package/page/components/Widgets/WidgetProvider.js +8 -0
- package/page/components/Widgets/Widgets.js +11 -0
- package/page/components/Widgets/WidgetsPreviewContext.js +9 -0
- package/page/components/Widgets/WidgetsPreviewProvider.js +8 -0
- package/page/components/Widgets/constants.js +4 -0
- package/page/components/Widgets/events.js +23 -0
- package/page/components/Widgets/helpers.js +23 -0
- package/page/components/Widgets/hooks.js +69 -0
- package/page/components/Widgets/index.js +1 -0
- package/page/components/Widgets/types.d.ts +127 -0
- package/page/components/index.js +1 -1
- package/page/constants/actionTypes.js +1 -0
- package/page/constants/index.js +5 -1
- package/page/hooks/index.d.ts +60 -0
- package/page/hooks/index.js +25 -0
- package/page/index.js +1 -2
- package/page/reducers/index.js +6 -0
- package/page/selectors/index.js +48 -2
- package/page/subscriptions/index.js +4 -0
- package/page/widgets/HTML/HTML.js +5 -0
- package/page/widgets/HTML/hooks.js +12 -0
- package/page/widgets/HTML/index.js +1 -0
- package/page/widgets/Placeholder/Placeholder.js +5 -0
- package/page/widgets/Placeholder/hooks.js +12 -0
- package/page/widgets/Placeholder/index.js +1 -0
- package/page/widgets/ProductList/ProductList.js +5 -0
- package/page/widgets/ProductList/hooks.js +25 -0
- package/page/widgets/ProductList/index.js +1 -0
- package/page/widgets/ProductSlider/ProductSlider.js +4 -0
- package/page/widgets/ProductSlider/hooks.js +27 -0
- package/page/widgets/ProductSlider/index.js +1 -0
- package/page/widgets/index.js +1 -0
- package/page/widgets/widgets.json +14 -0
- package/product/components/ProductCard/index.js +1 -1
- package/product/components/ProductGrid/components/Item/components/ItemDetails/index.js +8 -0
- package/product/components/ProductGrid/components/Item/components/ItemDetails/spec.js +1 -0
- package/product/components/ProductGrid/components/Item/components/ItemDiscount/index.js +5 -0
- package/product/components/ProductGrid/components/Item/components/ItemFavoritesButton/index.js +5 -0
- package/product/components/ProductGrid/components/Item/components/ItemFavoritesButton/spec.js +1 -0
- package/product/components/ProductGrid/components/Item/components/ItemImage/index.js +5 -0
- package/product/components/ProductGrid/components/Item/components/ItemImage/spec.js +1 -0
- package/product/components/ProductGrid/components/Item/components/ItemName/index.js +5 -0
- package/product/components/ProductGrid/components/Item/components/ItemName/spec.js +1 -0
- package/product/components/ProductGrid/components/Item/components/ItemPrice/index.js +5 -0
- package/product/components/ProductGrid/components/Item/components/ItemPrice/spec.js +1 -0
- package/product/components/ProductGrid/components/Item/index.js +7 -0
- package/product/components/ProductGrid/components/Iterator/index.js +5 -0
- package/product/components/ProductGrid/components/Layout/index.js +5 -0
- package/product/components/ProductGrid/index.js +22 -0
- package/product/components/ProductGrid/spec.js +1 -0
- package/product/components/ProductGridPrice/index.js +1 -1
- package/product/components/ProductSlider/index.js +4 -4
- package/product/components/index.js +1 -1
- package/styles/helpers/color.js +23 -0
- package/styles/helpers/index.js +1 -1
- package/styles/helpers/setPageBackgroundColor.js +2 -2
- package/styles/index.d.ts +17 -0
- package/styles/index.js +1 -1
- package/styles/theme/createTheme/createBreakpoints.d.ts +114 -0
- package/styles/theme/createTheme/createBreakpoints.js +41 -0
- package/styles/theme/createTheme/createSpacing.d.ts +23 -0
- package/styles/theme/createTheme/createSpacing.js +14 -0
- package/styles/theme/createTheme/index.d.ts +19 -0
- package/styles/theme/createTheme/index.js +5 -0
- package/styles/theme/createTheme/transitions.d.ts +100 -0
- package/styles/theme/createTheme/transitions.js +26 -0
- package/styles/theme/createTheme/zIndex.d.ts +12 -0
- package/styles/theme/createTheme/zIndex.js +3 -0
- package/styles/theme/hooks/index.d.ts +4 -0
- package/styles/theme/hooks/index.js +1 -0
- package/styles/theme/hooks/useActiveBreakpoint.d.ts +18 -0
- package/styles/theme/hooks/useActiveBreakpoint.js +4 -0
- package/styles/theme/hooks/useMediaQuery.d.ts +33 -0
- package/styles/theme/hooks/useMediaQuery.js +20 -0
- package/styles/theme/hooks/useResponsiveValue.d.ts +27 -0
- package/styles/theme/hooks/useResponsiveValue.js +4 -0
- package/styles/theme/hooks/useTheme.d.ts +8 -0
- package/styles/theme/hooks/useTheme.js +4 -0
- package/styles/theme/index.d.ts +8 -0
- package/styles/theme/index.js +1 -0
- package/styles/theme/providers/ActiveBreakpointProvider.d.ts +21 -0
- package/styles/theme/providers/ActiveBreakpointProvider.js +13 -0
- package/styles/theme/providers/ThemeProvider.d.ts +18 -0
- package/styles/theme/providers/ThemeProvider.js +7 -0
- package/styles/tss/index.js +3 -0
- package/tracking/selectors/cookieConsent.js +2 -2
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import{useEffect,useCallback,useRef,useContext,useMemo}from'react';import{logger}from'@shopgate/engage/core/helpers';import{useDispatch}from'react-redux';import{useRoute}from'@shopgate/engage/core/hooks';import{receivePageConfigV2}from'@shopgate/engage/page/action-creators';import{PAGE_PREVIEW_SLUG}from'@shopgate/engage/page/constants';import{ALLOWED_PAGE_PREVIEW_ORIGINS,CONSIDER_CONTAINER_MARGINS_ON_SCROLL_DEFAULT}from"./constants";import{getScrollContainer}from"./helpers";import{WidgetsPreviewContext}from"./WidgetsPreviewContext";import{dispatchWidgetPreviewEvent,useWidgetPreviewEvent}from"./events";/**
|
|
2
|
+
* @typedef {Object} MessageData
|
|
3
|
+
* @property {string} type Identifier for the kind of message
|
|
4
|
+
* @property {any} [payload] Optional data payload for this message
|
|
5
|
+
*/ /**
|
|
6
|
+
* @typedef {Object} IframeMessengerResult
|
|
7
|
+
* @property {function(MessageData, string=): void} sendToParent
|
|
8
|
+
* - Send data up to window.parent. If targetOrigin is omitted, uses the
|
|
9
|
+
* most recently seen origin (from an incoming message). If none seen yet,
|
|
10
|
+
* falls back to parentOrigins[0] or "*".
|
|
11
|
+
*/ /**
|
|
12
|
+
* Hook for postMessage communication when your component is inside an iframe.
|
|
13
|
+
*
|
|
14
|
+
* Listens on window for "message" events from any origin in parentOrigins,
|
|
15
|
+
* and only calls onMessage(data, rawEvent) if both origin and source match.
|
|
16
|
+
*
|
|
17
|
+
* @param {function(MessageData, any): void} onMessage
|
|
18
|
+
* Callback invoked when a trusted message arrives. Receives data and the
|
|
19
|
+
* raw event (so you can inspect origin, source, etc.).
|
|
20
|
+
* @param {string[]} parentOrigins
|
|
21
|
+
* Array of allowed parent origin strings (e.g.
|
|
22
|
+
* ['https://a.example.com','https://b.example.com']).
|
|
23
|
+
* @returns {IframeMessengerResult}
|
|
24
|
+
* An object with a single method:
|
|
25
|
+
* • sendToParent(data, [targetOrigin]): void
|
|
26
|
+
* – Posts data up to window.parent. By default it uses the most recently
|
|
27
|
+
* seen origin (from an incoming message). If none, uses parentOrigins[0].
|
|
28
|
+
*/function useIframeMessenger(onMessage,parentOrigins){// Keep a ref to the latest onMessage callback so the listener always has it.
|
|
29
|
+
var onMessageRef=useRef(onMessage);useEffect(function(){onMessageRef.current=onMessage;},[onMessage]);// Keep track of the last allowed origin we heard from
|
|
30
|
+
var lastOriginRef=useRef(null);/**
|
|
31
|
+
* Send a message up to the parent window.
|
|
32
|
+
* @param {MessageData} data - The data object to post.
|
|
33
|
+
* @param {string} [targetOrigin]
|
|
34
|
+
* Optional override for the origin to post to. Must be one of
|
|
35
|
+
* parentOrigins. If omitted, uses the last seen origin (lastOriginRef),
|
|
36
|
+
* or parentOrigins[0], or "*" if array is empty.
|
|
37
|
+
*/var sendToParent=useCallback(function(data,targetOrigin){// Determine which origin to use: explicit, then last seen, then first, then "*".
|
|
38
|
+
var originToUse=typeof targetOrigin==='string'?targetOrigin:lastOriginRef.current||new URL(document.referrer).origin||parentOrigins[0]||'*';if(!originToUse){logger.warn('useIframeMessenger: no targetOrigin available. '+'Provide parentOrigins or pass targetOrigin.');return;}window.parent.postMessage(data,originToUse);},[parentOrigins]);// Attach / detach the "message" listener.
|
|
39
|
+
useEffect(function(){/**
|
|
40
|
+
* Handler for incoming postMessage events.
|
|
41
|
+
* @param {any} rawEvent – The original MessageEvent object.
|
|
42
|
+
*/function handler(rawEvent){// Only proceed if the origin is in our whitelist.
|
|
43
|
+
if(!parentOrigins.includes(rawEvent.origin))return;// Ensure the message actually came from window.parent.
|
|
44
|
+
if(rawEvent.source!==window.parent)return;// Record this origin as most recently seen.
|
|
45
|
+
lastOriginRef.current=rawEvent.origin;// Forward the event.data and the raw event to the callback.
|
|
46
|
+
onMessageRef.current(rawEvent.data,rawEvent);}window.addEventListener('message',handler);return function(){window.removeEventListener('message',handler);};},[parentOrigins,sendToParent]);return{sendToParent:sendToParent};}/**
|
|
47
|
+
* Hook to handle communication with the parent window in a page preview iframe.
|
|
48
|
+
* @param {boolean} isActive Whether the preview communication is active.
|
|
49
|
+
*/export var usePreviewIframeCommunication=function usePreviewIframeCommunication(){var isActive=arguments.length>0&&arguments[0]!==undefined?arguments[0]:false;var dispatch=useDispatch();var _useRoute=useRoute(),considerContainerMarginsOnScroll=_useRoute.query.considerContainerMarginsOnScroll;// Detect if container margins should be considered at scroll to widget.
|
|
50
|
+
var considerVerticalMargins=useMemo(function(){if(!considerContainerMarginsOnScroll){return CONSIDER_CONTAINER_MARGINS_ON_SCROLL_DEFAULT;}return considerContainerMarginsOnScroll==='true';},[considerContainerMarginsOnScroll]);var _useIframeMessenger=useIframeMessenger(function(data){var _data$payload;if(data.type==='receivePageConfig'){// Page preview config received from the parent window.
|
|
51
|
+
dispatch(receivePageConfigV2({type:'cms',slug:PAGE_PREVIEW_SLUG,data:data.payload}));}else if(data.type==='scrollToWidget'&&((_data$payload=data.payload)===null||_data$payload===void 0?void 0:_data$payload.widgetCode)){// Parent window requested to scroll to a specific widget.
|
|
52
|
+
var scrollContainer=getScrollContainer();var target=scrollContainer.querySelector("#widget-code-".concat(data.payload.widgetCode));if(scrollContainer&&target){var marginTop=0;if(considerVerticalMargins){var styles=window.getComputedStyle(target);marginTop=parseFloat(styles.marginTop);}var containerTop=scrollContainer.getBoundingClientRect().top;var targetTop=target.getBoundingClientRect().top;var scrollOffset=targetTop-containerTop+scrollContainer.scrollTop-marginTop;var maxScrollTop=scrollContainer.scrollHeight-scrollContainer.clientHeight;var actualScrollTop=Math.min(scrollOffset,maxScrollTop);// Register the target element as the active widget.
|
|
53
|
+
dispatchWidgetPreviewEvent('set-active-widget-id',data.payload.widgetCode);/**
|
|
54
|
+
* Callback to highlight the widget after scrolling.
|
|
55
|
+
*/var highlightWidget=function highlightWidget(){dispatchWidgetPreviewEvent('highlight-widget',data.payload.widgetCode);};// Add listener to onScrollEnd if available, otherwise use scroll event.
|
|
56
|
+
if('onscrollend'in scrollContainer){/**
|
|
57
|
+
* Callback for the scrollend event.
|
|
58
|
+
*/var onEnded=function onEnded(){scrollContainer.removeEventListener('scrollend',onEnded);highlightWidget();};scrollContainer.addEventListener('scrollend',onEnded,{once:true});scrollContainer.scrollTo({top:actualScrollTop,behavior:'smooth'});return;}// Fallback: listen for scroll events until scrollTop ≈ actualScrollTop
|
|
59
|
+
/**
|
|
60
|
+
* Callback for the scroll event.
|
|
61
|
+
*/var onScroll=function onScroll(){// Allow a 1 px leeway for subpixel rendering
|
|
62
|
+
if(Math.abs(scrollContainer.scrollTop-actualScrollTop)<1){scrollContainer.removeEventListener('scroll',onScroll);highlightWidget();}};scrollContainer.addEventListener('scroll',onScroll);scrollContainer.scrollTo({top:actualScrollTop,behavior:'smooth'});}}},ALLOWED_PAGE_PREVIEW_ORIGINS),sendToParent=_useIframeMessenger.sendToParent;useWidgetPreviewEvent('widget-clicked',function(e){if(!isActive){return;}sendToParent({type:'widgetClicked',payload:{widgetCode:e.detail.widgetCode}});});};/**
|
|
63
|
+
* @typedef {import('./WidgetsPreviewContext.js').WidgetsPreviewContextType}
|
|
64
|
+
* WidgetsPreviewContextType
|
|
65
|
+
*/ /**
|
|
66
|
+
* The useWidgetsPreview hook provides access to the context that is wrapped around the Widgets
|
|
67
|
+
* component when it's rendered in preview mode.
|
|
68
|
+
* @returns {WidgetsPreviewContextType} The widget context.
|
|
69
|
+
*/export var useWidgetsPreview=function useWidgetsPreview(){return useContext(WidgetsPreviewContext);};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{default}from"./Widgets";export{WidgetContext}from"./WidgetContext";
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visibility settings for a widget.
|
|
3
|
+
*/
|
|
4
|
+
export interface WidgetDefinitionVisibility {
|
|
5
|
+
/**
|
|
6
|
+
* Whether the widget is hidden.
|
|
7
|
+
*/
|
|
8
|
+
isHidden: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Start date for scheduled widgets.
|
|
11
|
+
*/
|
|
12
|
+
scheduleStartDate: string;
|
|
13
|
+
/**
|
|
14
|
+
* End date for scheduled widgets.
|
|
15
|
+
*/
|
|
16
|
+
scheduleEndDate: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Layout settings for a widget.
|
|
21
|
+
*/
|
|
22
|
+
export interface WidgetDefinitionLayout {
|
|
23
|
+
/**
|
|
24
|
+
* Top margin for the widget.
|
|
25
|
+
*/
|
|
26
|
+
marginTop: number;
|
|
27
|
+
/**
|
|
28
|
+
* Bottom margin for the widget.
|
|
29
|
+
*/
|
|
30
|
+
marginBottom: number;
|
|
31
|
+
/**
|
|
32
|
+
* Left margin for the widget.
|
|
33
|
+
*/
|
|
34
|
+
marginLeft: number;
|
|
35
|
+
/**
|
|
36
|
+
* Right margin for the widget.
|
|
37
|
+
*/
|
|
38
|
+
marginRight: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Definition of a API widget
|
|
43
|
+
*/
|
|
44
|
+
export interface WidgetDefinition {
|
|
45
|
+
/**
|
|
46
|
+
* Unique code for the widget.
|
|
47
|
+
*/
|
|
48
|
+
code: string;
|
|
49
|
+
/**
|
|
50
|
+
* Name of the widget
|
|
51
|
+
*/
|
|
52
|
+
widgetConfigDefinitionCode: string;
|
|
53
|
+
/**
|
|
54
|
+
* Individual configuration for the widget.
|
|
55
|
+
*/
|
|
56
|
+
widgetConfig: Record<string, any>;
|
|
57
|
+
/**
|
|
58
|
+
* Visibility settings for the widget.
|
|
59
|
+
*/
|
|
60
|
+
visibility: WidgetDefinitionVisibility;
|
|
61
|
+
/**
|
|
62
|
+
* Layout settings for the widget.
|
|
63
|
+
*/
|
|
64
|
+
layout: WidgetDefinitionLayout;
|
|
65
|
+
/**
|
|
66
|
+
* Optional metadata for the widget (only available in preview mode)
|
|
67
|
+
*/
|
|
68
|
+
meta?: {
|
|
69
|
+
/**
|
|
70
|
+
* Hidden state related data
|
|
71
|
+
*/
|
|
72
|
+
hidden: {
|
|
73
|
+
/**
|
|
74
|
+
* Whether the widget is hidden.
|
|
75
|
+
*/
|
|
76
|
+
isHidden: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Tooltip text for hidden related UI elements.
|
|
79
|
+
*/
|
|
80
|
+
tooltip: string;
|
|
81
|
+
/**
|
|
82
|
+
* Label text for hidden related UI elements.
|
|
83
|
+
*/
|
|
84
|
+
label: string;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Scheduled state related data
|
|
88
|
+
*/
|
|
89
|
+
scheduled: {
|
|
90
|
+
/**
|
|
91
|
+
* Indicates if the widget is scheduled.
|
|
92
|
+
*/
|
|
93
|
+
isScheduled: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Indicates that the widget schedule time frame is currently active.
|
|
96
|
+
*/
|
|
97
|
+
isActive: boolean;
|
|
98
|
+
/**
|
|
99
|
+
* Indicates if the scheduled time frame has expired
|
|
100
|
+
*/
|
|
101
|
+
isExpired: boolean;
|
|
102
|
+
/**
|
|
103
|
+
* Tooltip text for schedule related UI elements.
|
|
104
|
+
*/
|
|
105
|
+
tooltip: string;
|
|
106
|
+
/**
|
|
107
|
+
* Label text for schedule related UI elements.
|
|
108
|
+
*/
|
|
109
|
+
label: string;
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface ScheduledStatus {
|
|
115
|
+
/**
|
|
116
|
+
* Indicates if the widget is scheduled.
|
|
117
|
+
*/
|
|
118
|
+
isScheduled: boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Indicates if the widget is currently hidden based on the scheduling
|
|
121
|
+
*/
|
|
122
|
+
isHidden: boolean;
|
|
123
|
+
/**
|
|
124
|
+
* Indicates if the scheduled time frame has expired.
|
|
125
|
+
*/
|
|
126
|
+
isExpired: boolean;
|
|
127
|
+
}
|
package/page/components/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{default as NotFound}from"./NotFound";
|
|
1
|
+
export{default as NotFound}from"./NotFound";export{default as Widgets}from"./Widgets";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export var REQUEST_PAGE_CONFIG_V2='REQUEST_PAGE_CONFIG_V2';export var RECEIVE_PAGE_CONFIG_V2='RECEIVE_PAGE_CONFIG_V2';export var ERROR_PAGE_CONFIG_V2='ERROR_PAGE_CONFIG_V2';
|
package/page/constants/index.js
CHANGED
|
@@ -1 +1,5 @@
|
|
|
1
|
-
import{PAGE_PATH,PAGE_PATTERN}from'@shopgate/pwa-common/constants/RoutePaths';export*from'@shopgate/pwa-common/constants/PageIDs';export{PAGE_PATH,PAGE_PATTERN};export var IMPRINT_PATH="".concat(PAGE_PATH,"/imprint");export var PAYMENT_PATH="".concat(PAGE_PATH,"/payment");export var PRIVACY_PATH="".concat(PAGE_PATH,"/privacy");export var RETURN_POLICY_PATH="".concat(PAGE_PATH,"/return_policy");export var SHIPPING_PATH="".concat(PAGE_PATH,"/shipping");export var TERMS_PATH="".concat(PAGE_PATH,"/terms");
|
|
1
|
+
import{PAGE_PATH,PAGE_PATTERN}from'@shopgate/pwa-common/constants/RoutePaths';export*from'@shopgate/pwa-common/constants/PageIDs';export{PAGE_PATH,PAGE_PATTERN};export*from"./actionTypes";export var IMPRINT_PATH="".concat(PAGE_PATH,"/imprint");export var PAYMENT_PATH="".concat(PAGE_PATH,"/payment");export var PRIVACY_PATH="".concat(PAGE_PATH,"/privacy");export var RETURN_POLICY_PATH="".concat(PAGE_PATH,"/return_policy");export var SHIPPING_PATH="".concat(PAGE_PATH,"/shipping");export var TERMS_PATH="".concat(PAGE_PATH,"/terms");export var PAGE_PREVIEW_PATTERN='/shopgate-internal-page-preview';export var PAGE_PREVIEW_SLUG='page_preview';/**
|
|
2
|
+
* Checks if the app is currently in page preview mode.
|
|
3
|
+
*/export var IS_PAGE_PREVIEW_ACTIVE=window.location.pathname.startsWith(PAGE_PREVIEW_PATTERN);/**
|
|
4
|
+
* One hour in milliseconds
|
|
5
|
+
*/export var PAGE_STATE_LIFETIME=3600000;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { WidgetContextType } from '../components/Widgets/WidgetContext';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The useWidget hook provides access to the context that is wrapped around a widget.
|
|
5
|
+
* @returns The widget context.
|
|
6
|
+
*/
|
|
7
|
+
export declare function useWidget<C = Record<string, any>>(): WidgetContextType<C>
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export type UseWidgetProductsOptions = {
|
|
11
|
+
/**
|
|
12
|
+
* The search value to use for the product search
|
|
13
|
+
*/
|
|
14
|
+
value: string;
|
|
15
|
+
/**
|
|
16
|
+
* The type of product search to perform.
|
|
17
|
+
*/
|
|
18
|
+
type: 'searchTerm' | 'itemNumbers' | 'brand' | 'category' | 'highlights';
|
|
19
|
+
/**
|
|
20
|
+
* The number of products to return per page.
|
|
21
|
+
* @default 32
|
|
22
|
+
*/
|
|
23
|
+
limit?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Sort order for the products
|
|
26
|
+
* @default 'relevance'
|
|
27
|
+
*/
|
|
28
|
+
sort?: 'relevance' | 'priceAsc' | 'priceDesc' | 'nameAsc' | 'nameDesc';
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type UseWidgetProductsResult = {
|
|
32
|
+
/**
|
|
33
|
+
* Function to fetch the next page of products.
|
|
34
|
+
*/
|
|
35
|
+
fetchNext: () => void;
|
|
36
|
+
/**
|
|
37
|
+
* Whether there are more products to fetch.
|
|
38
|
+
*/
|
|
39
|
+
hasNext: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Whether the products are currently being fetched.
|
|
42
|
+
*/
|
|
43
|
+
isFetching: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Number of products available in the current result set.
|
|
46
|
+
*/
|
|
47
|
+
totalResultCount: number;
|
|
48
|
+
/**
|
|
49
|
+
* Array of product results.
|
|
50
|
+
*/
|
|
51
|
+
results: Object[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The useWidgetProducts hook provides an easy way to retrieve products based on a search term or
|
|
56
|
+
* other criteria.
|
|
57
|
+
*/
|
|
58
|
+
export declare function useWidgetProducts(
|
|
59
|
+
options: UseWidgetProductsOptions
|
|
60
|
+
): UseWidgetProductsResult;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import _regeneratorRuntime from"@babel/runtime/regenerator";function asyncGeneratorStep(gen,resolve,reject,_next,_throw,key,arg){try{var info=gen[key](arg);var value=info.value;}catch(error){reject(error);return;}if(info.done){resolve(value);}else{Promise.resolve(value).then(_next,_throw);}}function _asyncToGenerator(fn){return function(){var self=this,args=arguments;return new Promise(function(resolve,reject){var gen=fn.apply(self,args);function _next(value){asyncGeneratorStep(gen,resolve,reject,_next,_throw,"next",value);}function _throw(err){asyncGeneratorStep(gen,resolve,reject,_next,_throw,"throw",err);}_next(undefined);});};}function _slicedToArray(arr,i){return _arrayWithHoles(arr)||_iterableToArrayLimit(arr,i)||_nonIterableRest();}function _nonIterableRest(){throw new TypeError("Invalid attempt to destructure non-iterable instance");}function _iterableToArrayLimit(arr,i){var _arr=[];var _n=true;var _d=false;var _e=undefined;try{for(var _i=arr[Symbol.iterator](),_s;!(_n=(_s=_i.next()).done);_n=true){_arr.push(_s.value);if(i&&_arr.length===i)break;}}catch(err){_d=true;_e=err;}finally{try{if(!_n&&_i["return"]!=null)_i["return"]();}finally{if(_d)throw _e;}}return _arr;}function _arrayWithHoles(arr){if(Array.isArray(arr))return arr;}function _extends(){_extends=Object.assign||function(target){for(var i=1;i<arguments.length;i++){var source=arguments[i];for(var key in source){if(Object.prototype.hasOwnProperty.call(source,key)){target[key]=source[key];}}}return target;};return _extends.apply(this,arguments);}import{useContext,useMemo,useCallback,useEffect,useState}from'react';import{useSelector,useDispatch}from'react-redux';import{WidgetContext}from'@shopgate/engage/page/components/Widgets';import{showInventoryInLists}from'@shopgate/engage/locations/helpers';import{ITEMS_PER_LOAD}from'@shopgate/engage/core/constants';import{transformDisplayOptions}from'@shopgate/engage/core/helpers';import{fetchProductsByQuery}from'@shopgate/engage/product';import{makeGetWidgetProducts}from"../selectors";/**
|
|
2
|
+
* @typedef {import('../components/Widgets/WidgetContext').WidgetContextType WidgetContextType}
|
|
3
|
+
*/ /**
|
|
4
|
+
* @typedef {import('./index').UseWidgetProductsOptions} UseWidgetProductsOptions
|
|
5
|
+
*/ /**
|
|
6
|
+
* @typedef {import('./index').UseWidgetProductsResult} UseWidgetProductsResult
|
|
7
|
+
*/ /**
|
|
8
|
+
* The useWidget hook provides access to the context that is wrapped around a widget.
|
|
9
|
+
* @returns {WidgetContextType} The widget context.
|
|
10
|
+
*/export var useWidget=function useWidget(){return useContext(WidgetContext);};var REQUEST_TYPE_MAPPING={highlights:1,searchTerm:2,brand:3,itemNumbers:4,category:5};/**
|
|
11
|
+
* Retrieves the products for the current widget.
|
|
12
|
+
* @param {UseWidgetProductsOptions} options Hook options
|
|
13
|
+
* @returns {UseWidgetProductsResult} The products and a function to fetch more products.
|
|
14
|
+
*/export var useWidgetProducts=function useWidgetProducts(){var options=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};var type=options.type,value=options.value,_options$limit=options.limit,limit=_options$limit===void 0?ITEMS_PER_LOAD:_options$limit,_options$sort=options.sort,sort=_options$sort===void 0?'relevance':_options$sort;var dispatch=useDispatch();var _useWidget=useWidget(),_useWidget$code=_useWidget.code,code=_useWidget$code===void 0?"".concat(type,"_").concat(value,"_").concat(limit,"_").concat(sort):_useWidget$code;// ###### Products selection ######
|
|
15
|
+
var showInventoryInProductLists=useSelector(showInventoryInLists);var selectorOptions=useMemo(function(){return _extends({sort:transformDisplayOptions(sort),value:value,useDefaultRequestForProductIds:true},showInventoryInProductLists&&{useDefaultRequestForProductIds:true});},[showInventoryInProductLists,sort,value]);var getWidgetProducts=useMemo(function(){return makeGetWidgetProducts(type,selectorOptions,code);},[code,selectorOptions,type]);var widgetProducts=useSelector(getWidgetProducts);// ###### Products request ######
|
|
16
|
+
var _useState=useState(true),_useState2=_slicedToArray(_useState,2),hasNext=_useState2[0],setHasNext=_useState2[1];var _useState3=useState(0),_useState4=_slicedToArray(_useState3,2),offset=_useState4[0],setOffset=_useState4[1];var isFetching=widgetProducts.isFetching;var baseRequestOptions=useMemo(function(){return _extends({limit:limit,sort:transformDisplayOptions(sort),useDefaultRequestForProductIds:true},showInventoryInProductLists&&{useDefaultRequestForProductIds:true});},[limit,showInventoryInProductLists,sort]);/**
|
|
17
|
+
* Callback to dispatch the initial fetch request for products when the hook mounts,
|
|
18
|
+
* or when its parameters change.
|
|
19
|
+
*/var fetchInitial=useCallback(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee(){var initialOptions,result;return _regeneratorRuntime.wrap(function _callee$(_context){while(1)switch(_context.prev=_context.next){case 0:if(value){_context.next=3;break;}setHasNext(false);return _context.abrupt("return");case 3:// Initial request needs to start at offset 0
|
|
20
|
+
initialOptions=_extends({},baseRequestOptions,{offset:0});_context.next=6;return dispatch(fetchProductsByQuery(REQUEST_TYPE_MAPPING[type],value,initialOptions,code));case 6:result=_context.sent;// Re-initialize offset and hasNext based on the result
|
|
21
|
+
setOffset(limit);setHasNext(result.totalProductCount>limit);case 9:case"end":return _context.stop();}},_callee);})),[code,dispatch,type,value,limit,baseRequestOptions]);// Effect to trigger the initial fetch when the component mounts or the parameters change.
|
|
22
|
+
useEffect(function(){fetchInitial();},[fetchInitial]);/**
|
|
23
|
+
* Callback to fetch the next chunk of products when e.g. users interacted with a "Load More"
|
|
24
|
+
* button.
|
|
25
|
+
*/var fetchNext=useCallback(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee2(){var nextOptions,result;return _regeneratorRuntime.wrap(function _callee2$(_context2){while(1)switch(_context2.prev=_context2.next){case 0:if(!(!hasNext||isFetching||!value)){_context2.next=2;break;}return _context2.abrupt("return");case 2:nextOptions=_extends({},baseRequestOptions,{offset:offset});_context2.next=5;return dispatch(fetchProductsByQuery(REQUEST_TYPE_MAPPING[type],value,nextOptions,code));case 5:result=_context2.sent;setOffset(offset+limit);setHasNext(result.totalProductCount>offset+limit);case 8:case"end":return _context2.stop();}},_callee2);})),[code,dispatch,hasNext,isFetching,value,offset,limit,baseRequestOptions,type]);return{fetchNext:fetchNext,hasNext:hasNext,isFetching:isFetching,results:widgetProducts.products.slice(0,offset),totalResultCount:widgetProducts.totalProductCount};};
|
package/page/index.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
/** @module page */export*from"./actions";export*from"./components";export*from"./constants"
|
|
2
|
-
export*from"./selectors";
|
|
1
|
+
/** @module page */export*from"./actions";export*from"./components";export*from"./constants";export*from"./selectors";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import{produce}from'immer';import{REQUEST_PAGE_CONFIG_V2,RECEIVE_PAGE_CONFIG_V2,ERROR_PAGE_CONFIG_V2,PAGE_STATE_LIFETIME}from"../constants";var defaultState={};/**
|
|
2
|
+
* Stores state of the v2 implementation of pages.
|
|
3
|
+
* @param {Object} [state={}] The current state.
|
|
4
|
+
* @param {Object} action The action object.
|
|
5
|
+
* @returns {Object} The new state.
|
|
6
|
+
*/export function pageV2(){var state=arguments.length>0&&arguments[0]!==undefined?arguments[0]:defaultState;var action=arguments.length>1?arguments[1]:undefined;/* eslint-disable no-param-reassign */var producer=produce(function(draft){switch(action.type){case REQUEST_PAGE_CONFIG_V2:{var pageType=action.pageType,pageSlug=action.pageSlug;draft[pageType]=draft[pageType]||{};draft[pageType][pageSlug]={data:null,isFetching:true,expires:0};break;}case RECEIVE_PAGE_CONFIG_V2:{var _pageType=action.pageType,_pageSlug=action.pageSlug,data=action.data;draft[_pageType]=draft[_pageType]||{};draft[_pageType][_pageSlug]={data:data,isFetching:false,expires:Date.now()+PAGE_STATE_LIFETIME};break;}case ERROR_PAGE_CONFIG_V2:{var _pageType2=action.pageType,_pageSlug2=action.pageSlug;draft[_pageType2]=draft[_pageType2]||{};draft[_pageType2][_pageSlug2]={data:null,isFetching:false};break;}default:break;}});/* eslint-enable no-param-reassign */return producer(state);}
|
package/page/selectors/index.js
CHANGED
|
@@ -1,7 +1,53 @@
|
|
|
1
|
-
import{createSelector}from'reselect';import{makeGetMenu,makeGetIsFetchingMenu}from'@shopgate/engage/core/selectors';import{LEGAL_MENU}from'@shopgate/engage/core/constants';import{hasNewServices}from'@shopgate/engage/core';import{PRIVACY_PATH}from"../constants";export*from'@shopgate/pwa-common/selectors/page';/**
|
|
1
|
+
function _extends(){_extends=Object.assign||function(target){for(var i=1;i<arguments.length;i++){var source=arguments[i];for(var key in source){if(Object.prototype.hasOwnProperty.call(source,key)){target[key]=source[key];}}}return target;};return _extends.apply(this,arguments);}import{createSelector}from'reselect';import{makeGetMenu,makeGetIsFetchingMenu}from'@shopgate/engage/core/selectors';import{getFulfillmentParams,getPopulatedProductsResult,SHOPGATE_CATALOG_GET_HIGHLIGHT_PRODUCTS}from'@shopgate/engage/product';import{getProductState}from'@shopgate/engage/product/selectors/product';import{LEGAL_MENU,SORT_PRICE_ASC,SORT_PRICE_DESC}from'@shopgate/engage/core/constants';import{hasNewServices,transformDisplayOptions,generateResultHash}from'@shopgate/engage/core/helpers';import{PRIVACY_PATH}from"../constants";export*from'@shopgate/pwa-common/selectors/page';/**
|
|
2
2
|
* Creates a selector that retrieves the privacy policy link.
|
|
3
3
|
*
|
|
4
4
|
* When the new services are active, the link is extracted from the "shopgate.cms.getMenu" response.
|
|
5
5
|
* Otherwise it's a static link to the legacy privacy page.
|
|
6
6
|
* @returns {string|null}
|
|
7
|
-
*/export var makeGetPrivacyPolicyLink=function makeGetPrivacyPolicyLink(){var getMenu=makeGetMenu(LEGAL_MENU);var getIsFetchingMenu=makeGetIsFetchingMenu(LEGAL_MENU);return createSelector(getMenu,getIsFetchingMenu,function(menu,fetching){if(!hasNewServices()){return PRIVACY_PATH;}if(fetching||!menu){return null;}var entry=menu.find(function(item){return item.url.includes('privacy');})||{};return(entry===null||entry===void 0?void 0:entry.url)||null;});}
|
|
7
|
+
*/export var makeGetPrivacyPolicyLink=function makeGetPrivacyPolicyLink(){var getMenu=makeGetMenu(LEGAL_MENU);var getIsFetchingMenu=makeGetIsFetchingMenu(LEGAL_MENU);return createSelector(getMenu,getIsFetchingMenu,function(menu,fetching){if(!hasNewServices()){return PRIVACY_PATH;}if(fetching||!menu){return null;}var entry=menu.find(function(item){return item.url.includes('privacy');})||{};return(entry===null||entry===void 0?void 0:entry.url)||null;});};/**
|
|
8
|
+
* @param {Object} state The current application state.
|
|
9
|
+
* @return {Function} A selector function that retrieves the pageV2 state.
|
|
10
|
+
*/var getPageV2State=function getPageV2State(state){return state.pageV2;};/**
|
|
11
|
+
* List of available page types.
|
|
12
|
+
* @typedef {'cms' | 'category'} PageType
|
|
13
|
+
*/ /**
|
|
14
|
+
* List of available dropzone types.
|
|
15
|
+
* @typedef {'cmsWidgetList'} DropzoneType
|
|
16
|
+
*/ /**
|
|
17
|
+
* Creates a selector that retrieves page data based on the type and slug.
|
|
18
|
+
* @param {Object} params The selector params
|
|
19
|
+
* @param {PageType} [params.type] The type of the page.
|
|
20
|
+
* @param {string|null} [params.slug=null] The slug of the page (optional).
|
|
21
|
+
* @returns {Function} A selector function that retrieves the page data.
|
|
22
|
+
*/export var makeGetPage=function makeGetPage(_ref){var _ref$type=_ref.type,type=_ref$type===void 0?'cms':_ref$type,_ref$slug=_ref.slug,slug=_ref$slug===void 0?null:_ref$slug;return createSelector(getPageV2State,function(pageState){if(type&&slug){var _pageState$type;return((_pageState$type=pageState[type])===null||_pageState$type===void 0?void 0:_pageState$type[slug])||null;}return pageState[type]||null;});};/**
|
|
23
|
+
* Creates a selector that retrieves the widget list from a page based on the type, slug and
|
|
24
|
+
* and dropzone name.
|
|
25
|
+
* @param {Object} params The selector params
|
|
26
|
+
* @param {PageType} [params.type='cms'] The type of the page.
|
|
27
|
+
* @param {DropzoneType} [params.dropzone='cmsWidgetList'] The dropzone name to retrieve the widget
|
|
28
|
+
* list from.
|
|
29
|
+
* @param {string|null} [params.slug=null] The slug of the page (optional).
|
|
30
|
+
* @returns {Function} A selector function that retrieves the widget list.
|
|
31
|
+
*/export var makeGetWidgetsFromPage=function makeGetWidgetsFromPage(_ref2){var _ref2$type=_ref2.type,type=_ref2$type===void 0?'cms':_ref2$type,_ref2$slug=_ref2.slug,slug=_ref2$slug===void 0?null:_ref2$slug,_ref2$dropzone=_ref2.dropzone,dropzone=_ref2$dropzone===void 0?'cmsWidgetList':_ref2$dropzone;var getPage=makeGetPage({type:type,slug:slug});return createSelector(getPage,function(page){var _ref3,_page$data,_page$data$dropzones;if(!page){return undefined;}return(_ref3=(_page$data=page.data)===null||_page$data===void 0?void 0:(_page$data$dropzones=_page$data.dropzones)===null||_page$data$dropzones===void 0?void 0:_page$data$dropzones[dropzone])!==null&&_ref3!==void 0?_ref3:[];});};/**
|
|
32
|
+
* Creates a selector that generates a hash to select results for widget products.
|
|
33
|
+
* @param {'searchTerm' | 'itemNumbers' | 'brand' | 'category' |'highlights'} type Type of the
|
|
34
|
+
* request to make.
|
|
35
|
+
* @param {Object} options Request options
|
|
36
|
+
* @param {string} id Unique identifier to find the result in the state.
|
|
37
|
+
* @returns {Function} A selector function that generates a hash for the widget products result.
|
|
38
|
+
*/var makeGetWidgetProductsResultHash=function makeGetWidgetProductsResultHash(type,options,id){var value=options.value,sort=options.sort,useDefaultRequestForProductIds=options.useDefaultRequestForProductIds,productIdType=options.productIdType;var transformedSort=transformDisplayOptions(sort);return createSelector(getFulfillmentParams,function(fulfillmentParams){var _hashParams;var hashParams={};switch(type){case'highlights':hashParams={id:id,pipeline:SHOPGATE_CATALOG_GET_HIGHLIGHT_PRODUCTS,sort:transformedSort};break;case'searchTerm':case'brand':hashParams=_extends({id:id,searchPhrase:value,sort:transformedSort},fulfillmentParams);break;case'itemNumbers':hashParams=_extends({id:id,productIds:value,productIdType:productIdType},!useDefaultRequestForProductIds&&{sort:transformedSort},{},fulfillmentParams);break;case'category':hashParams=_extends({id:id,categoryId:value,sort:transformedSort},fulfillmentParams);break;default:}return generateResultHash(hashParams,!!((_hashParams=hashParams)===null||_hashParams===void 0?void 0:_hashParams.sort),false);});};/**
|
|
39
|
+
* @param {'searchTerm' | 'itemNumbers' | 'brand' | 'category' |'highlights'} type Type of the
|
|
40
|
+
* request to make.
|
|
41
|
+
* @param {Object} options Request options
|
|
42
|
+
* @param {string} id Unique identifier to find the result in the state.
|
|
43
|
+
* @returns {Function} A selector function that retrieves the widget products result by hash.
|
|
44
|
+
*/var makeGetWidgetProductResultsByHash=function makeGetWidgetProductResultsByHash(type,options,id){var getWidgetProductResultsHash=makeGetWidgetProductsResultHash(type,options,id);return createSelector(getProductState,getWidgetProductResultsHash,function(productState,hash){return productState.resultsByHash[hash];});};/**
|
|
45
|
+
* Creates a selector that collects products for a widget.
|
|
46
|
+
* @param {'searchTerm' | 'itemNumbers' | 'brand' | 'category' |'highlights'} type Type of the
|
|
47
|
+
* request to make.
|
|
48
|
+
* @param {Object} options Request options
|
|
49
|
+
* @param {string} id Unique identifier to find the result in the state.
|
|
50
|
+
* @returns {Function} A selector function that collects products for a widget.
|
|
51
|
+
*/export var makeGetWidgetProducts=function makeGetWidgetProducts(type,options,id){var getWidgetProductResultsHash=makeGetWidgetProductsResultHash(type,options,id);var getWidgetProductResultsByHash=makeGetWidgetProductResultsByHash(type,options,id);return createSelector(function(state){return state;},function(state,props){return props!==null&&props!==void 0?props:{};},getWidgetProductResultsHash,getWidgetProductResultsByHash,function(state,props,resultsHash,resultsByHash){var result=_extends({isFetching:(resultsByHash===null||resultsByHash===void 0?void 0:resultsByHash.isFetching)||false},getPopulatedProductsResult(state,props,resultsHash,resultsByHash));// Since the getProducts pipeline does not support sorting when a product ID list is
|
|
52
|
+
// provided, we need to sort the products manually here.
|
|
53
|
+
if(type==='itemNumbers'){if(options.sort===SORT_PRICE_ASC){result.products=result.products.sort(function(p1,p2){return p1.price.unitPrice-p2.price.unitPrice;});}if(options.sort===SORT_PRICE_DESC){result.products=result.products.sort(function(p1,p2){return p2.price.unitPrice-p1.price.unitPrice;});}}return result;});};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import React from'react';import{HtmlSanitizer}from'@shopgate/engage/components';import{makeStyles}from'@shopgate/engage/styles';import{themeConfig}from'@shopgate/engage';import{useHtmlWidget}from"./hooks";var colors=themeConfig.colors;var useStyles=makeStyles()({root:{' h1, h2, h3, h4, h5, h6, p, ul, ol':{margin:'1rem 0'},' h1, h2, h3, h4, h5, h6':{fontWeight:600},' h1':{fontSize:'1.5rem'},' h2':{fontSize:'1.25rem'},' h3':{fontSize:'1.1rem'},' h4, h5, h6':{fontSize:'1rem'},' ol, ul':{paddingLeft:'1rem'},' ol > li':{listStyle:'decimal'},' ul > li':{listStyle:'disc'},' img':{display:'initial'},' img[style*="float: left"], img[style*="float:left"], img.pull-left':{marginRight:'1rem'},' img[style*="float: right"], img[style*="float:right"], img.pull-right':{marginLeft:'1rem'},' code, pre':{whiteSpace:'pre-wrap'},' blockquote, q':{paddingLeft:'1rem',margin:'2rem 0',borderLeft:".25rem solid ".concat(colors.shade6),fontStyle:'italic'},' > :first-child':{marginTop:0},// Clearfix for floated widget content
|
|
2
|
+
':after':{clear:'both',content:'.',display:'block',visibility:'hidden',height:0}}});/**
|
|
3
|
+
* The HtmlWidget component is used to display html code.
|
|
4
|
+
* @returns {JSX.Element}
|
|
5
|
+
*/var HtmlWidget=function HtmlWidget(){var _useStyles=useStyles(),classes=_useStyles.classes;var _useHtmlWidget=useHtmlWidget(),html=_useHtmlWidget.html;return React.createElement(HtmlSanitizer,{settings:{html:html},processStyles:true,className:classes.root},html);};export default HtmlWidget;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import{useWidget}from'@shopgate/engage/page/hooks';/**
|
|
2
|
+
* @typedef {Object} HtmlWidgetConfig
|
|
3
|
+
* @property {string} [html] - The HTML content to render.
|
|
4
|
+
* @property {string} [internalDescription] - Internal description for the widget.
|
|
5
|
+
*/ /**
|
|
6
|
+
* @typedef {Object} UseHtmlWidgetReturnType
|
|
7
|
+
* @property {string} [html] - The HTML content to render.
|
|
8
|
+
* @property {string} [internalDescription] - Internal description for the widget.
|
|
9
|
+
*/ /**
|
|
10
|
+
* Hook to access the html widget configuration.
|
|
11
|
+
* @returns {UseHtmlWidgetReturnType} the html widget configuration
|
|
12
|
+
*/export var useHtmlWidget=function useHtmlWidget(){/** @type {UseWidgetReturnType} */var _useWidget=useWidget(),config=_useWidget.config;var html=config.html;return{html:html};};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{default}from"./HTML";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import React from'react';import{makeStyles}from'@shopgate/engage/styles';import{usePlaceholderWidget}from"./hooks";var useStyles=makeStyles()(function(theme){return{root:{padding:theme.spacing(2),minHeight:200},name:{fontSize:14},pre:{background:'#f7f9fc',border:'1px solid #dbdde2',borderRadius:8,color:'#000',fontSize:10,overflowY:'auto',padding:theme.spacing(1)}};});/**
|
|
2
|
+
* The PlaceholderWidget component is used to display a placeholder for widget types that
|
|
3
|
+
* are not implemented yet.
|
|
4
|
+
* @returns {JSX.Element}
|
|
5
|
+
*/var PlaceholderWidget=function PlaceholderWidget(){var _useStyles=useStyles(),classes=_useStyles.classes;var _usePlaceholderWidget=usePlaceholderWidget(),code=_usePlaceholderWidget.code,name=_usePlaceholderWidget.name,config=_usePlaceholderWidget.config,layout=_usePlaceholderWidget.layout,visibility=_usePlaceholderWidget.visibility;return React.createElement("div",{className:classes.root},React.createElement("div",{className:classes.name},name),React.createElement("pre",{className:classes.pre},JSON.stringify({code:code,config:config,layout:layout,visibility:visibility},null,2)));};export default PlaceholderWidget;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import{useWidget}from'@shopgate/engage/page/hooks';/**
|
|
2
|
+
* @typedef {Object} PlaceholderWidgetConfig
|
|
3
|
+
* @property {string} foo Example property for the widget configuration.
|
|
4
|
+
* @property {number} bar Another example property for the widget configuration.
|
|
5
|
+
*/ /**
|
|
6
|
+
* @typedef {ReturnType< typeof import('@shopgate/engage/page/hooks')
|
|
7
|
+
* .useWidget<PlaceholderWidgetConfig> >} HookReturnType
|
|
8
|
+
*/ /**
|
|
9
|
+
* Local example hook to demonstrate how to extend the useWidget hook with a custom type for
|
|
10
|
+
* the widget configuration.
|
|
11
|
+
* @returns {HookReturnType}
|
|
12
|
+
*/export var usePlaceholderWidget=function usePlaceholderWidget(){return useWidget();};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{default}from"./Placeholder";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import React from'react';import{ActionButton,I18n}from'@shopgate/engage/components';import{ProductGrid}from'@shopgate/engage/product/components';import{useWidgetProducts}from'@shopgate/engage/page/hooks';import{makeStyles}from'@shopgate/engage/styles';import{useProductListWidget}from"./hooks";var useStyles=makeStyles()({root:{// Prevent that the ActionButton margin messes with the layout of the sibling widgets
|
|
2
|
+
overflow:'hidden'},grid:{'&&':{marginTop:0}}});/**
|
|
3
|
+
* The ProductListWidget is used to display product lists.
|
|
4
|
+
* @returns {JSX.Element}
|
|
5
|
+
*/var ProductListWidget=function ProductListWidget(){var _useStyles=useStyles(),classes=_useStyles.classes;var _useProductListWidget=useProductListWidget(),productsSearchType=_useProductListWidget.productsSearchType,productsSearchValue=_useProductListWidget.productsSearchValue,sort=_useProductListWidget.sort,productCount=_useProductListWidget.productCount,showLoadMore=_useProductListWidget.showLoadMore,flags=_useProductListWidget.flags;var _useWidgetProducts=useWidgetProducts({type:productsSearchType,value:productsSearchValue,limit:productCount,sort:sort}),fetchNext=_useWidgetProducts.fetchNext,hasNext=_useWidgetProducts.hasNext,isFetching=_useWidgetProducts.isFetching,results=_useWidgetProducts.results;return React.createElement("div",{className:classes.root},React.createElement(ProductGrid,{products:results,flags:flags,scope:"widgets",infiniteLoad:false,className:classes.grid}),hasNext&&showLoadMore&&React.createElement(ActionButton,{loading:isFetching,onClick:fetchNext},React.createElement(I18n.Text,{string:"common.load_more"})));};export default ProductListWidget;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import _camelCase from"lodash/camelCase";import{useMemo}from'react';import{useWidget}from'@shopgate/engage/page/hooks';/**
|
|
2
|
+
* @typedef {Object} ProductListWidgetProducts
|
|
3
|
+
* @property {"searchTerm" | "brand" | "category" | "itemNumbers"} productSelectorType Source type
|
|
4
|
+
* for the product list.
|
|
5
|
+
* @property {string} productsSearchTerm A search term to filter products by
|
|
6
|
+
* @property {string} productsBrand A brand to filter products by
|
|
7
|
+
* @property {string} productsCategory A category to filter products by
|
|
8
|
+
* @property {string} productsItemNumbers A comma-separated list of item numbers to filter products
|
|
9
|
+
* by
|
|
10
|
+
*/ /**
|
|
11
|
+
* @typedef {Object} ProductListWidgetConfig
|
|
12
|
+
* @property {ProductListWidgetProducts} products The products configuration for the widget.
|
|
13
|
+
* @property {number} productCount The number of products to display in the widget
|
|
14
|
+
* @property {"relevance" | "PriceDesc" | "PriceAsc"} sort Sort order for the products
|
|
15
|
+
* @property {boolean} loadMoreButton Whether to display a "Load more" button
|
|
16
|
+
* @property {boolean} showName Whether to display product names
|
|
17
|
+
* @property {boolean} showPrice Whether to display product prices
|
|
18
|
+
* @property {boolean} showRating Whether to display product ratings
|
|
19
|
+
*/ /**
|
|
20
|
+
* @typedef {ReturnType< typeof import('@shopgate/engage/page/hooks')
|
|
21
|
+
* .useWidget<ProductListWidgetConfig> >} UseWidgetReturnType
|
|
22
|
+
*/ // eslint-disable-next-line valid-jsdoc
|
|
23
|
+
/**
|
|
24
|
+
* Hook to access the Product List widget configuration.
|
|
25
|
+
*/export var useProductListWidget=function useProductListWidget(){/** @type {UseWidgetReturnType} */var _useWidget=useWidget(),config=_useWidget.config;var products=config.products,productCount=config.productCount,sort=config.sort,_config$loadMoreButto=config.loadMoreButton,loadMoreButton=_config$loadMoreButto===void 0?false:_config$loadMoreButto,_config$showName=config.showName,showName=_config$showName===void 0?false:_config$showName,_config$showPrice=config.showPrice,showPrice=_config$showPrice===void 0?false:_config$showPrice,_config$showRating=config.showRating,showRating=_config$showRating===void 0?false:_config$showRating;var productSelectorType=products.productSelectorType,productsBrand=products.productsBrand,productsCategory=products.productsCategory,productsItemNumbers=products.productsItemNumbers,productsSearchTerm=products.productsSearchTerm;var value=useMemo(function(){switch(productSelectorType){case'brand':return productsBrand;case'category':return productsCategory;case'itemNumbers':return productsItemNumbers.split(',').map(function(item){return item.trim();});case'searchTerm':default:return productsSearchTerm;}},[productSelectorType,productsBrand,productsCategory,productsItemNumbers,productsSearchTerm]);var flags=useMemo(function(){return{name:showName,price:showPrice,reviews:showRating};},[showName,showPrice,showRating]);return{productsSearchType:productSelectorType,productsSearchValue:value,sort:_camelCase(sort),productCount:productCount,showLoadMore:loadMoreButton,flags:flags};};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{default}from"./ProductList";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
function _extends(){_extends=Object.assign||function(target){for(var i=1;i<arguments.length;i++){var source=arguments[i];for(var key in source){if(Object.prototype.hasOwnProperty.call(source,key)){target[key]=source[key];}}}return target;};return _extends.apply(this,arguments);}function _defineProperty(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true});}else{obj[key]=value;}return obj;}import React,{useMemo}from'react';import{ProductSlider}from'@shopgate/engage/product/components';import{useWidgetProducts}from'@shopgate/engage/page/hooks';import{useTheme}from'@shopgate/engage/styles';import{useProductSliderWidget}from"./hooks";/**
|
|
2
|
+
* The ProductSliderWidget is used to display a product slider.
|
|
3
|
+
* @returns {JSX.Element}
|
|
4
|
+
*/var ProductSliderWidget=function ProductSliderWidget(){var _useProductSliderWidg=useProductSliderWidget(),productsSearchType=_useProductSliderWidg.productsSearchType,productsSearchValue=_useProductSliderWidg.productsSearchValue,sort=_useProductSliderWidg.sort,productCount=_useProductSliderWidg.productCount,swiperProps=_useProductSliderWidg.swiperProps,productItemProps=_useProductSliderWidg.productItemProps;var _useWidgetProducts=useWidgetProducts({type:productsSearchType,value:productsSearchValue,limit:productCount,sort:sort}),results=_useWidgetProducts.results;var theme=useTheme();var productIds=useMemo(function(){return results===null||results===void 0?void 0:results.map(function(result){return result.id;});},[results]);return React.createElement(ProductSlider,_extends({productIds:productIds,scope:"widgets",productItemProps:productItemProps,slidesPerView:2.3,breakpoints:_defineProperty(_defineProperty(_defineProperty({},theme.breakpoints.values.sm,{slidesPerView:3.3}),theme.breakpoints.values.md,{slidesPerView:4.3}),theme.breakpoints.values.lg,{slidesPerView:5.3})},swiperProps));};export default ProductSliderWidget;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import _camelCase from"lodash/camelCase";import{useMemo}from'react';import{useWidget}from'@shopgate/engage/page/hooks';/**
|
|
2
|
+
* @typedef {Object} ProductSliderWidgetProducts
|
|
3
|
+
* @property {"searchTerm" | "brand" | "category" | "itemNumbers"} productSelectorType
|
|
4
|
+
* Source type for the product list.
|
|
5
|
+
* @property {string} [productsSearchTerm] A search term to filter products by.
|
|
6
|
+
* @property {string} [productsBrand] A brand to filter products by.
|
|
7
|
+
* @property {string} [productsCategory] A category to filter products by.
|
|
8
|
+
* @property {string} [productsItemNumbers] A comma-separated list of item numbers
|
|
9
|
+
* to filter products by.
|
|
10
|
+
*/ /**
|
|
11
|
+
* @typedef {Object} ProductSliderWidgetConfig
|
|
12
|
+
* @property {ProductSliderWidgetProducts} products The products configuration for the widget.
|
|
13
|
+
* @property {number} productCount The number of products to display in the widget.
|
|
14
|
+
* @property {"relevance" | "priceDesc" | "priceAsc"} sort Sort order for the products.
|
|
15
|
+
* @property {boolean} [showName] Whether to display product names.
|
|
16
|
+
* @property {boolean} [showPrice] Whether to display product prices.
|
|
17
|
+
* @property {boolean} [showRating] Whether to display product ratings.
|
|
18
|
+
* @property {boolean} [slideAutomatic] Whether the slider should automatically slide.
|
|
19
|
+
* @property {boolean} [endlessSlider] Whether the slider should loop endlessly.
|
|
20
|
+
* @property {number} [sliderSpeed] The speed (in ms) for the slider autoplay.
|
|
21
|
+
*/ /**
|
|
22
|
+
* @typedef {ReturnType< typeof import('@shopgate/engage/page/hooks')
|
|
23
|
+
* .useWidget<ProductSliderWidgetConfig> >} UseWidgetReturnType
|
|
24
|
+
*/ // eslint-disable-next-line valid-jsdoc
|
|
25
|
+
/**
|
|
26
|
+
* Hook to access the Product Slider widget configuration.
|
|
27
|
+
*/export var useProductSliderWidget=function useProductSliderWidget(){/** @type {UseWidgetReturnType} */var _useWidget=useWidget(),config=_useWidget.config;var products=config.products,productCount=config.productCount,sort=config.sort,_config$showName=config.showName,showName=_config$showName===void 0?false:_config$showName,_config$showPrice=config.showPrice,showPrice=_config$showPrice===void 0?false:_config$showPrice,_config$showRating=config.showRating,showRating=_config$showRating===void 0?false:_config$showRating,_config$slideAutomati=config.slideAutomatic,slideAutomatic=_config$slideAutomati===void 0?true:_config$slideAutomati,_config$endlessSlider=config.endlessSlider,endlessSlider=_config$endlessSlider===void 0?true:_config$endlessSlider,_config$sliderSpeed=config.sliderSpeed,sliderSpeed=_config$sliderSpeed===void 0?7000:_config$sliderSpeed;var productSelectorType=products.productSelectorType,productsBrand=products.productsBrand,productsCategory=products.productsCategory,productsItemNumbers=products.productsItemNumbers,productsSearchTerm=products.productsSearchTerm;var value=useMemo(function(){switch(productSelectorType){case'brand':return productsBrand;case'category':return productsCategory;case'itemNumbers':return productsItemNumbers.split(',').map(function(item){return item.trim();});case'searchTerm':default:return productsSearchTerm;}},[productSelectorType,productsBrand,productsCategory,productsItemNumbers,productsSearchTerm]);var swiperProps=useMemo(function(){return{autoplay:slideAutomatic,delay:sliderSpeed,loop:endlessSlider};},[slideAutomatic,sliderSpeed,endlessSlider]);var productItemProps=useMemo(function(){return{hideName:!showName,hidePrice:!showPrice,hideRating:!showRating};},[showName,showPrice,showRating]);return{productsSearchType:productSelectorType,productsSearchValue:value,sort:_camelCase(sort),productCount:productCount,swiperProps:swiperProps,productItemProps:productItemProps};};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{default}from"./ProductSlider";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{default as PlaceholderWidget}from"./Placeholder";export{default as ProductListWidget}from"./ProductList";export{default as HtmlWidget}from"./HTML";export{default as ProductSliderWidget}from"./ProductSlider";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"@shopgate/widgets/productListWidget": {
|
|
3
|
+
"path": "@shopgate/engage/page/widgets/ProductList"
|
|
4
|
+
},
|
|
5
|
+
"@shopgate/widgets/productSliderWidget": {
|
|
6
|
+
"path": "@shopgate/engage/page/widgets/ProductSlider"
|
|
7
|
+
},
|
|
8
|
+
"@shopgate/widgets/htmlWidget": {
|
|
9
|
+
"path": "@shopgate/engage/page/widgets/HTML"
|
|
10
|
+
},
|
|
11
|
+
"@shopgate/widgetsInternal/Placeholder": {
|
|
12
|
+
"path": "@shopgate/engage/page/widgets/Placeholder"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -8,7 +8,7 @@ function _extends(){_extends=Object.assign||function(target){for(var i=1;i<argum
|
|
|
8
8
|
* @param {number} props.titleRows The max number of rows for the product title.
|
|
9
9
|
* @param {string} props.url Optional alternative url for the product link
|
|
10
10
|
* @return {JSX.Element}
|
|
11
|
-
*/function ProductCard(props){var product=props.product,hidePrice=props.hidePrice,hideRating=props.hideRating,hideName=props.hideName,titleRows=props.titleRows,url=props.url;var _useProductListType=useProductListType(),meta=_useProductListType.meta;var _getProductImageSetti=getProductImageSettings(),gridResolutions=_getProductImageSetti.ListImage;var _useWidgetSettings=useWidgetSettings('@shopgate/engage/rating'),_useWidgetSettings$sh=_useWidgetSettings.showEmptyRatingStars,showEmptyRatingStars=_useWidgetSettings$sh===void 0?false:_useWidgetSettings$sh;var showRatings=useMemo(function(){var _product$rating;if(!hideRating&&(product===null||product===void 0?void 0:(_product$rating=product.rating)===null||_product$rating===void 0?void 0:_product$rating.average)>0){return true;}if(!hideRating&&showEmptyRatingStars&&(product===null||product===void 0?void 0:product.rating)){return true;}return false;},[hideRating,product,showEmptyRatingStars]);return React.createElement(Link,{className:"engage__product-card",href:url||getProductRoute(product.id),itemProp:"item",itemScope:true,itemType:"http://schema.org/Product",tabIndex:0,state:_extends({},meta)},isBeta()&&product.featuredMedia?React.createElement(FeaturedMedia,{type:product.featuredMedia.type,url:product.featuredMedia.url,altText:product.featuredMedia.altText}):React.createElement(ProductImage,{src:product.featuredImageBaseUrl,resolutions:gridResolutions,alt:product.name,itemProp:"image"}),React.createElement(ProductBadges,{location:location,productId:product.id},!!(!hidePrice&&product.price.discount)&&React.createElement("div",{className:styles.badgeWrapper},React.createElement(SurroundPortals,{portalName:PRODUCT_ITEM_DISCOUNT,portalProps:{productId:product.id}},React.createElement(DiscountBadge,{text:"-".concat(product.price.discount,"%")})))),!(hidePrice&&hideRating)&&React.createElement("div",{className:"".concat(styles.details," engage__product-card__information")},showRatings&&React.createElement(RatingStars,{value:product.rating.average}),React.createElement(Swatches,{productId:product.id}),!hideName&&React.createElement(ProductName,{name:product.name,className:styles.title,testId:"Productname: ".concat(product.name),itemProp:"name",rows:titleRows||3}),React.createElement(MapPriceHint,{productId:product.id}),React.createElement(OrderQuantityHint,{productId:product.id}),!hidePrice&&React.createElement(SurroundPortals,{portalName:PRODUCT_ITEM_PRICE,portalProps:{productId:product.id,location:location}},React.createElement(ProductGridPrice,{product:product}))));}/**
|
|
11
|
+
*/function ProductCard(props){var product=props.product,hidePrice=props.hidePrice,hideRating=props.hideRating,hideName=props.hideName,titleRows=props.titleRows,url=props.url;var _useProductListType=useProductListType(),meta=_useProductListType.meta;var _getProductImageSetti=getProductImageSettings(),gridResolutions=_getProductImageSetti.ListImage;var _useWidgetSettings=useWidgetSettings('@shopgate/engage/rating'),_useWidgetSettings$sh=_useWidgetSettings.showEmptyRatingStars,showEmptyRatingStars=_useWidgetSettings$sh===void 0?false:_useWidgetSettings$sh;var showRatings=useMemo(function(){var _product$rating;if(!hideRating&&(product===null||product===void 0?void 0:(_product$rating=product.rating)===null||_product$rating===void 0?void 0:_product$rating.average)>0){return true;}if(!hideRating&&showEmptyRatingStars&&(product===null||product===void 0?void 0:product.rating)){return true;}return false;},[hideRating,product,showEmptyRatingStars]);return React.createElement(Link,{className:"engage__product-card",href:url||getProductRoute(product.id),itemProp:"item",itemScope:true,itemType:"http://schema.org/Product",tabIndex:0,state:_extends({},meta)},isBeta()&&product.featuredMedia?React.createElement(FeaturedMedia,{type:product.featuredMedia.type,url:product.featuredMedia.url,altText:product.featuredMedia.altText}):React.createElement(ProductImage,{src:product.featuredImageBaseUrl,resolutions:gridResolutions,alt:product.name,itemProp:"image"}),React.createElement(ProductBadges,{location:location,productId:product.id},!!(!hidePrice&&product.price.discount)&&React.createElement("div",{className:styles.badgeWrapper},React.createElement(SurroundPortals,{portalName:PRODUCT_ITEM_DISCOUNT,portalProps:{productId:product.id}},React.createElement(DiscountBadge,{text:"-".concat(product.price.discount,"%")})))),!(hidePrice&&hideRating&&hideName)&&React.createElement("div",{className:"".concat(styles.details," engage__product-card__information")},showRatings&&React.createElement(RatingStars,{value:product.rating.average}),React.createElement(Swatches,{productId:product.id}),!hideName&&React.createElement(ProductName,{name:product.name,className:styles.title,testId:"Productname: ".concat(product.name),itemProp:"name",rows:titleRows||3}),React.createElement(MapPriceHint,{productId:product.id}),React.createElement(OrderQuantityHint,{productId:product.id}),!hidePrice&&React.createElement(SurroundPortals,{portalName:PRODUCT_ITEM_PRICE,portalProps:{productId:product.id,location:location}},React.createElement(ProductGridPrice,{product:product}))));}/**
|
|
12
12
|
* After a refactoring of the Theme API ProductCard component, this component replaced a
|
|
13
13
|
* sub-component of the ProductCard.
|
|
14
14
|
* The original implementation exposed a couple of sub-components that don't exist in the new
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
function _extends(){_extends=Object.assign||function(target){for(var i=1;i<arguments.length;i++){var source=arguments[i];for(var key in source){if(Object.prototype.hasOwnProperty.call(source,key)){target[key]=source[key];}}}return target;};return _extends.apply(this,arguments);}import React,{useMemo,memo}from'react';import PropTypes from'prop-types';import{MapPriceHint,OrderQuantityHint,EffectivityDates,Swatches,AVAILABILITY_STATE_OK,AVAILABILITY_STATE_ALERT,getProductRoute}from'@shopgate/engage/product';import{hasNewServices as checkHasNewServices,i18n}from'@shopgate/engage/core/helpers';import{Availability,Link}from'@shopgate/engage/components';import{StockInfoLists}from'@shopgate/engage/locations/components';import{makeStyles}from'@shopgate/engage/styles';import ItemName from"../ItemName";import ItemPrice from"../ItemPrice";var useStyles=makeStyles()({root:{lineHeight:1.2,':not(:empty)':{padding:'12px 0 16px'}},quantityHint:{paddingTop:8}});/**
|
|
2
|
+
* The Product Grid Item Detail component.
|
|
3
|
+
* @param {Object} props The component props.
|
|
4
|
+
* @param {Object} props.product The product.
|
|
5
|
+
* @param {Object} props.display The display object.
|
|
6
|
+
* @param {Object} [props.productListTypeMeta] Optional meta object with data from the product list
|
|
7
|
+
* @returns {JSX.Element}
|
|
8
|
+
*/var ItemDetails=function ItemDetails(_ref){var product=_ref.product,display=_ref.display,productListTypeMeta=_ref.productListTypeMeta;var productId=product.id,_product$name=product.name,name=_product$name===void 0?null:_product$name,_product$stock=product.stock,stock=_product$stock===void 0?null:_product$stock;var _useStyles=useStyles(),classes=_useStyles.classes,cx=_useStyles.cx;var hasNewServices=useMemo(function(){return checkHasNewServices();},[]);if(display&&!display.name&&!display.price&&!display.reviews){return null;}return React.createElement(Link,{className:cx(classes.root,'theme__product-grid__item__item-details'),tabIndex:0,href:getProductRoute(productId),state:_extends({title:product.name},productListTypeMeta)},React.createElement(Swatches,{productId:productId}),React.createElement(ItemName,{display:display,productId:productId,name:name}),React.createElement(MapPriceHint,{productId:productId}),React.createElement(OrderQuantityHint,{productId:productId,className:classes.quantityHint}),React.createElement(EffectivityDates,{productId:productId}),hasNewServices&&React.createElement(React.Fragment,null,React.createElement(Availability,{state:!stock||stock.orderable?AVAILABILITY_STATE_OK:AVAILABILITY_STATE_ALERT,text:i18n.text('product.available.not'),showWhenAvailable:false}),React.createElement(StockInfoLists,{product:product})),React.createElement(ItemPrice,{product:product,display:display}));};ItemDetails.defaultProps={display:null,productListTypeMeta:null};export default memo(ItemDetails);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function _extends(){_extends=Object.assign||function(target){for(var i=1;i<arguments.length;i++){var source=arguments[i];for(var key in source){if(Object.prototype.hasOwnProperty.call(source,key)){target[key]=source[key];}}}return target;};return _extends.apply(this,arguments);}import React from'react';import{shallow}from'enzyme';import{hasNewServices}from'@shopgate/engage/core/helpers';import{Availability}from'@shopgate/engage/components';import{StockInfoLists}from'@shopgate/engage/locations/components';import ItemDetails from"./index";jest.mock('@shopgate/engage/product',function(){return{MapPriceHint:function MapPriceHint(){return null;},OrderQuantityHint:function OrderQuantityHint(){return null;},EffectivityDates:function EffectivityDates(){return null;},Swatches:function Swatches(){return null;},AVAILABILITY_STATE_OK:'AVAILABILITY_STATE_OK',AVAILABILITY_STATE_ALERT:'AVAILABILITY_STATE_ALERT',getProductRoute:jest.fn(function(productId){return"link-to-product/".concat(productId);})};});jest.mock('@shopgate/engage/locations/components',function(){return{StockInfoLists:function StockInfoLists(){return null;}};});jest.mock('@shopgate/engage/core/helpers',function(){return{hasNewServices:jest.fn().mockReturnValue(false),i18n:{text:function text(str){return str;}}};});jest.mock('@shopgate/engage/components');jest.mock('@shopgate/engage/core',function(){return{isIOSTheme:jest.fn().mockReturnValue(true),hasWebBridge:jest.fn().mockReturnValue(false),i18n:{text:function text(_text){return _text;}}};});jest.mock("../ItemName");jest.mock("../ItemPrice");describe('<ItemDetails />',function(){var props={product:{id:'1234',name:'Foo',price:{}}};var display={name:false,price:false,reviews:false};it('should render with minimal props',function(){var wrapper=shallow(React.createElement(ItemDetails,props));expect(wrapper).toMatchSnapshot();expect(wrapper.find(Availability).exists()).toBe(false);expect(wrapper.find(StockInfoLists).exists()).toBe(false);});it('should render additional components with new services',function(){hasNewServices.mockReturnValueOnce(true);var wrapper=shallow(React.createElement(ItemDetails,props));expect(wrapper).toMatchSnapshot();expect(wrapper.find(Availability).exists()).toBe(true);expect(wrapper.find(StockInfoLists).exists()).toBe(true);});it('should not render with display props set',function(){var wrapper=shallow(React.createElement(ItemDetails,_extends({},props,{display:display})));expect(wrapper).toBeEmptyRender();});});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import React,{memo,useMemo}from'react';import PropTypes from'prop-types';import{SurroundPortals,DiscountBadge}from'@shopgate/engage/components';import{PRODUCT_ITEM_DISCOUNT}from'@shopgate/engage/category';import{makeStyles}from'@shopgate/engage/styles';var useStyles=makeStyles()({root:{minWidth:40}});/**
|
|
2
|
+
* The item discount component
|
|
3
|
+
* @param {Object} props The component props.
|
|
4
|
+
* @returns {JSX.Element|null}
|
|
5
|
+
*/var ItemDiscount=function ItemDiscount(_ref){var productId=_ref.productId,discount=_ref.discount;var _useStyles=useStyles(),classes=_useStyles.classes,cx=_useStyles.cx;var portalProps=useMemo(function(){return{productId:productId};},[productId]);if(!discount){return null;}return React.createElement("div",{className:cx(classes.root,'theme__product-grid__item__item-discount')},React.createElement(SurroundPortals,{portalName:PRODUCT_ITEM_DISCOUNT,portalProps:portalProps},React.createElement(DiscountBadge,{text:"-".concat(discount,"%")})));};ItemDiscount.defaultProps={discount:null};export default memo(ItemDiscount);
|