Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(headless commerce SSR): copy common codes/sample between NextJS & Remix #4778

Merged
merged 36 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
7a76abb
add rec provider
fpbrault Dec 6, 2024
88051d9
add components from nextjs sample
fpbrault Dec 6, 2024
ea99cdf
update routes to use new components
fpbrault Dec 6, 2024
9bb627c
Update standalone-search-box.tsx
fpbrault Dec 6, 2024
8a29b93
add searchbar to cart
fpbrault Dec 6, 2024
a5fc660
Add generated files
Dec 6, 2024
3278199
disable recs
fpbrault Dec 16, 2024
c49203b
remove use client
fpbrault Dec 16, 2024
e780050
use context api
fpbrault Dec 16, 2024
8fe54c4
remove unused component
fpbrault Dec 16, 2024
0177ab1
Apply suggestions from code review
fpbrault Dec 16, 2024
e667e89
fix summary
fpbrault Dec 16, 2024
e3fcb22
add didyoumean
fpbrault Dec 16, 2024
3499fe8
add sort on search
fpbrault Dec 16, 2024
78f74e9
add parameter manager
fpbrault Dec 16, 2024
244e061
remove unneeded interface declarations
fpbrault Dec 17, 2024
4fb9437
add todo for location facet
fpbrault Dec 17, 2024
18a419f
enable recs
fpbrault Dec 17, 2024
25b3526
combined rec components
fpbrault Dec 17, 2024
67eedba
use fragment instead of query params
fpbrault Dec 17, 2024
1a3e588
improve searchbox
fpbrault Dec 17, 2024
ff6d826
fix build
fpbrault Dec 17, 2024
f787134
linting
fpbrault Dec 18, 2024
daeb563
Adjust parameter manager for remix
fbeaudoincoveo Dec 20, 2024
3097b65
Add parameter manager to listing pages
fbeaudoincoveo Dec 20, 2024
3a668e8
Store q in query string
fbeaudoincoveo Dec 20, 2024
f1b88d8
Revert faulty changes and use query string instead of hash to store q
fbeaudoincoveo Dec 20, 2024
d033d4a
fix tests
alexprudhomme Jan 2, 2025
a0bfae9
fix param manager problems
alexprudhomme Jan 3, 2025
7ca5dd5
wrong _kind
alexprudhomme Jan 3, 2025
af02a18
Move param manager up
fbeaudoincoveo Jan 7, 2025
3e9eb02
Implement param manager in remix sample
fbeaudoincoveo Jan 7, 2025
47498e6
Fix product view logic
fbeaudoincoveo Jan 7, 2025
0158fa6
Add generated files
Jan 13, 2025
e3e7af2
Merge branch 'master' into KIT-3774
fpbrault Jan 14, 2025
9221c73
Merge branch 'master' into KIT-3774
fpbrault Jan 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 1 addition & 23 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions packages/headless/src/app/commerce-ssr-engine/types/kind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,19 @@ export enum Kind {
ParameterManager = 'PARAMETER_MANAGER',
Recommendations = 'RECOMMENDATIONS',
}

