Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
dc14652
refactor: remove CORS allowed-origins configuration from gateway sett…
GravityDarkLab Jul 6, 2025
dcbbef9
feat: add isAuthenticated parameter to advanced search in CourseContr…
GravityDarkLab Jul 6, 2025
369a1d1
feat: add private courses retrieval for authenticated users
GravityDarkLab Jul 6, 2025
68af8f3
feat: enhance course service with enrollment count consistency and mo…
GravityDarkLab Jul 6, 2025
a661254
feat: integrate current lesson and total lessons tracking in Enrolled…
GravityDarkLab Jul 6, 2025
b78ea64
fix: add CORS support in the JWT error response header
GravityDarkLab Jul 6, 2025
b160af6
feat: allow public access to course search endpoint
GravityDarkLab Jul 6, 2025
41f5f52
fix: fix course repository methods naming for public and published
GravityDarkLab Jul 6, 2025
d1cf7c9
feat: update dependencies and enhance Tailwind CSS configuration with…
GravityDarkLab Jul 6, 2025
7c1f364
chore: update Features, Hero, and Footer components with new icons, d…
GravityDarkLab Jul 6, 2025
c85b0df
refactor: update response payload exports in index.ts to include Cour…
GravityDarkLab Jul 6, 2025
eed4877
docs: add JSDoc comments for user service methods to improve code doc…
GravityDarkLab Jul 6, 2025
9b3e8d1
feat: add user skill and course retrieval methods to user service for…
GravityDarkLab Jul 6, 2025
69ec5e6
feat: implement course service with methods for fetching, enrolling, …
GravityDarkLab Jul 6, 2025
8656daa
feat: add achievement service and types for user achievements, includ…
GravityDarkLab Jul 6, 2025
c04af01
feat: implement dashboard service to aggregate user data, including c…
GravityDarkLab Jul 6, 2025
8789f73
feat: add AI chat service for user interaction with course generation…
GravityDarkLab Jul 6, 2025
2908bfe
chore: remove unused mock data and assets to streamline project struc…
GravityDarkLab Jul 6, 2025
2feb289
refactor: clean up Profile component by removing unused imports, mock…
GravityDarkLab Jul 6, 2025
d6a134b
feat: enhance Dashboard component with dynamic data fetching, loading…
GravityDarkLab Jul 6, 2025
9ec10b7
feat: add CertificateGenerator and CourseCompletionCelebration compon…
GravityDarkLab Jul 6, 2025
1a12d71
feat: enhance Courses component with real-time search functionality, …
GravityDarkLab Jul 6, 2025
81195a4
feat: implement AIChatAssistant component for interactive user engage…
GravityDarkLab Jul 6, 2025
edb86d8
chore: update TypeScript build information to reflect the latest proj…
GravityDarkLab Jul 6, 2025
723a146
feat: create LessonPage component for displaying course lessons, incl…
GravityDarkLab Jul 6, 2025
ec07a14
feat: introduce AiCenter component for AI-driven URL and file analysi…
GravityDarkLab Jul 6, 2025
4544888
feat: implement CourseDetail component for displaying detailed course…
GravityDarkLab Jul 6, 2025
238fc1c
feat: align cards on the grid
GravityDarkLab Jul 6, 2025
504dfdc
feat: add CourseDetail and LessonPage routes; implement RequireAny ro…
GravityDarkLab Jul 6, 2025
a45bfbc
feat: add aria-labels for bookmarking button to improve accessibility
GravityDarkLab Jul 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,464 changes: 1,440 additions & 24 deletions client/package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,20 @@
"date-fns": "^3.6.0",
"embla-carousel-react": "^8.3.0",
"input-otp": "^1.2.4",
"jspdf": "^3.0.1",
"lodash": "^4.17.21",
"lucide-react": "^0.462.0",
"next-themes": "^0.3.0",
"react": "^18.3.1",
"react-day-picker": "^8.10.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.0",
"react-icons": "^5.5.0",
"react-markdown": "^10.1.0",
"react-resizable-panels": "^2.1.3",
"react-router-dom": "^6.26.2",
"recharts": "^2.12.7",
"simple-icons": "^15.4.0",
"sonner": "^1.5.0",
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7",
Expand All @@ -79,7 +83,7 @@
},
"devDependencies": {
"@eslint/js": "^9.9.0",
"@tailwindcss/typography": "^0.5.15",
"@tailwindcss/typography": "^0.5.16",
"@types/lodash": "^4.17.17",
"@types/node": "^22.5.5",
"@types/react": "^18.3.3",
Expand Down
73 changes: 28 additions & 45 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,66 +10,48 @@ import Signup from './pages/Signup';
import Dashboard from './pages/Dashboard';
import NotFound from './pages/NotFound';
import Courses from '@/pages/Courses';
import CourseDetail from '@/pages/CourseDetail';
import LessonPage from '@/pages/LessonPage';
import AiCenter from '@/pages/AiCenter';
//import About from '@/pages/About';
// import PrivacyPolicy from '@/pages/PrivacyPolicy';
// import TermsOfService from '@/pages/TermsOfService';
// import CookiePolicy from '@/pages/CookiePolicy';

import { AuthProvider } from '@/contexts/AuthProvider';
import { RequireAuth, RequireGuest } from '@/components/RouteGuards';
import { RequireAny, RequireAuth, RequireGuest } from '@/components/RouteGuards';
import Profile from '@/pages/Profile';
import ScrollToTop from '@/components/ScrollToTop';

const queryClient = new QueryClient();

const AppRoutes = () => (
<Routes>
{/* PUBLIC */}
<Route path="/" element={<Index />} />
{/* ANY */}
<Route path="/" element={<RequireAny><Index /></RequireAny>}/>

<Route
path="/login"
element={
<RequireGuest>
<Login />
</RequireGuest>
}
/>
<Route path="/courses" element={<RequireAny><Courses /></RequireAny>}/>

<Route
path="/signup"
element={
<RequireGuest>
<Signup />
</RequireGuest>
}
/>
{ /* Uncomment these routes when the components are available */ }
{/* <Route path="/about" element={<About />} />
<Route path="/privacy" element={<PrivacyPolicy />} />
<Route path="/terms" element={<TermsOfService />} />
<Route path="/cookies" element={<CookiePolicy />} /> */}

{/* PROTECTED */}
<Route
path="/dashboard"
element={
<RequireAuth>
<Dashboard />
</RequireAuth>
}
/>
{/* PUBLIC ONLY */}
<Route path="/login" element={<RequireGuest><Login /></RequireGuest>} />
<Route path="/signup" element={<RequireGuest><Signup /></RequireGuest>} />

<Route
path="/courses"
element={
<RequireAuth>
<Courses />
</RequireAuth>
}
/>
{/* PROTECTED */}
<Route path="/dashboard" element={<RequireAuth><Dashboard /></RequireAuth>} />
<Route path="/ai-center" element={<RequireAuth><AiCenter /></RequireAuth>} />
<Route path="/profile" element={<RequireAuth><Profile /></RequireAuth>} />

<Route
path="/profile"
element={
<RequireAuth>
<Profile />
</RequireAuth>
}
/>
<Route path="/courses/:courseId" element={<RequireAuth><CourseDetail /></RequireAuth>} />
<Route path="/courses/:courseId/lessons/:lessonId" element={<RequireAuth><LessonPage /></RequireAuth>} />

{/* CATCH-ALL */}
<Route path="*" element={<NotFound />} />
<Route path="*" element={<RequireAny><NotFound /></RequireAny>} />
</Routes>
);

Expand All @@ -79,6 +61,7 @@ const App = () => (
<Toaster />
<Sonner />
<BrowserRouter>
<ScrollToTop />
<AuthProvider>
<AppRoutes />
</AuthProvider>
Expand Down
1 change: 0 additions & 1 deletion client/src/assets/react.svg

This file was deleted.

192 changes: 192 additions & 0 deletions client/src/components/AIChatAssistant.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import React, { useState, useEffect, useRef } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Bot, User, Send } from 'lucide-react';
import { getAIChatResponse } from '@/services/aiChat.service';

interface Message {
id: string;
text: string;
sender: 'user' | 'ai';
timestamp: Date;
}

interface AIChatAssistantProps {
className?: string;
userSkills?: string[];
title?: string;
description?: string;
placeholder?: string;
initialMessage?: string;
height?: string;
showHeader?: boolean;
}

const AIChatAssistant: React.FC<AIChatAssistantProps> = ({
className,
userSkills = [],
title = "AI Learning Assistant",
description = "Chat with your AI assistant to get help with your learning journey",
placeholder = "Ask about your courses or what you want to learn next...",
initialMessage = "👋 Hello! I'm your AI learning assistant. I'm here to help you learn and grow.\n\n💡 **Available Commands:**\n• `/help` - Show all available commands\n• `/generate <topic>` - Create a personalized course\n• `/explain <subject>` - Get a quick explanation\n\nAsk me anything about your courses or what you'd like to learn next!",
height = "h-80",
showHeader = true
}) => {
const [messages, setMessages] = useState<Message[]>([
{
id: '1',
text: initialMessage,
sender: 'ai',
timestamp: new Date()
}
]);
const [inputMessage, setInputMessage] = useState('');
const [isTyping, setIsTyping] = useState(false);
const messagesEndRef = useRef<HTMLDivElement>(null);

const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};

useEffect(() => {
scrollToBottom();
}, [messages]);

const handleSendMessage = async () => {
if (!inputMessage.trim()) return;

const userMessage: Message = {
id: Date.now().toString(),
text: inputMessage,
sender: 'user',
timestamp: new Date()
};

setMessages(prev => [...prev, userMessage]);
setInputMessage('');
setIsTyping(true);

try {
// Use the AI chat service for the response
const aiText = await getAIChatResponse(inputMessage, userSkills);
const aiMessage: Message = {
id: (Date.now() + 1).toString(),
text: aiText,
sender: 'ai',
timestamp: new Date()
};
setMessages(prev => [...prev, aiMessage]);
} catch (error) {
// Fallback response if AI service fails
console.error('AI chat service failed:', error.message);
const aiMessage: Message = {
id: (Date.now() + 1).toString(),
text: "I understand you're asking about that. Let me help you with that. This is a simulated response - in a real implementation, this would connect to your AI service.",
sender: 'ai',
timestamp: new Date()
};
setMessages(prev => [...prev, aiMessage]);
} finally {
setIsTyping(false);
}
};

const handleKeyPress = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSendMessage();
}
};

