diff --git a/src/components/auth/RouteGuard.tsx b/src/components/auth/RouteGuard.tsx new file mode 100644 index 0000000..78635fa --- /dev/null +++ b/src/components/auth/RouteGuard.tsx @@ -0,0 +1,87 @@ +import { useEffect, useState } from 'react' +import { Navigate } from 'react-router-dom' +import { useWallet } from '../../hooks/useWallet' +import { useAuth } from '../../hooks/useAuth' +import { useRoleStore, type UserRole } from '../../stores/role.store' +import type { ReactNode } from 'react' + +interface RouteGuardProps { + children: ReactNode + allowedRole?: UserRole + requireAuth?: boolean +} + +export function RouteGuard({ + children, + allowedRole, + requireAuth = true, +}: RouteGuardProps) { + const { isConnected } = useWallet() + const { checkAuth, logout } = useAuth() + const { role, roleSelected } = useRoleStore() + const [isChecking, setIsChecking] = useState(true) + const [isAuthenticated, setIsAuthenticated] = useState(false) + + useEffect(() => { + const validateAuth = () => { + setIsChecking(true) + + if (!requireAuth) { + setIsAuthenticated(true) + setIsChecking(false) + return + } + + // Check wallet connection + if (!isConnected) { + setIsAuthenticated(false) + setIsChecking(false) + return + } + + // Check JWT token validity + const isValid = checkAuth() + if (!isValid) { + logout() + setIsAuthenticated(false) + setIsChecking(false) + return + } + + setIsAuthenticated(true) + setIsChecking(false) + } + + validateAuth() + }, [isConnected, checkAuth, logout, requireAuth]) + + if (isChecking) { + return ( +
+
+
+ ) + } + + // Redirect unauthenticated users + if (requireAuth && !isAuthenticated) { + return + } + + // Redirect if wallet not connected + if (requireAuth && !isConnected) { + return + } + + // Redirect if role not selected + if (requireAuth && !roleSelected) { + return + } + + // Redirect if role doesn't match requirement + if (requireAuth && allowedRole && role !== allowedRole) { + return + } + + return <>{children} +} diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts index b0c59bd..cf3775e 100644 --- a/src/hooks/useAuth.ts +++ b/src/hooks/useAuth.ts @@ -11,6 +11,17 @@ function bytesToBase64(bytes: Uint8Array): string { return btoa(binary) } +function isTokenExpired(token: string): boolean { + try { + const payload = JSON.parse(atob(token.split('.')[1])) + const exp = payload.exp + if (!exp) return false + return Date.now() >= exp * 1000 + } catch { + return true + } +} + export const useAuth = () => { const [isAuthLoading, setIsAuthLoading] = useState(false) const [authError, setAuthError] = useState(null) @@ -49,9 +60,22 @@ export const useAuth = () => { clearTokens() } + const checkAuth = () => { + const token = localStorage.getItem('accessToken') + if (!token) { + return false + } + if (isTokenExpired(token)) { + clearTokens() + return false + } + return true + } + return { authenticate, logout, + checkAuth, isAuthLoading, authError, isAuthenticated, diff --git a/src/router/index.tsx b/src/router/index.tsx index e3f34e9..b66d413 100644 --- a/src/router/index.tsx +++ b/src/router/index.tsx @@ -1,4 +1,4 @@ -import { createBrowserRouter, RouterProvider, Navigate } from 'react-router-dom' +import { createBrowserRouter, RouterProvider } from 'react-router-dom' import { Layout } from '../components/layout/Layout' import { Home } from '../pages/Home' import { Docs } from '../pages/Docs' @@ -16,32 +16,8 @@ import { LearnerProfile } from '../pages/LearnerProfile' import { NotFound } from '../pages/NotFound' import { History } from '../pages/History' import { RoleSelect } from '../pages/RoleSelect' -import { useRoleStore } from '../stores/role.store' +import { RouteGuard } from '../components/auth/RouteGuard' import type { UserRole } from '../stores/role.store' -import { useWallet } from '../hooks/useWallet' -import type { ReactNode } from 'react' - -function RoleGuard({ - allowedRole, - children, -}: { - allowedRole: UserRole - children: ReactNode -}) { - const { isConnected } = useWallet() - const { role, roleSelected } = useRoleStore() - - if (!isConnected) { - return - } - if (!roleSelected) { - return - } - if (role !== allowedRole) { - return - } - return <>{children} -} const router = createBrowserRouter([ { @@ -58,11 +34,11 @@ const router = createBrowserRouter([ }, { path: '/role-select', - element: , + element: , }, { path: '/dashboard', - element: , + element: , }, { path: '/vendors', @@ -70,11 +46,11 @@ const router = createBrowserRouter([ }, { path: '/vendors/dashboard', - element: , + element: , }, { path: '/vendors/register', - element: , + element: , }, { path: '/vendors/:id', @@ -82,19 +58,19 @@ const router = createBrowserRouter([ }, { path: '/sponsors', - element: , + element: , }, { path: '/sponsors/onboarding', - element: , + element: , }, { path: '/mentor', - element: , + element: , }, { path: '/vouch', - element: , + element: , }, { path: '/learner/:walletAddress', @@ -102,7 +78,7 @@ const router = createBrowserRouter([ }, { path: '/history', - element: , + element: , }, { path: '*',