diff --git a/src/components/ProtectedRoute.tsx b/src/components/ProtectedRoute.tsx new file mode 100644 index 000000000..e65fb5735 --- /dev/null +++ b/src/components/ProtectedRoute.tsx @@ -0,0 +1,36 @@ +import { Navigate } from 'react-router-dom'; +import { useAppSelector } from '../store/hooks'; + +interface ProtectedRouteProps { + children: React.ReactNode; + requireAuth?: boolean; + requireOnboarded?: boolean; + redirectTo?: string; +} + +/** + * Protected route component that handles authentication and onboarding checks + */ +const ProtectedRoute = ({ + children, + requireAuth = true, + requireOnboarded = false, + redirectTo, +}: ProtectedRouteProps) => { + const token = useAppSelector((state) => state.auth.token); + const isOnboarded = useAppSelector((state) => state.auth.isOnboarded); + + // If auth is required but user is not logged in + if (requireAuth && !token) { + return ; + } + + // If onboarding is required but user is not onboarded + if (requireOnboarded && !isOnboarded) { + return ; + } + + return <>{children}; +}; + +export default ProtectedRoute; diff --git a/src/components/PublicRoute.tsx b/src/components/PublicRoute.tsx new file mode 100644 index 000000000..48071e81c --- /dev/null +++ b/src/components/PublicRoute.tsx @@ -0,0 +1,32 @@ +import { Navigate } from 'react-router-dom'; +import { useAppSelector } from '../store/hooks'; + +interface PublicRouteProps { + children: React.ReactNode; + redirectTo?: string; +} + +/** + * Public route component that redirects authenticated users + * If logged in and onboarded -> redirect to /home + * If logged in but not onboarded -> redirect to /onboarding + */ +const PublicRoute = ({ children, redirectTo }: PublicRouteProps) => { + const token = useAppSelector((state) => state.auth.token); + const isOnboarded = useAppSelector((state) => state.auth.isOnboarded); + + // If user is logged in and onboarded, redirect to home + if (token && isOnboarded) { + return ; + } + + // If user is logged in but not onboarded, redirect to onboarding + if (token && !isOnboarded) { + return ; + } + + // User is not logged in, show public route + return <>{children}; +}; + +export default PublicRoute; diff --git a/src/pages/onboarding/Onboarding.tsx b/src/pages/onboarding/Onboarding.tsx index 5223883bd..91594be4a 100644 --- a/src/pages/onboarding/Onboarding.tsx +++ b/src/pages/onboarding/Onboarding.tsx @@ -1,5 +1,7 @@ import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; +import { useAppDispatch } from '../../store/hooks'; +import { setOnboarded } from '../../store/authSlice'; import ProgressIndicator from '../../components/ProgressIndicator'; import LottieAnimation from '../../components/LottieAnimation'; import FeaturesStep from './steps/FeaturesStep'; @@ -10,6 +12,7 @@ import GetStartedStep from './steps/GetStartedStep'; const Onboarding = () => { const navigate = useNavigate(); + const dispatch = useAppDispatch(); const [currentStep, setCurrentStep] = useState(1); const totalSteps = 5; @@ -29,6 +32,7 @@ const Onboarding = () => { }; const handleComplete = () => { + dispatch(setOnboarded(true)); navigate('/home'); }; diff --git a/src/store/authSlice.ts b/src/store/authSlice.ts index 17e9e6c5f..2ebbb5497 100644 --- a/src/store/authSlice.ts +++ b/src/store/authSlice.ts @@ -2,10 +2,12 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; interface AuthState { token: string | null; + isOnboarded: boolean; } const initialState: AuthState = { token: null, + isOnboarded: false, }; const authSlice = createSlice({ @@ -17,9 +19,13 @@ const authSlice = createSlice({ }, clearToken: (state) => { state.token = null; + state.isOnboarded = false; + }, + setOnboarded: (state, action: PayloadAction) => { + state.isOnboarded = action.payload; }, }, }); -export const { setToken, clearToken } = authSlice.actions; +export const { setToken, clearToken, setOnboarded } = authSlice.actions; export default authSlice.reducer; diff --git a/src/store/index.ts b/src/store/index.ts index aa726403b..c695e14bf 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -8,8 +8,8 @@ import socketReducer from './socketSlice'; const authPersistConfig = { key: 'auth', storage, - // Only persist the token - whitelist: ['token'], + // Persist token and onboarding status + whitelist: ['token', 'isOnboarded'], }; const persistedAuthReducer = persistReducer(authPersistConfig, authReducer);