Ensure authenticated access to dashboard

Implement authentication check for dashboard route, redirecting unauthenticated users to the authentication page.
This commit is contained in:
gpt-engineer-app[bot] 2025-03-10 09:26:18 +00:00
parent 60ee8bb62e
commit 2f66c2ddcd
3 changed files with 90 additions and 37 deletions

View File

@ -3,7 +3,7 @@ import { Toaster } from "@/components/ui/toaster";
import { Toaster as Sonner } from "@/components/ui/sonner"; import { Toaster as Sonner } from "@/components/ui/sonner";
import { TooltipProvider } from "@/components/ui/tooltip"; import { TooltipProvider } from "@/components/ui/tooltip";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; import { BrowserRouter, Routes, Route, Navigate, useLocation } from "react-router-dom";
import { AuthProvider, useAuth } from "@/contexts/AuthContext"; import { AuthProvider, useAuth } from "@/contexts/AuthContext";
import Index from "./pages/Index"; import Index from "./pages/Index";
import Auth from "./pages/Auth"; import Auth from "./pages/Auth";
@ -13,33 +13,58 @@ import Header from "./components/Header";
const queryClient = new QueryClient(); const queryClient = new QueryClient();
// Protected route component // Enhanced Protected route component that preserves the intended destination
const ProtectedRoute = ({ children }: { children: React.ReactNode }) => { const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
const { user, loading } = useAuth(); const { user, loading, isAuthenticated } = useAuth();
const location = useLocation();
if (loading) { if (loading) {
return <div className="min-h-screen flex items-center justify-center">Loading...</div>; return <div className="min-h-screen flex items-center justify-center">Loading...</div>;
} }
if (!user) { if (!isAuthenticated) {
return <Navigate to="/auth" />; // Redirect to the auth page but save the current location they were trying to access
return <Navigate to="/auth" state={{ from: location }} replace />;
} }
return <>{children}</>; return <>{children}</>;
}; };
const App = () => ( // Authentication guard to prevent authenticated users from accessing the auth page
<QueryClientProvider client={queryClient}> const AuthGuard = ({ children }: { children: React.ReactNode }) => {
<AuthProvider> const { isAuthenticated, loading } = useAuth();
if (loading) {
return <div className="min-h-screen flex items-center justify-center">Loading...</div>;
}
if (isAuthenticated) {
// If already logged in, redirect to dashboard
return <Navigate to="/dashboard" replace />;
}
return <>{children}</>;
};
// App needs to be wrapped with BrowserRouter to use the AuthProvider with routing capabilities
const AppContent = () => {
return (
<>
<TooltipProvider> <TooltipProvider>
<Toaster /> <Toaster />
<Sonner /> <Sonner />
<BrowserRouter>
<Header /> <Header />
<div className="pt-16 sm:pt-20"> <div className="pt-16 sm:pt-20">
<Routes> <Routes>
<Route path="/" element={<Index />} /> <Route path="/" element={<Index />} />
<Route path="/auth" element={<Auth />} /> <Route
path="/auth"
element={
<AuthGuard>
<Auth />
</AuthGuard>
}
/>
<Route <Route
path="/dashboard" path="/dashboard"
element={ element={
@ -52,9 +77,18 @@ const App = () => (
<Route path="*" element={<NotFound />} /> <Route path="*" element={<NotFound />} />
</Routes> </Routes>
</div> </div>
</BrowserRouter>
</TooltipProvider> </TooltipProvider>
</>
);
};
const App = () => (
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<AuthProvider>
<AppContent />
</AuthProvider> </AuthProvider>
</BrowserRouter>
</QueryClientProvider> </QueryClientProvider>
); );

View File

@ -1,8 +1,8 @@
import { createContext, useContext, useState, useEffect, ReactNode } from 'react'; import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
import { supabase } from '@/integrations/supabase/client'; import { supabase } from '@/integrations/supabase/client';
import { Session, User } from '@supabase/supabase-js'; import { Session, User } from '@supabase/supabase-js';
import { useToast } from '@/hooks/use-toast'; import { useToast } from '@/hooks/use-toast';
import { useNavigate, useLocation } from 'react-router-dom';
type AuthContextType = { type AuthContextType = {
session: Session | null; session: Session | null;
@ -12,6 +12,7 @@ type AuthContextType = {
signIn: (email: string, password: string) => Promise<void>; signIn: (email: string, password: string) => Promise<void>;
signUp: (email: string, password: string, fullName: string) => Promise<void>; signUp: (email: string, password: string, fullName: string) => Promise<void>;
signOut: () => Promise<void>; signOut: () => Promise<void>;
isAuthenticated: boolean;
}; };
const AuthContext = createContext<AuthContextType | undefined>(undefined); const AuthContext = createContext<AuthContextType | undefined>(undefined);
@ -21,13 +22,17 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
const [user, setUser] = useState<User | null>(null); const [user, setUser] = useState<User | null>(null);
const [profile, setProfile] = useState<any | null>(null); const [profile, setProfile] = useState<any | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [isAuthenticated, setIsAuthenticated] = useState(false);
const { toast } = useToast(); const { toast } = useToast();
const navigate = useNavigate();
const location = useLocation();
useEffect(() => { useEffect(() => {
// Get the initial session // Get the initial session
supabase.auth.getSession().then(({ data: { session } }) => { supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session); setSession(session);
setUser(session?.user ?? null); setUser(session?.user ?? null);
setIsAuthenticated(!!session?.user);
if (session?.user) { if (session?.user) {
fetchProfile(session.user.id); fetchProfile(session.user.id);
} }
@ -39,10 +44,15 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
(_event, session) => { (_event, session) => {
setSession(session); setSession(session);
setUser(session?.user ?? null); setUser(session?.user ?? null);
setIsAuthenticated(!!session?.user);
if (session?.user) { if (session?.user) {
fetchProfile(session.user.id); fetchProfile(session.user.id);
} else { } else {
setProfile(null); setProfile(null);
// If on a protected route and logged out, redirect to auth
if (location.pathname === '/dashboard') {
navigate('/auth');
}
} }
setLoading(false); setLoading(false);
} }
@ -51,7 +61,7 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
return () => { return () => {
subscription.unsubscribe(); subscription.unsubscribe();
}; };
}, []); }, [navigate, location.pathname]);
const fetchProfile = async (userId: string) => { const fetchProfile = async (userId: string) => {
try { try {
@ -83,6 +93,9 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
title: "Success!", title: "Success!",
description: "You are now signed in.", description: "You are now signed in.",
}); });
// Redirect to dashboard on successful sign in
navigate('/dashboard');
} catch (error: any) { } catch (error: any) {
toast({ toast({
title: "Error signing in", title: "Error signing in",
@ -137,6 +150,9 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
title: "Signed out", title: "Signed out",
description: "You have been successfully signed out.", description: "You have been successfully signed out.",
}); });
// Always redirect to home page after sign out
navigate('/');
} catch (error: any) { } catch (error: any) {
toast({ toast({
title: "Error signing out", title: "Error signing out",
@ -159,6 +175,7 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
signIn, signIn,
signUp, signUp,
signOut, signOut,
isAuthenticated,
}} }}
> >
{children} {children}

View File

@ -1,6 +1,6 @@
import { useState } from 'react'; import { useState } from 'react';
import { Navigate } from 'react-router-dom'; import { useLocation, useNavigate } from 'react-router-dom';
import { useAuth } from '@/contexts/AuthContext'; import { useAuth } from '@/contexts/AuthContext';
import { ShieldCheck, Mail, Lock, User, Loader2 } from 'lucide-react'; import { ShieldCheck, Mail, Lock, User, Loader2 } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
@ -10,23 +10,25 @@ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle }
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
const Auth = () => { const Auth = () => {
const { user, signIn, signUp, loading } = useAuth(); const { signIn, signUp, loading } = useAuth();
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [fullName, setFullName] = useState(''); const [fullName, setFullName] = useState('');
const [authError, setAuthError] = useState<string | null>(null); const [authError, setAuthError] = useState<string | null>(null);
const [activeTab, setActiveTab] = useState('login'); const [activeTab, setActiveTab] = useState('login');
// If user is already logged in, redirect to dashboard const location = useLocation();
if (user) { const navigate = useNavigate();
return <Navigate to="/dashboard" />;
} // Get the intended destination from the location state
const from = (location.state as any)?.from?.pathname || '/dashboard';
const handleSignIn = async (e: React.FormEvent) => { const handleSignIn = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
setAuthError(null); setAuthError(null);
try { try {
await signIn(email, password); await signIn(email, password);
// Redirect will be handled in the AuthContext after successful sign in
} catch (error: any) { } catch (error: any) {
console.error('Sign in error:', error); console.error('Sign in error:', error);
// Error is already handled by the toast in AuthContext // Error is already handled by the toast in AuthContext