Spaces:
Sleeping
Sleeping
muhammad naveed commited on
Upload folder using huggingface_hub
Browse files- Dockerfile +24 -0
- README.md +27 -4
- app/about/page.tsx +202 -0
- app/contact/page.tsx +246 -0
- app/courses/page.tsx +268 -0
- app/globals.css +212 -0
- app/layout.tsx +19 -0
- app/page.tsx +814 -0
- app/register/page.tsx +331 -0
- app/resources/page.tsx +1272 -0
- app/student/chat/page.tsx +420 -0
- app/student/dashboard/page.tsx +477 -0
- app/student/learn/page.tsx +521 -0
- app/student/progress/page.tsx +223 -0
- app/student/quiz/page.tsx +310 -0
- app/teacher/dashboard/page.tsx +317 -0
- lib/api.ts +387 -0
- next-env.d.ts +5 -0
- next.config.js +51 -0
- package-lock.json +0 -0
- package.json +30 -0
- postcss.config.js +6 -0
- tailwind.config.js +107 -0
- tsconfig.json +34 -0
- tsconfig.tsbuildinfo +1 -0
Dockerfile
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM node:18-alpine
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
# Copy package files
|
| 6 |
+
COPY package*.json ./
|
| 7 |
+
|
| 8 |
+
# Install dependencies
|
| 9 |
+
RUN npm install
|
| 10 |
+
|
| 11 |
+
# Copy application code
|
| 12 |
+
COPY . .
|
| 13 |
+
|
| 14 |
+
# Set the API URL to the Hugging Face deployed API
|
| 15 |
+
ENV NEXT_PUBLIC_API_URL=https://naveed247365-learnflow-api.hf.space
|
| 16 |
+
|
| 17 |
+
# Build the application
|
| 18 |
+
RUN npm run build
|
| 19 |
+
|
| 20 |
+
# Expose port
|
| 21 |
+
EXPOSE 3000
|
| 22 |
+
|
| 23 |
+
# Start the application
|
| 24 |
+
CMD ["npm", "start"]
|
README.md
CHANGED
|
@@ -1,10 +1,33 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
colorFrom: green
|
| 5 |
-
colorTo:
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: LearnFlow
|
| 3 |
+
emoji: 🎓
|
| 4 |
colorFrom: green
|
| 5 |
+
colorTo: blue
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
| 8 |
+
license: mit
|
| 9 |
+
app_port: 3000
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# LearnFlow - AI-Powered Python Learning Platform
|
| 13 |
+
|
| 14 |
+
An interactive Python learning platform with AI tutors, code execution, and progress tracking.
|
| 15 |
+
|
| 16 |
+
## Features
|
| 17 |
+
|
| 18 |
+
- **AI Python Tutor** - Get instant help with Python questions
|
| 19 |
+
- **Interactive Code Editor** - Write and run Python code
|
| 20 |
+
- **Quizzes** - Test your knowledge
|
| 21 |
+
- **Progress Tracking** - Track your learning journey
|
| 22 |
+
- **Learning Resources** - Guides, cheatsheets, videos
|
| 23 |
+
|
| 24 |
+
## Tech Stack
|
| 25 |
+
|
| 26 |
+
- Next.js 14
|
| 27 |
+
- Tailwind CSS
|
| 28 |
+
- TypeScript
|
| 29 |
+
- Monaco Editor
|
| 30 |
+
|
| 31 |
+
## Live Demo
|
| 32 |
+
|
| 33 |
+
Try it out! Register an account and start learning Python.
|
app/about/page.tsx
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import Link from 'next/link';
|
| 4 |
+
|
| 5 |
+
export default function AboutPage() {
|
| 6 |
+
const team = [
|
| 7 |
+
{ name: 'Dr. Sarah Chen', role: 'Founder & CEO', image: '👩💼', bio: 'Former Google engineer with 15 years in EdTech' },
|
| 8 |
+
{ name: 'Muhammad Ali', role: 'Lead AI Engineer', image: '👨💻', bio: 'PhD in Machine Learning, Stanford University' },
|
| 9 |
+
{ name: 'Emily Rodriguez', role: 'Head of Curriculum', image: '👩🏫', bio: '10 years teaching Python at MIT' },
|
| 10 |
+
{ name: 'James Wilson', role: 'CTO', image: '👨🔬', bio: 'Built scalable systems at AWS and Netflix' },
|
| 11 |
+
];
|
| 12 |
+
|
| 13 |
+
const values = [
|
| 14 |
+
{ icon: '🎯', title: 'Personalized Learning', description: 'AI adapts to your pace and learning style' },
|
| 15 |
+
{ icon: '🤝', title: 'Supportive Community', description: 'Learn alongside thousands of motivated students' },
|
| 16 |
+
{ icon: '💡', title: 'Practical Skills', description: 'Build real projects, not just theory' },
|
| 17 |
+
{ icon: '🚀', title: 'Career Growth', description: 'Skills that employers actually want' },
|
| 18 |
+
];
|
| 19 |
+
|
| 20 |
+
const milestones = [
|
| 21 |
+
{ year: '2022', event: 'LearnFlow founded with a mission to democratize coding education' },
|
| 22 |
+
{ year: '2023', event: 'Launched AI tutoring system, reached 10,000 students' },
|
| 23 |
+
{ year: '2024', event: 'Expanded to 50+ countries, 100,000+ students enrolled' },
|
| 24 |
+
{ year: '2025', event: 'Introduced advanced debugging agent and teacher dashboard' },
|
| 25 |
+
];
|
| 26 |
+
|
| 27 |
+
return (
|
| 28 |
+
<div className="min-h-screen bg-gradient-to-b from-cream-50 to-white">
|
| 29 |
+
{/* Header */}
|
| 30 |
+
<header className="bg-white/80 backdrop-blur-md border-b border-warmgray-100 sticky top-0 z-50">
|
| 31 |
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 32 |
+
<div className="flex items-center justify-between h-16">
|
| 33 |
+
<Link href="/" className="flex items-center space-x-3">
|
| 34 |
+
<div className="w-10 h-10 bg-gradient-to-br from-teal-500 to-teal-600 rounded-xl flex items-center justify-center shadow-soft">
|
| 35 |
+
<span className="text-white font-bold text-lg">L</span>
|
| 36 |
+
</div>
|
| 37 |
+
<span className="text-xl font-bold text-warmgray-900">LearnFlow</span>
|
| 38 |
+
</Link>
|
| 39 |
+
|
| 40 |
+
<nav className="hidden md:flex items-center space-x-1">
|
| 41 |
+
{[
|
| 42 |
+
{ name: 'Home', href: '/' },
|
| 43 |
+
{ name: 'Courses', href: '/courses' },
|
| 44 |
+
{ name: 'About', href: '/about' },
|
| 45 |
+
{ name: 'Resources', href: '/resources' },
|
| 46 |
+
{ name: 'Contact', href: '/contact' },
|
| 47 |
+
].map((item) => (
|
| 48 |
+
<Link
|
| 49 |
+
key={item.name}
|
| 50 |
+
href={item.href}
|
| 51 |
+
className={`px-4 py-2 rounded-full text-sm font-medium transition-all duration-200 ${
|
| 52 |
+
item.name === 'About'
|
| 53 |
+
? 'bg-teal-50 text-teal-700'
|
| 54 |
+
: 'text-warmgray-600 hover:text-teal-700 hover:bg-teal-50'
|
| 55 |
+
}`}
|
| 56 |
+
>
|
| 57 |
+
{item.name}
|
| 58 |
+
</Link>
|
| 59 |
+
))}
|
| 60 |
+
</nav>
|
| 61 |
+
|
| 62 |
+
<div className="flex items-center space-x-3">
|
| 63 |
+
<Link href="/register" className="text-warmgray-700 hover:text-teal-700 font-medium text-sm">
|
| 64 |
+
Sign Up
|
| 65 |
+
</Link>
|
| 66 |
+
<Link href="/" className="bg-teal-600 text-white px-5 py-2.5 rounded-full hover:bg-teal-700 transition-all font-medium text-sm">
|
| 67 |
+
Login
|
| 68 |
+
</Link>
|
| 69 |
+
</div>
|
| 70 |
+
</div>
|
| 71 |
+
</div>
|
| 72 |
+
</header>
|
| 73 |
+
|
| 74 |
+
{/* Hero Section */}
|
| 75 |
+
<section className="py-20 px-4">
|
| 76 |
+
<div className="max-w-4xl mx-auto text-center">
|
| 77 |
+
<h1 className="text-4xl md:text-5xl font-bold text-warmgray-900 mb-6">
|
| 78 |
+
About <span className="text-teal-600">LearnFlow</span>
|
| 79 |
+
</h1>
|
| 80 |
+
<p className="text-xl text-warmgray-600 leading-relaxed">
|
| 81 |
+
We're on a mission to make Python programming accessible to everyone through
|
| 82 |
+
AI-powered personalized learning experiences.
|
| 83 |
+
</p>
|
| 84 |
+
</div>
|
| 85 |
+
</section>
|
| 86 |
+
|
| 87 |
+
{/* Our Story */}
|
| 88 |
+
<section className="py-16 px-4 bg-white">
|
| 89 |
+
<div className="max-w-6xl mx-auto">
|
| 90 |
+
<div className="grid md:grid-cols-2 gap-12 items-center">
|
| 91 |
+
<div>
|
| 92 |
+
<h2 className="text-3xl font-bold text-warmgray-900 mb-6">Our Story</h2>
|
| 93 |
+
<p className="text-warmgray-600 mb-4">
|
| 94 |
+
LearnFlow was born from a simple observation: traditional coding education doesn't work for everyone.
|
| 95 |
+
Students learn at different paces, have different backgrounds, and need different types of support.
|
| 96 |
+
</p>
|
| 97 |
+
<p className="text-warmgray-600 mb-4">
|
| 98 |
+
We built LearnFlow to solve this problem. Our AI tutors adapt to each student's learning style,
|
| 99 |
+
providing personalized explanations, debugging help, and practice exercises.
|
| 100 |
+
</p>
|
| 101 |
+
<p className="text-warmgray-600">
|
| 102 |
+
Today, we're proud to help over 100,000 students worldwide learn Python effectively,
|
| 103 |
+
with teacher dashboards that help educators identify and support struggling students.
|
| 104 |
+
</p>
|
| 105 |
+
</div>
|
| 106 |
+
<div className="bg-gradient-to-br from-teal-500 to-sage-500 rounded-3xl p-8 text-white">
|
| 107 |
+
<div className="grid grid-cols-2 gap-6">
|
| 108 |
+
<div className="text-center">
|
| 109 |
+
<div className="text-4xl font-bold">100K+</div>
|
| 110 |
+
<div className="text-teal-100">Students</div>
|
| 111 |
+
</div>
|
| 112 |
+
<div className="text-center">
|
| 113 |
+
<div className="text-4xl font-bold">50+</div>
|
| 114 |
+
<div className="text-teal-100">Countries</div>
|
| 115 |
+
</div>
|
| 116 |
+
<div className="text-center">
|
| 117 |
+
<div className="text-4xl font-bold">95%</div>
|
| 118 |
+
<div className="text-teal-100">Completion Rate</div>
|
| 119 |
+
</div>
|
| 120 |
+
<div className="text-center">
|
| 121 |
+
<div className="text-4xl font-bold">4.8</div>
|
| 122 |
+
<div className="text-teal-100">Avg Rating</div>
|
| 123 |
+
</div>
|
| 124 |
+
</div>
|
| 125 |
+
</div>
|
| 126 |
+
</div>
|
| 127 |
+
</div>
|
| 128 |
+
</section>
|
| 129 |
+
|
| 130 |
+
{/* Our Values */}
|
| 131 |
+
<section className="py-16 px-4">
|
| 132 |
+
<div className="max-w-6xl mx-auto">
|
| 133 |
+
<h2 className="text-3xl font-bold text-warmgray-900 text-center mb-12">Our Values</h2>
|
| 134 |
+
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
|
| 135 |
+
{values.map((value, index) => (
|
| 136 |
+
<div key={index} className="bg-white rounded-2xl p-6 shadow-soft hover:shadow-soft-lg transition-shadow">
|
| 137 |
+
<div className="text-4xl mb-4">{value.icon}</div>
|
| 138 |
+
<h3 className="font-semibold text-warmgray-900 mb-2">{value.title}</h3>
|
| 139 |
+
<p className="text-warmgray-600 text-sm">{value.description}</p>
|
| 140 |
+
</div>
|
| 141 |
+
))}
|
| 142 |
+
</div>
|
| 143 |
+
</div>
|
| 144 |
+
</section>
|
| 145 |
+
|
| 146 |
+
{/* Timeline */}
|
| 147 |
+
<section className="py-16 px-4 bg-white">
|
| 148 |
+
<div className="max-w-4xl mx-auto">
|
| 149 |
+
<h2 className="text-3xl font-bold text-warmgray-900 text-center mb-12">Our Journey</h2>
|
| 150 |
+
<div className="space-y-8">
|
| 151 |
+
{milestones.map((milestone, index) => (
|
| 152 |
+
<div key={index} className="flex gap-6">
|
| 153 |
+
<div className="flex-shrink-0 w-20 text-right">
|
| 154 |
+
<span className="text-teal-600 font-bold">{milestone.year}</span>
|
| 155 |
+
</div>
|
| 156 |
+
<div className="flex-shrink-0 w-4 h-4 mt-1 bg-teal-500 rounded-full relative">
|
| 157 |
+
{index < milestones.length - 1 && (
|
| 158 |
+
<div className="absolute top-4 left-1/2 w-0.5 h-16 bg-teal-200 -translate-x-1/2" />
|
| 159 |
+
)}
|
| 160 |
+
</div>
|
| 161 |
+
<div className="flex-1 pb-8">
|
| 162 |
+
<p className="text-warmgray-700">{milestone.event}</p>
|
| 163 |
+
</div>
|
| 164 |
+
</div>
|
| 165 |
+
))}
|
| 166 |
+
</div>
|
| 167 |
+
</div>
|
| 168 |
+
</section>
|
| 169 |
+
|
| 170 |
+
{/* Team */}
|
| 171 |
+
<section className="py-16 px-4">
|
| 172 |
+
<div className="max-w-6xl mx-auto">
|
| 173 |
+
<h2 className="text-3xl font-bold text-warmgray-900 text-center mb-12">Meet Our Team</h2>
|
| 174 |
+
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
|
| 175 |
+
{team.map((member, index) => (
|
| 176 |
+
<div key={index} className="bg-white rounded-2xl p-6 shadow-soft text-center hover:shadow-soft-lg transition-shadow">
|
| 177 |
+
<div className="text-6xl mb-4">{member.image}</div>
|
| 178 |
+
<h3 className="font-semibold text-warmgray-900">{member.name}</h3>
|
| 179 |
+
<p className="text-teal-600 text-sm mb-2">{member.role}</p>
|
| 180 |
+
<p className="text-warmgray-500 text-sm">{member.bio}</p>
|
| 181 |
+
</div>
|
| 182 |
+
))}
|
| 183 |
+
</div>
|
| 184 |
+
</div>
|
| 185 |
+
</section>
|
| 186 |
+
|
| 187 |
+
{/* CTA */}
|
| 188 |
+
<section className="py-16 px-4 bg-gradient-to-r from-teal-600 to-teal-700">
|
| 189 |
+
<div className="max-w-4xl mx-auto text-center text-white">
|
| 190 |
+
<h2 className="text-3xl font-bold mb-4">Join Our Learning Community</h2>
|
| 191 |
+
<p className="text-teal-100 mb-8">Start your Python journey today with AI-powered personalized learning.</p>
|
| 192 |
+
<Link
|
| 193 |
+
href="/register"
|
| 194 |
+
className="inline-block bg-white text-teal-700 px-8 py-3 rounded-full font-semibold hover:bg-cream-50 transition-colors shadow-lg"
|
| 195 |
+
>
|
| 196 |
+
Get Started Free
|
| 197 |
+
</Link>
|
| 198 |
+
</div>
|
| 199 |
+
</section>
|
| 200 |
+
</div>
|
| 201 |
+
);
|
| 202 |
+
}
|
app/contact/page.tsx
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import Link from 'next/link';
|
| 4 |
+
import { useState } from 'react';
|
| 5 |
+
|
| 6 |
+
export default function ContactPage() {
|
| 7 |
+
const [formData, setFormData] = useState({
|
| 8 |
+
name: '',
|
| 9 |
+
email: '',
|
| 10 |
+
subject: '',
|
| 11 |
+
message: '',
|
| 12 |
+
});
|
| 13 |
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
| 14 |
+
const [submitted, setSubmitted] = useState(false);
|
| 15 |
+
|
| 16 |
+
const handleSubmit = async (e: React.FormEvent) => {
|
| 17 |
+
e.preventDefault();
|
| 18 |
+
setIsSubmitting(true);
|
| 19 |
+
|
| 20 |
+
// Simulate form submission
|
| 21 |
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
| 22 |
+
|
| 23 |
+
setIsSubmitting(false);
|
| 24 |
+
setSubmitted(true);
|
| 25 |
+
setFormData({ name: '', email: '', subject: '', message: '' });
|
| 26 |
+
};
|
| 27 |
+
|
| 28 |
+
const contactInfo = [
|
| 29 |
+
{ icon: '📧', label: 'Email', value: 'support@learnflow.com', href: 'mailto:support@learnflow.com' },
|
| 30 |
+
{ icon: '📞', label: 'Phone', value: '+1 (555) 123-4567', href: 'tel:+15551234567' },
|
| 31 |
+
{ icon: '📍', label: 'Address', value: '123 Learning Street, San Francisco, CA 94102', href: '#' },
|
| 32 |
+
];
|
| 33 |
+
|
| 34 |
+
const faqs = [
|
| 35 |
+
{ q: 'How do I reset my password?', a: 'Click "Forgot Password" on the login page and follow the instructions.' },
|
| 36 |
+
{ q: 'Can I get a refund?', a: 'Yes, we offer a 30-day money-back guarantee on all courses.' },
|
| 37 |
+
{ q: 'How do I contact my instructor?', a: 'Use the chat feature in your course dashboard to message your instructor.' },
|
| 38 |
+
{ q: 'Is there a mobile app?', a: 'Our mobile app is coming soon! Currently, our website works great on mobile browsers.' },
|
| 39 |
+
];
|
| 40 |
+
|
| 41 |
+
return (
|
| 42 |
+
<div className="min-h-screen bg-gradient-to-b from-cream-50 to-white">
|
| 43 |
+
{/* Header */}
|
| 44 |
+
<header className="bg-white/80 backdrop-blur-md border-b border-warmgray-100 sticky top-0 z-50">
|
| 45 |
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 46 |
+
<div className="flex items-center justify-between h-16">
|
| 47 |
+
<Link href="/" className="flex items-center space-x-3">
|
| 48 |
+
<div className="w-10 h-10 bg-gradient-to-br from-teal-500 to-teal-600 rounded-xl flex items-center justify-center shadow-soft">
|
| 49 |
+
<span className="text-white font-bold text-lg">L</span>
|
| 50 |
+
</div>
|
| 51 |
+
<span className="text-xl font-bold text-warmgray-900">LearnFlow</span>
|
| 52 |
+
</Link>
|
| 53 |
+
|
| 54 |
+
<nav className="hidden md:flex items-center space-x-1">
|
| 55 |
+
{[
|
| 56 |
+
{ name: 'Home', href: '/' },
|
| 57 |
+
{ name: 'Courses', href: '/courses' },
|
| 58 |
+
{ name: 'About', href: '/about' },
|
| 59 |
+
{ name: 'Resources', href: '/resources' },
|
| 60 |
+
{ name: 'Contact', href: '/contact' },
|
| 61 |
+
].map((item) => (
|
| 62 |
+
<Link
|
| 63 |
+
key={item.name}
|
| 64 |
+
href={item.href}
|
| 65 |
+
className={`px-4 py-2 rounded-full text-sm font-medium transition-all duration-200 ${
|
| 66 |
+
item.name === 'Contact'
|
| 67 |
+
? 'bg-teal-50 text-teal-700'
|
| 68 |
+
: 'text-warmgray-600 hover:text-teal-700 hover:bg-teal-50'
|
| 69 |
+
}`}
|
| 70 |
+
>
|
| 71 |
+
{item.name}
|
| 72 |
+
</Link>
|
| 73 |
+
))}
|
| 74 |
+
</nav>
|
| 75 |
+
|
| 76 |
+
<div className="flex items-center space-x-3">
|
| 77 |
+
<Link href="/register" className="text-warmgray-700 hover:text-teal-700 font-medium text-sm">
|
| 78 |
+
Sign Up
|
| 79 |
+
</Link>
|
| 80 |
+
<Link href="/" className="bg-teal-600 text-white px-5 py-2.5 rounded-full hover:bg-teal-700 transition-all font-medium text-sm">
|
| 81 |
+
Login
|
| 82 |
+
</Link>
|
| 83 |
+
</div>
|
| 84 |
+
</div>
|
| 85 |
+
</div>
|
| 86 |
+
</header>
|
| 87 |
+
|
| 88 |
+
{/* Hero Section */}
|
| 89 |
+
<section className="py-16 px-4">
|
| 90 |
+
<div className="max-w-4xl mx-auto text-center">
|
| 91 |
+
<h1 className="text-4xl md:text-5xl font-bold text-warmgray-900 mb-4">
|
| 92 |
+
Get in <span className="text-teal-600">Touch</span>
|
| 93 |
+
</h1>
|
| 94 |
+
<p className="text-lg text-warmgray-600">
|
| 95 |
+
Have questions? We'd love to hear from you. Send us a message and we'll respond as soon as possible.
|
| 96 |
+
</p>
|
| 97 |
+
</div>
|
| 98 |
+
</section>
|
| 99 |
+
|
| 100 |
+
{/* Contact Info Cards */}
|
| 101 |
+
<section className="px-4 pb-12">
|
| 102 |
+
<div className="max-w-4xl mx-auto">
|
| 103 |
+
<div className="grid md:grid-cols-3 gap-6">
|
| 104 |
+
{contactInfo.map((info, index) => (
|
| 105 |
+
<a
|
| 106 |
+
key={index}
|
| 107 |
+
href={info.href}
|
| 108 |
+
className="bg-white rounded-2xl p-6 shadow-soft hover:shadow-soft-lg transition-all text-center group"
|
| 109 |
+
>
|
| 110 |
+
<div className="text-4xl mb-3">{info.icon}</div>
|
| 111 |
+
<div className="text-sm text-warmgray-500 mb-1">{info.label}</div>
|
| 112 |
+
<div className="font-medium text-warmgray-900 group-hover:text-teal-600 transition-colors">
|
| 113 |
+
{info.value}
|
| 114 |
+
</div>
|
| 115 |
+
</a>
|
| 116 |
+
))}
|
| 117 |
+
</div>
|
| 118 |
+
</div>
|
| 119 |
+
</section>
|
| 120 |
+
|
| 121 |
+
{/* Contact Form & FAQs */}
|
| 122 |
+
<section className="px-4 pb-20">
|
| 123 |
+
<div className="max-w-6xl mx-auto">
|
| 124 |
+
<div className="grid lg:grid-cols-2 gap-12">
|
| 125 |
+
{/* Contact Form */}
|
| 126 |
+
<div className="bg-white rounded-3xl p-8 shadow-soft">
|
| 127 |
+
<h2 className="text-2xl font-bold text-warmgray-900 mb-6">Send us a Message</h2>
|
| 128 |
+
|
| 129 |
+
{submitted ? (
|
| 130 |
+
<div className="text-center py-12">
|
| 131 |
+
<div className="text-6xl mb-4">✅</div>
|
| 132 |
+
<h3 className="text-xl font-semibold text-warmgray-900 mb-2">Message Sent!</h3>
|
| 133 |
+
<p className="text-warmgray-600 mb-6">We'll get back to you within 24 hours.</p>
|
| 134 |
+
<button
|
| 135 |
+
onClick={() => setSubmitted(false)}
|
| 136 |
+
className="text-teal-600 font-medium hover:text-teal-700"
|
| 137 |
+
>
|
| 138 |
+
Send another message
|
| 139 |
+
</button>
|
| 140 |
+
</div>
|
| 141 |
+
) : (
|
| 142 |
+
<form onSubmit={handleSubmit} className="space-y-5">
|
| 143 |
+
<div>
|
| 144 |
+
<label className="block text-sm font-medium text-warmgray-700 mb-2">Your Name</label>
|
| 145 |
+
<input
|
| 146 |
+
type="text"
|
| 147 |
+
required
|
| 148 |
+
value={formData.name}
|
| 149 |
+
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
| 150 |
+
className="w-full px-4 py-3 rounded-xl border border-warmgray-200 focus:border-teal-500 focus:ring-2 focus:ring-teal-500/20 outline-none transition-all"
|
| 151 |
+
placeholder="John Doe"
|
| 152 |
+
/>
|
| 153 |
+
</div>
|
| 154 |
+
<div>
|
| 155 |
+
<label className="block text-sm font-medium text-warmgray-700 mb-2">Email Address</label>
|
| 156 |
+
<input
|
| 157 |
+
type="email"
|
| 158 |
+
required
|
| 159 |
+
value={formData.email}
|
| 160 |
+
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
|
| 161 |
+
className="w-full px-4 py-3 rounded-xl border border-warmgray-200 focus:border-teal-500 focus:ring-2 focus:ring-teal-500/20 outline-none transition-all"
|
| 162 |
+
placeholder="john@example.com"
|
| 163 |
+
/>
|
| 164 |
+
</div>
|
| 165 |
+
<div>
|
| 166 |
+
<label className="block text-sm font-medium text-warmgray-700 mb-2">Subject</label>
|
| 167 |
+
<select
|
| 168 |
+
required
|
| 169 |
+
value={formData.subject}
|
| 170 |
+
onChange={(e) => setFormData({ ...formData, subject: e.target.value })}
|
| 171 |
+
className="w-full px-4 py-3 rounded-xl border border-warmgray-200 focus:border-teal-500 focus:ring-2 focus:ring-teal-500/20 outline-none transition-all"
|
| 172 |
+
>
|
| 173 |
+
<option value="">Select a subject</option>
|
| 174 |
+
<option value="general">General Inquiry</option>
|
| 175 |
+
<option value="support">Technical Support</option>
|
| 176 |
+
<option value="billing">Billing Question</option>
|
| 177 |
+
<option value="partnership">Partnership Opportunity</option>
|
| 178 |
+
<option value="feedback">Feedback</option>
|
| 179 |
+
</select>
|
| 180 |
+
</div>
|
| 181 |
+
<div>
|
| 182 |
+
<label className="block text-sm font-medium text-warmgray-700 mb-2">Message</label>
|
| 183 |
+
<textarea
|
| 184 |
+
required
|
| 185 |
+
rows={5}
|
| 186 |
+
value={formData.message}
|
| 187 |
+
onChange={(e) => setFormData({ ...formData, message: e.target.value })}
|
| 188 |
+
className="w-full px-4 py-3 rounded-xl border border-warmgray-200 focus:border-teal-500 focus:ring-2 focus:ring-teal-500/20 outline-none transition-all resize-none"
|
| 189 |
+
placeholder="How can we help you?"
|
| 190 |
+
/>
|
| 191 |
+
</div>
|
| 192 |
+
<button
|
| 193 |
+
type="submit"
|
| 194 |
+
disabled={isSubmitting}
|
| 195 |
+
className="w-full bg-teal-600 text-white py-3 rounded-xl font-semibold hover:bg-teal-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
| 196 |
+
>
|
| 197 |
+
{isSubmitting ? (
|
| 198 |
+
<>
|
| 199 |
+
<svg className="animate-spin h-5 w-5" viewBox="0 0 24 24">
|
| 200 |
+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" />
|
| 201 |
+
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
|
| 202 |
+
</svg>
|
| 203 |
+
Sending...
|
| 204 |
+
</>
|
| 205 |
+
) : (
|
| 206 |
+
'Send Message'
|
| 207 |
+
)}
|
| 208 |
+
</button>
|
| 209 |
+
</form>
|
| 210 |
+
)}
|
| 211 |
+
</div>
|
| 212 |
+
|
| 213 |
+
{/* FAQs */}
|
| 214 |
+
<div>
|
| 215 |
+
<h2 className="text-2xl font-bold text-warmgray-900 mb-6">Frequently Asked Questions</h2>
|
| 216 |
+
<div className="space-y-4">
|
| 217 |
+
{faqs.map((faq, index) => (
|
| 218 |
+
<div key={index} className="bg-white rounded-2xl p-6 shadow-soft">
|
| 219 |
+
<h3 className="font-semibold text-warmgray-900 mb-2">{faq.q}</h3>
|
| 220 |
+
<p className="text-warmgray-600 text-sm">{faq.a}</p>
|
| 221 |
+
</div>
|
| 222 |
+
))}
|
| 223 |
+
</div>
|
| 224 |
+
|
| 225 |
+
{/* Social Links */}
|
| 226 |
+
<div className="mt-8 bg-gradient-to-br from-teal-500 to-sage-500 rounded-2xl p-6 text-white">
|
| 227 |
+
<h3 className="font-semibold mb-4">Follow Us</h3>
|
| 228 |
+
<div className="flex gap-4">
|
| 229 |
+
{['Twitter', 'LinkedIn', 'YouTube', 'Discord'].map((social) => (
|
| 230 |
+
<a
|
| 231 |
+
key={social}
|
| 232 |
+
href="#"
|
| 233 |
+
className="bg-white/20 hover:bg-white/30 transition-colors px-4 py-2 rounded-lg text-sm font-medium"
|
| 234 |
+
>
|
| 235 |
+
{social}
|
| 236 |
+
</a>
|
| 237 |
+
))}
|
| 238 |
+
</div>
|
| 239 |
+
</div>
|
| 240 |
+
</div>
|
| 241 |
+
</div>
|
| 242 |
+
</div>
|
| 243 |
+
</section>
|
| 244 |
+
</div>
|
| 245 |
+
);
|
| 246 |
+
}
|
app/courses/page.tsx
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import Link from 'next/link';
|
| 4 |
+
import { useState } from 'react';
|
| 5 |
+
|
| 6 |
+
export default function CoursesPage() {
|
| 7 |
+
const [selectedCategory, setSelectedCategory] = useState('all');
|
| 8 |
+
|
| 9 |
+
const categories = [
|
| 10 |
+
{ id: 'all', name: 'All Courses' },
|
| 11 |
+
{ id: 'beginner', name: 'Beginner' },
|
| 12 |
+
{ id: 'intermediate', name: 'Intermediate' },
|
| 13 |
+
{ id: 'advanced', name: 'Advanced' },
|
| 14 |
+
];
|
| 15 |
+
|
| 16 |
+
const courses = [
|
| 17 |
+
{
|
| 18 |
+
id: 1,
|
| 19 |
+
title: 'Python Fundamentals',
|
| 20 |
+
description: 'Learn Python from scratch. Variables, data types, operators, and basic syntax.',
|
| 21 |
+
level: 'beginner',
|
| 22 |
+
duration: '4 weeks',
|
| 23 |
+
lessons: 24,
|
| 24 |
+
students: 1250,
|
| 25 |
+
rating: 4.8,
|
| 26 |
+
image: '🐍',
|
| 27 |
+
color: 'bg-teal-500',
|
| 28 |
+
},
|
| 29 |
+
{
|
| 30 |
+
id: 2,
|
| 31 |
+
title: 'Control Flow & Loops',
|
| 32 |
+
description: 'Master if statements, for loops, while loops, and conditional logic.',
|
| 33 |
+
level: 'beginner',
|
| 34 |
+
duration: '3 weeks',
|
| 35 |
+
lessons: 18,
|
| 36 |
+
students: 980,
|
| 37 |
+
rating: 4.7,
|
| 38 |
+
image: '🔄',
|
| 39 |
+
color: 'bg-sage-500',
|
| 40 |
+
},
|
| 41 |
+
{
|
| 42 |
+
id: 3,
|
| 43 |
+
title: 'Functions & Modules',
|
| 44 |
+
description: 'Create reusable code with functions, parameters, return values, and modules.',
|
| 45 |
+
level: 'intermediate',
|
| 46 |
+
duration: '4 weeks',
|
| 47 |
+
lessons: 22,
|
| 48 |
+
students: 756,
|
| 49 |
+
rating: 4.9,
|
| 50 |
+
image: '⚡',
|
| 51 |
+
color: 'bg-lavender-500',
|
| 52 |
+
},
|
| 53 |
+
{
|
| 54 |
+
id: 4,
|
| 55 |
+
title: 'Object-Oriented Programming',
|
| 56 |
+
description: 'Classes, objects, inheritance, polymorphism, and encapsulation in Python.',
|
| 57 |
+
level: 'intermediate',
|
| 58 |
+
duration: '5 weeks',
|
| 59 |
+
lessons: 30,
|
| 60 |
+
students: 645,
|
| 61 |
+
rating: 4.8,
|
| 62 |
+
image: '🏗️',
|
| 63 |
+
color: 'bg-coral-500',
|
| 64 |
+
},
|
| 65 |
+
{
|
| 66 |
+
id: 5,
|
| 67 |
+
title: 'Data Structures',
|
| 68 |
+
description: 'Lists, dictionaries, sets, tuples, and when to use each data structure.',
|
| 69 |
+
level: 'intermediate',
|
| 70 |
+
duration: '4 weeks',
|
| 71 |
+
lessons: 26,
|
| 72 |
+
students: 823,
|
| 73 |
+
rating: 4.7,
|
| 74 |
+
image: '📊',
|
| 75 |
+
color: 'bg-teal-600',
|
| 76 |
+
},
|
| 77 |
+
{
|
| 78 |
+
id: 6,
|
| 79 |
+
title: 'File Handling & APIs',
|
| 80 |
+
description: 'Read/write files, work with JSON, and consume REST APIs with Python.',
|
| 81 |
+
level: 'advanced',
|
| 82 |
+
duration: '3 weeks',
|
| 83 |
+
lessons: 20,
|
| 84 |
+
students: 432,
|
| 85 |
+
rating: 4.9,
|
| 86 |
+
image: '📁',
|
| 87 |
+
color: 'bg-sage-600',
|
| 88 |
+
},
|
| 89 |
+
{
|
| 90 |
+
id: 7,
|
| 91 |
+
title: 'Error Handling & Debugging',
|
| 92 |
+
description: 'Try-except blocks, custom exceptions, and debugging techniques.',
|
| 93 |
+
level: 'intermediate',
|
| 94 |
+
duration: '2 weeks',
|
| 95 |
+
lessons: 14,
|
| 96 |
+
students: 567,
|
| 97 |
+
rating: 4.6,
|
| 98 |
+
image: '🐛',
|
| 99 |
+
color: 'bg-lavender-600',
|
| 100 |
+
},
|
| 101 |
+
{
|
| 102 |
+
id: 8,
|
| 103 |
+
title: 'Python Projects',
|
| 104 |
+
description: 'Build real-world projects: CLI tools, web scrapers, and automation scripts.',
|
| 105 |
+
level: 'advanced',
|
| 106 |
+
duration: '6 weeks',
|
| 107 |
+
lessons: 35,
|
| 108 |
+
students: 389,
|
| 109 |
+
rating: 4.9,
|
| 110 |
+
image: '🚀',
|
| 111 |
+
color: 'bg-coral-600',
|
| 112 |
+
},
|
| 113 |
+
];
|
| 114 |
+
|
| 115 |
+
const filteredCourses = selectedCategory === 'all'
|
| 116 |
+
? courses
|
| 117 |
+
: courses.filter(course => course.level === selectedCategory);
|
| 118 |
+
|
| 119 |
+
return (
|
| 120 |
+
<div className="min-h-screen bg-gradient-to-b from-cream-50 to-white">
|
| 121 |
+
{/* Header */}
|
| 122 |
+
<header className="bg-white/80 backdrop-blur-md border-b border-warmgray-100 sticky top-0 z-50">
|
| 123 |
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 124 |
+
<div className="flex items-center justify-between h-16">
|
| 125 |
+
<Link href="/" className="flex items-center space-x-3">
|
| 126 |
+
<div className="w-10 h-10 bg-gradient-to-br from-teal-500 to-teal-600 rounded-xl flex items-center justify-center shadow-soft">
|
| 127 |
+
<span className="text-white font-bold text-lg">L</span>
|
| 128 |
+
</div>
|
| 129 |
+
<span className="text-xl font-bold text-warmgray-900">LearnFlow</span>
|
| 130 |
+
</Link>
|
| 131 |
+
|
| 132 |
+
<nav className="hidden md:flex items-center space-x-1">
|
| 133 |
+
{[
|
| 134 |
+
{ name: 'Home', href: '/' },
|
| 135 |
+
{ name: 'Courses', href: '/courses' },
|
| 136 |
+
{ name: 'About', href: '/about' },
|
| 137 |
+
{ name: 'Resources', href: '/resources' },
|
| 138 |
+
{ name: 'Contact', href: '/contact' },
|
| 139 |
+
].map((item) => (
|
| 140 |
+
<Link
|
| 141 |
+
key={item.name}
|
| 142 |
+
href={item.href}
|
| 143 |
+
className={`px-4 py-2 rounded-full text-sm font-medium transition-all duration-200 ${
|
| 144 |
+
item.name === 'Courses'
|
| 145 |
+
? 'bg-teal-50 text-teal-700'
|
| 146 |
+
: 'text-warmgray-600 hover:text-teal-700 hover:bg-teal-50'
|
| 147 |
+
}`}
|
| 148 |
+
>
|
| 149 |
+
{item.name}
|
| 150 |
+
</Link>
|
| 151 |
+
))}
|
| 152 |
+
</nav>
|
| 153 |
+
|
| 154 |
+
<div className="flex items-center space-x-3">
|
| 155 |
+
<Link href="/register" className="text-warmgray-700 hover:text-teal-700 font-medium text-sm">
|
| 156 |
+
Sign Up
|
| 157 |
+
</Link>
|
| 158 |
+
<Link href="/" className="bg-teal-600 text-white px-5 py-2.5 rounded-full hover:bg-teal-700 transition-all font-medium text-sm">
|
| 159 |
+
Login
|
| 160 |
+
</Link>
|
| 161 |
+
</div>
|
| 162 |
+
</div>
|
| 163 |
+
</div>
|
| 164 |
+
</header>
|
| 165 |
+
|
| 166 |
+
{/* Hero Section */}
|
| 167 |
+
<section className="py-16 px-4">
|
| 168 |
+
<div className="max-w-7xl mx-auto text-center">
|
| 169 |
+
<h1 className="text-4xl md:text-5xl font-bold text-warmgray-900 mb-4">
|
| 170 |
+
Explore Our <span className="text-teal-600">Python Courses</span>
|
| 171 |
+
</h1>
|
| 172 |
+
<p className="text-lg text-warmgray-600 max-w-2xl mx-auto">
|
| 173 |
+
From beginner to advanced, find the perfect course to master Python programming with AI-powered guidance.
|
| 174 |
+
</p>
|
| 175 |
+
</div>
|
| 176 |
+
</section>
|
| 177 |
+
|
| 178 |
+
{/* Category Filter */}
|
| 179 |
+
<section className="px-4 pb-8">
|
| 180 |
+
<div className="max-w-7xl mx-auto">
|
| 181 |
+
<div className="flex flex-wrap justify-center gap-3">
|
| 182 |
+
{categories.map((category) => (
|
| 183 |
+
<button
|
| 184 |
+
key={category.id}
|
| 185 |
+
onClick={() => setSelectedCategory(category.id)}
|
| 186 |
+
className={`px-6 py-2.5 rounded-full font-medium transition-all duration-200 ${
|
| 187 |
+
selectedCategory === category.id
|
| 188 |
+
? 'bg-teal-600 text-white shadow-soft'
|
| 189 |
+
: 'bg-white text-warmgray-600 hover:bg-teal-50 hover:text-teal-700 border border-warmgray-200'
|
| 190 |
+
}`}
|
| 191 |
+
>
|
| 192 |
+
{category.name}
|
| 193 |
+
</button>
|
| 194 |
+
))}
|
| 195 |
+
</div>
|
| 196 |
+
</div>
|
| 197 |
+
</section>
|
| 198 |
+
|
| 199 |
+
{/* Courses Grid */}
|
| 200 |
+
<section className="px-4 pb-20">
|
| 201 |
+
<div className="max-w-7xl mx-auto">
|
| 202 |
+
<div className="grid md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
| 203 |
+
{filteredCourses.map((course) => (
|
| 204 |
+
<div
|
| 205 |
+
key={course.id}
|
| 206 |
+
className="bg-white rounded-2xl shadow-soft hover:shadow-soft-lg transition-all duration-300 overflow-hidden group"
|
| 207 |
+
>
|
| 208 |
+
<div className={`${course.color} h-32 flex items-center justify-center`}>
|
| 209 |
+
<span className="text-5xl">{course.image}</span>
|
| 210 |
+
</div>
|
| 211 |
+
<div className="p-5">
|
| 212 |
+
<div className="flex items-center gap-2 mb-2">
|
| 213 |
+
<span className={`px-2.5 py-1 rounded-full text-xs font-medium ${
|
| 214 |
+
course.level === 'beginner' ? 'bg-green-100 text-green-700' :
|
| 215 |
+
course.level === 'intermediate' ? 'bg-yellow-100 text-yellow-700' :
|
| 216 |
+
'bg-red-100 text-red-700'
|
| 217 |
+
}`}>
|
| 218 |
+
{course.level.charAt(0).toUpperCase() + course.level.slice(1)}
|
| 219 |
+
</span>
|
| 220 |
+
<span className="text-warmgray-400 text-xs">{course.duration}</span>
|
| 221 |
+
</div>
|
| 222 |
+
<h3 className="font-semibold text-warmgray-900 mb-2 group-hover:text-teal-600 transition-colors">
|
| 223 |
+
{course.title}
|
| 224 |
+
</h3>
|
| 225 |
+
<p className="text-sm text-warmgray-600 mb-4 line-clamp-2">
|
| 226 |
+
{course.description}
|
| 227 |
+
</p>
|
| 228 |
+
<div className="flex items-center justify-between text-sm">
|
| 229 |
+
<div className="flex items-center gap-3 text-warmgray-500">
|
| 230 |
+
<span>{course.lessons} lessons</span>
|
| 231 |
+
<span>{course.students} students</span>
|
| 232 |
+
</div>
|
| 233 |
+
<div className="flex items-center text-yellow-500">
|
| 234 |
+
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
| 235 |
+
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
|
| 236 |
+
</svg>
|
| 237 |
+
<span className="ml-1 text-warmgray-700">{course.rating}</span>
|
| 238 |
+
</div>
|
| 239 |
+
</div>
|
| 240 |
+
<Link
|
| 241 |
+
href="/register"
|
| 242 |
+
className="mt-4 block w-full text-center bg-teal-50 text-teal-700 py-2.5 rounded-xl font-medium hover:bg-teal-100 transition-colors"
|
| 243 |
+
>
|
| 244 |
+
Enroll Now
|
| 245 |
+
</Link>
|
| 246 |
+
</div>
|
| 247 |
+
</div>
|
| 248 |
+
))}
|
| 249 |
+
</div>
|
| 250 |
+
</div>
|
| 251 |
+
</section>
|
| 252 |
+
|
| 253 |
+
{/* CTA Section */}
|
| 254 |
+
<section className="py-16 px-4 bg-gradient-to-r from-teal-600 to-teal-700">
|
| 255 |
+
<div className="max-w-4xl mx-auto text-center text-white">
|
| 256 |
+
<h2 className="text-3xl font-bold mb-4">Ready to Start Learning?</h2>
|
| 257 |
+
<p className="text-teal-100 mb-8">Join thousands of students mastering Python with AI-powered tutoring.</p>
|
| 258 |
+
<Link
|
| 259 |
+
href="/register"
|
| 260 |
+
className="inline-block bg-white text-teal-700 px-8 py-3 rounded-full font-semibold hover:bg-cream-50 transition-colors shadow-lg"
|
| 261 |
+
>
|
| 262 |
+
Get Started Free
|
| 263 |
+
</Link>
|
| 264 |
+
</div>
|
| 265 |
+
</section>
|
| 266 |
+
</div>
|
| 267 |
+
);
|
| 268 |
+
}
|
app/globals.css
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@tailwind base;
|
| 2 |
+
@tailwind components;
|
| 3 |
+
@tailwind utilities;
|
| 4 |
+
|
| 5 |
+
/* VeryWellMind-inspired Wellness Theme */
|
| 6 |
+
:root {
|
| 7 |
+
/* Primary Colors */
|
| 8 |
+
--primary-teal: #0d9488;
|
| 9 |
+
--primary-teal-light: #14b8a6;
|
| 10 |
+
--primary-teal-dark: #0f766e;
|
| 11 |
+
|
| 12 |
+
/* Accent Colors */
|
| 13 |
+
--accent-lavender: #a855f7;
|
| 14 |
+
--accent-coral: #ff6b6b;
|
| 15 |
+
--accent-sage: #7a8b63;
|
| 16 |
+
|
| 17 |
+
/* Background Colors */
|
| 18 |
+
--bg-cream: #fdf9f3;
|
| 19 |
+
--bg-soft: #fafaf9;
|
| 20 |
+
--bg-white: #ffffff;
|
| 21 |
+
|
| 22 |
+
/* Text Colors */
|
| 23 |
+
--text-primary: #1c1917;
|
| 24 |
+
--text-secondary: #57534e;
|
| 25 |
+
--text-muted: #78716c;
|
| 26 |
+
|
| 27 |
+
/* Foreground/Background */
|
| 28 |
+
--foreground-rgb: 28, 25, 23;
|
| 29 |
+
--background-rgb: 253, 249, 243;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
@media (prefers-color-scheme: dark) {
|
| 33 |
+
:root {
|
| 34 |
+
--foreground-rgb: 250, 250, 249;
|
| 35 |
+
--background-rgb: 28, 25, 23;
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
* {
|
| 40 |
+
box-sizing: border-box;
|
| 41 |
+
padding: 0;
|
| 42 |
+
margin: 0;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
html {
|
| 46 |
+
scroll-behavior: smooth;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
body {
|
| 50 |
+
color: rgb(var(--foreground-rgb));
|
| 51 |
+
background: rgb(var(--background-rgb));
|
| 52 |
+
font-feature-settings: 'rlig' 1, 'calt' 1;
|
| 53 |
+
-webkit-font-smoothing: antialiased;
|
| 54 |
+
-moz-osx-font-smoothing: grayscale;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
/* Wellness Typography */
|
| 58 |
+
@layer base {
|
| 59 |
+
h1, h2, h3, h4, h5, h6 {
|
| 60 |
+
@apply font-display tracking-tight;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
h1 {
|
| 64 |
+
@apply text-4xl md:text-5xl lg:text-6xl font-bold leading-tight;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
h2 {
|
| 68 |
+
@apply text-3xl md:text-4xl font-bold;
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
h3 {
|
| 72 |
+
@apply text-xl md:text-2xl font-semibold;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
p {
|
| 76 |
+
@apply leading-relaxed;
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
/* Custom Components */
|
| 81 |
+
@layer components {
|
| 82 |
+
/* Wellness Card Style */
|
| 83 |
+
.wellness-card {
|
| 84 |
+
@apply bg-white rounded-2xl p-6 shadow-soft transition-all duration-300;
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
.wellness-card:hover {
|
| 88 |
+
@apply shadow-soft-lg -translate-y-1;
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
/* Soft Button Styles */
|
| 92 |
+
.btn-primary {
|
| 93 |
+
@apply bg-teal-600 text-white px-6 py-3 rounded-full font-medium
|
| 94 |
+
hover:bg-teal-700 transition-all duration-300
|
| 95 |
+
shadow-sm hover:shadow-md;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
.btn-secondary {
|
| 99 |
+
@apply bg-white text-teal-700 px-6 py-3 rounded-full font-medium
|
| 100 |
+
border-2 border-teal-200 hover:border-teal-300
|
| 101 |
+
hover:bg-teal-50 transition-all duration-300;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
.btn-soft {
|
| 105 |
+
@apply bg-cream-100 text-warmgray-700 px-6 py-3 rounded-full font-medium
|
| 106 |
+
hover:bg-cream-200 transition-all duration-300;
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
/* Category Pill */
|
| 110 |
+
.category-pill {
|
| 111 |
+
@apply inline-flex items-center px-4 py-2 rounded-full text-sm font-medium
|
| 112 |
+
transition-all duration-200;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
.category-pill-teal {
|
| 116 |
+
@apply bg-teal-100 text-teal-800 hover:bg-teal-200;
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
.category-pill-lavender {
|
| 120 |
+
@apply bg-lavender-100 text-lavender-800 hover:bg-lavender-200;
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
.category-pill-coral {
|
| 124 |
+
@apply bg-coral-100 text-coral-800 hover:bg-coral-200;
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
.category-pill-sage {
|
| 128 |
+
@apply bg-sage-100 text-sage-800 hover:bg-sage-200;
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
/* Input Styles */
|
| 132 |
+
.input-wellness {
|
| 133 |
+
@apply w-full px-4 py-3 rounded-xl border-2 border-warmgray-200
|
| 134 |
+
bg-white focus:outline-none focus:border-teal-400
|
| 135 |
+
focus:ring-4 focus:ring-teal-100 transition-all duration-200
|
| 136 |
+
placeholder:text-warmgray-400;
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
/* Section Styles */
|
| 140 |
+
.section-wellness {
|
| 141 |
+
@apply py-16 md:py-24;
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
/* Gradient Backgrounds */
|
| 145 |
+
.bg-gradient-wellness {
|
| 146 |
+
background: linear-gradient(135deg, #f0fdfa 0%, #faf5ff 50%, #fdf9f3 100%);
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
.bg-gradient-hero {
|
| 150 |
+
background:
|
| 151 |
+
radial-gradient(circle at 30% 20%, rgba(20, 184, 166, 0.08) 0%, transparent 50%),
|
| 152 |
+
radial-gradient(circle at 70% 80%, rgba(168, 85, 247, 0.06) 0%, transparent 50%),
|
| 153 |
+
linear-gradient(180deg, #fdf9f3 0%, #ffffff 100%);
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
/* Decorative Elements */
|
| 157 |
+
.blob-teal {
|
| 158 |
+
@apply absolute rounded-full bg-teal-200/30 blur-3xl;
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
.blob-lavender {
|
| 162 |
+
@apply absolute rounded-full bg-lavender-200/30 blur-3xl;
|
| 163 |
+
}
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
@layer utilities {
|
| 167 |
+
.text-balance {
|
| 168 |
+
text-wrap: balance;
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
.animate-float {
|
| 172 |
+
animation: float 6s ease-in-out infinite;
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
@keyframes float {
|
| 176 |
+
0%, 100% { transform: translateY(0px); }
|
| 177 |
+
50% { transform: translateY(-10px); }
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
.animate-pulse-soft {
|
| 181 |
+
animation: pulse-soft 4s ease-in-out infinite;
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
@keyframes pulse-soft {
|
| 185 |
+
0%, 100% { opacity: 1; }
|
| 186 |
+
50% { opacity: 0.8; }
|
| 187 |
+
}
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
/* Custom Scrollbar */
|
| 191 |
+
::-webkit-scrollbar {
|
| 192 |
+
width: 8px;
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
::-webkit-scrollbar-track {
|
| 196 |
+
background: #f5f5f4;
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
::-webkit-scrollbar-thumb {
|
| 200 |
+
background: #d6d3d1;
|
| 201 |
+
border-radius: 4px;
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
::-webkit-scrollbar-thumb:hover {
|
| 205 |
+
background: #a8a29e;
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
/* Selection Color */
|
| 209 |
+
::selection {
|
| 210 |
+
background-color: rgba(20, 184, 166, 0.2);
|
| 211 |
+
color: #0f766e;
|
| 212 |
+
}
|
app/layout.tsx
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import './globals.css';
|
| 2 |
+
import type { Metadata } from 'next';
|
| 3 |
+
|
| 4 |
+
export const metadata: Metadata = {
|
| 5 |
+
title: 'LearnFlow - AI-Powered Python Tutoring',
|
| 6 |
+
description: 'Interactive Python learning platform with AI tutors',
|
| 7 |
+
};
|
| 8 |
+
|
| 9 |
+
export default function RootLayout({
|
| 10 |
+
children,
|
| 11 |
+
}: {
|
| 12 |
+
children: React.ReactNode;
|
| 13 |
+
}) {
|
| 14 |
+
return (
|
| 15 |
+
<html lang="en">
|
| 16 |
+
<body className="font-sans antialiased">{children}</body>
|
| 17 |
+
</html>
|
| 18 |
+
);
|
| 19 |
+
}
|
app/page.tsx
ADDED
|
@@ -0,0 +1,814 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import { useState } from 'react';
|
| 4 |
+
import Link from 'next/link';
|
| 5 |
+
import { useRouter } from 'next/navigation';
|
| 6 |
+
|
| 7 |
+
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
|
| 8 |
+
|
| 9 |
+
export default function Home() {
|
| 10 |
+
const router = useRouter();
|
| 11 |
+
const [role, setRole] = useState<'student' | 'teacher'>('student');
|
| 12 |
+
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
| 13 |
+
const [loginEmail, setLoginEmail] = useState('');
|
| 14 |
+
const [loginPassword, setLoginPassword] = useState('');
|
| 15 |
+
const [loginError, setLoginError] = useState('');
|
| 16 |
+
const [isLoggingIn, setIsLoggingIn] = useState(false);
|
| 17 |
+
|
| 18 |
+
const handleLogin = async (e: React.FormEvent) => {
|
| 19 |
+
e.preventDefault();
|
| 20 |
+
setLoginError('');
|
| 21 |
+
setIsLoggingIn(true);
|
| 22 |
+
|
| 23 |
+
try {
|
| 24 |
+
let user;
|
| 25 |
+
let token;
|
| 26 |
+
|
| 27 |
+
// Try API login first
|
| 28 |
+
try {
|
| 29 |
+
const response = await fetch(`${API_URL}/auth/login`, {
|
| 30 |
+
method: 'POST',
|
| 31 |
+
headers: { 'Content-Type': 'application/json' },
|
| 32 |
+
body: JSON.stringify({
|
| 33 |
+
email: loginEmail,
|
| 34 |
+
password: loginPassword,
|
| 35 |
+
}),
|
| 36 |
+
});
|
| 37 |
+
|
| 38 |
+
if (response.ok) {
|
| 39 |
+
const data = await response.json();
|
| 40 |
+
user = data.user;
|
| 41 |
+
token = data.token;
|
| 42 |
+
localStorage.setItem('learnflow_token', token);
|
| 43 |
+
} else if (response.status === 401) {
|
| 44 |
+
setLoginError('Invalid email or password');
|
| 45 |
+
setIsLoggingIn(false);
|
| 46 |
+
return;
|
| 47 |
+
} else {
|
| 48 |
+
throw new Error('API not available');
|
| 49 |
+
}
|
| 50 |
+
} catch (apiError) {
|
| 51 |
+
// Fallback to localStorage
|
| 52 |
+
console.log('Using localStorage fallback for login');
|
| 53 |
+
const users = JSON.parse(localStorage.getItem('learnflow_users') || '[]');
|
| 54 |
+
user = users.find((u: any) => u.email === loginEmail && u.password === loginPassword);
|
| 55 |
+
|
| 56 |
+
if (!user) {
|
| 57 |
+
setLoginError('Invalid email or password');
|
| 58 |
+
setIsLoggingIn(false);
|
| 59 |
+
return;
|
| 60 |
+
}
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
// Check if role matches
|
| 64 |
+
if (user.role !== role) {
|
| 65 |
+
setLoginError(`This account is registered as a ${user.role}. Please select the correct role.`);
|
| 66 |
+
setIsLoggingIn(false);
|
| 67 |
+
return;
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
// Set current user session
|
| 71 |
+
localStorage.setItem('learnflow_current_user', JSON.stringify({
|
| 72 |
+
id: user.id,
|
| 73 |
+
name: user.name,
|
| 74 |
+
email: user.email,
|
| 75 |
+
role: user.role,
|
| 76 |
+
}));
|
| 77 |
+
|
| 78 |
+
// Redirect based on role
|
| 79 |
+
if (role === 'student') {
|
| 80 |
+
router.push('/student/dashboard');
|
| 81 |
+
} else {
|
| 82 |
+
router.push('/teacher/dashboard');
|
| 83 |
+
}
|
| 84 |
+
} catch (err) {
|
| 85 |
+
setLoginError('Something went wrong. Please try again.');
|
| 86 |
+
setIsLoggingIn(false);
|
| 87 |
+
}
|
| 88 |
+
};
|
| 89 |
+
|
| 90 |
+
const categories = [
|
| 91 |
+
{ name: 'Python Basics', color: 'teal', icon: '🐍' },
|
| 92 |
+
{ name: 'Data Science', color: 'lavender', icon: '📊' },
|
| 93 |
+
{ name: 'Web Dev', color: 'coral', icon: '🌐' },
|
| 94 |
+
{ name: 'AI & ML', color: 'sage', icon: '🤖' },
|
| 95 |
+
];
|
| 96 |
+
|
| 97 |
+
const features = [
|
| 98 |
+
{
|
| 99 |
+
icon: (
|
| 100 |
+
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 101 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
|
| 102 |
+
</svg>
|
| 103 |
+
),
|
| 104 |
+
title: 'AI-Powered Learning',
|
| 105 |
+
description: 'Personalized learning paths that adapt to your unique pace and style',
|
| 106 |
+
color: 'teal',
|
| 107 |
+
},
|
| 108 |
+
{
|
| 109 |
+
icon: (
|
| 110 |
+
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 111 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
|
| 112 |
+
</svg>
|
| 113 |
+
),
|
| 114 |
+
title: 'Interactive Coding',
|
| 115 |
+
description: 'Practice with real-time code execution and instant feedback',
|
| 116 |
+
color: 'lavender',
|
| 117 |
+
},
|
| 118 |
+
{
|
| 119 |
+
icon: (
|
| 120 |
+
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 121 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z" />
|
| 122 |
+
</svg>
|
| 123 |
+
),
|
| 124 |
+
title: 'Expert Community',
|
| 125 |
+
description: 'Connect with mentors and peers who support your learning journey',
|
| 126 |
+
color: 'coral',
|
| 127 |
+
},
|
| 128 |
+
{
|
| 129 |
+
icon: (
|
| 130 |
+
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 131 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z" />
|
| 132 |
+
</svg>
|
| 133 |
+
),
|
| 134 |
+
title: 'Verified Certificates',
|
| 135 |
+
description: 'Earn industry-recognized credentials that boost your career',
|
| 136 |
+
color: 'sage',
|
| 137 |
+
},
|
| 138 |
+
];
|
| 139 |
+
|
| 140 |
+
const courses = [
|
| 141 |
+
{
|
| 142 |
+
title: 'Python for Beginners',
|
| 143 |
+
description: 'Start your coding journey with the most beginner-friendly language',
|
| 144 |
+
duration: '8 weeks',
|
| 145 |
+
level: 'Beginner',
|
| 146 |
+
rating: 4.9,
|
| 147 |
+
students: '15,000+',
|
| 148 |
+
image: '🐍',
|
| 149 |
+
color: 'teal',
|
| 150 |
+
},
|
| 151 |
+
{
|
| 152 |
+
title: 'Data Science Essentials',
|
| 153 |
+
description: 'Master data analysis, visualization, and machine learning fundamentals',
|
| 154 |
+
duration: '12 weeks',
|
| 155 |
+
level: 'Intermediate',
|
| 156 |
+
rating: 4.8,
|
| 157 |
+
students: '12,000+',
|
| 158 |
+
image: '📊',
|
| 159 |
+
color: 'lavender',
|
| 160 |
+
},
|
| 161 |
+
{
|
| 162 |
+
title: 'Full Stack Web Development',
|
| 163 |
+
description: 'Build modern web applications from frontend to backend',
|
| 164 |
+
duration: '16 weeks',
|
| 165 |
+
level: 'Intermediate',
|
| 166 |
+
rating: 4.7,
|
| 167 |
+
students: '10,000+',
|
| 168 |
+
image: '🌐',
|
| 169 |
+
color: 'coral',
|
| 170 |
+
},
|
| 171 |
+
];
|
| 172 |
+
|
| 173 |
+
const testimonials = [
|
| 174 |
+
{
|
| 175 |
+
quote: "LearnFlow transformed my career. The AI tutor felt like having a personal mentor available 24/7.",
|
| 176 |
+
name: "Sarah Ahmed",
|
| 177 |
+
role: "Software Developer",
|
| 178 |
+
avatar: "SA",
|
| 179 |
+
},
|
| 180 |
+
{
|
| 181 |
+
quote: "The interactive coding exercises made learning Python actually fun. I went from zero to building apps in weeks.",
|
| 182 |
+
name: "Ali Hassan",
|
| 183 |
+
role: "Data Analyst",
|
| 184 |
+
avatar: "AH",
|
| 185 |
+
},
|
| 186 |
+
{
|
| 187 |
+
quote: "Finally, a learning platform that adapts to my pace. No more feeling left behind or bored.",
|
| 188 |
+
name: "Fatima Khan",
|
| 189 |
+
role: "Web Developer",
|
| 190 |
+
avatar: "FK",
|
| 191 |
+
},
|
| 192 |
+
];
|
| 193 |
+
|
| 194 |
+
return (
|
| 195 |
+
<div className="min-h-screen bg-cream-50">
|
| 196 |
+
{/* Header */}
|
| 197 |
+
<header className="bg-white/80 backdrop-blur-md border-b border-warmgray-100 sticky top-0 z-50">
|
| 198 |
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 199 |
+
<div className="flex justify-between items-center h-16">
|
| 200 |
+
<div className="flex items-center">
|
| 201 |
+
<div className="flex-shrink-0 flex items-center">
|
| 202 |
+
<div className="w-10 h-10 bg-gradient-to-br from-teal-500 to-teal-600 rounded-xl flex items-center justify-center shadow-soft">
|
| 203 |
+
<span className="text-white font-bold text-lg">LF</span>
|
| 204 |
+
</div>
|
| 205 |
+
<span className="ml-3 text-xl font-bold text-warmgray-900">LearnFlow</span>
|
| 206 |
+
</div>
|
| 207 |
+
</div>
|
| 208 |
+
|
| 209 |
+
<nav className="hidden md:flex items-center space-x-1">
|
| 210 |
+
{[
|
| 211 |
+
{ name: 'Home', href: '/' },
|
| 212 |
+
{ name: 'Courses', href: '/courses' },
|
| 213 |
+
{ name: 'About', href: '/about' },
|
| 214 |
+
{ name: 'Resources', href: '/resources' },
|
| 215 |
+
{ name: 'Contact', href: '/contact' },
|
| 216 |
+
].map((item, i) => (
|
| 217 |
+
<Link
|
| 218 |
+
key={item.name}
|
| 219 |
+
href={item.href}
|
| 220 |
+
className={`px-4 py-2 rounded-full text-sm font-medium transition-all duration-200 ${
|
| 221 |
+
i === 0
|
| 222 |
+
? 'bg-teal-50 text-teal-700'
|
| 223 |
+
: 'text-warmgray-600 hover:text-teal-700 hover:bg-teal-50'
|
| 224 |
+
}`}
|
| 225 |
+
>
|
| 226 |
+
{item.name}
|
| 227 |
+
</Link>
|
| 228 |
+
))}
|
| 229 |
+
</nav>
|
| 230 |
+
|
| 231 |
+
<div className="hidden md:flex items-center space-x-3">
|
| 232 |
+
<Link
|
| 233 |
+
href="/register"
|
| 234 |
+
className="text-warmgray-700 hover:text-teal-700 font-medium text-sm transition-colors px-4 py-2"
|
| 235 |
+
>
|
| 236 |
+
Sign Up
|
| 237 |
+
</Link>
|
| 238 |
+
<a
|
| 239 |
+
href="#login"
|
| 240 |
+
className="bg-teal-600 text-white px-5 py-2.5 rounded-full hover:bg-teal-700 transition-all duration-300 font-medium text-sm shadow-soft hover:shadow-md"
|
| 241 |
+
>
|
| 242 |
+
Login
|
| 243 |
+
</a>
|
| 244 |
+
</div>
|
| 245 |
+
|
| 246 |
+
<div className="md:hidden flex items-center">
|
| 247 |
+
<button
|
| 248 |
+
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
| 249 |
+
className="text-warmgray-700 hover:text-teal-600 p-2 rounded-lg hover:bg-teal-50 transition-colors"
|
| 250 |
+
>
|
| 251 |
+
<svg className="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 252 |
+
{mobileMenuOpen ? (
|
| 253 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
| 254 |
+
) : (
|
| 255 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
| 256 |
+
)}
|
| 257 |
+
</svg>
|
| 258 |
+
</button>
|
| 259 |
+
</div>
|
| 260 |
+
</div>
|
| 261 |
+
|
| 262 |
+
{mobileMenuOpen && (
|
| 263 |
+
<div className="md:hidden py-4 border-t border-warmgray-100 bg-white/95 backdrop-blur-md absolute left-0 right-0 shadow-soft-lg">
|
| 264 |
+
<div className="flex flex-col space-y-1 px-4">
|
| 265 |
+
{[
|
| 266 |
+
{ name: 'Home', href: '/' },
|
| 267 |
+
{ name: 'Courses', href: '/courses' },
|
| 268 |
+
{ name: 'About', href: '/about' },
|
| 269 |
+
{ name: 'Resources', href: '/resources' },
|
| 270 |
+
{ name: 'Contact', href: '/contact' },
|
| 271 |
+
].map((item, i) => (
|
| 272 |
+
<Link
|
| 273 |
+
key={item.name}
|
| 274 |
+
href={item.href}
|
| 275 |
+
className={`px-4 py-3 rounded-xl font-medium transition-colors ${
|
| 276 |
+
i === 0 ? 'bg-teal-50 text-teal-700' : 'text-warmgray-700 hover:bg-teal-50 hover:text-teal-700'
|
| 277 |
+
}`}
|
| 278 |
+
>
|
| 279 |
+
{item.name}
|
| 280 |
+
</Link>
|
| 281 |
+
))}
|
| 282 |
+
<div className="pt-4 mt-2 flex flex-col space-y-3 border-t border-warmgray-100">
|
| 283 |
+
<Link href="/register" className="text-teal-600 hover:text-teal-700 font-medium px-4 py-2">
|
| 284 |
+
Sign Up
|
| 285 |
+
</Link>
|
| 286 |
+
<a
|
| 287 |
+
href="#login"
|
| 288 |
+
className="bg-teal-600 text-white px-4 py-3 rounded-xl text-center hover:bg-teal-700 transition-colors font-medium"
|
| 289 |
+
>
|
| 290 |
+
Login
|
| 291 |
+
</a>
|
| 292 |
+
</div>
|
| 293 |
+
</div>
|
| 294 |
+
</div>
|
| 295 |
+
)}
|
| 296 |
+
</div>
|
| 297 |
+
</header>
|
| 298 |
+
|
| 299 |
+
{/* Hero Section */}
|
| 300 |
+
<section className="relative overflow-hidden bg-gradient-hero">
|
| 301 |
+
{/* Decorative blobs */}
|
| 302 |
+
<div className="blob-teal w-96 h-96 -top-48 -left-48 opacity-60" />
|
| 303 |
+
<div className="blob-lavender w-80 h-80 top-20 right-0 opacity-40" />
|
| 304 |
+
|
| 305 |
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16 md:py-24 relative">
|
| 306 |
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
|
| 307 |
+
<div className="text-center lg:text-left">
|
| 308 |
+
<div className="inline-flex items-center bg-teal-100 text-teal-800 px-4 py-2 rounded-full text-sm font-medium mb-6">
|
| 309 |
+
<span className="w-2 h-2 bg-teal-500 rounded-full mr-2 animate-pulse" />
|
| 310 |
+
AI-Powered Learning Platform
|
| 311 |
+
</div>
|
| 312 |
+
<h1 className="text-warmgray-900 mb-6 text-balance">
|
| 313 |
+
Your Journey to{' '}
|
| 314 |
+
<span className="text-transparent bg-clip-text bg-gradient-to-r from-teal-600 to-teal-500">
|
| 315 |
+
Programming Mastery
|
| 316 |
+
</span>{' '}
|
| 317 |
+
Starts Here
|
| 318 |
+
</h1>
|
| 319 |
+
<p className="text-lg md:text-xl text-warmgray-600 mb-8 leading-relaxed max-w-xl mx-auto lg:mx-0">
|
| 320 |
+
Experience personalized learning with AI tutors that understand your pace, answer your questions, and guide you every step of the way.
|
| 321 |
+
</p>
|
| 322 |
+
<div className="flex flex-col sm:flex-row gap-4 justify-center lg:justify-start">
|
| 323 |
+
<Link href="/register" className="btn-primary inline-flex items-center justify-center">
|
| 324 |
+
Start Learning Free
|
| 325 |
+
<svg className="w-5 h-5 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 326 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 7l5 5m0 0l-5 5m5-5H6" />
|
| 327 |
+
</svg>
|
| 328 |
+
</Link>
|
| 329 |
+
<a href="#courses" className="btn-secondary inline-flex items-center justify-center">
|
| 330 |
+
Explore Courses
|
| 331 |
+
</a>
|
| 332 |
+
</div>
|
| 333 |
+
|
| 334 |
+
{/* Trust badges */}
|
| 335 |
+
<div className="mt-10 flex flex-wrap items-center justify-center lg:justify-start gap-6 text-sm text-warmgray-500">
|
| 336 |
+
<div className="flex items-center">
|
| 337 |
+
<svg className="w-5 h-5 text-teal-500 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
| 338 |
+
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
| 339 |
+
</svg>
|
| 340 |
+
50,000+ Students
|
| 341 |
+
</div>
|
| 342 |
+
<div className="flex items-center">
|
| 343 |
+
<svg className="w-5 h-5 text-teal-500 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
| 344 |
+
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
| 345 |
+
</svg>
|
| 346 |
+
95% Success Rate
|
| 347 |
+
</div>
|
| 348 |
+
<div className="flex items-center">
|
| 349 |
+
<svg className="w-5 h-5 text-teal-500 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
| 350 |
+
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
| 351 |
+
</svg>
|
| 352 |
+
24/7 Support
|
| 353 |
+
</div>
|
| 354 |
+
</div>
|
| 355 |
+
</div>
|
| 356 |
+
|
| 357 |
+
{/* Hero Card */}
|
| 358 |
+
<div className="relative">
|
| 359 |
+
<div className="bg-white rounded-3xl p-8 shadow-soft-lg relative z-10">
|
| 360 |
+
<div className="text-center mb-6">
|
| 361 |
+
<div className="w-20 h-20 bg-gradient-to-br from-teal-400 to-teal-600 rounded-2xl flex items-center justify-center mx-auto mb-4 shadow-soft animate-float">
|
| 362 |
+
<span className="text-4xl">🎓</span>
|
| 363 |
+
</div>
|
| 364 |
+
<h3 className="text-xl font-bold text-warmgray-900 mb-2">Start Your Learning Journey</h3>
|
| 365 |
+
<p className="text-warmgray-600">Interactive Python tutoring with real-time AI feedback</p>
|
| 366 |
+
</div>
|
| 367 |
+
|
| 368 |
+
<div className="grid grid-cols-2 gap-4">
|
| 369 |
+
{[
|
| 370 |
+
{ value: '50K+', label: 'Students', bg: 'bg-teal-50', text: 'text-teal-700' },
|
| 371 |
+
{ value: '100+', label: 'Courses', bg: 'bg-lavender-50', text: 'text-lavender-700' },
|
| 372 |
+
{ value: '95%', label: 'Success', bg: 'bg-coral-50', text: 'text-coral-700' },
|
| 373 |
+
{ value: '24/7', label: 'Support', bg: 'bg-sage-50', text: 'text-sage-700' },
|
| 374 |
+
].map((stat) => (
|
| 375 |
+
<div key={stat.label} className={`${stat.bg} p-4 rounded-2xl text-center`}>
|
| 376 |
+
<div className={`text-2xl font-bold ${stat.text}`}>{stat.value}</div>
|
| 377 |
+
<div className="text-sm text-warmgray-600">{stat.label}</div>
|
| 378 |
+
</div>
|
| 379 |
+
))}
|
| 380 |
+
</div>
|
| 381 |
+
</div>
|
| 382 |
+
|
| 383 |
+
{/* Decorative elements */}
|
| 384 |
+
<div className="absolute -bottom-4 -right-4 w-32 h-32 bg-lavender-200 rounded-2xl -z-10 opacity-50" />
|
| 385 |
+
<div className="absolute -top-4 -left-4 w-24 h-24 bg-teal-200 rounded-2xl -z-10 opacity-50" />
|
| 386 |
+
</div>
|
| 387 |
+
</div>
|
| 388 |
+
</div>
|
| 389 |
+
</section>
|
| 390 |
+
|
| 391 |
+
{/* Categories Section */}
|
| 392 |
+
<section className="py-12 bg-white">
|
| 393 |
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 394 |
+
<div className="flex flex-wrap justify-center gap-3">
|
| 395 |
+
{categories.map((cat) => (
|
| 396 |
+
<button
|
| 397 |
+
key={cat.name}
|
| 398 |
+
className={`category-pill category-pill-${cat.color} flex items-center gap-2`}
|
| 399 |
+
>
|
| 400 |
+
<span>{cat.icon}</span>
|
| 401 |
+
{cat.name}
|
| 402 |
+
</button>
|
| 403 |
+
))}
|
| 404 |
+
</div>
|
| 405 |
+
</div>
|
| 406 |
+
</section>
|
| 407 |
+
|
| 408 |
+
{/* Features Section */}
|
| 409 |
+
<section className="section-wellness bg-white">
|
| 410 |
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 411 |
+
<div className="text-center mb-16">
|
| 412 |
+
<h2 className="text-warmgray-900 mb-4">Why Choose LearnFlow?</h2>
|
| 413 |
+
<p className="text-lg text-warmgray-600 max-w-2xl mx-auto">
|
| 414 |
+
Our platform combines cutting-edge AI with proven learning methods to create an experience that's effective and enjoyable
|
| 415 |
+
</p>
|
| 416 |
+
</div>
|
| 417 |
+
|
| 418 |
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
| 419 |
+
{features.map((feature) => (
|
| 420 |
+
<div key={feature.title} className="wellness-card text-center group">
|
| 421 |
+
<div
|
| 422 |
+
className={`w-16 h-16 mx-auto mb-6 rounded-2xl flex items-center justify-center transition-transform duration-300 group-hover:scale-110 ${
|
| 423 |
+
feature.color === 'teal' ? 'bg-teal-100 text-teal-600' :
|
| 424 |
+
feature.color === 'lavender' ? 'bg-lavender-100 text-lavender-600' :
|
| 425 |
+
feature.color === 'coral' ? 'bg-coral-100 text-coral-600' :
|
| 426 |
+
'bg-sage-100 text-sage-600'
|
| 427 |
+
}`}
|
| 428 |
+
>
|
| 429 |
+
{feature.icon}
|
| 430 |
+
</div>
|
| 431 |
+
<h3 className="text-lg font-bold text-warmgray-900 mb-3">{feature.title}</h3>
|
| 432 |
+
<p className="text-warmgray-600 text-sm leading-relaxed">{feature.description}</p>
|
| 433 |
+
</div>
|
| 434 |
+
))}
|
| 435 |
+
</div>
|
| 436 |
+
</div>
|
| 437 |
+
</section>
|
| 438 |
+
|
| 439 |
+
{/* Courses Section */}
|
| 440 |
+
<section id="courses" className="section-wellness bg-gradient-wellness">
|
| 441 |
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 442 |
+
<div className="text-center mb-16">
|
| 443 |
+
<h2 className="text-warmgray-900 mb-4">Popular Courses</h2>
|
| 444 |
+
<p className="text-lg text-warmgray-600 max-w-2xl mx-auto">
|
| 445 |
+
Choose from our carefully crafted programs designed to take you from beginner to professional
|
| 446 |
+
</p>
|
| 447 |
+
</div>
|
| 448 |
+
|
| 449 |
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
| 450 |
+
{courses.map((course) => (
|
| 451 |
+
<div key={course.title} className="wellness-card group">
|
| 452 |
+
<div className="flex items-start justify-between mb-4">
|
| 453 |
+
<div
|
| 454 |
+
className={`w-14 h-14 rounded-2xl flex items-center justify-center text-2xl ${
|
| 455 |
+
course.color === 'teal' ? 'bg-teal-100' :
|
| 456 |
+
course.color === 'lavender' ? 'bg-lavender-100' :
|
| 457 |
+
'bg-coral-100'
|
| 458 |
+
}`}
|
| 459 |
+
>
|
| 460 |
+
{course.image}
|
| 461 |
+
</div>
|
| 462 |
+
<span
|
| 463 |
+
className={`px-3 py-1 rounded-full text-xs font-medium ${
|
| 464 |
+
course.color === 'teal' ? 'bg-teal-100 text-teal-700' :
|
| 465 |
+
course.color === 'lavender' ? 'bg-lavender-100 text-lavender-700' :
|
| 466 |
+
'bg-coral-100 text-coral-700'
|
| 467 |
+
}`}
|
| 468 |
+
>
|
| 469 |
+
{course.level}
|
| 470 |
+
</span>
|
| 471 |
+
</div>
|
| 472 |
+
|
| 473 |
+
<h3 className="text-lg font-bold text-warmgray-900 mb-2">{course.title}</h3>
|
| 474 |
+
<p className="text-warmgray-600 text-sm mb-4 leading-relaxed">{course.description}</p>
|
| 475 |
+
|
| 476 |
+
<div className="flex items-center justify-between text-sm text-warmgray-500 mb-4">
|
| 477 |
+
<span className="flex items-center">
|
| 478 |
+
<svg className="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 479 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
| 480 |
+
</svg>
|
| 481 |
+
{course.duration}
|
| 482 |
+
</span>
|
| 483 |
+
<span className="flex items-center">
|
| 484 |
+
<svg className="w-4 h-4 mr-1 text-yellow-500" fill="currentColor" viewBox="0 0 20 20">
|
| 485 |
+
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
|
| 486 |
+
</svg>
|
| 487 |
+
{course.rating}
|
| 488 |
+
</span>
|
| 489 |
+
<span>{course.students}</span>
|
| 490 |
+
</div>
|
| 491 |
+
|
| 492 |
+
<button
|
| 493 |
+
className={`w-full py-3 rounded-xl font-medium transition-all duration-300 ${
|
| 494 |
+
course.color === 'teal' ? 'bg-teal-600 hover:bg-teal-700 text-white' :
|
| 495 |
+
course.color === 'lavender' ? 'bg-lavender-600 hover:bg-lavender-700 text-white' :
|
| 496 |
+
'bg-coral-600 hover:bg-coral-700 text-white'
|
| 497 |
+
}`}
|
| 498 |
+
>
|
| 499 |
+
Enroll Now
|
| 500 |
+
</button>
|
| 501 |
+
</div>
|
| 502 |
+
))}
|
| 503 |
+
</div>
|
| 504 |
+
|
| 505 |
+
<div className="text-center mt-12">
|
| 506 |
+
<a href="#" className="btn-secondary inline-flex items-center">
|
| 507 |
+
View All Courses
|
| 508 |
+
<svg className="w-5 h-5 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 509 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 7l5 5m0 0l-5 5m5-5H6" />
|
| 510 |
+
</svg>
|
| 511 |
+
</a>
|
| 512 |
+
</div>
|
| 513 |
+
</div>
|
| 514 |
+
</section>
|
| 515 |
+
|
| 516 |
+
{/* Testimonials Section */}
|
| 517 |
+
<section className="section-wellness bg-white">
|
| 518 |
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 519 |
+
<div className="text-center mb-16">
|
| 520 |
+
<h2 className="text-warmgray-900 mb-4">What Our Students Say</h2>
|
| 521 |
+
<p className="text-lg text-warmgray-600 max-w-2xl mx-auto">
|
| 522 |
+
Join thousands of learners who have transformed their careers with LearnFlow
|
| 523 |
+
</p>
|
| 524 |
+
</div>
|
| 525 |
+
|
| 526 |
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
| 527 |
+
{testimonials.map((testimonial, index) => (
|
| 528 |
+
<div key={index} className="wellness-card">
|
| 529 |
+
<div className="flex items-center mb-4">
|
| 530 |
+
{[...Array(5)].map((_, i) => (
|
| 531 |
+
<svg key={i} className="w-5 h-5 text-yellow-400" fill="currentColor" viewBox="0 0 20 20">
|
| 532 |
+
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
|
| 533 |
+
</svg>
|
| 534 |
+
))}
|
| 535 |
+
</div>
|
| 536 |
+
<p className="text-warmgray-700 mb-6 italic leading-relaxed">"{testimonial.quote}"</p>
|
| 537 |
+
<div className="flex items-center">
|
| 538 |
+
<div className="w-12 h-12 bg-gradient-to-br from-teal-400 to-teal-600 rounded-full flex items-center justify-center text-white font-bold">
|
| 539 |
+
{testimonial.avatar}
|
| 540 |
+
</div>
|
| 541 |
+
<div className="ml-4">
|
| 542 |
+
<div className="font-semibold text-warmgray-900">{testimonial.name}</div>
|
| 543 |
+
<div className="text-sm text-warmgray-500">{testimonial.role}</div>
|
| 544 |
+
</div>
|
| 545 |
+
</div>
|
| 546 |
+
</div>
|
| 547 |
+
))}
|
| 548 |
+
</div>
|
| 549 |
+
</div>
|
| 550 |
+
</section>
|
| 551 |
+
|
| 552 |
+
{/* Login Section */}
|
| 553 |
+
<section id="login" className="section-wellness bg-gradient-wellness">
|
| 554 |
+
<div className="max-w-xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 555 |
+
<div className="bg-white rounded-3xl p-8 md:p-10 shadow-soft-lg">
|
| 556 |
+
<div className="text-center mb-8">
|
| 557 |
+
<h2 className="text-2xl font-bold text-warmgray-900 mb-2">Welcome Back</h2>
|
| 558 |
+
<p className="text-warmgray-600">Access your personalized learning dashboard</p>
|
| 559 |
+
</div>
|
| 560 |
+
|
| 561 |
+
<div className="flex justify-center gap-2 mb-8 bg-warmgray-100 p-1.5 rounded-full">
|
| 562 |
+
<button
|
| 563 |
+
onClick={() => setRole('student')}
|
| 564 |
+
className={`flex-1 px-6 py-2.5 rounded-full font-medium text-sm transition-all duration-300 ${
|
| 565 |
+
role === 'student'
|
| 566 |
+
? 'bg-teal-600 text-white shadow-soft'
|
| 567 |
+
: 'text-warmgray-600 hover:text-warmgray-900'
|
| 568 |
+
}`}
|
| 569 |
+
>
|
| 570 |
+
Student
|
| 571 |
+
</button>
|
| 572 |
+
<button
|
| 573 |
+
onClick={() => setRole('teacher')}
|
| 574 |
+
className={`flex-1 px-6 py-2.5 rounded-full font-medium text-sm transition-all duration-300 ${
|
| 575 |
+
role === 'teacher'
|
| 576 |
+
? 'bg-lavender-600 text-white shadow-soft'
|
| 577 |
+
: 'text-warmgray-600 hover:text-warmgray-900'
|
| 578 |
+
}`}
|
| 579 |
+
>
|
| 580 |
+
Teacher
|
| 581 |
+
</button>
|
| 582 |
+
</div>
|
| 583 |
+
|
| 584 |
+
{/* Login Error */}
|
| 585 |
+
{loginError && (
|
| 586 |
+
<div className="mb-6 p-4 bg-coral-50 border border-coral-200 rounded-xl flex items-center text-coral-700">
|
| 587 |
+
<svg className="w-5 h-5 mr-3 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 588 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
| 589 |
+
</svg>
|
| 590 |
+
<span className="text-sm">{loginError}</span>
|
| 591 |
+
</div>
|
| 592 |
+
)}
|
| 593 |
+
|
| 594 |
+
<form onSubmit={handleLogin} className="space-y-5">
|
| 595 |
+
<div>
|
| 596 |
+
<label htmlFor="login-email" className="block text-sm font-medium text-warmgray-700 mb-2">
|
| 597 |
+
Email Address
|
| 598 |
+
</label>
|
| 599 |
+
<input
|
| 600 |
+
type="email"
|
| 601 |
+
id="login-email"
|
| 602 |
+
name="email"
|
| 603 |
+
required
|
| 604 |
+
value={loginEmail}
|
| 605 |
+
onChange={(e) => { setLoginEmail(e.target.value); setLoginError(''); }}
|
| 606 |
+
className="input-wellness"
|
| 607 |
+
placeholder="you@example.com"
|
| 608 |
+
disabled={isLoggingIn}
|
| 609 |
+
/>
|
| 610 |
+
</div>
|
| 611 |
+
<div>
|
| 612 |
+
<label htmlFor="login-password" className="block text-sm font-medium text-warmgray-700 mb-2">
|
| 613 |
+
Password
|
| 614 |
+
</label>
|
| 615 |
+
<input
|
| 616 |
+
type="password"
|
| 617 |
+
id="login-password"
|
| 618 |
+
name="password"
|
| 619 |
+
required
|
| 620 |
+
value={loginPassword}
|
| 621 |
+
onChange={(e) => { setLoginPassword(e.target.value); setLoginError(''); }}
|
| 622 |
+
className="input-wellness"
|
| 623 |
+
placeholder="Enter your password"
|
| 624 |
+
disabled={isLoggingIn}
|
| 625 |
+
/>
|
| 626 |
+
</div>
|
| 627 |
+
<div className="flex items-center justify-between">
|
| 628 |
+
<div className="flex items-center">
|
| 629 |
+
<input
|
| 630 |
+
id="remember-me"
|
| 631 |
+
name="remember-me"
|
| 632 |
+
type="checkbox"
|
| 633 |
+
className="h-4 w-4 text-teal-600 focus:ring-teal-500 border-warmgray-300 rounded"
|
| 634 |
+
/>
|
| 635 |
+
<label htmlFor="remember-me" className="ml-2 block text-sm text-warmgray-700">
|
| 636 |
+
Remember me
|
| 637 |
+
</label>
|
| 638 |
+
</div>
|
| 639 |
+
<a href="#" className="text-sm text-teal-600 hover:text-teal-700 font-medium">
|
| 640 |
+
Forgot password?
|
| 641 |
+
</a>
|
| 642 |
+
</div>
|
| 643 |
+
<button
|
| 644 |
+
type="submit"
|
| 645 |
+
disabled={isLoggingIn}
|
| 646 |
+
className={`w-full py-3 rounded-xl font-medium transition-all duration-300 flex items-center justify-center ${
|
| 647 |
+
role === 'student'
|
| 648 |
+
? 'bg-teal-600 hover:bg-teal-700 text-white'
|
| 649 |
+
: 'bg-lavender-600 hover:bg-lavender-700 text-white'
|
| 650 |
+
} ${isLoggingIn ? 'opacity-70 cursor-not-allowed' : ''}`}
|
| 651 |
+
>
|
| 652 |
+
{isLoggingIn ? (
|
| 653 |
+
<>
|
| 654 |
+
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white" fill="none" viewBox="0 0 24 24">
|
| 655 |
+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
| 656 |
+
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
|
| 657 |
+
</svg>
|
| 658 |
+
Signing In...
|
| 659 |
+
</>
|
| 660 |
+
) : (
|
| 661 |
+
'Sign In'
|
| 662 |
+
)}
|
| 663 |
+
</button>
|
| 664 |
+
</form>
|
| 665 |
+
|
| 666 |
+
<p className="text-center mt-6 text-sm text-warmgray-600">
|
| 667 |
+
Don't have an account?{' '}
|
| 668 |
+
<Link href="/register" className="text-teal-600 hover:text-teal-700 font-medium">
|
| 669 |
+
Sign up for free
|
| 670 |
+
</Link>
|
| 671 |
+
</p>
|
| 672 |
+
</div>
|
| 673 |
+
</div>
|
| 674 |
+
</section>
|
| 675 |
+
|
| 676 |
+
{/* Newsletter Section */}
|
| 677 |
+
<section className="py-16 bg-teal-700">
|
| 678 |
+
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
| 679 |
+
<h2 className="text-2xl md:text-3xl font-bold text-white mb-4">
|
| 680 |
+
Stay Updated with Latest Courses
|
| 681 |
+
</h2>
|
| 682 |
+
<p className="text-teal-100 mb-8 max-w-xl mx-auto">
|
| 683 |
+
Subscribe to our newsletter and get notified about new courses, tips, and exclusive offers
|
| 684 |
+
</p>
|
| 685 |
+
<form className="flex flex-col sm:flex-row gap-4 max-w-md mx-auto">
|
| 686 |
+
<input
|
| 687 |
+
type="email"
|
| 688 |
+
placeholder="Enter your email"
|
| 689 |
+
className="flex-1 px-5 py-3 rounded-full bg-white/10 border border-white/20 text-white placeholder:text-teal-200 focus:outline-none focus:ring-2 focus:ring-white/30"
|
| 690 |
+
/>
|
| 691 |
+
<button
|
| 692 |
+
type="submit"
|
| 693 |
+
className="bg-white text-teal-700 px-8 py-3 rounded-full font-medium hover:bg-teal-50 transition-colors"
|
| 694 |
+
>
|
| 695 |
+
Subscribe
|
| 696 |
+
</button>
|
| 697 |
+
</form>
|
| 698 |
+
</div>
|
| 699 |
+
</section>
|
| 700 |
+
|
| 701 |
+
{/* Footer */}
|
| 702 |
+
<footer className="bg-warmgray-900 text-white">
|
| 703 |
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
| 704 |
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 mb-12">
|
| 705 |
+
<div>
|
| 706 |
+
<div className="flex items-center mb-6">
|
| 707 |
+
<div className="w-10 h-10 bg-gradient-to-br from-teal-400 to-teal-600 rounded-xl flex items-center justify-center">
|
| 708 |
+
<span className="text-white font-bold">LF</span>
|
| 709 |
+
</div>
|
| 710 |
+
<span className="ml-3 text-lg font-bold">LearnFlow</span>
|
| 711 |
+
</div>
|
| 712 |
+
<p className="text-warmgray-400 text-sm leading-relaxed mb-6">
|
| 713 |
+
Empowering the next generation of developers with AI-powered learning experiences that adapt to your unique journey.
|
| 714 |
+
</p>
|
| 715 |
+
<div className="flex space-x-4">
|
| 716 |
+
{['facebook', 'twitter', 'linkedin', 'instagram'].map((social) => (
|
| 717 |
+
<a
|
| 718 |
+
key={social}
|
| 719 |
+
href="#"
|
| 720 |
+
className="w-10 h-10 bg-warmgray-800 hover:bg-teal-600 rounded-full flex items-center justify-center transition-colors"
|
| 721 |
+
>
|
| 722 |
+
<span className="sr-only">{social}</span>
|
| 723 |
+
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
| 724 |
+
{social === 'facebook' && (
|
| 725 |
+
<path fillRule="evenodd" d="M22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 4.991 3.657 9.128 8.438 9.878v-6.987h-2.54V12h2.54V9.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V12h2.773l-.443 2.89h-2.33v6.988C18.343 21.128 22 16.991 22 12z" clipRule="evenodd" />
|
| 726 |
+
)}
|
| 727 |
+
{social === 'twitter' && (
|
| 728 |
+
<path d="M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0022 5.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.072 4.072 0 012.8 9.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 012 18.407a11.616 11.616 0 006.29 1.84" />
|
| 729 |
+
)}
|
| 730 |
+
{social === 'linkedin' && (
|
| 731 |
+
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />
|
| 732 |
+
)}
|
| 733 |
+
{social === 'instagram' && (
|
| 734 |
+
<path fillRule="evenodd" d="M12.315 2c2.43 0 2.784.013 3.808.06 1.064.049 1.791.218 2.427.465a4.902 4.902 0 011.772 1.153 4.902 4.902 0 011.153 1.772c.247.636.416 1.363.465 2.427.048 1.067.06 1.407.06 4.123v.08c0 2.643-.012 2.987-.06 4.043-.049 1.064-.218 1.791-.465 2.427a4.902 4.902 0 01-1.153 1.772 4.902 4.902 0 01-1.772 1.153c-.636.247-1.363.416-2.427.465-1.067.048-1.407.06-4.123.06h-.08c-2.643 0-2.987-.012-4.043-.06-1.064-.049-1.791-.218-2.427-.465a4.902 4.902 0 01-1.772-1.153 4.902 4.902 0 01-1.153-1.772c-.247-.636-.416-1.363-.465-2.427-.047-1.024-.06-1.379-.06-3.808v-.63c0-2.43.013-2.784.06-3.808.049-1.064.218-1.791.465-2.427a4.902 4.902 0 011.153-1.772A4.902 4.902 0 015.45 2.525c.636-.247 1.363-.416 2.427-.465C8.901 2.013 9.256 2 11.685 2h.63zm-.081 1.802h-.468c-2.456 0-2.784.011-3.807.058-.975.045-1.504.207-1.857.344-.467.182-.8.398-1.15.748-.35.35-.566.683-.748 1.15-.137.353-.3.882-.344 1.857-.047 1.023-.058 1.351-.058 3.807v.468c0 2.456.011 2.784.058 3.807.045.975.207 1.504.344 1.857.182.466.399.8.748 1.15.35.35.683.566 1.15.748.353.137.882.3 1.857.344 1.054.048 1.37.058 4.041.058h.08c2.597 0 2.917-.01 3.96-.058.976-.045 1.505-.207 1.858-.344.466-.182.8-.398 1.15-.748.35-.35.566-.683.748-1.15.137-.353.3-.882.344-1.857.048-1.055.058-1.37.058-4.041v-.08c0-2.597-.01-2.917-.058-3.96-.045-.976-.207-1.505-.344-1.858a3.097 3.097 0 00-.748-1.15 3.098 3.098 0 00-1.15-.748c-.353-.137-.882-.3-1.857-.344-1.023-.047-1.351-.058-3.807-.058zM12 6.865a5.135 5.135 0 110 10.27 5.135 5.135 0 010-10.27zm0 1.802a3.333 3.333 0 100 6.666 3.333 3.333 0 000-6.666zm5.338-3.205a1.2 1.2 0 110 2.4 1.2 1.2 0 010-2.4z" clipRule="evenodd" />
|
| 735 |
+
)}
|
| 736 |
+
</svg>
|
| 737 |
+
</a>
|
| 738 |
+
))}
|
| 739 |
+
</div>
|
| 740 |
+
</div>
|
| 741 |
+
|
| 742 |
+
<div>
|
| 743 |
+
<h3 className="text-sm font-semibold uppercase tracking-wider mb-4">Quick Links</h3>
|
| 744 |
+
<ul className="space-y-3">
|
| 745 |
+
{['About Us', 'Courses', 'Instructors', 'Blog', 'Careers'].map((link) => (
|
| 746 |
+
<li key={link}>
|
| 747 |
+
<a href="#" className="text-warmgray-400 hover:text-teal-400 transition-colors text-sm">
|
| 748 |
+
{link}
|
| 749 |
+
</a>
|
| 750 |
+
</li>
|
| 751 |
+
))}
|
| 752 |
+
</ul>
|
| 753 |
+
</div>
|
| 754 |
+
|
| 755 |
+
<div>
|
| 756 |
+
<h3 className="text-sm font-semibold uppercase tracking-wider mb-4">Programs</h3>
|
| 757 |
+
<ul className="space-y-3">
|
| 758 |
+
{['Python Programming', 'Data Science', 'Web Development', 'Machine Learning', 'Cyber Security'].map((link) => (
|
| 759 |
+
<li key={link}>
|
| 760 |
+
<a href="#" className="text-warmgray-400 hover:text-teal-400 transition-colors text-sm">
|
| 761 |
+
{link}
|
| 762 |
+
</a>
|
| 763 |
+
</li>
|
| 764 |
+
))}
|
| 765 |
+
</ul>
|
| 766 |
+
</div>
|
| 767 |
+
|
| 768 |
+
<div>
|
| 769 |
+
<h3 className="text-sm font-semibold uppercase tracking-wider mb-4">Contact</h3>
|
| 770 |
+
<ul className="space-y-3 text-sm text-warmgray-400">
|
| 771 |
+
<li className="flex items-start">
|
| 772 |
+
<svg className="w-5 h-5 mr-3 mt-0.5 text-teal-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 773 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
|
| 774 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
|
| 775 |
+
</svg>
|
| 776 |
+
123 Education Street, Karachi, Pakistan
|
| 777 |
+
</li>
|
| 778 |
+
<li className="flex items-center">
|
| 779 |
+
<svg className="w-5 h-5 mr-3 text-teal-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 780 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
| 781 |
+
</svg>
|
| 782 |
+
info@learnflow.edu.pk
|
| 783 |
+
</li>
|
| 784 |
+
<li className="flex items-center">
|
| 785 |
+
<svg className="w-5 h-5 mr-3 text-teal-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 786 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
|
| 787 |
+
</svg>
|
| 788 |
+
+92 300 1234567
|
| 789 |
+
</li>
|
| 790 |
+
</ul>
|
| 791 |
+
</div>
|
| 792 |
+
</div>
|
| 793 |
+
|
| 794 |
+
<div className="border-t border-warmgray-800 pt-8 flex flex-col md:flex-row justify-between items-center">
|
| 795 |
+
<p className="text-sm text-warmgray-400">
|
| 796 |
+
© 2026 LearnFlow. All rights reserved.
|
| 797 |
+
</p>
|
| 798 |
+
<div className="flex space-x-6 mt-4 md:mt-0">
|
| 799 |
+
<a href="#" className="text-sm text-warmgray-400 hover:text-teal-400 transition-colors">
|
| 800 |
+
Privacy Policy
|
| 801 |
+
</a>
|
| 802 |
+
<a href="#" className="text-sm text-warmgray-400 hover:text-teal-400 transition-colors">
|
| 803 |
+
Terms of Service
|
| 804 |
+
</a>
|
| 805 |
+
<a href="#" className="text-sm text-warmgray-400 hover:text-teal-400 transition-colors">
|
| 806 |
+
Cookie Policy
|
| 807 |
+
</a>
|
| 808 |
+
</div>
|
| 809 |
+
</div>
|
| 810 |
+
</div>
|
| 811 |
+
</footer>
|
| 812 |
+
</div>
|
| 813 |
+
);
|
| 814 |
+
}
|
app/register/page.tsx
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import { useState } from 'react';
|
| 4 |
+
import Link from 'next/link';
|
| 5 |
+
import { useRouter } from 'next/navigation';
|
| 6 |
+
|
| 7 |
+
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
|
| 8 |
+
|
| 9 |
+
export default function RegisterPage() {
|
| 10 |
+
const router = useRouter();
|
| 11 |
+
const [isStudent, setIsStudent] = useState(true);
|
| 12 |
+
const [isLoading, setIsLoading] = useState(false);
|
| 13 |
+
const [error, setError] = useState('');
|
| 14 |
+
const [formData, setFormData] = useState({
|
| 15 |
+
name: '',
|
| 16 |
+
email: '',
|
| 17 |
+
password: '',
|
| 18 |
+
confirmPassword: '',
|
| 19 |
+
});
|
| 20 |
+
|
| 21 |
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
| 22 |
+
const { name, value } = e.target;
|
| 23 |
+
setFormData(prev => ({ ...prev, [name]: value }));
|
| 24 |
+
setError(''); // Clear error when user types
|
| 25 |
+
};
|
| 26 |
+
|
| 27 |
+
const handleRoleChange = (student: boolean) => {
|
| 28 |
+
setIsStudent(student);
|
| 29 |
+
setError('');
|
| 30 |
+
};
|
| 31 |
+
|
| 32 |
+
const handleSubmit = async (e: React.FormEvent) => {
|
| 33 |
+
e.preventDefault();
|
| 34 |
+
setError('');
|
| 35 |
+
setIsLoading(true);
|
| 36 |
+
|
| 37 |
+
// Validation
|
| 38 |
+
if (!formData.name.trim()) {
|
| 39 |
+
setError('Please enter your full name');
|
| 40 |
+
setIsLoading(false);
|
| 41 |
+
return;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
if (!formData.email.trim()) {
|
| 45 |
+
setError('Please enter your email');
|
| 46 |
+
setIsLoading(false);
|
| 47 |
+
return;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
if (formData.password.length < 6) {
|
| 51 |
+
setError('Password must be at least 6 characters');
|
| 52 |
+
setIsLoading(false);
|
| 53 |
+
return;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
if (formData.password !== formData.confirmPassword) {
|
| 57 |
+
setError('Passwords do not match');
|
| 58 |
+
setIsLoading(false);
|
| 59 |
+
return;
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
try {
|
| 63 |
+
// Try to register via API
|
| 64 |
+
let user;
|
| 65 |
+
let token;
|
| 66 |
+
|
| 67 |
+
try {
|
| 68 |
+
const response = await fetch(`${API_URL}/auth/register`, {
|
| 69 |
+
method: 'POST',
|
| 70 |
+
headers: { 'Content-Type': 'application/json' },
|
| 71 |
+
body: JSON.stringify({
|
| 72 |
+
name: formData.name,
|
| 73 |
+
email: formData.email,
|
| 74 |
+
password: formData.password,
|
| 75 |
+
role: isStudent ? 'student' : 'teacher',
|
| 76 |
+
}),
|
| 77 |
+
});
|
| 78 |
+
|
| 79 |
+
if (response.ok) {
|
| 80 |
+
const data = await response.json();
|
| 81 |
+
user = data.user;
|
| 82 |
+
token = data.token;
|
| 83 |
+
localStorage.setItem('learnflow_token', token);
|
| 84 |
+
} else if (response.status === 400) {
|
| 85 |
+
const data = await response.json();
|
| 86 |
+
setError(data.detail || 'Registration failed');
|
| 87 |
+
setIsLoading(false);
|
| 88 |
+
return;
|
| 89 |
+
} else {
|
| 90 |
+
throw new Error('API not available');
|
| 91 |
+
}
|
| 92 |
+
} catch (apiError) {
|
| 93 |
+
// Fallback to localStorage
|
| 94 |
+
console.log('Using localStorage fallback for registration');
|
| 95 |
+
|
| 96 |
+
user = {
|
| 97 |
+
id: Date.now().toString(),
|
| 98 |
+
name: formData.name,
|
| 99 |
+
email: formData.email,
|
| 100 |
+
role: isStudent ? 'student' : 'teacher',
|
| 101 |
+
createdAt: new Date().toISOString(),
|
| 102 |
+
};
|
| 103 |
+
|
| 104 |
+
const existingUsers = JSON.parse(localStorage.getItem('learnflow_users') || '[]');
|
| 105 |
+
if (existingUsers.find((u: any) => u.email === formData.email)) {
|
| 106 |
+
setError('An account with this email already exists');
|
| 107 |
+
setIsLoading(false);
|
| 108 |
+
return;
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
existingUsers.push({ ...user, password: formData.password });
|
| 112 |
+
localStorage.setItem('learnflow_users', JSON.stringify(existingUsers));
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
// Set current user session
|
| 116 |
+
localStorage.setItem('learnflow_current_user', JSON.stringify(user));
|
| 117 |
+
|
| 118 |
+
// Redirect based on role
|
| 119 |
+
if (isStudent) {
|
| 120 |
+
router.push('/student/dashboard');
|
| 121 |
+
} else {
|
| 122 |
+
router.push('/teacher/dashboard');
|
| 123 |
+
}
|
| 124 |
+
} catch (err) {
|
| 125 |
+
setError('Something went wrong. Please try again.');
|
| 126 |
+
setIsLoading(false);
|
| 127 |
+
}
|
| 128 |
+
};
|
| 129 |
+
|
| 130 |
+
return (
|
| 131 |
+
<div className="min-h-screen bg-cream-50">
|
| 132 |
+
{/* Header */}
|
| 133 |
+
<header className="bg-white/80 backdrop-blur-md border-b border-warmgray-100">
|
| 134 |
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 135 |
+
<div className="flex justify-between items-center h-16">
|
| 136 |
+
<Link href="/" className="flex items-center">
|
| 137 |
+
<div className="w-10 h-10 bg-gradient-to-br from-teal-500 to-teal-600 rounded-xl flex items-center justify-center shadow-soft">
|
| 138 |
+
<span className="text-white font-bold text-lg">LF</span>
|
| 139 |
+
</div>
|
| 140 |
+
<span className="ml-3 text-xl font-bold text-warmgray-900">LearnFlow</span>
|
| 141 |
+
</Link>
|
| 142 |
+
|
| 143 |
+
<Link
|
| 144 |
+
href="/"
|
| 145 |
+
className="text-warmgray-600 hover:text-teal-600 font-medium text-sm transition-colors flex items-center"
|
| 146 |
+
>
|
| 147 |
+
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 148 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
| 149 |
+
</svg>
|
| 150 |
+
Back to Home
|
| 151 |
+
</Link>
|
| 152 |
+
</div>
|
| 153 |
+
</div>
|
| 154 |
+
</header>
|
| 155 |
+
|
| 156 |
+
<div className="min-h-[calc(100vh-64px)] flex items-center justify-center py-12 px-4">
|
| 157 |
+
<div className="w-full max-w-md">
|
| 158 |
+
{/* Decorative elements */}
|
| 159 |
+
<div className="absolute top-20 left-10 w-64 h-64 bg-teal-200/20 rounded-full blur-3xl -z-10" />
|
| 160 |
+
<div className="absolute bottom-20 right-10 w-64 h-64 bg-lavender-200/20 rounded-full blur-3xl -z-10" />
|
| 161 |
+
|
| 162 |
+
<div className="bg-white rounded-3xl shadow-soft-lg p-8 md:p-10">
|
| 163 |
+
<div className="text-center mb-8">
|
| 164 |
+
<div className="mx-auto w-16 h-16 bg-gradient-to-br from-teal-400 to-teal-600 rounded-2xl flex items-center justify-center mb-4 shadow-soft">
|
| 165 |
+
<svg className="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 166 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z" />
|
| 167 |
+
</svg>
|
| 168 |
+
</div>
|
| 169 |
+
<h1 className="text-2xl font-bold text-warmgray-900 mb-2">Create Account</h1>
|
| 170 |
+
<p className="text-warmgray-600">Join our learning community today</p>
|
| 171 |
+
</div>
|
| 172 |
+
|
| 173 |
+
{/* Error Message */}
|
| 174 |
+
{error && (
|
| 175 |
+
<div className="mb-6 p-4 bg-coral-50 border border-coral-200 rounded-xl flex items-center text-coral-700">
|
| 176 |
+
<svg className="w-5 h-5 mr-3 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 177 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
| 178 |
+
</svg>
|
| 179 |
+
<span className="text-sm">{error}</span>
|
| 180 |
+
</div>
|
| 181 |
+
)}
|
| 182 |
+
|
| 183 |
+
<form onSubmit={handleSubmit} className="space-y-5">
|
| 184 |
+
<div>
|
| 185 |
+
<label htmlFor="name" className="block text-sm font-medium text-warmgray-700 mb-2">
|
| 186 |
+
Full Name
|
| 187 |
+
</label>
|
| 188 |
+
<input
|
| 189 |
+
type="text"
|
| 190 |
+
id="name"
|
| 191 |
+
name="name"
|
| 192 |
+
value={formData.name}
|
| 193 |
+
onChange={handleChange}
|
| 194 |
+
className="input-wellness"
|
| 195 |
+
placeholder="Enter your full name"
|
| 196 |
+
required
|
| 197 |
+
disabled={isLoading}
|
| 198 |
+
/>
|
| 199 |
+
</div>
|
| 200 |
+
|
| 201 |
+
<div>
|
| 202 |
+
<label htmlFor="email" className="block text-sm font-medium text-warmgray-700 mb-2">
|
| 203 |
+
Email Address
|
| 204 |
+
</label>
|
| 205 |
+
<input
|
| 206 |
+
type="email"
|
| 207 |
+
id="email"
|
| 208 |
+
name="email"
|
| 209 |
+
value={formData.email}
|
| 210 |
+
onChange={handleChange}
|
| 211 |
+
className="input-wellness"
|
| 212 |
+
placeholder="you@example.com"
|
| 213 |
+
required
|
| 214 |
+
disabled={isLoading}
|
| 215 |
+
/>
|
| 216 |
+
</div>
|
| 217 |
+
|
| 218 |
+
<div>
|
| 219 |
+
<label htmlFor="password" className="block text-sm font-medium text-warmgray-700 mb-2">
|
| 220 |
+
Password
|
| 221 |
+
</label>
|
| 222 |
+
<input
|
| 223 |
+
type="password"
|
| 224 |
+
id="password"
|
| 225 |
+
name="password"
|
| 226 |
+
value={formData.password}
|
| 227 |
+
onChange={handleChange}
|
| 228 |
+
className="input-wellness"
|
| 229 |
+
placeholder="At least 6 characters"
|
| 230 |
+
required
|
| 231 |
+
disabled={isLoading}
|
| 232 |
+
/>
|
| 233 |
+
</div>
|
| 234 |
+
|
| 235 |
+
<div>
|
| 236 |
+
<label htmlFor="confirmPassword" className="block text-sm font-medium text-warmgray-700 mb-2">
|
| 237 |
+
Confirm Password
|
| 238 |
+
</label>
|
| 239 |
+
<input
|
| 240 |
+
type="password"
|
| 241 |
+
id="confirmPassword"
|
| 242 |
+
name="confirmPassword"
|
| 243 |
+
value={formData.confirmPassword}
|
| 244 |
+
onChange={handleChange}
|
| 245 |
+
className="input-wellness"
|
| 246 |
+
placeholder="Confirm your password"
|
| 247 |
+
required
|
| 248 |
+
disabled={isLoading}
|
| 249 |
+
/>
|
| 250 |
+
</div>
|
| 251 |
+
|
| 252 |
+
{/* Role Selection */}
|
| 253 |
+
<div>
|
| 254 |
+
<label className="block text-sm font-medium text-warmgray-700 mb-3">
|
| 255 |
+
I am a
|
| 256 |
+
</label>
|
| 257 |
+
<div className="flex gap-3 p-1.5 bg-warmgray-100 rounded-full">
|
| 258 |
+
<button
|
| 259 |
+
type="button"
|
| 260 |
+
onClick={() => handleRoleChange(true)}
|
| 261 |
+
disabled={isLoading}
|
| 262 |
+
className={`flex-1 px-4 py-2.5 rounded-full text-sm font-medium transition-all duration-300 ${
|
| 263 |
+
isStudent
|
| 264 |
+
? 'bg-teal-600 text-white shadow-soft'
|
| 265 |
+
: 'text-warmgray-600 hover:text-warmgray-900'
|
| 266 |
+
}`}
|
| 267 |
+
>
|
| 268 |
+
<span className="flex items-center justify-center">
|
| 269 |
+
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 270 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
|
| 271 |
+
</svg>
|
| 272 |
+
Student
|
| 273 |
+
</span>
|
| 274 |
+
</button>
|
| 275 |
+
<button
|
| 276 |
+
type="button"
|
| 277 |
+
onClick={() => handleRoleChange(false)}
|
| 278 |
+
disabled={isLoading}
|
| 279 |
+
className={`flex-1 px-4 py-2.5 rounded-full text-sm font-medium transition-all duration-300 ${
|
| 280 |
+
!isStudent
|
| 281 |
+
? 'bg-lavender-600 text-white shadow-soft'
|
| 282 |
+
: 'text-warmgray-600 hover:text-warmgray-900'
|
| 283 |
+
}`}
|
| 284 |
+
>
|
| 285 |
+
<span className="flex items-center justify-center">
|
| 286 |
+
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 287 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
|
| 288 |
+
</svg>
|
| 289 |
+
Teacher
|
| 290 |
+
</span>
|
| 291 |
+
</button>
|
| 292 |
+
</div>
|
| 293 |
+
</div>
|
| 294 |
+
|
| 295 |
+
<button
|
| 296 |
+
type="submit"
|
| 297 |
+
disabled={isLoading}
|
| 298 |
+
className={`w-full py-3 rounded-xl font-medium transition-all duration-300 flex items-center justify-center ${
|
| 299 |
+
isStudent
|
| 300 |
+
? 'bg-teal-600 hover:bg-teal-700 text-white'
|
| 301 |
+
: 'bg-lavender-600 hover:bg-lavender-700 text-white'
|
| 302 |
+
} ${isLoading ? 'opacity-70 cursor-not-allowed' : ''}`}
|
| 303 |
+
>
|
| 304 |
+
{isLoading ? (
|
| 305 |
+
<>
|
| 306 |
+
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white" fill="none" viewBox="0 0 24 24">
|
| 307 |
+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
| 308 |
+
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
|
| 309 |
+
</svg>
|
| 310 |
+
Creating Account...
|
| 311 |
+
</>
|
| 312 |
+
) : (
|
| 313 |
+
'Create Account'
|
| 314 |
+
)}
|
| 315 |
+
</button>
|
| 316 |
+
</form>
|
| 317 |
+
|
| 318 |
+
<div className="mt-8 text-center">
|
| 319 |
+
<p className="text-sm text-warmgray-600">
|
| 320 |
+
Already have an account?{' '}
|
| 321 |
+
<Link href="/" className="font-medium text-teal-600 hover:text-teal-700">
|
| 322 |
+
Sign in
|
| 323 |
+
</Link>
|
| 324 |
+
</p>
|
| 325 |
+
</div>
|
| 326 |
+
</div>
|
| 327 |
+
</div>
|
| 328 |
+
</div>
|
| 329 |
+
</div>
|
| 330 |
+
);
|
| 331 |
+
}
|
app/resources/page.tsx
ADDED
|
@@ -0,0 +1,1272 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import Link from 'next/link';
|
| 4 |
+
import { useState } from 'react';
|
| 5 |
+
|
| 6 |
+
type Guide = {
|
| 7 |
+
title: string;
|
| 8 |
+
description: string;
|
| 9 |
+
icon: string;
|
| 10 |
+
difficulty: string;
|
| 11 |
+
readTime: string;
|
| 12 |
+
content: string;
|
| 13 |
+
};
|
| 14 |
+
|
| 15 |
+
type Cheatsheet = {
|
| 16 |
+
title: string;
|
| 17 |
+
icon: string;
|
| 18 |
+
downloads: string;
|
| 19 |
+
content: string;
|
| 20 |
+
};
|
| 21 |
+
|
| 22 |
+
type Video = {
|
| 23 |
+
title: string;
|
| 24 |
+
duration: string;
|
| 25 |
+
views: string;
|
| 26 |
+
thumbnail: string;
|
| 27 |
+
youtubeId: string;
|
| 28 |
+
channel: string;
|
| 29 |
+
};
|
| 30 |
+
|
| 31 |
+
type Tool = {
|
| 32 |
+
name: string;
|
| 33 |
+
description: string;
|
| 34 |
+
url: string;
|
| 35 |
+
icon: string;
|
| 36 |
+
};
|
| 37 |
+
|
| 38 |
+
export default function ResourcesPage() {
|
| 39 |
+
const [activeTab, setActiveTab] = useState('guides');
|
| 40 |
+
const [selectedGuide, setSelectedGuide] = useState<Guide | null>(null);
|
| 41 |
+
const [selectedVideo, setSelectedVideo] = useState<Video | null>(null);
|
| 42 |
+
|
| 43 |
+
const guides: Guide[] = [
|
| 44 |
+
{
|
| 45 |
+
title: 'Python Installation Guide',
|
| 46 |
+
description: 'Step-by-step guide to install Python on Windows, Mac, and Linux.',
|
| 47 |
+
icon: '📥',
|
| 48 |
+
difficulty: 'Beginner',
|
| 49 |
+
readTime: '5 min',
|
| 50 |
+
content: `# Python Installation Guide
|
| 51 |
+
|
| 52 |
+
## Windows Installation
|
| 53 |
+
|
| 54 |
+
1. **Download Python**: Visit [python.org](https://python.org) and download the latest version
|
| 55 |
+
2. **Run Installer**: Double-click the downloaded file
|
| 56 |
+
3. **Important**: Check "Add Python to PATH" before clicking Install
|
| 57 |
+
4. **Verify**: Open Command Prompt and type \`python --version\`
|
| 58 |
+
|
| 59 |
+
## Mac Installation
|
| 60 |
+
|
| 61 |
+
1. **Using Homebrew** (Recommended):
|
| 62 |
+
\`\`\`bash
|
| 63 |
+
brew install python
|
| 64 |
+
\`\`\`
|
| 65 |
+
|
| 66 |
+
2. **Or download from python.org**
|
| 67 |
+
|
| 68 |
+
3. **Verify**: Open Terminal and type \`python3 --version\`
|
| 69 |
+
|
| 70 |
+
## Linux Installation
|
| 71 |
+
|
| 72 |
+
Most Linux distributions come with Python pre-installed.
|
| 73 |
+
|
| 74 |
+
\`\`\`bash
|
| 75 |
+
# Ubuntu/Debian
|
| 76 |
+
sudo apt update
|
| 77 |
+
sudo apt install python3 python3-pip
|
| 78 |
+
|
| 79 |
+
# Fedora
|
| 80 |
+
sudo dnf install python3 python3-pip
|
| 81 |
+
|
| 82 |
+
# Verify installation
|
| 83 |
+
python3 --version
|
| 84 |
+
\`\`\`
|
| 85 |
+
|
| 86 |
+
## Next Steps
|
| 87 |
+
|
| 88 |
+
After installation, you can:
|
| 89 |
+
- Run Python scripts: \`python script.py\`
|
| 90 |
+
- Use the interactive shell: \`python\`
|
| 91 |
+
- Install packages: \`pip install package_name\``,
|
| 92 |
+
},
|
| 93 |
+
{
|
| 94 |
+
title: 'Setting Up VS Code for Python',
|
| 95 |
+
description: 'Configure VS Code with the best extensions for Python development.',
|
| 96 |
+
icon: '💻',
|
| 97 |
+
difficulty: 'Beginner',
|
| 98 |
+
readTime: '10 min',
|
| 99 |
+
content: `# Setting Up VS Code for Python
|
| 100 |
+
|
| 101 |
+
## Step 1: Install VS Code
|
| 102 |
+
|
| 103 |
+
Download from [code.visualstudio.com](https://code.visualstudio.com)
|
| 104 |
+
|
| 105 |
+
## Step 2: Essential Extensions
|
| 106 |
+
|
| 107 |
+
### Must-Have Extensions:
|
| 108 |
+
|
| 109 |
+
1. **Python** (Microsoft)
|
| 110 |
+
- IntelliSense, linting, debugging
|
| 111 |
+
- Search "Python" in Extensions
|
| 112 |
+
|
| 113 |
+
2. **Pylance**
|
| 114 |
+
- Fast, feature-rich language support
|
| 115 |
+
- Type checking and auto-imports
|
| 116 |
+
|
| 117 |
+
3. **Python Indent**
|
| 118 |
+
- Correct indentation on Enter
|
| 119 |
+
|
| 120 |
+
### Recommended Extensions:
|
| 121 |
+
|
| 122 |
+
4. **autoDocstring**
|
| 123 |
+
- Generate docstrings automatically
|
| 124 |
+
|
| 125 |
+
5. **Python Test Explorer**
|
| 126 |
+
- Run and debug tests easily
|
| 127 |
+
|
| 128 |
+
## Step 3: Configure Settings
|
| 129 |
+
|
| 130 |
+
Open Settings (Ctrl+,) and configure:
|
| 131 |
+
|
| 132 |
+
\`\`\`json
|
| 133 |
+
{
|
| 134 |
+
"python.linting.enabled": true,
|
| 135 |
+
"python.linting.pylintEnabled": true,
|
| 136 |
+
"python.formatting.provider": "black",
|
| 137 |
+
"editor.formatOnSave": true
|
| 138 |
+
}
|
| 139 |
+
\`\`\`
|
| 140 |
+
|
| 141 |
+
## Step 4: Select Python Interpreter
|
| 142 |
+
|
| 143 |
+
1. Press Ctrl+Shift+P
|
| 144 |
+
2. Type "Python: Select Interpreter"
|
| 145 |
+
3. Choose your Python installation
|
| 146 |
+
|
| 147 |
+
## Pro Tips
|
| 148 |
+
|
| 149 |
+
- Use \`Ctrl+Shift+P\` for command palette
|
| 150 |
+
- \`F5\` to run/debug
|
| 151 |
+
- \`Ctrl+\`\` to open terminal`,
|
| 152 |
+
},
|
| 153 |
+
{
|
| 154 |
+
title: 'Virtual Environments Explained',
|
| 155 |
+
description: 'Learn why and how to use virtual environments in Python projects.',
|
| 156 |
+
icon: '📦',
|
| 157 |
+
difficulty: 'Intermediate',
|
| 158 |
+
readTime: '8 min',
|
| 159 |
+
content: `# Virtual Environments Explained
|
| 160 |
+
|
| 161 |
+
## Why Use Virtual Environments?
|
| 162 |
+
|
| 163 |
+
- **Isolation**: Each project has its own dependencies
|
| 164 |
+
- **Version Control**: Different projects can use different package versions
|
| 165 |
+
- **Clean System**: Don't pollute global Python installation
|
| 166 |
+
- **Reproducibility**: Share exact dependencies with others
|
| 167 |
+
|
| 168 |
+
## Creating Virtual Environments
|
| 169 |
+
|
| 170 |
+
### Using venv (Built-in)
|
| 171 |
+
|
| 172 |
+
\`\`\`bash
|
| 173 |
+
# Create virtual environment
|
| 174 |
+
python -m venv myenv
|
| 175 |
+
|
| 176 |
+
# Activate (Windows)
|
| 177 |
+
myenv\\Scripts\\activate
|
| 178 |
+
|
| 179 |
+
# Activate (Mac/Linux)
|
| 180 |
+
source myenv/bin/activate
|
| 181 |
+
|
| 182 |
+
# Deactivate
|
| 183 |
+
deactivate
|
| 184 |
+
\`\`\`
|
| 185 |
+
|
| 186 |
+
### Using conda
|
| 187 |
+
|
| 188 |
+
\`\`\`bash
|
| 189 |
+
# Create environment
|
| 190 |
+
conda create --name myenv python=3.11
|
| 191 |
+
|
| 192 |
+
# Activate
|
| 193 |
+
conda activate myenv
|
| 194 |
+
|
| 195 |
+
# Deactivate
|
| 196 |
+
conda deactivate
|
| 197 |
+
\`\`\`
|
| 198 |
+
|
| 199 |
+
## Managing Dependencies
|
| 200 |
+
|
| 201 |
+
\`\`\`bash
|
| 202 |
+
# Install packages
|
| 203 |
+
pip install requests pandas
|
| 204 |
+
|
| 205 |
+
# Save dependencies
|
| 206 |
+
pip freeze > requirements.txt
|
| 207 |
+
|
| 208 |
+
# Install from requirements
|
| 209 |
+
pip install -r requirements.txt
|
| 210 |
+
\`\`\`
|
| 211 |
+
|
| 212 |
+
## Best Practices
|
| 213 |
+
|
| 214 |
+
1. Always use virtual environments for projects
|
| 215 |
+
2. Keep requirements.txt updated
|
| 216 |
+
3. Don't commit venv folder to git
|
| 217 |
+
4. Add venv to .gitignore`,
|
| 218 |
+
},
|
| 219 |
+
{
|
| 220 |
+
title: 'Git for Python Developers',
|
| 221 |
+
description: 'Version control basics every Python developer should know.',
|
| 222 |
+
icon: '🔀',
|
| 223 |
+
difficulty: 'Intermediate',
|
| 224 |
+
readTime: '15 min',
|
| 225 |
+
content: `# Git for Python Developers
|
| 226 |
+
|
| 227 |
+
## Getting Started
|
| 228 |
+
|
| 229 |
+
\`\`\`bash
|
| 230 |
+
# Configure Git
|
| 231 |
+
git config --global user.name "Your Name"
|
| 232 |
+
git config --global user.email "you@example.com"
|
| 233 |
+
|
| 234 |
+
# Initialize repository
|
| 235 |
+
git init
|
| 236 |
+
|
| 237 |
+
# Clone existing repo
|
| 238 |
+
git clone https://github.com/user/repo.git
|
| 239 |
+
\`\`\`
|
| 240 |
+
|
| 241 |
+
## Essential Commands
|
| 242 |
+
|
| 243 |
+
\`\`\`bash
|
| 244 |
+
# Check status
|
| 245 |
+
git status
|
| 246 |
+
|
| 247 |
+
# Stage changes
|
| 248 |
+
git add filename.py
|
| 249 |
+
git add . # All files
|
| 250 |
+
|
| 251 |
+
# Commit changes
|
| 252 |
+
git commit -m "Add feature X"
|
| 253 |
+
|
| 254 |
+
# View history
|
| 255 |
+
git log --oneline
|
| 256 |
+
\`\`\`
|
| 257 |
+
|
| 258 |
+
## Branching
|
| 259 |
+
|
| 260 |
+
\`\`\`bash
|
| 261 |
+
# Create branch
|
| 262 |
+
git branch feature-name
|
| 263 |
+
|
| 264 |
+
# Switch branch
|
| 265 |
+
git checkout feature-name
|
| 266 |
+
|
| 267 |
+
# Create and switch
|
| 268 |
+
git checkout -b feature-name
|
| 269 |
+
|
| 270 |
+
# Merge branch
|
| 271 |
+
git merge feature-name
|
| 272 |
+
\`\`\`
|
| 273 |
+
|
| 274 |
+
## Python .gitignore
|
| 275 |
+
|
| 276 |
+
\`\`\`
|
| 277 |
+
# Virtual environments
|
| 278 |
+
venv/
|
| 279 |
+
env/
|
| 280 |
+
.env
|
| 281 |
+
|
| 282 |
+
# Python cache
|
| 283 |
+
__pycache__/
|
| 284 |
+
*.pyc
|
| 285 |
+
*.pyo
|
| 286 |
+
|
| 287 |
+
# IDE
|
| 288 |
+
.vscode/
|
| 289 |
+
.idea/
|
| 290 |
+
|
| 291 |
+
# Distribution
|
| 292 |
+
dist/
|
| 293 |
+
build/
|
| 294 |
+
*.egg-info/
|
| 295 |
+
\`\`\`
|
| 296 |
+
|
| 297 |
+
## Remote Repositories
|
| 298 |
+
|
| 299 |
+
\`\`\`bash
|
| 300 |
+
# Add remote
|
| 301 |
+
git remote add origin URL
|
| 302 |
+
|
| 303 |
+
# Push changes
|
| 304 |
+
git push -u origin main
|
| 305 |
+
|
| 306 |
+
# Pull changes
|
| 307 |
+
git pull origin main
|
| 308 |
+
\`\`\``,
|
| 309 |
+
},
|
| 310 |
+
{
|
| 311 |
+
title: 'Debugging Techniques',
|
| 312 |
+
description: 'Master the art of finding and fixing bugs in your Python code.',
|
| 313 |
+
icon: '🐛',
|
| 314 |
+
difficulty: 'Intermediate',
|
| 315 |
+
readTime: '12 min',
|
| 316 |
+
content: `# Python Debugging Techniques
|
| 317 |
+
|
| 318 |
+
## 1. Print Debugging
|
| 319 |
+
|
| 320 |
+
\`\`\`python
|
| 321 |
+
def calculate(x, y):
|
| 322 |
+
print(f"DEBUG: x={x}, y={y}") # Add debug prints
|
| 323 |
+
result = x * y
|
| 324 |
+
print(f"DEBUG: result={result}")
|
| 325 |
+
return result
|
| 326 |
+
\`\`\`
|
| 327 |
+
|
| 328 |
+
## 2. Using the Debugger (pdb)
|
| 329 |
+
|
| 330 |
+
\`\`\`python
|
| 331 |
+
import pdb
|
| 332 |
+
|
| 333 |
+
def buggy_function():
|
| 334 |
+
x = 10
|
| 335 |
+
pdb.set_trace() # Breakpoint
|
| 336 |
+
y = x / 0 # Bug here
|
| 337 |
+
return y
|
| 338 |
+
\`\`\`
|
| 339 |
+
|
| 340 |
+
### pdb Commands:
|
| 341 |
+
- \`n\` - Next line
|
| 342 |
+
- \`s\` - Step into function
|
| 343 |
+
- \`c\` - Continue
|
| 344 |
+
- \`p variable\` - Print variable
|
| 345 |
+
- \`q\` - Quit
|
| 346 |
+
|
| 347 |
+
## 3. VS Code Debugging
|
| 348 |
+
|
| 349 |
+
1. Set breakpoints (click left of line number)
|
| 350 |
+
2. Press F5 to start debugging
|
| 351 |
+
3. Use debug controls (step over, step into, continue)
|
| 352 |
+
4. Watch variables in the sidebar
|
| 353 |
+
|
| 354 |
+
## 4. Logging
|
| 355 |
+
|
| 356 |
+
\`\`\`python
|
| 357 |
+
import logging
|
| 358 |
+
|
| 359 |
+
logging.basicConfig(level=logging.DEBUG)
|
| 360 |
+
logger = logging.getLogger(__name__)
|
| 361 |
+
|
| 362 |
+
def process_data(data):
|
| 363 |
+
logger.debug(f"Processing: {data}")
|
| 364 |
+
logger.info("Process started")
|
| 365 |
+
logger.warning("Something might be wrong")
|
| 366 |
+
logger.error("An error occurred")
|
| 367 |
+
\`\`\`
|
| 368 |
+
|
| 369 |
+
## 5. Common Bug Patterns
|
| 370 |
+
|
| 371 |
+
### Off-by-one errors
|
| 372 |
+
\`\`\`python
|
| 373 |
+
# Wrong
|
| 374 |
+
for i in range(len(items) + 1): # IndexError
|
| 375 |
+
|
| 376 |
+
# Correct
|
| 377 |
+
for i in range(len(items)):
|
| 378 |
+
\`\`\`
|
| 379 |
+
|
| 380 |
+
### Mutable default arguments
|
| 381 |
+
\`\`\`python
|
| 382 |
+
# Wrong
|
| 383 |
+
def add_item(item, items=[]):
|
| 384 |
+
items.append(item)
|
| 385 |
+
return items
|
| 386 |
+
|
| 387 |
+
# Correct
|
| 388 |
+
def add_item(item, items=None):
|
| 389 |
+
if items is None:
|
| 390 |
+
items = []
|
| 391 |
+
items.append(item)
|
| 392 |
+
return items
|
| 393 |
+
\`\`\``,
|
| 394 |
+
},
|
| 395 |
+
{
|
| 396 |
+
title: 'Python Best Practices',
|
| 397 |
+
description: 'Write clean, maintainable, and Pythonic code.',
|
| 398 |
+
icon: '✨',
|
| 399 |
+
difficulty: 'Advanced',
|
| 400 |
+
readTime: '20 min',
|
| 401 |
+
content: `# Python Best Practices
|
| 402 |
+
|
| 403 |
+
## 1. Follow PEP 8 Style Guide
|
| 404 |
+
|
| 405 |
+
\`\`\`python
|
| 406 |
+
# Good
|
| 407 |
+
def calculate_total(items):
|
| 408 |
+
total = sum(item.price for item in items)
|
| 409 |
+
return total
|
| 410 |
+
|
| 411 |
+
# Bad
|
| 412 |
+
def CalculateTotal(Items):
|
| 413 |
+
Total=sum(Item.price for Item in Items)
|
| 414 |
+
return Total
|
| 415 |
+
\`\`\`
|
| 416 |
+
|
| 417 |
+
## 2. Use Type Hints
|
| 418 |
+
|
| 419 |
+
\`\`\`python
|
| 420 |
+
from typing import List, Optional
|
| 421 |
+
|
| 422 |
+
def greet(name: str) -> str:
|
| 423 |
+
return f"Hello, {name}!"
|
| 424 |
+
|
| 425 |
+
def find_user(user_id: int) -> Optional[dict]:
|
| 426 |
+
# Returns user dict or None
|
| 427 |
+
pass
|
| 428 |
+
\`\`\`
|
| 429 |
+
|
| 430 |
+
## 3. Write Docstrings
|
| 431 |
+
|
| 432 |
+
\`\`\`python
|
| 433 |
+
def calculate_average(numbers: List[float]) -> float:
|
| 434 |
+
"""Calculate the average of a list of numbers.
|
| 435 |
+
|
| 436 |
+
Args:
|
| 437 |
+
numbers: A list of numbers to average.
|
| 438 |
+
|
| 439 |
+
Returns:
|
| 440 |
+
The arithmetic mean of the numbers.
|
| 441 |
+
|
| 442 |
+
Raises:
|
| 443 |
+
ValueError: If the list is empty.
|
| 444 |
+
"""
|
| 445 |
+
if not numbers:
|
| 446 |
+
raise ValueError("Cannot average empty list")
|
| 447 |
+
return sum(numbers) / len(numbers)
|
| 448 |
+
\`\`\`
|
| 449 |
+
|
| 450 |
+
## 4. Use Context Managers
|
| 451 |
+
|
| 452 |
+
\`\`\`python
|
| 453 |
+
# Good
|
| 454 |
+
with open('file.txt', 'r') as f:
|
| 455 |
+
content = f.read()
|
| 456 |
+
|
| 457 |
+
# Bad
|
| 458 |
+
f = open('file.txt', 'r')
|
| 459 |
+
content = f.read()
|
| 460 |
+
f.close()
|
| 461 |
+
\`\`\`
|
| 462 |
+
|
| 463 |
+
## 5. List Comprehensions
|
| 464 |
+
|
| 465 |
+
\`\`\`python
|
| 466 |
+
# Pythonic
|
| 467 |
+
squares = [x**2 for x in range(10)]
|
| 468 |
+
evens = [x for x in numbers if x % 2 == 0]
|
| 469 |
+
|
| 470 |
+
# Less Pythonic
|
| 471 |
+
squares = []
|
| 472 |
+
for x in range(10):
|
| 473 |
+
squares.append(x**2)
|
| 474 |
+
\`\`\`
|
| 475 |
+
|
| 476 |
+
## 6. Use f-strings
|
| 477 |
+
|
| 478 |
+
\`\`\`python
|
| 479 |
+
name = "Alice"
|
| 480 |
+
age = 30
|
| 481 |
+
|
| 482 |
+
# Good
|
| 483 |
+
message = f"{name} is {age} years old"
|
| 484 |
+
|
| 485 |
+
# Avoid
|
| 486 |
+
message = name + " is " + str(age) + " years old"
|
| 487 |
+
\`\`\`
|
| 488 |
+
|
| 489 |
+
## 7. Handle Exceptions Properly
|
| 490 |
+
|
| 491 |
+
\`\`\`python
|
| 492 |
+
try:
|
| 493 |
+
result = risky_operation()
|
| 494 |
+
except SpecificError as e:
|
| 495 |
+
logger.error(f"Operation failed: {e}")
|
| 496 |
+
raise
|
| 497 |
+
except Exception as e:
|
| 498 |
+
logger.exception("Unexpected error")
|
| 499 |
+
raise
|
| 500 |
+
\`\`\``,
|
| 501 |
+
},
|
| 502 |
+
];
|
| 503 |
+
|
| 504 |
+
const cheatsheets: Cheatsheet[] = [
|
| 505 |
+
{
|
| 506 |
+
title: 'Python Syntax Cheatsheet',
|
| 507 |
+
icon: '📝',
|
| 508 |
+
downloads: '12.5k',
|
| 509 |
+
content: `PYTHON SYNTAX CHEATSHEET
|
| 510 |
+
========================
|
| 511 |
+
|
| 512 |
+
VARIABLES & DATA TYPES
|
| 513 |
+
----------------------
|
| 514 |
+
x = 10 # Integer
|
| 515 |
+
y = 3.14 # Float
|
| 516 |
+
name = "Python" # String
|
| 517 |
+
is_true = True # Boolean
|
| 518 |
+
items = [1,2,3] # List
|
| 519 |
+
data = {"a": 1} # Dictionary
|
| 520 |
+
|
| 521 |
+
OPERATORS
|
| 522 |
+
---------
|
| 523 |
+
+ Addition - Subtraction
|
| 524 |
+
* Multiplication / Division
|
| 525 |
+
// Floor Division % Modulus
|
| 526 |
+
** Exponentiation
|
| 527 |
+
|
| 528 |
+
COMPARISON
|
| 529 |
+
----------
|
| 530 |
+
== Equal != Not Equal
|
| 531 |
+
> Greater < Less
|
| 532 |
+
>= Greater/Equal <= Less/Equal
|
| 533 |
+
|
| 534 |
+
CONTROL FLOW
|
| 535 |
+
------------
|
| 536 |
+
if condition:
|
| 537 |
+
# code
|
| 538 |
+
elif other:
|
| 539 |
+
# code
|
| 540 |
+
else:
|
| 541 |
+
# code
|
| 542 |
+
|
| 543 |
+
LOOPS
|
| 544 |
+
-----
|
| 545 |
+
for item in items:
|
| 546 |
+
print(item)
|
| 547 |
+
|
| 548 |
+
while condition:
|
| 549 |
+
# code
|
| 550 |
+
|
| 551 |
+
FUNCTIONS
|
| 552 |
+
---------
|
| 553 |
+
def greet(name):
|
| 554 |
+
return f"Hello, {name}"
|
| 555 |
+
|
| 556 |
+
CLASSES
|
| 557 |
+
-------
|
| 558 |
+
class Person:
|
| 559 |
+
def __init__(self, name):
|
| 560 |
+
self.name = name`,
|
| 561 |
+
},
|
| 562 |
+
{
|
| 563 |
+
title: 'String Methods Reference',
|
| 564 |
+
icon: '🔤',
|
| 565 |
+
downloads: '8.2k',
|
| 566 |
+
content: `STRING METHODS REFERENCE
|
| 567 |
+
========================
|
| 568 |
+
|
| 569 |
+
CASE METHODS
|
| 570 |
+
------------
|
| 571 |
+
s.upper() → "HELLO"
|
| 572 |
+
s.lower() → "hello"
|
| 573 |
+
s.capitalize() → "Hello"
|
| 574 |
+
s.title() → "Hello World"
|
| 575 |
+
s.swapcase() → "hELLO"
|
| 576 |
+
|
| 577 |
+
SEARCH METHODS
|
| 578 |
+
--------------
|
| 579 |
+
s.find("x") → index or -1
|
| 580 |
+
s.index("x") → index or error
|
| 581 |
+
s.count("x") → occurrences
|
| 582 |
+
s.startswith("x") → True/False
|
| 583 |
+
s.endswith("x") → True/False
|
| 584 |
+
|
| 585 |
+
MODIFICATION
|
| 586 |
+
------------
|
| 587 |
+
s.strip() → remove whitespace
|
| 588 |
+
s.lstrip() → remove left whitespace
|
| 589 |
+
s.rstrip() → remove right whitespace
|
| 590 |
+
s.replace("a","b") → replace all
|
| 591 |
+
|
| 592 |
+
SPLIT & JOIN
|
| 593 |
+
------------
|
| 594 |
+
s.split(",") → ["a","b","c"]
|
| 595 |
+
",".join(list) → "a,b,c"
|
| 596 |
+
s.splitlines() → split by newline
|
| 597 |
+
|
| 598 |
+
VALIDATION
|
| 599 |
+
----------
|
| 600 |
+
s.isalpha() → only letters?
|
| 601 |
+
s.isdigit() → only digits?
|
| 602 |
+
s.isalnum() → letters/digits?
|
| 603 |
+
s.isspace() → only whitespace?
|
| 604 |
+
s.isupper() → all uppercase?
|
| 605 |
+
s.islower() → all lowercase?
|
| 606 |
+
|
| 607 |
+
FORMATTING
|
| 608 |
+
----------
|
| 609 |
+
f"{name}" → f-string
|
| 610 |
+
"{} {}".format() → format method
|
| 611 |
+
s.center(20) → center in width
|
| 612 |
+
s.ljust(20) → left justify
|
| 613 |
+
s.rjust(20) → right justify
|
| 614 |
+
s.zfill(5) → pad with zeros`,
|
| 615 |
+
},
|
| 616 |
+
{
|
| 617 |
+
title: 'List Comprehensions',
|
| 618 |
+
icon: '📋',
|
| 619 |
+
downloads: '9.1k',
|
| 620 |
+
content: `LIST COMPREHENSIONS CHEATSHEET
|
| 621 |
+
==============================
|
| 622 |
+
|
| 623 |
+
BASIC SYNTAX
|
| 624 |
+
------------
|
| 625 |
+
[expression for item in iterable]
|
| 626 |
+
|
| 627 |
+
EXAMPLES
|
| 628 |
+
--------
|
| 629 |
+
# Squares
|
| 630 |
+
[x**2 for x in range(10)]
|
| 631 |
+
→ [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
|
| 632 |
+
|
| 633 |
+
# With condition (filter)
|
| 634 |
+
[x for x in range(10) if x % 2 == 0]
|
| 635 |
+
→ [0, 2, 4, 6, 8]
|
| 636 |
+
|
| 637 |
+
# Transform strings
|
| 638 |
+
[s.upper() for s in ["hello", "world"]]
|
| 639 |
+
→ ["HELLO", "WORLD"]
|
| 640 |
+
|
| 641 |
+
# Conditional expression
|
| 642 |
+
["even" if x%2==0 else "odd" for x in range(5)]
|
| 643 |
+
→ ["even", "odd", "even", "odd", "even"]
|
| 644 |
+
|
| 645 |
+
NESTED LOOPS
|
| 646 |
+
------------
|
| 647 |
+
[(x,y) for x in [1,2] for y in [3,4]]
|
| 648 |
+
→ [(1,3), (1,4), (2,3), (2,4)]
|
| 649 |
+
|
| 650 |
+
# Flatten nested list
|
| 651 |
+
[[1,2], [3,4]] → [1, 2, 3, 4]
|
| 652 |
+
[x for sublist in nested for x in sublist]
|
| 653 |
+
|
| 654 |
+
DICT COMPREHENSION
|
| 655 |
+
------------------
|
| 656 |
+
{k: v for k, v in items}
|
| 657 |
+
{x: x**2 for x in range(5)}
|
| 658 |
+
→ {0:0, 1:1, 2:4, 3:9, 4:16}
|
| 659 |
+
|
| 660 |
+
SET COMPREHENSION
|
| 661 |
+
-----------------
|
| 662 |
+
{x**2 for x in range(5)}
|
| 663 |
+
→ {0, 1, 4, 9, 16}
|
| 664 |
+
|
| 665 |
+
GENERATOR EXPRESSION
|
| 666 |
+
--------------------
|
| 667 |
+
(x**2 for x in range(10))
|
| 668 |
+
# Memory efficient, lazy evaluation`,
|
| 669 |
+
},
|
| 670 |
+
{
|
| 671 |
+
title: 'Dictionary Operations',
|
| 672 |
+
icon: '📖',
|
| 673 |
+
downloads: '7.8k',
|
| 674 |
+
content: `DICTIONARY OPERATIONS
|
| 675 |
+
=====================
|
| 676 |
+
|
| 677 |
+
CREATING DICTIONARIES
|
| 678 |
+
---------------------
|
| 679 |
+
d = {}
|
| 680 |
+
d = dict()
|
| 681 |
+
d = {"name": "Alice", "age": 30}
|
| 682 |
+
d = dict(name="Alice", age=30)
|
| 683 |
+
|
| 684 |
+
ACCESSING VALUES
|
| 685 |
+
----------------
|
| 686 |
+
d["key"] # KeyError if missing
|
| 687 |
+
d.get("key") # None if missing
|
| 688 |
+
d.get("key", default) # default if missing
|
| 689 |
+
|
| 690 |
+
ADDING/UPDATING
|
| 691 |
+
---------------
|
| 692 |
+
d["key"] = value
|
| 693 |
+
d.update({"a": 1, "b": 2})
|
| 694 |
+
d |= {"new": "data"} # Python 3.9+
|
| 695 |
+
|
| 696 |
+
REMOVING
|
| 697 |
+
--------
|
| 698 |
+
del d["key"]
|
| 699 |
+
d.pop("key") # returns value
|
| 700 |
+
d.pop("key", None) # no error if missing
|
| 701 |
+
d.popitem() # remove last item
|
| 702 |
+
d.clear() # remove all
|
| 703 |
+
|
| 704 |
+
ITERATION
|
| 705 |
+
---------
|
| 706 |
+
for key in d:
|
| 707 |
+
for key in d.keys():
|
| 708 |
+
for value in d.values():
|
| 709 |
+
for key, value in d.items():
|
| 710 |
+
|
| 711 |
+
USEFUL METHODS
|
| 712 |
+
--------------
|
| 713 |
+
d.keys() # all keys
|
| 714 |
+
d.values() # all values
|
| 715 |
+
d.items() # key-value pairs
|
| 716 |
+
len(d) # number of items
|
| 717 |
+
"key" in d # check existence
|
| 718 |
+
d.copy() # shallow copy
|
| 719 |
+
|
| 720 |
+
MERGING (Python 3.9+)
|
| 721 |
+
---------------------
|
| 722 |
+
merged = d1 | d2
|
| 723 |
+
d1 |= d2 # in-place
|
| 724 |
+
|
| 725 |
+
DEFAULT VALUES
|
| 726 |
+
--------------
|
| 727 |
+
from collections import defaultdict
|
| 728 |
+
d = defaultdict(list)
|
| 729 |
+
d["key"].append(value)`,
|
| 730 |
+
},
|
| 731 |
+
{
|
| 732 |
+
title: 'File I/O Quick Reference',
|
| 733 |
+
icon: '📁',
|
| 734 |
+
downloads: '6.3k',
|
| 735 |
+
content: `FILE I/O QUICK REFERENCE
|
| 736 |
+
========================
|
| 737 |
+
|
| 738 |
+
OPENING FILES
|
| 739 |
+
-------------
|
| 740 |
+
f = open("file.txt", mode)
|
| 741 |
+
|
| 742 |
+
MODES
|
| 743 |
+
-----
|
| 744 |
+
"r" - Read (default)
|
| 745 |
+
"w" - Write (overwrites)
|
| 746 |
+
"a" - Append
|
| 747 |
+
"x" - Create (error if exists)
|
| 748 |
+
"b" - Binary mode
|
| 749 |
+
"t" - Text mode (default)
|
| 750 |
+
"+" - Read and write
|
| 751 |
+
|
| 752 |
+
CONTEXT MANAGER (Recommended)
|
| 753 |
+
-----------------------------
|
| 754 |
+
with open("file.txt", "r") as f:
|
| 755 |
+
content = f.read()
|
| 756 |
+
# File automatically closed
|
| 757 |
+
|
| 758 |
+
READING
|
| 759 |
+
-------
|
| 760 |
+
f.read() # entire file
|
| 761 |
+
f.read(n) # n characters
|
| 762 |
+
f.readline() # one line
|
| 763 |
+
f.readlines() # list of lines
|
| 764 |
+
|
| 765 |
+
# Iterate lines
|
| 766 |
+
for line in f:
|
| 767 |
+
print(line)
|
| 768 |
+
|
| 769 |
+
WRITING
|
| 770 |
+
-------
|
| 771 |
+
f.write("text")
|
| 772 |
+
f.writelines(list_of_strings)
|
| 773 |
+
|
| 774 |
+
PRACTICAL EXAMPLES
|
| 775 |
+
------------------
|
| 776 |
+
# Read entire file
|
| 777 |
+
with open("data.txt") as f:
|
| 778 |
+
content = f.read()
|
| 779 |
+
|
| 780 |
+
# Read lines
|
| 781 |
+
with open("data.txt") as f:
|
| 782 |
+
lines = f.readlines()
|
| 783 |
+
|
| 784 |
+
# Write to file
|
| 785 |
+
with open("output.txt", "w") as f:
|
| 786 |
+
f.write("Hello World")
|
| 787 |
+
|
| 788 |
+
# Append to file
|
| 789 |
+
with open("log.txt", "a") as f:
|
| 790 |
+
f.write("New log entry\\n")
|
| 791 |
+
|
| 792 |
+
JSON FILES
|
| 793 |
+
----------
|
| 794 |
+
import json
|
| 795 |
+
|
| 796 |
+
# Read JSON
|
| 797 |
+
with open("data.json") as f:
|
| 798 |
+
data = json.load(f)
|
| 799 |
+
|
| 800 |
+
# Write JSON
|
| 801 |
+
with open("data.json", "w") as f:
|
| 802 |
+
json.dump(data, f, indent=2)`,
|
| 803 |
+
},
|
| 804 |
+
{
|
| 805 |
+
title: 'Exception Handling Guide',
|
| 806 |
+
icon: '⚠️',
|
| 807 |
+
downloads: '5.9k',
|
| 808 |
+
content: `EXCEPTION HANDLING GUIDE
|
| 809 |
+
========================
|
| 810 |
+
|
| 811 |
+
BASIC SYNTAX
|
| 812 |
+
------------
|
| 813 |
+
try:
|
| 814 |
+
# risky code
|
| 815 |
+
except ExceptionType:
|
| 816 |
+
# handle error
|
| 817 |
+
else:
|
| 818 |
+
# runs if no exception
|
| 819 |
+
finally:
|
| 820 |
+
# always runs
|
| 821 |
+
|
| 822 |
+
COMMON EXCEPTIONS
|
| 823 |
+
-----------------
|
| 824 |
+
ValueError - Wrong value type
|
| 825 |
+
TypeError - Wrong data type
|
| 826 |
+
KeyError - Dict key not found
|
| 827 |
+
IndexError - Index out of range
|
| 828 |
+
FileNotFoundError - File doesn't exist
|
| 829 |
+
ZeroDivisionError - Division by zero
|
| 830 |
+
AttributeError - Attribute not found
|
| 831 |
+
ImportError - Import failed
|
| 832 |
+
|
| 833 |
+
CATCHING EXCEPTIONS
|
| 834 |
+
-------------------
|
| 835 |
+
# Catch specific
|
| 836 |
+
try:
|
| 837 |
+
x = int("abc")
|
| 838 |
+
except ValueError:
|
| 839 |
+
print("Invalid number")
|
| 840 |
+
|
| 841 |
+
# Catch multiple
|
| 842 |
+
except (ValueError, TypeError):
|
| 843 |
+
print("Error occurred")
|
| 844 |
+
|
| 845 |
+
# Get exception info
|
| 846 |
+
except ValueError as e:
|
| 847 |
+
print(f"Error: {e}")
|
| 848 |
+
|
| 849 |
+
# Catch all (avoid when possible)
|
| 850 |
+
except Exception as e:
|
| 851 |
+
print(f"Unexpected: {e}")
|
| 852 |
+
|
| 853 |
+
RAISING EXCEPTIONS
|
| 854 |
+
------------------
|
| 855 |
+
raise ValueError("Invalid input")
|
| 856 |
+
raise TypeError("Expected string")
|
| 857 |
+
|
| 858 |
+
# Re-raise
|
| 859 |
+
except SomeError:
|
| 860 |
+
logger.error("Failed")
|
| 861 |
+
raise
|
| 862 |
+
|
| 863 |
+
CUSTOM EXCEPTIONS
|
| 864 |
+
-----------------
|
| 865 |
+
class CustomError(Exception):
|
| 866 |
+
def __init__(self, message):
|
| 867 |
+
self.message = message
|
| 868 |
+
super().__init__(message)
|
| 869 |
+
|
| 870 |
+
raise CustomError("Something wrong")
|
| 871 |
+
|
| 872 |
+
BEST PRACTICES
|
| 873 |
+
--------------
|
| 874 |
+
1. Catch specific exceptions
|
| 875 |
+
2. Don't use bare except:
|
| 876 |
+
3. Log exceptions properly
|
| 877 |
+
4. Clean up in finally
|
| 878 |
+
5. Fail fast, fail loud`,
|
| 879 |
+
},
|
| 880 |
+
];
|
| 881 |
+
|
| 882 |
+
const videos: Video[] = [
|
| 883 |
+
{
|
| 884 |
+
title: 'Python in 100 Seconds',
|
| 885 |
+
duration: '2:18',
|
| 886 |
+
views: '3.8M',
|
| 887 |
+
thumbnail: '🎬',
|
| 888 |
+
youtubeId: 'x7X9w_GIm1s',
|
| 889 |
+
channel: 'Fireship',
|
| 890 |
+
},
|
| 891 |
+
{
|
| 892 |
+
title: 'Python Tutorial for Beginners',
|
| 893 |
+
duration: '6:14:07',
|
| 894 |
+
views: '41M',
|
| 895 |
+
thumbnail: '🔄',
|
| 896 |
+
youtubeId: '_uQrJ0TkZlc',
|
| 897 |
+
channel: 'Programming with Mosh',
|
| 898 |
+
},
|
| 899 |
+
{
|
| 900 |
+
title: 'Python OOP Tutorial',
|
| 901 |
+
duration: '1:20:49',
|
| 902 |
+
views: '2.8M',
|
| 903 |
+
thumbnail: '🏗️',
|
| 904 |
+
youtubeId: 'ZDa-Z5JzLYM',
|
| 905 |
+
channel: 'Corey Schafer',
|
| 906 |
+
},
|
| 907 |
+
{
|
| 908 |
+
title: 'Python API Development',
|
| 909 |
+
duration: '19:03:32',
|
| 910 |
+
views: '1.2M',
|
| 911 |
+
thumbnail: '🌐',
|
| 912 |
+
youtubeId: '0sOvCWFmrtA',
|
| 913 |
+
channel: 'freeCodeCamp',
|
| 914 |
+
},
|
| 915 |
+
];
|
| 916 |
+
|
| 917 |
+
const tools: Tool[] = [
|
| 918 |
+
{
|
| 919 |
+
name: 'Python Official Docs',
|
| 920 |
+
description: 'The official Python documentation',
|
| 921 |
+
url: 'https://docs.python.org/3/',
|
| 922 |
+
icon: '📚',
|
| 923 |
+
},
|
| 924 |
+
{
|
| 925 |
+
name: 'PyPI',
|
| 926 |
+
description: 'Python Package Index - find libraries',
|
| 927 |
+
url: 'https://pypi.org/',
|
| 928 |
+
icon: '📦',
|
| 929 |
+
},
|
| 930 |
+
{
|
| 931 |
+
name: 'Replit',
|
| 932 |
+
description: 'Online Python IDE',
|
| 933 |
+
url: 'https://replit.com/languages/python3',
|
| 934 |
+
icon: '💻',
|
| 935 |
+
},
|
| 936 |
+
{
|
| 937 |
+
name: 'Stack Overflow',
|
| 938 |
+
description: 'Q&A for programmers',
|
| 939 |
+
url: 'https://stackoverflow.com/questions/tagged/python',
|
| 940 |
+
icon: '❓',
|
| 941 |
+
},
|
| 942 |
+
{
|
| 943 |
+
name: 'Real Python',
|
| 944 |
+
description: 'Python tutorials and articles',
|
| 945 |
+
url: 'https://realpython.com/',
|
| 946 |
+
icon: '🐍',
|
| 947 |
+
},
|
| 948 |
+
{
|
| 949 |
+
name: 'Python Tutor',
|
| 950 |
+
description: 'Visualize code execution',
|
| 951 |
+
url: 'https://pythontutor.com/',
|
| 952 |
+
icon: '👁️',
|
| 953 |
+
},
|
| 954 |
+
];
|
| 955 |
+
|
| 956 |
+
const downloadCheatsheet = (sheet: Cheatsheet) => {
|
| 957 |
+
const blob = new Blob([sheet.content], { type: 'text/plain' });
|
| 958 |
+
const url = URL.createObjectURL(blob);
|
| 959 |
+
const a = document.createElement('a');
|
| 960 |
+
a.href = url;
|
| 961 |
+
a.download = `${sheet.title.replace(/\s+/g, '_')}.txt`;
|
| 962 |
+
document.body.appendChild(a);
|
| 963 |
+
a.click();
|
| 964 |
+
document.body.removeChild(a);
|
| 965 |
+
URL.revokeObjectURL(url);
|
| 966 |
+
};
|
| 967 |
+
|
| 968 |
+
return (
|
| 969 |
+
<div className="min-h-screen bg-gradient-to-b from-cream-50 to-white">
|
| 970 |
+
{/* Header */}
|
| 971 |
+
<header className="bg-white/80 backdrop-blur-md border-b border-warmgray-100 sticky top-0 z-50">
|
| 972 |
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 973 |
+
<div className="flex items-center justify-between h-16">
|
| 974 |
+
<Link href="/" className="flex items-center space-x-3">
|
| 975 |
+
<div className="w-10 h-10 bg-gradient-to-br from-teal-500 to-teal-600 rounded-xl flex items-center justify-center shadow-soft">
|
| 976 |
+
<span className="text-white font-bold text-lg">L</span>
|
| 977 |
+
</div>
|
| 978 |
+
<span className="text-xl font-bold text-warmgray-900">LearnFlow</span>
|
| 979 |
+
</Link>
|
| 980 |
+
|
| 981 |
+
<nav className="hidden md:flex items-center space-x-1">
|
| 982 |
+
{[
|
| 983 |
+
{ name: 'Home', href: '/' },
|
| 984 |
+
{ name: 'Courses', href: '/courses' },
|
| 985 |
+
{ name: 'About', href: '/about' },
|
| 986 |
+
{ name: 'Resources', href: '/resources' },
|
| 987 |
+
{ name: 'Contact', href: '/contact' },
|
| 988 |
+
].map((item) => (
|
| 989 |
+
<Link
|
| 990 |
+
key={item.name}
|
| 991 |
+
href={item.href}
|
| 992 |
+
className={`px-4 py-2 rounded-full text-sm font-medium transition-all duration-200 ${
|
| 993 |
+
item.name === 'Resources'
|
| 994 |
+
? 'bg-teal-50 text-teal-700'
|
| 995 |
+
: 'text-warmgray-600 hover:text-teal-700 hover:bg-teal-50'
|
| 996 |
+
}`}
|
| 997 |
+
>
|
| 998 |
+
{item.name}
|
| 999 |
+
</Link>
|
| 1000 |
+
))}
|
| 1001 |
+
</nav>
|
| 1002 |
+
|
| 1003 |
+
<div className="flex items-center space-x-3">
|
| 1004 |
+
<Link href="/register" className="text-warmgray-700 hover:text-teal-700 font-medium text-sm">
|
| 1005 |
+
Sign Up
|
| 1006 |
+
</Link>
|
| 1007 |
+
<Link href="/" className="bg-teal-600 text-white px-5 py-2.5 rounded-full hover:bg-teal-700 transition-all font-medium text-sm">
|
| 1008 |
+
Login
|
| 1009 |
+
</Link>
|
| 1010 |
+
</div>
|
| 1011 |
+
</div>
|
| 1012 |
+
</div>
|
| 1013 |
+
</header>
|
| 1014 |
+
|
| 1015 |
+
{/* Hero Section */}
|
| 1016 |
+
<section className="py-16 px-4">
|
| 1017 |
+
<div className="max-w-4xl mx-auto text-center">
|
| 1018 |
+
<h1 className="text-4xl md:text-5xl font-bold text-warmgray-900 mb-4">
|
| 1019 |
+
Learning <span className="text-teal-600">Resources</span>
|
| 1020 |
+
</h1>
|
| 1021 |
+
<p className="text-lg text-warmgray-600">
|
| 1022 |
+
Free guides, cheatsheets, videos, and tools to accelerate your Python learning journey.
|
| 1023 |
+
</p>
|
| 1024 |
+
</div>
|
| 1025 |
+
</section>
|
| 1026 |
+
|
| 1027 |
+
{/* Tabs */}
|
| 1028 |
+
<section className="px-4 pb-8">
|
| 1029 |
+
<div className="max-w-6xl mx-auto">
|
| 1030 |
+
<div className="flex flex-wrap justify-center gap-2 bg-white rounded-2xl p-2 shadow-soft">
|
| 1031 |
+
{[
|
| 1032 |
+
{ id: 'guides', label: 'Guides', icon: '📖' },
|
| 1033 |
+
{ id: 'cheatsheets', label: 'Cheatsheets', icon: '📝' },
|
| 1034 |
+
{ id: 'videos', label: 'Videos', icon: '🎥' },
|
| 1035 |
+
{ id: 'tools', label: 'Tools', icon: '🛠️' },
|
| 1036 |
+
].map((tab) => (
|
| 1037 |
+
<button
|
| 1038 |
+
key={tab.id}
|
| 1039 |
+
onClick={() => setActiveTab(tab.id)}
|
| 1040 |
+
className={`px-6 py-3 rounded-xl font-medium transition-all duration-200 flex items-center gap-2 ${
|
| 1041 |
+
activeTab === tab.id
|
| 1042 |
+
? 'bg-teal-600 text-white shadow-soft'
|
| 1043 |
+
: 'text-warmgray-600 hover:bg-teal-50 hover:text-teal-700'
|
| 1044 |
+
}`}
|
| 1045 |
+
>
|
| 1046 |
+
<span>{tab.icon}</span>
|
| 1047 |
+
{tab.label}
|
| 1048 |
+
</button>
|
| 1049 |
+
))}
|
| 1050 |
+
</div>
|
| 1051 |
+
</div>
|
| 1052 |
+
</section>
|
| 1053 |
+
|
| 1054 |
+
{/* Content */}
|
| 1055 |
+
<section className="px-4 pb-20">
|
| 1056 |
+
<div className="max-w-6xl mx-auto">
|
| 1057 |
+
{activeTab === 'guides' && (
|
| 1058 |
+
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
| 1059 |
+
{guides.map((guide, index) => (
|
| 1060 |
+
<div
|
| 1061 |
+
key={index}
|
| 1062 |
+
onClick={() => setSelectedGuide(guide)}
|
| 1063 |
+
className="bg-white rounded-2xl p-6 shadow-soft hover:shadow-soft-lg transition-all group cursor-pointer"
|
| 1064 |
+
>
|
| 1065 |
+
<div className="text-4xl mb-4">{guide.icon}</div>
|
| 1066 |
+
<div className="flex items-center gap-2 mb-2">
|
| 1067 |
+
<span
|
| 1068 |
+
className={`px-2 py-1 rounded-full text-xs font-medium ${
|
| 1069 |
+
guide.difficulty === 'Beginner'
|
| 1070 |
+
? 'bg-green-100 text-green-700'
|
| 1071 |
+
: guide.difficulty === 'Intermediate'
|
| 1072 |
+
? 'bg-yellow-100 text-yellow-700'
|
| 1073 |
+
: 'bg-red-100 text-red-700'
|
| 1074 |
+
}`}
|
| 1075 |
+
>
|
| 1076 |
+
{guide.difficulty}
|
| 1077 |
+
</span>
|
| 1078 |
+
<span className="text-warmgray-400 text-xs">{guide.readTime} read</span>
|
| 1079 |
+
</div>
|
| 1080 |
+
<h3 className="font-semibold text-warmgray-900 mb-2 group-hover:text-teal-600 transition-colors">
|
| 1081 |
+
{guide.title}
|
| 1082 |
+
</h3>
|
| 1083 |
+
<p className="text-sm text-warmgray-600">{guide.description}</p>
|
| 1084 |
+
<div className="mt-4 text-teal-600 text-sm font-medium flex items-center gap-1">
|
| 1085 |
+
Read Guide
|
| 1086 |
+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 1087 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
| 1088 |
+
</svg>
|
| 1089 |
+
</div>
|
| 1090 |
+
</div>
|
| 1091 |
+
))}
|
| 1092 |
+
</div>
|
| 1093 |
+
)}
|
| 1094 |
+
|
| 1095 |
+
{activeTab === 'cheatsheets' && (
|
| 1096 |
+
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
| 1097 |
+
{cheatsheets.map((sheet, index) => (
|
| 1098 |
+
<div key={index} className="bg-white rounded-2xl p-6 shadow-soft hover:shadow-soft-lg transition-all group">
|
| 1099 |
+
<div className="flex items-center justify-between mb-4">
|
| 1100 |
+
<span className="text-4xl">{sheet.icon}</span>
|
| 1101 |
+
<span className="text-sm text-warmgray-400">{sheet.downloads} downloads</span>
|
| 1102 |
+
</div>
|
| 1103 |
+
<h3 className="font-semibold text-warmgray-900 mb-4 group-hover:text-teal-600 transition-colors">
|
| 1104 |
+
{sheet.title}
|
| 1105 |
+
</h3>
|
| 1106 |
+
<button
|
| 1107 |
+
onClick={() => downloadCheatsheet(sheet)}
|
| 1108 |
+
className="w-full bg-teal-50 text-teal-700 py-2.5 rounded-xl font-medium hover:bg-teal-100 transition-colors flex items-center justify-center gap-2"
|
| 1109 |
+
>
|
| 1110 |
+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 1111 |
+
<path
|
| 1112 |
+
strokeLinecap="round"
|
| 1113 |
+
strokeLinejoin="round"
|
| 1114 |
+
strokeWidth={2}
|
| 1115 |
+
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
| 1116 |
+
/>
|
| 1117 |
+
</svg>
|
| 1118 |
+
Download Cheatsheet
|
| 1119 |
+
</button>
|
| 1120 |
+
</div>
|
| 1121 |
+
))}
|
| 1122 |
+
</div>
|
| 1123 |
+
)}
|
| 1124 |
+
|
| 1125 |
+
{activeTab === 'videos' && (
|
| 1126 |
+
<div className="grid md:grid-cols-2 gap-6">
|
| 1127 |
+
{videos.map((video, index) => (
|
| 1128 |
+
<div
|
| 1129 |
+
key={index}
|
| 1130 |
+
onClick={() => setSelectedVideo(video)}
|
| 1131 |
+
className="bg-white rounded-2xl overflow-hidden shadow-soft hover:shadow-soft-lg transition-all group cursor-pointer"
|
| 1132 |
+
>
|
| 1133 |
+
<div className="bg-gradient-to-br from-teal-500 to-sage-500 h-48 flex items-center justify-center relative">
|
| 1134 |
+
<span className="text-6xl">{video.thumbnail}</span>
|
| 1135 |
+
<div className="absolute inset-0 bg-black/20 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
|
| 1136 |
+
<div className="w-16 h-16 bg-white rounded-full flex items-center justify-center">
|
| 1137 |
+
<svg className="w-8 h-8 text-teal-600 ml-1" fill="currentColor" viewBox="0 0 24 24">
|
| 1138 |
+
<path d="M8 5v14l11-7z" />
|
| 1139 |
+
</svg>
|
| 1140 |
+
</div>
|
| 1141 |
+
</div>
|
| 1142 |
+
<span className="absolute bottom-3 right-3 bg-black/70 text-white text-sm px-2 py-1 rounded">
|
| 1143 |
+
{video.duration}
|
| 1144 |
+
</span>
|
| 1145 |
+
</div>
|
| 1146 |
+
<div className="p-5">
|
| 1147 |
+
<h3 className="font-semibold text-warmgray-900 mb-1 group-hover:text-teal-600 transition-colors">
|
| 1148 |
+
{video.title}
|
| 1149 |
+
</h3>
|
| 1150 |
+
<p className="text-sm text-warmgray-500">
|
| 1151 |
+
{video.channel} • {video.views} views
|
| 1152 |
+
</p>
|
| 1153 |
+
</div>
|
| 1154 |
+
</div>
|
| 1155 |
+
))}
|
| 1156 |
+
</div>
|
| 1157 |
+
)}
|
| 1158 |
+
|
| 1159 |
+
{activeTab === 'tools' && (
|
| 1160 |
+
<div className="grid md:grid-cols-2 gap-6">
|
| 1161 |
+
{tools.map((tool, index) => (
|
| 1162 |
+
<a
|
| 1163 |
+
key={index}
|
| 1164 |
+
href={tool.url}
|
| 1165 |
+
target="_blank"
|
| 1166 |
+
rel="noopener noreferrer"
|
| 1167 |
+
className="bg-white rounded-2xl p-6 shadow-soft hover:shadow-soft-lg transition-all group flex items-center gap-4"
|
| 1168 |
+
>
|
| 1169 |
+
<div className="text-4xl">{tool.icon}</div>
|
| 1170 |
+
<div className="flex-1">
|
| 1171 |
+
<h3 className="font-semibold text-warmgray-900 group-hover:text-teal-600 transition-colors">
|
| 1172 |
+
{tool.name}
|
| 1173 |
+
</h3>
|
| 1174 |
+
<p className="text-sm text-warmgray-600">{tool.description}</p>
|
| 1175 |
+
</div>
|
| 1176 |
+
<svg
|
| 1177 |
+
className="w-5 h-5 text-warmgray-400 group-hover:text-teal-600 transition-colors"
|
| 1178 |
+
fill="none"
|
| 1179 |
+
stroke="currentColor"
|
| 1180 |
+
viewBox="0 0 24 24"
|
| 1181 |
+
>
|
| 1182 |
+
<path
|
| 1183 |
+
strokeLinecap="round"
|
| 1184 |
+
strokeLinejoin="round"
|
| 1185 |
+
strokeWidth={2}
|
| 1186 |
+
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
| 1187 |
+
/>
|
| 1188 |
+
</svg>
|
| 1189 |
+
</a>
|
| 1190 |
+
))}
|
| 1191 |
+
</div>
|
| 1192 |
+
)}
|
| 1193 |
+
</div>
|
| 1194 |
+
</section>
|
| 1195 |
+
|
| 1196 |
+
{/* Guide Modal */}
|
| 1197 |
+
{selectedGuide && (
|
| 1198 |
+
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4" onClick={() => setSelectedGuide(null)}>
|
| 1199 |
+
<div
|
| 1200 |
+
className="bg-white rounded-2xl max-w-3xl w-full max-h-[90vh] overflow-hidden"
|
| 1201 |
+
onClick={(e) => e.stopPropagation()}
|
| 1202 |
+
>
|
| 1203 |
+
<div className="p-6 border-b border-warmgray-100 flex items-center justify-between">
|
| 1204 |
+
<div className="flex items-center gap-3">
|
| 1205 |
+
<span className="text-3xl">{selectedGuide.icon}</span>
|
| 1206 |
+
<div>
|
| 1207 |
+
<h2 className="text-xl font-bold text-warmgray-900">{selectedGuide.title}</h2>
|
| 1208 |
+
<p className="text-sm text-warmgray-500">{selectedGuide.readTime} read</p>
|
| 1209 |
+
</div>
|
| 1210 |
+
</div>
|
| 1211 |
+
<button
|
| 1212 |
+
onClick={() => setSelectedGuide(null)}
|
| 1213 |
+
className="text-warmgray-400 hover:text-warmgray-600 p-2"
|
| 1214 |
+
>
|
| 1215 |
+
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 1216 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
| 1217 |
+
</svg>
|
| 1218 |
+
</button>
|
| 1219 |
+
</div>
|
| 1220 |
+
<div className="p-6 overflow-y-auto max-h-[calc(90vh-100px)]">
|
| 1221 |
+
<pre className="whitespace-pre-wrap font-mono text-sm text-warmgray-700 leading-relaxed">
|
| 1222 |
+
{selectedGuide.content}
|
| 1223 |
+
</pre>
|
| 1224 |
+
</div>
|
| 1225 |
+
</div>
|
| 1226 |
+
</div>
|
| 1227 |
+
)}
|
| 1228 |
+
|
| 1229 |
+
{/* Video Modal */}
|
| 1230 |
+
{selectedVideo && (
|
| 1231 |
+
<div className="fixed inset-0 bg-black/80 flex items-center justify-center z-50 p-4" onClick={() => setSelectedVideo(null)}>
|
| 1232 |
+
<div className="w-full max-w-4xl" onClick={(e) => e.stopPropagation()}>
|
| 1233 |
+
<div className="flex justify-between items-center mb-4">
|
| 1234 |
+
<h3 className="text-white font-semibold">{selectedVideo.title}</h3>
|
| 1235 |
+
<button onClick={() => setSelectedVideo(null)} className="text-white hover:text-warmgray-300 p-2">
|
| 1236 |
+
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 1237 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
| 1238 |
+
</svg>
|
| 1239 |
+
</button>
|
| 1240 |
+
</div>
|
| 1241 |
+
<div className="relative pb-[56.25%] h-0">
|
| 1242 |
+
<iframe
|
| 1243 |
+
className="absolute top-0 left-0 w-full h-full rounded-xl"
|
| 1244 |
+
src={`https://www.youtube.com/embed/${selectedVideo.youtubeId}?autoplay=1`}
|
| 1245 |
+
title={selectedVideo.title}
|
| 1246 |
+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
| 1247 |
+
allowFullScreen
|
| 1248 |
+
/>
|
| 1249 |
+
</div>
|
| 1250 |
+
<p className="text-warmgray-400 mt-3 text-sm">
|
| 1251 |
+
{selectedVideo.channel} • {selectedVideo.views} views
|
| 1252 |
+
</p>
|
| 1253 |
+
</div>
|
| 1254 |
+
</div>
|
| 1255 |
+
)}
|
| 1256 |
+
|
| 1257 |
+
{/* CTA */}
|
| 1258 |
+
<section className="py-16 px-4 bg-gradient-to-r from-teal-600 to-teal-700">
|
| 1259 |
+
<div className="max-w-4xl mx-auto text-center text-white">
|
| 1260 |
+
<h2 className="text-3xl font-bold mb-4">Want More Resources?</h2>
|
| 1261 |
+
<p className="text-teal-100 mb-8">Sign up for free and get access to exclusive learning materials.</p>
|
| 1262 |
+
<Link
|
| 1263 |
+
href="/register"
|
| 1264 |
+
className="inline-block bg-white text-teal-700 px-8 py-3 rounded-full font-semibold hover:bg-cream-50 transition-colors shadow-lg"
|
| 1265 |
+
>
|
| 1266 |
+
Get Started Free
|
| 1267 |
+
</Link>
|
| 1268 |
+
</div>
|
| 1269 |
+
</section>
|
| 1270 |
+
</div>
|
| 1271 |
+
);
|
| 1272 |
+
}
|
app/student/chat/page.tsx
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import { useState, useEffect, useRef } from 'react';
|
| 4 |
+
import { useRouter } from 'next/navigation';
|
| 5 |
+
import Link from 'next/link';
|
| 6 |
+
|
| 7 |
+
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
|
| 8 |
+
|
| 9 |
+
type Message = {
|
| 10 |
+
id: number;
|
| 11 |
+
role: 'user' | 'assistant';
|
| 12 |
+
content: string;
|
| 13 |
+
timestamp: Date;
|
| 14 |
+
};
|
| 15 |
+
|
| 16 |
+
type User = {
|
| 17 |
+
id: string;
|
| 18 |
+
name: string;
|
| 19 |
+
email: string;
|
| 20 |
+
role: string;
|
| 21 |
+
};
|
| 22 |
+
|
| 23 |
+
export default function ChatPage() {
|
| 24 |
+
const router = useRouter();
|
| 25 |
+
const [user, setUser] = useState<User | null>(null);
|
| 26 |
+
const [isLoading, setIsLoading] = useState(true);
|
| 27 |
+
const [messages, setMessages] = useState<Message[]>([
|
| 28 |
+
{
|
| 29 |
+
id: 1,
|
| 30 |
+
role: 'assistant',
|
| 31 |
+
content: "Hello! I'm your Python AI tutor. I'm here to help you learn Python programming. You can ask me about:\n\n- Python syntax and concepts\n- Code explanations\n- Debugging help\n- Best practices\n- Exercise guidance\n\nWhat would you like to learn today?",
|
| 32 |
+
timestamp: new Date()
|
| 33 |
+
}
|
| 34 |
+
]);
|
| 35 |
+
const [inputMessage, setInputMessage] = useState('');
|
| 36 |
+
const [isSending, setIsSending] = useState(false);
|
| 37 |
+
const messagesEndRef = useRef<HTMLDivElement>(null);
|
| 38 |
+
|
| 39 |
+
const suggestedQuestions = [
|
| 40 |
+
"How do I create a for loop?",
|
| 41 |
+
"Explain list comprehensions",
|
| 42 |
+
"What are Python functions?",
|
| 43 |
+
"How do I handle errors?",
|
| 44 |
+
];
|
| 45 |
+
|
| 46 |
+
useEffect(() => {
|
| 47 |
+
const currentUser = localStorage.getItem('learnflow_current_user');
|
| 48 |
+
if (!currentUser) {
|
| 49 |
+
router.push('/');
|
| 50 |
+
return;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
const parsedUser = JSON.parse(currentUser);
|
| 54 |
+
if (parsedUser.role !== 'student') {
|
| 55 |
+
router.push('/teacher/dashboard');
|
| 56 |
+
return;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
setUser(parsedUser);
|
| 60 |
+
setIsLoading(false);
|
| 61 |
+
}, [router]);
|
| 62 |
+
|
| 63 |
+
useEffect(() => {
|
| 64 |
+
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
| 65 |
+
}, [messages]);
|
| 66 |
+
|
| 67 |
+
const generateResponse = (question: string): string => {
|
| 68 |
+
const lowerQuestion = question.toLowerCase();
|
| 69 |
+
|
| 70 |
+
if (lowerQuestion.includes('for loop') || lowerQuestion.includes('loop')) {
|
| 71 |
+
return `Great question! A for loop in Python is used to iterate over sequences like lists, strings, or ranges.
|
| 72 |
+
|
| 73 |
+
Here's the basic syntax:
|
| 74 |
+
|
| 75 |
+
\`\`\`python
|
| 76 |
+
# Iterating over a list
|
| 77 |
+
fruits = ['apple', 'banana', 'cherry']
|
| 78 |
+
for fruit in fruits:
|
| 79 |
+
print(fruit)
|
| 80 |
+
|
| 81 |
+
# Using range
|
| 82 |
+
for i in range(5):
|
| 83 |
+
print(i) # Prints 0, 1, 2, 3, 4
|
| 84 |
+
|
| 85 |
+
# Using range with start, stop, step
|
| 86 |
+
for i in range(0, 10, 2):
|
| 87 |
+
print(i) # Prints 0, 2, 4, 6, 8
|
| 88 |
+
\`\`\`
|
| 89 |
+
|
| 90 |
+
Key points:
|
| 91 |
+
- No need for index counters like in other languages
|
| 92 |
+
- The indented block is the loop body
|
| 93 |
+
- Use \`break\` to exit early, \`continue\` to skip iterations
|
| 94 |
+
|
| 95 |
+
Would you like me to explain more or show different examples?`;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
if (lowerQuestion.includes('list comprehension')) {
|
| 99 |
+
return `List comprehensions are a concise way to create lists in Python!
|
| 100 |
+
|
| 101 |
+
Basic syntax: \`[expression for item in iterable]\`
|
| 102 |
+
|
| 103 |
+
Examples:
|
| 104 |
+
|
| 105 |
+
\`\`\`python
|
| 106 |
+
# Create a list of squares
|
| 107 |
+
squares = [x**2 for x in range(10)]
|
| 108 |
+
# Result: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
|
| 109 |
+
|
| 110 |
+
# With condition (filtering)
|
| 111 |
+
evens = [x for x in range(20) if x % 2 == 0]
|
| 112 |
+
# Result: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
|
| 113 |
+
|
| 114 |
+
# String manipulation
|
| 115 |
+
words = ['hello', 'world']
|
| 116 |
+
upper_words = [word.upper() for word in words]
|
| 117 |
+
# Result: ['HELLO', 'WORLD']
|
| 118 |
+
\`\`\`
|
| 119 |
+
|
| 120 |
+
Benefits:
|
| 121 |
+
- More readable than traditional loops
|
| 122 |
+
- Usually faster
|
| 123 |
+
- Can include conditions for filtering
|
| 124 |
+
|
| 125 |
+
Would you like to practice with some exercises?`;
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
if (lowerQuestion.includes('function')) {
|
| 129 |
+
return `Functions in Python are reusable blocks of code that perform specific tasks.
|
| 130 |
+
|
| 131 |
+
Basic syntax:
|
| 132 |
+
|
| 133 |
+
\`\`\`python
|
| 134 |
+
# Simple function
|
| 135 |
+
def greet(name):
|
| 136 |
+
return f"Hello, {name}!"
|
| 137 |
+
|
| 138 |
+
# Function with default parameter
|
| 139 |
+
def greet_with_title(name, title="Mr."):
|
| 140 |
+
return f"Hello, {title} {name}!"
|
| 141 |
+
|
| 142 |
+
# Function with multiple return values
|
| 143 |
+
def get_stats(numbers):
|
| 144 |
+
return min(numbers), max(numbers), sum(numbers)/len(numbers)
|
| 145 |
+
|
| 146 |
+
# Using the functions
|
| 147 |
+
print(greet("Alice")) # Hello, Alice!
|
| 148 |
+
print(greet_with_title("Smith")) # Hello, Mr. Smith!
|
| 149 |
+
minimum, maximum, average = get_stats([1, 2, 3, 4, 5])
|
| 150 |
+
\`\`\`
|
| 151 |
+
|
| 152 |
+
Key concepts:
|
| 153 |
+
- \`def\` keyword defines a function
|
| 154 |
+
- Parameters go in parentheses
|
| 155 |
+
- \`return\` sends values back
|
| 156 |
+
- Functions can have default parameters
|
| 157 |
+
- *args and **kwargs for flexible arguments
|
| 158 |
+
|
| 159 |
+
Want me to explain any of these in more detail?`;
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
if (lowerQuestion.includes('error') || lowerQuestion.includes('exception') || lowerQuestion.includes('debug')) {
|
| 163 |
+
return `Error handling in Python uses try/except blocks to gracefully handle exceptions.
|
| 164 |
+
|
| 165 |
+
\`\`\`python
|
| 166 |
+
# Basic try/except
|
| 167 |
+
try:
|
| 168 |
+
result = 10 / 0
|
| 169 |
+
except ZeroDivisionError:
|
| 170 |
+
print("Cannot divide by zero!")
|
| 171 |
+
|
| 172 |
+
# Multiple exceptions
|
| 173 |
+
try:
|
| 174 |
+
value = int("not a number")
|
| 175 |
+
except ValueError:
|
| 176 |
+
print("Invalid number format")
|
| 177 |
+
except TypeError:
|
| 178 |
+
print("Type error occurred")
|
| 179 |
+
|
| 180 |
+
# Using else and finally
|
| 181 |
+
try:
|
| 182 |
+
file = open("data.txt", "r")
|
| 183 |
+
data = file.read()
|
| 184 |
+
except FileNotFoundError:
|
| 185 |
+
print("File not found!")
|
| 186 |
+
else:
|
| 187 |
+
print("File read successfully")
|
| 188 |
+
finally:
|
| 189 |
+
file.close() # Always executes
|
| 190 |
+
|
| 191 |
+
# Raising exceptions
|
| 192 |
+
def validate_age(age):
|
| 193 |
+
if age < 0:
|
| 194 |
+
raise ValueError("Age cannot be negative")
|
| 195 |
+
return age
|
| 196 |
+
\`\`\`
|
| 197 |
+
|
| 198 |
+
Common exceptions:
|
| 199 |
+
- \`ValueError\` - Wrong value type
|
| 200 |
+
- \`TypeError\` - Wrong data type
|
| 201 |
+
- \`KeyError\` - Missing dictionary key
|
| 202 |
+
- \`IndexError\` - Index out of range
|
| 203 |
+
- \`FileNotFoundError\` - File doesn't exist
|
| 204 |
+
|
| 205 |
+
Need help debugging a specific error?`;
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
// Default response
|
| 209 |
+
return `That's a great question about "${question}"!
|
| 210 |
+
|
| 211 |
+
As your AI Python tutor, I can help you understand this concept better. Here are some suggestions:
|
| 212 |
+
|
| 213 |
+
1. **Try it yourself**: Open the Learn page and experiment with code
|
| 214 |
+
2. **Take a quiz**: Test your knowledge on this topic
|
| 215 |
+
3. **Ask follow-up questions**: I'm here to help clarify anything
|
| 216 |
+
|
| 217 |
+
Would you like me to:
|
| 218 |
+
- Provide a code example?
|
| 219 |
+
- Explain the underlying concept?
|
| 220 |
+
- Give you a practice exercise?
|
| 221 |
+
|
| 222 |
+
Feel free to ask more specific questions!`;
|
| 223 |
+
};
|
| 224 |
+
|
| 225 |
+
const handleSendMessage = async () => {
|
| 226 |
+
if (!inputMessage.trim() || isSending) return;
|
| 227 |
+
|
| 228 |
+
const userMessage: Message = {
|
| 229 |
+
id: messages.length + 1,
|
| 230 |
+
role: 'user',
|
| 231 |
+
content: inputMessage.trim(),
|
| 232 |
+
timestamp: new Date()
|
| 233 |
+
};
|
| 234 |
+
|
| 235 |
+
setMessages(prev => [...prev, userMessage]);
|
| 236 |
+
setInputMessage('');
|
| 237 |
+
setIsSending(true);
|
| 238 |
+
|
| 239 |
+
let responseText = '';
|
| 240 |
+
|
| 241 |
+
// Try to call the real API
|
| 242 |
+
try {
|
| 243 |
+
const token = localStorage.getItem('learnflow_token');
|
| 244 |
+
const response = await fetch(`${API_URL}/chat`, {
|
| 245 |
+
method: 'POST',
|
| 246 |
+
headers: {
|
| 247 |
+
'Content-Type': 'application/json',
|
| 248 |
+
...(token && { 'Authorization': `Bearer ${token}` }),
|
| 249 |
+
},
|
| 250 |
+
body: JSON.stringify({
|
| 251 |
+
messages: [...messages, userMessage].map(m => ({
|
| 252 |
+
role: m.role,
|
| 253 |
+
content: m.content
|
| 254 |
+
})),
|
| 255 |
+
user_id: user?.id || 'anonymous',
|
| 256 |
+
}),
|
| 257 |
+
});
|
| 258 |
+
|
| 259 |
+
if (response.ok) {
|
| 260 |
+
const data = await response.json();
|
| 261 |
+
responseText = data.response;
|
| 262 |
+
} else {
|
| 263 |
+
// Fallback to simulated response
|
| 264 |
+
responseText = generateResponse(userMessage.content);
|
| 265 |
+
}
|
| 266 |
+
} catch (error) {
|
| 267 |
+
// API not available, use simulated response
|
| 268 |
+
console.log('Using simulated response (API not available)');
|
| 269 |
+
responseText = generateResponse(userMessage.content);
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
const assistantMessage: Message = {
|
| 273 |
+
id: messages.length + 2,
|
| 274 |
+
role: 'assistant',
|
| 275 |
+
content: responseText,
|
| 276 |
+
timestamp: new Date()
|
| 277 |
+
};
|
| 278 |
+
|
| 279 |
+
setMessages(prev => [...prev, assistantMessage]);
|
| 280 |
+
setIsSending(false);
|
| 281 |
+
};
|
| 282 |
+
|
| 283 |
+
const handleSuggestedQuestion = (question: string) => {
|
| 284 |
+
setInputMessage(question);
|
| 285 |
+
};
|
| 286 |
+
|
| 287 |
+
if (isLoading) {
|
| 288 |
+
return (
|
| 289 |
+
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center">
|
| 290 |
+
<div className="text-center">
|
| 291 |
+
<div className="w-16 h-16 border-4 border-blue-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"></div>
|
| 292 |
+
<p className="text-gray-600">Loading chat...</p>
|
| 293 |
+
</div>
|
| 294 |
+
</div>
|
| 295 |
+
);
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
return (
|
| 299 |
+
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex flex-col">
|
| 300 |
+
<header className="bg-white/80 backdrop-blur-md shadow-lg border-b border-white/20 sticky top-0 z-10">
|
| 301 |
+
<div className="max-w-4xl mx-auto px-4 py-4 sm:px-6 lg:px-8">
|
| 302 |
+
<div className="flex items-center justify-between">
|
| 303 |
+
<Link href="/student/dashboard" className="flex items-center space-x-3">
|
| 304 |
+
<div className="w-10 h-10 bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg flex items-center justify-center">
|
| 305 |
+
<span className="text-white font-bold text-sm">LF</span>
|
| 306 |
+
</div>
|
| 307 |
+
<div>
|
| 308 |
+
<span className="text-xl font-bold text-gray-900">AI Python Tutor</span>
|
| 309 |
+
<div className="flex items-center text-sm text-green-600">
|
| 310 |
+
<span className="w-2 h-2 bg-green-500 rounded-full mr-2"></span>
|
| 311 |
+
Online
|
| 312 |
+
</div>
|
| 313 |
+
</div>
|
| 314 |
+
</Link>
|
| 315 |
+
<Link
|
| 316 |
+
href="/student/dashboard"
|
| 317 |
+
className="text-gray-600 hover:text-gray-800 font-medium flex items-center"
|
| 318 |
+
>
|
| 319 |
+
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 320 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
| 321 |
+
</svg>
|
| 322 |
+
Back
|
| 323 |
+
</Link>
|
| 324 |
+
</div>
|
| 325 |
+
</div>
|
| 326 |
+
</header>
|
| 327 |
+
|
| 328 |
+
<main className="flex-1 max-w-4xl w-full mx-auto px-4 py-6 flex flex-col">
|
| 329 |
+
{/* Suggested Questions */}
|
| 330 |
+
{messages.length <= 1 && (
|
| 331 |
+
<div className="mb-4">
|
| 332 |
+
<p className="text-sm text-gray-500 mb-2">Try asking:</p>
|
| 333 |
+
<div className="flex flex-wrap gap-2">
|
| 334 |
+
{suggestedQuestions.map((question, index) => (
|
| 335 |
+
<button
|
| 336 |
+
key={index}
|
| 337 |
+
onClick={() => handleSuggestedQuestion(question)}
|
| 338 |
+
className="px-3 py-1.5 bg-white rounded-full text-sm text-blue-600 hover:bg-blue-50 border border-blue-200 transition-colors"
|
| 339 |
+
>
|
| 340 |
+
{question}
|
| 341 |
+
</button>
|
| 342 |
+
))}
|
| 343 |
+
</div>
|
| 344 |
+
</div>
|
| 345 |
+
)}
|
| 346 |
+
|
| 347 |
+
{/* Messages */}
|
| 348 |
+
<div className="flex-1 overflow-y-auto space-y-4 mb-4">
|
| 349 |
+
{messages.map((message) => (
|
| 350 |
+
<div
|
| 351 |
+
key={message.id}
|
| 352 |
+
className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}
|
| 353 |
+
>
|
| 354 |
+
<div
|
| 355 |
+
className={`max-w-[80%] p-4 rounded-2xl ${
|
| 356 |
+
message.role === 'user'
|
| 357 |
+
? 'bg-gradient-to-r from-blue-500 to-purple-600 text-white'
|
| 358 |
+
: 'bg-white shadow-lg text-gray-800'
|
| 359 |
+
}`}
|
| 360 |
+
>
|
| 361 |
+
{message.role === 'assistant' && (
|
| 362 |
+
<div className="flex items-center mb-2">
|
| 363 |
+
<div className="w-6 h-6 bg-gradient-to-r from-green-400 to-blue-500 rounded-full flex items-center justify-center mr-2">
|
| 364 |
+
<svg className="w-3 h-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 365 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
|
| 366 |
+
</svg>
|
| 367 |
+
</div>
|
| 368 |
+
<span className="text-xs text-gray-500">AI Tutor</span>
|
| 369 |
+
</div>
|
| 370 |
+
)}
|
| 371 |
+
<div className="whitespace-pre-wrap text-sm">{message.content}</div>
|
| 372 |
+
<div className={`text-xs mt-2 ${message.role === 'user' ? 'text-blue-200' : 'text-gray-400'}`}>
|
| 373 |
+
{message.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
| 374 |
+
</div>
|
| 375 |
+
</div>
|
| 376 |
+
</div>
|
| 377 |
+
))}
|
| 378 |
+
|
| 379 |
+
{isSending && (
|
| 380 |
+
<div className="flex justify-start">
|
| 381 |
+
<div className="bg-white shadow-lg p-4 rounded-2xl">
|
| 382 |
+
<div className="flex items-center space-x-2">
|
| 383 |
+
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0ms' }}></div>
|
| 384 |
+
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '150ms' }}></div>
|
| 385 |
+
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '300ms' }}></div>
|
| 386 |
+
</div>
|
| 387 |
+
</div>
|
| 388 |
+
</div>
|
| 389 |
+
)}
|
| 390 |
+
|
| 391 |
+
<div ref={messagesEndRef} />
|
| 392 |
+
</div>
|
| 393 |
+
|
| 394 |
+
{/* Input */}
|
| 395 |
+
<div className="bg-white rounded-2xl shadow-lg p-4">
|
| 396 |
+
<div className="flex items-center space-x-4">
|
| 397 |
+
<input
|
| 398 |
+
type="text"
|
| 399 |
+
value={inputMessage}
|
| 400 |
+
onChange={(e) => setInputMessage(e.target.value)}
|
| 401 |
+
onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()}
|
| 402 |
+
placeholder="Ask me anything about Python..."
|
| 403 |
+
disabled={isSending}
|
| 404 |
+
className="flex-1 bg-gray-100 rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50"
|
| 405 |
+
/>
|
| 406 |
+
<button
|
| 407 |
+
onClick={handleSendMessage}
|
| 408 |
+
disabled={!inputMessage.trim() || isSending}
|
| 409 |
+
className="bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 disabled:opacity-50 disabled:cursor-not-allowed text-white p-3 rounded-xl transition-all"
|
| 410 |
+
>
|
| 411 |
+
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 412 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
|
| 413 |
+
</svg>
|
| 414 |
+
</button>
|
| 415 |
+
</div>
|
| 416 |
+
</div>
|
| 417 |
+
</main>
|
| 418 |
+
</div>
|
| 419 |
+
);
|
| 420 |
+
}
|
app/student/dashboard/page.tsx
ADDED
|
@@ -0,0 +1,477 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import { useState, useEffect } from 'react';
|
| 4 |
+
import Link from 'next/link';
|
| 5 |
+
import { useRouter } from 'next/navigation';
|
| 6 |
+
|
| 7 |
+
type Module = {
|
| 8 |
+
id: string;
|
| 9 |
+
name: string;
|
| 10 |
+
description: string;
|
| 11 |
+
masteryLevel: number; // 0-100
|
| 12 |
+
progress: number; // 0-100
|
| 13 |
+
status: 'locked' | 'in-progress' | 'completed';
|
| 14 |
+
};
|
| 15 |
+
|
| 16 |
+
type User = {
|
| 17 |
+
id: string;
|
| 18 |
+
name: string;
|
| 19 |
+
email: string;
|
| 20 |
+
role: string;
|
| 21 |
+
};
|
| 22 |
+
|
| 23 |
+
export default function StudentDashboard() {
|
| 24 |
+
const router = useRouter();
|
| 25 |
+
const [user, setUser] = useState<User | null>(null);
|
| 26 |
+
const [isLoading, setIsLoading] = useState(true);
|
| 27 |
+
|
| 28 |
+
const [modules, setModules] = useState<Module[]>([
|
| 29 |
+
{
|
| 30 |
+
id: 'mod1',
|
| 31 |
+
name: 'Python Basics',
|
| 32 |
+
description: 'Variables, data types, operators, input/output',
|
| 33 |
+
masteryLevel: 85,
|
| 34 |
+
progress: 100,
|
| 35 |
+
status: 'completed'
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
id: 'mod2',
|
| 39 |
+
name: 'Control Flow',
|
| 40 |
+
description: 'Conditional statements, loops, break/continue',
|
| 41 |
+
masteryLevel: 65,
|
| 42 |
+
progress: 75,
|
| 43 |
+
status: 'in-progress'
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
id: 'mod3',
|
| 47 |
+
name: 'Functions',
|
| 48 |
+
description: 'Defining functions, parameters, return values',
|
| 49 |
+
masteryLevel: 30,
|
| 50 |
+
progress: 40,
|
| 51 |
+
status: 'in-progress'
|
| 52 |
+
},
|
| 53 |
+
{
|
| 54 |
+
id: 'mod4',
|
| 55 |
+
name: 'Data Structures',
|
| 56 |
+
description: 'Lists, tuples, dictionaries, sets',
|
| 57 |
+
masteryLevel: 0,
|
| 58 |
+
progress: 0,
|
| 59 |
+
status: 'locked'
|
| 60 |
+
},
|
| 61 |
+
{
|
| 62 |
+
id: 'mod5',
|
| 63 |
+
name: 'Object-Oriented Programming',
|
| 64 |
+
description: 'Classes, objects, inheritance, polymorphism',
|
| 65 |
+
masteryLevel: 0,
|
| 66 |
+
progress: 0,
|
| 67 |
+
status: 'locked'
|
| 68 |
+
},
|
| 69 |
+
{
|
| 70 |
+
id: 'mod6',
|
| 71 |
+
name: 'File Operations',
|
| 72 |
+
description: 'Reading/writing files, CSV, JSON',
|
| 73 |
+
masteryLevel: 0,
|
| 74 |
+
progress: 0,
|
| 75 |
+
status: 'locked'
|
| 76 |
+
},
|
| 77 |
+
{
|
| 78 |
+
id: 'mod7',
|
| 79 |
+
name: 'Error Handling',
|
| 80 |
+
description: 'Try/except blocks, exception types, debugging',
|
| 81 |
+
masteryLevel: 0,
|
| 82 |
+
progress: 0,
|
| 83 |
+
status: 'locked'
|
| 84 |
+
},
|
| 85 |
+
{
|
| 86 |
+
id: 'mod8',
|
| 87 |
+
name: 'Libraries & APIs',
|
| 88 |
+
description: 'Installing packages, using APIs, virtual environments',
|
| 89 |
+
masteryLevel: 0,
|
| 90 |
+
progress: 0,
|
| 91 |
+
status: 'locked'
|
| 92 |
+
}
|
| 93 |
+
]);
|
| 94 |
+
|
| 95 |
+
const [recentActivities, setRecentActivities] = useState([
|
| 96 |
+
{ id: 1, activity: 'Completed "Loops in Python" quiz', timestamp: '2026-01-15 14:30', score: 85 },
|
| 97 |
+
{ id: 2, activity: 'Submitted "Function Practice" exercise', timestamp: '2026-01-15 12:15', result: 'Passed' },
|
| 98 |
+
{ id: 3, activity: 'Asked "How do I use list comprehensions?"', timestamp: '2026-01-15 10:45', response: 'Explained' }
|
| 99 |
+
]);
|
| 100 |
+
|
| 101 |
+
useEffect(() => {
|
| 102 |
+
// Check if user is logged in
|
| 103 |
+
const currentUser = localStorage.getItem('learnflow_current_user');
|
| 104 |
+
if (!currentUser) {
|
| 105 |
+
router.push('/');
|
| 106 |
+
return;
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
const parsedUser = JSON.parse(currentUser);
|
| 110 |
+
if (parsedUser.role !== 'student') {
|
| 111 |
+
router.push('/teacher/dashboard');
|
| 112 |
+
return;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
setUser(parsedUser);
|
| 116 |
+
setIsLoading(false);
|
| 117 |
+
}, [router]);
|
| 118 |
+
|
| 119 |
+
const handleLogout = () => {
|
| 120 |
+
localStorage.removeItem('learnflow_current_user');
|
| 121 |
+
router.push('/');
|
| 122 |
+
};
|
| 123 |
+
|
| 124 |
+
if (isLoading) {
|
| 125 |
+
return (
|
| 126 |
+
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center">
|
| 127 |
+
<div className="text-center">
|
| 128 |
+
<div className="w-16 h-16 border-4 border-blue-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"></div>
|
| 129 |
+
<p className="text-gray-600">Loading dashboard...</p>
|
| 130 |
+
</div>
|
| 131 |
+
</div>
|
| 132 |
+
);
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
return (
|
| 136 |
+
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
| 137 |
+
{/* Animated background */}
|
| 138 |
+
<div className="absolute inset-0 overflow-hidden">
|
| 139 |
+
<div className="absolute -top-40 -right-40 w-80 h-80 bg-purple-200 rounded-full mix-blend-multiply filter blur-xl opacity-30 animate-pulse"></div>
|
| 140 |
+
<div className="absolute -bottom-40 -left-40 w-80 h-80 bg-blue-200 rounded-full mix-blend-multiply filter blur-xl opacity-30 animate-pulse animation-delay-2000"></div>
|
| 141 |
+
</div>
|
| 142 |
+
|
| 143 |
+
<div className="relative">
|
| 144 |
+
<header className="bg-white/80 backdrop-blur-md shadow-lg border-b border-white/20 sticky top-0 z-10">
|
| 145 |
+
<div className="max-w-7xl mx-auto px-4 py-4 sm:px-6 lg:px-8 flex justify-between items-center">
|
| 146 |
+
<div className="flex items-center space-x-4">
|
| 147 |
+
<div className="w-10 h-10 bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg flex items-center justify-center">
|
| 148 |
+
<span className="text-white font-bold text-sm">LF</span>
|
| 149 |
+
</div>
|
| 150 |
+
<h1 className="text-2xl font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
|
| 151 |
+
LearnFlow Dashboard
|
| 152 |
+
</h1>
|
| 153 |
+
</div>
|
| 154 |
+
|
| 155 |
+
<div className="flex items-center space-x-4">
|
| 156 |
+
<div className="flex items-center space-x-3 bg-gray-100/50 rounded-full px-4 py-2">
|
| 157 |
+
<div className="w-8 h-8 bg-gradient-to-r from-green-400 to-blue-500 rounded-full flex items-center justify-center">
|
| 158 |
+
<span className="text-white text-sm font-semibold">{user?.name?.charAt(0).toUpperCase() || 'U'}</span>
|
| 159 |
+
</div>
|
| 160 |
+
<span className="text-gray-700 font-medium">{user?.name?.split(' ')[0] || 'User'}</span>
|
| 161 |
+
</div>
|
| 162 |
+
<button
|
| 163 |
+
onClick={handleLogout}
|
| 164 |
+
className="bg-gradient-to-r from-red-500 to-pink-600 hover:from-red-600 hover:to-pink-700 text-white px-4 py-2 rounded-lg transition-all duration-300 transform hover:scale-105 shadow-md"
|
| 165 |
+
>
|
| 166 |
+
Logout
|
| 167 |
+
</button>
|
| 168 |
+
</div>
|
| 169 |
+
</div>
|
| 170 |
+
</header>
|
| 171 |
+
|
| 172 |
+
<main className="max-w-7xl mx-auto px-4 py-8 sm:px-6 lg:px-8">
|
| 173 |
+
<div className="mb-8">
|
| 174 |
+
<h2 className="text-3xl font-bold text-gray-800 mb-2">Welcome back, {user?.name?.split(' ')[0] || 'Student'}!</h2>
|
| 175 |
+
<p className="text-gray-600">Continue your Python learning journey where you left off</p>
|
| 176 |
+
</div>
|
| 177 |
+
|
| 178 |
+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
| 179 |
+
{/* Progress Overview */}
|
| 180 |
+
<div className="lg:col-span-2">
|
| 181 |
+
<div className="bg-white/70 backdrop-blur-sm rounded-2xl shadow-xl border border-white/20 p-6 mb-8">
|
| 182 |
+
<h2 className="text-2xl font-bold text-gray-800 mb-6 flex items-center">
|
| 183 |
+
<svg className="w-6 h-6 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 184 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
| 185 |
+
</svg>
|
| 186 |
+
Your Learning Progress
|
| 187 |
+
</h2>
|
| 188 |
+
|
| 189 |
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
|
| 190 |
+
<div className="bg-gradient-to-br from-blue-100 to-blue-200 p-5 rounded-xl shadow-md border border-white/50">
|
| 191 |
+
<div className="flex items-center justify-between">
|
| 192 |
+
<div>
|
| 193 |
+
<p className="text-sm font-semibold text-blue-800">Overall Mastery</p>
|
| 194 |
+
<p className="text-3xl font-bold text-blue-600 mt-1">72%</p>
|
| 195 |
+
</div>
|
| 196 |
+
<div className="w-12 h-12 bg-blue-500/20 rounded-full flex items-center justify-center">
|
| 197 |
+
<svg className="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 198 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z" />
|
| 199 |
+
</svg>
|
| 200 |
+
</div>
|
| 201 |
+
</div>
|
| 202 |
+
</div>
|
| 203 |
+
|
| 204 |
+
<div className="bg-gradient-to-br from-green-100 to-green-200 p-5 rounded-xl shadow-md border border-white/50">
|
| 205 |
+
<div className="flex items-center justify-between">
|
| 206 |
+
<div>
|
| 207 |
+
<p className="text-sm font-semibold text-green-800">Modules Completed</p>
|
| 208 |
+
<p className="text-3xl font-bold text-green-600 mt-1">2/8</p>
|
| 209 |
+
</div>
|
| 210 |
+
<div className="w-12 h-12 bg-green-500/20 rounded-full flex items-center justify-center">
|
| 211 |
+
<svg className="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 212 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
| 213 |
+
</svg>
|
| 214 |
+
</div>
|
| 215 |
+
</div>
|
| 216 |
+
</div>
|
| 217 |
+
|
| 218 |
+
<div className="bg-gradient-to-br from-yellow-100 to-yellow-200 p-5 rounded-xl shadow-md border border-white/50">
|
| 219 |
+
<div className="flex items-center justify-between">
|
| 220 |
+
<div>
|
| 221 |
+
<p className="text-sm font-semibold text-yellow-800">Current Streak</p>
|
| 222 |
+
<p className="text-3xl font-bold text-yellow-600 mt-1">5 days</p>
|
| 223 |
+
</div>
|
| 224 |
+
<div className="w-12 h-12 bg-yellow-500/20 rounded-full flex items-center justify-center">
|
| 225 |
+
<svg className="w-6 h-6 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 226 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 18.657A8 8 0 016.343 7.343S7 9 9 10c0-2 .5-5 2.986-7C14 5 16.09 5.777 17.656 7.343A7.975 7.975 0 0120 13a7.975 7.975 0 01-2.343 5.657z" />
|
| 227 |
+
</svg>
|
| 228 |
+
</div>
|
| 229 |
+
</div>
|
| 230 |
+
</div>
|
| 231 |
+
|
| 232 |
+
<div className="bg-gradient-to-br from-purple-100 to-purple-200 p-5 rounded-xl shadow-md border border-white/50">
|
| 233 |
+
<div className="flex items-center justify-between">
|
| 234 |
+
<div>
|
| 235 |
+
<p className="text-sm font-semibold text-purple-800">Hours Learned</p>
|
| 236 |
+
<p className="text-3xl font-bold text-purple-600 mt-1">24</p>
|
| 237 |
+
</div>
|
| 238 |
+
<div className="w-12 h-12 bg-purple-500/20 rounded-full flex items-center justify-center">
|
| 239 |
+
<svg className="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 240 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
| 241 |
+
</svg>
|
| 242 |
+
</div>
|
| 243 |
+
</div>
|
| 244 |
+
</div>
|
| 245 |
+
</div>
|
| 246 |
+
|
| 247 |
+
<h3 className="text-xl font-semibold text-gray-800 mb-4 flex items-center">
|
| 248 |
+
<svg className="w-5 h-5 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 249 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
|
| 250 |
+
</svg>
|
| 251 |
+
Learning Modules
|
| 252 |
+
</h3>
|
| 253 |
+
|
| 254 |
+
<div className="space-y-4">
|
| 255 |
+
{modules.map((module) => (
|
| 256 |
+
<div
|
| 257 |
+
key={module.id}
|
| 258 |
+
className={`bg-white/50 backdrop-blur-sm rounded-xl p-5 border transition-all duration-300 hover:shadow-lg ${
|
| 259 |
+
module.status === 'locked'
|
| 260 |
+
? 'opacity-60 border-gray-200'
|
| 261 |
+
: module.status === 'in-progress'
|
| 262 |
+
? 'border-blue-200 shadow-md'
|
| 263 |
+
: 'border-green-200 shadow-md'
|
| 264 |
+
}`}
|
| 265 |
+
>
|
| 266 |
+
<div className="flex justify-between items-start">
|
| 267 |
+
<div className="flex-1">
|
| 268 |
+
<div className="flex items-center mb-2">
|
| 269 |
+
<h4 className="font-bold text-lg text-gray-800">{module.name}</h4>
|
| 270 |
+
{module.status === 'completed' && (
|
| 271 |
+
<span className="ml-3 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
|
| 272 |
+
<svg className="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 273 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
| 274 |
+
</svg>
|
| 275 |
+
Completed
|
| 276 |
+
</span>
|
| 277 |
+
)}
|
| 278 |
+
{module.status === 'in-progress' && (
|
| 279 |
+
<span className="ml-3 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
| 280 |
+
<svg className="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 281 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
| 282 |
+
</svg>
|
| 283 |
+
In Progress
|
| 284 |
+
</span>
|
| 285 |
+
)}
|
| 286 |
+
</div>
|
| 287 |
+
<p className="text-gray-600 text-sm mb-3">{module.description}</p>
|
| 288 |
+
|
| 289 |
+
<div className="flex items-center justify-between mb-2">
|
| 290 |
+
<span className="text-sm font-medium text-gray-700">Progress: {module.progress}%</span>
|
| 291 |
+
<span className="text-sm font-medium text-gray-700">Mastery: {module.masteryLevel}%</span>
|
| 292 |
+
</div>
|
| 293 |
+
|
| 294 |
+
<div className="w-full bg-gray-200 rounded-full h-2.5 mb-2">
|
| 295 |
+
<div
|
| 296 |
+
className={`h-2.5 rounded-full ${
|
| 297 |
+
module.status === 'completed' ? 'bg-green-500' :
|
| 298 |
+
module.status === 'in-progress' ? 'bg-blue-500' : 'bg-gray-400'
|
| 299 |
+
}`}
|
| 300 |
+
style={{ width: `${module.progress}%` }}
|
| 301 |
+
></div>
|
| 302 |
+
</div>
|
| 303 |
+
</div>
|
| 304 |
+
|
| 305 |
+
<Link
|
| 306 |
+
href={`/student/learn?module=${module.id}`}
|
| 307 |
+
className={`ml-4 px-4 py-2 rounded-lg font-medium transition-all duration-300 transform hover:scale-105 ${
|
| 308 |
+
module.status === 'locked'
|
| 309 |
+
? 'bg-gray-300 text-gray-500 cursor-not-allowed'
|
| 310 |
+
: module.status === 'completed'
|
| 311 |
+
? 'bg-gradient-to-r from-green-500 to-green-600 hover:from-green-600 hover:to-green-700 text-white shadow-md'
|
| 312 |
+
: module.status === 'in-progress'
|
| 313 |
+
? 'bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 text-white shadow-md'
|
| 314 |
+
: 'bg-gradient-to-r from-purple-500 to-purple-600 hover:from-purple-600 hover:to-purple-700 text-white shadow-md'
|
| 315 |
+
}`}
|
| 316 |
+
prefetch={false}
|
| 317 |
+
>
|
| 318 |
+
{module.status === 'completed' ? 'Review' :
|
| 319 |
+
module.status === 'in-progress' ? 'Continue' : 'Start'}
|
| 320 |
+
</Link>
|
| 321 |
+
</div>
|
| 322 |
+
|
| 323 |
+
<div className="mt-3 flex items-center">
|
| 324 |
+
<span className={`text-xs px-3 py-1 rounded-full font-medium ${
|
| 325 |
+
module.masteryLevel >= 90 ? 'bg-gradient-to-r from-blue-100 to-blue-200 text-blue-800' :
|
| 326 |
+
module.masteryLevel >= 70 ? 'bg-gradient-to-r from-green-100 to-green-200 text-green-800' :
|
| 327 |
+
module.masteryLevel >= 40 ? 'bg-gradient-to-r from-yellow-100 to-yellow-200 text-yellow-800' :
|
| 328 |
+
'bg-gradient-to-r from-red-100 to-red-200 text-red-800'
|
| 329 |
+
}`}>
|
| 330 |
+
{module.masteryLevel >= 90 ? '⭐ Mastered' :
|
| 331 |
+
module.masteryLevel >= 70 ? '💪 Proficient' :
|
| 332 |
+
module.masteryLevel >= 40 ? '📚 Learning' : '🌱 Beginner'}
|
| 333 |
+
</span>
|
| 334 |
+
</div>
|
| 335 |
+
</div>
|
| 336 |
+
))}
|
| 337 |
+
</div>
|
| 338 |
+
</div>
|
| 339 |
+
</div>
|
| 340 |
+
|
| 341 |
+
{/* Sidebar */}
|
| 342 |
+
<div className="space-y-6">
|
| 343 |
+
{/* Recent Activity */}
|
| 344 |
+
<div className="bg-white/70 backdrop-blur-sm rounded-2xl shadow-xl border border-white/20 p-6">
|
| 345 |
+
<h3 className="text-xl font-bold text-gray-800 mb-4 flex items-center">
|
| 346 |
+
<svg className="w-5 h-5 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 347 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
| 348 |
+
</svg>
|
| 349 |
+
Recent Activity
|
| 350 |
+
</h3>
|
| 351 |
+
<ul className="space-y-4">
|
| 352 |
+
{recentActivities.map((activity) => (
|
| 353 |
+
<li key={activity.id} className="border-b border-gray-100 pb-4 last:border-0 last:pb-0">
|
| 354 |
+
<div className="flex items-start">
|
| 355 |
+
<div className="flex-shrink-0 mt-1">
|
| 356 |
+
<div className="w-8 h-8 bg-gradient-to-r from-blue-100 to-purple-100 rounded-full flex items-center justify-center">
|
| 357 |
+
<svg className="w-4 h-4 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 358 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
| 359 |
+
</svg>
|
| 360 |
+
</div>
|
| 361 |
+
</div>
|
| 362 |
+
<div className="ml-3">
|
| 363 |
+
<p className="text-sm font-medium text-gray-800">{activity.activity}</p>
|
| 364 |
+
<p className="text-xs text-gray-500 mt-1">{activity.timestamp}</p>
|
| 365 |
+
{activity.score && (
|
| 366 |
+
<span className="inline-block mt-1 px-2 py-1 text-xs bg-green-100 text-green-800 rounded-full">
|
| 367 |
+
Score: {activity.score}%
|
| 368 |
+
</span>
|
| 369 |
+
)}
|
| 370 |
+
{activity.result && (
|
| 371 |
+
<span className="inline-block mt-1 px-2 py-1 text-xs bg-blue-100 text-blue-800 rounded-full">
|
| 372 |
+
{activity.result}
|
| 373 |
+
</span>
|
| 374 |
+
)}
|
| 375 |
+
</div>
|
| 376 |
+
</div>
|
| 377 |
+
</li>
|
| 378 |
+
))}
|
| 379 |
+
</ul>
|
| 380 |
+
</div>
|
| 381 |
+
|
| 382 |
+
{/* Continue Learning */}
|
| 383 |
+
<div className="bg-gradient-to-br from-blue-500 to-purple-600 rounded-2xl shadow-xl p-6 text-white">
|
| 384 |
+
<h3 className="text-xl font-bold mb-4 flex items-center">
|
| 385 |
+
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 386 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
| 387 |
+
</svg>
|
| 388 |
+
Continue Learning
|
| 389 |
+
</h3>
|
| 390 |
+
<div className="bg-white/20 backdrop-blur-sm rounded-xl p-4 mb-4">
|
| 391 |
+
<p className="font-semibold">Control Flow Module</p>
|
| 392 |
+
<p className="text-sm opacity-90 mt-1">75% complete • 65% mastery</p>
|
| 393 |
+
<div className="w-full bg-white/30 rounded-full h-2 mt-3">
|
| 394 |
+
<div className="bg-white h-2 rounded-full" style={{ width: '75%' }}></div>
|
| 395 |
+
</div>
|
| 396 |
+
</div>
|
| 397 |
+
<Link
|
| 398 |
+
href="/student/learn?module=mod2"
|
| 399 |
+
className="w-full bg-white text-blue-600 hover:bg-gray-100 font-semibold py-3 px-4 rounded-lg transition-all duration-300 transform hover:scale-105 block text-center"
|
| 400 |
+
prefetch={false}
|
| 401 |
+
>
|
| 402 |
+
Continue Learning
|
| 403 |
+
</Link>
|
| 404 |
+
</div>
|
| 405 |
+
|
| 406 |
+
{/* Quick Actions */}
|
| 407 |
+
<div className="bg-white/70 backdrop-blur-sm rounded-2xl shadow-xl border border-white/20 p-6">
|
| 408 |
+
<h3 className="text-xl font-bold text-gray-800 mb-4 flex items-center">
|
| 409 |
+
<svg className="w-5 h-5 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 410 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
| 411 |
+
</svg>
|
| 412 |
+
Quick Actions
|
| 413 |
+
</h3>
|
| 414 |
+
<div className="space-y-3">
|
| 415 |
+
<Link
|
| 416 |
+
href="/student/quiz"
|
| 417 |
+
className="flex items-center p-4 bg-gradient-to-r from-gray-50 to-gray-100 hover:from-blue-50 hover:to-purple-50 rounded-xl transition-all duration-300 transform hover:scale-[1.02] border border-gray-100 hover:border-blue-200"
|
| 418 |
+
prefetch={false}
|
| 419 |
+
>
|
| 420 |
+
<div className="w-10 h-10 bg-gradient-to-r from-blue-100 to-blue-200 rounded-lg flex items-center justify-center mr-3">
|
| 421 |
+
<svg className="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 422 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
| 423 |
+
</svg>
|
| 424 |
+
</div>
|
| 425 |
+
<span className="font-medium text-gray-800">Take a Quiz</span>
|
| 426 |
+
</Link>
|
| 427 |
+
|
| 428 |
+
<Link
|
| 429 |
+
href="/student/progress"
|
| 430 |
+
className="flex items-center p-4 bg-gradient-to-r from-gray-50 to-gray-100 hover:from-green-50 hover:to-teal-50 rounded-xl transition-all duration-300 transform hover:scale-[1.02] border border-gray-100 hover:border-green-200"
|
| 431 |
+
prefetch={false}
|
| 432 |
+
>
|
| 433 |
+
<div className="w-10 h-10 bg-gradient-to-r from-green-100 to-green-200 rounded-lg flex items-center justify-center mr-3">
|
| 434 |
+
<svg className="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 435 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
| 436 |
+
</svg>
|
| 437 |
+
</div>
|
| 438 |
+
<span className="font-medium text-gray-800">View Progress</span>
|
| 439 |
+
</Link>
|
| 440 |
+
|
| 441 |
+
<Link
|
| 442 |
+
href="/student/chat"
|
| 443 |
+
className="flex items-center p-4 bg-gradient-to-r from-gray-50 to-gray-100 hover:from-purple-50 hover:to-pink-50 rounded-xl transition-all duration-300 transform hover:scale-[1.02] border border-gray-100 hover:border-purple-200"
|
| 444 |
+
prefetch={false}
|
| 445 |
+
>
|
| 446 |
+
<div className="w-10 h-10 bg-gradient-to-r from-purple-100 to-purple-200 rounded-lg flex items-center justify-center mr-3">
|
| 447 |
+
<svg className="w-5 h-5 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 448 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
|
| 449 |
+
</svg>
|
| 450 |
+
</div>
|
| 451 |
+
<span className="font-medium text-gray-800">Chat with Tutor</span>
|
| 452 |
+
</Link>
|
| 453 |
+
</div>
|
| 454 |
+
</div>
|
| 455 |
+
</div>
|
| 456 |
+
</div>
|
| 457 |
+
</main>
|
| 458 |
+
</div>
|
| 459 |
+
|
| 460 |
+
<style jsx global>{`
|
| 461 |
+
.animation-delay-2000 {
|
| 462 |
+
animation-delay: 2s;
|
| 463 |
+
}
|
| 464 |
+
.animation-delay-4000 {
|
| 465 |
+
animation-delay: 4s;
|
| 466 |
+
}
|
| 467 |
+
@keyframes pulse {
|
| 468 |
+
0%, 100% { opacity: 0.3; }
|
| 469 |
+
50% { opacity: 0.4; }
|
| 470 |
+
}
|
| 471 |
+
.animate-pulse {
|
| 472 |
+
animation: pulse 6s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
| 473 |
+
}
|
| 474 |
+
`}</style>
|
| 475 |
+
</div>
|
| 476 |
+
);
|
| 477 |
+
}
|
app/student/learn/page.tsx
ADDED
|
@@ -0,0 +1,521 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import { useState, useRef, useEffect } from 'react';
|
| 4 |
+
import { useRouter } from 'next/navigation';
|
| 5 |
+
import dynamic from 'next/dynamic';
|
| 6 |
+
|
| 7 |
+
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
|
| 8 |
+
|
| 9 |
+
// Dynamically import Monaco Editor with error handling to avoid SSR issues
|
| 10 |
+
const MonacoEditor = dynamic(
|
| 11 |
+
() => import('@monaco-editor/react'),
|
| 12 |
+
{
|
| 13 |
+
ssr: false,
|
| 14 |
+
loading: () => (
|
| 15 |
+
<div className="h-full w-full flex items-center justify-center bg-gray-900">
|
| 16 |
+
<div className="text-green-400 font-mono text-sm">Loading code editor...</div>
|
| 17 |
+
</div>
|
| 18 |
+
)
|
| 19 |
+
}
|
| 20 |
+
);
|
| 21 |
+
|
| 22 |
+
export default function LearnPage() {
|
| 23 |
+
const [code, setCode] = useState<string>('print("Hello, Python learner!")\n# Write your code here');
|
| 24 |
+
const [output, setOutput] = useState<string>('');
|
| 25 |
+
const [isLoading, setIsLoading] = useState<boolean>(false);
|
| 26 |
+
const [messages, setMessages] = useState<Array<{role: string, content: string}>>([
|
| 27 |
+
{ role: 'assistant', content: 'Hello! I\'m your Python tutor. What would you like to learn today?' }
|
| 28 |
+
]);
|
| 29 |
+
const [inputMessage, setInputMessage] = useState<string>('');
|
| 30 |
+
const editorRef = useRef<any>(null);
|
| 31 |
+
|
| 32 |
+
const handleRunCode = async () => {
|
| 33 |
+
setIsLoading(true);
|
| 34 |
+
setOutput('Running code...');
|
| 35 |
+
|
| 36 |
+
try {
|
| 37 |
+
const token = localStorage.getItem('learnflow_token');
|
| 38 |
+
const response = await fetch(`${API_URL}/execute`, {
|
| 39 |
+
method: 'POST',
|
| 40 |
+
headers: {
|
| 41 |
+
'Content-Type': 'application/json',
|
| 42 |
+
...(token && { 'Authorization': `Bearer ${token}` }),
|
| 43 |
+
},
|
| 44 |
+
body: JSON.stringify({ code }),
|
| 45 |
+
});
|
| 46 |
+
|
| 47 |
+
if (response.ok) {
|
| 48 |
+
const data = await response.json();
|
| 49 |
+
if (data.error) {
|
| 50 |
+
setOutput(`Error:\n${data.error}\n\nOutput:\n${data.output || '(none)'}`);
|
| 51 |
+
} else {
|
| 52 |
+
setOutput(data.output || 'Code executed successfully (no output)');
|
| 53 |
+
}
|
| 54 |
+
} else {
|
| 55 |
+
// Fallback: simulate execution for demo
|
| 56 |
+
simulateExecution();
|
| 57 |
+
}
|
| 58 |
+
} catch (error) {
|
| 59 |
+
// API not available, simulate execution
|
| 60 |
+
console.log('Using simulated execution (API not available)');
|
| 61 |
+
simulateExecution();
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
setIsLoading(false);
|
| 65 |
+
};
|
| 66 |
+
|
| 67 |
+
const simulateExecution = () => {
|
| 68 |
+
// Simple Python simulation for common operations
|
| 69 |
+
const lines = code.split('\n');
|
| 70 |
+
let output = '';
|
| 71 |
+
|
| 72 |
+
for (const line of lines) {
|
| 73 |
+
const trimmed = line.trim();
|
| 74 |
+
if (trimmed.startsWith('print(')) {
|
| 75 |
+
const match = trimmed.match(/print\((.*)\)/);
|
| 76 |
+
if (match) {
|
| 77 |
+
let content = match[1];
|
| 78 |
+
// Handle string literals
|
| 79 |
+
if ((content.startsWith('"') && content.endsWith('"')) ||
|
| 80 |
+
(content.startsWith("'") && content.endsWith("'"))) {
|
| 81 |
+
output += content.slice(1, -1) + '\n';
|
| 82 |
+
} else if (content.startsWith('f"') || content.startsWith("f'")) {
|
| 83 |
+
output += content.slice(2, -1) + '\n';
|
| 84 |
+
} else {
|
| 85 |
+
output += content + '\n';
|
| 86 |
+
}
|
| 87 |
+
}
|
| 88 |
+
}
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
setOutput(output || 'Code executed successfully!\n(Simulated - connect API Gateway for real execution)');
|
| 92 |
+
};
|
| 93 |
+
|
| 94 |
+
const [isSending, setIsSending] = useState<boolean>(false);
|
| 95 |
+
|
| 96 |
+
const generateLocalResponse = (question: string): string => {
|
| 97 |
+
const lowerQuestion = question.toLowerCase();
|
| 98 |
+
|
| 99 |
+
if (lowerQuestion.includes('loop') || lowerQuestion.includes('for')) {
|
| 100 |
+
return `**For Loops in Python**
|
| 101 |
+
|
| 102 |
+
A for loop iterates over a sequence (list, string, range, etc.):
|
| 103 |
+
|
| 104 |
+
\`\`\`python
|
| 105 |
+
# Basic for loop
|
| 106 |
+
for i in range(5):
|
| 107 |
+
print(i) # Prints 0, 1, 2, 3, 4
|
| 108 |
+
|
| 109 |
+
# Loop through a list
|
| 110 |
+
fruits = ['apple', 'banana', 'cherry']
|
| 111 |
+
for fruit in fruits:
|
| 112 |
+
print(fruit)
|
| 113 |
+
|
| 114 |
+
# Loop with enumerate (get index too)
|
| 115 |
+
for index, fruit in enumerate(fruits):
|
| 116 |
+
print(f"{index}: {fruit}")
|
| 117 |
+
\`\`\`
|
| 118 |
+
|
| 119 |
+
**Key points:**
|
| 120 |
+
- \`range(n)\` generates numbers 0 to n-1
|
| 121 |
+
- Use \`break\` to exit early
|
| 122 |
+
- Use \`continue\` to skip to next iteration
|
| 123 |
+
|
| 124 |
+
Would you like me to explain more or try it in the code editor?`;
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
if (lowerQuestion.includes('function') || lowerQuestion.includes('def')) {
|
| 128 |
+
return `**Functions in Python**
|
| 129 |
+
|
| 130 |
+
Functions are reusable blocks of code:
|
| 131 |
+
|
| 132 |
+
\`\`\`python
|
| 133 |
+
# Basic function
|
| 134 |
+
def greet(name):
|
| 135 |
+
return f"Hello, {name}!"
|
| 136 |
+
|
| 137 |
+
# With default parameter
|
| 138 |
+
def greet(name, greeting="Hello"):
|
| 139 |
+
return f"{greeting}, {name}!"
|
| 140 |
+
|
| 141 |
+
# Multiple return values
|
| 142 |
+
def get_stats(numbers):
|
| 143 |
+
return min(numbers), max(numbers)
|
| 144 |
+
|
| 145 |
+
# Calling functions
|
| 146 |
+
message = greet("Alice")
|
| 147 |
+
minimum, maximum = get_stats([1, 2, 3, 4, 5])
|
| 148 |
+
\`\`\`
|
| 149 |
+
|
| 150 |
+
Try writing a function in the code editor!`;
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
if (lowerQuestion.includes('list')) {
|
| 154 |
+
return `**Lists in Python**
|
| 155 |
+
|
| 156 |
+
Lists are ordered, mutable collections:
|
| 157 |
+
|
| 158 |
+
\`\`\`python
|
| 159 |
+
# Create a list
|
| 160 |
+
numbers = [1, 2, 3, 4, 5]
|
| 161 |
+
mixed = [1, "hello", 3.14, True]
|
| 162 |
+
|
| 163 |
+
# Access elements
|
| 164 |
+
first = numbers[0] # 1
|
| 165 |
+
last = numbers[-1] # 5
|
| 166 |
+
|
| 167 |
+
# Modify lists
|
| 168 |
+
numbers.append(6) # Add to end
|
| 169 |
+
numbers.insert(0, 0) # Insert at index
|
| 170 |
+
numbers.remove(3) # Remove value
|
| 171 |
+
|
| 172 |
+
# List comprehension
|
| 173 |
+
squares = [x**2 for x in range(5)]
|
| 174 |
+
# Result: [0, 1, 4, 9, 16]
|
| 175 |
+
\`\`\`
|
| 176 |
+
|
| 177 |
+
Try creating a list in the code editor!`;
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
if (lowerQuestion.includes('if') || lowerQuestion.includes('condition')) {
|
| 181 |
+
return `**If Statements in Python**
|
| 182 |
+
|
| 183 |
+
Control flow with conditions:
|
| 184 |
+
|
| 185 |
+
\`\`\`python
|
| 186 |
+
age = 18
|
| 187 |
+
|
| 188 |
+
if age < 13:
|
| 189 |
+
print("Child")
|
| 190 |
+
elif age < 20:
|
| 191 |
+
print("Teenager")
|
| 192 |
+
else:
|
| 193 |
+
print("Adult")
|
| 194 |
+
|
| 195 |
+
# One-line conditional
|
| 196 |
+
status = "Adult" if age >= 18 else "Minor"
|
| 197 |
+
|
| 198 |
+
# Multiple conditions
|
| 199 |
+
if age >= 18 and has_license:
|
| 200 |
+
print("Can drive")
|
| 201 |
+
\`\`\`
|
| 202 |
+
|
| 203 |
+
Try writing an if statement in the code editor!`;
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
return `Great question about "${question}"!
|
| 207 |
+
|
| 208 |
+
I'm your Python AI tutor. I can help you with:
|
| 209 |
+
- **Python syntax** - variables, operators, data types
|
| 210 |
+
- **Control flow** - if/else, loops, functions
|
| 211 |
+
- **Data structures** - lists, dictionaries, tuples
|
| 212 |
+
- **Code debugging** - finding and fixing errors
|
| 213 |
+
|
| 214 |
+
Try asking about a specific topic, or write some code in the editor and I'll help you understand it!`;
|
| 215 |
+
};
|
| 216 |
+
|
| 217 |
+
const handleSendMessage = async () => {
|
| 218 |
+
if (!inputMessage.trim() || isSending) return;
|
| 219 |
+
|
| 220 |
+
const userMessage = { role: 'user', content: inputMessage };
|
| 221 |
+
const newMessages = [...messages, userMessage];
|
| 222 |
+
setMessages(newMessages);
|
| 223 |
+
const currentInput = inputMessage;
|
| 224 |
+
setInputMessage('');
|
| 225 |
+
setIsSending(true);
|
| 226 |
+
|
| 227 |
+
try {
|
| 228 |
+
const token = localStorage.getItem('learnflow_token');
|
| 229 |
+
const response = await fetch(`${API_URL}/chat`, {
|
| 230 |
+
method: 'POST',
|
| 231 |
+
headers: {
|
| 232 |
+
'Content-Type': 'application/json',
|
| 233 |
+
...(token && { 'Authorization': `Bearer ${token}` }),
|
| 234 |
+
},
|
| 235 |
+
body: JSON.stringify({
|
| 236 |
+
messages: newMessages.map(m => ({
|
| 237 |
+
role: m.role,
|
| 238 |
+
content: m.content
|
| 239 |
+
})),
|
| 240 |
+
user_id: 'learn-page-user',
|
| 241 |
+
}),
|
| 242 |
+
});
|
| 243 |
+
|
| 244 |
+
if (response.ok) {
|
| 245 |
+
const data = await response.json();
|
| 246 |
+
setMessages(prev => [...prev, {
|
| 247 |
+
role: 'assistant',
|
| 248 |
+
content: data.response
|
| 249 |
+
}]);
|
| 250 |
+
} else {
|
| 251 |
+
// Fallback to local response
|
| 252 |
+
setMessages(prev => [...prev, {
|
| 253 |
+
role: 'assistant',
|
| 254 |
+
content: generateLocalResponse(currentInput)
|
| 255 |
+
}]);
|
| 256 |
+
}
|
| 257 |
+
} catch (error) {
|
| 258 |
+
// API not available, use local response
|
| 259 |
+
console.log('Using local response (API not available)');
|
| 260 |
+
setMessages(prev => [...prev, {
|
| 261 |
+
role: 'assistant',
|
| 262 |
+
content: generateLocalResponse(currentInput)
|
| 263 |
+
}]);
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
setIsSending(false);
|
| 267 |
+
};
|
| 268 |
+
|
| 269 |
+
const handleEditorDidMount = (editor: any) => {
|
| 270 |
+
editorRef.current = editor;
|
| 271 |
+
};
|
| 272 |
+
|
| 273 |
+
return (
|
| 274 |
+
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
| 275 |
+
{/* Animated background elements */}
|
| 276 |
+
<div className="absolute inset-0 overflow-hidden">
|
| 277 |
+
<div className="absolute -top-40 -right-40 w-80 h-80 bg-purple-200 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-pulse"></div>
|
| 278 |
+
<div className="absolute -bottom-40 -left-40 w-80 h-80 bg-blue-200 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-pulse animation-delay-2000"></div>
|
| 279 |
+
</div>
|
| 280 |
+
|
| 281 |
+
<div className="relative">
|
| 282 |
+
<header className="bg-white/80 backdrop-blur-md shadow-lg border-b border-white/20 sticky top-0 z-10">
|
| 283 |
+
<div className="max-w-7xl mx-auto px-4 py-4 sm:px-6 lg:px-8">
|
| 284 |
+
<div className="flex items-center justify-between">
|
| 285 |
+
<div>
|
| 286 |
+
<h1 className="text-3xl font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
|
| 287 |
+
Python Learning Studio
|
| 288 |
+
</h1>
|
| 289 |
+
<p className="text-gray-600 mt-1">Interactive coding with AI-powered tutoring</p>
|
| 290 |
+
</div>
|
| 291 |
+
<div className="flex items-center space-x-4">
|
| 292 |
+
<div className="bg-gradient-to-r from-blue-100 to-purple-100 rounded-lg px-4 py-2">
|
| 293 |
+
<span className="text-sm font-medium text-gray-700">Module: Control Flow</span>
|
| 294 |
+
</div>
|
| 295 |
+
<div className="w-10 h-10 bg-gradient-to-r from-green-400 to-blue-500 rounded-full flex items-center justify-center">
|
| 296 |
+
<span className="text-white font-bold text-sm">A</span>
|
| 297 |
+
</div>
|
| 298 |
+
</div>
|
| 299 |
+
</div>
|
| 300 |
+
</div>
|
| 301 |
+
</header>
|
| 302 |
+
|
| 303 |
+
<main className="max-w-7xl mx-auto px-4 py-8 sm:px-6 lg:px-8">
|
| 304 |
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">
|
| 305 |
+
{/* Chat Interface */}
|
| 306 |
+
<div className="bg-white/70 backdrop-blur-sm rounded-2xl shadow-xl border border-white/20 overflow-hidden">
|
| 307 |
+
<div className="p-6 border-b border-white/20">
|
| 308 |
+
<div className="flex items-center">
|
| 309 |
+
<div className="w-3 h-3 bg-green-500 rounded-full mr-2"></div>
|
| 310 |
+
<h2 className="text-xl font-bold text-gray-800 flex items-center">
|
| 311 |
+
<svg className="w-5 h-5 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 312 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
|
| 313 |
+
</svg>
|
| 314 |
+
AI Tutor Chat
|
| 315 |
+
</h2>
|
| 316 |
+
</div>
|
| 317 |
+
</div>
|
| 318 |
+
|
| 319 |
+
<div className="h-96 overflow-y-auto p-6 bg-gradient-to-b from-gray-50 to-gray-100">
|
| 320 |
+
{messages.map((msg, index) => (
|
| 321 |
+
<div
|
| 322 |
+
key={index}
|
| 323 |
+
className={`mb-4 p-4 rounded-2xl max-w-[85%] ${
|
| 324 |
+
msg.role === 'user'
|
| 325 |
+
? 'ml-auto bg-gradient-to-r from-blue-500 to-purple-600 text-white shadow-lg'
|
| 326 |
+
: 'mr-auto bg-gradient-to-r from-green-100 to-blue-100 text-gray-800 shadow-md'
|
| 327 |
+
}`}
|
| 328 |
+
>
|
| 329 |
+
<div className="flex items-start">
|
| 330 |
+
{msg.role === 'assistant' && (
|
| 331 |
+
<div className="w-6 h-6 bg-gradient-to-r from-green-400 to-blue-500 rounded-full flex items-center justify-center mr-2 flex-shrink-0">
|
| 332 |
+
<svg className="w-3 h-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 333 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
|
| 334 |
+
</svg>
|
| 335 |
+
</div>
|
| 336 |
+
)}
|
| 337 |
+
<div className={`${msg.role === 'user' ? 'text-white' : 'text-gray-800'}`}>
|
| 338 |
+
{msg.content}
|
| 339 |
+
</div>
|
| 340 |
+
</div>
|
| 341 |
+
</div>
|
| 342 |
+
))}
|
| 343 |
+
</div>
|
| 344 |
+
|
| 345 |
+
<div className="p-4 border-t border-white/20 bg-white/50">
|
| 346 |
+
<div className="flex">
|
| 347 |
+
<input
|
| 348 |
+
type="text"
|
| 349 |
+
value={inputMessage}
|
| 350 |
+
onChange={(e) => setInputMessage(e.target.value)}
|
| 351 |
+
onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()}
|
| 352 |
+
placeholder="Ask a Python question..."
|
| 353 |
+
className="flex-1 bg-white/80 backdrop-blur-sm border border-gray-200 rounded-l-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200"
|
| 354 |
+
/>
|
| 355 |
+
<button
|
| 356 |
+
onClick={handleSendMessage}
|
| 357 |
+
disabled={isSending || !inputMessage.trim()}
|
| 358 |
+
className="bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white px-6 py-3 rounded-r-xl disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-300 transform hover:scale-105 font-medium"
|
| 359 |
+
>
|
| 360 |
+
<div className="flex items-center">
|
| 361 |
+
{isSending ? (
|
| 362 |
+
<>
|
| 363 |
+
<svg className="animate-spin w-4 h-4 mr-1" fill="none" viewBox="0 0 24 24">
|
| 364 |
+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
| 365 |
+
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
| 366 |
+
</svg>
|
| 367 |
+
Thinking...
|
| 368 |
+
</>
|
| 369 |
+
) : (
|
| 370 |
+
<>
|
| 371 |
+
<svg className="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 372 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
|
| 373 |
+
</svg>
|
| 374 |
+
Send
|
| 375 |
+
</>
|
| 376 |
+
)}
|
| 377 |
+
</div>
|
| 378 |
+
</button>
|
| 379 |
+
</div>
|
| 380 |
+
</div>
|
| 381 |
+
</div>
|
| 382 |
+
|
| 383 |
+
{/* Code Editor */}
|
| 384 |
+
<div className="bg-white/70 backdrop-blur-sm rounded-2xl shadow-xl border border-white/20 overflow-hidden">
|
| 385 |
+
<div className="p-5 border-b border-white/20 bg-gradient-to-r from-gray-50 to-gray-100">
|
| 386 |
+
<div className="flex justify-between items-center">
|
| 387 |
+
<h2 className="text-xl font-bold text-gray-800 flex items-center">
|
| 388 |
+
<svg className="w-5 h-5 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 389 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
|
| 390 |
+
</svg>
|
| 391 |
+
Python Code Editor
|
| 392 |
+
</h2>
|
| 393 |
+
<button
|
| 394 |
+
onClick={handleRunCode}
|
| 395 |
+
disabled={isLoading}
|
| 396 |
+
className="bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 text-white px-6 py-2 rounded-lg disabled:opacity-50 transition-all duration-300 transform hover:scale-105 font-medium flex items-center"
|
| 397 |
+
>
|
| 398 |
+
{isLoading ? (
|
| 399 |
+
<>
|
| 400 |
+
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" fill="none" viewBox="0 0 24 24">
|
| 401 |
+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
| 402 |
+
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
| 403 |
+
</svg>
|
| 404 |
+
Running...
|
| 405 |
+
</>
|
| 406 |
+
) : (
|
| 407 |
+
<>
|
| 408 |
+
<svg className="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 409 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14.828 14.828a4 4 0 01-5.656 0M9 10h1m4 0h1m-6 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
| 410 |
+
</svg>
|
| 411 |
+
Run Code
|
| 412 |
+
</>
|
| 413 |
+
)}
|
| 414 |
+
</button>
|
| 415 |
+
</div>
|
| 416 |
+
</div>
|
| 417 |
+
|
| 418 |
+
<div className="h-80">
|
| 419 |
+
<MonacoEditor
|
| 420 |
+
height="100%"
|
| 421 |
+
language="python"
|
| 422 |
+
value={code}
|
| 423 |
+
onChange={(value) => setCode(value || '')}
|
| 424 |
+
onMount={handleEditorDidMount}
|
| 425 |
+
theme="vs-light"
|
| 426 |
+
options={{
|
| 427 |
+
minimap: { enabled: true },
|
| 428 |
+
fontSize: 14,
|
| 429 |
+
scrollBeyondLastLine: false,
|
| 430 |
+
automaticLayout: true,
|
| 431 |
+
fontFamily: 'Consolas, "Courier New", monospace',
|
| 432 |
+
}}
|
| 433 |
+
/>
|
| 434 |
+
</div>
|
| 435 |
+
|
| 436 |
+
<div className="p-4 border-t border-white/20 bg-gray-50/50">
|
| 437 |
+
<h3 className="font-bold text-gray-800 mb-2 flex items-center">
|
| 438 |
+
<svg className="w-4 h-4 mr-2 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 439 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
| 440 |
+
</svg>
|
| 441 |
+
Output
|
| 442 |
+
</h3>
|
| 443 |
+
<div className="bg-gray-900 text-green-400 p-4 rounded-lg font-mono text-sm h-32 overflow-auto border border-gray-700">
|
| 444 |
+
{output || 'Click "Run Code" to execute your Python code...'}
|
| 445 |
+
</div>
|
| 446 |
+
</div>
|
| 447 |
+
</div>
|
| 448 |
+
</div>
|
| 449 |
+
|
| 450 |
+
{/* Learning Resources */}
|
| 451 |
+
<div className="bg-white/70 backdrop-blur-sm rounded-2xl shadow-xl border border-white/20 p-6">
|
| 452 |
+
<h2 className="text-2xl font-bold text-gray-800 mb-6 flex items-center">
|
| 453 |
+
<svg className="w-6 h-6 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 454 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.746 0 3.332.477 4.5 1.253v13C19.832 18.477 18.246 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
|
| 455 |
+
</svg>
|
| 456 |
+
Learning Resources
|
| 457 |
+
</h2>
|
| 458 |
+
|
| 459 |
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
| 460 |
+
<div className="bg-gradient-to-br from-blue-50 to-blue-100 rounded-xl p-6 border border-white/50 hover:shadow-lg transition-all duration-300 transform hover:-translate-y-1">
|
| 461 |
+
<div className="w-12 h-12 bg-gradient-to-r from-blue-500 to-blue-600 rounded-lg flex items-center justify-center mb-4">
|
| 462 |
+
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 463 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
|
| 464 |
+
</svg>
|
| 465 |
+
</div>
|
| 466 |
+
<h3 className="font-bold text-lg text-gray-800 mb-2">Python Basics</h3>
|
| 467 |
+
<p className="text-gray-600 mb-4">Variables, data types, operators, and basic syntax</p>
|
| 468 |
+
<button className="bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 text-white px-4 py-2 rounded-lg transition-all duration-300 transform hover:scale-105">
|
| 469 |
+
Start Module
|
| 470 |
+
</button>
|
| 471 |
+
</div>
|
| 472 |
+
|
| 473 |
+
<div className="bg-gradient-to-br from-purple-50 to-purple-100 rounded-xl p-6 border border-white/50 hover:shadow-lg transition-all duration-300 transform hover:-translate-y-1">
|
| 474 |
+
<div className="w-12 h-12 bg-gradient-to-r from-purple-500 to-purple-600 rounded-lg flex items-center justify-center mb-4">
|
| 475 |
+
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 476 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
| 477 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
| 478 |
+
</svg>
|
| 479 |
+
</div>
|
| 480 |
+
<h3 className="font-bold text-lg text-gray-800 mb-2">Control Flow</h3>
|
| 481 |
+
<p className="text-gray-600 mb-4">If statements, loops, functions, and control structures</p>
|
| 482 |
+
<button className="bg-gradient-to-r from-purple-500 to-purple-600 hover:from-purple-600 hover:to-purple-700 text-white px-4 py-2 rounded-lg transition-all duration-300 transform hover:scale-105">
|
| 483 |
+
Start Module
|
| 484 |
+
</button>
|
| 485 |
+
</div>
|
| 486 |
+
|
| 487 |
+
<div className="bg-gradient-to-br from-green-50 to-green-100 rounded-xl p-6 border border-white/50 hover:shadow-lg transition-all duration-300 transform hover:-translate-y-1">
|
| 488 |
+
<div className="w-12 h-12 bg-gradient-to-r from-green-500 to-green-600 rounded-lg flex items-center justify-center mb-4">
|
| 489 |
+
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 490 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z" />
|
| 491 |
+
</svg>
|
| 492 |
+
</div>
|
| 493 |
+
<h3 className="font-bold text-lg text-gray-800 mb-2">Practice Exercises</h3>
|
| 494 |
+
<p className="text-gray-600 mb-4">Hands-on coding challenges and projects</p>
|
| 495 |
+
<button className="bg-gradient-to-r from-green-500 to-green-600 hover:from-green-600 hover:to-green-700 text-white px-4 py-2 rounded-lg transition-all duration-300 transform hover:scale-105">
|
| 496 |
+
Start Practice
|
| 497 |
+
</button>
|
| 498 |
+
</div>
|
| 499 |
+
</div>
|
| 500 |
+
</div>
|
| 501 |
+
</main>
|
| 502 |
+
</div>
|
| 503 |
+
|
| 504 |
+
<style jsx global>{`
|
| 505 |
+
.animation-delay-2000 {
|
| 506 |
+
animation-delay: 2s;
|
| 507 |
+
}
|
| 508 |
+
.animation-delay-4000 {
|
| 509 |
+
animation-delay: 4s;
|
| 510 |
+
}
|
| 511 |
+
@keyframes pulse {
|
| 512 |
+
0%, 100% { opacity: 0.2; }
|
| 513 |
+
50% { opacity: 0.3; }
|
| 514 |
+
}
|
| 515 |
+
.animate-pulse {
|
| 516 |
+
animation: pulse 6s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
| 517 |
+
}
|
| 518 |
+
`}</style>
|
| 519 |
+
</div>
|
| 520 |
+
);
|
| 521 |
+
}
|
app/student/progress/page.tsx
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import { useState, useEffect } from 'react';
|
| 4 |
+
import { useRouter } from 'next/navigation';
|
| 5 |
+
import Link from 'next/link';
|
| 6 |
+
|
| 7 |
+
type User = {
|
| 8 |
+
id: string;
|
| 9 |
+
name: string;
|
| 10 |
+
email: string;
|
| 11 |
+
role: string;
|
| 12 |
+
};
|
| 13 |
+
|
| 14 |
+
type ModuleProgress = {
|
| 15 |
+
id: string;
|
| 16 |
+
name: string;
|
| 17 |
+
mastery: number;
|
| 18 |
+
completed: number;
|
| 19 |
+
total: number;
|
| 20 |
+
lastActivity: string;
|
| 21 |
+
};
|
| 22 |
+
|
| 23 |
+
export default function ProgressPage() {
|
| 24 |
+
const router = useRouter();
|
| 25 |
+
const [user, setUser] = useState<User | null>(null);
|
| 26 |
+
const [isLoading, setIsLoading] = useState(true);
|
| 27 |
+
|
| 28 |
+
const overallStats = {
|
| 29 |
+
totalMastery: 72,
|
| 30 |
+
totalHours: 24,
|
| 31 |
+
streak: 5,
|
| 32 |
+
quizzesTaken: 12,
|
| 33 |
+
exercisesCompleted: 45,
|
| 34 |
+
rank: 'Intermediate'
|
| 35 |
+
};
|
| 36 |
+
|
| 37 |
+
const moduleProgress: ModuleProgress[] = [
|
| 38 |
+
{ id: 'mod1', name: 'Python Basics', mastery: 92, completed: 10, total: 10, lastActivity: '2 days ago' },
|
| 39 |
+
{ id: 'mod2', name: 'Control Flow', mastery: 75, completed: 8, total: 12, lastActivity: '1 day ago' },
|
| 40 |
+
{ id: 'mod3', name: 'Functions', mastery: 45, completed: 5, total: 15, lastActivity: 'Today' },
|
| 41 |
+
{ id: 'mod4', name: 'Data Structures', mastery: 30, completed: 3, total: 18, lastActivity: '3 days ago' },
|
| 42 |
+
{ id: 'mod5', name: 'OOP', mastery: 0, completed: 0, total: 20, lastActivity: 'Not started' },
|
| 43 |
+
{ id: 'mod6', name: 'File Operations', mastery: 0, completed: 0, total: 10, lastActivity: 'Not started' },
|
| 44 |
+
];
|
| 45 |
+
|
| 46 |
+
const recentAchievements = [
|
| 47 |
+
{ id: 1, title: 'Quiz Master', description: 'Scored 100% on 3 quizzes', icon: '🏆', date: '2 days ago' },
|
| 48 |
+
{ id: 2, title: 'Code Warrior', description: 'Completed 10 exercises in a day', icon: '⚔️', date: '5 days ago' },
|
| 49 |
+
{ id: 3, title: 'Fast Learner', description: 'Finished a module in record time', icon: '🚀', date: '1 week ago' },
|
| 50 |
+
];
|
| 51 |
+
|
| 52 |
+
const weeklyActivity = [
|
| 53 |
+
{ day: 'Mon', hours: 2 },
|
| 54 |
+
{ day: 'Tue', hours: 1.5 },
|
| 55 |
+
{ day: 'Wed', hours: 3 },
|
| 56 |
+
{ day: 'Thu', hours: 2.5 },
|
| 57 |
+
{ day: 'Fri', hours: 1 },
|
| 58 |
+
{ day: 'Sat', hours: 4 },
|
| 59 |
+
{ day: 'Sun', hours: 2 },
|
| 60 |
+
];
|
| 61 |
+
|
| 62 |
+
useEffect(() => {
|
| 63 |
+
const currentUser = localStorage.getItem('learnflow_current_user');
|
| 64 |
+
if (!currentUser) {
|
| 65 |
+
router.push('/');
|
| 66 |
+
return;
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
const parsedUser = JSON.parse(currentUser);
|
| 70 |
+
if (parsedUser.role !== 'student') {
|
| 71 |
+
router.push('/teacher/dashboard');
|
| 72 |
+
return;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
setUser(parsedUser);
|
| 76 |
+
setIsLoading(false);
|
| 77 |
+
}, [router]);
|
| 78 |
+
|
| 79 |
+
if (isLoading) {
|
| 80 |
+
return (
|
| 81 |
+
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center">
|
| 82 |
+
<div className="text-center">
|
| 83 |
+
<div className="w-16 h-16 border-4 border-blue-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"></div>
|
| 84 |
+
<p className="text-gray-600">Loading progress...</p>
|
| 85 |
+
</div>
|
| 86 |
+
</div>
|
| 87 |
+
);
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
return (
|
| 91 |
+
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
| 92 |
+
<header className="bg-white/80 backdrop-blur-md shadow-lg border-b border-white/20 sticky top-0 z-10">
|
| 93 |
+
<div className="max-w-7xl mx-auto px-4 py-4 sm:px-6 lg:px-8">
|
| 94 |
+
<div className="flex items-center justify-between">
|
| 95 |
+
<Link href="/student/dashboard" className="flex items-center space-x-3">
|
| 96 |
+
<div className="w-10 h-10 bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg flex items-center justify-center">
|
| 97 |
+
<span className="text-white font-bold text-sm">LF</span>
|
| 98 |
+
</div>
|
| 99 |
+
<span className="text-xl font-bold text-gray-900">My Progress</span>
|
| 100 |
+
</Link>
|
| 101 |
+
<Link
|
| 102 |
+
href="/student/dashboard"
|
| 103 |
+
className="text-gray-600 hover:text-gray-800 font-medium flex items-center"
|
| 104 |
+
>
|
| 105 |
+
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 106 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
| 107 |
+
</svg>
|
| 108 |
+
Back to Dashboard
|
| 109 |
+
</Link>
|
| 110 |
+
</div>
|
| 111 |
+
</div>
|
| 112 |
+
</header>
|
| 113 |
+
|
| 114 |
+
<main className="max-w-7xl mx-auto px-4 py-8 sm:px-6 lg:px-8">
|
| 115 |
+
{/* Overall Stats */}
|
| 116 |
+
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4 mb-8">
|
| 117 |
+
<div className="bg-white rounded-xl p-4 shadow-lg">
|
| 118 |
+
<div className="text-3xl font-bold text-blue-600">{overallStats.totalMastery}%</div>
|
| 119 |
+
<div className="text-sm text-gray-500">Overall Mastery</div>
|
| 120 |
+
</div>
|
| 121 |
+
<div className="bg-white rounded-xl p-4 shadow-lg">
|
| 122 |
+
<div className="text-3xl font-bold text-green-600">{overallStats.totalHours}h</div>
|
| 123 |
+
<div className="text-sm text-gray-500">Total Hours</div>
|
| 124 |
+
</div>
|
| 125 |
+
<div className="bg-white rounded-xl p-4 shadow-lg">
|
| 126 |
+
<div className="text-3xl font-bold text-orange-600">{overallStats.streak}</div>
|
| 127 |
+
<div className="text-sm text-gray-500">Day Streak</div>
|
| 128 |
+
</div>
|
| 129 |
+
<div className="bg-white rounded-xl p-4 shadow-lg">
|
| 130 |
+
<div className="text-3xl font-bold text-purple-600">{overallStats.quizzesTaken}</div>
|
| 131 |
+
<div className="text-sm text-gray-500">Quizzes Taken</div>
|
| 132 |
+
</div>
|
| 133 |
+
<div className="bg-white rounded-xl p-4 shadow-lg">
|
| 134 |
+
<div className="text-3xl font-bold text-teal-600">{overallStats.exercisesCompleted}</div>
|
| 135 |
+
<div className="text-sm text-gray-500">Exercises Done</div>
|
| 136 |
+
</div>
|
| 137 |
+
<div className="bg-white rounded-xl p-4 shadow-lg">
|
| 138 |
+
<div className="text-3xl font-bold text-indigo-600">{overallStats.rank}</div>
|
| 139 |
+
<div className="text-sm text-gray-500">Current Rank</div>
|
| 140 |
+
</div>
|
| 141 |
+
</div>
|
| 142 |
+
|
| 143 |
+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
| 144 |
+
{/* Module Progress */}
|
| 145 |
+
<div className="lg:col-span-2 bg-white rounded-2xl shadow-xl p-6">
|
| 146 |
+
<h2 className="text-xl font-bold text-gray-900 mb-6">Module Progress</h2>
|
| 147 |
+
<div className="space-y-4">
|
| 148 |
+
{moduleProgress.map((module) => (
|
| 149 |
+
<div key={module.id} className="border border-gray-100 rounded-xl p-4">
|
| 150 |
+
<div className="flex justify-between items-start mb-2">
|
| 151 |
+
<div>
|
| 152 |
+
<h3 className="font-semibold text-gray-900">{module.name}</h3>
|
| 153 |
+
<p className="text-sm text-gray-500">{module.completed}/{module.total} lessons completed</p>
|
| 154 |
+
</div>
|
| 155 |
+
<span className={`px-3 py-1 rounded-full text-sm font-medium ${
|
| 156 |
+
module.mastery >= 80 ? 'bg-green-100 text-green-800' :
|
| 157 |
+
module.mastery >= 50 ? 'bg-yellow-100 text-yellow-800' :
|
| 158 |
+
module.mastery > 0 ? 'bg-orange-100 text-orange-800' :
|
| 159 |
+
'bg-gray-100 text-gray-600'
|
| 160 |
+
}`}>
|
| 161 |
+
{module.mastery}% Mastery
|
| 162 |
+
</span>
|
| 163 |
+
</div>
|
| 164 |
+
<div className="w-full bg-gray-200 rounded-full h-2 mb-2">
|
| 165 |
+
<div
|
| 166 |
+
className={`h-2 rounded-full ${
|
| 167 |
+
module.mastery >= 80 ? 'bg-green-500' :
|
| 168 |
+
module.mastery >= 50 ? 'bg-yellow-500' :
|
| 169 |
+
module.mastery > 0 ? 'bg-orange-500' :
|
| 170 |
+
'bg-gray-400'
|
| 171 |
+
}`}
|
| 172 |
+
style={{ width: `${module.mastery}%` }}
|
| 173 |
+
></div>
|
| 174 |
+
</div>
|
| 175 |
+
<p className="text-xs text-gray-400">Last activity: {module.lastActivity}</p>
|
| 176 |
+
</div>
|
| 177 |
+
))}
|
| 178 |
+
</div>
|
| 179 |
+
</div>
|
| 180 |
+
|
| 181 |
+
{/* Sidebar */}
|
| 182 |
+
<div className="space-y-6">
|
| 183 |
+
{/* Weekly Activity */}
|
| 184 |
+
<div className="bg-white rounded-2xl shadow-xl p-6">
|
| 185 |
+
<h2 className="text-xl font-bold text-gray-900 mb-4">Weekly Activity</h2>
|
| 186 |
+
<div className="flex items-end justify-between h-32">
|
| 187 |
+
{weeklyActivity.map((day, index) => (
|
| 188 |
+
<div key={index} className="flex flex-col items-center">
|
| 189 |
+
<div
|
| 190 |
+
className="w-8 bg-blue-500 rounded-t"
|
| 191 |
+
style={{ height: `${(day.hours / 4) * 100}%` }}
|
| 192 |
+
></div>
|
| 193 |
+
<span className="text-xs text-gray-500 mt-2">{day.day}</span>
|
| 194 |
+
</div>
|
| 195 |
+
))}
|
| 196 |
+
</div>
|
| 197 |
+
<div className="text-center mt-4 text-sm text-gray-600">
|
| 198 |
+
Total this week: {weeklyActivity.reduce((acc, d) => acc + d.hours, 0)} hours
|
| 199 |
+
</div>
|
| 200 |
+
</div>
|
| 201 |
+
|
| 202 |
+
{/* Achievements */}
|
| 203 |
+
<div className="bg-white rounded-2xl shadow-xl p-6">
|
| 204 |
+
<h2 className="text-xl font-bold text-gray-900 mb-4">Recent Achievements</h2>
|
| 205 |
+
<div className="space-y-4">
|
| 206 |
+
{recentAchievements.map((achievement) => (
|
| 207 |
+
<div key={achievement.id} className="flex items-start space-x-3">
|
| 208 |
+
<div className="text-2xl">{achievement.icon}</div>
|
| 209 |
+
<div>
|
| 210 |
+
<h3 className="font-semibold text-gray-900">{achievement.title}</h3>
|
| 211 |
+
<p className="text-sm text-gray-500">{achievement.description}</p>
|
| 212 |
+
<p className="text-xs text-gray-400">{achievement.date}</p>
|
| 213 |
+
</div>
|
| 214 |
+
</div>
|
| 215 |
+
))}
|
| 216 |
+
</div>
|
| 217 |
+
</div>
|
| 218 |
+
</div>
|
| 219 |
+
</div>
|
| 220 |
+
</main>
|
| 221 |
+
</div>
|
| 222 |
+
);
|
| 223 |
+
}
|
app/student/quiz/page.tsx
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import { useState, useEffect } from 'react';
|
| 4 |
+
import { useRouter } from 'next/navigation';
|
| 5 |
+
import Link from 'next/link';
|
| 6 |
+
|
| 7 |
+
type Question = {
|
| 8 |
+
id: number;
|
| 9 |
+
question: string;
|
| 10 |
+
options: string[];
|
| 11 |
+
correctAnswer: number;
|
| 12 |
+
explanation: string;
|
| 13 |
+
};
|
| 14 |
+
|
| 15 |
+
type User = {
|
| 16 |
+
id: string;
|
| 17 |
+
name: string;
|
| 18 |
+
email: string;
|
| 19 |
+
role: string;
|
| 20 |
+
};
|
| 21 |
+
|
| 22 |
+
export default function QuizPage() {
|
| 23 |
+
const router = useRouter();
|
| 24 |
+
const [user, setUser] = useState<User | null>(null);
|
| 25 |
+
const [isLoading, setIsLoading] = useState(true);
|
| 26 |
+
const [currentQuestion, setCurrentQuestion] = useState(0);
|
| 27 |
+
const [selectedAnswer, setSelectedAnswer] = useState<number | null>(null);
|
| 28 |
+
const [showResult, setShowResult] = useState(false);
|
| 29 |
+
const [score, setScore] = useState(0);
|
| 30 |
+
const [quizCompleted, setQuizCompleted] = useState(false);
|
| 31 |
+
const [answers, setAnswers] = useState<number[]>([]);
|
| 32 |
+
|
| 33 |
+
const questions: Question[] = [
|
| 34 |
+
{
|
| 35 |
+
id: 1,
|
| 36 |
+
question: "What is the correct way to create a variable in Python?",
|
| 37 |
+
options: ["var x = 5", "x = 5", "int x = 5", "let x = 5"],
|
| 38 |
+
correctAnswer: 1,
|
| 39 |
+
explanation: "In Python, you create variables by simply assigning a value. No keyword like 'var', 'let', or type declaration is needed."
|
| 40 |
+
},
|
| 41 |
+
{
|
| 42 |
+
id: 2,
|
| 43 |
+
question: "Which of the following is a valid Python list?",
|
| 44 |
+
options: ["[1, 2, 3]", "{1, 2, 3}", "(1, 2, 3)", "<1, 2, 3>"],
|
| 45 |
+
correctAnswer: 0,
|
| 46 |
+
explanation: "Lists in Python are created using square brackets []. Curly braces {} create sets or dicts, and parentheses () create tuples."
|
| 47 |
+
},
|
| 48 |
+
{
|
| 49 |
+
id: 3,
|
| 50 |
+
question: "What does the 'len()' function do?",
|
| 51 |
+
options: ["Converts to lowercase", "Returns the length of an object", "Creates a new list", "Removes an item"],
|
| 52 |
+
correctAnswer: 1,
|
| 53 |
+
explanation: "The len() function returns the number of items in an object like a string, list, tuple, etc."
|
| 54 |
+
},
|
| 55 |
+
{
|
| 56 |
+
id: 4,
|
| 57 |
+
question: "How do you start a for loop in Python?",
|
| 58 |
+
options: ["for (i = 0; i < 10; i++)", "for i in range(10):", "foreach i in range(10)", "loop i from 0 to 10"],
|
| 59 |
+
correctAnswer: 1,
|
| 60 |
+
explanation: "Python uses 'for item in iterable:' syntax. The range() function generates a sequence of numbers."
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
id: 5,
|
| 64 |
+
question: "What will print(type(5.0)) output?",
|
| 65 |
+
options: ["<class 'int'>", "<class 'float'>", "<class 'number'>", "<class 'decimal'>"],
|
| 66 |
+
correctAnswer: 1,
|
| 67 |
+
explanation: "5.0 is a floating-point number in Python, so type() returns <class 'float'>."
|
| 68 |
+
}
|
| 69 |
+
];
|
| 70 |
+
|
| 71 |
+
useEffect(() => {
|
| 72 |
+
const currentUser = localStorage.getItem('learnflow_current_user');
|
| 73 |
+
if (!currentUser) {
|
| 74 |
+
router.push('/');
|
| 75 |
+
return;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
const parsedUser = JSON.parse(currentUser);
|
| 79 |
+
if (parsedUser.role !== 'student') {
|
| 80 |
+
router.push('/teacher/dashboard');
|
| 81 |
+
return;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
setUser(parsedUser);
|
| 85 |
+
setIsLoading(false);
|
| 86 |
+
}, [router]);
|
| 87 |
+
|
| 88 |
+
const handleAnswerSelect = (answerIndex: number) => {
|
| 89 |
+
if (showResult) return;
|
| 90 |
+
setSelectedAnswer(answerIndex);
|
| 91 |
+
};
|
| 92 |
+
|
| 93 |
+
const handleSubmitAnswer = () => {
|
| 94 |
+
if (selectedAnswer === null) return;
|
| 95 |
+
|
| 96 |
+
const newAnswers = [...answers, selectedAnswer];
|
| 97 |
+
setAnswers(newAnswers);
|
| 98 |
+
|
| 99 |
+
if (selectedAnswer === questions[currentQuestion].correctAnswer) {
|
| 100 |
+
setScore(score + 1);
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
setShowResult(true);
|
| 104 |
+
};
|
| 105 |
+
|
| 106 |
+
const handleNextQuestion = () => {
|
| 107 |
+
if (currentQuestion < questions.length - 1) {
|
| 108 |
+
setCurrentQuestion(currentQuestion + 1);
|
| 109 |
+
setSelectedAnswer(null);
|
| 110 |
+
setShowResult(false);
|
| 111 |
+
} else {
|
| 112 |
+
setQuizCompleted(true);
|
| 113 |
+
}
|
| 114 |
+
};
|
| 115 |
+
|
| 116 |
+
const handleRestartQuiz = () => {
|
| 117 |
+
setCurrentQuestion(0);
|
| 118 |
+
setSelectedAnswer(null);
|
| 119 |
+
setShowResult(false);
|
| 120 |
+
setScore(0);
|
| 121 |
+
setQuizCompleted(false);
|
| 122 |
+
setAnswers([]);
|
| 123 |
+
};
|
| 124 |
+
|
| 125 |
+
if (isLoading) {
|
| 126 |
+
return (
|
| 127 |
+
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center">
|
| 128 |
+
<div className="text-center">
|
| 129 |
+
<div className="w-16 h-16 border-4 border-blue-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"></div>
|
| 130 |
+
<p className="text-gray-600">Loading quiz...</p>
|
| 131 |
+
</div>
|
| 132 |
+
</div>
|
| 133 |
+
);
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
if (quizCompleted) {
|
| 137 |
+
const percentage = Math.round((score / questions.length) * 100);
|
| 138 |
+
return (
|
| 139 |
+
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
| 140 |
+
<header className="bg-white/80 backdrop-blur-md shadow-lg border-b border-white/20 sticky top-0 z-10">
|
| 141 |
+
<div className="max-w-4xl mx-auto px-4 py-4 sm:px-6 lg:px-8">
|
| 142 |
+
<div className="flex items-center justify-between">
|
| 143 |
+
<Link href="/student/dashboard" className="flex items-center space-x-3">
|
| 144 |
+
<div className="w-10 h-10 bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg flex items-center justify-center">
|
| 145 |
+
<span className="text-white font-bold text-sm">LF</span>
|
| 146 |
+
</div>
|
| 147 |
+
<span className="text-xl font-bold text-gray-900">LearnFlow</span>
|
| 148 |
+
</Link>
|
| 149 |
+
</div>
|
| 150 |
+
</div>
|
| 151 |
+
</header>
|
| 152 |
+
|
| 153 |
+
<main className="max-w-2xl mx-auto px-4 py-12">
|
| 154 |
+
<div className="bg-white rounded-2xl shadow-xl p-8 text-center">
|
| 155 |
+
<div className={`w-24 h-24 mx-auto mb-6 rounded-full flex items-center justify-center ${
|
| 156 |
+
percentage >= 80 ? 'bg-green-100' : percentage >= 60 ? 'bg-yellow-100' : 'bg-red-100'
|
| 157 |
+
}`}>
|
| 158 |
+
<span className={`text-4xl font-bold ${
|
| 159 |
+
percentage >= 80 ? 'text-green-600' : percentage >= 60 ? 'text-yellow-600' : 'text-red-600'
|
| 160 |
+
}`}>{percentage}%</span>
|
| 161 |
+
</div>
|
| 162 |
+
|
| 163 |
+
<h2 className="text-2xl font-bold text-gray-900 mb-2">Quiz Completed!</h2>
|
| 164 |
+
<p className="text-gray-600 mb-6">
|
| 165 |
+
You scored {score} out of {questions.length} questions correctly.
|
| 166 |
+
</p>
|
| 167 |
+
|
| 168 |
+
<div className={`p-4 rounded-lg mb-8 ${
|
| 169 |
+
percentage >= 80 ? 'bg-green-50 text-green-800' :
|
| 170 |
+
percentage >= 60 ? 'bg-yellow-50 text-yellow-800' :
|
| 171 |
+
'bg-red-50 text-red-800'
|
| 172 |
+
}`}>
|
| 173 |
+
{percentage >= 80 ? "Excellent! You've mastered this topic!" :
|
| 174 |
+
percentage >= 60 ? "Good job! Keep practicing to improve." :
|
| 175 |
+
"Keep learning! Review the material and try again."}
|
| 176 |
+
</div>
|
| 177 |
+
|
| 178 |
+
<div className="flex gap-4 justify-center">
|
| 179 |
+
<button
|
| 180 |
+
onClick={handleRestartQuiz}
|
| 181 |
+
className="bg-blue-500 hover:bg-blue-600 text-white px-6 py-3 rounded-lg font-medium transition-colors"
|
| 182 |
+
>
|
| 183 |
+
Retry Quiz
|
| 184 |
+
</button>
|
| 185 |
+
<Link
|
| 186 |
+
href="/student/dashboard"
|
| 187 |
+
className="bg-gray-200 hover:bg-gray-300 text-gray-800 px-6 py-3 rounded-lg font-medium transition-colors"
|
| 188 |
+
>
|
| 189 |
+
Back to Dashboard
|
| 190 |
+
</Link>
|
| 191 |
+
</div>
|
| 192 |
+
</div>
|
| 193 |
+
</main>
|
| 194 |
+
</div>
|
| 195 |
+
);
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
const question = questions[currentQuestion];
|
| 199 |
+
|
| 200 |
+
return (
|
| 201 |
+
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
| 202 |
+
<header className="bg-white/80 backdrop-blur-md shadow-lg border-b border-white/20 sticky top-0 z-10">
|
| 203 |
+
<div className="max-w-4xl mx-auto px-4 py-4 sm:px-6 lg:px-8">
|
| 204 |
+
<div className="flex items-center justify-between">
|
| 205 |
+
<Link href="/student/dashboard" className="flex items-center space-x-3">
|
| 206 |
+
<div className="w-10 h-10 bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg flex items-center justify-center">
|
| 207 |
+
<span className="text-white font-bold text-sm">LF</span>
|
| 208 |
+
</div>
|
| 209 |
+
<span className="text-xl font-bold text-gray-900">LearnFlow Quiz</span>
|
| 210 |
+
</Link>
|
| 211 |
+
<div className="text-sm text-gray-600">
|
| 212 |
+
Question {currentQuestion + 1} of {questions.length}
|
| 213 |
+
</div>
|
| 214 |
+
</div>
|
| 215 |
+
</div>
|
| 216 |
+
</header>
|
| 217 |
+
|
| 218 |
+
<main className="max-w-2xl mx-auto px-4 py-8">
|
| 219 |
+
{/* Progress Bar */}
|
| 220 |
+
<div className="mb-8">
|
| 221 |
+
<div className="w-full bg-gray-200 rounded-full h-2">
|
| 222 |
+
<div
|
| 223 |
+
className="bg-blue-500 h-2 rounded-full transition-all duration-300"
|
| 224 |
+
style={{ width: `${((currentQuestion + 1) / questions.length) * 100}%` }}
|
| 225 |
+
></div>
|
| 226 |
+
</div>
|
| 227 |
+
</div>
|
| 228 |
+
|
| 229 |
+
<div className="bg-white rounded-2xl shadow-xl p-8">
|
| 230 |
+
<h2 className="text-xl font-bold text-gray-900 mb-6">{question.question}</h2>
|
| 231 |
+
|
| 232 |
+
<div className="space-y-3 mb-8">
|
| 233 |
+
{question.options.map((option, index) => (
|
| 234 |
+
<button
|
| 235 |
+
key={index}
|
| 236 |
+
onClick={() => handleAnswerSelect(index)}
|
| 237 |
+
disabled={showResult}
|
| 238 |
+
className={`w-full p-4 text-left rounded-lg border-2 transition-all ${
|
| 239 |
+
showResult
|
| 240 |
+
? index === question.correctAnswer
|
| 241 |
+
? 'border-green-500 bg-green-50'
|
| 242 |
+
: index === selectedAnswer
|
| 243 |
+
? 'border-red-500 bg-red-50'
|
| 244 |
+
: 'border-gray-200'
|
| 245 |
+
: selectedAnswer === index
|
| 246 |
+
? 'border-blue-500 bg-blue-50'
|
| 247 |
+
: 'border-gray-200 hover:border-blue-300 hover:bg-blue-50'
|
| 248 |
+
}`}
|
| 249 |
+
>
|
| 250 |
+
<div className="flex items-center">
|
| 251 |
+
<span className={`w-8 h-8 rounded-full flex items-center justify-center mr-3 text-sm font-medium ${
|
| 252 |
+
showResult
|
| 253 |
+
? index === question.correctAnswer
|
| 254 |
+
? 'bg-green-500 text-white'
|
| 255 |
+
: index === selectedAnswer
|
| 256 |
+
? 'bg-red-500 text-white'
|
| 257 |
+
: 'bg-gray-200 text-gray-600'
|
| 258 |
+
: selectedAnswer === index
|
| 259 |
+
? 'bg-blue-500 text-white'
|
| 260 |
+
: 'bg-gray-200 text-gray-600'
|
| 261 |
+
}`}>
|
| 262 |
+
{String.fromCharCode(65 + index)}
|
| 263 |
+
</span>
|
| 264 |
+
<span className="text-gray-800">{option}</span>
|
| 265 |
+
</div>
|
| 266 |
+
</button>
|
| 267 |
+
))}
|
| 268 |
+
</div>
|
| 269 |
+
|
| 270 |
+
{showResult && (
|
| 271 |
+
<div className={`p-4 rounded-lg mb-6 ${
|
| 272 |
+
selectedAnswer === question.correctAnswer ? 'bg-green-50 text-green-800' : 'bg-red-50 text-red-800'
|
| 273 |
+
}`}>
|
| 274 |
+
<p className="font-medium mb-1">
|
| 275 |
+
{selectedAnswer === question.correctAnswer ? 'Correct!' : 'Incorrect!'}
|
| 276 |
+
</p>
|
| 277 |
+
<p className="text-sm">{question.explanation}</p>
|
| 278 |
+
</div>
|
| 279 |
+
)}
|
| 280 |
+
|
| 281 |
+
<div className="flex justify-between">
|
| 282 |
+
<Link
|
| 283 |
+
href="/student/dashboard"
|
| 284 |
+
className="text-gray-600 hover:text-gray-800 font-medium"
|
| 285 |
+
>
|
| 286 |
+
Exit Quiz
|
| 287 |
+
</Link>
|
| 288 |
+
|
| 289 |
+
{!showResult ? (
|
| 290 |
+
<button
|
| 291 |
+
onClick={handleSubmitAnswer}
|
| 292 |
+
disabled={selectedAnswer === null}
|
| 293 |
+
className="bg-blue-500 hover:bg-blue-600 disabled:bg-gray-300 disabled:cursor-not-allowed text-white px-6 py-2 rounded-lg font-medium transition-colors"
|
| 294 |
+
>
|
| 295 |
+
Submit Answer
|
| 296 |
+
</button>
|
| 297 |
+
) : (
|
| 298 |
+
<button
|
| 299 |
+
onClick={handleNextQuestion}
|
| 300 |
+
className="bg-blue-500 hover:bg-blue-600 text-white px-6 py-2 rounded-lg font-medium transition-colors"
|
| 301 |
+
>
|
| 302 |
+
{currentQuestion < questions.length - 1 ? 'Next Question' : 'See Results'}
|
| 303 |
+
</button>
|
| 304 |
+
)}
|
| 305 |
+
</div>
|
| 306 |
+
</div>
|
| 307 |
+
</main>
|
| 308 |
+
</div>
|
| 309 |
+
);
|
| 310 |
+
}
|
app/teacher/dashboard/page.tsx
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import { useState, useEffect } from 'react';
|
| 4 |
+
import { useRouter } from 'next/navigation';
|
| 5 |
+
import Link from 'next/link';
|
| 6 |
+
|
| 7 |
+
type Student = {
|
| 8 |
+
id: string;
|
| 9 |
+
name: string;
|
| 10 |
+
currentModule: string;
|
| 11 |
+
masteryPercentage: number;
|
| 12 |
+
lastActive: string;
|
| 13 |
+
status: 'active' | 'struggling' | 'idle';
|
| 14 |
+
};
|
| 15 |
+
|
| 16 |
+
type StruggleAlert = {
|
| 17 |
+
id: string;
|
| 18 |
+
studentId: string;
|
| 19 |
+
studentName: string;
|
| 20 |
+
issue: string;
|
| 21 |
+
timestamp: string;
|
| 22 |
+
resolved: boolean;
|
| 23 |
+
};
|
| 24 |
+
|
| 25 |
+
type User = {
|
| 26 |
+
id: string;
|
| 27 |
+
name: string;
|
| 28 |
+
email: string;
|
| 29 |
+
role: string;
|
| 30 |
+
};
|
| 31 |
+
|
| 32 |
+
export default function TeacherDashboard() {
|
| 33 |
+
const router = useRouter();
|
| 34 |
+
const [user, setUser] = useState<User | null>(null);
|
| 35 |
+
const [isLoading, setIsLoading] = useState(true);
|
| 36 |
+
|
| 37 |
+
const [students, setStudents] = useState<Student[]>([
|
| 38 |
+
{ id: '1', name: 'Alex Johnson', currentModule: 'Control Flow', masteryPercentage: 65, lastActive: '2026-01-15 14:30', status: 'active' },
|
| 39 |
+
{ id: '2', name: 'Sam Smith', currentModule: 'Functions', masteryPercentage: 42, lastActive: '2026-01-15 13:45', status: 'struggling' },
|
| 40 |
+
{ id: '3', name: 'Taylor Brown', currentModule: 'Data Structures', masteryPercentage: 88, lastActive: '2026-01-15 15:15', status: 'active' },
|
| 41 |
+
{ id: '4', name: 'Jordan Davis', currentModule: 'Basics', masteryPercentage: 92, lastActive: '2026-01-15 12:20', status: 'idle' },
|
| 42 |
+
{ id: '5', name: 'Casey Wilson', currentModule: 'OOP', masteryPercentage: 35, lastActive: '2026-01-15 14:50', status: 'struggling' },
|
| 43 |
+
{ id: '6', name: 'Morgan Lee', currentModule: 'Files', masteryPercentage: 76, lastActive: '2026-01-15 15:05', status: 'active' }
|
| 44 |
+
]);
|
| 45 |
+
|
| 46 |
+
const [alerts, setAlerts] = useState<StruggleAlert[]>([
|
| 47 |
+
{ id: 'a1', studentId: '2', studentName: 'Sam Smith', issue: 'Stuck on for loop exercise for 15 minutes', timestamp: '2026-01-15 14:25', resolved: false },
|
| 48 |
+
{ id: 'a2', studentId: '5', studentName: 'Casey Wilson', issue: 'Repeated the same syntax error 3 times', timestamp: '2026-01-15 14:48', resolved: false }
|
| 49 |
+
]);
|
| 50 |
+
|
| 51 |
+
const [classStats, setClassStats] = useState({
|
| 52 |
+
totalStudents: 24,
|
| 53 |
+
activeStudents: 18,
|
| 54 |
+
averageMastery: 68,
|
| 55 |
+
strugglingStudents: 4
|
| 56 |
+
});
|
| 57 |
+
|
| 58 |
+
useEffect(() => {
|
| 59 |
+
// Check if user is logged in
|
| 60 |
+
const currentUser = localStorage.getItem('learnflow_current_user');
|
| 61 |
+
if (!currentUser) {
|
| 62 |
+
router.push('/');
|
| 63 |
+
return;
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
const parsedUser = JSON.parse(currentUser);
|
| 67 |
+
if (parsedUser.role !== 'teacher') {
|
| 68 |
+
router.push('/student/dashboard');
|
| 69 |
+
return;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
setUser(parsedUser);
|
| 73 |
+
setIsLoading(false);
|
| 74 |
+
}, [router]);
|
| 75 |
+
|
| 76 |
+
const handleLogout = () => {
|
| 77 |
+
localStorage.removeItem('learnflow_current_user');
|
| 78 |
+
router.push('/');
|
| 79 |
+
};
|
| 80 |
+
|
| 81 |
+
if (isLoading) {
|
| 82 |
+
return (
|
| 83 |
+
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
| 84 |
+
<div className="text-center">
|
| 85 |
+
<div className="w-16 h-16 border-4 border-purple-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"></div>
|
| 86 |
+
<p className="text-gray-600">Loading dashboard...</p>
|
| 87 |
+
</div>
|
| 88 |
+
</div>
|
| 89 |
+
);
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
const resolveAlert = (alertId: string) => {
|
| 93 |
+
setAlerts(alerts.map(alert =>
|
| 94 |
+
alert.id === alertId ? {...alert, resolved: true} : alert
|
| 95 |
+
));
|
| 96 |
+
};
|
| 97 |
+
|
| 98 |
+
return (
|
| 99 |
+
<div className="min-h-screen bg-gray-50">
|
| 100 |
+
<header className="bg-white shadow">
|
| 101 |
+
<div className="max-w-7xl mx-auto px-4 py-6 sm:px-6 lg:px-8">
|
| 102 |
+
<div className="flex justify-between items-center">
|
| 103 |
+
<div>
|
| 104 |
+
<div className="flex items-center space-x-4">
|
| 105 |
+
<Link href="/" className="flex items-center">
|
| 106 |
+
<div className="w-10 h-10 bg-gradient-to-r from-purple-500 to-indigo-600 rounded-lg flex items-center justify-center">
|
| 107 |
+
<span className="text-white font-bold text-sm">LF</span>
|
| 108 |
+
</div>
|
| 109 |
+
</Link>
|
| 110 |
+
<div>
|
| 111 |
+
<h1 className="text-2xl font-bold text-gray-900">Teacher Dashboard</h1>
|
| 112 |
+
<p className="text-sm text-gray-500">Welcome, {user?.name || 'Teacher'}</p>
|
| 113 |
+
</div>
|
| 114 |
+
</div>
|
| 115 |
+
</div>
|
| 116 |
+
<div className="flex items-center space-x-4">
|
| 117 |
+
<div className="flex items-center space-x-3 bg-gray-100 rounded-full px-4 py-2">
|
| 118 |
+
<div className="w-8 h-8 bg-gradient-to-r from-purple-400 to-indigo-500 rounded-full flex items-center justify-center">
|
| 119 |
+
<span className="text-white text-sm font-semibold">{user?.name?.charAt(0).toUpperCase() || 'T'}</span>
|
| 120 |
+
</div>
|
| 121 |
+
<span className="text-gray-700 font-medium">{user?.name?.split(' ')[0] || 'Teacher'}</span>
|
| 122 |
+
</div>
|
| 123 |
+
<button
|
| 124 |
+
onClick={handleLogout}
|
| 125 |
+
className="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg transition-colors"
|
| 126 |
+
>
|
| 127 |
+
Logout
|
| 128 |
+
</button>
|
| 129 |
+
</div>
|
| 130 |
+
</div>
|
| 131 |
+
</div>
|
| 132 |
+
</header>
|
| 133 |
+
|
| 134 |
+
<main className="max-w-7xl mx-auto px-4 py-8 sm:px-6 lg:px-8">
|
| 135 |
+
{/* Class Statistics */}
|
| 136 |
+
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-4 mb-8">
|
| 137 |
+
<div className="bg-white overflow-hidden shadow rounded-lg">
|
| 138 |
+
<div className="px-4 py-5 sm:p-6">
|
| 139 |
+
<div className="flex items-center">
|
| 140 |
+
<div className="flex-shrink-0 bg-blue-500 rounded-md p-3">
|
| 141 |
+
<svg className="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 142 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
|
| 143 |
+
</svg>
|
| 144 |
+
</div>
|
| 145 |
+
<div className="ml-5 w-0 flex-1">
|
| 146 |
+
<dl>
|
| 147 |
+
<dt className="text-sm font-medium text-gray-500 truncate">Total Students</dt>
|
| 148 |
+
<dd className="text-2xl font-semibold text-gray-900">{classStats.totalStudents}</dd>
|
| 149 |
+
</dl>
|
| 150 |
+
</div>
|
| 151 |
+
</div>
|
| 152 |
+
</div>
|
| 153 |
+
</div>
|
| 154 |
+
|
| 155 |
+
<div className="bg-white overflow-hidden shadow rounded-lg">
|
| 156 |
+
<div className="px-4 py-5 sm:p-6">
|
| 157 |
+
<div className="flex items-center">
|
| 158 |
+
<div className="flex-shrink-0 bg-green-500 rounded-md p-3">
|
| 159 |
+
<svg className="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 160 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
| 161 |
+
</svg>
|
| 162 |
+
</div>
|
| 163 |
+
<div className="ml-5 w-0 flex-1">
|
| 164 |
+
<dl>
|
| 165 |
+
<dt className="text-sm font-medium text-gray-500 truncate">Active Students</dt>
|
| 166 |
+
<dd className="text-2xl font-semibold text-gray-900">{classStats.activeStudents}</dd>
|
| 167 |
+
</dl>
|
| 168 |
+
</div>
|
| 169 |
+
</div>
|
| 170 |
+
</div>
|
| 171 |
+
</div>
|
| 172 |
+
|
| 173 |
+
<div className="bg-white overflow-hidden shadow rounded-lg">
|
| 174 |
+
<div className="px-4 py-5 sm:p-6">
|
| 175 |
+
<div className="flex items-center">
|
| 176 |
+
<div className="flex-shrink-0 bg-yellow-500 rounded-md p-3">
|
| 177 |
+
<svg className="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 178 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
| 179 |
+
</svg>
|
| 180 |
+
</div>
|
| 181 |
+
<div className="ml-5 w-0 flex-1">
|
| 182 |
+
<dl>
|
| 183 |
+
<dt className="text-sm font-medium text-gray-500 truncate">Avg. Mastery</dt>
|
| 184 |
+
<dd className="text-2xl font-semibold text-gray-900">{classStats.averageMastery}%</dd>
|
| 185 |
+
</dl>
|
| 186 |
+
</div>
|
| 187 |
+
</div>
|
| 188 |
+
</div>
|
| 189 |
+
</div>
|
| 190 |
+
|
| 191 |
+
<div className="bg-white overflow-hidden shadow rounded-lg">
|
| 192 |
+
<div className="px-4 py-5 sm:p-6">
|
| 193 |
+
<div className="flex items-center">
|
| 194 |
+
<div className="flex-shrink-0 bg-red-500 rounded-md p-3">
|
| 195 |
+
<svg className="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 196 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-1.937-1-2.707.333-1.54 2.667-1.937 5.333-1.937 8z" />
|
| 197 |
+
</svg>
|
| 198 |
+
</div>
|
| 199 |
+
<div className="ml-5 w-0 flex-1">
|
| 200 |
+
<dl>
|
| 201 |
+
<dt className="text-sm font-medium text-gray-500 truncate">Struggling Students</dt>
|
| 202 |
+
<dd className="text-2xl font-semibold text-gray-900">{classStats.strugglingStudents}</dd>
|
| 203 |
+
</dl>
|
| 204 |
+
</div>
|
| 205 |
+
</div>
|
| 206 |
+
</div>
|
| 207 |
+
</div>
|
| 208 |
+
</div>
|
| 209 |
+
|
| 210 |
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
| 211 |
+
{/* Student List */}
|
| 212 |
+
<div className="bg-white shadow rounded-lg p-6">
|
| 213 |
+
<h2 className="text-xl font-semibold text-gray-800 mb-4">Student Progress</h2>
|
| 214 |
+
|
| 215 |
+
<div className="overflow-x-auto">
|
| 216 |
+
<table className="min-w-full divide-y divide-gray-200">
|
| 217 |
+
<thead className="bg-gray-50">
|
| 218 |
+
<tr>
|
| 219 |
+
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Student</th>
|
| 220 |
+
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Module</th>
|
| 221 |
+
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Mastery</th>
|
| 222 |
+
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
|
| 223 |
+
</tr>
|
| 224 |
+
</thead>
|
| 225 |
+
<tbody className="bg-white divide-y divide-gray-200">
|
| 226 |
+
{students.map((student) => (
|
| 227 |
+
<tr key={student.id}>
|
| 228 |
+
<td className="px-6 py-4 whitespace-nowrap">
|
| 229 |
+
<div className="text-sm font-medium text-gray-900">{student.name}</div>
|
| 230 |
+
</td>
|
| 231 |
+
<td className="px-6 py-4 whitespace-nowrap">
|
| 232 |
+
<div className="text-sm text-gray-500">{student.currentModule}</div>
|
| 233 |
+
</td>
|
| 234 |
+
<td className="px-6 py-4 whitespace-nowrap">
|
| 235 |
+
<div className="flex items-center">
|
| 236 |
+
<div className="w-24 bg-gray-200 rounded-full h-2.5 mr-2">
|
| 237 |
+
<div
|
| 238 |
+
className={`h-2.5 rounded-full ${
|
| 239 |
+
student.masteryPercentage >= 90 ? 'bg-blue-600' :
|
| 240 |
+
student.masteryPercentage >= 70 ? 'bg-green-500' :
|
| 241 |
+
student.masteryPercentage >= 40 ? 'bg-yellow-500' : 'bg-red-500'
|
| 242 |
+
}`}
|
| 243 |
+
style={{ width: `${student.masteryPercentage}%` }}
|
| 244 |
+
></div>
|
| 245 |
+
</div>
|
| 246 |
+
<span className="text-sm text-gray-500">{student.masteryPercentage}%</span>
|
| 247 |
+
</div>
|
| 248 |
+
</td>
|
| 249 |
+
<td className="px-6 py-4 whitespace-nowrap">
|
| 250 |
+
<span className={`px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${
|
| 251 |
+
student.status === 'active' ? 'bg-green-100 text-green-800' :
|
| 252 |
+
student.status === 'struggling' ? 'bg-red-100 text-red-800' : 'bg-gray-100 text-gray-800'
|
| 253 |
+
}`}>
|
| 254 |
+
{student.status.charAt(0).toUpperCase() + student.status.slice(1)}
|
| 255 |
+
</span>
|
| 256 |
+
</td>
|
| 257 |
+
</tr>
|
| 258 |
+
))}
|
| 259 |
+
</tbody>
|
| 260 |
+
</table>
|
| 261 |
+
</div>
|
| 262 |
+
</div>
|
| 263 |
+
|
| 264 |
+
{/* Struggle Alerts */}
|
| 265 |
+
<div className="bg-white shadow rounded-lg p-6">
|
| 266 |
+
<h2 className="text-xl font-semibold text-gray-800 mb-4">Struggle Alerts</h2>
|
| 267 |
+
|
| 268 |
+
<div className="space-y-4">
|
| 269 |
+
{alerts.filter(a => !a.resolved).map((alert) => (
|
| 270 |
+
<div key={alert.id} className="border-l-4 border-red-500 bg-red-50 p-4 rounded">
|
| 271 |
+
<div className="flex justify-between">
|
| 272 |
+
<div>
|
| 273 |
+
<p className="font-medium text-red-800">{alert.studentName}</p>
|
| 274 |
+
<p className="text-sm text-red-600">{alert.issue}</p>
|
| 275 |
+
<p className="text-xs text-red-500">{alert.timestamp}</p>
|
| 276 |
+
</div>
|
| 277 |
+
<button
|
| 278 |
+
onClick={() => resolveAlert(alert.id)}
|
| 279 |
+
className="text-sm bg-red-500 hover:bg-red-600 text-white px-3 py-1 rounded-md"
|
| 280 |
+
>
|
| 281 |
+
Resolve
|
| 282 |
+
</button>
|
| 283 |
+
</div>
|
| 284 |
+
</div>
|
| 285 |
+
))}
|
| 286 |
+
|
| 287 |
+
{alerts.filter(a => !a.resolved).length === 0 && (
|
| 288 |
+
<div className="text-center py-8 text-gray-500">
|
| 289 |
+
<p>No active struggle alerts</p>
|
| 290 |
+
</div>
|
| 291 |
+
)}
|
| 292 |
+
</div>
|
| 293 |
+
|
| 294 |
+
<div className="mt-6">
|
| 295 |
+
<h3 className="text-lg font-medium text-gray-800 mb-3">Generate Exercise</h3>
|
| 296 |
+
<div className="space-y-3">
|
| 297 |
+
<select className="w-full border border-gray-300 rounded-md px-3 py-2">
|
| 298 |
+
<option>Select student</option>
|
| 299 |
+
{students.filter(s => s.status === 'struggling').map(s => (
|
| 300 |
+
<option key={s.id} value={s.id}>{s.name}</option>
|
| 301 |
+
))}
|
| 302 |
+
</select>
|
| 303 |
+
<textarea
|
| 304 |
+
className="w-full border border-gray-300 rounded-md px-3 py-2 h-24"
|
| 305 |
+
placeholder="Describe the custom exercise to generate..."
|
| 306 |
+
></textarea>
|
| 307 |
+
<button className="w-full bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded-md">
|
| 308 |
+
Generate Exercise
|
| 309 |
+
</button>
|
| 310 |
+
</div>
|
| 311 |
+
</div>
|
| 312 |
+
</div>
|
| 313 |
+
</div>
|
| 314 |
+
</main>
|
| 315 |
+
</div>
|
| 316 |
+
);
|
| 317 |
+
}
|
lib/api.ts
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// API Service Layer for LearnFlow Frontend
|
| 2 |
+
|
| 3 |
+
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
|
| 4 |
+
|
| 5 |
+
// Service URLs
|
| 6 |
+
const SERVICES = {
|
| 7 |
+
triage: process.env.NEXT_PUBLIC_TRIAGE_URL || 'http://localhost:8001',
|
| 8 |
+
concepts: process.env.NEXT_PUBLIC_CONCEPTS_URL || 'http://localhost:8002',
|
| 9 |
+
codeReview: process.env.NEXT_PUBLIC_CODE_REVIEW_URL || 'http://localhost:8003',
|
| 10 |
+
debug: process.env.NEXT_PUBLIC_DEBUG_URL || 'http://localhost:8004',
|
| 11 |
+
exercise: process.env.NEXT_PUBLIC_EXERCISE_URL || 'http://localhost:8005',
|
| 12 |
+
progress: process.env.NEXT_PUBLIC_PROGRESS_URL || 'http://localhost:8006',
|
| 13 |
+
};
|
| 14 |
+
|
| 15 |
+
// Auth token management
|
| 16 |
+
let authToken: string | null = null;
|
| 17 |
+
|
| 18 |
+
export const setAuthToken = (token: string) => {
|
| 19 |
+
authToken = token;
|
| 20 |
+
if (typeof window !== 'undefined') {
|
| 21 |
+
localStorage.setItem('learnflow_token', token);
|
| 22 |
+
}
|
| 23 |
+
};
|
| 24 |
+
|
| 25 |
+
export const getAuthToken = (): string | null => {
|
| 26 |
+
if (authToken) return authToken;
|
| 27 |
+
if (typeof window !== 'undefined') {
|
| 28 |
+
return localStorage.getItem('learnflow_token');
|
| 29 |
+
}
|
| 30 |
+
return null;
|
| 31 |
+
};
|
| 32 |
+
|
| 33 |
+
export const clearAuthToken = () => {
|
| 34 |
+
authToken = null;
|
| 35 |
+
if (typeof window !== 'undefined') {
|
| 36 |
+
localStorage.removeItem('learnflow_token');
|
| 37 |
+
}
|
| 38 |
+
};
|
| 39 |
+
|
| 40 |
+
// Generic fetch wrapper
|
| 41 |
+
async function apiFetch<T>(
|
| 42 |
+
url: string,
|
| 43 |
+
options: RequestInit = {}
|
| 44 |
+
): Promise<T> {
|
| 45 |
+
const token = getAuthToken();
|
| 46 |
+
|
| 47 |
+
const headers: HeadersInit = {
|
| 48 |
+
'Content-Type': 'application/json',
|
| 49 |
+
...(token && { Authorization: `Bearer ${token}` }),
|
| 50 |
+
...options.headers,
|
| 51 |
+
};
|
| 52 |
+
|
| 53 |
+
const response = await fetch(url, {
|
| 54 |
+
...options,
|
| 55 |
+
headers,
|
| 56 |
+
});
|
| 57 |
+
|
| 58 |
+
if (!response.ok) {
|
| 59 |
+
const error = await response.json().catch(() => ({ message: 'Request failed' }));
|
| 60 |
+
throw new Error(error.message || `HTTP error! status: ${response.status}`);
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
return response.json();
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
// ==================== AUTH API ====================
|
| 67 |
+
|
| 68 |
+
export interface LoginRequest {
|
| 69 |
+
email: string;
|
| 70 |
+
password: string;
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
export interface RegisterRequest {
|
| 74 |
+
name: string;
|
| 75 |
+
email: string;
|
| 76 |
+
password: string;
|
| 77 |
+
role: 'student' | 'teacher';
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
export interface AuthResponse {
|
| 81 |
+
token: string;
|
| 82 |
+
user: {
|
| 83 |
+
id: string;
|
| 84 |
+
name: string;
|
| 85 |
+
email: string;
|
| 86 |
+
role: 'student' | 'teacher';
|
| 87 |
+
};
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
export const authAPI = {
|
| 91 |
+
login: async (data: LoginRequest): Promise<AuthResponse> => {
|
| 92 |
+
return apiFetch<AuthResponse>(`${API_BASE_URL}/auth/login`, {
|
| 93 |
+
method: 'POST',
|
| 94 |
+
body: JSON.stringify(data),
|
| 95 |
+
});
|
| 96 |
+
},
|
| 97 |
+
|
| 98 |
+
register: async (data: RegisterRequest): Promise<AuthResponse> => {
|
| 99 |
+
return apiFetch<AuthResponse>(`${API_BASE_URL}/auth/register`, {
|
| 100 |
+
method: 'POST',
|
| 101 |
+
body: JSON.stringify(data),
|
| 102 |
+
});
|
| 103 |
+
},
|
| 104 |
+
|
| 105 |
+
me: async (): Promise<AuthResponse['user']> => {
|
| 106 |
+
return apiFetch<AuthResponse['user']>(`${API_BASE_URL}/auth/me`);
|
| 107 |
+
},
|
| 108 |
+
};
|
| 109 |
+
|
| 110 |
+
// ==================== TRIAGE API ====================
|
| 111 |
+
|
| 112 |
+
export interface QueryRequest {
|
| 113 |
+
query: string;
|
| 114 |
+
user_id: string;
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
export interface RoutingResponse {
|
| 118 |
+
agent: string;
|
| 119 |
+
message: string;
|
| 120 |
+
params: Record<string, any>;
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
export const triageAPI = {
|
| 124 |
+
routeQuery: async (data: QueryRequest): Promise<RoutingResponse> => {
|
| 125 |
+
return apiFetch<RoutingResponse>(`${SERVICES.triage}/query`, {
|
| 126 |
+
method: 'POST',
|
| 127 |
+
body: JSON.stringify(data),
|
| 128 |
+
});
|
| 129 |
+
},
|
| 130 |
+
|
| 131 |
+
health: async (): Promise<{ status: string }> => {
|
| 132 |
+
return apiFetch<{ status: string }>(`${SERVICES.triage}/health`);
|
| 133 |
+
},
|
| 134 |
+
};
|
| 135 |
+
|
| 136 |
+
// ==================== CONCEPTS API ====================
|
| 137 |
+
|
| 138 |
+
export interface ExplainRequest {
|
| 139 |
+
topic: string;
|
| 140 |
+
level?: 'beginner' | 'intermediate' | 'advanced';
|
| 141 |
+
user_context?: Record<string, any>;
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
export interface ExplanationResponse {
|
| 145 |
+
topic: string;
|
| 146 |
+
explanation: string;
|
| 147 |
+
examples: string[];
|
| 148 |
+
level: string;
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
export const conceptsAPI = {
|
| 152 |
+
explain: async (data: ExplainRequest): Promise<ExplanationResponse> => {
|
| 153 |
+
return apiFetch<ExplanationResponse>(`${SERVICES.concepts}/explain`, {
|
| 154 |
+
method: 'POST',
|
| 155 |
+
body: JSON.stringify(data),
|
| 156 |
+
});
|
| 157 |
+
},
|
| 158 |
+
|
| 159 |
+
health: async (): Promise<{ status: string }> => {
|
| 160 |
+
return apiFetch<{ status: string }>(`${SERVICES.concepts}/health`);
|
| 161 |
+
},
|
| 162 |
+
};
|
| 163 |
+
|
| 164 |
+
// ==================== EXERCISE API ====================
|
| 165 |
+
|
| 166 |
+
export interface ExerciseRequest {
|
| 167 |
+
module: string;
|
| 168 |
+
topic: string;
|
| 169 |
+
difficulty?: 'beginner' | 'intermediate' | 'advanced';
|
| 170 |
+
user_id?: string;
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
export interface ExerciseResponse {
|
| 174 |
+
id: string;
|
| 175 |
+
module: string;
|
| 176 |
+
topic: string;
|
| 177 |
+
problem: string;
|
| 178 |
+
starter_code: string;
|
| 179 |
+
test_cases: Array<{ input: string; expected_output: string }>;
|
| 180 |
+
difficulty: string;
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
export interface SubmissionRequest {
|
| 184 |
+
quiz_id: string;
|
| 185 |
+
user_id: string;
|
| 186 |
+
code: string;
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
export interface GradeResponse {
|
| 190 |
+
quiz_id: string;
|
| 191 |
+
user_id: string;
|
| 192 |
+
score: number;
|
| 193 |
+
feedback: string;
|
| 194 |
+
passed_tests: number;
|
| 195 |
+
total_tests: number;
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
export const exerciseAPI = {
|
| 199 |
+
generate: async (data: ExerciseRequest): Promise<ExerciseResponse> => {
|
| 200 |
+
return apiFetch<ExerciseResponse>(`${SERVICES.exercise}/generate`, {
|
| 201 |
+
method: 'POST',
|
| 202 |
+
body: JSON.stringify(data),
|
| 203 |
+
});
|
| 204 |
+
},
|
| 205 |
+
|
| 206 |
+
grade: async (data: SubmissionRequest): Promise<GradeResponse> => {
|
| 207 |
+
return apiFetch<GradeResponse>(`${SERVICES.exercise}/grade`, {
|
| 208 |
+
method: 'POST',
|
| 209 |
+
body: JSON.stringify(data),
|
| 210 |
+
});
|
| 211 |
+
},
|
| 212 |
+
|
| 213 |
+
health: async (): Promise<{ status: string }> => {
|
| 214 |
+
return apiFetch<{ status: string }>(`${SERVICES.exercise}/health`);
|
| 215 |
+
},
|
| 216 |
+
};
|
| 217 |
+
|
| 218 |
+
// ==================== CODE REVIEW API ====================
|
| 219 |
+
|
| 220 |
+
export interface ReviewRequest {
|
| 221 |
+
code: string;
|
| 222 |
+
context?: string;
|
| 223 |
+
user_id?: string;
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
export interface ReviewResponse {
|
| 227 |
+
overall_quality: string;
|
| 228 |
+
issues: Array<{
|
| 229 |
+
type: string;
|
| 230 |
+
line: number;
|
| 231 |
+
message: string;
|
| 232 |
+
suggestion: string;
|
| 233 |
+
}>;
|
| 234 |
+
suggestions: string[];
|
| 235 |
+
score: number;
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
export const codeReviewAPI = {
|
| 239 |
+
review: async (data: ReviewRequest): Promise<ReviewResponse> => {
|
| 240 |
+
return apiFetch<ReviewResponse>(`${SERVICES.codeReview}/review`, {
|
| 241 |
+
method: 'POST',
|
| 242 |
+
body: JSON.stringify(data),
|
| 243 |
+
});
|
| 244 |
+
},
|
| 245 |
+
|
| 246 |
+
health: async (): Promise<{ status: string }> => {
|
| 247 |
+
return apiFetch<{ status: string }>(`${SERVICES.codeReview}/health`);
|
| 248 |
+
},
|
| 249 |
+
};
|
| 250 |
+
|
| 251 |
+
// ==================== DEBUG API ====================
|
| 252 |
+
|
| 253 |
+
export interface DebugRequest {
|
| 254 |
+
code: string;
|
| 255 |
+
error_message: string;
|
| 256 |
+
user_id?: string;
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
export interface DebugResponse {
|
| 260 |
+
error_type: string;
|
| 261 |
+
explanation: string;
|
| 262 |
+
fix_suggestion: string;
|
| 263 |
+
corrected_code: string;
|
| 264 |
+
learning_tip: string;
|
| 265 |
+
}
|
| 266 |
+
|
| 267 |
+
export const debugAPI = {
|
| 268 |
+
analyze: async (data: DebugRequest): Promise<DebugResponse> => {
|
| 269 |
+
return apiFetch<DebugResponse>(`${SERVICES.debug}/analyze`, {
|
| 270 |
+
method: 'POST',
|
| 271 |
+
body: JSON.stringify(data),
|
| 272 |
+
});
|
| 273 |
+
},
|
| 274 |
+
|
| 275 |
+
health: async (): Promise<{ status: string }> => {
|
| 276 |
+
return apiFetch<{ status: string }>(`${SERVICES.debug}/health`);
|
| 277 |
+
},
|
| 278 |
+
};
|
| 279 |
+
|
| 280 |
+
// ==================== PROGRESS API ====================
|
| 281 |
+
|
| 282 |
+
export interface ProgressData {
|
| 283 |
+
user_id: string;
|
| 284 |
+
overall_mastery: number;
|
| 285 |
+
modules_completed: number;
|
| 286 |
+
current_module: string;
|
| 287 |
+
modules: Record<string, Record<string, { mastery_score: number }>>;
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
export interface UpdateProgressRequest {
|
| 291 |
+
user_id: string;
|
| 292 |
+
module: string;
|
| 293 |
+
topic: string;
|
| 294 |
+
score: number;
|
| 295 |
+
activity_type: 'quiz' | 'exercise' | 'lesson';
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
export const progressAPI = {
|
| 299 |
+
getProgress: async (userId: string): Promise<ProgressData> => {
|
| 300 |
+
return apiFetch<ProgressData>(`${SERVICES.progress}/progress/${userId}`);
|
| 301 |
+
},
|
| 302 |
+
|
| 303 |
+
updateProgress: async (data: UpdateProgressRequest): Promise<ProgressData> => {
|
| 304 |
+
return apiFetch<ProgressData>(`${SERVICES.progress}/progress`, {
|
| 305 |
+
method: 'POST',
|
| 306 |
+
body: JSON.stringify(data),
|
| 307 |
+
});
|
| 308 |
+
},
|
| 309 |
+
|
| 310 |
+
health: async (): Promise<{ status: string }> => {
|
| 311 |
+
return apiFetch<{ status: string }>(`${SERVICES.progress}/health`);
|
| 312 |
+
},
|
| 313 |
+
};
|
| 314 |
+
|
| 315 |
+
// ==================== AI CHAT API ====================
|
| 316 |
+
|
| 317 |
+
export interface ChatMessage {
|
| 318 |
+
role: 'user' | 'assistant';
|
| 319 |
+
content: string;
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
export interface ChatRequest {
|
| 323 |
+
messages: ChatMessage[];
|
| 324 |
+
user_id: string;
|
| 325 |
+
context?: string;
|
| 326 |
+
}
|
| 327 |
+
|
| 328 |
+
export interface ChatResponse {
|
| 329 |
+
response: string;
|
| 330 |
+
agent_used: string;
|
| 331 |
+
}
|
| 332 |
+
|
| 333 |
+
export const chatAPI = {
|
| 334 |
+
sendMessage: async (data: ChatRequest): Promise<ChatResponse> => {
|
| 335 |
+
// First route through triage
|
| 336 |
+
const routing = await triageAPI.routeQuery({
|
| 337 |
+
query: data.messages[data.messages.length - 1].content,
|
| 338 |
+
user_id: data.user_id,
|
| 339 |
+
});
|
| 340 |
+
|
| 341 |
+
// Then call the appropriate agent
|
| 342 |
+
if (routing.agent === 'concepts-agent') {
|
| 343 |
+
const response = await conceptsAPI.explain({
|
| 344 |
+
topic: data.messages[data.messages.length - 1].content,
|
| 345 |
+
level: 'intermediate',
|
| 346 |
+
});
|
| 347 |
+
return {
|
| 348 |
+
response: `${response.explanation}\n\nExamples:\n${response.examples.join('\n')}`,
|
| 349 |
+
agent_used: routing.agent,
|
| 350 |
+
};
|
| 351 |
+
}
|
| 352 |
+
|
| 353 |
+
// Default response
|
| 354 |
+
return {
|
| 355 |
+
response: routing.message,
|
| 356 |
+
agent_used: routing.agent,
|
| 357 |
+
};
|
| 358 |
+
},
|
| 359 |
+
};
|
| 360 |
+
|
| 361 |
+
// ==================== UTILITY FUNCTIONS ====================
|
| 362 |
+
|
| 363 |
+
export const checkServicesHealth = async (): Promise<Record<string, boolean>> => {
|
| 364 |
+
const services = ['triage', 'concepts', 'codeReview', 'debug', 'exercise', 'progress'] as const;
|
| 365 |
+
const results: Record<string, boolean> = {};
|
| 366 |
+
|
| 367 |
+
await Promise.all(
|
| 368 |
+
services.map(async (service) => {
|
| 369 |
+
try {
|
| 370 |
+
const api = {
|
| 371 |
+
triage: triageAPI,
|
| 372 |
+
concepts: conceptsAPI,
|
| 373 |
+
codeReview: codeReviewAPI,
|
| 374 |
+
debug: debugAPI,
|
| 375 |
+
exercise: exerciseAPI,
|
| 376 |
+
progress: progressAPI,
|
| 377 |
+
}[service];
|
| 378 |
+
await api.health();
|
| 379 |
+
results[service] = true;
|
| 380 |
+
} catch {
|
| 381 |
+
results[service] = false;
|
| 382 |
+
}
|
| 383 |
+
})
|
| 384 |
+
);
|
| 385 |
+
|
| 386 |
+
return results;
|
| 387 |
+
};
|
next-env.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/// <reference types="next" />
|
| 2 |
+
/// <reference types="next/image-types/global" />
|
| 3 |
+
|
| 4 |
+
// NOTE: This file should not be edited
|
| 5 |
+
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
next.config.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/** @type {import('next').NextConfig} */
|
| 2 |
+
const nextConfig = {
|
| 3 |
+
experimental: {
|
| 4 |
+
// Enable SWC transforms for better dynamic import handling
|
| 5 |
+
swcPlugins: [],
|
| 6 |
+
},
|
| 7 |
+
// Handle dynamic imports properly
|
| 8 |
+
webpack: (config, { isServer }) => {
|
| 9 |
+
if (!isServer) {
|
| 10 |
+
// Allow dynamic imports in client-side bundles
|
| 11 |
+
config.resolve.fallback = {
|
| 12 |
+
...config.resolve.fallback,
|
| 13 |
+
fs: false,
|
| 14 |
+
path: false,
|
| 15 |
+
os: false,
|
| 16 |
+
crypto: false,
|
| 17 |
+
stream: false,
|
| 18 |
+
buffer: false,
|
| 19 |
+
util: false,
|
| 20 |
+
};
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
// Configure chunk splitting for better loading
|
| 24 |
+
config.optimization.splitChunks = {
|
| 25 |
+
chunks: 'all',
|
| 26 |
+
cacheGroups: {
|
| 27 |
+
default: {
|
| 28 |
+
minChunks: 2,
|
| 29 |
+
priority: 20,
|
| 30 |
+
reuseExistingChunk: true,
|
| 31 |
+
},
|
| 32 |
+
vendor: {
|
| 33 |
+
test: /[\\/]node_modules[\\/]/,
|
| 34 |
+
name: 'vendors',
|
| 35 |
+
priority: 10,
|
| 36 |
+
chunks: 'all',
|
| 37 |
+
},
|
| 38 |
+
monaco: {
|
| 39 |
+
test: /[\\/]node_modules[\\/]monaco-editor/,
|
| 40 |
+
name: 'monaco',
|
| 41 |
+
priority: 30,
|
| 42 |
+
chunks: 'all',
|
| 43 |
+
},
|
| 44 |
+
},
|
| 45 |
+
};
|
| 46 |
+
|
| 47 |
+
return config;
|
| 48 |
+
},
|
| 49 |
+
};
|
| 50 |
+
|
| 51 |
+
module.exports = nextConfig;
|
package-lock.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "learnflow-frontend",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"private": true,
|
| 5 |
+
"scripts": {
|
| 6 |
+
"dev": "next dev",
|
| 7 |
+
"build": "next build",
|
| 8 |
+
"start": "next start",
|
| 9 |
+
"lint": "next lint"
|
| 10 |
+
},
|
| 11 |
+
"dependencies": {
|
| 12 |
+
"react": "^18",
|
| 13 |
+
"react-dom": "^18",
|
| 14 |
+
"next": "14.0.0",
|
| 15 |
+
"@monaco-editor/react": "^4.6.0",
|
| 16 |
+
"axios": "^1.5.0",
|
| 17 |
+
"clsx": "^2.0.0",
|
| 18 |
+
"tailwind-merge": "^1.14.0"
|
| 19 |
+
},
|
| 20 |
+
"devDependencies": {
|
| 21 |
+
"typescript": "^5",
|
| 22 |
+
"@types/node": "^20",
|
| 23 |
+
"@types/react": "^18",
|
| 24 |
+
"@types/react-dom": "^18",
|
| 25 |
+
"autoprefixer": "^10",
|
| 26 |
+
"postcss": "^8",
|
| 27 |
+
"tailwindcss": "^3",
|
| 28 |
+
"eslint-config-next": "14.0.0"
|
| 29 |
+
}
|
| 30 |
+
}
|
postcss.config.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
module.exports = {
|
| 2 |
+
plugins: {
|
| 3 |
+
tailwindcss: {},
|
| 4 |
+
autoprefixer: {},
|
| 5 |
+
},
|
| 6 |
+
}
|
tailwind.config.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/** @type {import('tailwindcss').Config} */
|
| 2 |
+
module.exports = {
|
| 3 |
+
content: [
|
| 4 |
+
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
|
| 5 |
+
'./components/**/*.{js,ts,jsx,tsx,mdx}',
|
| 6 |
+
'./app/**/*.{js,ts,jsx,tsx,mdx}',
|
| 7 |
+
],
|
| 8 |
+
theme: {
|
| 9 |
+
extend: {
|
| 10 |
+
colors: {
|
| 11 |
+
// VeryWellMind-inspired wellness color palette
|
| 12 |
+
teal: {
|
| 13 |
+
50: '#f0fdfa',
|
| 14 |
+
100: '#ccfbf1',
|
| 15 |
+
200: '#99f6e4',
|
| 16 |
+
300: '#5eead4',
|
| 17 |
+
400: '#2dd4bf',
|
| 18 |
+
500: '#14b8a6',
|
| 19 |
+
600: '#0d9488',
|
| 20 |
+
700: '#0f766e',
|
| 21 |
+
800: '#115e59',
|
| 22 |
+
900: '#134e4a',
|
| 23 |
+
},
|
| 24 |
+
sage: {
|
| 25 |
+
50: '#f6f7f4',
|
| 26 |
+
100: '#e8ebe3',
|
| 27 |
+
200: '#d4dac9',
|
| 28 |
+
300: '#b5c0a5',
|
| 29 |
+
400: '#96a67f',
|
| 30 |
+
500: '#7a8b63',
|
| 31 |
+
600: '#5f6e4c',
|
| 32 |
+
700: '#4b573d',
|
| 33 |
+
800: '#3e4734',
|
| 34 |
+
900: '#353d2e',
|
| 35 |
+
},
|
| 36 |
+
lavender: {
|
| 37 |
+
50: '#faf5ff',
|
| 38 |
+
100: '#f3e8ff',
|
| 39 |
+
200: '#e9d5ff',
|
| 40 |
+
300: '#d8b4fe',
|
| 41 |
+
400: '#c084fc',
|
| 42 |
+
500: '#a855f7',
|
| 43 |
+
600: '#9333ea',
|
| 44 |
+
700: '#7e22ce',
|
| 45 |
+
800: '#6b21a8',
|
| 46 |
+
900: '#581c87',
|
| 47 |
+
},
|
| 48 |
+
cream: {
|
| 49 |
+
50: '#fefdfb',
|
| 50 |
+
100: '#fdf9f3',
|
| 51 |
+
200: '#faf3e6',
|
| 52 |
+
300: '#f5e9d4',
|
| 53 |
+
400: '#eddbb8',
|
| 54 |
+
500: '#e3c99a',
|
| 55 |
+
600: '#d4b07a',
|
| 56 |
+
700: '#c19660',
|
| 57 |
+
800: '#a67b4b',
|
| 58 |
+
900: '#8a6540',
|
| 59 |
+
},
|
| 60 |
+
coral: {
|
| 61 |
+
50: '#fff5f5',
|
| 62 |
+
100: '#ffe3e3',
|
| 63 |
+
200: '#ffc9c9',
|
| 64 |
+
300: '#ffa8a8',
|
| 65 |
+
400: '#ff8787',
|
| 66 |
+
500: '#ff6b6b',
|
| 67 |
+
600: '#fa5252',
|
| 68 |
+
700: '#f03e3e',
|
| 69 |
+
800: '#e03131',
|
| 70 |
+
900: '#c92a2a',
|
| 71 |
+
},
|
| 72 |
+
warmgray: {
|
| 73 |
+
50: '#fafaf9',
|
| 74 |
+
100: '#f5f5f4',
|
| 75 |
+
200: '#e7e5e4',
|
| 76 |
+
300: '#d6d3d1',
|
| 77 |
+
400: '#a8a29e',
|
| 78 |
+
500: '#78716c',
|
| 79 |
+
600: '#57534e',
|
| 80 |
+
700: '#44403c',
|
| 81 |
+
800: '#292524',
|
| 82 |
+
900: '#1c1917',
|
| 83 |
+
},
|
| 84 |
+
},
|
| 85 |
+
fontFamily: {
|
| 86 |
+
sans: ['Inter', 'system-ui', 'sans-serif'],
|
| 87 |
+
display: ['Georgia', 'serif'],
|
| 88 |
+
},
|
| 89 |
+
borderRadius: {
|
| 90 |
+
'4xl': '2rem',
|
| 91 |
+
'5xl': '2.5rem',
|
| 92 |
+
},
|
| 93 |
+
boxShadow: {
|
| 94 |
+
'soft': '0 2px 15px -3px rgba(0, 0, 0, 0.07), 0 10px 20px -2px rgba(0, 0, 0, 0.04)',
|
| 95 |
+
'soft-lg': '0 10px 40px -10px rgba(0, 0, 0, 0.1)',
|
| 96 |
+
'inner-soft': 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.02)',
|
| 97 |
+
},
|
| 98 |
+
backgroundImage: {
|
| 99 |
+
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
|
| 100 |
+
'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
|
| 101 |
+
'wellness-gradient': 'linear-gradient(135deg, #f0fdfa 0%, #faf5ff 50%, #fdf9f3 100%)',
|
| 102 |
+
'hero-pattern': 'radial-gradient(circle at 30% 20%, rgba(20, 184, 166, 0.1) 0%, transparent 50%)',
|
| 103 |
+
},
|
| 104 |
+
},
|
| 105 |
+
},
|
| 106 |
+
plugins: [],
|
| 107 |
+
}
|
tsconfig.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"lib": [
|
| 4 |
+
"dom",
|
| 5 |
+
"dom.iterable",
|
| 6 |
+
"esnext"
|
| 7 |
+
],
|
| 8 |
+
"allowJs": true,
|
| 9 |
+
"skipLibCheck": true,
|
| 10 |
+
"strict": false,
|
| 11 |
+
"noEmit": true,
|
| 12 |
+
"incremental": true,
|
| 13 |
+
"esModuleInterop": true,
|
| 14 |
+
"module": "esnext",
|
| 15 |
+
"moduleResolution": "node",
|
| 16 |
+
"resolveJsonModule": true,
|
| 17 |
+
"isolatedModules": true,
|
| 18 |
+
"jsx": "preserve",
|
| 19 |
+
"plugins": [
|
| 20 |
+
{
|
| 21 |
+
"name": "next"
|
| 22 |
+
}
|
| 23 |
+
]
|
| 24 |
+
},
|
| 25 |
+
"include": [
|
| 26 |
+
"next-env.d.ts",
|
| 27 |
+
".next/types/**/*.ts",
|
| 28 |
+
"**/*.ts",
|
| 29 |
+
"**/*.tsx"
|
| 30 |
+
],
|
| 31 |
+
"exclude": [
|
| 32 |
+
"node_modules"
|
| 33 |
+
]
|
| 34 |
+
}
|
tsconfig.tsbuildinfo
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"fileNames":["./node_modules/typescript/lib/lib.es5.d.ts","./node_modules/typescript/lib/lib.es2015.d.ts","./node_modules/typescript/lib/lib.es2016.d.ts","./node_modules/typescript/lib/lib.es2017.d.ts","./node_modules/typescript/lib/lib.es2018.d.ts","./node_modules/typescript/lib/lib.es2019.d.ts","./node_modules/typescript/lib/lib.es2020.d.ts","./node_modules/typescript/lib/lib.es2021.d.ts","./node_modules/typescript/lib/lib.es2022.d.ts","./node_modules/typescript/lib/lib.es2023.d.ts","./node_modules/typescript/lib/lib.es2024.d.ts","./node_modules/typescript/lib/lib.esnext.d.ts","./node_modules/typescript/lib/lib.dom.d.ts","./node_modules/typescript/lib/lib.dom.iterable.d.ts","./node_modules/typescript/lib/lib.es2015.core.d.ts","./node_modules/typescript/lib/lib.es2015.collection.d.ts","./node_modules/typescript/lib/lib.es2015.generator.d.ts","./node_modules/typescript/lib/lib.es2015.iterable.d.ts","./node_modules/typescript/lib/lib.es2015.promise.d.ts","./node_modules/typescript/lib/lib.es2015.proxy.d.ts","./node_modules/typescript/lib/lib.es2015.reflect.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2016.array.include.d.ts","./node_modules/typescript/lib/lib.es2016.intl.d.ts","./node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","./node_modules/typescript/lib/lib.es2017.date.d.ts","./node_modules/typescript/lib/lib.es2017.object.d.ts","./node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2017.string.d.ts","./node_modules/typescript/lib/lib.es2017.intl.d.ts","./node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","./node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","./node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","./node_modules/typescript/lib/lib.es2018.intl.d.ts","./node_modules/typescript/lib/lib.es2018.promise.d.ts","./node_modules/typescript/lib/lib.es2018.regexp.d.ts","./node_modules/typescript/lib/lib.es2019.array.d.ts","./node_modules/typescript/lib/lib.es2019.object.d.ts","./node_modules/typescript/lib/lib.es2019.string.d.ts","./node_modules/typescript/lib/lib.es2019.symbol.d.ts","./node_modules/typescript/lib/lib.es2019.intl.d.ts","./node_modules/typescript/lib/lib.es2020.bigint.d.ts","./node_modules/typescript/lib/lib.es2020.date.d.ts","./node_modules/typescript/lib/lib.es2020.promise.d.ts","./node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2020.string.d.ts","./node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2020.intl.d.ts","./node_modules/typescript/lib/lib.es2020.number.d.ts","./node_modules/typescript/lib/lib.es2021.promise.d.ts","./node_modules/typescript/lib/lib.es2021.string.d.ts","./node_modules/typescript/lib/lib.es2021.weakref.d.ts","./node_modules/typescript/lib/lib.es2021.intl.d.ts","./node_modules/typescript/lib/lib.es2022.array.d.ts","./node_modules/typescript/lib/lib.es2022.error.d.ts","./node_modules/typescript/lib/lib.es2022.intl.d.ts","./node_modules/typescript/lib/lib.es2022.object.d.ts","./node_modules/typescript/lib/lib.es2022.string.d.ts","./node_modules/typescript/lib/lib.es2022.regexp.d.ts","./node_modules/typescript/lib/lib.es2023.array.d.ts","./node_modules/typescript/lib/lib.es2023.collection.d.ts","./node_modules/typescript/lib/lib.es2023.intl.d.ts","./node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts","./node_modules/typescript/lib/lib.es2024.collection.d.ts","./node_modules/typescript/lib/lib.es2024.object.d.ts","./node_modules/typescript/lib/lib.es2024.promise.d.ts","./node_modules/typescript/lib/lib.es2024.regexp.d.ts","./node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2024.string.d.ts","./node_modules/typescript/lib/lib.esnext.array.d.ts","./node_modules/typescript/lib/lib.esnext.collection.d.ts","./node_modules/typescript/lib/lib.esnext.intl.d.ts","./node_modules/typescript/lib/lib.esnext.disposable.d.ts","./node_modules/typescript/lib/lib.esnext.promise.d.ts","./node_modules/typescript/lib/lib.esnext.decorators.d.ts","./node_modules/typescript/lib/lib.esnext.iterator.d.ts","./node_modules/typescript/lib/lib.esnext.float16.d.ts","./node_modules/typescript/lib/lib.esnext.error.d.ts","./node_modules/typescript/lib/lib.esnext.sharedmemory.d.ts","./node_modules/typescript/lib/lib.decorators.d.ts","./node_modules/typescript/lib/lib.decorators.legacy.d.ts","./node_modules/next/dist/styled-jsx/types/css.d.ts","./node_modules/@types/react/global.d.ts","./node_modules/csstype/index.d.ts","./node_modules/@types/prop-types/index.d.ts","./node_modules/@types/react/index.d.ts","./node_modules/next/dist/styled-jsx/types/index.d.ts","./node_modules/next/dist/styled-jsx/types/macro.d.ts","./node_modules/next/dist/styled-jsx/types/style.d.ts","./node_modules/next/dist/styled-jsx/types/global.d.ts","./node_modules/next/dist/shared/lib/amp.d.ts","./node_modules/next/amp.d.ts","./node_modules/@types/node/compatibility/disposable.d.ts","./node_modules/@types/node/compatibility/indexable.d.ts","./node_modules/@types/node/compatibility/iterators.d.ts","./node_modules/@types/node/compatibility/index.d.ts","./node_modules/@types/node/globals.typedarray.d.ts","./node_modules/@types/node/buffer.buffer.d.ts","./node_modules/@types/node/globals.d.ts","./node_modules/@types/node/web-globals/abortcontroller.d.ts","./node_modules/@types/node/web-globals/domexception.d.ts","./node_modules/@types/node/web-globals/events.d.ts","./node_modules/undici-types/header.d.ts","./node_modules/undici-types/readable.d.ts","./node_modules/undici-types/file.d.ts","./node_modules/undici-types/fetch.d.ts","./node_modules/undici-types/formdata.d.ts","./node_modules/undici-types/connector.d.ts","./node_modules/undici-types/client.d.ts","./node_modules/undici-types/errors.d.ts","./node_modules/undici-types/dispatcher.d.ts","./node_modules/undici-types/global-dispatcher.d.ts","./node_modules/undici-types/global-origin.d.ts","./node_modules/undici-types/pool-stats.d.ts","./node_modules/undici-types/pool.d.ts","./node_modules/undici-types/handlers.d.ts","./node_modules/undici-types/balanced-pool.d.ts","./node_modules/undici-types/agent.d.ts","./node_modules/undici-types/mock-interceptor.d.ts","./node_modules/undici-types/mock-agent.d.ts","./node_modules/undici-types/mock-client.d.ts","./node_modules/undici-types/mock-pool.d.ts","./node_modules/undici-types/mock-errors.d.ts","./node_modules/undici-types/proxy-agent.d.ts","./node_modules/undici-types/env-http-proxy-agent.d.ts","./node_modules/undici-types/retry-handler.d.ts","./node_modules/undici-types/retry-agent.d.ts","./node_modules/undici-types/api.d.ts","./node_modules/undici-types/interceptors.d.ts","./node_modules/undici-types/util.d.ts","./node_modules/undici-types/cookies.d.ts","./node_modules/undici-types/patch.d.ts","./node_modules/undici-types/websocket.d.ts","./node_modules/undici-types/eventsource.d.ts","./node_modules/undici-types/filereader.d.ts","./node_modules/undici-types/diagnostics-channel.d.ts","./node_modules/undici-types/content-type.d.ts","./node_modules/undici-types/cache.d.ts","./node_modules/undici-types/index.d.ts","./node_modules/@types/node/web-globals/fetch.d.ts","./node_modules/@types/node/assert.d.ts","./node_modules/@types/node/assert/strict.d.ts","./node_modules/@types/node/async_hooks.d.ts","./node_modules/@types/node/buffer.d.ts","./node_modules/@types/node/child_process.d.ts","./node_modules/@types/node/cluster.d.ts","./node_modules/@types/node/console.d.ts","./node_modules/@types/node/constants.d.ts","./node_modules/@types/node/crypto.d.ts","./node_modules/@types/node/dgram.d.ts","./node_modules/@types/node/diagnostics_channel.d.ts","./node_modules/@types/node/dns.d.ts","./node_modules/@types/node/dns/promises.d.ts","./node_modules/@types/node/domain.d.ts","./node_modules/@types/node/events.d.ts","./node_modules/@types/node/fs.d.ts","./node_modules/@types/node/fs/promises.d.ts","./node_modules/@types/node/http.d.ts","./node_modules/@types/node/http2.d.ts","./node_modules/@types/node/https.d.ts","./node_modules/@types/node/inspector.generated.d.ts","./node_modules/@types/node/module.d.ts","./node_modules/@types/node/net.d.ts","./node_modules/@types/node/os.d.ts","./node_modules/@types/node/path.d.ts","./node_modules/@types/node/perf_hooks.d.ts","./node_modules/@types/node/process.d.ts","./node_modules/@types/node/punycode.d.ts","./node_modules/@types/node/querystring.d.ts","./node_modules/@types/node/readline.d.ts","./node_modules/@types/node/readline/promises.d.ts","./node_modules/@types/node/repl.d.ts","./node_modules/@types/node/sea.d.ts","./node_modules/@types/node/stream.d.ts","./node_modules/@types/node/stream/promises.d.ts","./node_modules/@types/node/stream/consumers.d.ts","./node_modules/@types/node/stream/web.d.ts","./node_modules/@types/node/string_decoder.d.ts","./node_modules/@types/node/test.d.ts","./node_modules/@types/node/timers.d.ts","./node_modules/@types/node/timers/promises.d.ts","./node_modules/@types/node/tls.d.ts","./node_modules/@types/node/trace_events.d.ts","./node_modules/@types/node/tty.d.ts","./node_modules/@types/node/url.d.ts","./node_modules/@types/node/util.d.ts","./node_modules/@types/node/v8.d.ts","./node_modules/@types/node/vm.d.ts","./node_modules/@types/node/wasi.d.ts","./node_modules/@types/node/worker_threads.d.ts","./node_modules/@types/node/zlib.d.ts","./node_modules/@types/node/index.d.ts","./node_modules/next/dist/server/get-page-files.d.ts","./node_modules/@types/react/canary.d.ts","./node_modules/@types/react/experimental.d.ts","./node_modules/@types/react-dom/index.d.ts","./node_modules/@types/react-dom/canary.d.ts","./node_modules/@types/react-dom/experimental.d.ts","./node_modules/next/dist/compiled/webpack/webpack.d.ts","./node_modules/next/dist/server/config.d.ts","./node_modules/next/dist/lib/load-custom-routes.d.ts","./node_modules/next/dist/shared/lib/image-config.d.ts","./node_modules/next/dist/build/webpack/plugins/subresource-integrity-plugin.d.ts","./node_modules/next/dist/server/body-streams.d.ts","./node_modules/next/dist/server/future/route-kind.d.ts","./node_modules/next/dist/server/future/route-definitions/route-definition.d.ts","./node_modules/next/dist/server/future/route-matches/route-match.d.ts","./node_modules/next/dist/client/components/app-router-headers.d.ts","./node_modules/next/dist/server/request-meta.d.ts","./node_modules/next/dist/server/config-shared.d.ts","./node_modules/next/dist/server/base-http/index.d.ts","./node_modules/next/dist/server/api-utils/index.d.ts","./node_modules/next/dist/server/node-environment.d.ts","./node_modules/next/dist/server/require-hook.d.ts","./node_modules/next/dist/server/node-polyfill-crypto.d.ts","./node_modules/next/dist/build/analysis/get-page-static-info.d.ts","./node_modules/next/dist/build/webpack/loaders/get-module-build-info.d.ts","./node_modules/next/dist/build/webpack/plugins/middleware-plugin.d.ts","./node_modules/next/dist/server/lib/revalidate.d.ts","./node_modules/next/dist/lib/setup-exception-listeners.d.ts","./node_modules/next/dist/build/index.d.ts","./node_modules/next/dist/server/response-cache/types.d.ts","./node_modules/next/dist/server/response-cache/index.d.ts","./node_modules/next/dist/server/lib/incremental-cache/index.d.ts","./node_modules/next/dist/client/components/hooks-server-context.d.ts","./node_modules/next/dist/client/components/static-generation-async-storage.external.d.ts","./node_modules/next/dist/server/render-result.d.ts","./node_modules/next/dist/server/future/helpers/i18n-provider.d.ts","./node_modules/next/dist/server/web/next-url.d.ts","./node_modules/next/dist/compiled/@edge-runtime/cookies/index.d.ts","./node_modules/next/dist/server/web/spec-extension/cookies.d.ts","./node_modules/next/dist/server/web/spec-extension/request.d.ts","./node_modules/next/dist/server/web/spec-extension/fetch-event.d.ts","./node_modules/next/dist/server/web/spec-extension/response.d.ts","./node_modules/next/dist/server/web/types.d.ts","./node_modules/next/dist/build/webpack/plugins/pages-manifest-plugin.d.ts","./node_modules/next/dist/shared/lib/router/utils/route-regex.d.ts","./node_modules/next/dist/shared/lib/router/utils/route-matcher.d.ts","./node_modules/next/dist/shared/lib/router/utils/parse-url.d.ts","./node_modules/next/dist/server/base-http/node.d.ts","./node_modules/next/dist/server/font-utils.d.ts","./node_modules/next/dist/build/webpack/plugins/flight-manifest-plugin.d.ts","./node_modules/next/dist/server/future/route-modules/route-module.d.ts","./node_modules/next/dist/server/load-components.d.ts","./node_modules/next/dist/shared/lib/router/utils/middleware-route-matcher.d.ts","./node_modules/next/dist/build/webpack/plugins/next-font-manifest-plugin.d.ts","./node_modules/next/dist/server/future/route-definitions/locale-route-definition.d.ts","./node_modules/next/dist/server/future/route-definitions/pages-route-definition.d.ts","./node_modules/next/dist/shared/lib/mitt.d.ts","./node_modules/next/dist/client/with-router.d.ts","./node_modules/next/dist/client/router.d.ts","./node_modules/next/dist/client/route-loader.d.ts","./node_modules/next/dist/client/page-loader.d.ts","./node_modules/next/dist/shared/lib/bloom-filter.d.ts","./node_modules/next/dist/shared/lib/router/router.d.ts","./node_modules/next/dist/shared/lib/router-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/loadable-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/loadable.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/image-config-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/hooks-client-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/head-manager-context.shared-runtime.d.ts","./node_modules/next/dist/server/future/route-definitions/app-page-route-definition.d.ts","./node_modules/next/dist/shared/lib/modern-browserslist-target.d.ts","./node_modules/next/dist/shared/lib/constants.d.ts","./node_modules/next/dist/build/webpack/loaders/metadata/types.d.ts","./node_modules/next/dist/build/webpack/loaders/next-app-loader.d.ts","./node_modules/next/dist/server/lib/app-dir-module.d.ts","./node_modules/next/dist/server/web/spec-extension/adapters/request-cookies.d.ts","./node_modules/next/dist/server/async-storage/draft-mode-provider.d.ts","./node_modules/next/dist/server/web/spec-extension/adapters/headers.d.ts","./node_modules/next/dist/client/components/request-async-storage.external.d.ts","./node_modules/next/dist/server/app-render/create-error-handler.d.ts","./node_modules/next/dist/server/app-render/app-render.d.ts","./node_modules/next/dist/shared/lib/server-inserted-html.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/amp-context.shared-runtime.d.ts","./node_modules/next/dist/server/future/route-modules/app-page/vendored/contexts/entrypoints.d.ts","./node_modules/next/dist/server/future/route-modules/app-page/module.compiled.d.ts","./node_modules/next/dist/client/components/error-boundary.d.ts","./node_modules/next/dist/client/components/router-reducer/create-initial-router-state.d.ts","./node_modules/next/dist/client/components/app-router.d.ts","./node_modules/next/dist/client/components/layout-router.d.ts","./node_modules/next/dist/client/components/render-from-template-context.d.ts","./node_modules/next/dist/client/components/action-async-storage.external.d.ts","./node_modules/next/dist/client/components/static-generation-bailout.d.ts","./node_modules/next/dist/client/components/static-generation-searchparams-bailout-provider.d.ts","./node_modules/next/dist/client/components/searchparams-bailout-proxy.d.ts","./node_modules/next/dist/server/app-render/rsc/preloads.d.ts","./node_modules/next/dist/server/app-render/rsc/taint.d.ts","./node_modules/next/dist/client/components/not-found-boundary.d.ts","./node_modules/next/dist/server/app-render/entry-base.d.ts","./node_modules/next/dist/build/templates/app-page.d.ts","./node_modules/next/dist/server/future/route-modules/app-page/module.d.ts","./node_modules/next/dist/server/app-render/types.d.ts","./node_modules/next/dist/client/components/router-reducer/fetch-server-response.d.ts","./node_modules/next/dist/client/components/router-reducer/router-reducer-types.d.ts","./node_modules/next/dist/shared/lib/app-router-context.shared-runtime.d.ts","./node_modules/next/dist/server/future/route-modules/pages/vendored/contexts/entrypoints.d.ts","./node_modules/next/dist/server/future/route-modules/pages/module.compiled.d.ts","./node_modules/next/dist/build/templates/pages.d.ts","./node_modules/next/dist/server/future/route-modules/pages/module.d.ts","./node_modules/next/dist/server/render.d.ts","./node_modules/next/dist/server/future/route-definitions/pages-api-route-definition.d.ts","./node_modules/next/dist/server/future/route-matches/pages-api-route-match.d.ts","./node_modules/next/dist/server/future/route-matchers/route-matcher.d.ts","./node_modules/next/dist/server/future/route-matcher-providers/route-matcher-provider.d.ts","./node_modules/next/dist/server/future/route-matcher-managers/route-matcher-manager.d.ts","./node_modules/next/dist/server/future/normalizers/normalizer.d.ts","./node_modules/next/dist/server/future/normalizers/locale-route-normalizer.d.ts","./node_modules/next/dist/server/future/normalizers/request/rsc.d.ts","./node_modules/next/dist/server/future/normalizers/request/postponed.d.ts","./node_modules/next/dist/server/base-server.d.ts","./node_modules/next/dist/server/image-optimizer.d.ts","./node_modules/next/dist/server/next-server.d.ts","./node_modules/next/dist/lib/coalesced-function.d.ts","./node_modules/next/dist/trace/shared.d.ts","./node_modules/next/dist/trace/trace.d.ts","./node_modules/next/dist/trace/index.d.ts","./node_modules/next/dist/build/webpack-config.d.ts","./node_modules/next/dist/build/webpack/plugins/define-env-plugin.d.ts","./node_modules/next/dist/build/swc/index.d.ts","./node_modules/next/dist/server/dev/parse-version-info.d.ts","./node_modules/next/dist/server/dev/hot-reloader-types.d.ts","./node_modules/next/dist/telemetry/storage.d.ts","./node_modules/next/dist/server/lib/types.d.ts","./node_modules/next/dist/server/lib/router-utils/types.d.ts","./node_modules/next/dist/server/lib/render-server.d.ts","./node_modules/next/dist/server/lib/router-server.d.ts","./node_modules/next/dist/shared/lib/router/utils/path-match.d.ts","./node_modules/next/dist/server/lib/router-utils/filesystem.d.ts","./node_modules/next/dist/server/lib/router-utils/setup-dev-bundler.d.ts","./node_modules/next/dist/server/lib/dev-bundler-service.d.ts","./node_modules/next/dist/server/dev/static-paths-worker.d.ts","./node_modules/next/dist/server/dev/next-dev-server.d.ts","./node_modules/next/dist/server/next.d.ts","./node_modules/next/dist/lib/metadata/types/alternative-urls-types.d.ts","./node_modules/next/dist/lib/metadata/types/extra-types.d.ts","./node_modules/next/dist/lib/metadata/types/metadata-types.d.ts","./node_modules/next/dist/lib/metadata/types/manifest-types.d.ts","./node_modules/next/dist/lib/metadata/types/opengraph-types.d.ts","./node_modules/next/dist/lib/metadata/types/twitter-types.d.ts","./node_modules/next/dist/lib/metadata/types/metadata-interface.d.ts","./node_modules/next/types/index.d.ts","./node_modules/next/dist/shared/lib/html-context.shared-runtime.d.ts","./node_modules/@next/env/dist/index.d.ts","./node_modules/next/dist/shared/lib/utils.d.ts","./node_modules/next/dist/pages/_app.d.ts","./node_modules/next/app.d.ts","./node_modules/next/dist/server/web/spec-extension/unstable-cache.d.ts","./node_modules/next/dist/server/web/spec-extension/revalidate-path.d.ts","./node_modules/next/dist/server/web/spec-extension/revalidate-tag.d.ts","./node_modules/next/dist/server/web/spec-extension/unstable-no-store.d.ts","./node_modules/next/cache.d.ts","./node_modules/next/dist/shared/lib/runtime-config.external.d.ts","./node_modules/next/config.d.ts","./node_modules/next/dist/pages/_document.d.ts","./node_modules/next/document.d.ts","./node_modules/next/dist/shared/lib/dynamic.d.ts","./node_modules/next/dynamic.d.ts","./node_modules/next/dist/pages/_error.d.ts","./node_modules/next/error.d.ts","./node_modules/next/dist/shared/lib/head.d.ts","./node_modules/next/head.d.ts","./node_modules/next/dist/shared/lib/get-img-props.d.ts","./node_modules/next/dist/client/image-component.d.ts","./node_modules/next/dist/shared/lib/image-external.d.ts","./node_modules/next/image.d.ts","./node_modules/next/dist/client/link.d.ts","./node_modules/next/link.d.ts","./node_modules/next/dist/client/components/redirect.d.ts","./node_modules/next/dist/client/components/not-found.d.ts","./node_modules/next/dist/client/components/navigation.d.ts","./node_modules/next/navigation.d.ts","./node_modules/next/router.d.ts","./node_modules/next/dist/client/script.d.ts","./node_modules/next/script.d.ts","./node_modules/next/dist/server/web/spec-extension/user-agent.d.ts","./node_modules/next/dist/compiled/@edge-runtime/primitives/url.d.ts","./node_modules/next/dist/server/web/spec-extension/image-response.d.ts","./node_modules/next/dist/compiled/@vercel/og/satori/index.d.ts","./node_modules/next/dist/compiled/@vercel/og/emoji/index.d.ts","./node_modules/next/dist/compiled/@vercel/og/types.d.ts","./node_modules/next/server.d.ts","./node_modules/next/types/global.d.ts","./node_modules/next/types/compiled.d.ts","./node_modules/next/index.d.ts","./node_modules/next/image-types/global.d.ts","./next-env.d.ts","./app/layout.tsx","./.next/types/app/layout.ts","./app/page.tsx","./.next/types/app/page.ts","./app/register/page.tsx","./.next/types/app/register/page.ts","./app/student/chat/page.tsx","./.next/types/app/student/chat/page.ts","./app/student/dashboard/page.tsx","./.next/types/app/student/dashboard/page.ts","./node_modules/monaco-editor/esm/vs/editor/editor.api.d.ts","./node_modules/@monaco-editor/loader/lib/types.d.ts","./node_modules/monaco-editor/esm/vs/editor/editor.main.d.ts","./node_modules/@monaco-editor/react/dist/index.d.ts","./app/student/learn/page.tsx","./.next/types/app/student/learn/page.ts","./app/student/progress/page.tsx","./.next/types/app/student/progress/page.ts","./app/student/quiz/page.tsx","./.next/types/app/student/quiz/page.ts","./app/teacher/dashboard/page.tsx","./.next/types/app/teacher/dashboard/page.ts","./node_modules/@types/json5/index.d.ts","./node_modules/@types/trusted-types/lib/index.d.ts","./node_modules/@types/trusted-types/index.d.ts"],"fileIdsList":[[99,145,342,389],[99,145,342,391],[99,145,342,393],[99,145,342,395],[99,145,342,397],[99,145,342,403],[99,145,342,405],[99,145,342,407],[99,145,342,409],[99,145,386],[87,99,145,369,373],[87,99,145,359,402],[99,145,386,387],[99,145,399],[87,99,145,399,400,401],[99,145],[99,142,145],[99,144,145],[145],[99,145,150,178],[99,145,146,151,156,164,175,186],[99,145,146,147,156,164],[94,95,96,99,145],[99,145,148,187],[99,145,149,150,157,165],[99,145,150,175,183],[99,145,151,153,156,164],[99,144,145,152],[99,145,153,154],[99,145,155,156],[99,144,145,156],[99,145,156,157,158,175,186],[99,145,156,157,158,171,175,178],[99,145,153,156,159,164,175,186],[99,145,156,157,159,160,164,175,183,186],[99,145,159,161,175,183,186],[97,98,99,100,101,102,103,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192],[99,145,156,162],[99,145,163,186,191],[99,145,153,156,164,175],[99,145,165],[99,145,166],[99,144,145,167],[99,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192],[99,145,169],[99,145,170],[99,145,156,171,172],[99,145,171,173,187,189],[99,145,156,175,176,178],[99,145,177,178],[99,145,175,176],[99,145,178],[99,145,179],[99,142,145,175,180],[99,145,156,181,182],[99,145,181,182],[99,145,150,164,175,183],[99,145,184],[99,145,164,185],[99,145,159,170,186],[99,145,150,187],[99,145,175,188],[99,145,163,189],[99,145,190],[99,140,145],[99,140,145,156,158,167,175,178,186,189,191],[99,145,175,192],[87,99,145,197,198,199],[87,99,145,197,198],[87,99,145],[87,91,99,145,196,343,382],[87,91,99,145,195,343,382],[84,85,86,99,145],[99,145,412],[92,99,145],[99,145,347],[99,145,349,350,351,352],[99,145,354],[99,145,202,211,218,343],[99,145,202,209,213,220],[99,145,211,320],[99,145,268,278,291,385],[99,145,299],[99,145,202,211,217,255,265,318,385],[99,145,217,385],[99,145,211,265,266,385],[99,145,211,217,255,385],[99,145,385],[99,145,217,218,385],[99,144,145,193],[87,99,145,279,280,296],[87,99,145,196],[87,99,145,279,294],[99,145,275,297,370,371],[99,145,232],[99,144,145,193,232,269,270,271],[87,99,145,294,297],[99,145,294,296],[87,99,145,294,295,297],[99,144,145,193,212,225,226],[87,99,145,203,364],[87,99,145,186,193],[87,99,145,217,253],[87,99,145,217],[99,145,251,256],[87,99,145,252,346],[87,91,99,145,159,193,195,196,343,380,381],[99,145,201],[99,145,336,337,338,339,340,341],[99,145,338],[87,99,145,344,346],[87,99,145,346],[99,145,159,193,212,346],[99,145,159,193,210,227,228,243,272,273,293,294],[99,145,226,227,272,281,282,283,284,285,286,287,288,289,290,385],[87,99,145,170,193,211,225,243,245,247,293,343,385],[99,145,159,193,212,213,232,233,269],[99,145,159,193,211,213],[99,145,159,175,193,210,212,213],[99,145,159,170,186,193,201,203,210,211,212,213,217,220,222,224,225,228,229,237,239,242,243,245,246,247,294,302,304,307,309,310,311,343],[99,145,159,175,193],[99,145,202,203,204,210,343,346,385],[99,145,211],[99,145,159,175,186,193,207,319,321,322,385],[99,145,170,186,193,207,210,212,225,236,237,239,240,241,245,307,312,314,332,333],[99,145,211,215,225],[99,145,210,211],[99,145,229,308],[99,145,308],[99,145,206,207],[99,145,206,248],[99,145,206],[99,145,208,229,306],[99,145,305],[99,145,207,208],[99,145,208,303],[99,145,207],[99,145,293],[99,145,159,193,210,228,244,263,268,274,277,292,294],[99,145,257,258,259,260,261,262,275,276,297,344],[99,145,301],[99,145,159,193,210,228,244,249,298,300,302,343,346],[99,145,159,186,193,203,210,211,224],[99,145,267],[99,145,159,193,325,331],[99,145,222,224,346],[99,145,326,332,335],[99,145,159,215,325,327],[99,145,202,211,222,246,329],[99,145,159,193,211,217,246,315,323,324,328,329,330],[99,145,194,243,244,343,346],[99,145,159,170,186,193,208,210,212,215,219,220,222,224,225,228,236,237,239,240,241,242,245,304,312,313,346],[99,145,159,193,210,211,215,314,334],[99,145,159,193,220,227],[87,99,145,159,170,193,201,203,210,213,228,242,243,245,247,301,343,346],[99,145,159,170,186,193,205,208,209,212],[99,145,223],[99,145,159,193,220,228],[99,145,159,193,211,229],[99,145,159,193],[99,145,231],[99,145,233],[99,145,211,230,232,236],[99,145,211,230,232],[99,145,159,193,205,211,212,233,234,235],[87,99,145,294,295,296],[99,145,264],[87,99,145,203],[87,99,145,239],[87,99,145,194,242,247,343,346],[99,145,203,364,365],[87,99,145,256],[87,99,145,170,186,193,201,250,252,254,255,346],[99,145,212,217,239],[99,145,170,193],[99,145,238],[87,99,145,157,159,170,193,201,256,265,343,344,345],[83,87,88,89,90,99,145,195,196,343,382],[99,145,150],[99,145,316,317],[99,145,316],[99,145,356],[99,145,358],[99,145,360],[99,145,362],[99,145,366],[91,93,99,145,343,348,353,355,357,359,361,363,367,369,373,374,376,383,384,385],[99,145,368],[99,145,372],[99,145,252],[99,145,375],[99,144,145,233,234,235,236,377,378,379,382],[99,145,193],[87,91,99,145,159,161,170,193,195,196,197,199,201,213,335,342,346,382],[99,112,116,145,186],[99,112,145,175,186],[99,107,145],[99,109,112,145,183,186],[99,145,164,183],[99,107,145,193],[99,109,112,145,164,186],[99,104,105,108,111,145,156,175,186],[99,112,119,145],[99,104,110,145],[99,112,133,134,145],[99,108,112,145,178,186,193],[99,133,145,193],[99,106,107,145,193],[99,112,145],[99,106,107,108,109,110,111,112,113,114,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,134,135,136,137,138,139,145],[99,112,127,145],[99,112,119,120,145],[99,110,112,120,121,145],[99,111,145],[99,104,107,112,145],[99,112,116,120,121,145],[99,116,145],[99,110,112,115,145,186],[99,104,109,112,119,145],[99,145,175],[99,107,112,133,145,191,193]],"fileInfos":[{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"ee7bad0c15b58988daa84371e0b89d313b762ab83cb5b31b8a2d1162e8eb41c2","impliedFormat":1},{"version":"27bdc30a0e32783366a5abeda841bc22757c1797de8681bbe81fbc735eeb1c10","impliedFormat":1},{"version":"8fd575e12870e9944c7e1d62e1f5a73fcf23dd8d3a321f2a2c74c20d022283fe","impliedFormat":1},{"version":"2ab096661c711e4a81cc464fa1e6feb929a54f5340b46b0a07ac6bbf857471f0","impliedFormat":1},{"version":"080941d9f9ff9307f7e27a83bcd888b7c8270716c39af943532438932ec1d0b9","affectsGlobalScope":true,"impliedFormat":1},{"version":"2e80ee7a49e8ac312cc11b77f1475804bee36b3b2bc896bead8b6e1266befb43","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"a680117f487a4d2f30ea46f1b4b7f58bef1480456e18ba53ee85c2746eeca012","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a","affectsGlobalScope":true,"impliedFormat":1},{"version":"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d","affectsGlobalScope":true,"impliedFormat":1},{"version":"3925a6c820dcb1a06506c90b1577db1fdbf7705d65b62b99dce4be75c637e26b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a3d63ef2b853447ec4f749d3f368ce642264246e02911fcb1590d8c161b8005","affectsGlobalScope":true,"impliedFormat":1},{"version":"8cdf8847677ac7d20486e54dd3fcf09eda95812ac8ace44b4418da1bbbab6eb8","affectsGlobalScope":true,"impliedFormat":1},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true,"impliedFormat":1},{"version":"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"df83c2a6c73228b625b0beb6669c7ee2a09c914637e2d35170723ad49c0f5cd4","affectsGlobalScope":true,"impliedFormat":1},{"version":"436aaf437562f276ec2ddbee2f2cdedac7664c1e4c1d2c36839ddd582eeb3d0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e3c06ea092138bf9fa5e874a1fdbc9d54805d074bee1de31b99a11e2fec239d","affectsGlobalScope":true,"impliedFormat":1},{"version":"87dc0f382502f5bbce5129bdc0aea21e19a3abbc19259e0b43ae038a9fc4e326","affectsGlobalScope":true,"impliedFormat":1},{"version":"b1cb28af0c891c8c96b2d6b7be76bd394fddcfdb4709a20ba05a7c1605eea0f9","affectsGlobalScope":true,"impliedFormat":1},{"version":"2fef54945a13095fdb9b84f705f2b5994597640c46afeb2ce78352fab4cb3279","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac77cb3e8c6d3565793eb90a8373ee8033146315a3dbead3bde8db5eaf5e5ec6","affectsGlobalScope":true,"impliedFormat":1},{"version":"56e4ed5aab5f5920980066a9409bfaf53e6d21d3f8d020c17e4de584d29600ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ece9f17b3866cc077099c73f4983bddbcb1dc7ddb943227f1ec070f529dedd1","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a6282c8827e4b9a95f4bf4f5c205673ada31b982f50572d27103df8ceb8013c","affectsGlobalScope":true,"impliedFormat":1},{"version":"1c9319a09485199c1f7b0498f2988d6d2249793ef67edda49d1e584746be9032","affectsGlobalScope":true,"impliedFormat":1},{"version":"e3a2a0cee0f03ffdde24d89660eba2685bfbdeae955a6c67e8c4c9fd28928eeb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811c71eee4aa0ac5f7adf713323a5c41b0cf6c4e17367a34fbce379e12bbf0a4","affectsGlobalScope":true,"impliedFormat":1},{"version":"51ad4c928303041605b4d7ae32e0c1ee387d43a24cd6f1ebf4a2699e1076d4fa","affectsGlobalScope":true,"impliedFormat":1},{"version":"60037901da1a425516449b9a20073aa03386cce92f7a1fd902d7602be3a7c2e9","affectsGlobalScope":true,"impliedFormat":1},{"version":"d4b1d2c51d058fc21ec2629fff7a76249dec2e36e12960ea056e3ef89174080f","affectsGlobalScope":true,"impliedFormat":1},{"version":"22adec94ef7047a6c9d1af3cb96be87a335908bf9ef386ae9fd50eeb37f44c47","affectsGlobalScope":true,"impliedFormat":1},{"version":"196cb558a13d4533a5163286f30b0509ce0210e4b316c56c38d4c0fd2fb38405","affectsGlobalScope":true,"impliedFormat":1},{"version":"73f78680d4c08509933daf80947902f6ff41b6230f94dd002ae372620adb0f60","affectsGlobalScope":true,"impliedFormat":1},{"version":"c5239f5c01bcfa9cd32f37c496cf19c61d69d37e48be9de612b541aac915805b","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"0990a7576222f248f0a3b888adcb7389f957928ce2afb1cd5128169086ff4d29","impliedFormat":1},{"version":"eb5b19b86227ace1d29ea4cf81387279d04bb34051e944bc53df69f58914b788","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac51dd7d31333793807a6abaa5ae168512b6131bd41d9c5b98477fc3b7800f9f","impliedFormat":1},{"version":"87d9d29dbc745f182683f63187bf3d53fd8673e5fca38ad5eaab69798ed29fbc","impliedFormat":1},{"version":"7a3aa194cfd5919c4da251ef04ea051077e22702638d4edcb9579e9101653519","affectsGlobalScope":true,"impliedFormat":1},{"version":"cc69795d9954ee4ad57545b10c7bf1a7260d990231b1685c147ea71a6faa265c","impliedFormat":1},{"version":"8bc6c94ff4f2af1f4023b7bb2379b08d3d7dd80c698c9f0b07431ea16101f05f","impliedFormat":1},{"version":"1b61d259de5350f8b1e5db06290d31eaebebc6baafd5f79d314b5af9256d7153","impliedFormat":1},{"version":"57194e1f007f3f2cbef26fa299d4c6b21f4623a2eddc63dfeef79e38e187a36e","impliedFormat":1},{"version":"0f6666b58e9276ac3a38fdc80993d19208442d6027ab885580d93aec76b4ef00","impliedFormat":1},{"version":"05fd364b8ef02fb1e174fbac8b825bdb1e5a36a016997c8e421f5fab0a6da0a0","impliedFormat":1},{"version":"70521b6ab0dcba37539e5303104f29b721bfb2940b2776da4cc818c07e1fefc1","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab41ef1f2cdafb8df48be20cd969d875602483859dc194e9c97c8a576892c052","affectsGlobalScope":true,"impliedFormat":1},{"version":"d153a11543fd884b596587ccd97aebbeed950b26933ee000f94009f1ab142848","affectsGlobalScope":true,"impliedFormat":1},{"version":"21d819c173c0cf7cc3ce57c3276e77fd9a8a01d35a06ad87158781515c9a438a","impliedFormat":1},{"version":"98cffbf06d6bab333473c70a893770dbe990783904002c4f1a960447b4b53dca","affectsGlobalScope":true,"impliedFormat":1},{"version":"ba481bca06f37d3f2c137ce343c7d5937029b2468f8e26111f3c9d9963d6568d","affectsGlobalScope":true,"impliedFormat":1},{"version":"6d9ef24f9a22a88e3e9b3b3d8c40ab1ddb0853f1bfbd5c843c37800138437b61","affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","affectsGlobalScope":true,"impliedFormat":1},{"version":"5929864ce17fba74232584d90cb721a89b7ad277220627cc97054ba15a98ea8f","impliedFormat":1},{"version":"763fe0f42b3d79b440a9b6e51e9ba3f3f91352469c1e4b3b67bfa4ff6352f3f4","impliedFormat":1},{"version":"25c8056edf4314820382a5fdb4bb7816999acdcb929c8f75e3f39473b87e85bc","impliedFormat":1},{"version":"c464d66b20788266e5353b48dc4aa6bc0dc4a707276df1e7152ab0c9ae21fad8","impliedFormat":1},{"version":"78d0d27c130d35c60b5e5566c9f1e5be77caf39804636bc1a40133919a949f21","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"1d6e127068ea8e104a912e42fc0a110e2aa5a66a356a917a163e8cf9a65e4a75","impliedFormat":1},{"version":"5ded6427296cdf3b9542de4471d2aa8d3983671d4cac0f4bf9c637208d1ced43","impliedFormat":1},{"version":"7f182617db458e98fc18dfb272d40aa2fff3a353c44a89b2c0ccb3937709bfb5","impliedFormat":1},{"version":"cadc8aced301244057c4e7e73fbcae534b0f5b12a37b150d80e5a45aa4bebcbd","impliedFormat":1},{"version":"385aab901643aa54e1c36f5ef3107913b10d1b5bb8cbcd933d4263b80a0d7f20","impliedFormat":1},{"version":"9670d44354bab9d9982eca21945686b5c24a3f893db73c0dae0fd74217a4c219","impliedFormat":1},{"version":"0b8a9268adaf4da35e7fa830c8981cfa22adbbe5b3f6f5ab91f6658899e657a7","impliedFormat":1},{"version":"11396ed8a44c02ab9798b7dca436009f866e8dae3c9c25e8c1fbc396880bf1bb","impliedFormat":1},{"version":"ba7bc87d01492633cb5a0e5da8a4a42a1c86270e7b3d2dea5d156828a84e4882","impliedFormat":1},{"version":"4893a895ea92c85345017a04ed427cbd6a1710453338df26881a6019432febdd","impliedFormat":1},{"version":"c21dc52e277bcfc75fac0436ccb75c204f9e1b3fa5e12729670910639f27343e","impliedFormat":1},{"version":"13f6f39e12b1518c6650bbb220c8985999020fe0f21d818e28f512b7771d00f9","impliedFormat":1},{"version":"9b5369969f6e7175740bf51223112ff209f94ba43ecd3bb09eefff9fd675624a","impliedFormat":1},{"version":"4fe9e626e7164748e8769bbf74b538e09607f07ed17c2f20af8d680ee49fc1da","impliedFormat":1},{"version":"24515859bc0b836719105bb6cc3d68255042a9f02a6022b3187948b204946bd2","impliedFormat":1},{"version":"ea0148f897b45a76544ae179784c95af1bd6721b8610af9ffa467a518a086a43","impliedFormat":1},{"version":"24c6a117721e606c9984335f71711877293a9651e44f59f3d21c1ea0856f9cc9","impliedFormat":1},{"version":"dd3273ead9fbde62a72949c97dbec2247ea08e0c6952e701a483d74ef92d6a17","impliedFormat":1},{"version":"405822be75ad3e4d162e07439bac80c6bcc6dbae1929e179cf467ec0b9ee4e2e","impliedFormat":1},{"version":"0db18c6e78ea846316c012478888f33c11ffadab9efd1cc8bcc12daded7a60b6","impliedFormat":1},{"version":"e61be3f894b41b7baa1fbd6a66893f2579bfad01d208b4ff61daef21493ef0a8","impliedFormat":1},{"version":"bd0532fd6556073727d28da0edfd1736417a3f9f394877b6d5ef6ad88fba1d1a","impliedFormat":1},{"version":"89167d696a849fce5ca508032aabfe901c0868f833a8625d5a9c6e861ef935d2","impliedFormat":1},{"version":"615ba88d0128ed16bf83ef8ccbb6aff05c3ee2db1cc0f89ab50a4939bfc1943f","impliedFormat":1},{"version":"a4d551dbf8746780194d550c88f26cf937caf8d56f102969a110cfaed4b06656","impliedFormat":1},{"version":"8bd86b8e8f6a6aa6c49b71e14c4ffe1211a0e97c80f08d2c8cc98838006e4b88","impliedFormat":1},{"version":"317e63deeb21ac07f3992f5b50cdca8338f10acd4fbb7257ebf56735bf52ab00","impliedFormat":1},{"version":"4732aec92b20fb28c5fe9ad99521fb59974289ed1e45aecb282616202184064f","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"bf67d53d168abc1298888693338cb82854bdb2e69ef83f8a0092093c2d562107","impliedFormat":1},{"version":"2cbe0621042e2a68c7cbce5dfed3906a1862a16a7d496010636cdbdb91341c0f","affectsGlobalScope":true,"impliedFormat":1},{"version":"e2677634fe27e87348825bb041651e22d50a613e2fdf6a4a3ade971d71bac37e","impliedFormat":1},{"version":"7394959e5a741b185456e1ef5d64599c36c60a323207450991e7a42e08911419","impliedFormat":1},{"version":"8c0bcd6c6b67b4b503c11e91a1fb91522ed585900eab2ab1f61bba7d7caa9d6f","impliedFormat":1},{"version":"8cd19276b6590b3ebbeeb030ac271871b9ed0afc3074ac88a94ed2449174b776","affectsGlobalScope":true,"impliedFormat":1},{"version":"696eb8d28f5949b87d894b26dc97318ef944c794a9a4e4f62360cd1d1958014b","impliedFormat":1},{"version":"3f8fa3061bd7402970b399300880d55257953ee6d3cd408722cb9ac20126460c","impliedFormat":1},{"version":"35ec8b6760fd7138bbf5809b84551e31028fb2ba7b6dc91d95d098bf212ca8b4","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"68bd56c92c2bd7d2339457eb84d63e7de3bd56a69b25f3576e1568d21a162398","affectsGlobalScope":true,"impliedFormat":1},{"version":"3e93b123f7c2944969d291b35fed2af79a6e9e27fdd5faa99748a51c07c02d28","impliedFormat":1},{"version":"9d19808c8c291a9010a6c788e8532a2da70f811adb431c97520803e0ec649991","impliedFormat":1},{"version":"87aad3dd9752067dc875cfaa466fc44246451c0c560b820796bdd528e29bef40","impliedFormat":1},{"version":"4aacb0dd020eeaef65426153686cc639a78ec2885dc72ad220be1d25f1a439df","impliedFormat":1},{"version":"f0bd7e6d931657b59605c44112eaf8b980ba7f957a5051ed21cb93d978cf2f45","impliedFormat":1},{"version":"8db0ae9cb14d9955b14c214f34dae1b9ef2baee2fe4ce794a4cd3ac2531e3255","affectsGlobalScope":true,"impliedFormat":1},{"version":"15fc6f7512c86810273af28f224251a5a879e4261b4d4c7e532abfbfc3983134","impliedFormat":1},{"version":"58adba1a8ab2d10b54dc1dced4e41f4e7c9772cbbac40939c0dc8ce2cdb1d442","impliedFormat":1},{"version":"2fd4c143eff88dabb57701e6a40e02a4dbc36d5eb1362e7964d32028056a782b","impliedFormat":1},{"version":"714435130b9015fae551788df2a88038471a5a11eb471f27c4ede86552842bc9","impliedFormat":1},{"version":"855cd5f7eb396f5f1ab1bc0f8580339bff77b68a770f84c6b254e319bbfd1ac7","impliedFormat":1},{"version":"5650cf3dace09e7c25d384e3e6b818b938f68f4e8de96f52d9c5a1b3db068e86","impliedFormat":1},{"version":"1354ca5c38bd3fd3836a68e0f7c9f91f172582ba30ab15bb8c075891b91502b7","affectsGlobalScope":true,"impliedFormat":1},{"version":"27fdb0da0daf3b337c5530c5f266efe046a6ceb606e395b346974e4360c36419","impliedFormat":1},{"version":"2d2fcaab481b31a5882065c7951255703ddbe1c0e507af56ea42d79ac3911201","impliedFormat":1},{"version":"a192fe8ec33f75edbc8d8f3ed79f768dfae11ff5735e7fe52bfa69956e46d78d","impliedFormat":1},{"version":"ca867399f7db82df981d6915bcbb2d81131d7d1ef683bc782b59f71dda59bc85","affectsGlobalScope":true,"impliedFormat":1},{"version":"0e456fd5b101271183d99a9087875a282323e3a3ff0d7bcf1881537eaa8b8e63","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e043a1bc8fbf2a255bccf9bf27e0f1caf916c3b0518ea34aa72357c0afd42ec","impliedFormat":1},{"version":"b4f70ec656a11d570e1a9edce07d118cd58d9760239e2ece99306ee9dfe61d02","impliedFormat":1},{"version":"3bc2f1e2c95c04048212c569ed38e338873f6a8593930cf5a7ef24ffb38fc3b6","impliedFormat":1},{"version":"6e70e9570e98aae2b825b533aa6292b6abd542e8d9f6e9475e88e1d7ba17c866","impliedFormat":1},{"version":"f9d9d753d430ed050dc1bf2667a1bab711ccbb1c1507183d794cc195a5b085cc","impliedFormat":1},{"version":"9eece5e586312581ccd106d4853e861aaaa1a39f8e3ea672b8c3847eedd12f6e","impliedFormat":1},{"version":"47ab634529c5955b6ad793474ae188fce3e6163e3a3fb5edd7e0e48f14435333","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"45650f47bfb376c8a8ed39d4bcda5902ab899a3150029684ee4c10676d9fbaee","impliedFormat":1},{"version":"0225ecb9ed86bdb7a2c7fd01f1556906902929377b44483dc4b83e03b3ef227d","affectsGlobalScope":true,"impliedFormat":1},{"version":"74cf591a0f63db318651e0e04cb55f8791385f86e987a67fd4d2eaab8191f730","impliedFormat":1},{"version":"5eab9b3dc9b34f185417342436ec3f106898da5f4801992d8ff38ab3aff346b5","impliedFormat":1},{"version":"12ed4559eba17cd977aa0db658d25c4047067444b51acfdcbf38470630642b23","affectsGlobalScope":true,"impliedFormat":1},{"version":"f3ffabc95802521e1e4bcba4c88d8615176dc6e09111d920c7a213bdda6e1d65","impliedFormat":1},{"version":"ddc734b4fae82a01d247e9e342d020976640b5e93b4e9b3a1e30e5518883a060","impliedFormat":1},{"version":"ae56f65caf3be91108707bd8dfbccc2a57a91feb5daabf7165a06a945545ed26","impliedFormat":1},{"version":"a136d5de521da20f31631a0a96bf712370779d1c05b7015d7019a9b2a0446ca9","impliedFormat":1},{"version":"c3b41e74b9a84b88b1dca61ec39eee25c0dbc8e7d519ba11bb070918cfacf656","affectsGlobalScope":true,"impliedFormat":1},{"version":"4737a9dc24d0e68b734e6cfbcea0c15a2cfafeb493485e27905f7856988c6b29","affectsGlobalScope":true,"impliedFormat":1},{"version":"36d8d3e7506b631c9582c251a2c0b8a28855af3f76719b12b534c6edf952748d","impliedFormat":1},{"version":"1ca69210cc42729e7ca97d3a9ad48f2e9cb0042bada4075b588ae5387debd318","impliedFormat":1},{"version":"f5ebe66baaf7c552cfa59d75f2bfba679f329204847db3cec385acda245e574e","impliedFormat":1},{"version":"ed59add13139f84da271cafd32e2171876b0a0af2f798d0c663e8eeb867732cf","affectsGlobalScope":true,"impliedFormat":1},{"version":"05db535df8bdc30d9116fe754a3473d1b6479afbc14ae8eb18b605c62677d518","impliedFormat":1},{"version":"b1810689b76fd473bd12cc9ee219f8e62f54a7d08019a235d07424afbf074d25","impliedFormat":1},{"version":"8caa5c86be1b793cd5f599e27ecb34252c41e011980f7d61ae4989a149ff6ccc","impliedFormat":1},{"version":"91b0f6d01993021ecbe01eb076db6a3cf1b66359c1d99104f43436010e81afb5","impliedFormat":1},{"version":"d1bd4e51810d159899aad1660ccb859da54e27e08b8c9862b40cd36c1d9ff00f","impliedFormat":1},{"version":"17ed71200119e86ccef2d96b73b02ce8854b76ad6bd21b5021d4269bec527b5f","impliedFormat":1},{"version":"1cfa8647d7d71cb03847d616bd79320abfc01ddea082a49569fda71ac5ece66b","impliedFormat":1},{"version":"bb7a61dd55dc4b9422d13da3a6bb9cc5e89be888ef23bbcf6558aa9726b89a1c","impliedFormat":1},{"version":"db6d2d9daad8a6d83f281af12ce4355a20b9a3e71b82b9f57cddcca0a8964a96","impliedFormat":1},{"version":"cfe4ef4710c3786b6e23dae7c086c70b4f4835a2e4d77b75d39f9046106e83d3","impliedFormat":1},{"version":"cbea99888785d49bb630dcbb1613c73727f2b5a2cf02e1abcaab7bcf8d6bf3c5","impliedFormat":1},{"version":"3b8f725c3d5ffb64bf876c87409686875102c6f7450b268d8f5188b6920f7c25","impliedFormat":1},{"version":"a86f82d646a739041d6702101afa82dcb935c416dd93cbca7fd754fd0282ce1f","impliedFormat":1},{"version":"2dad084c67e649f0f354739ec7df7c7df0779a28a4f55c97c6b6883ae850d1ce","impliedFormat":1},{"version":"fa5bbc7ab4130dd8cdc55ea294ec39f76f2bc507a0f75f4f873e38631a836ca7","impliedFormat":1},{"version":"df45ca1176e6ac211eae7ddf51336dc075c5314bc5c253651bae639defd5eec5","impliedFormat":1},{"version":"cf86de1054b843e484a3c9300d62fbc8c97e77f168bbffb131d560ca0474d4a8","impliedFormat":1},{"version":"37f7b8e560025858aae5195ca74a3e95ecd55591e2babc0acd57bc1dab4ea8ea","impliedFormat":1},{"version":"e2d5483c9a79900ba9d6012135f18b662b3ca1d33fde4f5e39b71f74e47d6331","impliedFormat":1},{"version":"22b9fab85e85b95f6378b5a2bd43c9d2e15106d760e0e58111c416fe224cc76f","impliedFormat":1},{"version":"fc46f093d1b754a8e3e34a071a1dd402f42003927676757a9a10c6f1d195a35b","impliedFormat":1},{"version":"b7b3258e8d47333721f9d4c287361d773f8fa88e52d1148812485d9fc06d2577","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"49e567e0aa388ab416eeb7a7de9bce5045a7b628bad18d1f6fa9d3eacee7bc3f","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"87eaecac33864ecec8972b1773c5d897f0f589deb7ac8fe0dcdf4b721b06e28d","impliedFormat":1},{"version":"47e5af2a841356a961f815e7c55d72554db0c11b4cba4d0caab91f8717846a94","impliedFormat":1},{"version":"4c91cc1ab59b55d880877ccf1999ded0bb2ebc8e3a597c622962d65bf0e76be8","impliedFormat":1},{"version":"fa1ea09d3e073252eccff2f6630a4ce5633cc2ff963ba672dd8fd6783108ea83","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"309816cd6e597f4d4b080bc5e36215c6b78196f744d578adf61589bee5fd7eea","impliedFormat":1},{"version":"bdb44eca306ff5b62bcf2b4e70e96a40987e018029d95565e2f234aad80830cf","impliedFormat":1},{"version":"edaa0bbf2891b17f904a67aef7f9d53371c993fe3ff6dec708c2aff6083b01af","impliedFormat":1},{"version":"89aece12f9cd6d736ae7c350800f257a2363f6322ae8f998da73153fb405d8af","impliedFormat":1},{"version":"d23518a5f155f1a3e07214baf0295687507122ae2e6e9bd5e772551ebd4b3157","impliedFormat":1},{"version":"aa9a92be255ec97f669ea89678fafcbd35d165f65b68ff22685263f6eaeb3c9c","impliedFormat":1},{"version":"fa8b514302736759e491d3df074a61f54ed1a6a69b4aadee05dbcdda53f881c3","impliedFormat":1},{"version":"e8da637cbd6ed1cf6c36e9424f6bcee4515ca2c677534d4006cbd9a05f930f0c","impliedFormat":1},{"version":"ca1b882a105a1972f82cc58e3be491e7d750a1eb074ffd13b198269f57ed9e1b","impliedFormat":1},{"version":"c9d71f340f1a4576cd2a572f73a54dc7212161fa172dfe3dea64ac627c8fcb50","impliedFormat":1},{"version":"3867ca0e9757cc41e04248574f4f07b8f9e3c0c2a796a5eb091c65bfd2fc8bdb","impliedFormat":1},{"version":"6c66f6f7d9ff019a644ff50dd013e6bf59be4bf389092948437efa6b77dc8f9a","impliedFormat":1},{"version":"4e10622f89fea7b05dd9b52fb65e1e2b5cbd96d4cca3d9e1a60bb7f8a9cb86a1","impliedFormat":1},{"version":"ef2d1bd01d144d426b72db3744e7a6b6bb518a639d5c9c8d86438fb75a3b1934","impliedFormat":1},{"version":"b9750fe7235da7d8bf75cb171bf067b7350380c74271d3f80f49aea7466b55b5","impliedFormat":1},{"version":"ac60bbee0d4235643cc52b57768b22de8c257c12bd8c2039860540cab1fa1d82","impliedFormat":1},{"version":"973b59a17aaa817eb205baf6c132b83475a5c0a44e8294a472af7793b1817e89","impliedFormat":1},{"version":"ada39cbb2748ab2873b7835c90c8d4620723aedf323550e8489f08220e477c7f","impliedFormat":1},{"version":"6e5f5cee603d67ee1ba6120815497909b73399842254fc1e77a0d5cdc51d8c9c","impliedFormat":1},{"version":"f79e0681538ef94c273a46bb1a073b4fe9fdc93ef7f40cc2c3abd683b85f51fc","impliedFormat":1},{"version":"70f3814c457f54a7efe2d9ce9d2686de9250bb42eb7f4c539bd2280a42e52d33","impliedFormat":1},{"version":"17ace83a5bea3f1da7e0aef7aab0f52bca22619e243537a83a89352a611b837d","impliedFormat":1},{"version":"ef61792acbfa8c27c9bd113f02731e66229f7d3a169e3c1993b508134f1a58e0","impliedFormat":1},{"version":"6cf2d240d4e449ccfee82aff7ce0fd1890c1b6d4f144ec003aa51f7f70f68935","impliedFormat":1},{"version":"f6404e7837b96da3ea4d38c4f1a3812c96c9dcdf264e93d5bdb199f983a3ef4b","impliedFormat":1},{"version":"c5426dbfc1cf90532f66965a7aa8c1136a78d4d0f96d8180ecbfc11d7722f1a5","impliedFormat":1},{"version":"65a15fc47900787c0bd18b603afb98d33ede930bed1798fc984d5ebb78b26cf9","impliedFormat":1},{"version":"9d202701f6e0744adb6314d03d2eb8fc994798fc83d91b691b75b07626a69801","impliedFormat":1},{"version":"de9d2df7663e64e3a91bf495f315a7577e23ba088f2949d5ce9ec96f44fba37d","impliedFormat":1},{"version":"c7af78a2ea7cb1cd009cfb5bdb48cd0b03dad3b54f6da7aab615c2e9e9d570c5","impliedFormat":1},{"version":"1dc574e42493e8bf9bb37be44d9e38c5bd7bbc04f884e5e58b4d69636cb192b3","impliedFormat":1},{"version":"9deab571c42ed535c17054f35da5b735d93dc454d83c9a5330ecc7a4fb184e9e","affectsGlobalScope":true,"impliedFormat":1},{"version":"db01d18853469bcb5601b9fc9826931cc84cc1a1944b33cad76fd6f1e3d8c544","affectsGlobalScope":true,"impliedFormat":1},{"version":"6b8e8c0331a0c2e9fb53b8b0d346e44a8db8c788dae727a2c52f4cf3bd857f0d","impliedFormat":1},{"version":"903e299a28282fa7b714586e28409ed73c3b63f5365519776bf78e8cf173db36","affectsGlobalScope":true,"impliedFormat":1},{"version":"fa6c12a7c0f6b84d512f200690bfc74819e99efae69e4c95c4cd30f6884c526e","impliedFormat":1},{"version":"f1c32f9ce9c497da4dc215c3bc84b722ea02497d35f9134db3bb40a8d918b92b","impliedFormat":1},{"version":"b73c319af2cc3ef8f6421308a250f328836531ea3761823b4cabbd133047aefa","affectsGlobalScope":true,"impliedFormat":1},{"version":"e433b0337b8106909e7953015e8fa3f2d30797cea27141d1c5b135365bb975a6","impliedFormat":1},{"version":"dd3900b24a6a8745efeb7ad27629c0f8a626470ac229c1d73f1fe29d67e44dca","impliedFormat":1},{"version":"ddff7fc6edbdc5163a09e22bf8df7bef75f75369ebd7ecea95ba55c4386e2441","impliedFormat":1},{"version":"106c6025f1d99fd468fd8bf6e5bda724e11e5905a4076c5d29790b6c3745e50c","impliedFormat":1},{"version":"ec29be0737d39268696edcec4f5e97ce26f449fa9b7afc2f0f99a86def34a418","impliedFormat":1},{"version":"8945919709e0c6069c32ca26a675a0de90fd2ad70d5bc3ba281c628729a0c39d","impliedFormat":1},{"version":"ec6cba1c02c675e4dd173251b156792e8d3b0c816af6d6ad93f1a55d674591aa","impliedFormat":1},{"version":"763ee3998716d599321e34b7f7e93a8e57bef751206325226ebf088bf75ea460","impliedFormat":1},{"version":"e15d3c84d5077bb4a3adee4c791022967b764dc41cb8fa3cfa44d4379b2c95f5","impliedFormat":1},{"version":"3556cfbab7b43da96d15a442ddbb970e1f2fc97876d055b6555d86d7ac57dae5","impliedFormat":1},{"version":"437751e0352c6e924ddf30e90849f1d9eb00ca78c94d58d6a37202ec84eb8393","impliedFormat":1},{"version":"48e8af7fdb2677a44522fd185d8c87deff4d36ee701ea003c6c780b1407a1397","impliedFormat":1},{"version":"606e6f841ba9667de5d83ca458449f0ed8c511ba635f753eaa731e532dea98c7","impliedFormat":1},{"version":"7c0d4fc71fe32cedb758c7e3c08715235a51e5a22d184306a59dae10a9c7ffaa","impliedFormat":1},{"version":"ce8a0b21e80cf5f10adc9336b46ffc666696d1373a763b170baf69a722f85d67","impliedFormat":1},{"version":"2e4f37ffe8862b14d8e24ae8763daaa8340c0df0b859d9a9733def0eee7562d9","impliedFormat":1},{"version":"13283350547389802aa35d9f2188effaeac805499169a06ef5cd77ce2a0bd63f","impliedFormat":1},{"version":"680793958f6a70a44c8d9ae7d46b7a385361c69ac29dcab3ed761edce1c14ab8","impliedFormat":1},{"version":"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","impliedFormat":1},{"version":"baeffe1b7d836196d497eb755699718deb729a2033078a018f037a14ecaeb9a7","impliedFormat":1},{"version":"39da0a8478aede3a55308089e231c5966b2196e7201494280b1e19f8ec8e24d4","impliedFormat":1},{"version":"90be1a7f573bad71331ff10deeadce25b09034d3d27011c2155bcb9cb9800b7f","impliedFormat":1},{"version":"bc7221c9a8dc71587ff784120f7707985627282dad0a99439e893a1588651ef0","impliedFormat":1},{"version":"438c7513b1df91dcef49b13cd7a1c4720f91a36e88c1df731661608b7c055f10","impliedFormat":1},{"version":"ad444a874f011d3a797f1a41579dbfcc6b246623f49c20009f60e211dbd5315e","impliedFormat":1},{"version":"1124613ba0669e7ea5fb785ede1c3f254ed1968335468b048b8fc35c172393de","impliedFormat":1},{"version":"5fa139523e35fd907f3dd6c2e38ef2066687b27ed88e2680783e05662355ac04","impliedFormat":1},{"version":"9c250db4bab4f78fad08be7f4e43e962cc143e0f78763831653549ceb477344a","impliedFormat":1},{"version":"9385cdc09850950bc9b59cca445a3ceb6fcca32b54e7b626e746912e489e535e","impliedFormat":1},{"version":"0a72186f94215d020cb386f7dca81d7495ab6c17066eb07d0f44a5bf33c1b21a","impliedFormat":1},{"version":"db7c948e2e69559324be7628cb63296ec8986d60f26173f9e324aeb8a2fe23d8","impliedFormat":1},{"version":"9c2353ef1fb353a1c8f30af2cf104f0bc64ebc2fcdb98c2834d451bd654664ab","impliedFormat":1},{"version":"63a8e96f65a22604eae82737e409d1536e69a467bb738bec505f4f97cce9d878","impliedFormat":1},{"version":"3fd78152a7031315478f159c6a5872c712ece6f01212c78ea82aef21cb0726e2","impliedFormat":1},{"version":"7fda4c0e3f50513286029633c458ee82cee563cd6af20b92e43b4425c969c146","impliedFormat":1},{"version":"cda4052f66b1e6cb7cf1fdfd96335d1627aa24a3b8b82ba4a9f873ec3a7bcde8","impliedFormat":1},{"version":"703733dde084b7e856f5940f9c3c12007ca62858accb9482c2b65e030877702d","impliedFormat":1},{"version":"413cb597cc5933562ec064bfb1c3a9164ef5d2f09e5f6b7bd19f483d5352449e","impliedFormat":1},{"version":"fd933f824347f9edd919618a76cdb6a0c0085c538115d9a287fa0c7f59957ab3","impliedFormat":1},{"version":"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","impliedFormat":1},{"version":"6a1aa3e55bdc50503956c5cd09ae4cd72e3072692d742816f65c66ca14f4dfdd","impliedFormat":1},{"version":"ab75cfd9c4f93ffd601f7ca1753d6a9d953bbedfbd7a5b3f0436ac8a1de60dfa","impliedFormat":1},{"version":"6cc79183c88040697e1552ba81c5245b0c701b965623774587c4b9d1e7497278","impliedFormat":1},{"version":"b73cbf0a72c8800cf8f96a9acfe94f3ad32ca71342a8908b8ae484d61113f647","impliedFormat":1},{"version":"bae6dd176832f6423966647382c0d7ba9e63f8c167522f09a982f086cd4e8b23","impliedFormat":1},{"version":"1364f64d2fb03bbb514edc42224abd576c064f89be6a990136774ecdd881a1da","impliedFormat":1},{"version":"c9958eb32126a3843deedda8c22fb97024aa5d6dd588b90af2d7f2bfac540f23","impliedFormat":1},{"version":"950fb67a59be4c2dbe69a5786292e60a5cb0e8612e0e223537784c731af55db1","impliedFormat":1},{"version":"e927c2c13c4eaf0a7f17e6022eee8519eb29ef42c4c13a31e81a611ab8c95577","impliedFormat":1},{"version":"07ca44e8d8288e69afdec7a31fa408ce6ab90d4f3d620006701d5544646da6aa","impliedFormat":1},{"version":"82c27d4cf380b0e6cd62628f069b850298d20051f0b7b0a1904fdb38c53fa7a6","impliedFormat":1},{"version":"c97b9278c8ce212c1bdf4fae9c77d58c15565d4ebf663d761a9deb924b6ca8b3","impliedFormat":1},{"version":"8bb6e7ce91ec84336203e87010b1198514548c2e44789752c1741eaac02f2431","impliedFormat":1},{"version":"b33ac7d8d7d1bfc8cc06c75d1ee186d21577ab2026f482e29babe32b10b26512","impliedFormat":1},{"version":"24f8f342c14c911eedfee43074c6a0d0a5ebb5ec984353bffaeadddb3f6a6b1c","impliedFormat":1},{"version":"6459054aabb306821a043e02b89d54da508e3a6966601a41e71c166e4ea1474f","impliedFormat":1},{"version":"03d4a10c21ac451b682246f3261b769247baf774c4878551c02256ae98299b1c","impliedFormat":1},{"version":"2d9b710fee8c3d7eabee626af8fd6ec2cf6f71e6b7429b307b8f67d70b1707c5","impliedFormat":1},{"version":"652a4bbefba6aa309bfc3063f59ed1a2e739c1d802273b0e6e0aa7082659f3b3","impliedFormat":1},{"version":"d7ca19bfb1ba4c3ef59d43bd7cd3719d8c5ffb60a9b6f402dee4e229f4d921aa","impliedFormat":1},{"version":"0c0a85a19b60f2ec18a32ff051bb1423860977a16b645dbf159baa7202bc633b","impliedFormat":1},{"version":"fc5bdc1d13667041055811568043956c75150923d8b9a32b989ac7588418ce47","impliedFormat":1},{"version":"f974e4a06953682a2c15d5bd5114c0284d5abf8bc0fe4da25cb9159427b70072","impliedFormat":1},{"version":"d3b290cc3c08cbde2b463df2616b948fb32733dafe3ac29b9e6ded26baee5489","impliedFormat":1},{"version":"94404c4a878fe291e7578a2a80264c6f18e9f1933fbb57e48f0eb368672e389c","impliedFormat":1},{"version":"5c1b7f03aa88be854bc15810bfd5bd5a1943c5a7620e1c53eddd2a013996343e","impliedFormat":1},{"version":"f416c9c3eee9d47ff49132c34f96b9180e50485d435d5748f0e8b72521d28d2e","impliedFormat":1},{"version":"9558d365d0e72b6d9bd8c1742fe1185f983965c6d2eff88a117a59b9f51d3c5f","impliedFormat":1},{"version":"6cc2961fbe8d32e34fd4c7f1b7045353016fff50df98bc31af7c7d1b4b6eb552","impliedFormat":1},{"version":"01aa917531e116485beca44a14970834687b857757159769c16b228eb1e49c5f","impliedFormat":1},{"version":"a2e1f7010ae5f746b937621840cb87dee9eeb69188d32880bd9752029084212c","impliedFormat":1},{"version":"dd30eb34b5c4597a568de0efb8b34e328c224648c258759ac541beb16256ffb6","impliedFormat":1},{"version":"6129bd7098131a0e346352901bc8d461a76d0568686bb0e1f8499df91fde8a1f","impliedFormat":1},{"version":"7cd7923a36835c1194a92b808068a524c4e7c0ff7bdc8712865800e6963d75da","impliedFormat":1},{"version":"82200d39d66c91f502f74c85db8c7a8d56cfc361c20d7da6d7b68a4eeaaefbf4","impliedFormat":1},{"version":"741067675daa6d4334a2dc80a4452ca3850e89d5852e330db7cb2b5f867173b1","impliedFormat":1},{"version":"a1c8542ed1189091dd39e732e4390882a9bcd15c0ca093f6e9483eba4e37573f","impliedFormat":1},{"version":"131b1475d2045f20fb9f43b7aa6b7cb51f25250b5e4c6a1d4aa3cf4dd1a68793","impliedFormat":1},{"version":"3a17f09634c50cce884721f54fd9e7b98e03ac505889c560876291fcf8a09e90","impliedFormat":1},{"version":"32531dfbb0cdc4525296648f53b2b5c39b64282791e2a8c765712e49e6461046","impliedFormat":1},{"version":"0ce1b2237c1c3df49748d61568160d780d7b26693bd9feb3acb0744a152cd86d","impliedFormat":1},{"version":"e489985388e2c71d3542612685b4a7db326922b57ac880f299da7026a4e8a117","impliedFormat":1},{"version":"76264a4df0b7c78b7b12dfaedc05d9f1016f27be1f3d0836417686ff6757f659","impliedFormat":1},{"version":"c0fabd699e6e0b6bfc1728c048e52737b73fb6609eeeae0f7f4775ff14ff2df6","affectsGlobalScope":true,"impliedFormat":1},{"version":"fd1b9d883b9446f1e1da1e1033a6a98995c25fbf3c10818a78960e2f2917d10c","impliedFormat":1},{"version":"19252079538942a69be1645e153f7dbbc1ef56b4f983c633bf31fe26aeac32cd","impliedFormat":1},{"version":"4dd4f6e28afc1ee30ce76ffc659d19e14dff29cb19b7747610ada3535b7409af","impliedFormat":1},{"version":"1640728521f6ab040fc4a85edd2557193839d0cd0e41c02004fc8d415363d4e2","impliedFormat":1},{"version":"65c24a8baa2cca1de069a0ba9fba82a173690f52d7e2d0f1f7542d59d5eb4db0","impliedFormat":1},{"version":"ec9fd890d681789cb0aa9efbc50b1e0afe76fbf3c49c3ac50ff80e90e29c6bcb","impliedFormat":1},{"version":"5fbd292aa08208ae99bf06d5da63321fdc768ee43a7a104980963100a3841752","impliedFormat":1},{"version":"9eac5a6beea91cfb119688bf44a5688b129b804ede186e5e2413572a534c21bb","impliedFormat":1},{"version":"e81bf06c0600517d8f04cc5de398c28738bfdf04c91fb42ad835bfe6b0d63a23","impliedFormat":1},{"version":"363996fe13c513a7793aa28ffb05b5d0230db2b3d21b7bfaf21f79e4cde54b4e","impliedFormat":1},{"version":"b7fff2d004c5879cae335db8f954eb1d61242d9f2d28515e67902032723caeab","impliedFormat":1},{"version":"5f3dc10ae646f375776b4e028d2bed039a93eebbba105694d8b910feebbe8b9c","impliedFormat":1},{"version":"7f6c48cacd08c1b1e29737b8221b7661e6b855767f8778f9a181fa2f74c09d21","impliedFormat":1},{"version":"4545c1a1ceca170d5d83452dd7c4994644c35cf676a671412601689d9a62da35","impliedFormat":1},{"version":"15959543f93f27e8e2b1a012fe28e14b682034757e2d7a6c1f02f87107fc731e","impliedFormat":1},{"version":"a2d648d333cf67b9aeac5d81a1a379d563a8ffa91ddd61c6179f68de724260ff","impliedFormat":1},{"version":"4e828bf688597c32905215785730cbdb603b54e284d472a23fc0195c6d4aeee8","impliedFormat":1},{"version":"a3f41ed1b4f2fc3049394b945a68ae4fdefd49fa1739c32f149d32c0545d67f5","impliedFormat":1},{"version":"4da80db9ed5a1a20fd5bfce863dd178b8928bcaf4a3d75e8657bcae32e572ede","impliedFormat":1},{"version":"47699512e6d8bebf7be488182427189f999affe3addc1c87c882d36b7f2d0b0e","impliedFormat":1},{"version":"f72ee46ae3f73e6c5ff0da682177251d80500dd423bfd50286124cd0ca11e160","impliedFormat":1},{"version":"898b714aad9cfd0e546d1ad2c031571de7622bd0f9606a499bee193cf5e7cf0c","impliedFormat":1},{"version":"d707fb7ca32930495019a4c85500385f6850c785ee0987a1b6bcad6ade95235e","impliedFormat":1},{"version":"fedebeae32c5cdd1a85b4e0504a01996e4a8adf3dfa72876920d3dd6e42978e7","impliedFormat":1},{"version":"5d26aae738fa3efc87c24f6e5ec07c54694e6bcf431cc38d3da7576d6bb35bd6","impliedFormat":1},{"version":"cdf21eee8007e339b1b9945abf4a7b44930b1d695cc528459e68a3adc39a622e","impliedFormat":1},{"version":"e0aa1079d58134e55ad2f73508ad1be565a975f2247245d76c64c1ca9e5e5b26","impliedFormat":1},{"version":"cd0c5af42811a4a56a0f77856cfa6c170278e9522888db715b11f176df3ff1f2","impliedFormat":1},{"version":"68f81dad9e8d7b7aa15f35607a70c8b68798cf579ac44bd85325b8e2f1fb3600","impliedFormat":1},{"version":"1de80059b8078ea5749941c9f863aa970b4735bdbb003be4925c853a8b6b4450","impliedFormat":1},{"version":"1d079c37fa53e3c21ed3fa214a27507bda9991f2a41458705b19ed8c2b61173d","impliedFormat":1},{"version":"94fd3ce628bd94a2caf431e8d85901dbe3a64ab52c0bd1dbe498f63ca18789f7","impliedFormat":1},{"version":"5835a6e0d7cd2738e56b671af0e561e7c1b4fb77751383672f4b009f4e161d70","impliedFormat":1},{"version":"c0eeaaa67c85c3bb6c52b629ebbfd3b2292dc67e8c0ffda2fc6cd2f78dc471e6","impliedFormat":1},{"version":"4b7f74b772140395e7af67c4841be1ab867c11b3b82a51b1aeb692822b76c872","impliedFormat":1},{"version":"27be6622e2922a1b412eb057faa854831b95db9db5035c3f6d4b677b902ab3b7","impliedFormat":1},{"version":"2470a2412a59c6177cd4408dd7edb099ca7ace68c0187f54187dfee56dc9c5aa","impliedFormat":99},{"version":"c2008605e78208cfa9cd70bd29856b72dda7ad89df5dc895920f8e10bcb9cd0a","impliedFormat":99},{"version":"ec61ebac4d71c4698318673efbb5c481a6c4d374da8d285f6557541a5bd318d0","impliedFormat":99},{"version":"16fd66ae997b2f01c972531239da90fbf8ab4022bb145b9587ef746f6cecde5a","affectsGlobalScope":true,"impliedFormat":1},{"version":"fc8fbee8f73bf5ffd6ba08ba1c554d6f714c49cae5b5e984afd545ab1b7abe06","affectsGlobalScope":true,"impliedFormat":1},{"version":"3586f5ea3cc27083a17bd5c9059ede9421d587286d5a47f4341a4c2d00e4fa91","impliedFormat":1},{"version":"521fc35a732f1a19f5d52024c2c22e257aa63258554968f7806a823be2f82b03","impliedFormat":1},{"version":"b789bf89eb19c777ed1e956dbad0925ca795701552d22e68fd130a032008b9f9","impliedFormat":1},"9269d492817e359123ac64c8205e5d05dab63d71a3a7a229e68b5d9a0e8150bf","9036ae3ed72f744b33643af8e52b6e920bad5000534ddb5783c0b9f48f69d1c9","13fe295414f8fb40f00c222881b4cdf0bd7307292581b68793a657365b82f2a3","773719ba9c61d8d214621803d5b4789224dda790582e69dfa7e8a7863fee6c0c","593bba908bc1947c33f4b527d20dcedc96fa87e953b082e4667f8e8272154bc4","1457b7757cde4056b3008434882ea1c18ec5f7c61150b4f33b44a45fcf4cdf55","ff0236b46656604f1ab88d7e6cfe4cb399f5f70534b59f4c4684f0937389c404","7f2a3c03c8d709b1d8df85c3c7c586d84047351c70b17dd31c25d10545e4a36f","9acdf9ba116b4ea4215a8750fcd976324d30846da9e1ab419ff0a11098c4ee7a","5339f80c9aeada565c03c138854229837d15f5df2cef981b061565b06810828a","0b2eb709a02f1a688daa3929f579acec0b413f983d5c019ccdf951cb5b2bd8e5",{"version":"b99c4ff8d11110b9699edc4e5c3bb304360a70ca47e4f9851fc42ad0b663ba83","affectsGlobalScope":true,"impliedFormat":1},{"version":"2e5c41e56098ae93bc42a15473acb1a176f28b2ba95c38f3c67820434acd85b6","impliedFormat":1},{"version":"ba340fc8e5b0f08d90c6ef8552b8ce7bb5dc8e18d47b57f90dee105bc77d0d9e","affectsGlobalScope":true,"impliedFormat":1},{"version":"0f263eeea7877199808b1bc220df95b25a72695ec214eb96d740d4b06c7cc868","impliedFormat":1},"d0e134d21c26b531a5dbe6d801b7b7a6409129926713b18fe0ab5efec2789c1c","c873123a6480c3cd533524bfdea0bf7871bb7d549abc3b6252d59de1bf2ed3cf","9e19240df6ed90533e9a734fad6084dd3801347b81e153169411c275c4a6c6c9","56062695918e75372afd67333c94642091525272ecc6378839069e37ec6802ba","626efb23ccea0f0c727d061cb5ba2b90aa17678bacf7672c7c2871dbbd6d89f4","98908f00c2992f073cf6e78988bc734a0f6f717fd9c17450ba0a42e2e7ff5fb6","9586ec947eac461551939a20205e2d7390447340d78225faa9bc8ccfa590237b","7c8dbc5f184cb9834a27e42081f4f6b35d3f77045ac959f72f60c9e4af05c6b8",{"version":"96d14f21b7652903852eef49379d04dbda28c16ed36468f8c9fa08f7c14c9538","impliedFormat":1},{"version":"15fe687c59d62741b4494d5e623d497d55eb38966ecf5bea7f36e48fc3fbe15e","impliedFormat":1},{"version":"2c3b8be03577c98530ef9cb1a76e2c812636a871f367e9edf4c5f3ce702b77f8","affectsGlobalScope":true,"impliedFormat":1}],"root":[[388,398],[403,410]],"options":{"allowJs":true,"esModuleInterop":true,"jsx":1,"module":99,"skipLibCheck":true,"strict":false},"referencedMap":[[390,1],[392,2],[394,3],[396,4],[398,5],[404,6],[406,7],[408,8],[410,9],[389,10],[391,11],[393,11],[395,11],[397,11],[403,12],[405,11],[407,11],[409,11],[388,13],[400,14],[402,15],[345,16],[411,16],[142,17],[143,17],[144,18],[99,19],[145,20],[146,21],[147,22],[94,16],[97,23],[95,16],[96,16],[148,24],[149,25],[150,26],[151,27],[152,28],[153,29],[154,29],[155,30],[156,31],[157,32],[158,33],[100,16],[98,16],[159,34],[160,35],[161,36],[193,37],[162,38],[163,39],[164,40],[165,41],[166,42],[167,43],[168,44],[169,45],[170,46],[171,47],[172,47],[173,48],[174,16],[175,49],[177,50],[176,51],[178,52],[179,53],[180,54],[181,55],[182,56],[183,57],[184,58],[185,59],[186,60],[187,61],[188,62],[189,63],[190,64],[101,16],[102,16],[103,16],[141,65],[191,66],[192,67],[86,16],[198,68],[199,69],[197,70],[195,71],[196,72],[84,16],[87,73],[413,74],[412,16],[85,16],[399,16],[401,14],[93,75],[348,76],[353,77],[355,78],[217,79],[222,80],[321,81],[292,82],[300,83],[319,84],[218,85],[266,16],[267,86],[320,87],[243,88],[219,89],[247,88],[237,88],[204,88],[284,90],[209,16],[281,91],[279,92],[226,16],[282,93],[372,94],[290,70],[371,16],[370,95],[283,70],[272,96],[280,97],[295,98],[296,99],[287,16],[227,100],[285,16],[286,70],[365,101],[368,102],[254,103],[253,104],[252,105],[375,70],[251,106],[231,16],[378,16],[381,16],[380,70],[382,107],[200,16],[315,16],[202,108],[336,16],[337,16],[339,16],[342,109],[338,16],[340,110],[341,110],[221,16],[347,106],[356,111],[360,112],[213,113],[274,114],[273,16],[291,115],[288,16],[289,16],[294,116],[270,117],[212,118],[241,119],[312,120],[205,121],[211,122],[201,123],[323,124],[334,125],[322,16],[333,126],[242,16],[229,127],[309,128],[308,16],[311,129],[310,129],[263,130],[248,130],[303,131],[249,131],[207,132],[206,16],[307,133],[306,134],[305,135],[304,136],[208,137],[278,138],[293,139],[277,140],[299,141],[301,142],[298,140],[244,137],[194,16],[313,143],[268,144],[332,145],[225,146],[327,147],[220,16],[328,148],[330,149],[331,150],[326,16],[325,121],[245,151],[314,152],[335,153],[214,16],[216,16],[228,154],[302,155],[210,156],[215,16],[224,157],[223,158],[230,159],[271,160],[269,95],[232,161],[234,162],[379,16],[233,163],[235,164],[350,16],[351,16],[349,16],[352,16],[377,16],[236,165],[276,70],[92,16],[297,166],[255,16],[265,167],[358,70],[364,168],[262,70],[362,70],[261,169],[344,170],[260,168],[203,16],[366,171],[258,70],[259,70],[250,16],[264,16],[257,172],[256,173],[246,174],[240,175],[329,16],[239,176],[238,16],[354,16],[275,70],[346,177],[83,16],[91,178],[88,70],[89,16],[90,16],[324,179],[318,180],[316,16],[317,181],[357,182],[359,183],[361,184],[363,185],[387,186],[367,186],[386,187],[369,188],[373,189],[374,190],[376,191],[383,192],[385,16],[384,193],[343,194],[81,16],[82,16],[13,16],[14,16],[16,16],[15,16],[2,16],[17,16],[18,16],[19,16],[20,16],[21,16],[22,16],[23,16],[24,16],[3,16],[25,16],[26,16],[4,16],[27,16],[31,16],[28,16],[29,16],[30,16],[32,16],[33,16],[34,16],[5,16],[35,16],[36,16],[37,16],[38,16],[6,16],[42,16],[39,16],[40,16],[41,16],[43,16],[7,16],[44,16],[49,16],[50,16],[45,16],[46,16],[47,16],[48,16],[8,16],[54,16],[51,16],[52,16],[53,16],[55,16],[9,16],[56,16],[57,16],[58,16],[60,16],[59,16],[61,16],[62,16],[10,16],[63,16],[64,16],[65,16],[11,16],[66,16],[67,16],[68,16],[69,16],[70,16],[1,16],[71,16],[72,16],[12,16],[76,16],[74,16],[79,16],[78,16],[73,16],[77,16],[75,16],[80,16],[119,195],[129,196],[118,195],[139,197],[110,198],[109,199],[138,193],[132,200],[137,201],[112,202],[126,203],[111,204],[135,205],[107,206],[106,193],[136,207],[108,208],[113,209],[114,16],[117,209],[104,16],[140,210],[130,211],[121,212],[122,213],[124,214],[120,215],[123,216],[133,193],[115,217],[116,218],[125,219],[105,220],[128,211],[127,209],[131,16],[134,221]],"affectedFilesPendingEmit":[390,392,394,396,398,404,406,408,410,389,391,393,395,397,403,405,407,409],"version":"5.9.3"}
|