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: '*',