Skip to content

Commit 960ac70

Browse files
Merge pull request #27 from AET-DevOps25/feature/add-user-sessions-and-auth
Add user sessions and auth
2 parents fea1691 + b0c650c commit 960ac70

31 files changed

+3250
-249
lines changed

client/src/App.tsx

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@ import { Toaster as Sonner } from "@/components/ui/sonner";
33
import { TooltipProvider } from "@/components/ui/tooltip";
44
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
55
import { BrowserRouter, Routes, Route } from "react-router-dom";
6+
import { AuthProvider } from "@/contexts/AuthContext";
67
import Index from "./pages/Index";
78
import NotFound from "./pages/NotFound";
9+
import LoginForm from "./components/auth/LoginForm";
10+
import RegisterForm from "./components/auth/RegisterForm";
11+
import ProtectedRoute from "./components/ProtectedRoute";
812

913
const queryClient = new QueryClient();
1014

@@ -14,11 +18,43 @@ const App = () => (
1418
<Toaster />
1519
<Sonner />
1620
<BrowserRouter>
17-
<Routes>
18-
<Route path="/" element={<Index />} />
19-
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
20-
<Route path="*" element={<NotFound />} />
21-
</Routes>
21+
<AuthProvider>
22+
<Routes>
23+
{/* Public routes */}
24+
<Route path="/" element={<Index />} />
25+
26+
{/* Auth routes - redirect to dashboard if already authenticated */}
27+
<Route
28+
path="/login"
29+
element={
30+
<ProtectedRoute requireAuth={false}>
31+
<LoginForm />
32+
</ProtectedRoute>
33+
}
34+
/>
35+
<Route
36+
path="/register"
37+
element={
38+
<ProtectedRoute requireAuth={false}>
39+
<RegisterForm />
40+
</ProtectedRoute>
41+
}
42+
/>
43+
44+
{/* Protected routes */}
45+
<Route
46+
path="/dashboard"
47+
element={
48+
<ProtectedRoute>
49+
<Index />
50+
</ProtectedRoute>
51+
}
52+
/>
53+
54+
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
55+
<Route path="*" element={<NotFound />} />
56+
</Routes>
57+
</AuthProvider>
2258
</BrowserRouter>
2359
</TooltipProvider>
2460
</QueryClientProvider>

client/src/components/DashboardSection.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { useState, useMemo } from 'react';
2-
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
32
import { Button } from '@/components/ui/button';
43
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
5-
import { ArrowLeft, FileText, Brain, MessageSquare, BookOpen, Upload, Sparkles } from 'lucide-react';
4+
import { ArrowLeft, FileText, Brain, MessageSquare, BookOpen, Upload, Sparkles, Loader2 } from 'lucide-react';
65
import UploadSection from '@/components/UploadSection';
76
import SummaryTab from '@/components/SummaryTab';
87
import QuizTab from '@/components/QuizTab';
@@ -18,9 +17,10 @@ interface DashboardSectionProps {
1817
uploadedFiles: UploadedFileWithId[];
1918
onFileUpload: (files: File[], documentIds: string[]) => void;
2019
onBackToHome: () => void;
20+
isLoadingDocuments?: boolean;
2121
}
2222

23-
const DashboardSection = ({ uploadedFiles, onFileUpload, onBackToHome }: DashboardSectionProps) => {
23+
const DashboardSection = ({ uploadedFiles, onFileUpload, onBackToHome, isLoadingDocuments = false }: DashboardSectionProps) => {
2424
const [activeTab, setActiveTab] = useState('upload');
2525
const [quizzes, setQuizzes] = useState([]);
2626
const [answers, setAnswers] = useState({}); // answers: { [documentId: string]: { [questionIndex: number]: string | number } }
@@ -37,8 +37,18 @@ const DashboardSection = ({ uploadedFiles, onFileUpload, onBackToHome }: Dashboa
3737
console.log('DashboardSection - documentIds memo recalculated:', uploadedFiles.map(item => item.documentId));
3838
return uploadedFiles.map(item => item.documentId);
3939
}, [uploadedFiles]);
40-
4140

41+
// Show loading state while documents are being fetched
42+
if (isLoadingDocuments) {
43+
return (
44+
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
45+
<div className="text-center">
46+
<Loader2 className="h-8 w-8 animate-spin mx-auto mb-4 text-blue-500" />
47+
<p className="text-gray-600">Loading your documents...</p>
48+
</div>
49+
</div>
50+
);
51+
}
4252

4353
return (
4454
<div className="min-h-screen bg-gray-50">
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import React, { useState } from 'react';
2+
import { Button } from '@/components/ui/button';
3+
import {
4+
DropdownMenu,
5+
DropdownMenuContent,
6+
DropdownMenuItem,
7+
DropdownMenuLabel,
8+
DropdownMenuSeparator,
9+
DropdownMenuTrigger
10+
} from '@/components/ui/dropdown-menu';
11+
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
12+
import { Brain, User, Settings, LogOut, ChevronDown } from 'lucide-react';
13+
import { useAuth } from '@/contexts/AuthContext';
14+
import ProfileModal from './auth/ProfileModal';
15+
16+
interface NavigationProps {
17+
onBackToHome?: () => void;
18+
showBackButton?: boolean;
19+
}
20+
21+
const Navigation: React.FC<NavigationProps> = ({ onBackToHome, showBackButton }) => {
22+
const { user, logout, isAuthenticated } = useAuth();
23+
const [isProfileModalOpen, setIsProfileModalOpen] = useState(false);
24+
25+
const handleLogout = () => {
26+
logout();
27+
if (onBackToHome) {
28+
onBackToHome();
29+
}
30+
};
31+
32+
const getInitials = (firstName?: string, lastName?: string) => {
33+
if (!firstName || !lastName) return 'U';
34+
return `${firstName.charAt(0)}${lastName.charAt(0)}`.toUpperCase();
35+
};
36+
37+
return (
38+
<nav className="bg-white shadow-lg border-b">
39+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
40+
<div className="flex justify-between items-center h-16">
41+
{/* Left side - Logo and back button */}
42+
<div className="flex items-center space-x-4">
43+
<div className="flex items-center space-x-3">
44+
<div className="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center">
45+
<Brain className="h-5 w-5 text-white" />
46+
</div>
47+
<span className="text-xl font-bold text-gray-900">StudyMate</span>
48+
</div>
49+
50+
{showBackButton && onBackToHome && (
51+
<Button
52+
variant="ghost"
53+
onClick={onBackToHome}
54+
className="text-gray-600 hover:text-gray-900"
55+
>
56+
← Back to Home
57+
</Button>
58+
)}
59+
</div>
60+
61+
{/* Right side - User menu or login button */}
62+
<div className="flex items-center space-x-4">
63+
{isAuthenticated && user ? (
64+
<DropdownMenu>
65+
<DropdownMenuTrigger asChild>
66+
<Button
67+
variant="ghost"
68+
className="flex items-center space-x-2 hover:bg-gray-100"
69+
>
70+
<Avatar className="h-8 w-8">
71+
<AvatarFallback className="bg-blue-500 text-white text-sm">
72+
{getInitials(user.firstName, user.lastName)}
73+
</AvatarFallback>
74+
</Avatar>
75+
<span className="hidden sm:block text-sm font-medium text-gray-700">
76+
{user.firstName} {user.lastName}
77+
</span>
78+
<ChevronDown className="h-4 w-4 text-gray-500" />
79+
</Button>
80+
</DropdownMenuTrigger>
81+
<DropdownMenuContent align="end" className="w-56">
82+
<DropdownMenuLabel>
83+
<div className="flex flex-col space-y-1">
84+
<p className="text-sm font-medium text-gray-900">
85+
{user.firstName} {user.lastName}
86+
</p>
87+
<p className="text-xs text-gray-500">
88+
{user.email}
89+
</p>
90+
</div>
91+
</DropdownMenuLabel>
92+
<DropdownMenuSeparator />
93+
<DropdownMenuItem
94+
onClick={() => setIsProfileModalOpen(true)}
95+
className="cursor-pointer"
96+
>
97+
<User className="mr-2 h-4 w-4" />
98+
<span>Profile</span>
99+
</DropdownMenuItem>
100+
<DropdownMenuItem
101+
onClick={() => setIsProfileModalOpen(true)}
102+
className="cursor-pointer"
103+
>
104+
<Settings className="mr-2 h-4 w-4" />
105+
<span>Settings</span>
106+
</DropdownMenuItem>
107+
<DropdownMenuSeparator />
108+
<DropdownMenuItem
109+
onClick={handleLogout}
110+
className="cursor-pointer text-red-600 hover:text-red-700 hover:bg-red-50"
111+
>
112+
<LogOut className="mr-2 h-4 w-4" />
113+
<span>Log out</span>
114+
</DropdownMenuItem>
115+
</DropdownMenuContent>
116+
</DropdownMenu>
117+
) : (
118+
<div className="flex items-center space-x-2">
119+
<Button
120+
variant="ghost"
121+
onClick={() => window.location.href = '/login'}
122+
className="text-gray-600 hover:text-gray-900"
123+
>
124+
Sign In
125+
</Button>
126+
<Button
127+
onClick={() => window.location.href = '/register'}
128+
className="bg-blue-500 hover:bg-blue-600 text-white"
129+
>
130+
Get Started
131+
</Button>
132+
</div>
133+
)}
134+
</div>
135+
</div>
136+
</div>
137+
138+
{/* Profile Modal */}
139+
<ProfileModal
140+
isOpen={isProfileModalOpen}
141+
onClose={() => setIsProfileModalOpen(false)}
142+
/>
143+
</nav>
144+
);
145+
};
146+
147+
export default Navigation;
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React from 'react';
2+
import { Navigate, useLocation } from 'react-router-dom';
3+
import { useAuth } from '@/contexts/AuthContext';
4+
import { Loader2 } from 'lucide-react';
5+
6+
interface ProtectedRouteProps {
7+
children: React.ReactNode;
8+
requireAuth?: boolean;
9+
redirectTo?: string;
10+
}
11+
12+
const ProtectedRoute: React.FC<ProtectedRouteProps> = ({
13+
children,
14+
requireAuth = true,
15+
redirectTo = '/login'
16+
}) => {
17+
const { isAuthenticated, isLoading } = useAuth();
18+
const location = useLocation();
19+
20+
// Show loading spinner while checking authentication
21+
if (isLoading) {
22+
return (
23+
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-50 via-white to-purple-50">
24+
<div className="text-center">
25+
<Loader2 className="h-8 w-8 animate-spin mx-auto mb-4 text-blue-500" />
26+
<p className="text-gray-600">Loading...</p>
27+
</div>
28+
</div>
29+
);
30+
}
31+
32+
// If authentication is required and user is not authenticated
33+
if (requireAuth && !isAuthenticated) {
34+
// Save the attempted URL for redirect after login
35+
return <Navigate to={redirectTo} state={{ from: location }} replace />;
36+
}
37+
38+
// If authentication is not required and user is authenticated (for login/register pages)
39+
if (!requireAuth && isAuthenticated) {
40+
// Redirect to the intended page or dashboard
41+
const from = location.state?.from?.pathname || '/dashboard';
42+
return <Navigate to={from} replace />;
43+
}
44+
45+
return <>{children}</>;
46+
};
47+
48+
export default ProtectedRoute;

0 commit comments

Comments
 (0)