emotion-food
v1.3.0
Published
Framework-agnostic cart + delivery logic (Redux slices, API client, storage helpers) extracted from e-commerce-food.
Maintainers
Readme
emotion-food
Framework-agnostic cart + delivery + checkout logic extracted from the
e-commerce-food project. The package ships Redux Toolkit slices, API
functions and small storage helpers — no UI, no Next.js coupling.
Drop it into any React / Redux project, call configure() once, and you have
the same cart and delivery behaviour that powers e-commerce-food.
Install
npm install emotion-food @reduxjs/toolkit react-redux redux-persist@reduxjs/toolkit is a peer dependency. react-redux / redux-persist are
only needed on the consumer side if you want to wire the slices into a store.
Configure (once, at app startup)
import { configure } from 'emotion-food';
configure({
baseUrl: process.env.NEXT_PUBLIC_BASE_URL,
// How to read the current locale — whatever your app uses.
resolveLocale: async () => 'en',
defaultLocale: 'en',
// How to read the user's access token (cookie, storage, auth lib, …).
getAccessToken: () => myAuth.getToken(),
// Optional: return a branch id to inject as both `branch` header + query.
getBranchId: () => localStorage.getItem('store_id'),
// Optional: called on a 401 so you can clear auth and redirect.
onUnauthorized: () => {
document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:01 GMT;';
window.dispatchEvent(new Event('auth:unauthorized'));
},
});If you don't configure, baseUrl defaults to '', tokens to null, and the
locale to en.
Redux store wiring
import { configureStore, combineReducers } from '@reduxjs/toolkit';
import { persistReducer, persistStore } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import { cartReducer, deliveryReducer } from 'emotion-food';
const root = combineReducers({
cart: cartReducer,
delivery: deliveryReducer,
});
const persisted = persistReducer(
{ key: 'root', storage, whitelist: ['cart', 'delivery'] },
root,
);
export const store = configureStore({ reducer: persisted });
export const persistor = persistStore(store);Slice keys matter. The bundled selectors (selectCartItems,
selectDelivery, …) expect the reducers to be mounted under cart and
delivery.
Cart
import {
// actions
setCart, resetCart,
// selectors
selectCartItems,
// api
getCart, addToCart, updateItemCart,
removeItemFromCart, removeItemFromCartByPayload,
applyCoupon, removeCoupon,
// storage
getCartId, setCartId, clearCartId,
} from 'emotion-food';The API helpers read the active cart_id from localStorage (key
cart_id). addToCart creates the cart on the first call and persists the
returned id automatically.
useCartActions hook
High-level React hook bundling addToCart, updateItemCart and
removeItemFromCart with dispatch of the returned cart into the Redux
cart slice and analytics/branch-metadata extraction. UI concerns (toasts,
i18n, analytics, address-state updates) are injected as callbacks.
import { useCartActions } from 'emotion-food';
const {
addToCartFirstItem,
incrementItem,
decrementItem,
removeItem,
addItemDirectly,
incrementItemDirectly,
removeItemDirectly,
addToCartWithOptions,
} = useCartActions({
t: useTranslations('Cart'), // optional, identity fn by default
onSuccess: (msg) => toast.success(msg),
onError: (msg) => toast.error(msg),
trackEvent, // optional, noop by default
onAddressUpdate: ({ address, branchName, branchId, orderType }) => {
// called when the cart response carries new branch/address info;
// wire this to your own address store or AddressContext
},
defaultCurrency: 'EGP', // fallback for analytics
});Requires react and react-redux in the consuming app (both declared as
optional peer dependencies).
Delivery
Replaces the old AddressContext with a Redux slice so it can be persisted
via redux-persist.
import {
// actions
setCity, setState, setAddress, setAddressId,
setOrderType, setBranchName, setBranchId,
setLatitude, setLongitude, setCoordinates,
setDelivery, resetDelivery,
// selectors
selectDelivery, selectOrderType, selectBranchId, selectCoordinates,
// popup / coverage APIs
deliverySuggestion, deliveryCase, pickupCase, saveLocationData,
// saved-address CRUD
getAddresses, getAddressLabels,
addAddress, editAddress, deleteAddress,
} from 'emotion-food';Typical flow:
// 1. Check coverage (guest or logged in)
const coverage = await deliveryCase({ latitude, longitude });
// 2. If covered, persist the selection against the cart
if (coverage?.success) {
await saveLocationData({
order_type: 'delivery',
latitude,
longitude,
address_id: selectedAddressId, // optional, for logged-in users
});
dispatch(setDelivery({
orderType: 'delivery',
branchId: coverage.branch.id,
branchName: coverage.branch.name,
latitude,
longitude,
}));
}Checkout
import { getCheckout, placeOrder } from 'emotion-food';
const summary = await getCheckout();
const order = await placeOrder({ payment_method: 'card' });placeOrder forwards the persisted cart_id and attaches the auth token
from configure({ getAccessToken }).
Direct HTTP client
If you need to hit other endpoints on the same backend with the same headers / locale / branch injection, use the exported client:
import { get, post, put, patch, del, customFetch } from 'emotion-food';
const data = await get('/e-commerce/something');What is not included
- UI (modals, pages, components) — this is a logic-only package.
userandwishlistslices — extract those separately if you need them.- Next.js specifics (
next/headers,next-intl,notFound()) — replaced by the pluggableresolveLocale/onUnauthorizedhooks inconfigure().
Subpath exports
For tree-shaking or narrower imports:
| Subpath | Exports |
|------------------------------|---------------------------------------------|
| emotion-food/config | configure, getConfig, resetConfig |
| emotion-food/http | customFetch, get, post, put, patch, del |
| emotion-food/storage/cart | getCartId, setCartId, clearCartId |
| emotion-food/storage/cookies | cookie helpers |
| emotion-food/cart | cart slice + api |
| emotion-food/delivery | delivery slice + api + addresses |
| emotion-food/checkout | checkout api |
