diff --git a/frontend/app/(dashboard)/_components/user-nav.tsx b/frontend/app/(dashboard)/_components/user-nav.tsx index 8abd30e..e1c5ffe 100644 --- a/frontend/app/(dashboard)/_components/user-nav.tsx +++ b/frontend/app/(dashboard)/_components/user-nav.tsx @@ -15,13 +15,12 @@ import { DropdownMenuTrigger, } from '@/components/ui/new-york/ui/dropdown-menu'; import { useRetrieveUserQuery } from '@/redux/features/authApiSlice'; +import { RootState } from '@/redux/store'; +import { useSelector } from 'react-redux'; export function UserNav() { - const { - data: user, - isLoading, - isFetching, - } = useRetrieveUserQuery(); + const user = useSelector((state: RootState) => state.auth.user); + console.log('user in navbar', user); return ( diff --git a/frontend/app/(dashboard)/dashboard/page.tsx b/frontend/app/(dashboard)/dashboard/page.tsx index b197f1b..156b946 100644 --- a/frontend/app/(dashboard)/dashboard/page.tsx +++ b/frontend/app/(dashboard)/dashboard/page.tsx @@ -2,9 +2,13 @@ import { useRetrieveUserQuery } from '@/redux/features/authApiSlice'; import { List, Spinner } from '@/components/common'; +import { useSelector } from 'react-redux'; +import { RootState } from '@/redux/store'; export default function Page() { - const { data: user, isLoading, isFetching } = useRetrieveUserQuery(); + + const user = useSelector((state: RootState) => state.auth.user); + console.log('user', user); const config = [ { @@ -21,13 +25,6 @@ export default function Page() { }, ]; - if (isLoading || isFetching) { - return ( -
- -
- ); - } return ( <> diff --git a/frontend/hooks/use-verify.ts b/frontend/hooks/use-verify.ts index 9d5a4e1..5980066 100644 --- a/frontend/hooks/use-verify.ts +++ b/frontend/hooks/use-verify.ts @@ -1,12 +1,14 @@ import { useEffect } from 'react'; import { useAppDispatch } from '@/redux/hooks'; -import { setAuth, finishInitialLoad } from '@/redux/features/authSlice'; -import { useVerifyMutation } from '@/redux/features/authApiSlice'; +import { setAuth, setUser, finishInitialLoad } from '@/redux/features/authSlice'; +import { useRetrieveUserQuery, useVerifyMutation } from '@/redux/features/authApiSlice'; export default function useVerify() { const dispatch = useAppDispatch(); - const [verify] = useVerifyMutation(); + const [verify] = useVerifyMutation(); + + const { data: userData } = useRetrieveUserQuery(); useEffect(() => { verify(undefined) @@ -17,5 +19,11 @@ export default function useVerify() { .finally(() => { dispatch(finishInitialLoad()); }); - }, []); + }, []); + + useEffect(() => { + if (userData) { + dispatch(setUser(userData)); + } + }, [userData, dispatch]); } diff --git a/frontend/redux/features/authSlice.ts b/frontend/redux/features/authSlice.ts index 41c1db6..3bf86af 100644 --- a/frontend/redux/features/authSlice.ts +++ b/frontend/redux/features/authSlice.ts @@ -1,30 +1,36 @@ -import { createSlice } from '@reduxjs/toolkit'; +import { User } from '@/typings'; +import { PayloadAction, createSlice } from '@reduxjs/toolkit'; interface AuthState { isAuthenticated: boolean; - isLoading: boolean; + isLoading: boolean; + user: User | null; } const initialState = { isAuthenticated: false, - isLoading: true, + isLoading: true, + user: null, } as AuthState; const authSlice = createSlice({ - name: 'auth', - initialState, - reducers: { - setAuth: state => { - state.isAuthenticated = true; - }, - logout: state => { - state.isAuthenticated = false; - }, - finishInitialLoad: state => { - state.isLoading = false; - }, - }, + name: 'auth', + initialState, + reducers: { + setAuth: (state) => { + state.isAuthenticated = true; + }, + setUser(state, action: PayloadAction) { + state.user = action.payload; + }, + logout: (state) => { + state.isAuthenticated = false; + }, + finishInitialLoad: (state) => { + state.isLoading = false; + }, + }, }); -export const { setAuth, logout, finishInitialLoad } = authSlice.actions; +export const { setAuth, setUser, logout, finishInitialLoad } = authSlice.actions; export default authSlice.reducer; diff --git a/frontend/redux/localStorage.tsx b/frontend/redux/localStorage.tsx new file mode 100644 index 0000000..ba58a5c --- /dev/null +++ b/frontend/redux/localStorage.tsx @@ -0,0 +1,31 @@ +// utils/localStorage.js + +import { RootState } from './store'; + +export const loadState = () => { + try { + if (typeof window !== 'undefined') { + console.log('You are on the browser'); + return undefined; + } else { + console.log('You are on the server'); + // 👉️ can't use localStorage + } + const serializedState = localStorage.getItem('state'); + if (serializedState === null) { + return undefined; + } + return JSON.parse(serializedState); + } catch (err) { + return undefined; + } +}; + +export const saveState = (state: RootState) => { + try { + const serializedState = JSON.stringify(state); + localStorage.setItem('state', serializedState); + } catch { + // ignore write errors + } +}; diff --git a/frontend/redux/provider.tsx b/frontend/redux/provider.tsx index 628aec6..a31f3ca 100644 --- a/frontend/redux/provider.tsx +++ b/frontend/redux/provider.tsx @@ -1,12 +1,18 @@ 'use client'; - -import { store } from './store'; +import { useRef } from 'react'; import { Provider } from 'react-redux'; +import { AppStore, makeStore } from './store'; -interface Props { - children: React.ReactNode; -} +export default function StoreProvider({ + children, +}: { + children: React.ReactNode; +}) { + const storeRef = useRef(); + if (!storeRef.current) { + // Create the store instance the first time this renders + storeRef.current = makeStore(); + } -export default function CustomProvider({ children }: Props) { - return {children}; + return {children}; } diff --git a/frontend/redux/store.ts b/frontend/redux/store.ts index 2708dab..f95ff8f 100644 --- a/frontend/redux/store.ts +++ b/frontend/redux/store.ts @@ -1,16 +1,31 @@ import { configureStore } from '@reduxjs/toolkit'; import { apiSlice } from './services/apiSlice'; import authReducer from './features/authSlice'; +import { loadState, saveState } from './localStorage'; // Import the utility functions -export const store = configureStore({ - reducer: { - [apiSlice.reducerPath]: apiSlice.reducer, - auth: authReducer, - }, - middleware: getDefaultMiddleware => - getDefaultMiddleware().concat(apiSlice.middleware), - devTools: process.env.NODE_ENV !== 'production', -}); +export const makeStore = () => { + let preloadedState; // Load any saved state -export type RootState = ReturnType<(typeof store)['getState']>; -export type AppDispatch = (typeof store)['dispatch']; + const store = configureStore({ + reducer: { + [apiSlice.reducerPath]: apiSlice.reducer, + auth: authReducer, + }, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware().concat(apiSlice.middleware), + preloadedState, // Pass the loaded state + devTools: process.env.NODE_ENV !== 'production', + }); + + store.subscribe(() => { + saveState(store.getState()); // Save the current state to local storage + }); + + return store; +}; + +// Infer the type of makeStore +export type AppStore = ReturnType; +// Infer the `RootState` and `AppDispatch` types from the store itself +export type RootState = ReturnType; +export type AppDispatch = AppStore['dispatch']; diff --git a/frontend/typings.d.ts b/frontend/typings.d.ts index 77b7599..bf30910 100644 --- a/frontend/typings.d.ts +++ b/frontend/typings.d.ts @@ -1,3 +1,10 @@ export type Todo = { id: number; }; + +export type User = { + id: number; + first_name: string; + last_name: string; + email: string; +}; \ No newline at end of file