Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions apps/retail/components/header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 }

Expand Down Expand Up @@ -137,9 +139,8 @@ const TopHeader: React.FC<TopHeaderProps> = ({ 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`)
Comment on lines 141 to 144
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Incorrect usage of useDispatch hook in event handler

The useDispatch hook is being called inside an event handler, which violates React's rules of hooks. Hooks should only be called at the top level of your component.

- onClick={() => {
-   const dispatch = useDispatch()
-   logoutUser(dispatch, router)
-   router.push(`/homePage`)
- }}
+ const dispatch = useDispatch()
+ onClick={() => {
+   logoutUser(dispatch, router)
+   // Remove this line as logoutUser already redirects to login page
+   // router.push(`/homePage`)
+ }}

Committable suggestion skipped: line range outside the PR's diff.

}}
src="/images/Home_icon.svg"
Expand Down Expand Up @@ -179,6 +180,20 @@ const TopHeader: React.FC<TopHeaderProps> = ({ handleMenuClick }) => {
/>
{t['orderHistory']}
</Box>
<Box
onClick={() => {
const dispatch = useDispatch()
logoutUser(dispatch, router)
setMenuModalOpen(false)
}}
className={styles.top_header_modal}
>
<Image
src="/images/logout.svg"
alt="Logout icon"
/>
Logout
</Box>
Comment on lines +183 to +196
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Incorrect usage of useDispatch hook in Logout menu item

Similar to the home icon issue, useDispatch is being called inside an event handler. Additionally, the menu item's behavior conflicts with the logout utility's redirect.

+ const dispatch = useDispatch()
  <Box
    onClick={() => {
-     const dispatch = useDispatch()
      logoutUser(dispatch, router)
      setMenuModalOpen(false)
    }}
    className={styles.top_header_modal}
  >

Consider removing setMenuModalOpen(false) as it's unnecessary - the logout function will redirect the user away from this page anyway.

Committable suggestion skipped: line range outside the PR's diff.

</BottomModal>
</>
)
Expand Down
7 changes: 6 additions & 1 deletion apps/retail/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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 (
<BecknProvider
Expand Down
7 changes: 5 additions & 2 deletions apps/retail/pages/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import ProductCardRenderer from '@components/productCard/product-card-renderer'
import SearchBar from '../components/header/SearchBar'
import { useLanguage } from '../hooks/useLanguage'
import { ParsedItemModel } from '../types/search.types'
import { fetchWithCacheControl } from '@utils/api-utils'
import TopSheet from '@components/topSheet/TopSheet'
import LoaderWithMessage from '@components/loader/LoaderWithMessage'
import Filter from '../components/filter/Filter'
Expand Down Expand Up @@ -78,8 +79,10 @@ const Search = () => {

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))
Expand Down
5 changes: 5 additions & 0 deletions apps/retail/public/images/logout.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
52 changes: 33 additions & 19 deletions apps/retail/store/index.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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
Expand Down
42 changes: 42 additions & 0 deletions apps/retail/utilities/api-utils.ts
Original file line number Diff line number Diff line change
@@ -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
})
}
Comment on lines +30 to +42
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Prevent adding multiple identical interceptors.

If setupAxiosInterceptors is called multiple times, it will add the same interceptor multiple times. Consider adding a check to prevent this.

+let isInterceptorSetup = false;

 export const setupAxiosInterceptors = () => {
+  if (isInterceptorSetup) return;
+
   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
   })
+  
+  isInterceptorSetup = true;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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
})
}
let isInterceptorSetup = false;
export const setupAxiosInterceptors = () => {
if (isInterceptorSetup) return;
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
})
isInterceptorSetup = true;
}

41 changes: 41 additions & 0 deletions apps/retail/utilities/logout.ts
Original file line number Diff line number Diff line change
@@ -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<any>, 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<string, string | null> = {}

// 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('/')
}