diff --git a/src/app/components/Navbar.tsx b/src/app/components/Navbar.tsx new file mode 100644 index 0000000..13e2685 --- /dev/null +++ b/src/app/components/Navbar.tsx @@ -0,0 +1,102 @@ +"use client"; + +import { useState } from "react"; + +interface NavbarProps { + onLogout?: () => void; + isSidebarOpen?: boolean; +} + +export default function Navbar({ + onLogout, + isSidebarOpen = true, +}: NavbarProps) { + const [isDarkMode, setIsDarkMode] = useState(false); + + const handleLogout = () => { + if (onLogout) { + onLogout(); + } else { + console.log("Logout clicked"); + } + }; + + return ( + + ); +} diff --git a/src/app/components/ProblemsTable.tsx b/src/app/components/ProblemsTable.tsx new file mode 100644 index 0000000..58936ff --- /dev/null +++ b/src/app/components/ProblemsTable.tsx @@ -0,0 +1,156 @@ +"use client"; + +import { Problem } from "@/types"; + +interface ProblemsTableProps { + problems: Problem[]; + onProblemClick?: (problemId: string) => void; +} + +export default function ProblemsTable({ + problems, + onProblemClick, +}: ProblemsTableProps) { + const getDifficultyColor = (difficulty: string) => { + switch (difficulty) { + case "Easy": + return "bg-green-50 text-green-700 border border-green-200"; + case "Medium": + return "bg-yellow-50 text-yellow-700 border border-yellow-200"; + case "Hard": + return "bg-red-50 text-red-700 border border-red-200"; + default: + return "bg-gray-50 text-gray-700 border border-gray-200"; + } + }; + + const getStatusIcon = (status: string) => { + switch (status) { + case "solved": + return ( +
+ + + +
+ ); + case "attempted": + return ( +
+ ); + default: + return ( +
+ ); + } + }; + + const handleProblemClick = (problemId: string) => { + if (onProblemClick) { + onProblemClick(problemId); + } + }; + + return ( +
+
+ + + + + + + + + + + + + {problems.map((problem, index) => ( + handleProblemClick(problem.id)} + > + + + + + + + + ))} + +
+ Status + + Problem + + Difficulty + + Category + + Acceptance + + Action +
{getStatusIcon(problem.status)} +
+
+ {problem.title} +
+
+ {problem.tags.map((tag, index) => ( + + {tag} + + ))} +
+
+
+ + {problem.difficulty} + + + {problem.category} + + {problem.acceptance}% + + +
+
+ + {problems.length === 0 && ( +
+ No problems found. Try adjusting your filters. +
+ )} +
+ ); +} diff --git a/src/app/components/RecentActivity.tsx b/src/app/components/RecentActivity.tsx new file mode 100644 index 0000000..b597008 --- /dev/null +++ b/src/app/components/RecentActivity.tsx @@ -0,0 +1,67 @@ +"use client"; + +interface ActivityItem { + id: string; + title: string; + difficulty: "Easy" | "Medium" | "Hard"; + status: "Completed" | "In Progress"; + progress: number; +} + +interface RecentActivityProps { + activities: ActivityItem[]; +} + +export default function RecentActivity({ activities }: RecentActivityProps) { + const getDifficultyColor = (difficulty: string) => { + switch (difficulty) { + case "Easy": + return "bg-green-50 text-green-700 border border-green-200"; + case "Medium": + return "bg-yellow-50 text-yellow-700 border border-yellow-200"; + case "Hard": + return "bg-red-50 text-red-700 border border-red-200"; + default: + return "bg-gray-50 text-gray-700 border border-gray-200"; + } + }; + + return ( +
+

Recent Activity

+
+ {activities.map((activity, index) => ( +
+
+
+

+ {activity.title} +

+
+ + {activity.difficulty} + + + {activity.status} + +
+
+ +
+
+ ))} +
+
+ ); +} diff --git a/src/app/components/SearchBar.tsx b/src/app/components/SearchBar.tsx new file mode 100644 index 0000000..13f4563 --- /dev/null +++ b/src/app/components/SearchBar.tsx @@ -0,0 +1,47 @@ +"use client"; + +import { useState } from "react"; + +interface SearchBarProps { + onSearch?: (query: string) => void; + placeholder?: string; +} + +export default function SearchBar({ + onSearch, + placeholder = "Search problems, topics, or algorithms...", +}: SearchBarProps) { + const [searchQuery, setSearchQuery] = useState(""); + + const handleSearch = (value: string) => { + setSearchQuery(value); + if (onSearch) { + onSearch(value); + } + }; + + return ( +
+ + + + handleSearch(e.target.value)} + className="w-full pl-12 pr-4 py-3 bg-white border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm" + /> +
+ ); +} diff --git a/src/app/components/Sidebar.tsx b/src/app/components/Sidebar.tsx new file mode 100644 index 0000000..21e3d80 --- /dev/null +++ b/src/app/components/Sidebar.tsx @@ -0,0 +1,216 @@ +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; + +interface SidebarProps { + selectedCategory?: string; + onCategoryChange?: (category: string) => void; + isOpen: boolean; + onClose?: () => void; +} + +export default function Sidebar({ + selectedCategory, + onCategoryChange, + isOpen, + onClose, +}: SidebarProps) { + const pathname = usePathname(); + + const mainNavItems = [ + { + icon: ( + + + + ), + label: "Dashboard", + href: "/", + }, + { + icon: ( + + + + ), + label: "Problems", + href: "/problems", + }, + { + icon: ( + + + + ), + label: "Competitions", + href: "/competitions", + }, + { + icon: ( + + + + ), + label: "Learn", + href: "/learn", + }, + { + icon: ( + + + + ), + label: "Progress", + href: "/progress", + }, + ]; + + const categories = [ + "Neural Networks", + "Computer Vision", + "NLP", + "Reinforcement Learning", + "Time Series", + ]; + + return ( + <> + + + {isOpen && ( +
+ )} + + ); +} diff --git a/src/app/components/StatsCard.tsx b/src/app/components/StatsCard.tsx new file mode 100644 index 0000000..94de7f0 --- /dev/null +++ b/src/app/components/StatsCard.tsx @@ -0,0 +1,21 @@ +"use client"; + +interface StatsCardProps { + title: string; + value: string | number; + icon: React.ReactNode; +} + +export default function StatsCard({ title, value, icon }: StatsCardProps) { + return ( +
+
+
+

{title}

+

{value}

+
+
{icon}
+
+
+ ); +} diff --git a/src/app/components/WeeklyGoals.tsx b/src/app/components/WeeklyGoals.tsx new file mode 100644 index 0000000..922884d --- /dev/null +++ b/src/app/components/WeeklyGoals.tsx @@ -0,0 +1,45 @@ +"use client"; + +interface Goal { + title: string; + current: number; + target: number; +} + +interface WeeklyGoalsProps { + goals: Goal[]; +} + +export default function WeeklyGoals({ goals }: WeeklyGoalsProps) { + const getProgressPercentage = (current: number, target: number) => { + return Math.min((current / target) * 100, 100); + }; + + return ( +
+

Weekly Goals

+
+ {goals.map((goal, index) => ( +
+
+ + {goal.title} + + + {goal.current}/{goal.target} + +
+
+
+
+
+ ))} +
+
+ ); +} diff --git a/src/app/globals.css b/src/app/globals.css index b5c61c9..a461c50 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,3 +1 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; +@import "tailwindcss"; \ No newline at end of file diff --git a/src/app/page.tsx b/src/app/page.tsx index 9af8686..5aab8ea 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,8 +1,199 @@ +"use client"; + +import { useState } from "react"; +import Sidebar from "./components/Sidebar"; +import Navbar from "./components/Navbar"; +import StatsCard from "./components/StatsCard"; +import RecentActivity from "./components/RecentActivity"; +import WeeklyGoals from "./components/WeeklyGoals"; + +const STATS = { + problemsSolved: 47, + currentStreak: 12, + ranking: "#1,247", + achievements: 8, +}; + +const RECENT_ACTIVITIES = [ + { + id: "1", + title: "Image Classification with CNN", + difficulty: "Medium" as const, + status: "Completed" as const, + progress: 100, + }, + { + id: "2", + title: "Sentiment Analysis Model", + difficulty: "Easy" as const, + status: "In Progress" as const, + progress: 60, + }, + { + id: "3", + title: "Time Series Forecasting", + difficulty: "Hard" as const, + status: "In Progress" as const, + progress: 30, + }, +]; + +const WEEKLY_GOALS = [ + { title: "Problems Solved", current: 7, target: 10 }, + { title: "Learning Hours", current: 8, target: 12 }, + { title: "Code Reviews", current: 3, target: 5 }, +]; + export default function Home() { + const [isSidebarOpen, setIsSidebarOpen] = useState(true); + const [selectedCategory, setSelectedCategory] = useState(""); + + const handleCategoryChange = (category: string) => { + setSelectedCategory(category); + }; + + const handleLogout = () => { + console.log("Logging out..."); + }; + return ( -
-

MLBoost Homepage

-

The "LeetCode for ML" is under construction.

-
+
+ setIsSidebarOpen(false)} + /> + + {!isSidebarOpen && ( + + )} + +
+ + +
+
+

+ Welcome back, Alex +

+

+ Continue your AI/ML learning journey +

+
+ +
+ + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> +
+ +
+
+ +
+ +
+ +
+
+
+
+
); } diff --git a/src/app/problems/page.tsx b/src/app/problems/page.tsx new file mode 100644 index 0000000..993e0dc --- /dev/null +++ b/src/app/problems/page.tsx @@ -0,0 +1,349 @@ +"use client"; + +import { useState, useMemo, useEffect } from "react"; +import Sidebar from "../components/Sidebar"; +import Navbar from "../components/Navbar"; +import ProblemsTable from "../components/ProblemsTable"; +import SearchBar from "../components/SearchBar"; +import { Problem, FilterState } from "@/types"; +import { fetchProblems } from "@/lib/api"; + +const SAMPLE_PROBLEMS: Problem[] = [ + { + id: "1", + title: "Linear Regression from Scratch", + difficulty: "Easy", + category: "Supervised Learning", + acceptance: 78, + status: "solved", + tags: ["regression", "numpy"], + }, + { + id: "2", + title: "Convolutional Neural Network", + difficulty: "Medium", + category: "Deep Learning", + acceptance: 54, + status: "solved", + tags: ["cnn", "tensorflow", "images"], + }, + { + id: "3", + title: "LSTM Time Series Prediction", + difficulty: "Medium", + category: "Deep Learning", + acceptance: 48, + status: "unsolved", + tags: ["lstm", "time-series"], + }, + { + id: "4", + title: "Transformer Architecture", + difficulty: "Hard", + category: "NLP", + acceptance: 32, + status: "unsolved", + tags: ["transformer", "attention", "nlp"], + }, + { + id: "5", + title: "K-Means Clustering", + difficulty: "Easy", + category: "Unsupervised Learning", + acceptance: 72, + status: "attempted", + tags: ["clustering", "unsupervised"], + }, + { + id: "6", + title: "Object Detection with YOLO", + difficulty: "Medium", + category: "Computer Vision", + acceptance: 45, + status: "unsolved", + tags: ["object-detection", "yolo", "cv"], + }, + { + id: "7", + title: "Sentiment Analysis", + difficulty: "Easy", + category: "NLP", + acceptance: 65, + status: "solved", + tags: ["nlp", "classification"], + }, + { + id: "8", + title: "GAN for Image Generation", + difficulty: "Hard", + category: "Deep Learning", + acceptance: 28, + status: "unsolved", + tags: ["gan", "generative", "images"], + }, + { + id: "9", + title: "Q-Learning CartPole", + difficulty: "Medium", + category: "Reinforcement Learning", + acceptance: 51, + status: "unsolved", + tags: ["rl", "q-learning"], + }, + { + id: "10", + title: "Neural Style Transfer", + difficulty: "Hard", + category: "Computer Vision", + acceptance: 38, + status: "unsolved", + tags: ["style-transfer", "cnn", "art"], + }, +]; + +export default function ProblemsPage() { + const [problems, setProblems] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [filters, setFilters] = useState({ + level: "All Levels", + category: "All Categories", + statusFilter: "all", + }); + const [selectedCategory, setSelectedCategory] = useState(""); + const [isSidebarOpen, setIsSidebarOpen] = useState(true); + const [searchQuery, setSearchQuery] = useState(""); + + useEffect(() => { + const loadProblems = async () => { + try { + setIsLoading(true); + // Try to get problems from the backend API + const data = await fetchProblems(); + + // If API doesn't return any data, use our hardcoded sample problems instead + const problemsData = + !data || data.length === 0 ? SAMPLE_PROBLEMS : data; + setProblems(problemsData); + } catch (error) { + console.error("Failed to fetch problems, using hardcoded data:", error); + // If API call fails, just use the hardcoded sample data + setProblems(SAMPLE_PROBLEMS); + } finally { + setIsLoading(false); + } + }; + + loadProblems(); + }, []); + + const filteredProblems = useMemo(() => { + let filtered = [...problems]; + + if (filters.level !== "All Levels") { + filtered = filtered.filter((p) => p.difficulty === filters.level); + } + + if (filters.category !== "All Categories") { + filtered = filtered.filter((p) => p.category === filters.category); + } + + if (searchQuery) { + const query = searchQuery.toLowerCase(); + filtered = filtered.filter( + (p) => + p.title.toLowerCase().includes(query) || + p.tags.some((tag) => tag.toLowerCase().includes(query)) || + p.category.toLowerCase().includes(query) + ); + } + + if (filters.statusFilter === "solved") { + filtered = filtered.filter((p) => p.status === "solved"); + } else if (filters.statusFilter === "todo") { + filtered = filtered.filter( + (p) => p.status === "unsolved" || p.status === "attempted" + ); + } + + return filtered; + }, [filters, problems, searchQuery]); + + const handleCategoryChange = (category: string) => { + setSelectedCategory(category); + setFilters((prev) => ({ ...prev, category })); + }; + + const handleLogout = () => { + console.log("Logging out..."); + }; + + return ( +
+ setIsSidebarOpen(false)} + /> + + {!isSidebarOpen && ( + + )} + +
+ + +
+
+

+ Practice Problems +

+

+ Master AI/ML concepts through hands-on challenges +

+
+ +
+
+
+ + + + +
+ +
+ + + + +
+ +
+ {filteredProblems.length} problems +
+
+
+ +
+ +
+ +
+ + + +
+ + {isLoading ? ( +
+
+
+

Loading problems...

+
+
+ ) : ( + console.log("Navigate to problem:", id)} + /> + )} +
+
+
+ ); +} diff --git a/src/lib/api.ts b/src/lib/api.ts new file mode 100644 index 0000000..c989622 --- /dev/null +++ b/src/lib/api.ts @@ -0,0 +1,139 @@ +import { Problem } from "@/types"; + +const API_BASE_URL = + process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000/api"; + +export async function fetchProblems(): Promise { + try { + const response = await fetch(`${API_BASE_URL}/problems`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + throw new Error("Failed to fetch problems"); + } + + const data = await response.json(); + return data; + } catch (error) { + console.error("Error fetching problems:", error); + throw error; + } +} + +export async function fetchProblemById(problemId: string): Promise { + try { + const response = await fetch(`${API_BASE_URL}/problems/${problemId}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + throw new Error("Failed to fetch problem"); + } + + const data = await response.json(); + return data; + } catch (error) { + console.error("Error fetching problem:", error); + throw error; + } +} + +export async function fetchProblemsByCategory( + category: string +): Promise { + try { + const response = await fetch( + `${API_BASE_URL}/problems?category=${encodeURIComponent(category)}`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + } + ); + + if (!response.ok) { + throw new Error("Failed to fetch problems by category"); + } + + const data = await response.json(); + return data; + } catch (error) { + console.error("Error fetching problems by category:", error); + throw error; + } +} + +export async function fetchUserStats(): Promise { + try { + const response = await fetch(`${API_BASE_URL}/user/stats`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + throw new Error("Failed to fetch user stats"); + } + + const data = await response.json(); + return data; + } catch (error) { + console.error("Error fetching user stats:", error); + throw error; + } +} + +export async function submitSolution( + problemId: string, + solution: string +): Promise { + try { + const response = await fetch( + `${API_BASE_URL}/problems/${problemId}/submit`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ solution }), + } + ); + + if (!response.ok) { + throw new Error("Failed to submit solution"); + } + + const data = await response.json(); + return data; + } catch (error) { + console.error("Error submitting solution:", error); + throw error; + } +} + +export async function logout(): Promise { + try { + const response = await fetch(`${API_BASE_URL}/auth/logout`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + throw new Error("Failed to logout"); + } + } catch (error) { + console.error("Error during logout:", error); + throw error; + } +} diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..05623ea --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,15 @@ +export interface Problem { + id: string; + title: string; + difficulty: "Easy" | "Medium" | "Hard"; + category: string; + acceptance: number; + status: "solved" | "attempted" | "unsolved"; + tags: string[]; +} + +export interface FilterState { + level: string; + category: string; + statusFilter: "all" | "todo" | "solved"; +}