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) {
|
export function ProtectedRoute({ children, requireAdmin = false }: ProtectedRouteProps) {
|
||||||
const { user, loading: authLoading } = useAuth();
|
const { user, loading: authLoading, isAdmin } = useAuth();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -21,15 +21,12 @@ export function ProtectedRoute({ children, requireAdmin = false }: ProtectedRout
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for admin role if required (only after user is loaded)
|
// Check for admin role if required (only after user is loaded AND admin check is complete)
|
||||||
if (!authLoading && user && requireAdmin) {
|
if (!authLoading && user && requireAdmin && !isAdmin) {
|
||||||
const userRole = user.user_metadata?.role;
|
|
||||||
if (userRole !== 'admin') {
|
|
||||||
// Redirect non-admin users to member dashboard
|
// Redirect non-admin users to member dashboard
|
||||||
navigate('/dashboard');
|
navigate('/dashboard');
|
||||||
}
|
}
|
||||||
}
|
}, [user, authLoading, isAdmin, navigate, requireAdmin]);
|
||||||
}, [user, authLoading, navigate, requireAdmin]);
|
|
||||||
|
|
||||||
// Show loading skeleton while checking auth
|
// Show loading skeleton while checking auth
|
||||||
if (authLoading) {
|
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
|
// Don't render if admin access required but user is not admin
|
||||||
if (requireAdmin && user.user_metadata?.role !== 'admin') {
|
if (requireAdmin && !isAdmin) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export default function Auth() {
|
|||||||
const [pendingUserId, setPendingUserId] = useState<string | null>(null);
|
const [pendingUserId, setPendingUserId] = useState<string | null>(null);
|
||||||
const [resendCountdown, setResendCountdown] = useState(0);
|
const [resendCountdown, setResendCountdown] = useState(0);
|
||||||
const [isResendOTP, setIsResendOTP] = useState(false); // Track if this is resend OTP for existing user
|
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 navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
@@ -35,10 +35,12 @@ export default function Auth() {
|
|||||||
sessionStorage.removeItem('redirectAfterLogin');
|
sessionStorage.removeItem('redirectAfterLogin');
|
||||||
navigate(savedRedirect);
|
navigate(savedRedirect);
|
||||||
} else {
|
} 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
|
// Countdown timer for resend OTP
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -108,13 +110,8 @@ export default function Auth() {
|
|||||||
toast({ title: 'Error', description: error.message, variant: 'destructive' });
|
toast({ title: 'Error', description: error.message, variant: 'destructive' });
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
} else {
|
} else {
|
||||||
// Get redirect from sessionStorage or use default
|
// Login successful - the useEffect watching 'user' will handle the redirect
|
||||||
const savedRedirect = sessionStorage.getItem('redirectAfterLogin');
|
// This ensures we have the full user metadata including role
|
||||||
const redirectTo = savedRedirect || '/dashboard';
|
|
||||||
if (savedRedirect) {
|
|
||||||
sessionStorage.removeItem('redirectAfterLogin');
|
|
||||||
}
|
|
||||||
navigate(redirectTo);
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -25,12 +25,10 @@ export default function AdminBootcamp() {
|
|||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!authLoading) {
|
if (user && isAdmin) {
|
||||||
if (!user) navigate('/auth');
|
fetchBootcamps();
|
||||||
else if (!isAdmin) navigate('/dashboard');
|
|
||||||
else fetchBootcamps();
|
|
||||||
}
|
}
|
||||||
}, [user, isAdmin, authLoading]);
|
}, [user, isAdmin]);
|
||||||
|
|
||||||
const fetchBootcamps = async () => {
|
const fetchBootcamps = async () => {
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
|
|||||||
@@ -79,15 +79,11 @@ export default function AdminConsulting() {
|
|||||||
const [notifyMember, setNotifyMember] = useState(true);
|
const [notifyMember, setNotifyMember] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!authLoading) {
|
if (user && isAdmin) {
|
||||||
if (!user) navigate('/auth');
|
|
||||||
else if (!isAdmin) navigate('/dashboard');
|
|
||||||
else {
|
|
||||||
fetchSessions();
|
fetchSessions();
|
||||||
fetchSettings();
|
fetchSettings();
|
||||||
}
|
}
|
||||||
}
|
}, [user, isAdmin]);
|
||||||
}, [user, isAdmin, authLoading]);
|
|
||||||
|
|
||||||
const fetchSessions = async () => {
|
const fetchSessions = async () => {
|
||||||
// Fetch sessions with profile data
|
// Fetch sessions with profile data
|
||||||
|
|||||||
@@ -75,12 +75,10 @@ export default function AdminEvents() {
|
|||||||
const [blockForm, setBlockForm] = useState(emptyBlock);
|
const [blockForm, setBlockForm] = useState(emptyBlock);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!authLoading) {
|
if (user && isAdmin) {
|
||||||
if (!user) navigate('/auth');
|
fetchData();
|
||||||
else if (!isAdmin) navigate('/dashboard');
|
|
||||||
else fetchData();
|
|
||||||
}
|
}
|
||||||
}, [user, isAdmin, authLoading]);
|
}, [user, isAdmin]);
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
const [eventsRes, blocksRes, productsRes] = await Promise.all([
|
const [eventsRes, blocksRes, productsRes] = await Promise.all([
|
||||||
|
|||||||
@@ -80,12 +80,10 @@ export default function AdminProducts() {
|
|||||||
const [filterStatus, setFilterStatus] = useState<string>('all');
|
const [filterStatus, setFilterStatus] = useState<string>('all');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!authLoading) {
|
if (user && isAdmin) {
|
||||||
if (!user) navigate('/auth');
|
fetchProducts();
|
||||||
else if (!isAdmin) navigate('/dashboard');
|
|
||||||
else fetchProducts();
|
|
||||||
}
|
}
|
||||||
}, [user, isAdmin, authLoading]);
|
}, [user, isAdmin]);
|
||||||
|
|
||||||
const fetchProducts = async () => {
|
const fetchProducts = async () => {
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
|
|||||||
@@ -15,12 +15,6 @@ export default function AdminSettings() {
|
|||||||
const { user, isAdmin, loading: authLoading } = useAuth();
|
const { user, isAdmin, loading: authLoading } = useAuth();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!authLoading) {
|
|
||||||
if (!user) navigate('/auth');
|
|
||||||
else if (!isAdmin) navigate('/dashboard');
|
|
||||||
}
|
|
||||||
}, [user, isAdmin, authLoading, navigate]);
|
|
||||||
|
|
||||||
if (authLoading) {
|
if (authLoading) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -47,9 +47,8 @@ export default function MemberAccess() {
|
|||||||
const [selectedType, setSelectedType] = useState<string>('all');
|
const [selectedType, setSelectedType] = useState<string>('all');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!authLoading && !user) navigate('/auth');
|
if (user) fetchAccess();
|
||||||
else if (user) fetchAccess();
|
}, [user]);
|
||||||
}, [user, authLoading]);
|
|
||||||
|
|
||||||
const fetchAccess = async () => {
|
const fetchAccess = async () => {
|
||||||
const [accessRes, paidOrdersRes, consultingRes] = await Promise.all([
|
const [accessRes, paidOrdersRes, consultingRes] = await Promise.all([
|
||||||
|
|||||||
@@ -35,9 +35,8 @@ export default function MemberProfile() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!authLoading && !user) navigate('/auth');
|
if (user) fetchProfile();
|
||||||
else if (user) fetchProfile();
|
}, [user]);
|
||||||
}, [user, authLoading]);
|
|
||||||
|
|
||||||
const fetchProfile = async () => {
|
const fetchProfile = async () => {
|
||||||
const { data } = await supabase
|
const { data } = await supabase
|
||||||
|
|||||||
Reference in New Issue
Block a user