return (
<Card className={`h-full ${className}`}>
{showHeader && (
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<Bot className="h-5 w-5 text-blue-600" />
<span>{title}</span>
</CardTitle>
<CardDescription>{description}</CardDescription>
</CardHeader>
)}
<CardContent className="p-0">
{/* Messages Container */}
<div className={`${height} overflow-y-auto p-4 space-y-4`}>
{messages.map((message) => (
<div
key={message.id}
className={`flex ${message.sender === 'user' ? 'justify-end' : 'justify-start'}`}
>
<div
className={`flex max-w-xs items-start space-x-2 rounded-lg px-3 py-2 ${
message.sender === 'user'
? 'bg-blue-600 text-white'
: 'bg-gray-100 text-gray-900'
}`}
>
{message.sender === 'ai' && (
<Bot className="h-4 w-4 mt-0.5 text-blue-600" />
)}
<div className="flex-1">
<div
className="text-sm whitespace-pre-wrap"
dangerouslySetInnerHTML={{
__html: message.text
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
.replace(/\n/g, '<br>')
}}
/>
<p className="text-xs opacity-70 mt-1">
{message.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
</p>
</div>
{message.sender === 'user' && (
<User className="h-4 w-4 mt-0.5 text-blue-200" />
)}
</div>
</div>
))}

{isTyping && (
<div className="flex justify-start">
<div className="flex max-w-xs items-start space-x-2 rounded-lg bg-gray-100 px-3 py-2">
<Bot className="h-4 w-4 mt-0.5 text-blue-600" />
<div className="flex space-x-1">
<div className="h-2 w-2 animate-bounce rounded-full bg-gray-400"></div>
<div className="h-2 w-2 animate-bounce rounded-full bg-gray-400" style={{ animationDelay: '0.1s' }}></div>
<div className="h-2 w-2 animate-bounce rounded-full bg-gray-400" style={{ animationDelay: '0.2s' }}></div>
</div>
</div>
</div>
)}

<div ref={messagesEndRef} />
</div>

{/* Input Area */}
<div className="border-t p-4">
<div className="flex space-x-2">
<Input
value={inputMessage}
onChange={(e) => setInputMessage(e.target.value)}
onKeyPress={handleKeyPress}
placeholder={placeholder}
className="flex-1"
/>
<Button
onClick={handleSendMessage}
disabled={!inputMessage.trim() || isTyping}
size="sm"
className="px-3"
>
<Send className="h-4 w-4" />
</Button>
</div>
</div>
</CardContent>
</Card>
);
};

export default AIChatAssistant;
Loading
Loading