mirror of
https://github.com/th30d4y/OpenLearnX.git
synced 2026-05-26 11:25:49 +00:00
harini
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
import { CodingProblemView } from "@/components/coding-problem-view"
|
||||
|
||||
interface CodingProblemPageProps {
|
||||
params: {
|
||||
problemId: string
|
||||
}
|
||||
}
|
||||
|
||||
export default function CodingProblemPage({ params }: CodingProblemPageProps) {
|
||||
return <CodingProblemView problemId={params.problemId} />
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { CodingProblemList } from "@/components/coding-problem-list"
|
||||
|
||||
export default function CodingPage() {
|
||||
return <CodingProblemList />
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
"use client"
|
||||
|
||||
import { CourseSidebar } from "@/components/course-sidebar"
|
||||
import { LessonViewer } from "@/components/lesson-viewer"
|
||||
import { Loader2 } from "lucide-react"
|
||||
import { useAuth } from "@/context/auth-context"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { toast } from "react-hot-toast"
|
||||
import { useState, useEffect } from "react"
|
||||
import type { Course } from "@/lib/types"
|
||||
import api from "@/lib/api" // Corrected import: default import
|
||||
|
||||
interface CourseDetailPageProps {
|
||||
params: {
|
||||
courseId: string
|
||||
lessonId: string
|
||||
}
|
||||
}
|
||||
|
||||
export default function CourseDetailPage({ params }: CourseDetailPageProps) {
|
||||
const { courseId, lessonId } = params
|
||||
const { user, firebaseUser, isLoadingAuth } = useAuth() // Allow firebaseUser
|
||||
const router = useRouter()
|
||||
const [course, setCourse] = useState<Course | null>(null)
|
||||
const [isLoadingCourse, setIsLoadingCourse] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoadingAuth && !user && !firebaseUser) {
|
||||
// Allow either MetaMask or Firebase user
|
||||
toast.error("Please login to view courses.")
|
||||
router.push("/")
|
||||
return
|
||||
}
|
||||
|
||||
const fetchCourse = async () => {
|
||||
setIsLoadingCourse(true)
|
||||
setError(null)
|
||||
try {
|
||||
// --- ORIGINAL API CALL (UNCOMMENT WHEN BACKEND IS READY) ---
|
||||
const response = await api.get<Course>(`/api/courses/${courseId}`)
|
||||
setCourse(response.data)
|
||||
} catch (err: any) {
|
||||
console.error("Failed to fetch course details:", err)
|
||||
setError(err.response?.data?.message || "Failed to load course details.")
|
||||
toast.error(err.response?.data?.message || "Failed to load course details.")
|
||||
} finally {
|
||||
setIsLoadingCourse(false)
|
||||
}
|
||||
}
|
||||
|
||||
if (user || firebaseUser) {
|
||||
// Only fetch if either user type is logged in
|
||||
fetchCourse()
|
||||
}
|
||||
}, [user, firebaseUser, isLoadingAuth, router, courseId])
|
||||
|
||||
if (isLoadingAuth || isLoadingCourse) {
|
||||
return (
|
||||
<div className="flex justify-center items-center min-h-[calc(100vh-64px)]">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-primary-purple" />
|
||||
<span className="ml-2 text-lg">Loading course...</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="flex justify-center items-center min-h-[calc(100vh-64px)] text-red-500">
|
||||
<p>{error}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!course) {
|
||||
return (
|
||||
<div className="flex justify-center items-center min-h-[calc(100vh-64px)] text-gray-600 dark:text-gray-300">
|
||||
<p className="text-xl">Course not found.</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col md:flex-row min-h-[calc(100vh-64px)]">
|
||||
<CourseSidebar courseId={course.id} modules={course.modules} activeLessonId={lessonId} />
|
||||
<div className="flex-1 p-4 md:p-8 overflow-y-auto">
|
||||
<LessonViewer courseId={course.id} lessonId={lessonId} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
"use client"
|
||||
|
||||
import { CourseSidebar } from "@/components/course-sidebar"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Loader2 } from "lucide-react"
|
||||
import { useAuth } from "@/context/auth-context"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { toast } from "react-hot-toast"
|
||||
import { useState, useEffect } from "react"
|
||||
import type { Course } from "@/lib/types"
|
||||
import api from "@/lib/api" // Corrected import: default import
|
||||
|
||||
interface CourseOverviewPageProps {
|
||||
params: {
|
||||
courseId: string
|
||||
}
|
||||
}
|
||||
|
||||
export default function CourseOverviewPage({ params }: CourseOverviewPageProps) {
|
||||
const { courseId } = params
|
||||
const { user, firebaseUser, isLoadingAuth } = useAuth() // Allow firebaseUser
|
||||
const router = useRouter()
|
||||
const [course, setCourse] = useState<Course | null>(null)
|
||||
const [isLoadingCourse, setIsLoadingCourse] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoadingAuth && !user && !firebaseUser) {
|
||||
// Allow either MetaMask or Firebase user
|
||||
toast.error("Please login to view courses.")
|
||||
router.push("/")
|
||||
return
|
||||
}
|
||||
|
||||
const fetchCourse = async () => {
|
||||
setIsLoadingCourse(true)
|
||||
setError(null)
|
||||
try {
|
||||
// --- ORIGINAL API CALL (UNCOMMENT WHEN BACKEND IS READY) ---
|
||||
const response = await api.get<Course>(`/api/courses/${courseId}`)
|
||||
setCourse(response.data)
|
||||
} catch (err: any) {
|
||||
console.error("Failed to fetch course details:", err)
|
||||
setError(err.response?.data?.message || "Failed to load course details.")
|
||||
toast.error(err.response?.data?.message || "Failed to load course details.")
|
||||
} finally {
|
||||
setIsLoadingCourse(false)
|
||||
}
|
||||
}
|
||||
|
||||
if (user || firebaseUser) {
|
||||
// Only fetch if either user type is logged in
|
||||
fetchCourse()
|
||||
}
|
||||
}, [user, firebaseUser, isLoadingAuth, router, courseId])
|
||||
|
||||
useEffect(() => {
|
||||
if (course && course.modules.length > 0 && course.modules[0].lessons.length > 0) {
|
||||
// Redirect to the first lesson of the course
|
||||
router.replace(`/courses/${courseId}/lesson/${course.modules[0].lessons[0].id}`)
|
||||
}
|
||||
}, [course, courseId, router])
|
||||
|
||||
if (isLoadingAuth || isLoadingCourse) {
|
||||
return (
|
||||
<div className="flex justify-center items-center min-h-[calc(100vh-64px)]">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-primary-purple" />
|
||||
<span className="ml-2 text-lg">Loading course...</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="flex justify-center items-center min-h-[calc(100vh-64px)] text-red-500">
|
||||
<p>{error}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!course) {
|
||||
return (
|
||||
<div className="flex justify-center items-center min-h-[calc(100vh-64px)] text-gray-600 dark:text-gray-300">
|
||||
<p className="text-xl">Course not found.</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col md:flex-row min-h-[calc(100vh-64px)]">
|
||||
<CourseSidebar courseId={course.id} modules={course.modules} activeLessonId="" />
|
||||
<div className="flex-1 p-4 md:p-8 overflow-y-auto">
|
||||
<Card className="bg-white shadow-md rounded-lg p-6 dark:bg-gray-800 dark:text-gray-100">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl font-bold text-primary-purple">{course.title} Overview</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<p className="text-lg text-gray-700 dark:text-gray-200">{course.description}</p>
|
||||
<p className="text-gray-600 dark:text-gray-300">Select a lesson from the sidebar to begin.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { CourseList } from "@/components/course-list"
|
||||
|
||||
export default function CoursesPage() {
|
||||
return <CourseList />
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { DashboardStatsOverview } from "@/components/dashboard-stats"
|
||||
|
||||
export default function DashboardPage() {
|
||||
return <DashboardStatsOverview />
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer utilities {
|
||||
.text-balance {
|
||||
text-wrap: balance;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 0 0% 3.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 0 0% 3.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 0 0% 3.9%;
|
||||
--primary: 0 0% 9%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 0 0% 96.1%;
|
||||
--secondary-foreground: 0 0% 9%;
|
||||
--muted: 0 0% 96.1%;
|
||||
--muted-foreground: 0 0% 45.1%;
|
||||
--accent: 0 0% 96.1%;
|
||||
--accent-foreground: 0 0% 9%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 0 0% 89.8%;
|
||||
--input: 0 0% 89.8%;
|
||||
--ring: 0 0% 3.9%;
|
||||
--chart-1: 12 76% 61%;
|
||||
--chart-2: 173 58% 39%;
|
||||
--chart-3: 197 37% 24%;
|
||||
--chart-4: 43 74% 66%;
|
||||
--chart-5: 27 87% 67%;
|
||||
--radius: 0.5rem;
|
||||
--sidebar-background: 0 0% 98%;
|
||||
--sidebar-foreground: 240 5.3% 26.1%;
|
||||
--sidebar-primary: 240 5.9% 10%;
|
||||
--sidebar-primary-foreground: 0 0% 98%;
|
||||
--sidebar-accent: 240 4.8% 95.9%;
|
||||
--sidebar-accent-foreground: 240 5.9% 10%;
|
||||
--sidebar-border: 220 13% 91%;
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
}
|
||||
.dark {
|
||||
--background: 0 0% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--card: 0 0% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
--popover: 0 0% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 0 0% 9%;
|
||||
--secondary: 0 0% 14.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
--muted: 0 0% 14.9%;
|
||||
--muted-foreground: 0 0% 63.9%;
|
||||
--accent: 0 0% 14.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 0 0% 14.9%;
|
||||
--input: 0 0% 14.9%;
|
||||
--ring: 0 0% 83.1%;
|
||||
--chart-1: 220 70% 50%;
|
||||
--chart-2: 160 60% 45%;
|
||||
--chart-3: 30 80% 55%;
|
||||
--chart-4: 280 65% 60%;
|
||||
--chart-5: 340 75% 55%;
|
||||
--sidebar-background: 240 5.9% 10%;
|
||||
--sidebar-foreground: 240 4.8% 95.9%;
|
||||
--sidebar-primary: 224.3 76.3% 48%;
|
||||
--sidebar-primary-foreground: 0 0% 100%;
|
||||
--sidebar-accent: 240 3.7% 15.9%;
|
||||
--sidebar-accent-foreground: 240 4.8% 95.9%;
|
||||
--sidebar-border: 240 3.7% 15.9%;
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import type React from "react"
|
||||
import type { Metadata } from "next"
|
||||
import { Inter } from "next/font/google"
|
||||
import "./globals.css"
|
||||
import { Toaster } from "react-hot-toast"
|
||||
import { AuthProvider } from "@/context/auth-context"
|
||||
import { Navbar } from "@/components/ui/navbar"
|
||||
import { ThemeProvider } from "@/components/theme-provider"
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] })
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "OpenLearnX - Decentralized Adaptive Learning",
|
||||
description: "AI-powered adaptive testing with blockchain-secured credentials.",
|
||||
generator: 'v0.dev'
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body className={inter.className}>
|
||||
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
|
||||
<AuthProvider>
|
||||
<Navbar />
|
||||
<main>{children}</main>
|
||||
<Toaster position="top-right" />
|
||||
</AuthProvider>
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import Link from "next/link"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Brain, Zap, LinkIcon, BookOpen, Code, Lightbulb } from "lucide-react"
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="flex flex-col min-h-[calc(100vh-64px)]">
|
||||
<section className="relative w-full py-12 md:py-24 lg:py-32 bg-gradient-to-r from-primary-blue to-primary-purple text-white">
|
||||
<div className="container px-4 md:px-6 text-center">
|
||||
<div className="max-w-3xl mx-auto space-y-4">
|
||||
<h1 className="text-4xl font-bold tracking-tighter sm:text-5xl md:text-6xl">
|
||||
OpenLearnX – Decentralized Adaptive Learning
|
||||
</h1>
|
||||
<p className="text-lg md:text-xl">
|
||||
Unlock your potential with AI-powered adaptive learning, coding practice, and blockchain-secured
|
||||
credentials.
|
||||
</p>
|
||||
<div className="flex flex-col gap-2 sm:flex-row justify-center">
|
||||
<Link href="/courses">
|
||||
<Button className="bg-white text-primary-purple hover:bg-gray-100 px-8 py-3 text-lg font-semibold rounded-full shadow-lg">
|
||||
Start Learning
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="w-full py-12 md:py-24 lg:py-32 bg-gray-50 dark:bg-gray-900">
|
||||
<div className="container px-4 md:px-6">
|
||||
<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
|
||||
<Card className="bg-white shadow-md rounded-lg p-6 dark:bg-gray-800 dark:text-gray-100">
|
||||
<CardHeader className="flex flex-row items-center space-x-4 pb-2">
|
||||
<BookOpen className="h-8 w-8 text-primary-blue" />
|
||||
<CardTitle className="text-xl font-semibold">Interactive Courses</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-gray-600 dark:text-gray-300">
|
||||
Engage with rich multimedia content, track your progress, and master new subjects at your own pace.
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="bg-white shadow-md rounded-lg p-6 dark:bg-gray-800 dark:text-gray-100">
|
||||
<CardHeader className="flex flex-row items-center space-x-4 pb-2">
|
||||
<Code className="h-8 w-8 text-primary-purple" />
|
||||
<CardTitle className="text-xl font-semibold">LeetCode-Style Coding Practice</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-gray-600 dark:text-gray-300">
|
||||
Sharpen your coding skills with interactive problems, instant feedback, and a built-in code editor.
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="bg-white shadow-md rounded-lg p-6 dark:bg-gray-800 dark:text-gray-100">
|
||||
<CardHeader className="flex flex-row items-center space-x-4 pb-2">
|
||||
<Lightbulb className="h-8 w-8 text-primary-blue" />
|
||||
<CardTitle className="text-xl font-semibold">Advanced Quiz Platform</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-gray-600 dark:text-gray-300">
|
||||
Test your knowledge with timed multiple-choice quizzes, detailed explanations, and performance tracking.
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="bg-white shadow-md rounded-lg p-6 dark:bg-gray-800 dark:text-gray-100">
|
||||
<CardHeader className="flex flex-row items-center space-x-4 pb-2">
|
||||
<Brain className="h-8 w-8 text-primary-purple" />
|
||||
<CardTitle className="text-xl font-semibold">AI-powered Adaptive Learning</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-gray-600 dark:text-gray-300">
|
||||
Our platform intelligently adjusts content and question difficulty based on your performance.
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="bg-white shadow-md rounded-lg p-6 dark:bg-gray-800 dark:text-gray-100">
|
||||
<CardHeader className="flex flex-row items-center space-x-4 pb-2">
|
||||
<LinkIcon className="h-8 w-8 text-primary-blue" />
|
||||
<CardTitle className="text-xl font-semibold">Blockchain-secured Credentials</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-gray-600 dark:text-gray-300">
|
||||
Your achievements are secured on the blockchain, providing verifiable and tamper-proof records.
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="bg-white shadow-md rounded-lg p-6 dark:bg-gray-800 dark:text-gray-100">
|
||||
<CardHeader className="flex flex-row items-center space-x-4 pb-2">
|
||||
<Zap className="h-8 w-8 text-primary-purple" />
|
||||
<CardTitle className="text-xl font-semibold">Personalized Dashboard</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-gray-600 dark:text-gray-300">
|
||||
Track your progress, identify strengths and weaknesses, and visualize your learning journey.
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { QuizRunner } from "@/components/quiz-runner"
|
||||
|
||||
interface QuizPageProps {
|
||||
params: {
|
||||
quizId: string
|
||||
}
|
||||
}
|
||||
|
||||
export default function QuizPage({ params }: QuizPageProps) {
|
||||
return <QuizRunner quizId={params.quizId} />
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { QuizList } from "@/components/quiz-list"
|
||||
|
||||
export default function QuizzesPage() {
|
||||
return <QuizList />
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { TestingSession } from "@/components/testing-session"
|
||||
|
||||
export default function TestPage() {
|
||||
return <TestingSession />
|
||||
}
|
||||
Reference in New Issue
Block a user