/*
* Order Routes
* Author: Isaiah Sanchez <isanchez@lunchbox.io>
*
* This component is rendered by pages/Order.
* This component defines all of the ordering routes.
*/
import React, {useState, useEffect} from "react";
import {Switch} from "react-router-dom";
import {config, Copy, constants, Routes, helpers} from "utils";
import {Fragments, Templates} from "components";
import {ErrorBoundary} from "components/elements";
import {useMenuContext} from "components/providers/Menu/MenuContext";
import {analytics} from "hooks";
import {Cart, Checkout, Locations} from "pages";
import {
Complete,
ClearDiscount,
ClearGiftCard,
Discount,
GiftCard,
Guests,
GenerateLink,
PackingInstructions,
Prompt as Confirm,
ScheduleDate,
TableNumber,
Upsells,
InterceptCheckoutComplete,
BeamPersonalCommunityImpactTabs,
} from "pages/Order";
import styles from "./style.module.scss";
const {
Routes: {RouteWithProps},
} = Fragments;
const {ItemDetails, LoginSignup, PaymentForm} = Templates;
const {orderModsAsObject} = helpers;
const animationConfig = {
animate: "in",
className: styles["order-route"],
exit: "out",
initial: "initial",
transition: {
type: "tween",
},
variants: {
in: {
x: 0,
},
initial: {
x: "100%",
},
out: {
x: "-100%",
},
},
};
const beamAnimationConfig = {
animate: "in",
className: styles["order-route"],
exit: "out",
initial: "initial",
variants: {
in: {
y: 0,
},
initial: {
y: "100%",
},
out: {
y: "100%",
},
},
};
/**
* FallBack component if none of the Beam widgets works
*
* @param props
* @memberof Pages.Pages/Order
*/
const FallBack = (props) => {
useEffect(() => {
props.onComplete();
}, []);
return null;
};
const OrderRoutes = React.memo(
({
browserLocation,
location,
history,
cart,
orderContext,
patronContext,
setHeader,
onClose,
}) => {
// Hooks
const [beam, setBeam] = useState(null);
const [ticketInformation, setTicketInformation] = useState({});
const {packingMenu, upsellableItems, fetchUpsellMenu} = useMenuContext();
// Constants
const hasPackingMenu = packingMenu && packingMenu.menus.array.length;
const hasUpsells = upsellableItems.length;
useEffect(() => {
fetchUpsellMenu();
return () => {
orderContext.resetGiftCard();
};
}, []);
// Render
return (
<Switch location={location} key={location.pathname}>
<RouteWithProps
exact
path={Routes.ROOT}
component={Cart}
cart={cart}
history={history}
orderContext={orderContext}
patronContext={patronContext}
hasUpsells={hasUpsells}
hasPackingMenu={hasPackingMenu}
animation={animationConfig}
onUpsellSuccess={() => {
if (hasPackingMenu) {
history.push(Routes.Cart);
} else if (
config[orderContext.order.diningOption].guests &&
(!patronContext.patron.firstName ||
!patronContext.patron.lastName)
) {
history.push(Routes.GUESTS);
}
}}
addUpsellToCart={(item, group) => {
history.push({
pathname: Routes.UPSELLS_ITEM_DETAIL(item),
state: {group},
});
}}
setHeader={setHeader}
/>
<RouteWithProps
path={Routes.FETCH_DISCOUNT}
component={Discount}
items={orderContext.items}
orderType={orderContext.order.diningOption}
scheduledAt={orderContext.order.scheduledAt}
order={orderContext}
onSuccess={(discount) => {
orderContext.setDiscount(discount);
history.goBack();
}}
setHeader={setHeader}
animation={animationConfig}
/>
<RouteWithProps
path={Routes.FETCH_GIFTCARD}
component={GiftCard}
onSuccess={({balance, giftCardNumber}) => {
orderContext.setGiftCard({balance, giftCardNumber});
history.goBack();
}}
animation={animationConfig}
/>
<RouteWithProps
path={Routes.CLEAR_GIFTCARD}
component={ClearGiftCard}
onConfirm={() => {
orderContext.resetGiftCard();
history.goBack();
}}
onCancel={() => history.goBack()}
animation={animationConfig}
/>
<RouteWithProps
path={Routes.CLEAR_DISCOUNT}
component={ClearDiscount}
onConfirm={() => {
orderContext.resetDiscount();
history.goBack();
}}
onCancel={() => history.replace(Routes.ROOT)}
animation={animationConfig}
/>
<RouteWithProps
path={Routes.FETCH_ITEM_DETAIL}
component={({
match: {
params: {index},
},
location: {
state: {isGroup},
},
}) => {
const orderItem = orderContext.items[index];
const itemDetailProps = {
...orderItem,
buttonText: Copy.CART_STATIC.ACCEPT_EDITS_BUTTON_TEXT,
close: () => history.goBack(),
id: isGroup ? orderItem.group : orderItem.item,
isCartItem: true,
mods: orderModsAsObject(orderItem.mods),
onConfirm: (data) => orderContext.editItemAtIndex(data, index),
};
return <ItemDetails {...itemDetailProps} />;
}}
animation={animationConfig}
/>
<RouteWithProps
path={Routes.REMOVE_CART_ITEM}
component={({
match: {
params: {index},
},
}) => {
const onConfirm = () => {
if (index !== undefined) {
analytics.product.removed({});
orderContext.removeFromOrder(Number(index));
} else {
orderContext.resetDiscount();
orderContext.clearItems();
orderContext.clearPackingItems();
}
history.replace(Routes.ROOT);
};
const text =
index !== undefined
? orderContext.items[index].name
: "All Items";
return (
<Confirm
onConfirm={onConfirm}
onCancel={() => history.replace(Routes.ROOT)}
name={text}
/>
);
}}
animation={animationConfig}
/>
<RouteWithProps
path={Routes.PACKING}
component={PackingInstructions}
onSuccess={() => {
if (
config[orderContext.order.diningOption].guests &&
(!patronContext.patron.firstName ||
!patronContext.patron.lastName)
) {
history.push(Routes.GUESTS);
} else {
history.push(Routes.CHECK_OUT);
}
}}
animation={animationConfig}
setHeader={setHeader}
/>
<RouteWithProps
exact
path={Routes.UPSELLS}
component={Upsells}
onSuccess={() => {
if (hasPackingMenu) {
history.push(Routes.PACKING);
} else if (
config[orderContext.order.diningOption].guests &&
(!patronContext.patron.firstName ||
!patronContext.patron.lastName)
) {
history.push(Routes.GUESTS);
} else {
history.push(Routes.CHECK_OUT);
}
}}
addToCart={(item, group, restaurantGroup) => {
history.push({
pathname: Routes.UPSELLS_ITEM_DETAIL(item),
state: {group, restaurantGroup},
});
}}
setHeader={setHeader}
animation={animationConfig}
/>
<RouteWithProps
path={Routes.UPSELLS_ITEM}
component={({
match: {
params: {id},
},
location: {
state: {group},
},
}) => (
<ItemDetails
id={id}
onConfirm={(item, quantity) => {
orderContext.addToOrder(
{group, ...item, isGroup: false},
quantity,
true,
);
}}
showQuantity
buttonText={Copy.CART_STATIC.ADD_TO_CART_BUTTON_TEXT}
close={() => {
if (config?.theme?.checkout?.upsell_layout === "Type2") {
history.replace(Routes.ROOT);
} else {
history.replace(Routes.UPSELLS);
}
}}
isUpsell
/>
)}
animation={animationConfig}
/>
<RouteWithProps
path={Routes.LOCATIONS}
component={Locations}
order={orderContext}
animation={animationConfig}
/>
<RouteWithProps
path={Routes.SET_TABLE_NUMBER}
component={TableNumber}
order={orderContext}
setHeader={setHeader}
onSuccess={(data) => {
orderContext.setTableNumber(data);
history.goBack();
}}
animation={animationConfig}
/>
<RouteWithProps
path={Routes.FETCH_SCHEDULE_DATE}
component={ScheduleDate}
order={orderContext}
setHeader={setHeader}
onSuccess={() => history.goBack()}
animation={animationConfig}
/>
<RouteWithProps
path={Routes.LOG_IN}
trackerPath="cart"
component={LoginSignup}
lastLocation={browserLocation.pathname}
onComplete={({isEmployee}) => {
if (isEmployee) {
history.replace(Routes.LINK);
} else if (config?.theme?.checkout?.upsell_layout === "Type2") {
history.replace(Routes.ROOT);
} else if (hasUpsells) {
history.replace(Routes.UPSELLS);
} else if (hasPackingMenu) {
history.replace(Routes.UPSELLS);
} else {
history.replace(Routes.CHECK_OUT);
}
}}
animation={animationConfig}
/>
<RouteWithProps
exact
path={Routes.GUESTS}
component={Guests}
history={history}
patron={patronContext.patron}
updatePatron={patronContext.updatePatron}
animation={animationConfig}
/>
<RouteWithProps
exact
path={Routes.CHECK_OUT}
component={Checkout}
history={history}
patronContext={patronContext}
orderContext={orderContext}
beam={beam}
setBeam={setBeam}
setHeader={setHeader}
animation={beamAnimationConfig}
cart={cart}
onSuccess={(order) => {
setTicketInformation(order);
history.replace(Routes.INTERCEPT_CHECKOUT_COMPLETE);
}}
/>
{/* Interceptor between Checkout & Complete for Beam new user */}
<RouteWithProps
path={Routes.INTERCEPT_CHECKOUT_COMPLETE}
component={InterceptCheckoutComplete}
render={() => {
const onComplete = () => {
if (beam) beam?.postTransaction(beam);
history.replace(Routes.PURCHASE_COMPLETE);
};
const onSkipBeamSelection = () => {
history.replace(Routes.PURCHASE_COMPLETE);
};
return (
<ErrorBoundary error={<FallBack onComplete={onComplete} />}>
<InterceptCheckoutComplete
patronEmail={patronContext?.patron?.email?.value}
beam={beam}
setBeam={setBeam}
onComplete={onComplete}
onSkipBeamSelection={onSkipBeamSelection}
setHeader={setHeader}
/>
</ErrorBoundary>
);
}}
animation={animationConfig}
/>
<RouteWithProps
path={Routes.FETCH_CARDS}
component={PaymentForm}
order={orderContext}
onSuccess={() => history.goBack()}
setHeader={setHeader}
animation={animationConfig}
/>
<RouteWithProps
path={Routes.PURCHASE_COMPLETE}
component={Complete}
beam={beam}
ticketInformation={ticketInformation}
order={orderContext}
onSuccess={onClose}
onSeeMoreImpact={() => {
history.replace(Routes.PERSONAL_COMMUNITY_IMPACT);
}}
setHeader={setHeader}
animation={animationConfig}
/>
<RouteWithProps
path={Routes.PERSONAL_COMMUNITY_IMPACT}
component={BeamPersonalCommunityImpactTabs}
beam={beam}
onClose={onClose}
setHeader={setHeader}
animation={animationConfig}
/>
<RouteWithProps
path={Routes.LINK}
component={GenerateLink}
order={orderContext}
onSuccess={onClose}
animation={animationConfig}
/>
</Switch>
);
},
);
export default OrderRoutes;