diff --git a/packages/snap-client/src/Client/Client.ts b/packages/snap-client/src/Client/Client.ts
index cf1dd0aea..94fb90379 100644
--- a/packages/snap-client/src/Client/Client.ts
+++ b/packages/snap-client/src/Client/Client.ts
@@ -188,13 +188,15 @@ export class Client {
siteId: params.siteId || this.globals.siteId,
};
- const [profile, recommendations] = await Promise.all([
+ const [meta, profile, recommendations] = await Promise.all([
+ this.meta({ siteId: params.siteId || '' }),
this.requesters.recommend.getProfile(profileParams),
this.requesters.recommend.batchRecommendations(recommendParams),
]);
return {
...profile,
+ meta,
results: recommendations[0].results,
};
}
diff --git a/packages/snap-client/src/Client/apis/Hybrid.ts b/packages/snap-client/src/Client/apis/Hybrid.ts
index fcd649c0c..97adf0743 100644
--- a/packages/snap-client/src/Client/apis/Hybrid.ts
+++ b/packages/snap-client/src/Client/apis/Hybrid.ts
@@ -149,7 +149,7 @@ export class HybridAPI extends API {
},
tags: {
'on-sale': {
- location: 'left-middle',
+ location: 'left-middle-upper',
component: 'BadgePill',
parameters: {
color: '#0000FF',
diff --git a/packages/snap-client/src/types.ts b/packages/snap-client/src/types.ts
index 432312f26..b476f720c 100644
--- a/packages/snap-client/src/types.ts
+++ b/packages/snap-client/src/types.ts
@@ -1,5 +1,11 @@
import { AppMode } from '@searchspring/snap-toolbox';
-import type { MetaRequestModel, SearchResponseModelResult, SearchRequestModel, AutocompleteRequestModel } from '@searchspring/snapi-types';
+import type {
+ MetaRequestModel,
+ SearchResponseModelResult,
+ SearchRequestModel,
+ AutocompleteRequestModel,
+ MetaResponseModel,
+} from '@searchspring/snapi-types';
export type HTTPHeaders = { [key: string]: string };
@@ -192,4 +198,4 @@ type RecommendationRequestValueFilterModel = {
value: string | number;
};
-export type RecommendCombinedResponseModel = ProfileResponseModel & { results: SearchResponseModelResult[] };
+export type RecommendCombinedResponseModel = ProfileResponseModel & { results: SearchResponseModelResult[] } & { meta: MetaResponseModel };
diff --git a/packages/snap-preact-components/src/components/Atoms/BadgeImage/BadgeImage.stories.tsx b/packages/snap-preact-components/src/components/Atoms/BadgeImage/BadgeImage.stories.tsx
new file mode 100644
index 000000000..70b786d12
--- /dev/null
+++ b/packages/snap-preact-components/src/components/Atoms/BadgeImage/BadgeImage.stories.tsx
@@ -0,0 +1 @@
+// TODO
diff --git a/packages/snap-preact-components/src/components/Atoms/BadgeImage/BadgeImage.test.tsx b/packages/snap-preact-components/src/components/Atoms/BadgeImage/BadgeImage.test.tsx
new file mode 100644
index 000000000..70b786d12
--- /dev/null
+++ b/packages/snap-preact-components/src/components/Atoms/BadgeImage/BadgeImage.test.tsx
@@ -0,0 +1 @@
+// TODO
diff --git a/packages/snap-preact-components/src/components/Atoms/BadgeImage/BadgeImage.tsx b/packages/snap-preact-components/src/components/Atoms/BadgeImage/BadgeImage.tsx
new file mode 100644
index 000000000..f8dd441bf
--- /dev/null
+++ b/packages/snap-preact-components/src/components/Atoms/BadgeImage/BadgeImage.tsx
@@ -0,0 +1,61 @@
+/** @jsx jsx */
+import { Fragment, h } from 'preact';
+
+import { jsx, css } from '@emotion/react';
+import classnames from 'classnames';
+import { observer } from 'mobx-react-lite';
+
+import { Theme, useTheme, CacheProvider } from '../../../providers';
+import { ComponentProps, StylingCSS } from '../../../types';
+import type { ResultBadge, OverlayResultBadge } from '@searchspring/snap-store-mobx';
+
+const CSS = {
+ BadgeImage: ({}: BadgeImageProps) => {
+ return css({});
+ },
+};
+
+export const BadgeImage = observer((properties: BadgeImageProps): JSX.Element => {
+ const globalTheme: Theme = useTheme();
+
+ const props: BadgeImageProps = {
+ // default props
+ // global theme
+ ...globalTheme?.components?.badgeImage,
+ // props
+ ...properties,
+ ...properties.theme?.components?.badgeImage,
+ };
+ const { badge, disableStyles, className, style } = props;
+
+ const styling: { css?: StylingCSS } = {};
+
+ if (!disableStyles) {
+ styling.css = [CSS.BadgeImage(props), style];
+ } else if (style) {
+ styling.css = [style];
+ }
+
+ return badge?.parameters?.url ? (
+
+
+
+ ) : (
+
+ );
+});
+
+export interface BadgeImageProps extends ComponentProps {
+ badge: ResultBadge | OverlayResultBadge;
+}
diff --git a/packages/snap-preact-components/src/components/Atoms/BadgeImage/index.ts b/packages/snap-preact-components/src/components/Atoms/BadgeImage/index.ts
new file mode 100644
index 000000000..bd28ae6ca
--- /dev/null
+++ b/packages/snap-preact-components/src/components/Atoms/BadgeImage/index.ts
@@ -0,0 +1 @@
+export * from './BadgeImage';
diff --git a/packages/snap-preact-components/src/components/Atoms/BadgeImage/readme.md b/packages/snap-preact-components/src/components/Atoms/BadgeImage/readme.md
new file mode 100644
index 000000000..8ecd13918
--- /dev/null
+++ b/packages/snap-preact-components/src/components/Atoms/BadgeImage/readme.md
@@ -0,0 +1 @@
+# BadgeImage // TODO
diff --git a/packages/snap-preact-components/src/components/Atoms/BadgePill/BadgePill.stories.tsx b/packages/snap-preact-components/src/components/Atoms/BadgePill/BadgePill.stories.tsx
new file mode 100644
index 000000000..70b786d12
--- /dev/null
+++ b/packages/snap-preact-components/src/components/Atoms/BadgePill/BadgePill.stories.tsx
@@ -0,0 +1 @@
+// TODO
diff --git a/packages/snap-preact-components/src/components/Atoms/BadgePill/BadgePill.test.tsx b/packages/snap-preact-components/src/components/Atoms/BadgePill/BadgePill.test.tsx
new file mode 100644
index 000000000..70b786d12
--- /dev/null
+++ b/packages/snap-preact-components/src/components/Atoms/BadgePill/BadgePill.test.tsx
@@ -0,0 +1 @@
+// TODO
diff --git a/packages/snap-preact-components/src/components/Atoms/BadgePill/BadgePill.tsx b/packages/snap-preact-components/src/components/Atoms/BadgePill/BadgePill.tsx
new file mode 100644
index 000000000..498d7eae2
--- /dev/null
+++ b/packages/snap-preact-components/src/components/Atoms/BadgePill/BadgePill.tsx
@@ -0,0 +1,68 @@
+/** @jsx jsx */
+import { Fragment, h } from 'preact';
+
+import { jsx, css } from '@emotion/react';
+import classnames from 'classnames';
+import { observer } from 'mobx-react-lite';
+
+import { Theme, useTheme, CacheProvider } from '../../../providers';
+import { ComponentProps, StylingCSS } from '../../../types';
+import type { ResultBadge, OverlayResultBadge } from '@searchspring/snap-store-mobx';
+
+const CSS = {
+ BadgePill: (props: BadgePillProps) => {
+ const { parameters } = props.badge;
+ const { color, colorText } = parameters;
+ return css({
+ padding: '0.2em 0.5em',
+
+ background: color || 'rgba(255, 255, 255, 0.5)',
+ color: colorText || '#000000',
+ });
+ },
+};
+
+export const BadgePill = observer((properties: BadgePillProps): JSX.Element => {
+ const globalTheme: Theme = useTheme();
+
+ const props: BadgePillProps = {
+ // default props
+ // global theme
+ ...globalTheme?.components?.badgePill,
+ // props
+ ...properties,
+ ...properties.theme?.components?.badgePill,
+ };
+ const { badge, disableStyles, className, style } = props;
+
+ const styling: { css?: StylingCSS } = {};
+
+ if (!disableStyles) {
+ styling.css = [CSS.BadgePill(props), style];
+ } else if (style) {
+ styling.css = [style];
+ }
+
+ return badge ? (
+
+
+ {badge.label}
+
+
+ ) : (
+
+ );
+});
+
+export interface BadgePillProps extends ComponentProps {
+ badge: ResultBadge | OverlayResultBadge;
+}
diff --git a/packages/snap-preact-components/src/components/Atoms/BadgePill/index.ts b/packages/snap-preact-components/src/components/Atoms/BadgePill/index.ts
new file mode 100644
index 000000000..146bdbb1d
--- /dev/null
+++ b/packages/snap-preact-components/src/components/Atoms/BadgePill/index.ts
@@ -0,0 +1 @@
+export * from './BadgePill';
diff --git a/packages/snap-preact-components/src/components/Atoms/BadgePill/readme.md b/packages/snap-preact-components/src/components/Atoms/BadgePill/readme.md
new file mode 100644
index 000000000..ee0ae2407
--- /dev/null
+++ b/packages/snap-preact-components/src/components/Atoms/BadgePill/readme.md
@@ -0,0 +1,2 @@
+# BadgePill // TODO
+
diff --git a/packages/snap-preact-components/src/components/Atoms/SelfServeBadge/SelfServeBadge.stories.tsx b/packages/snap-preact-components/src/components/Atoms/SelfServeBadge/SelfServeBadge.stories.tsx
deleted file mode 100644
index e69de29bb..000000000
diff --git a/packages/snap-preact-components/src/components/Atoms/SelfServeBadge/SelfServeBadge.test.tsx b/packages/snap-preact-components/src/components/Atoms/SelfServeBadge/SelfServeBadge.test.tsx
deleted file mode 100644
index 211741201..000000000
--- a/packages/snap-preact-components/src/components/Atoms/SelfServeBadge/SelfServeBadge.test.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-describe('Result Component', () => {
- it('todo', () => {
- expect(1).toBe(1);
- });
-});
diff --git a/packages/snap-preact-components/src/components/Atoms/SelfServeBadge/SelfServeBadge.tsx b/packages/snap-preact-components/src/components/Atoms/SelfServeBadge/SelfServeBadge.tsx
deleted file mode 100644
index 64de2e0c3..000000000
--- a/packages/snap-preact-components/src/components/Atoms/SelfServeBadge/SelfServeBadge.tsx
+++ /dev/null
@@ -1,257 +0,0 @@
-/** @jsx jsx */
-import { ComponentChildren, Fragment, h } from 'preact';
-
-import { jsx, css } from '@emotion/react';
-import type { CSSObject } from '@emotion/serialize';
-import classnames from 'classnames';
-import { observer } from 'mobx-react-lite';
-
-import { Theme, useTheme, CacheProvider } from '../../../providers';
-import { ComponentProps, StylingCSS } from '../../../types';
-
-import type { AbstractController, SearchController } from '@searchspring/snap-controller';
-import type { Product } from '@searchspring/snap-store-mobx';
-
-const positioningStyles = ({ type, overlayLocation, location, overlayLocationOptions }: SelfServeBadgeProps): CSSObject => {
- let styles: CSSObject = {
- [overlayLocation]: 0, // left or right
- };
-
- if (type === 'callout') {
- styles = { ...styles, display: 'inline-block' };
- } else if (type === 'overlay') {
- styles = { ...styles, position: 'absolute' };
-
- const index = overlayLocationOptions.findIndex((option: any) => option.name === location);
- if (index === 0) {
- // first option, assume top
- styles = { ...styles, top: 0 };
- } else if (index === overlayLocationOptions.length - 1) {
- // last in list, assume bottom
- styles = { ...styles, bottom: 0 };
- } else if (index > 0 && index < overlayLocationOptions.length - 1) {
- // middle options, calculate percentage based on position in list
- const percentage = (index / (overlayLocationOptions.length - 1)) * 100;
- styles = { ...styles, top: `${percentage}%`, transform: `translateY(-${percentage}%)`, margin: 'auto' };
- }
- }
-
- return styles;
-};
-const CSS = {
- selfServeBadge: ({ type }: SelfServeBadgeProps) =>
- css({
- position: type === 'overlay' ? 'relative' : undefined,
- }),
- BadgePill: (props: SelfServeBadgeProps) => {
- const { parameters } = props;
- const { color, colorText } = parameters;
- return css({
- ...positioningStyles(props),
-
- padding: '0.2em 0.5em',
-
- background: color || 'rgba(255, 255, 255, 0.5)',
- color: colorText || '#000000',
- });
- },
- BadgeImage: (props: SelfServeBadgeProps) => {
- const { parameters } = props;
- const { color, colorText } = parameters;
- return css({
- ...positioningStyles(props),
-
- background: color || 'rgba(255, 255, 255, 0.5)',
- color: colorText || '#000000',
- });
- },
-};
-
-export const SelfServeBadge = observer((properties: SelfServeBadgeProps): JSX.Element => {
- const globalTheme: Theme = useTheme();
-
- const props: SelfServeBadgeProps = {
- // default props
- callout: properties.children ? '' : 'callout',
- type: properties.children ? 'overlay' : 'callout',
- // global theme
- ...globalTheme?.components?.badge,
- // props
- ...properties,
- ...properties.theme?.components?.badge,
- };
- const { result, callout, type, children, controller, disableStyles, className, style } = props;
-
- const styling: { css?: StylingCSS } = {};
-
- // {
- // tag: 'on-sale',
- // label: '30% Off',
- // component: '',
- // location: 'left',
- // parameters: {
- // color: '#0000FF',
- // colorText: '#FFFFFF',
- // url: '',
- // },
- // }
-
- // locations: {
- // overlay: {
- // [position: string]: {
- // name: string;
- // label: string;
- // description: string;
- // }[];
- // };
- // callouts: {
- // name: string;
- // label: string;
- // description: string;
- // }[];
- // };
-
- if (!disableStyles) {
- styling.css = [CSS.selfServeBadge(props), style];
- } else if (style) {
- styling.css = [style];
- }
-
- const resultBadges = result.badges;
- const locations = (controller?.store as any)?.meta?.badges?.locations;
- if (!resultBadges?.length || !locations) {
- return {children};
- }
-
- const callouts = locations?.callouts;
- const overlay = locations?.overlay;
-
- const calloutBadge = resultBadges.find((badge: any) => {
- return callouts?.some((callout: any) => callout.name === badge.location) && badge.location === callout;
- });
-
- const overlayBadges =
- resultBadges
- ?.map((badge: any) => {
- // add overlayLocation property to badge to be used in styling
- const isRightOverlay = overlay.right.some((rightOverlays: any) => rightOverlays.name === badge.location);
- const isLeftOverlay = overlay.left.some((leftOverlays: any) => leftOverlays.name === badge.location);
- const overlayLocation = isRightOverlay ? 'right' : isLeftOverlay ? 'left' : '';
-
- return {
- ...badge,
- overlayLocation,
- overlayLocationOptions: overlay[overlayLocation],
- };
- })
- .filter((badge: any) => {
- // filter out badges that are not overlay badges
- return badge.overlayLocation;
- }) || [];
-
- if (calloutBadge && type === 'callout') {
- return (
-
-
- {(() => {
- switch (calloutBadge.component) {
- case 'BadgePill':
- return ;
- case 'BadgeImage':
- return ;
- default:
- return;
- }
- })()}
-
-
- );
- }
-
- if (overlayBadges.length && type === 'overlay') {
- console.log('overlayBadges', overlayBadges);
- return (
-
-
- {children}
- {overlayBadges.map((badge: any) => {
- switch (badge.component) {
- case 'BadgePill':
- return ;
- case 'BadgeImage':
- return ;
- default:
- return;
- }
- })}
-
-
- );
- }
-
- return {children};
-});
-
-const BadgePill = ({ badge, parentProps, type }: any) => {
- const styling: { css?: StylingCSS } = {};
- if (!parentProps.disableStyles) {
- styling.css = [CSS.BadgePill({ ...parentProps, ...badge, type }), parentProps.style];
- } else if (parentProps.style) {
- styling.css = [parentProps.style];
- }
-
- return (
-
- {badge.label}
-
- );
-};
-
-const BadgeImage = ({ badge, parentProps, type }: any) => {
- const styling: { css?: StylingCSS } = {};
- if (!parentProps.disableStyles) {
- styling.css = [CSS.BadgeImage({ ...parentProps, ...badge, type }), parentProps.style];
- } else if (parentProps.style) {
- styling.css = [parentProps.style];
- }
- return (
-
- );
-};
-
-export interface SelfServeBadgeProps extends ComponentProps {
- result: Product;
- controller?: AbstractController | SearchController;
-
- children?: ComponentChildren;
- callout?: string;
-}
diff --git a/packages/snap-preact-components/src/components/Atoms/SelfServeBadge/index.ts b/packages/snap-preact-components/src/components/Atoms/SelfServeBadge/index.ts
deleted file mode 100644
index 1eebea509..000000000
--- a/packages/snap-preact-components/src/components/Atoms/SelfServeBadge/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './SelfServeBadge';
diff --git a/packages/snap-preact-components/src/components/Atoms/SelfServeBadge/readme.md b/packages/snap-preact-components/src/components/Atoms/SelfServeBadge/readme.md
deleted file mode 100644
index c19bf3e99..000000000
--- a/packages/snap-preact-components/src/components/Atoms/SelfServeBadge/readme.md
+++ /dev/null
@@ -1,2 +0,0 @@
-# SelfServeBadge
-
diff --git a/packages/snap-preact-components/src/components/Molecules/CalloutBadge/CalloutBadge.stories.tsx b/packages/snap-preact-components/src/components/Molecules/CalloutBadge/CalloutBadge.stories.tsx
new file mode 100644
index 000000000..70b786d12
--- /dev/null
+++ b/packages/snap-preact-components/src/components/Molecules/CalloutBadge/CalloutBadge.stories.tsx
@@ -0,0 +1 @@
+// TODO
diff --git a/packages/snap-preact-components/src/components/Molecules/CalloutBadge/CalloutBadge.test.tsx b/packages/snap-preact-components/src/components/Molecules/CalloutBadge/CalloutBadge.test.tsx
new file mode 100644
index 000000000..70b786d12
--- /dev/null
+++ b/packages/snap-preact-components/src/components/Molecules/CalloutBadge/CalloutBadge.test.tsx
@@ -0,0 +1 @@
+// TODO
diff --git a/packages/snap-preact-components/src/components/Molecules/CalloutBadge/CalloutBadge.tsx b/packages/snap-preact-components/src/components/Molecules/CalloutBadge/CalloutBadge.tsx
new file mode 100644
index 000000000..bcb53e832
--- /dev/null
+++ b/packages/snap-preact-components/src/components/Molecules/CalloutBadge/CalloutBadge.tsx
@@ -0,0 +1,69 @@
+/** @jsx jsx */
+import { Fragment, h } from 'preact';
+
+import { jsx, css } from '@emotion/react';
+import classnames from 'classnames';
+import { observer } from 'mobx-react-lite';
+
+import { Theme, useTheme, CacheProvider } from '../../../providers';
+import { ComponentProps, StylingCSS, ComponentMap } from '../../../types';
+import { defaultBadgeComponentMap } from '../../../utilities';
+
+import type { AutocompleteController, RecommendationController, SearchController } from '@searchspring/snap-controller';
+import type { Product } from '@searchspring/snap-store-mobx';
+
+const CSS = {
+ CalloutBadge: ({}: CalloutBadgeProps) =>
+ css({
+ display: 'inline-block',
+ }),
+};
+
+export const CalloutBadge = observer((properties: CalloutBadgeProps): JSX.Element => {
+ const globalTheme: Theme = useTheme();
+
+ const props: CalloutBadgeProps = {
+ // default props
+ // global theme
+ ...globalTheme?.components?.calloutBadge,
+ // props
+ ...properties,
+ ...properties.theme?.components?.calloutBadge,
+ };
+ const { result, name, controller, disableStyles, className, style } = props;
+
+ const styling: { css?: StylingCSS } = {};
+
+ const badgeComponentMap = Object.assign(defaultBadgeComponentMap, props.componentMap || {});
+
+ if (!disableStyles) {
+ styling.css = [CSS.CalloutBadge(props), style];
+ } else if (style) {
+ styling.css = [style];
+ }
+
+ const calloutBadge = controller.store.badges.getCalloutBadge(result, name);
+
+ if (calloutBadge) {
+ const BadgeComponent = badgeComponentMap[calloutBadge.component];
+ if (!BadgeComponent) {
+ controller?.log?.warn(`Badge component not found for ${calloutBadge.component}`);
+ return ;
+ }
+ return (
+
+
+
+
+
+ );
+ }
+ return ;
+});
+
+export interface CalloutBadgeProps extends ComponentProps {
+ result: Product;
+ controller: SearchController | AutocompleteController | RecommendationController;
+ name: string;
+ componentMap?: ComponentMap;
+}
diff --git a/packages/snap-preact-components/src/components/Molecules/CalloutBadge/index.ts b/packages/snap-preact-components/src/components/Molecules/CalloutBadge/index.ts
new file mode 100644
index 000000000..f2a6241d3
--- /dev/null
+++ b/packages/snap-preact-components/src/components/Molecules/CalloutBadge/index.ts
@@ -0,0 +1 @@
+export * from './CalloutBadge';
diff --git a/packages/snap-preact-components/src/components/Molecules/CalloutBadge/readme.md b/packages/snap-preact-components/src/components/Molecules/CalloutBadge/readme.md
new file mode 100644
index 000000000..3663814b6
--- /dev/null
+++ b/packages/snap-preact-components/src/components/Molecules/CalloutBadge/readme.md
@@ -0,0 +1,2 @@
+# CalloutBadge // TODO
+
diff --git a/packages/snap-preact-components/src/components/Molecules/OverlayBadge/OverlayBadge.stories.tsx b/packages/snap-preact-components/src/components/Molecules/OverlayBadge/OverlayBadge.stories.tsx
new file mode 100644
index 000000000..70b786d12
--- /dev/null
+++ b/packages/snap-preact-components/src/components/Molecules/OverlayBadge/OverlayBadge.stories.tsx
@@ -0,0 +1 @@
+// TODO
diff --git a/packages/snap-preact-components/src/components/Molecules/OverlayBadge/OverlayBadge.test.tsx b/packages/snap-preact-components/src/components/Molecules/OverlayBadge/OverlayBadge.test.tsx
new file mode 100644
index 000000000..70b786d12
--- /dev/null
+++ b/packages/snap-preact-components/src/components/Molecules/OverlayBadge/OverlayBadge.test.tsx
@@ -0,0 +1 @@
+// TODO
diff --git a/packages/snap-preact-components/src/components/Molecules/OverlayBadge/OverlayBadge.tsx b/packages/snap-preact-components/src/components/Molecules/OverlayBadge/OverlayBadge.tsx
new file mode 100644
index 000000000..f07566719
--- /dev/null
+++ b/packages/snap-preact-components/src/components/Molecules/OverlayBadge/OverlayBadge.tsx
@@ -0,0 +1,105 @@
+/** @jsx jsx */
+import { ComponentChildren, Fragment, h } from 'preact';
+
+import { jsx, css } from '@emotion/react';
+import type { CSSObject } from '@emotion/serialize';
+import classnames from 'classnames';
+import { observer } from 'mobx-react-lite';
+
+import { Theme, useTheme, CacheProvider } from '../../../providers';
+import { ComponentProps, StylingCSS, ComponentMap } from '../../../types';
+import { defaultBadgeComponentMap } from '../../../utilities';
+
+import type { AutocompleteController, RecommendationController, SearchController } from '@searchspring/snap-controller';
+import type { Product, OverlayResultBadge } from '@searchspring/snap-store-mobx';
+
+const CSS = {
+ OverlayBadge: ({}: OverlayBadgeProps) => {
+ return css({
+ position: 'relative',
+ });
+ },
+ BadgePositioning: (props: OverlayResultBadge) => {
+ const { location, overlayLocation, overlayLocationOptions } = props;
+ let positioningStyles: CSSObject = {};
+
+ const index = overlayLocationOptions.findIndex((option: any) => option.name === location);
+ if (index === 0) {
+ // first option, assume top
+ positioningStyles = { top: 0 };
+ } else if (index === overlayLocationOptions.length - 1) {
+ // last in list, assume bottom
+ positioningStyles = { bottom: 0 };
+ } else if (index > 0 && index < overlayLocationOptions.length - 1) {
+ // middle options, calculate percentage based on position in list
+ const percentage = (index / (overlayLocationOptions.length - 1)) * 100;
+ positioningStyles = { top: `${percentage}%`, transform: `translateY(-${percentage}%)`, margin: 'auto' };
+ }
+
+ return css({
+ [overlayLocation]: 0, // left or right
+ position: 'absolute',
+ maxWidth: '50%',
+ boxSizing: 'border-box',
+ ...positioningStyles,
+ });
+ },
+};
+
+export const OverlayBadge = observer((properties: OverlayBadgeProps): JSX.Element => {
+ const globalTheme: Theme = useTheme();
+
+ const props: OverlayBadgeProps = {
+ // default props
+ // global theme
+ ...globalTheme?.components?.overlayBadge,
+ // props
+ ...properties,
+ ...properties.theme?.components?.overlayBadge,
+ };
+ const { result, children, controller, disableStyles, className, style } = props;
+
+ const styling: { css?: StylingCSS } = {};
+
+ if (!children) {
+ controller?.log?.warn('OverlayBadge component must have children');
+ return ;
+ }
+
+ const badgeComponentMap = Object.assign(defaultBadgeComponentMap, props.componentMap || {});
+
+ if (!disableStyles) {
+ styling.css = [CSS.OverlayBadge(props), style];
+ } else if (style) {
+ styling.css = [style];
+ }
+
+ const overlayBadges = controller.store.badges.getOverlayBadges(result);
+
+ if (overlayBadges?.length) {
+ return (
+
+
+ {children}
+ {overlayBadges.map((badge) => {
+ const BadgeComponent = badgeComponentMap[badge?.component];
+ if (!BadgeComponent) {
+ controller?.log?.warn(`Badge component not found for ${badge?.component}`);
+ return;
+ }
+ return ;
+ })}
+
+
+ );
+ }
+
+ return {children};
+});
+
+export interface OverlayBadgeProps extends ComponentProps {
+ result: Product;
+ controller: SearchController | AutocompleteController | RecommendationController;
+ children: ComponentChildren;
+ componentMap?: ComponentMap;
+}
diff --git a/packages/snap-preact-components/src/components/Molecules/OverlayBadge/index.ts b/packages/snap-preact-components/src/components/Molecules/OverlayBadge/index.ts
new file mode 100644
index 000000000..efbc84f73
--- /dev/null
+++ b/packages/snap-preact-components/src/components/Molecules/OverlayBadge/index.ts
@@ -0,0 +1 @@
+export * from './OverlayBadge';
diff --git a/packages/snap-preact-components/src/components/Molecules/OverlayBadge/readme.md b/packages/snap-preact-components/src/components/Molecules/OverlayBadge/readme.md
new file mode 100644
index 000000000..8d3bb0960
--- /dev/null
+++ b/packages/snap-preact-components/src/components/Molecules/OverlayBadge/readme.md
@@ -0,0 +1,2 @@
+# OverlayBadge // TODO
+
diff --git a/packages/snap-preact-components/src/components/Molecules/Result/Result.tsx b/packages/snap-preact-components/src/components/Molecules/Result/Result.tsx
index c7f8935de..64f125ffa 100644
--- a/packages/snap-preact-components/src/components/Molecules/Result/Result.tsx
+++ b/packages/snap-preact-components/src/components/Molecules/Result/Result.tsx
@@ -6,13 +6,13 @@ import { jsx, css } from '@emotion/react';
import classnames from 'classnames';
import { Image, ImageProps } from '../../Atoms/Image';
-import { BadgeProps } from '../../Atoms/Badge';
import { Price, PriceProps } from '../../Atoms/Price';
import { Theme, useTheme, CacheProvider } from '../../../providers';
import { defined, cloneWithProps } from '../../../utilities';
import { filters } from '@searchspring/snap-toolbox';
import { ComponentProps, LayoutType, Layout, StylingCSS } from '../../../types';
-import { SelfServeBadge } from '../../Atoms/SelfServeBadge';
+import { CalloutBadge, CalloutBadgeProps } from '../../Molecules/CalloutBadge';
+import { OverlayBadge, OverlayBadgeProps } from '../../Molecules/OverlayBadge';
import type { SearchController, AutocompleteController, RecommendationController } from '@searchspring/snap-controller';
import type { Product } from '@searchspring/snap-store-mobx';
@@ -104,12 +104,23 @@ export const Result = observer((properties: ResultProps): JSX.Element => {
// component theme overrides
theme: props.theme,
},
- badge: {
+ calloutBadge: {
// default props
- className: 'ss__result__badge',
- content: 'Sale',
+ className: 'ss__result__callout-badge',
// global theme
- ...globalTheme?.components?.badge,
+ ...globalTheme?.components?.calloutBadge,
+ // inherited props
+ ...defined({
+ disableStyles,
+ }),
+ // component theme overrides
+ theme: props.theme,
+ },
+ overlayBadge: {
+ // default props
+ className: 'ss__result__overlay-badge',
+ // global theme
+ ...globalTheme?.components?.overlayBadge,
// inherited props
...defined({
disableStyles,
@@ -134,7 +145,6 @@ export const Result = observer((properties: ResultProps): JSX.Element => {
},
};
- // const onSale = Boolean(core?.msrp && core.price && core?.msrp * 1 > core?.price * 1);
let displayName = core?.name;
if (props.truncateTitle) {
displayName = filters.truncate(core?.name || '', props.truncateTitle.limit, props.truncateTitle.append);
@@ -147,41 +157,48 @@ export const Result = observer((properties: ResultProps): JSX.Element => {
styling.css = [style];
}
+ const ImageWrapper = () => {
+ return (
+
+ );
+ };
+
return core ? (
{!hideBadge ? (
-
-
-
+
+
+
) : (
-
+
)}
+ {!hideBadge && (
+
+ )}
{!hideTitle && (
)}
- {!hideBadge &&
}
{cloneWithProps(detailSlot, { result })}
@@ -218,7 +234,8 @@ export const Result = observer((properties: ResultProps): JSX.Element => {
});
interface ResultSubProps {
- badge: BadgeProps;
+ calloutBadge: CalloutBadgeProps;
+ overlayBadge: OverlayBadgeProps;
price: PriceProps;
image: ImageProps;
}
diff --git a/packages/snap-preact-components/src/index.ts b/packages/snap-preact-components/src/index.ts
index 5871db84e..604416d5a 100644
--- a/packages/snap-preact-components/src/index.ts
+++ b/packages/snap-preact-components/src/index.ts
@@ -10,12 +10,12 @@ export * from './components/Atoms/Loading';
export * from './components/Atoms/Merchandising';
export * from './components/Atoms/Overlay';
export * from './components/Atoms/Price';
-export * from './components/Atoms/SelfServeBadge';
export * from './components/Atoms/Skeleton';
export * from './components/Trackers/Recommendation/ResultTracker';
export * from './components/Trackers/Recommendation/ProfileTracker';
// MOLECULES
+export * from './components/Molecules/CalloutBadge';
export * from './components/Molecules/Carousel';
export * from './components/Molecules/Checkbox';
export * from './components/Molecules/ErrorHandler';
@@ -24,6 +24,7 @@ export * from './components/Molecules/FacetHierarchyOptions';
export * from './components/Molecules/FacetListOptions';
export * from './components/Molecules/FacetPaletteOptions';
export * from './components/Molecules/Filter';
+export * from './components/Molecules/OverlayBadge';
export * from './components/Molecules/Pagination';
export * from './components/Molecules/Result';
export * from './components/Molecules/Select';
diff --git a/packages/snap-preact-components/src/types.ts b/packages/snap-preact-components/src/types.ts
index 575099dea..052b52173 100644
--- a/packages/snap-preact-components/src/types.ts
+++ b/packages/snap-preact-components/src/types.ts
@@ -9,6 +9,10 @@ export interface ComponentProps extends RenderableProps {
theme?: Theme;
}
+export type ComponentMap = {
+ [key: string]: (args?: any) => JSX.Element;
+};
+
export enum Layout {
GRID = 'grid',
LIST = 'list',
diff --git a/packages/snap-preact-components/src/utilities/defaultBadgeComponentMap.ts b/packages/snap-preact-components/src/utilities/defaultBadgeComponentMap.ts
new file mode 100644
index 000000000..4e8c7f924
--- /dev/null
+++ b/packages/snap-preact-components/src/utilities/defaultBadgeComponentMap.ts
@@ -0,0 +1,11 @@
+import { BadgePill } from '../components/Atoms/BadgePill';
+import { BadgeImage } from '../components/Atoms/BadgeImage';
+import { ComponentMap } from '../types';
+
+export const defaultBadgeComponentMap: ComponentMap = {
+ BadgePill: BadgePill, // TODO: remove these
+ 'searchspring/pill': BadgePill,
+
+ BadgeImage: BadgeImage, // TODO: remove these
+ 'searchspring/image': BadgeImage,
+};
diff --git a/packages/snap-preact-components/src/utilities/index.ts b/packages/snap-preact-components/src/utilities/index.ts
index 18d6190bd..14cea20f7 100644
--- a/packages/snap-preact-components/src/utilities/index.ts
+++ b/packages/snap-preact-components/src/utilities/index.ts
@@ -4,3 +4,4 @@ export * from './componentArgs';
export * from './sprintf';
export * from './LightenDarkenColor';
export * from './shiftColor';
+export * from './defaultBadgeComponentMap';
diff --git a/packages/snap-shared/src/MockData/MockData.ts b/packages/snap-shared/src/MockData/MockData.ts
index 34668d750..8babba093 100644
--- a/packages/snap-shared/src/MockData/MockData.ts
+++ b/packages/snap-shared/src/MockData/MockData.ts
@@ -122,6 +122,7 @@ export class MockData {
const profileFile = `${__dirname}/recommend/profile/${this.config.siteId}/${files?.profileFile || this.config.recommend?.profile}.json`;
const resultsFile = `${__dirname}/recommend/results/${this.config.siteId}/${files?.resultsFile || this.config.recommend?.results}.json`;
return {
+ meta: this.meta(),
profile: getJSON(profileFile).profile,
results: getJSON(resultsFile)[0].results,
};
diff --git a/packages/snap-store-mobx/src/Autocomplete/AutocompleteStore.ts b/packages/snap-store-mobx/src/Autocomplete/AutocompleteStore.ts
index 10ac30008..a91969ddc 100644
--- a/packages/snap-store-mobx/src/Autocomplete/AutocompleteStore.ts
+++ b/packages/snap-store-mobx/src/Autocomplete/AutocompleteStore.ts
@@ -9,6 +9,7 @@ import {
SearchPaginationStore,
SearchSortingStore,
SearchHistoryStore,
+ SearchBadgeStore,
} from '../Search/Stores';
import { StorageStore } from '../Storage/StorageStore';
import {
@@ -22,7 +23,7 @@ import {
import type { AutocompleteResponseModel, MetaResponseModel } from '@searchspring/snapi-types';
import type { TrendingResponseModel } from '@searchspring/snap-client';
-import type { AutocompleteStoreConfig, StoreServices } from '../types';
+import type { AutocompleteStoreConfig, MetaBadges, StoreServices } from '../types';
export class AutocompleteStore extends AbstractStore {
public services: StoreServices;
@@ -39,6 +40,7 @@ export class AutocompleteStore extends AbstractStore {
public storage: StorageStore;
public trending: AutocompleteTrendingStore;
public history: AutocompleteHistoryStore;
+ public badges!: SearchBadgeStore;
constructor(config: AutocompleteStoreConfig, services: StoreServices) {
super(config);
@@ -192,5 +194,7 @@ export class AutocompleteStore extends AbstractStore {
this.pagination = new SearchPaginationStore(this.config, this.services, data.pagination, this.meta);
this.sorting = new SearchSortingStore(this.services, data.sorting || [], data.search || {}, this.meta);
+ // TODO: remove MetaBadges typing once MetaResponseModel is updated
+ this.badges = new SearchBadgeStore(this.meta as MetaResponseModel & { badges: MetaBadges });
}
}
diff --git a/packages/snap-store-mobx/src/Finder/FinderStore.ts b/packages/snap-store-mobx/src/Finder/FinderStore.ts
index 2dd749693..a71f8be84 100644
--- a/packages/snap-store-mobx/src/Finder/FinderStore.ts
+++ b/packages/snap-store-mobx/src/Finder/FinderStore.ts
@@ -11,7 +11,7 @@ import { UrlManager } from '@searchspring/snap-url-manager';
export class FinderStore extends AbstractStore {
public services: StoreServices;
public config!: FinderStoreConfig;
- public meta: MetaResponseModel = {};
+ public meta!: MetaResponseModel;
public storage: StorageStore;
public persistedStorage!: StorageStore;
public pagination!: SearchPaginationStore;
@@ -94,10 +94,10 @@ export class FinderStore extends AbstractStore {
}
}
- public update(data: SearchResponseModel & { meta: MetaResponseModel }, selectedSelections?: SelectedSelection[]): void {
+ public update(data: SearchResponseModel & { meta?: MetaResponseModel }, selectedSelections?: SelectedSelection[]): void {
this.error = undefined;
this.loaded = !!data.pagination;
- this.meta = data.meta;
+ this.meta = data.meta || {};
this.pagination = new SearchPaginationStore(this.config, this.services, data.pagination, this.meta);
this.selections = new FinderSelectionStore(this.config, this.services, {
state: this.state,
diff --git a/packages/snap-store-mobx/src/Recommendation/RecommendationStore.ts b/packages/snap-store-mobx/src/Recommendation/RecommendationStore.ts
index db2445098..5fff9b6d4 100644
--- a/packages/snap-store-mobx/src/Recommendation/RecommendationStore.ts
+++ b/packages/snap-store-mobx/src/Recommendation/RecommendationStore.ts
@@ -1,15 +1,18 @@
import { makeObservable, observable } from 'mobx';
import { AbstractStore } from '../Abstract/AbstractStore';
-import { SearchResultStore } from '../Search/Stores';
+import { SearchBadgeStore, SearchResultStore } from '../Search/Stores';
import { RecommendationProfileStore } from './Stores';
-import type { RecommendationStoreConfig, StoreServices } from '../types';
+import type { MetaBadges, RecommendationStoreConfig, StoreServices } from '../types';
import type { RecommendCombinedResponseModel } from '@searchspring/snap-client';
+import { MetaResponseModel } from '@searchspring/snapi-types';
export class RecommendationStore extends AbstractStore {
public services: StoreServices;
+ public meta!: MetaResponseModel;
public loaded = false;
public profile!: RecommendationProfileStore;
public results!: SearchResultStore;
+ public badges!: SearchBadgeStore;
constructor(config: RecommendationStoreConfig, services: StoreServices) {
super(config);
@@ -32,10 +35,13 @@ export class RecommendationStore extends AbstractStore {
this.update();
}
- public update(data?: RecommendCombinedResponseModel): void {
+ public update(data?: RecommendCombinedResponseModel & { meta?: MetaResponseModel }): void {
this.error = undefined;
this.loaded = !!data?.profile;
+ this.meta = data?.meta || {};
this.profile = new RecommendationProfileStore(this.services, data);
this.results = new SearchResultStore(this.config, this.services, {}, data?.results);
+ // TODO: remove MetaBadges typing once MetaResponseModel is updated
+ this.badges = new SearchBadgeStore(this.meta as MetaResponseModel & { badges: MetaBadges });
}
}
diff --git a/packages/snap-store-mobx/src/Search/SearchStore.ts b/packages/snap-store-mobx/src/Search/SearchStore.ts
index a71b51021..a34eca869 100644
--- a/packages/snap-store-mobx/src/Search/SearchStore.ts
+++ b/packages/snap-store-mobx/src/Search/SearchStore.ts
@@ -1,7 +1,7 @@
import { makeObservable, observable } from 'mobx';
import type { SearchResponseModel, MetaResponseModel } from '@searchspring/snapi-types';
-import type { SearchStoreConfig, StoreServices } from '../types';
+import type { MetaBadges, SearchStoreConfig, StoreServices } from '../types';
import {
SearchMerchandisingStore,
SearchFacetStore,
@@ -11,6 +11,7 @@ import {
SearchSortingStore,
SearchQueryStore,
SearchHistoryStore,
+ SearchBadgeStore,
} from './Stores';
import type { HistoryStoreConfig } from './Stores/SearchHistoryStore';
import { AbstractStore } from '../Abstract/AbstractStore';
@@ -28,6 +29,7 @@ export class SearchStore extends AbstractStore {
public sorting!: SearchSortingStore;
public storage: StorageStore;
public history: SearchHistoryStore;
+ public badges!: SearchBadgeStore;
constructor(config: SearchStoreConfig, services: StoreServices) {
super(config);
@@ -101,5 +103,7 @@ export class SearchStore extends AbstractStore {
this.results = new SearchResultStore(this.config, this.services, this.meta, data?.results || [], data.pagination, data.merchandising);
this.pagination = new SearchPaginationStore(this.config, this.services, data.pagination, this.meta);
this.sorting = new SearchSortingStore(this.services, data?.sorting || [], data?.search || {}, this.meta);
+ // TODO: remove MetaBadges typing once MetaResponseModel is updated
+ this.badges = new SearchBadgeStore(this.meta as MetaResponseModel & { badges: MetaBadges });
}
}
diff --git a/packages/snap-store-mobx/src/Search/Stores/SearchBadgeStore.ts b/packages/snap-store-mobx/src/Search/Stores/SearchBadgeStore.ts
new file mode 100644
index 000000000..6ecdca6c6
--- /dev/null
+++ b/packages/snap-store-mobx/src/Search/Stores/SearchBadgeStore.ts
@@ -0,0 +1,51 @@
+import { MetaResponseModel } from '@searchspring/snapi-types';
+import { Product } from './SearchResultStore';
+import type { MetaBadges, ResultBadge, OverlayResultBadge } from '../../types';
+
+export class SearchBadgeStore {
+ private meta: MetaResponseModel & { badges: MetaBadges };
+
+ constructor(meta: MetaResponseModel & { badges: MetaBadges }) {
+ this.meta = meta;
+ }
+
+ getCalloutBadge(result: Product, name: string): ResultBadge | undefined {
+ const resultBadges = result.badges;
+ const callouts = this.meta?.badges?.locations?.callouts;
+
+ if (!resultBadges?.length || !callouts) {
+ return;
+ }
+
+ return resultBadges.find((badge) => {
+ return callouts?.some((callout) => callout.name === badge.location) && badge.location === name;
+ });
+ }
+
+ getOverlayBadges(result: Product): OverlayResultBadge[] | undefined {
+ const resultBadges = result.badges;
+ const overlay = this.meta.badges?.locations?.overlay;
+
+ if (!resultBadges?.length || !overlay) {
+ return;
+ }
+
+ return resultBadges
+ ?.map((badge) => {
+ // add overlayLocation and overlayLocationOptions properties to be used in styling
+ const isRightOverlay = overlay.right.some((rightOverlays) => rightOverlays.name === badge.location);
+ const isLeftOverlay = overlay.left.some((leftOverlays) => leftOverlays.name === badge.location);
+ const overlayLocation = isRightOverlay ? 'right' : isLeftOverlay ? 'left' : '';
+
+ return {
+ ...badge,
+ overlayLocation,
+ overlayLocationOptions: overlay[overlayLocation as keyof typeof overlay],
+ };
+ })
+ .filter((badge) => {
+ // filter out badges that are not overlay badges
+ return badge.overlayLocation;
+ });
+ }
+}
diff --git a/packages/snap-store-mobx/src/Search/Stores/SearchResultStore.ts b/packages/snap-store-mobx/src/Search/Stores/SearchResultStore.ts
index dd3874446..295e009eb 100644
--- a/packages/snap-store-mobx/src/Search/Stores/SearchResultStore.ts
+++ b/packages/snap-store-mobx/src/Search/Stores/SearchResultStore.ts
@@ -1,6 +1,6 @@
import { makeObservable, observable } from 'mobx';
-import type { SearchStoreConfig, StoreServices, StoreConfigs } from '../../types';
+import type { SearchStoreConfig, StoreServices, StoreConfigs, ResultBadge, MetaBadges } from '../../types';
import type {
SearchResponseModelResult,
SearchResponseModelPagination,
@@ -55,7 +55,7 @@ export class Banner {
public custom = {};
public config: SearchResponseModelMerchandisingContentConfig;
public value: string;
- public badges: any;
+ public badges: ResultBadge[] = [];
constructor(services: StoreServices, banner: SearchResponseModelMerchandisingContentInline) {
this.id = 'ss-ib-' + banner.config!.position!.index;
@@ -70,39 +70,6 @@ export class Banner {
}
}
-export type ResultBadge = {
- tag: string;
- label: string;
-};
-
-export type MetaBadges = {
- locations: {
- overlay: {
- [position: string]: {
- name: string;
- label: string;
- description: string;
- }[];
- };
- callouts: {
- name: string;
- label: string;
- description: string;
- }[];
- };
- tags: {
- [tag: string]: {
- component: string;
- location: string;
- parameters: {
- color: string;
- colorText: string;
- url: string;
- };
- };
- };
-};
-
export class Product {
public type = 'product';
public id: string;
@@ -112,7 +79,7 @@ export class Product {
};
public custom = {};
public children?: Array = [];
- public badges: any;
+ public badges: ResultBadge[] = [];
constructor(services: StoreServices, result: SearchResponseModelResult, metaData: MetaResponseModel) {
this.id = result.id!;
diff --git a/packages/snap-store-mobx/src/Search/Stores/index.ts b/packages/snap-store-mobx/src/Search/Stores/index.ts
index a3939aab1..bb7552ce2 100644
--- a/packages/snap-store-mobx/src/Search/Stores/index.ts
+++ b/packages/snap-store-mobx/src/Search/Stores/index.ts
@@ -6,3 +6,4 @@ export { SearchResultStore, Product, Banner } from './SearchResultStore';
export { SearchSortingStore } from './SearchSortingStore';
export { SearchQueryStore } from './SearchQueryStore';
export { SearchHistoryStore } from './SearchHistoryStore';
+export { SearchBadgeStore } from './SearchBadgeStore';
diff --git a/packages/snap-store-mobx/src/types.ts b/packages/snap-store-mobx/src/types.ts
index 4ed54f770..67d121ca4 100644
--- a/packages/snap-store-mobx/src/types.ts
+++ b/packages/snap-store-mobx/src/types.ts
@@ -127,3 +127,39 @@ export type SelectedSelection = {
export type FinderStoreState = {
persisted: boolean;
};
+
+export type BadgeTag = {
+ location: string;
+ component: string;
+ parameters: {
+ [key: string]: string;
+ };
+};
+export type BadgeLocation = {
+ name: string;
+ label: string;
+ description: string;
+};
+
+export type ResultBadge = BadgeTag & {
+ tag: string;
+ label: string;
+};
+
+export type OverlayResultBadge = ResultBadge & {
+ overlayLocation: string;
+ overlayLocationOptions: BadgeLocation[];
+};
+
+export type MetaBadges = {
+ locations: {
+ overlay: {
+ left: BadgeLocation[];
+ right: BadgeLocation[];
+ };
+ callouts: BadgeLocation[];
+ };
+ tags: {
+ [key: string]: BadgeTag;
+ };
+};