|
1 | | -import { useParams, Outlet, useLocation, useNavigate } from 'react-router-dom'; |
| 1 | +import { |
| 2 | + useParams, |
| 3 | + Outlet, |
| 4 | + useLocation, |
| 5 | + useNavigate, |
| 6 | + matchPath, |
| 7 | +} from 'react-router-dom'; |
2 | 8 | import { useEffect, useState, createContext, useContext, useMemo } from 'react'; |
3 | 9 | import { useRoleAccess } from '@/hooks/useRoleAccess'; |
4 | 10 | import { CourseNavBar } from '@/components/CourseNavBar'; |
@@ -76,17 +82,17 @@ export function CourseLayout() { |
76 | 82 | // This prevents students from having admin privileges after admin sign-out |
77 | 83 | const hasRoleMismatch = useMemo(() => { |
78 | 84 | if (!user || !effectiveRole) return false; |
79 | | - |
| 85 | + |
80 | 86 | // If user is not an admin globally, they should never have ADMIN effectiveRole |
81 | 87 | if (user.role !== 'ADMIN' && effectiveRole === 'ADMIN') { |
82 | 88 | return true; |
83 | 89 | } |
84 | | - |
| 90 | + |
85 | 91 | // If user is a student globally, they should never have ADMIN effectiveRole |
86 | 92 | if (user.role === 'STUDENT' && effectiveRole === 'ADMIN') { |
87 | 93 | return true; |
88 | 94 | } |
89 | | - |
| 95 | + |
90 | 96 | return false; |
91 | 97 | }, [user, effectiveRole]); |
92 | 98 |
|
@@ -130,36 +136,35 @@ export function CourseLayout() { |
130 | 136 | courseId, |
131 | 137 | ]); |
132 | 138 |
|
| 139 | + // Only redirect from settings if user doesn't have access |
| 140 | + // Let React Router handle all other route matching - no redirects for dashboard routes |
133 | 141 | useEffect(() => { |
134 | 142 | if (!courseId) return; |
135 | | - if (loading) return; |
136 | | - if (!effectiveRole) return; |
137 | 143 |
|
138 | | - const basePath = `/courses/${courseId}`; |
139 | | - const normalizedPath = location.pathname.replace(/\/$/, ''); |
| 144 | + const currentPath = location.pathname; |
140 | 145 |
|
141 | | - const allowedPaths = new Set<string>([basePath]); |
142 | | - |
143 | | - // Allow dashboard routes for all roles (dashboard/:teamId) |
144 | | - allowedPaths.add(`${basePath}/dashboard`); |
145 | | - |
146 | | - if (effectiveRole === 'INSTRUCTOR' || effectiveRole === 'ADMIN') { |
147 | | - allowedPaths.add(`${basePath}/settings`); |
| 146 | + // Never redirect from dashboard routes - let React Router handle them |
| 147 | + if (currentPath.includes('/dashboard')) { |
| 148 | + return; |
148 | 149 | } |
149 | 150 |
|
150 | | - const normalizedAllowed = Array.from(allowedPaths).map((path) => |
151 | | - path.replace(/\/$/, '') |
| 151 | + // Only check settings route access |
| 152 | + const settingsMatch = matchPath( |
| 153 | + { path: '/courses/:courseId/settings', end: true }, |
| 154 | + currentPath |
152 | 155 | ); |
153 | 156 |
|
154 | | - // Check if path matches exactly or starts with an allowed path (for nested routes like dashboard/:teamId) |
155 | | - const isAllowed = normalizedAllowed.some((path) => |
156 | | - path === normalizedPath || normalizedPath.startsWith(path + '/') |
157 | | - ); |
| 157 | + if (settingsMatch?.params.courseId === courseId) { |
| 158 | + // Wait for loading to complete and effectiveRole to be set before checking settings access |
| 159 | + if (loading) return; |
| 160 | + if (!effectiveRole) return; |
158 | 161 |
|
159 | | - if (!isAllowed) { |
160 | | - navigate(basePath, { replace: true }); |
| 162 | + // Only redirect from settings if user doesn't have access |
| 163 | + if (effectiveRole !== 'INSTRUCTOR' && effectiveRole !== 'ADMIN') { |
| 164 | + navigate(`/courses/${courseId}`, { replace: true }); |
| 165 | + } |
161 | 166 | } |
162 | | - }, [courseId, effectiveRole, location.pathname, navigate, loading]); |
| 167 | + }, [courseId, effectiveRole, navigate, loading, location.pathname]); |
163 | 168 |
|
164 | 169 | const toggleViewAsStudent = () => { |
165 | 170 | if (!isAdmin) return; |
|
0 commit comments