<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
</body>
</html>
import React, { useState, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import checkActiveStore from '../../functions/utils/checkActiveStore';
import { useAlert } from 'react-alert';
import { isEmpty, capitalize } from 'lodash';
import {
Heading as HeadingComponent,
ProductTile as ProductTileComponent,
MiniCart as MiniCartComponent,
Button,
} from '@beetsandroots/components';
import { ALERT_TIMEOUT_6000 } from '../../../app/functions/utils/constants';
import HeaderTeaser from '../common/header/HeaderTeaser';
import Menu from '../common/header/Menu';
import {
PRODUCT_TYPE_INGREDIENT,
PRODUCT_TYPE_CUSTOM,
PRODUCT_TYPE_PRECONGIGURED,
PRODUCT_TYPE_BASIC,
CATEGORY_TYPE_DISH,
} from './Constants';
import { getCategoriesByParentID } from '../../functions/actions/product/ProductListActions';
import { getProductsByCategoryID } from '../../functions/actions/product/ProductListActions';
import { setCartDisplay } from '../../functions/actions/common/header/HeaderActions';
import {
setLineItemQuantity,
checkCartByID,
getActiveCart,
getShippingMethodPickup,
addShippingMethodToCart,
getShippingMethodDelivery,
} from '../../functions/actions/cart/CartActions';
import { config } from '../../../../../app.config';
import { appURL } from '../../../utils/Url';
import './MenuPage.scss';
import { formatPrice } from '../../../utils/Formatter';
import { isStoreOpenToday } from '../../../utils/StoreUtils';
import { isCartVouchersOnly } from '../../../utils/CartUtils';
import { isProductAvailable } from '../../../utils/ProductPriceCalculation';
import { compareOrderHints } from '../../../utils/Comparer';
import { removeUserDeliveryAddress, setActiveStore } from '../../functions/actions/common/ApplicationActions';
const uuid = require('uuid/v4');
import DirectProductByCategoryID from './DirectProductByCategoryID';
import { getUserCreditsAndReferralCode } from '../../functions/actions/user/UserActions';
import { getLocalStorage } from '../../LocalStorage';
import axios from 'axios';
const MenuPage = (props) => {
const { t } = useTranslation();
const dispatch = useDispatch();
const alert = useAlert();
useEffect(() => {
dispatch(getActiveCart());
}, []);
// ToDo: Such general things should be done by writing an own provider and should be handled inside
// App.js between switch and Route
// toggle mini icon in header
dispatch(setCartDisplay(props.showMiniCart));
const contextState = useSelector((state) => state.application.context);
const currentStoreKey = useSelector((state) => state.application.active.store)?.key;
const stores = useSelector((state) => state.entities.store.items);
const user = useSelector((state) => state.application.user);
const authenticated = useSelector((state) => state.application.authenticated);
const currentActiveStore = useSelector((state) => state.application.active.store);
const productTypes = useSelector((state) => state.entities.producttype.items);
const categories = useSelector((state) => state.entities.category.items);
const categoryState = useSelector((state) => state.entities.category);
const shippingMethod = useSelector((state) => state.application.active.shippingMethod);
const cart = useSelector((state) => state.application.cart);
const orderType = contextState.app === 'delivery' ? contextState.app : 'pickup';
const items = useSelector((state) => state.ui.productlist.products);
const subCategories = useSelector((state) => state.ui.productlist.categories);
const minimumOrderValue = useSelector((state) => state.application.active.store.minimumOrderValue);
const category = categories.find((elm) => elm.externalID == config.categories.mainExternalId);
const deliveryFee = useSelector((state) => state.application.active.shippingMethod?.delivery?.fee);
const referralVoucher = useSelector((state) => state.application.referral);
const applicationFailureLS = getLocalStorage()?.getItem('applicationFailure');
const userDeliveryAddress = useSelector((state) => state.application.active.userDeliveryAddress);
let formattedDeliveryFee = t('common.header.minicart.text.free');
if (deliveryFee && deliveryFee.price.centAmount > 0) {
formattedDeliveryFee = formatPrice(deliveryFee.price.centAmount, null, deliveryFee.price.currencyCode);
}
const addToBasketLabel = t('category.productlistpage.addToBasket');
const customizeLabel = t('category.productlistpage.customize');
const checkoutURL = contextState.app === 'preorder' ? appURL(`checkout/${currentStoreKey}`) : appURL(`checkout`);
const storesURL = appURL('stores');
const [showBasket, setShowBasket] = useState(false);
const cartStoreKey = cart?.storeKey;
const restaurantParam = props.match?.params.restaurant;
useEffect(() => {
if (contextState.app === 'delivery') {
dispatch(getShippingMethodDelivery());
} else {
dispatch(getShippingMethodPickup());
}
if (!isEmpty(cart?.shippingMethod?.name) && cart.shippingMethod?.name !== capitalize(orderType)) {
dispatch(addShippingMethodToCart());
}
}, [contextState.app]);
checkActiveStore(props.match);
useEffect(() => {
// check if active cart is set to restaurant in URL
if (cartStoreKey) {
dispatch(checkCartByID(cartStoreKey, restaurantParam, () => props.history.push(storesURL)));
}
}, [cartStoreKey]);
const verifyActiveStoreWithUserDeliveryAddress = async () => {
const result = await axios.post('/check-sd-address/', userDeliveryAddress);
if (!result.data.success) {
await dispatch(removeUserDeliveryAddress());
return;
}
const storeId = result.data?.data?.itemlist[0].store_id;
const store = stores.find((store) => store.externalId == storeId);
if (
currentActiveStore &&
!isEmpty(currentActiveStore) &&
store &&
!isEmpty(store) &&
currentActiveStore.key === store.key
)
return;
const storeObj = {
...store,
geoLocation: {
latitude: store.latitude,
longitude: store.longitude,
},
};
await dispatch(setActiveStore(storeObj));
await dispatch(getActiveCart());
};
useEffect(() => {
if (contextState.app === 'delivery' && !isEmpty(userDeliveryAddress) && !isEmpty(currentActiveStore)) {
verifyActiveStoreWithUserDeliveryAddress();
} else if (
contextState.app === 'delivery' &&
isEmpty(userDeliveryAddress) &&
!isEmpty(currentActiveStore) &&
currentActiveStore.key.includes('catering') &&
!isEmpty(stores)
) {
dispatch(setActiveStore(stores.find((store) => store.key === 'berlin-mitte')));
dispatch(getActiveCart());
}
}, [currentActiveStore, stores]);
useEffect(() => {
// if cart is null(invalid) get the right one
if (cart === null) {
// dispatch(removeCart(storesURL));
dispatch(getActiveCart());
}
}, [cart]);
if (applicationFailureLS) {
alert.info(t('common.message.error.connectivityIssue'), {
timeout: ALERT_TIMEOUT_6000,
});
getLocalStorage()?.removeItem('applicationFailure');
}
const cartQuantity = () => {
let quantity = 0;
cart.lineItems.map((item) => {
if (!item.parentItemID) {
quantity += item.quantity;
}
});
return quantity;
};
useEffect(() => {
if (authenticated) dispatch(getUserCreditsAndReferralCode());
}, [authenticated]);
const categoryHasProducts = (category, products) => {
return products?.some((item) => {
// consider only non ingredient products
if (PRODUCT_TYPE_INGREDIENT != item.type) {
// check for directly assigned products
if (item.categories.find((elm) => elm.id == category.id)) {
return true;
}
// check for products assigned to subcategories
return category.children?.some((subCat) => {
return categoryHasProducts(subCat, products);
});
}
});
};
const categoryHasSubcategories = (category) => {
if (category.children && category.children.length > 0) {
return category.children.find((cat) => cat.type == CATEGORY_TYPE_DISH);
}
return false;
};
useEffect(() => {
if (category) {
// get subcategories for selected header category
dispatch(getCategoriesByParentID({ ...contextState, store: currentStoreKey }, categoryState, category.id));
// get all products (not ingredients!) within selected header category
const productTypesNI = productTypes.filter(
(elm) =>
elm.name == PRODUCT_TYPE_CUSTOM || elm.name == PRODUCT_TYPE_PRECONGIGURED || elm.name == PRODUCT_TYPE_BASIC
);
dispatch(
getProductsByCategoryID({ ...contextState, store: currentActiveStore.key }, productTypesNI, category.id)
);
}
if (stores.length > 0 && isEmpty(currentActiveStore) && contextState.app === 'delivery') {
dispatch(setActiveStore(stores.find((store) => store.key === 'berlin-mitte')));
}
}, [category, currentActiveStore]);
const [disableQuantityBtn, setDisableQuantityBtn] = useState(false);
const storeOpened = isStoreOpenToday(currentActiveStore, contextState.app);
const handleSetLineItemQuantity = async (pliID, quantity) => {
if (!storeOpened) {
return;
}
setDisableQuantityBtn(true);
await dispatch(setLineItemQuantity(pliID, quantity));
setDisableQuantityBtn(false);
};
const [availableItemsInStore, setAvailableItemsInStore] = useState(items);
const [filteredSubcategories, setFilteredSubcategories] = useState(subCategories);
useEffect(() => {
setAvailableItemsInStore(items.filter((item) => isProductAvailable(item, currentActiveStore)));
const filteredSubcats = subCategories.filter((subCat) => categoryHasProducts(subCat, availableItemsInStore));
setFilteredSubcategories(filteredSubcats.sort(compareOrderHints));
}, [items, subCategories, currentActiveStore]);
return (
<>
<HeaderTeaser />
<Menu key={'pdp-menu-key'} menuCategories={filteredSubcategories} scrollOffset={-70} anchorOffset={-70} />
<div className="container-fluid" id="menuPage">
{category && (
<div className="row">
<div className="col-lg-8">
<DirectProductByCategoryID
categoryID={category.id}
products={availableItemsInStore}
contextState={contextState}
theProsps={props}
dispatch={dispatch}
addToBasketLabel={addToBasketLabel}
customizeLabel={customizeLabel}
showFirstCardDeck={true}
/>
{filteredSubcategories &&
filteredSubcategories?.length > 0 && // if there are any subcategories with products found
filteredSubcategories?.map((cat) => {
if (categoryHasProducts(cat, availableItemsInStore))
return (
<div key={cat.id} id={cat.slug} className="dish-type">
{!categoryHasSubcategories(cat) && (
<>
<HeadingComponent style="semi-bold" type="h4" text={cat.name} subtext={cat.description} />
<DirectProductByCategoryID
categoryID={cat.id}
products={availableItemsInStore}
contextState={contextState}
theProsps={props}
dispatch={dispatch}
addToBasketLabel={addToBasketLabel}
customizeLabel={customizeLabel}
showFirstCardDeck={false}
/>
</>
)}
<div>
{cat.children?.map((subCat) => {
if (subCat.type == CATEGORY_TYPE_DISH) {
if (categoryHasProducts(subCat, availableItemsInStore))
return (
<div key={subCat.id}>
<HeadingComponent
style="semi-bold"
type="h4"
text={subCat.name}
subtext={subCat.description}
/>
<DirectProductByCategoryID
categoryID={subCat.id}
products={availableItemsInStore}
contextState={contextState}
theProsps={props}
dispatch={dispatch}
addToBasketLabel={addToBasketLabel}
customizeLabel={customizeLabel}
showFirstCardDeck={false}
/>
</div>
);
}
})}
</div>
</div>
);
})}
</div>
<div className="goto-basket-btn">
<Button
dataDismiss="modal"
type="primary"
label={t('menu.button.view_basket')}
onClick={() => setShowBasket(true)}
/>
{cart && cart.lineItems && <div className="cart-quantity">{cartQuantity()}</div>}
{cart && cart.totalPrice && <div className="cart-price">{cart.totalPrice.formattedPrice}</div>}
</div>
<div className={`sticky-top cart-box ${showBasket && 'show-basket'}`}>
{showBasket && (
<div onClick={() => setShowBasket(false)} className="close-basket">
+
</div>
)}
<MiniCartComponent
getCurrentCart={() => dispatch(getActiveCart())}
cart={cart}
labelDeliveryFee={t('common.header.hero.fee')}
deliverFee={formattedDeliveryFee}
isVouchersOnly={isCartVouchersOnly(cart)}
orderType={contextState.app}
labelMinimumOrderValue={t('common.header.minicart.warning.minimumOrderValue')}
minimumOrderValue={minimumOrderValue}
emptyCartIcon={contextState.assetsURL + '/styles/images/empty-cart.svg'}
title={t('common.header.minicart.title')}
textEmptyCart={t('common.header.minicart.text.empty')}
labelTotalPrice={t('common.header.minicart.label.total')}
labelButton={t('common.header.minicart.button.checkout')}
referralVoucher={referralVoucher}
isStoreOpened={storeOpened}
onClickButton={() => {
if (storeOpened) props.history.push(checkoutURL);
else {
alert.info(t('cart.checkoutpage.cartform.payment.message.error.closedStore'), {
timeout: ALERT_TIMEOUT_6000,
});
}
}}
onClickButtonQuantity={handleSetLineItemQuantity}
disableQuantityBtn={disableQuantityBtn}
credits={user?.referralCode?.amountToGo}
labelCredits={t('common.header.hero.credits')}
store={currentActiveStore}
/>
</div>
</div>
)}
</div>
</>
);
};
export default withRouter(MenuPage);
Output
You can jump to the latest bin by adding /latest
to your URL
Keyboard Shortcuts
Shortcut | Action |
---|---|
ctrl + [num] | Toggle nth panel |
ctrl + 0 | Close focused panel |
ctrl + enter | Re-render output. If console visible: run JS in console |
Ctrl + l | Clear the console |
ctrl + / | Toggle comment on selected lines |
ctrl + ] | Indents selected lines |
ctrl + [ | Unindents selected lines |
tab | Code complete & Emmet expand |
ctrl + shift + L | Beautify code in active panel |
ctrl + s | Save & lock current Bin from further changes |
ctrl + shift + s | Open the share options |
ctrl + y | Archive Bin |
Complete list of JS Bin shortcuts |
JS Bin URLs
URL | Action |
---|---|
/ | Show the full rendered output. This content will update in real time as it's updated from the /edit url. |
/edit | Edit the current bin |
/watch | Follow a Code Casting session |
/embed | Create an embeddable version of the bin |
/latest | Load the very latest bin (/latest goes in place of the revision) |
/[username]/last | View the last edited bin for this user |
/[username]/last/edit | Edit the last edited bin for this user |
/[username]/last/watch | Follow the Code Casting session for the latest bin for this user |
/quiet | Remove analytics and edit button from rendered output |
.js | Load only the JavaScript for a bin |
.css | Load only the CSS for a bin |
Except for username prefixed urls, the url may start with http://jsbin.com/abc and the url fragments can be added to the url to view it differently. |