Skip to content

Commit

Permalink
feat(headless/commerce): add basic plp, search and recs use case exam…
Browse files Browse the repository at this point in the history
  • Loading branch information
Spuffynism authored Jun 14, 2024
1 parent 56da0e3 commit 7dcb7ac
Show file tree
Hide file tree
Showing 13 changed files with 324 additions and 12 deletions.
18 changes: 15 additions & 3 deletions packages/headless/doc-parser/use-cases/commerce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ const controllers: ControllerConfiguration[] = [
},
{
initializer: 'buildProductListing',
samplePaths: {},
samplePaths: {
react_fn: [
'packages/samples/headless-react/src/components/commerce/product-listing.fn.tsx',
],
},
},
{
initializer: 'buildProductTemplatesManager',
Expand All @@ -41,7 +45,11 @@ const controllers: ControllerConfiguration[] = [
},
{
initializer: 'buildRecommendations',
samplePaths: {},
samplePaths: {
react_fn: [
'packages/samples/headless-react/src/components/commerce/recommendations.fn.tsx',
],
},
},
{
initializer: 'buildRedirectionTrigger',
Expand All @@ -53,7 +61,11 @@ const controllers: ControllerConfiguration[] = [
},
{
initializer: 'buildSearch',
samplePaths: {},
samplePaths: {
react_fn: [
'packages/samples/headless-react/src/components/commerce/search.fn.tsx',
],
},
},
{
initializer: 'buildSearchBox',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {RecordValue, Schema, StringValue} from '@coveo/bueno';
import {getOrganizationEndpoints} from '../../api/platform-client';
import {CartInitialState} from '../../controllers/commerce/context/cart/headless-cart';
import {ContextOptions} from '../../controllers/commerce/context/headless-context';
import {cartDefinition} from '../../features/commerce/context/cart/cart-validation';
Expand Down Expand Up @@ -38,26 +39,52 @@ export const commerceEngineConfigurationSchema =
}),
});

