Fix admin redirect by using isAdmin from auth context instead of user_metadata.role
The root cause was that ProtectedRoute and Auth.tsx were checking user.user_metadata?.role, but the admin role is stored in the user_roles table, not in user metadata. Changes: - ProtectedRoute: Use isAdmin flag from useAuth context instead of user.user_metadata?.role - Auth.tsx: Use isAdmin flag for role-based redirect logic - Remove redundant auth checks from individual admin/member pages (ProtectedRoute handles it) - Add isAdmin to useEffect dependencies to ensure redirect happens after admin check completes This fixes the issue where admins were being redirected to /dashboard instead of /admin after login, because the role check was happening before the async admin role lookup completed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,7 @@ interface ProtectedRouteProps {
|
||||
}
|
||||
|
||||
export function ProtectedRoute({ children, requireAdmin = false }: ProtectedRouteProps) {
|
||||
const { user, loading: authLoading } = useAuth();
|
||||
const { user, loading: authLoading, isAdmin } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -21,15 +21,12 @@ export function ProtectedRoute({ children, requireAdmin = false }: ProtectedRout
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for admin role if required (only after user is loaded)
|
||||
if (!authLoading && user && requireAdmin) {
|
||||
const userRole = user.user_metadata?.role;
|
||||
if (userRole !== 'admin') {
|
||||
// Redirect non-admin users to member dashboard
|
||||
navigate('/dashboard');
|
||||
}
|
||||
// Check for admin role if required (only after user is loaded AND admin check is complete)
|
||||
if (!authLoading && user && requireAdmin && !isAdmin) {
|
||||
// Redirect non-admin users to member dashboard
|
||||
navigate('/dashboard');
|
||||
}
|
||||
}, [user, authLoading, navigate, requireAdmin]);
|
||||
}, [user, authLoading, isAdmin, navigate, requireAdmin]);
|
||||
|
||||
// Show loading skeleton while checking auth
|
||||
if (authLoading) {
|
||||
@@ -53,7 +50,7 @@ export function ProtectedRoute({ children, requireAdmin = false }: ProtectedRout
|
||||
}
|
||||
|
||||
// Don't render if admin access required but user is not admin
|
||||
if (requireAdmin && user.user_metadata?.role !== 'admin') {
|
||||
if (requireAdmin && !isAdmin) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ export default function Auth() {
|
||||
const [pendingUserId, setPendingUserId] = useState<string | null>(null);
|
||||
const [resendCountdown, setResendCountdown] = useState(0);
|
||||
const [isResendOTP, setIsResendOTP] = useState(false); // Track if this is resend OTP for existing user
|
||||
const { signIn, signUp, user, sendAuthOTP, verifyAuthOTP, getUserByEmail } = useAuth();
|
||||
const { signIn, signUp, user, isAdmin, sendAuthOTP, verifyAuthOTP, getUserByEmail } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
@@ -35,10 +35,12 @@ export default function Auth() {
|
||||
sessionStorage.removeItem('redirectAfterLogin');
|
||||
navigate(savedRedirect);
|
||||
} else {
|
||||
navigate('/dashboard');
|
||||
// Default redirect based on user role (use isAdmin flag from context)
|
||||
const defaultRedirect = isAdmin ? '/admin' : '/dashboard';
|
||||
navigate(defaultRedirect);
|
||||
}
|
||||
}
|
||||
}, [user, navigate]);
|
||||
}, [user, isAdmin, navigate]);
|
||||
|
||||
// Countdown timer for resend OTP
|
||||
useEffect(() => {
|
||||
@@ -108,13 +110,8 @@ export default function Auth() {
|
||||
toast({ title: 'Error', description: error.message, variant: 'destructive' });
|
||||
setLoading(false);
|
||||
} else {
|
||||
// Get redirect from sessionStorage or use default
|
||||
const savedRedirect = sessionStorage.getItem('redirectAfterLogin');
|
||||
const redirectTo = savedRedirect || '/dashboard';
|
||||
if (savedRedirect) {
|
||||
sessionStorage.removeItem('redirectAfterLogin');
|
||||
}
|
||||
navigate(redirectTo);
|
||||
// Login successful - the useEffect watching 'user' will handle the redirect
|
||||
// This ensures we have the full user metadata including role
|
||||
setLoading(false);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -25,12 +25,10 @@ export default function AdminBootcamp() {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (!authLoading) {
|
||||
if (!user) navigate('/auth');
|
||||
else if (!isAdmin) navigate('/dashboard');
|
||||
else fetchBootcamps();
|
||||
if (user && isAdmin) {
|
||||
fetchBootcamps();
|
||||
}
|
||||
}, [user, isAdmin, authLoading]);
|
||||
}, [user, isAdmin]);
|
||||
|
||||
const fetchBootcamps = async () => {
|
||||
const { data, error } = await supabase
|
||||
|
||||
@@ -79,15 +79,11 @@ export default function AdminConsulting() {
|
||||
const [notifyMember, setNotifyMember] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (!authLoading) {
|
||||
if (!user) navigate('/auth');
|
||||
else if (!isAdmin) navigate('/dashboard');
|
||||
else {
|
||||
fetchSessions();
|
||||
fetchSettings();
|
||||
}
|
||||
if (user && isAdmin) {
|
||||
fetchSessions();
|
||||
fetchSettings();
|
||||
}
|
||||
}, [user, isAdmin, authLoading]);
|
||||
}, [user, isAdmin]);
|
||||
|
||||
const fetchSessions = async () => {
|
||||
// Fetch sessions with profile data
|
||||
|
||||
@@ -75,12 +75,10 @@ export default function AdminEvents() {
|
||||
const [blockForm, setBlockForm] = useState(emptyBlock);
|
||||
|
||||
useEffect(() => {
|
||||
if (!authLoading) {
|
||||
if (!user) navigate('/auth');
|
||||
else if (!isAdmin) navigate('/dashboard');
|
||||
else fetchData();
|
||||
if (user && isAdmin) {
|
||||
fetchData();
|
||||
}
|
||||
}, [user, isAdmin, authLoading]);
|
||||
}, [user, isAdmin]);
|
||||
|
||||
const fetchData = async () => {
|
||||
const [eventsRes, blocksRes, productsRes] = await Promise.all([
|
||||
|
||||
@@ -80,12 +80,10 @@ export default function AdminProducts() {
|
||||
const [filterStatus, setFilterStatus] = useState<string>('all');
|
||||
|
||||
useEffect(() => {
|
||||
if (!authLoading) {
|
||||
if (!user) navigate('/auth');
|
||||
else if (!isAdmin) navigate('/dashboard');
|
||||
else fetchProducts();
|
||||
if (user && isAdmin) {
|
||||
fetchProducts();
|
||||
}
|
||||
}, [user, isAdmin, authLoading]);
|
||||
}, [user, isAdmin]);
|
||||
|
||||
const fetchProducts = async () => {
|
||||
const { data, error } = await supabase
|
||||
|
||||
@@ -15,12 +15,6 @@ export default function AdminSettings() {
|
||||
const { user, isAdmin, loading: authLoading } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (!authLoading) {
|
||||
if (!user) navigate('/auth');
|
||||
else if (!isAdmin) navigate('/dashboard');
|
||||
}
|
||||
}, [user, isAdmin, authLoading, navigate]);
|
||||
|
||||
if (authLoading) {
|
||||
return (
|
||||
|
||||
@@ -47,9 +47,8 @@ export default function MemberAccess() {
|
||||
const [selectedType, setSelectedType] = useState<string>('all');
|
||||
|
||||
useEffect(() => {
|
||||
if (!authLoading && !user) navigate('/auth');
|
||||
else if (user) fetchAccess();
|
||||
}, [user, authLoading]);
|
||||
if (user) fetchAccess();
|
||||
}, [user]);
|
||||
|
||||
const fetchAccess = async () => {
|
||||
const [accessRes, paidOrdersRes, consultingRes] = await Promise.all([
|
||||
|
||||
@@ -35,9 +35,8 @@ export default function MemberProfile() {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!authLoading && !user) navigate('/auth');
|
||||
else if (user) fetchProfile();
|
||||
}, [user, authLoading]);
|
||||
if (user) fetchProfile();
|
||||
}, [user]);
|
||||
|
||||
const fetchProfile = async () => {
|
||||
const { data } = await supabase
|
||||
|
||||
Reference in New Issue
Block a user