export function createControllerWithKind<TController, TKind extends Kind>(
controller: TController,
kind: TKind
): TController & {_kind: TKind} {
const copy = Object.defineProperties(
{},
Object.getOwnPropertyDescriptors(controller)
);

Object.defineProperty(copy, '_kind', {
value: kind,
});

return copy as TController & {_kind: TKind};
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import {UniversalControllerDefinitionWithProps} from '../../../../app/commerce-ssr-engine/types/common.js';
import {Kind} from '../../../../app/commerce-ssr-engine/types/kind.js';
import {
createControllerWithKind,
Kind,
} from '../../../../app/commerce-ssr-engine/types/kind.js';
import {Cart, buildCart, CartInitialState} from './headless-cart.js';

export type {CartState, CartItem, CartProps} from './headless-cart.js';
Expand Down Expand Up @@ -27,10 +30,8 @@ export function defineCart(): CartDefinition {
standalone: true,
recommendation: true,
buildWithProps: (engine, props) => {
return {
...buildCart(engine, {initialState: props.initialState}),
_kind: Kind.Cart,
};
const controller = buildCart(engine, {initialState: props.initialState});
return createControllerWithKind(controller, Kind.Cart);
},
};
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import {UniversalControllerDefinitionWithProps} from '../../../app/commerce-ssr-engine/types/common.js';
import {Kind} from '../../../app/commerce-ssr-engine/types/kind.js';
import {
createControllerWithKind,
Kind,
} from '../../../app/commerce-ssr-engine/types/kind.js';
import {
Context,
buildContext,
Expand Down Expand Up @@ -29,10 +32,8 @@ export function defineContext(): ContextDefinition {
standalone: true,
recommendation: true,
buildWithProps: (engine, props) => {
return {
...buildContext(engine, {options: props}),
_kind: Kind.Context,
};
const controller = buildContext(engine, {options: props});
return createControllerWithKind(controller, Kind.Context);
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import {
SolutionType,
SubControllerDefinitionWithProps,
} from '../../../../app/commerce-ssr-engine/types/common.js';
import {
createControllerWithKind,
Kind,
} from '../../../../app/commerce-ssr-engine/types/kind.js';
import {CoreEngineNext} from '../../../../app/engine.js';
import {commerceFacetSetReducer as commerceFacetSet} from '../../../../features/commerce/facets/facet-set/facet-set-slice.js';
import {manualNumericFacetReducer as manualNumericFacetSet} from '../../../../features/commerce/facets/numeric-facet/manual-numeric-facet-slice.js';
Expand Down Expand Up @@ -55,18 +59,23 @@ export function defineParameterManager<
if (!loadCommerceProductListingParameterReducers(engine)) {
throw loadReducerError;
}
return buildProductListing(engine).parameterManager({
const controller = buildProductListing(engine).parameterManager({
...props,
excludeDefaultParameters: true,
});

return createControllerWithKind(controller, Kind.ParameterManager);
} else {
if (!loadCommerceSearchParameterReducers(engine)) {
throw loadReducerError;
}
return buildSearch(engine).parameterManager({

const controller = buildSearch(engine).parameterManager({
...props,
excludeDefaultParameters: true,
});

return createControllerWithKind(controller, Kind.ParameterManager);
}
},
} as SubControllerDefinitionWithProps<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import {
recommendationInternalOptionKey,
RecommendationOnlyControllerDefinitionWithProps,
} from '../../../app/commerce-ssr-engine/types/common.js';
import {Kind} from '../../../app/commerce-ssr-engine/types/kind.js';
import {
createControllerWithKind,
Kind,
} from '../../../app/commerce-ssr-engine/types/kind.js';
import {
RecommendationsOptions,
RecommendationsState,
Expand Down Expand Up @@ -52,16 +55,7 @@ export function defineRecommendations(
const controller = buildRecommendations(engine, {
options: {...staticOptions, ...options},
});
const copy = Object.defineProperties(
{},
Object.getOwnPropertyDescriptors(controller)
);

Object.defineProperty(copy, '_kind', {
value: Kind.Recommendations,
});

return copy as typeof controller & {_kind: Kind.Recommendations};
return createControllerWithKind(controller, Kind.Recommendations);
},
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import {useBreadcrumbManager} from '@/lib/commerce-engine';
import {
NumericFacetValue,
DateFacetValue,
CategoryFacetValue,
RegularFacetValue,
LocationFacetValue,
} from '@coveo/headless-react/ssr-commerce';

export default function BreadcrumbManager() {
const {state, methods} = useBreadcrumbManager();

const renderBreadcrumbValue = (
value:
| CategoryFacetValue
| RegularFacetValue
| NumericFacetValue
| DateFacetValue
| LocationFacetValue,
type: string
) => {
switch (type) {
case 'hierarchical':
return (value as CategoryFacetValue).path.join(' > ');
fpbrault marked this conversation as resolved.
Show resolved Hide resolved
case 'regular':
return (value as RegularFacetValue).value;
case 'numericalRange':
return (
(value as NumericFacetValue).start +
' - ' +
(value as NumericFacetValue).end
);
case 'dateRange':
return (
(value as DateFacetValue).start +
' - ' +
(value as DateFacetValue).end
);
default:
// TODO COMHUB-291 support location breadcrumb
return null;
}
};

return (
<div>
<div>
<button onClick={methods?.deselectAll}>Clear all filters</button>
</div>
<ul>
{state.facetBreadcrumbs.map((facetBreadcrumb) => {
return (
<li key={`${facetBreadcrumb.facetId}-breadcrumbs`}>
{facetBreadcrumb.values.map((value, index) => {
return (
<button
key={`${value.value}-breadcrumb-${index}`}
onClick={() => value.deselect()}
>
{facetBreadcrumb.facetDisplayName}:{' '}
{renderBreadcrumbValue(value.value, facetBreadcrumb.type)} X
</button>
);
})}
</li>
);
})}
</ul>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import externalContextService from '@/external-services/external-context-service';
import {useContext, useEngine} from '@/lib/commerce-engine';
import {
CommerceEngine,
ContextOptions,
loadProductListingActions,
loadSearchActions,
} from '@coveo/headless-react/ssr-commerce';
import {LoaderFunctionArgs} from '@remix-run/node';
import {Form, useFetcher, useLoaderData} from '@remix-run/react';
import {useState} from 'react';

export const loader = async ({}: LoaderFunctionArgs) => {
const contextInfo = await externalContextService.getContextInformation();
return contextInfo;
};

export default function ContextDropdown({
useCase,
}: {
useCase?: 'listing' | 'search';
}) {
const {state, methods} = useContext();
const engine = useEngine();
const fetcher = useFetcher();
const serverContext = useLoaderData<typeof loader>();
const [, setContext] = useState<{
language: string;
country: string;
currency: string;
}>(serverContext);

const handleChange = async (e: React.ChangeEvent<HTMLSelectElement>) => {
const [language, country, currency] = e.target.value.split('-');
const newContext = {language, country, currency};
setContext(newContext);
methods?.setLanguage(language);
methods?.setCountry(country);
methods?.setCurrency(currency as ContextOptions['currency']);

fetcher.submit(
{language, country, currency},
{method: 'post', action: '/context/update'}
);

if (useCase === 'search') {
engine?.dispatch(
loadSearchActions(engine as CommerceEngine).executeSearch()
);
} else if (useCase === 'listing') {
engine?.dispatch(
loadProductListingActions(
engine as CommerceEngine
).fetchProductListing()
);
}
};

return (
<div>
Context dropdown:
<Form method="post" action="/context/update">
<select
value={`${state.language}-${state.country}-${state.currency}`}
onChange={handleChange}
>
{externalContextService.getContextOptions().map((association) => (
<option key={association} value={association}>
{association}
</option>
))}
</select>
</Form>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {useDidYouMean} from '@/lib/commerce-engine';

export default function DidYouMean() {
const {state, methods} = useDidYouMean();

if (!state.hasQueryCorrection) {
return null;
}

if (state.wasAutomaticallyCorrected) {
return (
<div>
<p>
No results for <b>{state.originalQuery}</b>
</p>
<p>
Query was automatically corrected to <b>{state.wasCorrectedTo}</b>
</p>
</div>
);
}

return (
<div>
<p>
Search for
<span onClick={() => methods?.applyCorrection()}>
<b>{state.queryCorrection.correctedQuery}</b>
</span>
instead?
</p>
</div>
);
}
Loading
Loading