Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 10 additions & 2 deletions packages/core/src/billing/billing-address-reducer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Action, combineReducers, composeReducers } from '@bigcommerce/data-store';

import { CheckoutAction, CheckoutActionType } from '../checkout';
import {
CheckoutAction,
CheckoutActionType,
CheckoutHydrateAction,
CheckoutHydrateActionType,
} from '../checkout';
import { clearErrorReducer } from '../common/error';
import { objectSet, replace } from '../common/utility';
import { OrderAction } from '../order';
Expand Down Expand Up @@ -29,14 +34,17 @@ export default function billingAddressReducer(

function dataReducer(
data: BillingAddress | undefined,
action: CheckoutAction | BillingAddressAction | OrderAction,
action: CheckoutAction | BillingAddressAction | OrderAction | CheckoutHydrateAction,
): BillingAddress | undefined {
switch (action.type) {
case BillingAddressActionType.UpdateBillingAddressSucceeded:
case BillingAddressActionType.ContinueAsGuestSucceeded:
case CheckoutActionType.LoadCheckoutSucceeded:
return replace(data, action.payload && action.payload.billingAddress);

case CheckoutHydrateActionType.HydrateInitialState:
return replace(data, action.payload?.checkout?.billingAddress);

default:
return data;
}
Expand Down
13 changes: 11 additions & 2 deletions packages/core/src/cart/cart-reducer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { Action, combineReducers, composeReducers } from '@bigcommerce/data-store';

import { BillingAddressAction, BillingAddressActionType } from '../billing';
import { CheckoutAction, CheckoutActionType } from '../checkout';
import {
CheckoutAction,
CheckoutActionType,
CheckoutHydrateAction,
CheckoutHydrateActionType,
} from '../checkout';
import { clearErrorReducer } from '../common/error';
import { objectMerge, objectSet } from '../common/utility';
import {
Expand Down Expand Up @@ -32,7 +37,8 @@ function dataReducer(
| CheckoutAction
| ConsignmentAction
| CouponAction
| GiftCertificateAction,
| GiftCertificateAction
| CheckoutHydrateAction,
): Cart | undefined {
switch (action.type) {
case BillingAddressActionType.UpdateBillingAddressSucceeded:
Expand All @@ -48,6 +54,9 @@ function dataReducer(
case GiftCertificateActionType.RemoveGiftCertificateSucceeded:
return objectMerge(data, action.payload && action.payload.cart);

case CheckoutHydrateActionType.HydrateInitialState:
return objectMerge(data, action.payload?.checkout?.cart);

default:
return data;
}
Expand Down
11 changes: 10 additions & 1 deletion packages/core/src/checkout/checkout-action-creator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createAction, createErrorAction, ThunkAction } from '@bigcommerce/data-store';
import { Action, createAction, createErrorAction, ThunkAction } from '@bigcommerce/data-store';
import { concat, defer, merge, Observable, of } from 'rxjs';
import { catchError } from 'rxjs/operators';

Expand All @@ -10,6 +10,8 @@ import { FormFieldsActionCreator } from '../form';

import Checkout, { CheckoutRequestBody } from './checkout';
import { CheckoutActionType, LoadCheckoutAction, UpdateCheckoutAction } from './checkout-actions';
import { CheckoutHydrateActionType } from './checkout-hydrate-actions';
import CheckoutInitialState from './checkout-initial-state';
import CheckoutRequestSender from './checkout-request-sender';
import InternalCheckoutSelectors from './internal-checkout-selectors';

Expand Down Expand Up @@ -143,6 +145,13 @@ export default class CheckoutActionCreator {
};
}

hydrateInitialState(state: CheckoutInitialState): Action<CheckoutInitialState> {
return {
type: CheckoutHydrateActionType.HydrateInitialState,
payload: state,
};
}

private _transformCustomerAddresses(body: Checkout): Checkout {
return {
...body,
Expand Down
11 changes: 11 additions & 0 deletions packages/core/src/checkout/checkout-hydrate-actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Action } from '@bigcommerce/data-store';

import CheckoutInitialState from './checkout-initial-state';

export enum CheckoutHydrateActionType {
HydrateInitialState = 'HYDRATE_INITIAL_STATE',
}

export interface CheckoutHydrateAction extends Action<CheckoutInitialState> {
type: CheckoutHydrateActionType.HydrateInitialState;
}
12 changes: 12 additions & 0 deletions packages/core/src/checkout/checkout-initial-state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Config } from '../config';
import { Extension } from '../extension';
import { FormFields } from '../form';

import Checkout from './checkout';

export default interface CheckoutInitialState {
config?: Config;
formFields?: FormFields;
checkout?: Checkout;
extensions?: Extension[];
}
17 changes: 16 additions & 1 deletion packages/core/src/checkout/checkout-reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { SpamProtectionAction, SpamProtectionActionType } from '../spam-protecti
import { StoreCreditAction, StoreCreditActionType } from '../store-credit';

import { CheckoutAction, CheckoutActionType } from './checkout-actions';
import { CheckoutHydrateAction, CheckoutHydrateActionType } from './checkout-hydrate-actions';
import CheckoutState, {
CheckoutDataState,
CheckoutErrorsState,
Expand Down Expand Up @@ -46,7 +47,8 @@ function dataReducer(
| GiftCertificateAction
| OrderAction
| SpamProtectionAction
| StoreCreditAction,
| StoreCreditAction
| CheckoutHydrateAction,
): CheckoutDataState | undefined {
switch (action.type) {
case CheckoutActionType.LoadCheckoutSucceeded:
Expand Down Expand Up @@ -78,6 +80,19 @@ function dataReducer(
case OrderActionType.SubmitOrderSucceeded:
return objectSet(data, 'orderId', action.payload && action.payload.order.orderId);

case CheckoutHydrateActionType.HydrateInitialState:
return objectMerge(
data,
omit(action.payload?.checkout, [
'billingAddress',
'cart',
'consignments',
'customer',
'coupons',
'giftCertificates',
]),
) as CheckoutDataState;

default:
return data;
}
Expand Down
21 changes: 21 additions & 0 deletions packages/core/src/checkout/checkout-service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ import { StoreCreditActionCreator, StoreCreditRequestSender } from '../store-cre
import { SubscriptionsActionCreator, SubscriptionsRequestSender } from '../subscription';

import CheckoutActionCreator from './checkout-action-creator';
import CheckoutInitialState from './checkout-initial-state';
import CheckoutRequestSender from './checkout-request-sender';
import CheckoutSelectors from './checkout-selectors';
import CheckoutService from './checkout-service';
Expand Down Expand Up @@ -1636,4 +1637,24 @@ describe('CheckoutService', () => {
);
});
});

describe('#hydrateInitialState', () => {
it('creates instance with initial data', async () => {
const initialState: CheckoutInitialState = {
config: getConfig(),
formFields: getFormFields(),
checkout: getCheckout(),
extensions: getExtensions(),
};

const state = await checkoutService.hydrateInitialState(initialState);

expect(state.data.getCheckout()).toEqual(initialState.checkout);
expect(state.data.getConfig()).toEqual(initialState.config?.storeConfig);
expect(state.data.getCustomerAccountFields()).toEqual(
initialState.formFields?.customerAccount,
);
expect(state.data.getExtensions()).toEqual(initialState.extensions);
});
});
});
25 changes: 25 additions & 0 deletions packages/core/src/checkout/checkout-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import { Subscriptions, SubscriptionsActionCreator } from '../subscription';

import { CheckoutRequestBody } from './checkout';
import CheckoutActionCreator from './checkout-action-creator';
import CheckoutInitialState from './checkout-initial-state';
import CheckoutParams from './checkout-params';
import CheckoutSelectors from './checkout-selectors';
import CheckoutStore from './checkout-store';
Expand Down Expand Up @@ -180,6 +181,30 @@ export default class CheckoutService {
return this._storeProjection.subscribe(subscriber, ...filters);
}

/**
* Hydrates the checkout service with an initial state.
*
* The initial state can contain various checkout data such as cart items,
* customer information, and other relevant state.
*
* ```js
* const initialState = {
* // ... initial checkout state data
* };
*
* const state = await service.hydrateInitialState(initialState);
*
* console.log(state.data.getCheckout());
* ```
*
* @alpha
* @param state - The initial state data to hydrate the checkout service with.
* @returns A promise that resolves to the current state after hydration.
*/
hydrateInitialState(state: CheckoutInitialState): Promise<CheckoutSelectors> {
return this._dispatch(this._checkoutActionCreator.hydrateInitialState(state));
}

/**
* Loads the current checkout.
*
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/checkout/create-checkout-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ import createCheckoutStoreReducer from './create-checkout-store-reducer';
import { createInternalCheckoutSelectorsFactory } from './create-internal-checkout-selectors';

export default function createCheckoutStore(
initialState: Partial<CheckoutStoreState> = {},
initialStoreState: Partial<CheckoutStoreState> = {},
options?: CheckoutStoreOptions,
): CheckoutStore {
const actionTransformer = createActionTransformer(createRequestErrorFactory());
const createInternalCheckoutSelectors = createInternalCheckoutSelectorsFactory();
const stateTransformer = (state: CheckoutStoreState) => createInternalCheckoutSelectors(state);
const reducer = createCheckoutStoreReducer();

return createDataStore(createCheckoutStoreReducer(), initialState, {
return createDataStore(reducer, initialStoreState, {
actionTransformer,
stateTransformer,
...options,
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/checkout/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './checkout-actions';
export * from './checkout-hydrate-actions';

export { default as Checkout, CheckoutPayment } from './checkout';
export { default as CHECKOUT_DEFAULT_INCLUDES } from './checkout-default-includes';
Expand Down
9 changes: 8 additions & 1 deletion packages/core/src/config/config-reducer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Action, combineReducers, composeReducers } from '@bigcommerce/data-store';

import { CheckoutHydrateAction, CheckoutHydrateActionType } from '../checkout';
import { clearErrorReducer } from '../common/error';
import { objectMerge, objectSet } from '../common/utility';

Expand All @@ -20,11 +21,17 @@ export default function configReducer(
return reducer(state, action);
}

function dataReducer(data: Config | undefined, action: LoadConfigAction): Config | undefined {
function dataReducer(
data: Config | undefined,
action: LoadConfigAction | CheckoutHydrateAction,
): Config | undefined {
switch (action.type) {
case ConfigActionType.LoadConfigSucceeded:
return objectMerge(data, action.payload);

case CheckoutHydrateActionType.HydrateInitialState:
return objectMerge(data, action.payload?.config);

default:
return data;
}
Expand Down
15 changes: 8 additions & 7 deletions packages/core/src/config/config-selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,14 @@ export function createConfigSelectorFactory(): ConfigSelectorFactory {
const getStoreConfig = createSelector(
(state: ConfigState) => state.data,
(_: ConfigState, { formState }: ConfigSelectorDependencies) => formState && formState.data,
(data, formFields) => () =>
data && formFields
? {
...data.storeConfig,
formFields,
}
: undefined,
(data, formFields = { customerAccount: [], shippingAddress: [], billingAddress: [] }) =>
() =>
data
? {
...data.storeConfig,
formFields,
}
: undefined,
);

const getStoreConfigOrThrow = createSelector(getStoreConfig, (getStoreConfig) => () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export interface StoreCurrency {
code: string;
decimalPlaces: string;
decimalSeparator: string;
isTransactional: boolean;
symbolLocation: string;
symbol: string;
thousandsSeparator: string;
Expand Down Expand Up @@ -117,6 +118,7 @@ export interface CheckoutSettings {
isSpamProtectionEnabled: boolean;
isTrustedShippingAddressEnabled: boolean;
orderTermsAndConditions: string;
orderTermsAndConditionsLocation: string;
orderTermsAndConditionsLink: string;
orderTermsAndConditionsType: string;
privacyPolicyUrl: string;
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/config/configs.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export function getConfig(): Config {
isSpamProtectionEnabled: true,
isTrustedShippingAddressEnabled: false,
orderTermsAndConditions: '',
orderTermsAndConditionsLocation: '',
orderTermsAndConditionsLink: '',
orderTermsAndConditionsType: '',
privacyPolicyUrl: '',
Expand All @@ -58,6 +59,7 @@ export function getConfig(): Config {
code: 'USD',
decimalPlaces: '2',
decimalSeparator: '.',
isTransactional: true,
symbolLocation: 'left',
symbol: '$',
thousandsSeparator: ',',
Expand Down
12 changes: 10 additions & 2 deletions packages/core/src/coupon/coupon-reducer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Action, combineReducers, composeReducers } from '@bigcommerce/data-store';

import { CheckoutAction, CheckoutActionType } from '../checkout';
import {
CheckoutAction,
CheckoutActionType,
CheckoutHydrateAction,
CheckoutHydrateActionType,
} from '../checkout';
import { clearErrorReducer } from '../common/error';
import { arrayReplace, objectSet } from '../common/utility';
import { OrderAction, OrderActionType } from '../order';
Expand All @@ -25,7 +30,7 @@ export default function couponReducer(

function dataReducer(
data: Coupon[] | undefined,
action: CouponAction | CheckoutAction | OrderAction | ConsignmentAction,
action: CouponAction | CheckoutAction | OrderAction | ConsignmentAction | CheckoutHydrateAction,
): Coupon[] | undefined {
switch (action.type) {
case CheckoutActionType.LoadCheckoutSucceeded:
Expand All @@ -35,6 +40,9 @@ function dataReducer(
case OrderActionType.LoadOrderSucceeded:
return arrayReplace(data, action.payload && action.payload.coupons);

case CheckoutHydrateActionType.HydrateInitialState:
return arrayReplace(data, action.payload?.checkout?.coupons);

default:
return data;
}
Expand Down
Loading