Skip to content

Commit

Permalink
feat(commerce): support setting latitude and longitude on context con…
Browse files Browse the repository at this point in the history
  • Loading branch information
Spuffynism authored Oct 24, 2024
1 parent 47a9758 commit 7a33a0b
Show file tree
Hide file tree
Showing 15 changed files with 143 additions and 23 deletions.
2 changes: 2 additions & 0 deletions packages/headless/src/api/commerce/commerce-api-params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export interface ViewParams {

export type UserParams = {
userAgent?: string;
latitude?: number;
longitude?: number;
};

export interface CartItemParam {
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 @@ -78,6 +78,7 @@ export {buildController} from './controllers/controller/headless-controller.js';
export type {
ContextOptions,
View,
UserLocation,
ContextProps,
Context,
ContextState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import {
ContextProps,
ContextOptions,
View,
UserLocation,
} from './headless-context.js';

export type {ContextState, Context, ContextProps} from './headless-context.js';
export type {View, ContextOptions};
export type {View, UserLocation, ContextOptions};

export interface ContextDefinition
extends UniversalControllerDefinitionWithoutProps<Context> {}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
setContext,
setLocation,
setView,
} from '../../../features/commerce/context/context-actions.js';
import {contextReducer} from '../../../features/commerce/context/context-slice.js';
Expand Down Expand Up @@ -82,4 +83,12 @@ describe('headless commerce context', () => {
});
expect(setView).toHaveBeenCalled();
});

