import React, {useState, useReducer, useRef} from "react";
import {helpers, config, Copy, images as configImages} from "utils";
import {useMenuContext} from "components/providers/Menu/MenuContext";
import {
modsReducer,
mapModifications,
mergeModifications,
mapItemTabs,
nestedModifierState,
} from "../utils";
/**
* Nested modifier drawer hook. Parse properties of nested modifier drawers and return the same component with additional properties.
* @alias withNestedModifiers
* @memberof! NestedModifiers
* @type {function}
* @param {ReactElement} Component - The {@link Component} from the parent nested drawer state.
* @return {ReactElement} The {@link Return} element (type from the selector).
*/
const withNestedModifiers = (Component) => (props) => {
const {
addModifier,
onClose,
parent,
removeModifier,
showQuantity,
style,
} = props;
const {item, option} = parent;
const {
groupsHash,
optionsHash,
itemsHash,
upsellsItemsHash,
upsellsOptionsHash,
} = useMenuContext();
const [modifications, dispatch] = useReducer(modsReducer, parent?.modifiers);
const optionRefs = useRef({});
const [errors, setErrors] = useState([]);
const [globalError, setGlobalError] = useState(null);
const [nestedDrawer, setNestedDrawer] = useState({});
const [quantity, setQuantity] = useState(parent?.quantity || 1);
const increment = () => setQuantity(quantity + 1);
const decrement = () => setQuantity(quantity > 1 ? quantity - 1 : quantity);
const getOptionHash = () => optionsHash; // (isUpsell ? upsellsOptionsHash : optionsHash);
const {items = []} = optionsHash[option] ?? {};
const {name, options} = items?.filter((i) => i?.id === item)?.[0] ?? {};
const payload = {
...parent,
isNested: true,
quantity,
modifiers: modifications,
};
/**
* Event Handlers
* closeDrawer(event)
* addNestedModifier(payload)
* removeNestedModifier(payload)
* onSubmit()
*/
const closeDrawer = (e) => {
e.preventDefault();
setNestedDrawer({});
};
const addNestedModifier = (payload) => {
const {option, item, isNested, price, quantity = 0, modifiers} = payload;
if (isNested && !quantity) {
// Adding new nested mod, open parent drawer
return setNestedDrawer(payload);
}
// Saving a nested mod, close parent drawer
setNestedDrawer({});
const {max = 1, name: optionName} = getOptionHash()[option];
// Remove first modification for this option user if selecting more than the max allowed
if (modifications[option]?.length === max) {
// If the RG is configured to so that the last mod item is not replace return immediately
if (config.not_replace_mod) {
setGlobalError(
helpers.stringReplacer(Copy.ITEM_DETAILS_STATIC.MAX_ITEM_SELECTED, [
{replaceTarget: "{option}", replaceValue: optionName},
]),
);
return;
}
dispatch({
payload: {
...payload,
item: modifications[option][0]?.item,
},
type: "REMOVE_MOD",
});
}
setGlobalError(null);
dispatch({
payload,
type: "ADD_MOD",
});
};
const removeNestedModifier = (payload) => {
setGlobalError(null);
dispatch({
payload,
type: "REMOVE_MOD",
});
};
/**
* validateMods - cross check the itemHash options with the modifier state to validate quantities (min and max).
*
* @param {string} optionId - if the validation is nested, the option id to check
* @param {string} modId - if the validation is nested, the item id to check
*/
const validateMods = ({optionId, modId} = {}) => {
// Start with no errors
let newErrors = [];
const optionsHashByType = getOptionHash();
// Validate modifier state against options from nested item id within itemsHash
let reductionObj = itemsHash?.[modId]?.options ?? [];
// Get modifiers to validate from modifier state, recursively search for nesting
const nestedModsState = nestedModifierState({
modId,
modsState: modifications,
optionId,
});
// Reduce the options from itemsHash, tabs or itemInfo
newErrors = reductionObj.reduce((accu, i) => {
// Find this option in the optionsHash
const optionId = i?.id;
const option = optionId && optionsHashByType?.[optionId];
if (!option) return accu;
const {max = 0, min = 0} = option;
// Look for this option in the modifiers state
const currOptionModifiers = nestedModsState?.[optionId] ?? [];
// How many modifiers were selected
const selectedQty = currOptionModifiers?.length;
// If there is a minimum (greater than 0) and nothing selected in the modifiers state
// Or if there is a minimum and the length of our selected modifiers state of this option does not meet the minimum
if ((min && !selectedQty) || (min && selectedQty < min)) {
// Minimum was not met for this modifier
accu.push({
message: helpers.stringReplacer(
Copy.ITEM_DETAILS_STATIC.SELECTION_MIN_ERROR,
[
{
replaceTarget: "{option}",
replaceValue: min,
},
],
),
optionId,
type: "option",
});
}
// If there is a maximum (greater than 0) and the length of our selected modifiers state is greater than the maximum
if (max && selectedQty > max) {
// Too many modifiers were chosen
accu.push({
message: helpers.stringReplacer(
Copy.ITEM_DETAILS_STATIC.SELECTION_MAX_ERROR,
[
{
replaceTarget: "{option}",
replaceValue: max,
},
],
),
optionId,
type: "option",
});
}
return accu;
}, []);
// Update the error state with any new errors
setErrors(newErrors);
// Also return the errors in case it's needed
return newErrors;
};
const onSubmit = () => {
const errorList = validateMods({
modId: item,
optionId: option,
});
if (!errorList.length) {
// Edit an existing nested modifier
if (parent?.quantity) {
// For now, we replace the existing modifier
removeModifier(payload);
}
addModifier(payload);
}
};
const additionalProps = {
addNestedModifier,
closeDrawer,
decrement,
errors,
increment,
modifications,
name,
nestedDrawer,
onClose,
onSubmit,
options,
optionsHash,
quantity,
removeNestedModifier,
showQuantity,
style,
};
return <Component {...additionalProps} />;
};
export default withNestedModifiers;