var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0)
t[p[i]] = s[p[i]];
return t;
};
/**
* @file @todo need to convert to actions @@training
*/
import { oneRouter } from '@skava/router';
import { getTyped } from 'composition';
import { observable, action } from 'xmobx/mobx';
import { isNumber, isObj, isFunction, isEmpty, toNumber, isArray, isSafe } from 'exotic';
import { isErrorLikeResponse } from '@skava/is-error-like-response';
import ObservableContainer from 'src/bootstrapper/connectData/ObservableContainer';
import { locationContainer } from 'src/bootstrapper/google';
// domain
import { geocodeApi } from 'src/state/storeLocator/container.geocode';
import { OneProduct } from 'src/state/__product';
// !!!!!!!!!!!! FIXME !!!!!!!!!!!!!!!!!!! STATE NEVER IMPORTS FROM VIEWS !!!!!!!!!!!!!!!!!
import { estimateShippingFormContainer } from 'src/views/widgets/ShoppingCart/MiniCart/EstimateShipping/FormState';
import { oneStorage } from '@skava/persistence';
// @note !!! CHANGED TO AVOID CIRCULAR
import { sessionApis } from 'state/session/container.apis';
import { listContainer } from 'state/list/container';
import { listContainerApi } from 'state/list/container.list';
import { application } from 'src/state/application';
import { OneSkuCartPlugin } from 'state/__product/OneSku.CartPlugin';
// local
import { toastMessage, responseMessage } from 'src/state/errorView/_fixture';
import { errorContainer } from 'src/state/errorView/container';
import { transformGeoCodeData } from './transform';
import { shippingAndTaxContainer } from './container.shippingAndTax';
import AddToBag from './queries/AddToBag.graphql';
import MultipleAddToBag from './queries/MultipleAddToBag.graphql';
import UpdateBag from './queries/UpdateBag.graphql';
import DeleteFromBag from './queries/DeleteFromBag.graphql';
import EstimateShipping from './queries/EstimateShipping.graphql';
/**
* @todo reuse @@perf @@haircut
* @todo some responses don't map to this...
*/
function toStatus(response) {
return getTyped(response).string('properties.state.status');
}
function toErrorMessage(response) {
return getTyped(response).string('properties.state.errormessage');
}
// we can easily separate our stores for atomically sized stores
// takes no more time than 1 big store, but then we can hot swap pieces
class CartApi extends ObservableContainer {
constructor() {
super(...arguments);
/**
* @protected we only access this in CartContainer
*
* @todo any params can use oneRouter
* @description debug, fetch cart, update view
* @example
* this.fetchCartApi = this.fetchCartApi.bind(this) === fetchCart = () => {}
*
* @todo we may just want to return fixture here
*
* @return {Promise<Object>}
*/
this.fetchViewBag = () => __awaiter(this, void 0, void 0, function* () {
const response = yield sessionApis.viewBag();
cartContainer.updateFrom(response);
return response;
});
}
}
CartApi.debugName = 'CartApi';
const cartApi = new CartApi();
/**
* @todo move to deps
*/
const _toQuantity = (item, sku) => {
const isOneProduct = isObj(item.dynamicState) === true && isNumber(item.dynamicState.quantity) === true;
if (isOneProduct === true) {
return item.dynamicState.quantity;
}
else if (isNumber(item.quantity) === true) {
return item.quantity;
}
else if (isNumber(sku.quantity) === true) {
return sku.quantity;
}
else {
return 1;
}
};
const toQuantity = (item, sku) => {
// calling this inside to always get 1, should not be needed
return _toQuantity(item, sku) || 1;
};
const getPayLoad = (props) => {
const { item, sku, isMaster } = props;
const itemQuantity = toQuantity(item, sku);
const quantity = itemQuantity ? toNumber(itemQuantity) : 1;
return {
skuId: item.isBundle ? item.identifier : sku.identifier,
itemid: item.identifier,
title: encodeURIComponent(sku.name || item.name),
quantity,
itemType: isMaster ? 'bundle' : 'sku',
customParams: {
categoryId: isMaster ? 'bundles' : isObj(item.firstCategory) ? item.firstCategory.value : '',
},
};
};
const fromProductToAddToBagParams = (item) => {
const sku = item.currentlySelectedSku || item.skus[0];
const products = [];
const isMaster = item.isMasterProduct === true ? true : false;
if (isMaster) {
const bundleItems = item.bundleList.map(subproduct => products.push(constructSubPayload(subproduct)));
}
const subproducts = products.filter(product => !!product);
const payload = getPayLoad({ item, sku, isMaster });
if (isMaster) {
payload.subProducts = subproducts;
}
return payload;
};
function bundleParams(label) {
switch (label) {
case 'bundleMainProduct':
return 'mainProduct';
case 'bundleOptionalProducts':
return 'optionalProduct';
case 'bundleMandatoryProducts':
return 'mandatoryProduct';
default:
return 'otherProduct';
}
}
const constructSubPayload = (item) => {
const sku = item.currentlySelectedSku || item.skus[0];
// @todo customParams to be taken by looping flags - need clarity
const customParams = (item.iteminfo.flags && item.iteminfo.flags[0]) || {};
const customParamLabel = bundleParams(customParams.label);
const customParamValue = customParams.value;
if ((customParamLabel === 'optionalProduct' && !item.addOnEnable) ||
customParamLabel === 'otherProduct') {
return '';
}
const subProductsPayload = {
skuId: sku.identifier,
itemid: item.identifier,
customParams: {},
};
subProductsPayload.customParams[customParamLabel] = customParamValue;
subProductsPayload.customParams.quantity = item.quantity || 1;
return subProductsPayload;
};
/**
* @description there are a few ways we can do this relational data
*
* @example 1. since we have stores for Product already and can store the api data there
* because those state-tree stores already have simplified transforms
*
* @example 2. even just importing Catalog/Product or /ProductDisplay/Product, can extend it easily
* then just do .create(apiData).toJSON() and it would be transformed
*
* @example 3. or you can do the same transforms in this store
*
*/
class CartContainer extends ObservableContainer {
/**
* @example this.fetchCartApi = this.fetchCartApi.bind(this) === fetchCart = () => {}
* @description debug, fetch cart, update view
*/
constructor() {
super();
this.cartDetails = {};
/**
* @todo need to take save for later out of cart
*/
this.saveForLaterProduct = [];
this.cartCount = 0;
this.saveListItemsId = [];
this.saveListId = '';
this._shouldShowPlaceHolderOnRender = false;
this.fetched = {
hasFetchedSaveForLater: false,
hasFetchedCart: false,
};
// ========= @lifecycle =========
/**
* @deprecated
*/
this.handleMount = () => {
if (typeof window !== 'object') {
// this seems to mount way too many times on the server
console.warn('@note was viewing cart on server');
return;
}
/**
* @todo - only use this when we mount the zip
*/
// if (window.google === undefined) {
// loadGoogle()
// }
// this.viewCart()
if (this.fetched.hasFetchedSaveForLater === false) {
this.fetched.hasFetchedSaveForLater = true;
this.fetchSaveForLater();
}
else {
console.warn('[Cart] MOUNTED MULTIPLE TIMES!!!');
}
};
/**
* @deprecated
*/
this.handleUnMount = () => {
// @todo
// componentWillUnmount
};
/**
* !!!!!!! theey do not eveen belong iin a class
* @todo needs to be in state/list
*/
/**
* @todo move to deps
*/
this.transformSaveForLaterItems = (item) => {
const saveForLaterProduct = item;
const { array } = getTyped(item);
const { skuId } = array('sku.0');
saveForLaterProduct.skuId = skuId;
array('additionalProps').forEach((product) => {
if (product.label === 'productId') {
saveForLaterProduct.identifier = product.value;
}
if (product.label === 'quantity') {
saveForLaterProduct.quantity = product.value;
}
});
return saveForLaterProduct;
};
/**
* @todo move to deps
*/
this.transformListId = (listId) => {
const list = {
listId: listId.listItemId,
productId: listId.identifier,
};
return list;
};
// default until we replace it
// do not push to this array, only replace saveForLaterProduct =
}
// ========= @actions =========
/**
* @public
* @type {Action}
* @see CartApi
*
* @param {Object} params object (in this case I think it is ProductDetails???)
* return value is not used unless the view needs it
*
* @description this is the interface provided to the views/components
* we can use this to call CartApi
* and CartApi class can use
*/
handleAddToCartResponse(addToCartResponse, viewCartResponse) {
if (isErrorLikeResponse(addToCartResponse) || isErrorLikeResponse(viewCartResponse)) {
const isCartLimitExceeded = addToCartResponse.data.addToBag.properties.state.status ===
responseMessage.cartLimitExceeded;
const toastText = isCartLimitExceeded
? toastMessage.cartLimitFailure
: toastMessage.addToCartFailure;
errorContainer.setError({
errorMessage: toastText,
});
return false;
}
else if (!application.isDesktop) {
errorContainer.setError({
errorMessage: toastMessage.addToCartSuccess,
});
}
// done loading
application.setIsLoadingBig(false).setIsLoading(false);
return true;
}
addToBag(item, transformOneProduct = true) {
return __awaiter(this, void 0, void 0, function* () {
// Enable the placeholder in minicart if no products till viewbag gets success
this._shouldShowPlaceHolderOnRender = true;
// start loading
application.setIsLoadingBig(true).setIsLoading(true);
const getAddToBag = {
response: {},
};
// @note the tslint error that shows up for `prefer-conditional-expression`
let payload = item;
if (transformOneProduct) {
if (isArray(item)) {
payload = item.map(fromProductToAddToBagParams);
}
else {
payload = fromProductToAddToBagParams(item);
}
}
if (isArray(item) && item.length > 1) {
const params = {
addToBagParams: payload,
};
getAddToBag.response = (yield this.client.mutate({
mutation: MultipleAddToBag,
variables: {
input: params,
},
}));
}
else {
getAddToBag.response = (yield this.client.mutate({
mutation: AddToBag,
variables: {
input: payload,
},
}));
}
const cartResponse = yield this.viewCart();
Loading ...