grocery-app/frontend/src/context/StoreContext.jsx

211 lines
6.4 KiB
JavaScript

import { createContext, useContext, useEffect, useState } from 'react';
import { isTransientApiError } from '../api/offlineCache';
import { getHouseholdStores, getLocationZones } from '../api/stores';
import { AuthContext } from './AuthContext';
import { HouseholdContext } from './HouseholdContext';
const DEFAULT_LOCATION_NAME = "Default Location";
function getHouseholdStoreKey(store) {
return String(store?.household_store_id ?? store?.store_id ?? store?.id ?? "");
}
function isDefaultLocationName(store) {
return store?.location_name === DEFAULT_LOCATION_NAME || store?.display_name === store?.name;
}
function chooseStoreLocation(locations) {
if (!locations || locations.length === 0) return null;
return (
locations.find(isDefaultLocationName) ||
locations.find((store) => store.is_default) ||
locations[0]
);
}
function chooseStoreByHouseholdStoreId(stores, householdStoreId) {
const matchingLocations = stores.filter(
(store) => getHouseholdStoreKey(store) === String(householdStoreId)
);
return chooseStoreLocation(matchingLocations);
}
function getLocationStorageKey(householdId, householdStoreId) {
return `activeStoreLocationId_${householdId}_${householdStoreId}`;
}
function chooseLocationByStoredIds(stores, householdId, householdStoreId) {
const storageKey = getLocationStorageKey(householdId, householdStoreId);
const savedLocationId = localStorage.getItem(storageKey);
if (!savedLocationId) return null;
return stores.find(
(store) =>
String(store.id) === String(savedLocationId) &&
getHouseholdStoreKey(store) === String(householdStoreId)
);
}
export const StoreContext = createContext({
stores: [],
activeStore: null,
zones: [],
loading: false,
zonesLoading: false,
error: null,
setActiveStore: () => { },
refreshStores: () => { },
refreshZones: () => { },
});
export const StoreProvider = ({ children }) => {
const { token } = useContext(AuthContext);
const { activeHousehold } = useContext(HouseholdContext);
const [stores, setStores] = useState([]);
const [activeStore, setActiveStoreState] = useState(null);
const [zones, setZones] = useState([]);
const [loading, setLoading] = useState(false);
const [zonesLoading, setZonesLoading] = useState(false);
const [error, setError] = useState(null);
// Load stores when household changes
useEffect(() => {
if (token && activeHousehold) {
loadStores();
} else {
// Clear state when logged out or no household
setStores([]);
setActiveStoreState(null);
setZones([]);
}
}, [token, activeHousehold?.id]);
// Load active store from localStorage on mount (per household)
useEffect(() => {
if (!activeHousehold || stores.length === 0) return;
const householdStoreStorageKey = `activeHouseholdStoreId_${activeHousehold.id}`;
const legacyLocationStorageKey = `activeStoreId_${activeHousehold.id}`;
const savedHouseholdStoreId = localStorage.getItem(householdStoreStorageKey);
if (savedHouseholdStoreId) {
const store =
chooseLocationByStoredIds(stores, activeHousehold.id, savedHouseholdStoreId) ||
chooseStoreByHouseholdStoreId(stores, savedHouseholdStoreId);
if (store) {
setActiveStoreState(store);
return;
}
}
const savedLocationId = localStorage.getItem(legacyLocationStorageKey);
if (savedLocationId) {
const savedLocation = stores.find(s => String(s.id) === String(savedLocationId));
const store = savedLocation || chooseStoreByHouseholdStoreId(stores, getHouseholdStoreKey(savedLocation));
if (store) {
const householdStoreId = getHouseholdStoreKey(store);
setActiveStoreState(store);
localStorage.setItem(householdStoreStorageKey, householdStoreId);
localStorage.setItem(
getLocationStorageKey(activeHousehold.id, householdStoreId),
String(store.id)
);
return;
}
}
// No saved store or not found, use the default store's representative location.
const defaultStore = chooseStoreByHouseholdStoreId(
stores,
getHouseholdStoreKey(stores.find(s => s.is_default) || stores[0])
);
setActiveStoreState(defaultStore);
const householdStoreId = getHouseholdStoreKey(defaultStore);
localStorage.setItem(householdStoreStorageKey, householdStoreId);
localStorage.setItem(
getLocationStorageKey(activeHousehold.id, householdStoreId),
String(defaultStore.id)
);
}, [stores, activeHousehold]);
useEffect(() => {
if (token && activeHousehold?.id && activeStore?.id) {
loadZones();
} else {
setZones([]);
}
}, [token, activeHousehold?.id, activeStore?.id]);
const loadStores = async () => {
if (!token || !activeHousehold) return;
setLoading(true);
setError(null);
try {
const response = await getHouseholdStores(activeHousehold.id);
setStores(response.data);
} catch (err) {
console.error('[StoreContext] Failed to load stores:', err);
setError(
err.response?.data?.error?.message ||
err.response?.data?.message ||
'Failed to load stores'
);
if (!isTransientApiError(err)) {
setStores([]);
setActiveStoreState(null);
}
} finally {
setLoading(false);
}
};
const setActiveStore = (store) => {
setActiveStoreState(store);
if (store && activeHousehold) {
const storageKey = `activeHouseholdStoreId_${activeHousehold.id}`;
const householdStoreId = getHouseholdStoreKey(store);
localStorage.setItem(storageKey, householdStoreId);
localStorage.setItem(
getLocationStorageKey(activeHousehold.id, householdStoreId),
String(store.id)
);
}
};
const loadZones = async () => {
if (!token || !activeHousehold?.id || !activeStore?.id) return;
setZonesLoading(true);
try {
const response = await getLocationZones(activeHousehold.id, activeStore.id);
setZones(response.data?.zones || []);
} catch (err) {
console.error('[StoreContext] Failed to load zones:', err);
if (!isTransientApiError(err)) {
setZones([]);
}
} finally {
setZonesLoading(false);
}
};
const value = {
stores,
activeStore,
zones,
loading,
zonesLoading,
error,
setActiveStore,
refreshStores: loadStores,
refreshZones: loadZones,
};
return (
<StoreContext.Provider value={value}>
{children}
</StoreContext.Provider>
);
};