// TODO KIT-3244: Use a different sample organization
export function getSampleCommerceEngineConfiguration(): CommerceEngineConfiguration {
return {
organizationId: 'fashioncoveodemocomgzh7iep8',
// deepcode ignore HardcodedNonCryptoSecret: Public key freely available for our documentation
accessToken: 'xx149e3ec9-786f-4c6c-b64f-49a403b930de',
accessToken: 'xxc481d5de-16cb-4290-bd78-45345319d94c',
organizationId: 'barcasportsmcy01fvu',
organizationEndpoints: getOrganizationEndpoints(
'barcasportsmcy01fvu',
'dev'
),
analytics: {
trackingId: 'sports',
},
context: {
language: 'en',
country: 'CA',
currency: 'CAD',
country: 'US',
currency: 'USD',
view: {
url: 'https://www.example.com',
url: 'https://sports-dev.barca.group/browse/promotions/skis-boards/surfboards',
referrer: document.referrer,
},
},
cart: {
items: [
{
name: 'Sample Product',
productId: 'SP01057_00001',
quantity: 1,
name: 'Kayaker Canoe',
price: 800,
},
{
productId: 'SP00081_00001',
quantity: 1,
name: 'Bamboo Canoe Paddle',
price: 120,
},
{
productId: 'SP04236_00005',
quantity: 1,
name: 'Eco-Brave Rashguard',
price: 33,
},
{
productId: 'SP04236_00005',
quantity: 1,
price: 100,
productId: 'sample-product-id',
name: 'Eco-Brave Rashguard',
price: 33,
},
],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ describe('buildCommerceEngine', () => {
}

beforeEach(() => {
Object.defineProperty(global, 'document', {
value: {referrer: 'referrer'},
configurable: true,
});
options = {
configuration: getSampleCommerceEngineConfiguration(),
loggerOptions: {level: 'silent'},
Expand Down
1 change: 1 addition & 0 deletions packages/headless/src/commerce.index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type {
CommerceEngineOptions,
} from './app/commerce-engine/commerce-engine';
export {buildCommerceEngine} from './app/commerce-engine/commerce-engine';
export {getSampleCommerceEngineConfiguration} from './app/commerce-engine/commerce-engine-configuration';

export type {
CoreEngine,
Expand Down
17 changes: 17 additions & 0 deletions packages/samples/headless-react/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import {RecommendationPage} from './pages/RecommendationPage';
import {SamlPage} from './pages/SamlPage';
import {SearchPage, SearchPageProps} from './pages/SearchPage';
import {StandaloneSearchBoxPage} from './pages/StandaloneSearchBoxPage';
import {CommerceApp} from './pages/commerce/CommerceApp';
import {ProductListingPage} from './pages/commerce/ProductListingPage';
import {RecommendationsPage} from './pages/commerce/RecommendationsPage';
import {SearchPage as CommerceSearchPage} from './pages/commerce/SearchPage';

function App(props: SearchPageProps) {
const activeNavLink: React.CSSProperties = {color: 'red'};
Expand Down Expand Up @@ -80,6 +84,14 @@ function App(props: SearchPageProps) {
Product Recommendations
</NavLink>
</button>
<button>
<NavLink
to="/commerce"
style={({isActive}) => (isActive ? activeNavLink : {})}
>
Commerce
</NavLink>
</button>
</nav>
<Routes>
<Route path="/recommendation" element={<RecommendationPage />} />
Expand All @@ -95,6 +107,11 @@ function App(props: SearchPageProps) {
path="/product-recommendations"
element={<ProductRecommendationsPage />}
/>
<Route path="/commerce" element={<CommerceApp />}>
<Route path="search" element={<CommerceSearchPage />} />
<Route path="product-listing" element={<ProductListingPage />} />
<Route path="recommendations" element={<RecommendationsPage />} />
</Route>
<Route path="/search-page" element={<SearchPage {...props} />} />
<Route path="/" element={<SearchPage {...props} />} />
</Routes>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {ProductListing as HeadlessProductListing} from '@coveo/headless/commerce';
import {useEffect, useState, FunctionComponent} from 'react';

interface ProductListingProps {
controller: HeadlessProductListing;
}

export const ProductListing: FunctionComponent<ProductListingProps> = (
props
) => {
const {controller} = props;
const [state, setState] = useState(controller.state);

useEffect(() => controller.subscribe(() => setState(controller.state)), []);

if (!state.products.length) {
return <button onClick={() => controller.refresh()}>Refresh</button>;
}

return (
<ul>
{state.products.map(({ec_name, clickUri, permanentid}) => (
<li key={permanentid}>
<a href={clickUri}>{ec_name}</a>
</li>
))}
</ul>
);
};

// usage

/**
* ```tsx
* const controller = buildProductListing(engine);
*
* <ProductListing controller={controller} />;
* ```
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {Recommendations as HeadlessRecommendations} from '@coveo/headless/commerce';
import {useEffect, useState, FunctionComponent} from 'react';

interface RecommendationsProps {
controller: HeadlessRecommendations;
}

export const Recommendations: FunctionComponent<RecommendationsProps> = (
props
) => {
const {controller} = props;
const [state, setState] = useState(controller.state);

useEffect(() => controller.subscribe(() => setState(controller.state)), []);

if (!state.products.length) {
return <button onClick={() => controller.refresh()}>Refresh</button>;
}

return (
<ul>
{state.products.map(({ec_name, clickUri, permanentid}) => (
<li key={permanentid}>
<a href={clickUri}>{ec_name}</a>
</li>
))}
</ul>
);
};

// usage

/**
* ```tsx
* const controller = buildRecommendations(engine);
*
* <Recommendations controller={controller} />;
* ```
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {Search as HeadlessSearch} from '@coveo/headless/commerce';
import {useEffect, useState, FunctionComponent} from 'react';

interface SearchProps {
controller: HeadlessSearch;
}

export const Search: FunctionComponent<SearchProps> = (props) => {
const {controller} = props;
const [state, setState] = useState(controller.state);

useEffect(() => controller.subscribe(() => setState(controller.state)), []);

if (!state.products.length) {
return (
<button onClick={() => controller.executeFirstSearch()}>Refresh</button>
);
}

return (
<ul>
{state.products.map(({ec_name, clickUri, permanentid}) => (
<li key={permanentid}>
<a href={clickUri}>{ec_name}</a>
</li>
))}
</ul>
);
};

// usage

/**
* ```tsx
* const controller = buildSearch(engine);
*
* <Search controller={controller} />;
* ```
*/
2 changes: 2 additions & 0 deletions packages/samples/headless-react/src/context/engine.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {SearchEngine} from '@coveo/headless';
import {CommerceEngine} from '@coveo/headless/commerce';
import {ProductListingEngine} from '@coveo/headless/product-listing';
import {ProductRecommendationEngine} from '@coveo/headless/product-recommendation';
import {RecommendationEngine} from '@coveo/headless/recommendation';
Expand All @@ -9,6 +10,7 @@ export interface AppContextType {
recommendationEngine: RecommendationEngine;
productListingEngine: ProductListingEngine;
productRecommendationEngine: ProductRecommendationEngine;
commerceEngine: CommerceEngine;
}

export const AppContext = createContext<Partial<AppContextType>>({});
41 changes: 41 additions & 0 deletions packages/samples/headless-react/src/pages/commerce/CommerceApp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {NavLink, Outlet} from 'react-router-dom';
import {Section} from '../../layout/section';

export function CommerceApp() {
const activeNavLink: React.CSSProperties = {color: 'red'};

return (
<Section title="commerce">
<nav>
<button>
<NavLink
end
to="/commerce/search"
style={({isActive}) => (isActive ? activeNavLink : {})}
>
Search
</NavLink>
</button>
<button>
<NavLink
end
to="/commerce/product-listing"
style={({isActive}) => (isActive ? activeNavLink : {})}
>
Product Listing
</NavLink>
</button>
<button>
<NavLink
end
to="/commerce/recommendations"
style={({isActive}) => (isActive ? activeNavLink : {})}
>
Recommendations
</NavLink>
</button>
</nav>
<Outlet />
</Section>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {
buildCommerceEngine,
buildProductListing,
getSampleCommerceEngineConfiguration,
} from '@coveo/headless/commerce';
import {useMemo} from 'react';
import {ProductListing} from '../../components/commerce/product-listing.fn';
import {AppContext} from '../../context/engine';
import {Section} from '../../layout/section';

export function ProductListingPage() {
const engine = useMemo(
() =>
buildCommerceEngine({
configuration: getSampleCommerceEngineConfiguration(),
}),
[]
);

const productListing = buildProductListing(engine);

return (
<AppContext.Provider value={{commerceEngine: engine}}>
<Section title="product-listing">
<ProductListing controller={productListing} />
</Section>
</AppContext.Provider>
);
}
Loading

0 comments on commit 7dcb7ac

Please sign in to comment.