diff --git a/apps/retail/components/header/index.tsx b/apps/retail/components/header/index.tsx index 413dc70c1..2a97523af 100644 --- a/apps/retail/components/header/index.tsx +++ b/apps/retail/components/header/index.tsx @@ -10,6 +10,8 @@ import Qrcode from '@components/qrCode/Qrcode' import BottomModalScan from '@components/BottomModal/BottomModalScan' import BecknButton from '@beckn-ui/molecules/src/components/button/Button' import TopSheet from '@components/topSheet/TopSheet' +import { useDispatch } from 'react-redux' +import { logoutUser } from '@utils/logout' type PathnameObjectType = { [key: string]: string } @@ -137,9 +139,8 @@ const TopHeader: React.FC = ({ handleMenuClick }) => { w={'20px'} h={'20px'} onClick={() => { - const user = localStorage.getItem('userPhone') as string - localStorage.clear() - localStorage.setItem('userPhone', user) + const dispatch = useDispatch() + logoutUser(dispatch, router) router.push(`/homePage`) }} src="/images/Home_icon.svg" @@ -179,6 +180,20 @@ const TopHeader: React.FC = ({ handleMenuClick }) => { /> {t['orderHistory']} + { + const dispatch = useDispatch() + logoutUser(dispatch, router) + setMenuModalOpen(false) + }} + className={styles.top_header_modal} + > + Logout icon + Logout + ) diff --git a/apps/retail/pages/_app.tsx b/apps/retail/pages/_app.tsx index c87af32f5..51d644f7f 100644 --- a/apps/retail/pages/_app.tsx +++ b/apps/retail/pages/_app.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useEffect } from 'react' import type { AppProps } from 'next/app' import Layout from '@components/layout/Layout' @@ -13,11 +13,16 @@ import '../styles/globals.css' import { Provider } from 'react-redux' import store from '@store/index' import { Garuda } from 'garudaa' +import { setupAxiosInterceptors } from '@utils/api-utils' +// Initialize Garuda Garuda.init({ projectId: '65c0d663cbe90cafae9185f6', host: 'https://garuda-api.becknprotocol.io' }) + +// Set up axios interceptors for cache control +setupAxiosInterceptors() function MyApp({ Component, pageProps }: AppProps) { return ( { const fetchDataForSearch = () => { setIsLoading(true) - axios - .post(`${apiUrl}/client/v2/search`, searchPayload) + fetchWithCacheControl(`${apiUrl}/client/v2/search`, { + method: 'POST', + data: searchPayload + }) .then(res => { const parsedSearchItems = transformData(res.data) localStorage.setItem('searchItems', JSON.stringify(parsedSearchItems)) diff --git a/apps/retail/public/images/logout.svg b/apps/retail/public/images/logout.svg new file mode 100644 index 000000000..e57a28911 --- /dev/null +++ b/apps/retail/public/images/logout.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/retail/store/index.ts b/apps/retail/store/index.ts index e5fa66036..07c1751b7 100644 --- a/apps/retail/store/index.ts +++ b/apps/retail/store/index.ts @@ -1,4 +1,4 @@ -import { configureStore } from '@reduxjs/toolkit' +import { configureStore, combineReducers } from '@reduxjs/toolkit' import specialOfferProductsReducer from './specialOfferProducts-slice' import newestProductReducer from './newestProduct-slice' import SortedProductsListReducer from './sortedProductList-slice' @@ -13,25 +13,39 @@ import favoriteReducer from './favorite-slice' import responseDataReducer from './responseData-slice' import geoMapLocationSearchReducer from './geoMapLocationSearch-slice' +// Combine all reducers +const appReducer = combineReducers({ + specialOfferProductsList: specialOfferProductsReducer, + newestProductsList: newestProductReducer, + sortedProductsList: SortedProductsListReducer, + cartUi: cartUiReducer, + cart: cartSliceReducer, + userInfo: userInfoReducer, + sideNavBar: sideNavBarReducer, + megaMenu: megaMenuReducer, + activeMenuItem: activeMenuItemReducer, + settingBox: settingBoxReducer, + favorite: favoriteReducer, + transactionId: responseDataReducer, + quoteResponse: responseDataReducer, + customerDetails: responseDataReducer, + initResponse: responseDataReducer, + geoLocationSearchPageUI: geoMapLocationSearchReducer +}) + +// Root reducer with state reset on logout +const rootReducer = (state: any, action: any) => { + // When logout action is dispatched, reset all state except userInfo (which is handled in its own reducer) + if (action.type === 'userInfo/userLogout') { + // Return a fresh state + state = undefined + } + + return appReducer(state, action) +} + const store = configureStore({ - reducer: { - specialOfferProductsList: specialOfferProductsReducer, - newestProductsList: newestProductReducer, - sortedProductsList: SortedProductsListReducer, - cartUi: cartUiReducer, - cart: cartSliceReducer, - userInfo: userInfoReducer, - sideNavBar: sideNavBarReducer, - megaMenu: megaMenuReducer, - activeMenuItem: activeMenuItemReducer, - settingBox: settingBoxReducer, - favorite: favoriteReducer, - transactionId: responseDataReducer, - quoteResponse: responseDataReducer, - customerDetails: responseDataReducer, - initResponse: responseDataReducer, - geoLocationSearchPageUI: geoMapLocationSearchReducer - }, + reducer: rootReducer, middleware: getDefaultMiddleware => getDefaultMiddleware({ serializableCheck: false diff --git a/apps/retail/utilities/api-utils.ts b/apps/retail/utilities/api-utils.ts new file mode 100644 index 000000000..4fd755606 --- /dev/null +++ b/apps/retail/utilities/api-utils.ts @@ -0,0 +1,42 @@ +import axios, { AxiosRequestConfig } from 'axios' + +/** + * Fetch utility with proper cache control headers + * @param url The URL to fetch + * @param options Additional axios request options + * @returns Promise with the axios response + */ +export const fetchWithCacheControl = async (url: string, options: AxiosRequestConfig = {}) => { + const defaultHeaders = { + 'Cache-Control': 'no-cache, no-store, must-revalidate', + Pragma: 'no-cache', + Expires: '0' + } + + const requestOptions: AxiosRequestConfig = { + ...options, + headers: { + ...defaultHeaders, + ...options.headers + } + } + + return axios(url, requestOptions) +} + +/** + * Adds cache control headers to all axios requests + */ +export const setupAxiosInterceptors = () => { + axios.interceptors.request.use(config => { + // Add cache control headers to all requests + config.headers = { + ...config.headers, + 'Cache-Control': 'no-cache, no-store, must-revalidate', + Pragma: 'no-cache', + Expires: '0' + } + + return config + }) +} diff --git a/apps/retail/utilities/logout.ts b/apps/retail/utilities/logout.ts new file mode 100644 index 000000000..688ee7a90 --- /dev/null +++ b/apps/retail/utilities/logout.ts @@ -0,0 +1,41 @@ +import { userInfoActions } from '../store/user-slice' +import Cookies from 'js-cookie' +import { NextRouter } from 'next/router' +import { Dispatch } from '@reduxjs/toolkit' + +/** + * Comprehensive logout utility that clears all user data and caches + * @param dispatch Redux dispatch function + * @param router Next.js router + */ +export const logoutUser = (dispatch: Dispatch, router: NextRouter) => { + // Clear Redux state + dispatch(userInfoActions.userLogout()) + + // Clear cookies + Cookies.remove('userInfo') + Cookies.remove('authToken') + + // List of localStorage keys to preserve (if needed) + const keysToPreserve = ['userPhone'] + const preservedData: Record = {} + + // Save data that needs to be preserved + keysToPreserve.forEach(key => { + preservedData[key] = localStorage.getItem(key) + }) + + // Clear all localStorage + localStorage.clear() + + // Restore preserved data + Object.entries(preservedData).forEach(([key, value]) => { + if (value) localStorage.setItem(key, value) + }) + + // Clear session storage + sessionStorage.clear() + + // Redirect to login page + router.push('/') +}