it('setLocation dispatches #setLocation', () => {
context.setLocation({
latitude: 27.1127,
longitude: 109.3497,
});
expect(setLocation).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {CommerceEngine} from '../../../app/commerce-engine/commerce-engine.js';
import {stateKey} from '../../../app/state-key.js';
import {
setContext,
setLocation,
setView,
} from '../../../features/commerce/context/context-actions.js';
import {contextReducer as commerceContext} from '../../../features/commerce/context/context-slice.js';
Expand All @@ -19,12 +20,18 @@ export interface ContextOptions {
country: string;
currency: CurrencyCodeISO4217;
view: View;
location?: UserLocation;
}

export interface View {
url: string;
}

export interface UserLocation {
latitude: number;
longitude: number;
}

export interface ContextProps {
/**
* The initial options that should be applied to this `Context` controller.
Expand Down Expand Up @@ -60,6 +67,12 @@ export interface Context extends Controller {
*/
setView(view: View): void;

/**
* Sets the location.
* @param location - The new location.
*/
setLocation(location: UserLocation): void;

/**
* A scoped and simplified part of the headless state that is relevant to the `Context` controller.
*/
Expand All @@ -71,6 +84,7 @@ export interface ContextState {
country: string;
currency: CurrencyCodeISO4217;
view: View;
location?: UserLocation;
}

/**
Expand Down Expand Up @@ -129,6 +143,8 @@ export function buildContext(
),

setView: (view: View) => dispatch(setView(view)),

setLocation: (location: UserLocation) => dispatch(setLocation(location)),
};
}

Expand Down
40 changes: 39 additions & 1 deletion packages/headless/src/features/commerce/common/actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ describe('commerce common actions', () => {
country: 'CA',
currency: 'CAD',
clientId: 'client_id',
page: 0,
context: {
user: {
userAgent: 'user_agent',
Expand Down Expand Up @@ -93,15 +94,26 @@ describe('commerce common actions', () => {
};
});

it('given a basic state, returns the expected base request', () => {
const request = Actions.buildBaseCommerceAPIRequest(
state,
navigatorContext
);

expect(request).toEqual(expected);
});

it('given a state with no commercePagination section, returns the expected base request', () => {
delete state.commercePagination;

const {page, ...expectedWithoutPagination} = expected;

const request = Actions.buildBaseCommerceAPIRequest(
state,
navigatorContext
);

expect(request).toEqual({...expected});
expect(request).toEqual(expectedWithoutPagination);
});

it('given a state that has the commercePagination section, returns expected base request with expected #page and #perPage', () => {
Expand Down Expand Up @@ -155,6 +167,32 @@ describe('commerce common actions', () => {

expect(request).toEqual(expectedWithPagination);
});

it('given a state that has latitude and longitude, returns expected base request with expected #latitude and #longitude', () => {
state.commerceContext.location = {
latitude: 48.8566,
longitude: 2.3522,
};

const expectedWithLatitudeAndLongitude: CommerceAPIRequest = {
...expected,
context: {
...expected.context,
user: {
...expected.context.user,
latitude: state.commerceContext.location.latitude,
longitude: state.commerceContext.location.longitude,
},
},
};

const request = Actions.buildBaseCommerceAPIRequest(
state,
navigatorContext
);

expect(request).toEqual(expectedWithLatitudeAndLongitude);
});
});

describe('#buildCommerceAPIRequest', () => {
Expand Down
15 changes: 7 additions & 8 deletions packages/headless/src/features/commerce/common/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const buildBaseCommerceAPIRequest = (
navigatorContext: NavigatorContext,
slotId?: string
): BaseCommerceAPIRequest => {
const {view, ...restOfContext} = state.commerceContext;
const {view, location, ...restOfContext} = state.commerceContext;
return {
accessToken: state.configuration.accessToken,
url:
Expand All @@ -75,13 +75,12 @@ export const buildBaseCommerceAPIRequest = (
? {clientId: navigatorContext.clientId}
: {}),
context: {
...(navigatorContext.userAgent
? {
user: {
userAgent: navigatorContext.userAgent,
},
}
: {}),
user: {
...location,
...(navigatorContext.userAgent
? {userAgent: navigatorContext.userAgent}
: {}),
},
view: {
...view,
...(navigatorContext.referrer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import {
SetViewPayload,
setContext,
setView,
SetLocationPayload,
setLocation,
} from './context-actions.js';
import {contextReducer as commerceContext} from './context-slice.js';

export type {SetContextPayload, SetViewPayload};
export type {SetContextPayload, SetViewPayload, SetLocationPayload};

/**
* The context action creators.
Expand All @@ -29,6 +31,14 @@ export interface ContextActionCreators {
* @returns A dispatchable action.
*/
setView(payload: SetViewPayload): PayloadAction<SetViewPayload>;

/**
* Sets the location context property without modifying any other context properties.
*
* @param payload - The action creator payload.
* @returns A dispatchable action.
*/
setLocation(payload: SetLocationPayload): PayloadAction<SetLocationPayload>;
}

/**
Expand All @@ -44,5 +54,6 @@ export function loadContextActions(
return {
setContext,
setView,
setLocation,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@ import {CurrencyCodeISO4217} from '@coveo/relay-event-types';
import {createAction} from '@reduxjs/toolkit';
import {ViewParams} from '../../../api/commerce/commerce-api-params.js';
import {validatePayload} from '../../../utils/validate-payload.js';
import {contextDefinition, viewDefinition} from './context-validation.js';
import {LocationState} from './context-state.js';
import {
contextDefinition,
locationDefinition,
viewDefinition,
} from './context-validation.js';

export interface SetContextPayload {
language: string;
country: string;
currency: CurrencyCodeISO4217;
view: SetViewPayload;
location?: SetLocationPayload;
}

export const setContext = createAction(
Expand All @@ -22,3 +28,10 @@ export const setView = createAction(
'commerce/context/setView',
(payload: SetViewPayload) => validatePayload(payload, viewDefinition)
);

export type SetLocationPayload = LocationState;

export const setLocation = createAction(
'commerce/context/setLocation',
(payload: SetLocationPayload) => validatePayload(payload, locationDefinition)
);
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {setContext, setView} from './context-actions.js';
import {setContext, setLocation, setView} from './context-actions.js';
import {contextReducer} from './context-slice.js';
import {CommerceContextState, getContextInitialState} from './context-state.js';

Expand Down Expand Up @@ -43,4 +43,14 @@ describe('context-slice', () => {
};
expect(contextReducer(state, setView(view)).view).toEqual(view);
});

it('should allow to set the location', () => {
const location = {
latitude: -10.2,
longitude: 20.1,
};
expect(contextReducer(state, setLocation(location)).location).toEqual(
location
);
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {createReducer} from '@reduxjs/toolkit';
import {setContext, setView} from './context-actions.js';
import {setContext, setLocation, setView} from './context-actions.js';
import {getContextInitialState} from './context-state.js';

export const contextReducer = createReducer(
Expand All @@ -12,6 +12,9 @@ export const contextReducer = createReducer(
})
.addCase(setView, (state, {payload}) => {
state.view = payload;
})
.addCase(setLocation, (state, {payload}) => {
state.location = payload;
});
}
);
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import {CurrencyCodeISO4217} from '@coveo/relay-event-types';
import {ViewParams} from '../../../api/commerce/commerce-api-params.js';
import {
UserParams,
ViewParams,
} from '../../../api/commerce/commerce-api-params.js';

export type LocationState = Required<
Pick<UserParams, 'latitude' | 'longitude'>
>;

export interface CommerceContextState {
language: string;
country: string;
currency: CurrencyCodeISO4217;
view: ViewParams;
location?: LocationState;
}

export const getContextInitialState = (): CommerceContextState => ({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {RecordValue, Schema, StringValue} from '@coveo/bueno';
import {NumberValue, RecordValue, Schema, StringValue} from '@coveo/bueno';
import {CurrencyCodeISO4217} from '@coveo/relay-event-types';
import {requiredNonEmptyString} from '../../../utils/validate-payload.js';

Expand All @@ -14,6 +14,11 @@ export const viewDefinition = {
url: requiredNonEmptyString,
};

export const locationDefinition = {
latitude: new NumberValue({min: -90, max: 90, required: true}),
longitude: new NumberValue({min: -180, max: 180, required: true}),
};

export const contextDefinition = {
language: requiredNonEmptyString,
country: requiredNonEmptyString,
Expand All @@ -22,6 +27,10 @@ export const contextDefinition = {
options: {required: true},
values: viewDefinition,
}),
location: new RecordValue({
options: {required: false},
values: locationDefinition,
}),
};

export const contextSchema = new Schema(contextDefinition);
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,12 @@ export const buildQuerySuggestRequest = (
? {clientId: navigatorContext.clientId}
: {}),
context: {
...(navigatorContext.userAgent
? {
user: {
userAgent: navigatorContext.userAgent,
},
}
: {}),
user: {
...location,
...(navigatorContext.userAgent
? {userAgent: navigatorContext.userAgent}
: {}),
},
view: {
...view,
...(navigatorContext.referrer
Expand Down
1 change: 1 addition & 0 deletions packages/headless/src/ssr-commerce.index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ export type {
ContextProps,
ContextState,
View,
UserLocation,
ContextDefinition,
} from './controllers/commerce/context/headless-context.ssr.js';
export {defineContext} from './controllers/commerce/context/headless-context.ssr.js';
Expand Down

0 comments on commit 7a33a0b

Please sign in to comment.