Spaces:
Sleeping
Sleeping
muhammad naveed commited on
Upload folder using huggingface_hub
Browse files- app/globals.css +160 -0
- app/resources/page.tsx +117 -73
- app/student/dashboard/page.tsx +128 -106
- app/student/learn/page.tsx +271 -233
app/globals.css
CHANGED
|
@@ -210,3 +210,163 @@ body {
|
|
| 210 |
background-color: rgba(20, 184, 166, 0.2);
|
| 211 |
color: #0f766e;
|
| 212 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
background-color: rgba(20, 184, 166, 0.2);
|
| 211 |
color: #0f766e;
|
| 212 |
}
|
| 213 |
+
|
| 214 |
+
/* ===== RESPONSIVE UTILITIES ===== */
|
| 215 |
+
|
| 216 |
+
/* Mobile-first responsive container */
|
| 217 |
+
.container-responsive {
|
| 218 |
+
@apply w-full px-4 mx-auto;
|
| 219 |
+
max-width: 100%;
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
@media (min-width: 640px) {
|
| 223 |
+
.container-responsive {
|
| 224 |
+
@apply px-6;
|
| 225 |
+
max-width: 640px;
|
| 226 |
+
}
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
@media (min-width: 768px) {
|
| 230 |
+
.container-responsive {
|
| 231 |
+
@apply px-8;
|
| 232 |
+
max-width: 768px;
|
| 233 |
+
}
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
@media (min-width: 1024px) {
|
| 237 |
+
.container-responsive {
|
| 238 |
+
max-width: 1024px;
|
| 239 |
+
}
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
@media (min-width: 1280px) {
|
| 243 |
+
.container-responsive {
|
| 244 |
+
max-width: 1280px;
|
| 245 |
+
}
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
/* Responsive Text */
|
| 249 |
+
.text-responsive-xs {
|
| 250 |
+
@apply text-xs sm:text-sm;
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
.text-responsive-sm {
|
| 254 |
+
@apply text-sm sm:text-base;
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
.text-responsive-base {
|
| 258 |
+
@apply text-base sm:text-lg md:text-xl;
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
.text-responsive-lg {
|
| 262 |
+
@apply text-lg sm:text-xl md:text-2xl;
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
.text-responsive-xl {
|
| 266 |
+
@apply text-xl sm:text-2xl md:text-3xl lg:text-4xl;
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
.text-responsive-2xl {
|
| 270 |
+
@apply text-2xl sm:text-3xl md:text-4xl lg:text-5xl;
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
/* Responsive Spacing */
|
| 274 |
+
.space-responsive {
|
| 275 |
+
@apply space-y-4 sm:space-y-6 md:space-y-8;
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
.padding-responsive {
|
| 279 |
+
@apply p-4 sm:p-6 md:p-8 lg:p-10;
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
.margin-responsive {
|
| 283 |
+
@apply m-4 sm:m-6 md:m-8;
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
/* Responsive Grid */
|
| 287 |
+
.grid-responsive {
|
| 288 |
+
@apply grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 sm:gap-6;
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
.grid-responsive-2 {
|
| 292 |
+
@apply grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6;
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
.grid-responsive-3 {
|
| 296 |
+
@apply grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6;
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
/* Hide/Show on devices */
|
| 300 |
+
.mobile-only {
|
| 301 |
+
@apply block sm:hidden;
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
.tablet-up {
|
| 305 |
+
@apply hidden sm:block;
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
.desktop-only {
|
| 309 |
+
@apply hidden lg:block;
|
| 310 |
+
}
|
| 311 |
+
|
| 312 |
+
/* Responsive Card */
|
| 313 |
+
.card-responsive {
|
| 314 |
+
@apply bg-white rounded-xl sm:rounded-2xl p-4 sm:p-6 shadow-sm hover:shadow-md transition-all;
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
/* Responsive Button */
|
| 318 |
+
.btn-responsive {
|
| 319 |
+
@apply px-4 py-2 sm:px-6 sm:py-3 text-sm sm:text-base rounded-lg sm:rounded-xl;
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
/* Touch-friendly targets for mobile */
|
| 323 |
+
@media (max-width: 640px) {
|
| 324 |
+
button, a, .clickable {
|
| 325 |
+
min-height: 44px;
|
| 326 |
+
min-width: 44px;
|
| 327 |
+
}
|
| 328 |
+
|
| 329 |
+
input, textarea, select {
|
| 330 |
+
font-size: 16px !important; /* Prevents zoom on iOS */
|
| 331 |
+
}
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
/* Responsive Navigation */
|
| 335 |
+
.nav-responsive {
|
| 336 |
+
@apply flex flex-col sm:flex-row items-center gap-2 sm:gap-4;
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
/* Safe area for mobile notches */
|
| 340 |
+
.safe-area-top {
|
| 341 |
+
padding-top: env(safe-area-inset-top);
|
| 342 |
+
}
|
| 343 |
+
|
| 344 |
+
.safe-area-bottom {
|
| 345 |
+
padding-bottom: env(safe-area-inset-bottom);
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
/* Responsive Modal */
|
| 349 |
+
.modal-responsive {
|
| 350 |
+
@apply w-[95vw] sm:w-[85vw] md:w-[75vw] lg:w-[60vw] max-w-4xl;
|
| 351 |
+
max-height: 90vh;
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
/* Responsive Chat */
|
| 355 |
+
.chat-container {
|
| 356 |
+
@apply h-[50vh] sm:h-[60vh] md:h-[70vh] overflow-y-auto;
|
| 357 |
+
}
|
| 358 |
+
|
| 359 |
+
.chat-input-container {
|
| 360 |
+
@apply fixed bottom-0 left-0 right-0 sm:relative p-4 bg-white border-t sm:border-t-0;
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
/* Landscape mode adjustments */
|
| 364 |
+
@media (orientation: landscape) and (max-height: 500px) {
|
| 365 |
+
.chat-container {
|
| 366 |
+
height: 40vh;
|
| 367 |
+
}
|
| 368 |
+
|
| 369 |
+
.section-wellness {
|
| 370 |
+
@apply py-8;
|
| 371 |
+
}
|
| 372 |
+
}
|
app/resources/page.tsx
CHANGED
|
@@ -39,6 +39,7 @@ 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 |
{
|
|
@@ -969,13 +970,13 @@ BEST PRACTICES
|
|
| 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-
|
| 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">
|
| 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">
|
|
@@ -1000,7 +1001,7 @@ BEST PRACTICES
|
|
| 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>
|
|
@@ -1008,43 +1009,86 @@ BEST PRACTICES
|
|
| 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-
|
| 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
|
| 1031 |
{[
|
| 1032 |
{ id: 'guides', label: 'Guides', icon: '📖' },
|
| 1033 |
-
{ id: 'cheatsheets', label: '
|
| 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>
|
|
@@ -1052,20 +1096,20 @@ BEST PRACTICES
|
|
| 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
|
| 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'
|
|
@@ -1077,11 +1121,11 @@ BEST PRACTICES
|
|
| 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" />
|
|
@@ -1093,19 +1137,19 @@ BEST PRACTICES
|
|
| 1093 |
)}
|
| 1094 |
|
| 1095 |
{activeTab === 'cheatsheets' && (
|
| 1096 |
-
<div className="grid
|
| 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
|
|
@@ -1115,7 +1159,7 @@ BEST PRACTICES
|
|
| 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 |
))}
|
|
@@ -1123,31 +1167,31 @@ BEST PRACTICES
|
|
| 1123 |
)}
|
| 1124 |
|
| 1125 |
{activeTab === 'videos' && (
|
| 1126 |
-
<div className="grid
|
| 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>
|
|
@@ -1157,24 +1201,24 @@ BEST PRACTICES
|
|
| 1157 |
)}
|
| 1158 |
|
| 1159 |
{activeTab === 'tools' && (
|
| 1160 |
-
<div className="grid
|
| 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"
|
|
@@ -1195,30 +1239,30 @@ BEST PRACTICES
|
|
| 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>
|
|
@@ -1228,26 +1272,26 @@ BEST PRACTICES
|
|
| 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>
|
|
@@ -1255,13 +1299,13 @@ BEST PRACTICES
|
|
| 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>
|
|
|
|
| 39 |
const [activeTab, setActiveTab] = useState('guides');
|
| 40 |
const [selectedGuide, setSelectedGuide] = useState<Guide | null>(null);
|
| 41 |
const [selectedVideo, setSelectedVideo] = useState<Video | null>(null);
|
| 42 |
+
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
| 43 |
|
| 44 |
const guides: Guide[] = [
|
| 45 |
{
|
|
|
|
| 970 |
<div className="min-h-screen bg-gradient-to-b from-cream-50 to-white">
|
| 971 |
{/* Header */}
|
| 972 |
<header className="bg-white/80 backdrop-blur-md border-b border-warmgray-100 sticky top-0 z-50">
|
| 973 |
+
<div className="max-w-7xl mx-auto px-3 sm:px-4 lg:px-8">
|
| 974 |
+
<div className="flex items-center justify-between h-14 sm:h-16">
|
| 975 |
+
<Link href="/" className="flex items-center space-x-2 sm:space-x-3">
|
| 976 |
+
<div className="w-8 h-8 sm:w-10 sm:h-10 bg-gradient-to-br from-teal-500 to-teal-600 rounded-lg sm:rounded-xl flex items-center justify-center shadow-soft">
|
| 977 |
+
<span className="text-white font-bold text-sm sm:text-lg">LF</span>
|
| 978 |
</div>
|
| 979 |
+
<span className="text-lg sm:text-xl font-bold text-warmgray-900">LearnFlow</span>
|
| 980 |
</Link>
|
| 981 |
|
| 982 |
<nav className="hidden md:flex items-center space-x-1">
|
|
|
|
| 1001 |
))}
|
| 1002 |
</nav>
|
| 1003 |
|
| 1004 |
+
<div className="hidden md:flex items-center space-x-3">
|
| 1005 |
<Link href="/register" className="text-warmgray-700 hover:text-teal-700 font-medium text-sm">
|
| 1006 |
Sign Up
|
| 1007 |
</Link>
|
|
|
|
| 1009 |
Login
|
| 1010 |
</Link>
|
| 1011 |
</div>
|
| 1012 |
+
|
| 1013 |
+
{/* Mobile menu button */}
|
| 1014 |
+
<button
|
| 1015 |
+
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
| 1016 |
+
className="md:hidden p-2 rounded-lg text-warmgray-600 hover:bg-teal-50"
|
| 1017 |
+
>
|
| 1018 |
+
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 1019 |
+
{mobileMenuOpen ? (
|
| 1020 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
| 1021 |
+
) : (
|
| 1022 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
| 1023 |
+
)}
|
| 1024 |
+
</svg>
|
| 1025 |
+
</button>
|
| 1026 |
</div>
|
| 1027 |
+
|
| 1028 |
+
{/* Mobile menu */}
|
| 1029 |
+
{mobileMenuOpen && (
|
| 1030 |
+
<div className="md:hidden py-4 border-t border-warmgray-100">
|
| 1031 |
+
<div className="flex flex-col space-y-1">
|
| 1032 |
+
{[
|
| 1033 |
+
{ name: 'Home', href: '/' },
|
| 1034 |
+
{ name: 'Courses', href: '/courses' },
|
| 1035 |
+
{ name: 'About', href: '/about' },
|
| 1036 |
+
{ name: 'Resources', href: '/resources' },
|
| 1037 |
+
{ name: 'Contact', href: '/contact' },
|
| 1038 |
+
].map((item) => (
|
| 1039 |
+
<Link
|
| 1040 |
+
key={item.name}
|
| 1041 |
+
href={item.href}
|
| 1042 |
+
className={`px-4 py-3 rounded-xl font-medium ${
|
| 1043 |
+
item.name === 'Resources' ? 'bg-teal-50 text-teal-700' : 'text-warmgray-700 hover:bg-teal-50'
|
| 1044 |
+
}`}
|
| 1045 |
+
>
|
| 1046 |
+
{item.name}
|
| 1047 |
+
</Link>
|
| 1048 |
+
))}
|
| 1049 |
+
<div className="pt-4 mt-2 border-t border-warmgray-100 flex flex-col space-y-2">
|
| 1050 |
+
<Link href="/register" className="text-teal-600 font-medium px-4 py-2">Sign Up</Link>
|
| 1051 |
+
<Link href="/" className="bg-teal-600 text-white px-4 py-3 rounded-xl text-center font-medium">Login</Link>
|
| 1052 |
+
</div>
|
| 1053 |
+
</div>
|
| 1054 |
+
</div>
|
| 1055 |
+
)}
|
| 1056 |
</div>
|
| 1057 |
</header>
|
| 1058 |
|
| 1059 |
{/* Hero Section */}
|
| 1060 |
+
<section className="py-8 sm:py-12 md:py-16 px-4">
|
| 1061 |
<div className="max-w-4xl mx-auto text-center">
|
| 1062 |
+
<h1 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-bold text-warmgray-900 mb-2 sm:mb-4">
|
| 1063 |
Learning <span className="text-teal-600">Resources</span>
|
| 1064 |
</h1>
|
| 1065 |
+
<p className="text-sm sm:text-base md:text-lg text-warmgray-600 px-2">
|
| 1066 |
Free guides, cheatsheets, videos, and tools to accelerate your Python learning journey.
|
| 1067 |
</p>
|
| 1068 |
</div>
|
| 1069 |
</section>
|
| 1070 |
|
| 1071 |
{/* Tabs */}
|
| 1072 |
+
<section className="px-3 sm:px-4 pb-6 sm:pb-8">
|
| 1073 |
<div className="max-w-6xl mx-auto">
|
| 1074 |
+
<div className="flex justify-center gap-1 sm:gap-2 bg-white rounded-xl sm:rounded-2xl p-1.5 sm:p-2 shadow-soft overflow-x-auto">
|
| 1075 |
{[
|
| 1076 |
{ id: 'guides', label: 'Guides', icon: '📖' },
|
| 1077 |
+
{ id: 'cheatsheets', label: 'Sheets', icon: '📝' },
|
| 1078 |
{ id: 'videos', label: 'Videos', icon: '🎥' },
|
| 1079 |
{ id: 'tools', label: 'Tools', icon: '🛠️' },
|
| 1080 |
].map((tab) => (
|
| 1081 |
<button
|
| 1082 |
key={tab.id}
|
| 1083 |
onClick={() => setActiveTab(tab.id)}
|
| 1084 |
+
className={`px-3 py-2 sm:px-6 sm:py-3 rounded-lg sm:rounded-xl font-medium transition-all duration-200 flex items-center gap-1 sm:gap-2 text-sm sm:text-base whitespace-nowrap ${
|
| 1085 |
activeTab === tab.id
|
| 1086 |
? 'bg-teal-600 text-white shadow-soft'
|
| 1087 |
: 'text-warmgray-600 hover:bg-teal-50 hover:text-teal-700'
|
| 1088 |
}`}
|
| 1089 |
>
|
| 1090 |
+
<span className="text-base sm:text-lg">{tab.icon}</span>
|
| 1091 |
+
<span className="hidden xs:inline sm:inline">{tab.label}</span>
|
| 1092 |
</button>
|
| 1093 |
))}
|
| 1094 |
</div>
|
|
|
|
| 1096 |
</section>
|
| 1097 |
|
| 1098 |
{/* Content */}
|
| 1099 |
+
<section className="px-3 sm:px-4 pb-12 sm:pb-20">
|
| 1100 |
<div className="max-w-6xl mx-auto">
|
| 1101 |
{activeTab === 'guides' && (
|
| 1102 |
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6">
|
| 1103 |
{guides.map((guide, index) => (
|
| 1104 |
<div
|
| 1105 |
key={index}
|
| 1106 |
onClick={() => setSelectedGuide(guide)}
|
| 1107 |
+
className="bg-white rounded-xl sm:rounded-2xl p-4 sm:p-6 shadow-soft hover:shadow-soft-lg transition-all group cursor-pointer"
|
| 1108 |
>
|
| 1109 |
+
<div className="text-3xl sm:text-4xl mb-3 sm:mb-4">{guide.icon}</div>
|
| 1110 |
+
<div className="flex flex-wrap items-center gap-2 mb-2">
|
| 1111 |
<span
|
| 1112 |
+
className={`px-2 py-0.5 sm:py-1 rounded-full text-xs font-medium ${
|
| 1113 |
guide.difficulty === 'Beginner'
|
| 1114 |
? 'bg-green-100 text-green-700'
|
| 1115 |
: guide.difficulty === 'Intermediate'
|
|
|
|
| 1121 |
</span>
|
| 1122 |
<span className="text-warmgray-400 text-xs">{guide.readTime} read</span>
|
| 1123 |
</div>
|
| 1124 |
+
<h3 className="font-semibold text-sm sm:text-base text-warmgray-900 mb-1 sm:mb-2 group-hover:text-teal-600 transition-colors">
|
| 1125 |
{guide.title}
|
| 1126 |
</h3>
|
| 1127 |
+
<p className="text-xs sm:text-sm text-warmgray-600 line-clamp-2">{guide.description}</p>
|
| 1128 |
+
<div className="mt-3 sm:mt-4 text-teal-600 text-xs sm:text-sm font-medium flex items-center gap-1">
|
| 1129 |
Read Guide
|
| 1130 |
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 1131 |
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
|
|
|
| 1137 |
)}
|
| 1138 |
|
| 1139 |
{activeTab === 'cheatsheets' && (
|
| 1140 |
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6">
|
| 1141 |
{cheatsheets.map((sheet, index) => (
|
| 1142 |
+
<div key={index} className="bg-white rounded-xl sm:rounded-2xl p-4 sm:p-6 shadow-soft hover:shadow-soft-lg transition-all group">
|
| 1143 |
+
<div className="flex items-center justify-between mb-3 sm:mb-4">
|
| 1144 |
+
<span className="text-3xl sm:text-4xl">{sheet.icon}</span>
|
| 1145 |
+
<span className="text-xs sm:text-sm text-warmgray-400">{sheet.downloads} downloads</span>
|
| 1146 |
</div>
|
| 1147 |
+
<h3 className="font-semibold text-sm sm:text-base text-warmgray-900 mb-3 sm:mb-4 group-hover:text-teal-600 transition-colors">
|
| 1148 |
{sheet.title}
|
| 1149 |
</h3>
|
| 1150 |
<button
|
| 1151 |
onClick={() => downloadCheatsheet(sheet)}
|
| 1152 |
+
className="w-full bg-teal-50 text-teal-700 py-2 sm:py-2.5 rounded-lg sm:rounded-xl text-sm font-medium hover:bg-teal-100 transition-colors flex items-center justify-center gap-2"
|
| 1153 |
>
|
| 1154 |
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 1155 |
<path
|
|
|
|
| 1159 |
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
| 1160 |
/>
|
| 1161 |
</svg>
|
| 1162 |
+
<span className="hidden xs:inline">Download </span>Cheatsheet
|
| 1163 |
</button>
|
| 1164 |
</div>
|
| 1165 |
))}
|
|
|
|
| 1167 |
)}
|
| 1168 |
|
| 1169 |
{activeTab === 'videos' && (
|
| 1170 |
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 sm:gap-6">
|
| 1171 |
{videos.map((video, index) => (
|
| 1172 |
<div
|
| 1173 |
key={index}
|
| 1174 |
onClick={() => setSelectedVideo(video)}
|
| 1175 |
+
className="bg-white rounded-xl sm:rounded-2xl overflow-hidden shadow-soft hover:shadow-soft-lg transition-all group cursor-pointer"
|
| 1176 |
>
|
| 1177 |
+
<div className="bg-gradient-to-br from-teal-500 to-sage-500 h-36 sm:h-48 flex items-center justify-center relative">
|
| 1178 |
+
<span className="text-4xl sm:text-6xl">{video.thumbnail}</span>
|
| 1179 |
<div className="absolute inset-0 bg-black/20 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
|
| 1180 |
+
<div className="w-12 h-12 sm:w-16 sm:h-16 bg-white rounded-full flex items-center justify-center">
|
| 1181 |
+
<svg className="w-6 h-6 sm:w-8 sm:h-8 text-teal-600 ml-1" fill="currentColor" viewBox="0 0 24 24">
|
| 1182 |
<path d="M8 5v14l11-7z" />
|
| 1183 |
</svg>
|
| 1184 |
</div>
|
| 1185 |
</div>
|
| 1186 |
+
<span className="absolute bottom-2 right-2 sm:bottom-3 sm:right-3 bg-black/70 text-white text-xs sm:text-sm px-2 py-1 rounded">
|
| 1187 |
{video.duration}
|
| 1188 |
</span>
|
| 1189 |
</div>
|
| 1190 |
+
<div className="p-3 sm:p-5">
|
| 1191 |
+
<h3 className="font-semibold text-sm sm:text-base text-warmgray-900 mb-1 group-hover:text-teal-600 transition-colors line-clamp-1">
|
| 1192 |
{video.title}
|
| 1193 |
</h3>
|
| 1194 |
+
<p className="text-xs sm:text-sm text-warmgray-500">
|
| 1195 |
{video.channel} • {video.views} views
|
| 1196 |
</p>
|
| 1197 |
</div>
|
|
|
|
| 1201 |
)}
|
| 1202 |
|
| 1203 |
{activeTab === 'tools' && (
|
| 1204 |
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 sm:gap-6">
|
| 1205 |
{tools.map((tool, index) => (
|
| 1206 |
<a
|
| 1207 |
key={index}
|
| 1208 |
href={tool.url}
|
| 1209 |
target="_blank"
|
| 1210 |
rel="noopener noreferrer"
|
| 1211 |
+
className="bg-white rounded-xl sm:rounded-2xl p-4 sm:p-6 shadow-soft hover:shadow-soft-lg transition-all group flex items-center gap-3 sm:gap-4"
|
| 1212 |
>
|
| 1213 |
+
<div className="text-3xl sm:text-4xl flex-shrink-0">{tool.icon}</div>
|
| 1214 |
+
<div className="flex-1 min-w-0">
|
| 1215 |
+
<h3 className="font-semibold text-sm sm:text-base text-warmgray-900 group-hover:text-teal-600 transition-colors">
|
| 1216 |
{tool.name}
|
| 1217 |
</h3>
|
| 1218 |
+
<p className="text-xs sm:text-sm text-warmgray-600 line-clamp-1">{tool.description}</p>
|
| 1219 |
</div>
|
| 1220 |
<svg
|
| 1221 |
+
className="w-4 h-4 sm:w-5 sm:h-5 text-warmgray-400 group-hover:text-teal-600 transition-colors flex-shrink-0"
|
| 1222 |
fill="none"
|
| 1223 |
stroke="currentColor"
|
| 1224 |
viewBox="0 0 24 24"
|
|
|
|
| 1239 |
|
| 1240 |
{/* Guide Modal */}
|
| 1241 |
{selectedGuide && (
|
| 1242 |
+
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-2 sm:p-4" onClick={() => setSelectedGuide(null)}>
|
| 1243 |
<div
|
| 1244 |
+
className="bg-white rounded-xl sm:rounded-2xl max-w-3xl w-full max-h-[95vh] sm:max-h-[90vh] overflow-hidden"
|
| 1245 |
onClick={(e) => e.stopPropagation()}
|
| 1246 |
>
|
| 1247 |
+
<div className="p-4 sm:p-6 border-b border-warmgray-100 flex items-center justify-between">
|
| 1248 |
+
<div className="flex items-center gap-2 sm:gap-3 flex-1 min-w-0">
|
| 1249 |
+
<span className="text-2xl sm:text-3xl flex-shrink-0">{selectedGuide.icon}</span>
|
| 1250 |
+
<div className="min-w-0">
|
| 1251 |
+
<h2 className="text-base sm:text-xl font-bold text-warmgray-900 truncate">{selectedGuide.title}</h2>
|
| 1252 |
+
<p className="text-xs sm:text-sm text-warmgray-500">{selectedGuide.readTime} read</p>
|
| 1253 |
</div>
|
| 1254 |
</div>
|
| 1255 |
<button
|
| 1256 |
onClick={() => setSelectedGuide(null)}
|
| 1257 |
+
className="text-warmgray-400 hover:text-warmgray-600 p-2 flex-shrink-0"
|
| 1258 |
>
|
| 1259 |
+
<svg className="w-5 h-5 sm:w-6 sm:h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 1260 |
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
| 1261 |
</svg>
|
| 1262 |
</button>
|
| 1263 |
</div>
|
| 1264 |
+
<div className="p-4 sm:p-6 overflow-y-auto max-h-[calc(95vh-80px)] sm:max-h-[calc(90vh-100px)]">
|
| 1265 |
+
<pre className="whitespace-pre-wrap font-mono text-xs sm:text-sm text-warmgray-700 leading-relaxed">
|
| 1266 |
{selectedGuide.content}
|
| 1267 |
</pre>
|
| 1268 |
</div>
|
|
|
|
| 1272 |
|
| 1273 |
{/* Video Modal */}
|
| 1274 |
{selectedVideo && (
|
| 1275 |
+
<div className="fixed inset-0 bg-black/80 flex items-center justify-center z-50 p-2 sm:p-4" onClick={() => setSelectedVideo(null)}>
|
| 1276 |
<div className="w-full max-w-4xl" onClick={(e) => e.stopPropagation()}>
|
| 1277 |
+
<div className="flex justify-between items-center mb-2 sm:mb-4">
|
| 1278 |
+
<h3 className="text-white font-semibold text-sm sm:text-base line-clamp-1 flex-1 mr-2">{selectedVideo.title}</h3>
|
| 1279 |
+
<button onClick={() => setSelectedVideo(null)} className="text-white hover:text-warmgray-300 p-2 flex-shrink-0">
|
| 1280 |
+
<svg className="w-5 h-5 sm:w-6 sm:h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 1281 |
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
| 1282 |
</svg>
|
| 1283 |
</button>
|
| 1284 |
</div>
|
| 1285 |
<div className="relative pb-[56.25%] h-0">
|
| 1286 |
<iframe
|
| 1287 |
+
className="absolute top-0 left-0 w-full h-full rounded-lg sm:rounded-xl"
|
| 1288 |
src={`https://www.youtube.com/embed/${selectedVideo.youtubeId}?autoplay=1`}
|
| 1289 |
title={selectedVideo.title}
|
| 1290 |
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
| 1291 |
allowFullScreen
|
| 1292 |
/>
|
| 1293 |
</div>
|
| 1294 |
+
<p className="text-warmgray-400 mt-2 sm:mt-3 text-xs sm:text-sm">
|
| 1295 |
{selectedVideo.channel} • {selectedVideo.views} views
|
| 1296 |
</p>
|
| 1297 |
</div>
|
|
|
|
| 1299 |
)}
|
| 1300 |
|
| 1301 |
{/* CTA */}
|
| 1302 |
+
<section className="py-10 sm:py-16 px-4 bg-gradient-to-r from-teal-600 to-teal-700">
|
| 1303 |
<div className="max-w-4xl mx-auto text-center text-white">
|
| 1304 |
+
<h2 className="text-xl sm:text-2xl md:text-3xl font-bold mb-2 sm:mb-4">Want More Resources?</h2>
|
| 1305 |
+
<p className="text-sm sm:text-base text-teal-100 mb-6 sm:mb-8">Sign up for free and get access to exclusive learning materials.</p>
|
| 1306 |
<Link
|
| 1307 |
href="/register"
|
| 1308 |
+
className="inline-block bg-white text-teal-700 px-6 py-2.5 sm:px-8 sm:py-3 rounded-full font-semibold text-sm sm:text-base hover:bg-cream-50 transition-colors shadow-lg"
|
| 1309 |
>
|
| 1310 |
Get Started Free
|
| 1311 |
</Link>
|
app/student/dashboard/page.tsx
CHANGED
|
@@ -141,102 +141,122 @@ export default function StudentDashboard() {
|
|
| 141 |
</div>
|
| 142 |
|
| 143 |
<div className="relative">
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 156 |
-
<div className="flex items-center space-x-3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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-
|
| 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
|
| 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>
|
|
@@ -244,18 +264,18 @@ export default function StudentDashboard() {
|
|
| 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'
|
|
@@ -263,37 +283,39 @@ export default function StudentDashboard() {
|
|
| 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="
|
| 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="
|
| 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="
|
| 291 |
-
<span className="
|
| 292 |
</div>
|
| 293 |
|
| 294 |
-
<div className="w-full bg-gray-200 rounded-full h-2
|
| 295 |
<div
|
| 296 |
-
className={`h-2
|
| 297 |
module.status === 'completed' ? 'bg-green-500' :
|
| 298 |
module.status === 'in-progress' ? 'bg-blue-500' : 'bg-gray-400'
|
| 299 |
}`}
|
|
@@ -304,7 +326,7 @@ export default function StudentDashboard() {
|
|
| 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'
|
|
@@ -320,8 +342,8 @@ export default function StudentDashboard() {
|
|
| 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' :
|
|
@@ -339,36 +361,36 @@ export default function StudentDashboard() {
|
|
| 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-
|
| 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-
|
| 367 |
Score: {activity.score}%
|
| 368 |
</span>
|
| 369 |
)}
|
| 370 |
{activity.result && (
|
| 371 |
-
<span className="inline-block mt-1 px-2 py-
|
| 372 |
{activity.result}
|
| 373 |
</span>
|
| 374 |
)}
|
|
@@ -380,23 +402,23 @@ export default function StudentDashboard() {
|
|
| 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
|
|
@@ -404,51 +426,51 @@ export default function StudentDashboard() {
|
|
| 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>
|
|
|
|
| 141 |
</div>
|
| 142 |
|
| 143 |
<div className="relative">
|
| 144 |
+
{/* Responsive Header */}
|
| 145 |
+
<header className="bg-white/90 backdrop-blur-md shadow-lg border-b border-white/20 sticky top-0 z-10">
|
| 146 |
+
<div className="max-w-7xl mx-auto px-3 py-3 sm:px-4 sm:py-4 lg:px-8">
|
| 147 |
+
<div className="flex justify-between items-center">
|
| 148 |
+
{/* Logo */}
|
| 149 |
+
<div className="flex items-center space-x-2 sm:space-x-4">
|
| 150 |
+
<div className="w-8 h-8 sm:w-10 sm:h-10 bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg flex items-center justify-center flex-shrink-0">
|
| 151 |
+
<span className="text-white font-bold text-xs sm:text-sm">LF</span>
|
| 152 |
+
</div>
|
| 153 |
+
<h1 className="text-lg sm:text-xl lg:text-2xl font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
|
| 154 |
+
<span className="hidden sm:inline">LearnFlow </span>Dashboard
|
| 155 |
+
</h1>
|
| 156 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
|
| 158 |
+
{/* Desktop User Menu */}
|
| 159 |
+
<div className="hidden sm:flex items-center space-x-3 sm:space-x-4">
|
| 160 |
+
<div className="flex items-center space-x-2 sm:space-x-3 bg-gray-100/50 rounded-full px-3 py-1.5 sm:px-4 sm:py-2">
|
| 161 |
+
<div className="w-7 h-7 sm:w-8 sm:h-8 bg-gradient-to-r from-green-400 to-blue-500 rounded-full flex items-center justify-center">
|
| 162 |
+
<span className="text-white text-xs sm:text-sm font-semibold">{user?.name?.charAt(0).toUpperCase() || 'U'}</span>
|
| 163 |
+
</div>
|
| 164 |
+
<span className="text-gray-700 font-medium text-sm sm:text-base hidden md:inline">{user?.name?.split(' ')[0] || 'User'}</span>
|
| 165 |
+
</div>
|
| 166 |
+
<button
|
| 167 |
+
onClick={handleLogout}
|
| 168 |
+
className="bg-gradient-to-r from-red-500 to-pink-600 hover:from-red-600 hover:to-pink-700 text-white px-3 py-1.5 sm:px-4 sm:py-2 rounded-lg transition-all text-sm sm:text-base"
|
| 169 |
+
>
|
| 170 |
+
Logout
|
| 171 |
+
</button>
|
| 172 |
+
</div>
|
| 173 |
+
|
| 174 |
+
{/* Mobile User Avatar & Logout */}
|
| 175 |
+
<div className="flex sm:hidden items-center space-x-2">
|
| 176 |
<div className="w-8 h-8 bg-gradient-to-r from-green-400 to-blue-500 rounded-full flex items-center justify-center">
|
| 177 |
<span className="text-white text-sm font-semibold">{user?.name?.charAt(0).toUpperCase() || 'U'}</span>
|
| 178 |
</div>
|
| 179 |
+
<button
|
| 180 |
+
onClick={handleLogout}
|
| 181 |
+
className="bg-red-500 text-white p-2 rounded-lg"
|
| 182 |
+
>
|
| 183 |
+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 184 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
|
| 185 |
+
</svg>
|
| 186 |
+
</button>
|
| 187 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
</div>
|
| 189 |
</div>
|
| 190 |
</header>
|
| 191 |
|
| 192 |
+
<main className="max-w-7xl mx-auto px-3 py-4 sm:px-4 sm:py-6 md:px-6 md:py-8 lg:px-8">
|
| 193 |
+
<div className="mb-4 sm:mb-6 md:mb-8">
|
| 194 |
+
<h2 className="text-xl sm:text-2xl md:text-3xl font-bold text-gray-800 mb-1 sm:mb-2">Welcome back, {user?.name?.split(' ')[0] || 'Student'}!</h2>
|
| 195 |
+
<p className="text-sm sm:text-base text-gray-600">Continue your Python learning journey where you left off</p>
|
| 196 |
</div>
|
| 197 |
|
| 198 |
+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4 sm:gap-6 md:gap-8">
|
| 199 |
{/* Progress Overview */}
|
| 200 |
<div className="lg:col-span-2">
|
| 201 |
+
<div className="bg-white/70 backdrop-blur-sm rounded-xl sm:rounded-2xl shadow-xl border border-white/20 p-4 sm:p-6 mb-4 sm:mb-6 md:mb-8">
|
| 202 |
+
<h2 className="text-lg sm:text-xl md:text-2xl font-bold text-gray-800 mb-4 sm:mb-6 flex items-center">
|
| 203 |
+
<svg className="w-5 h-5 sm:w-6 sm:h-6 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 204 |
<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" />
|
| 205 |
</svg>
|
| 206 |
Your Learning Progress
|
| 207 |
</h2>
|
| 208 |
|
| 209 |
+
<div className="grid grid-cols-2 lg:grid-cols-4 gap-2 sm:gap-3 md:gap-4 mb-4 sm:mb-6 md:mb-8">
|
| 210 |
+
<div className="bg-gradient-to-br from-blue-100 to-blue-200 p-3 sm:p-4 md:p-5 rounded-lg sm:rounded-xl shadow-md border border-white/50">
|
| 211 |
<div className="flex items-center justify-between">
|
| 212 |
<div>
|
| 213 |
+
<p className="text-xs sm:text-sm font-semibold text-blue-800">Overall Mastery</p>
|
| 214 |
+
<p className="text-xl sm:text-2xl md:text-3xl font-bold text-blue-600 mt-0.5 sm:mt-1">72%</p>
|
| 215 |
</div>
|
| 216 |
+
<div className="w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 bg-blue-500/20 rounded-full flex items-center justify-center flex-shrink-0">
|
| 217 |
+
<svg className="w-4 h-4 sm:w-5 sm:h-5 md:w-6 md:h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 218 |
<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" />
|
| 219 |
</svg>
|
| 220 |
</div>
|
| 221 |
</div>
|
| 222 |
</div>
|
| 223 |
|
| 224 |
+
<div className="bg-gradient-to-br from-green-100 to-green-200 p-3 sm:p-4 md:p-5 rounded-lg sm:rounded-xl shadow-md border border-white/50">
|
| 225 |
<div className="flex items-center justify-between">
|
| 226 |
<div>
|
| 227 |
+
<p className="text-xs sm:text-sm font-semibold text-green-800">Modules Completed</p>
|
| 228 |
+
<p className="text-xl sm:text-2xl md:text-3xl font-bold text-green-600 mt-0.5 sm:mt-1">2/8</p>
|
| 229 |
</div>
|
| 230 |
+
<div className="w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 bg-green-500/20 rounded-full flex items-center justify-center flex-shrink-0">
|
| 231 |
+
<svg className="w-4 h-4 sm:w-5 sm:h-5 md:w-6 md:h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 232 |
<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" />
|
| 233 |
</svg>
|
| 234 |
</div>
|
| 235 |
</div>
|
| 236 |
</div>
|
| 237 |
|
| 238 |
+
<div className="bg-gradient-to-br from-yellow-100 to-yellow-200 p-3 sm:p-4 md:p-5 rounded-lg sm:rounded-xl shadow-md border border-white/50">
|
| 239 |
<div className="flex items-center justify-between">
|
| 240 |
<div>
|
| 241 |
+
<p className="text-xs sm:text-sm font-semibold text-yellow-800">Current Streak</p>
|
| 242 |
+
<p className="text-xl sm:text-2xl md:text-3xl font-bold text-yellow-600 mt-0.5 sm:mt-1">5 days</p>
|
| 243 |
</div>
|
| 244 |
+
<div className="w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 bg-yellow-500/20 rounded-full flex items-center justify-center flex-shrink-0">
|
| 245 |
+
<svg className="w-4 h-4 sm:w-5 sm:h-5 md:w-6 md:h-6 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 246 |
<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" />
|
| 247 |
</svg>
|
| 248 |
</div>
|
| 249 |
</div>
|
| 250 |
</div>
|
| 251 |
|
| 252 |
+
<div className="bg-gradient-to-br from-purple-100 to-purple-200 p-3 sm:p-4 md:p-5 rounded-lg sm:rounded-xl shadow-md border border-white/50">
|
| 253 |
<div className="flex items-center justify-between">
|
| 254 |
<div>
|
| 255 |
+
<p className="text-xs sm:text-sm font-semibold text-purple-800">Hours Learned</p>
|
| 256 |
+
<p className="text-xl sm:text-2xl md:text-3xl font-bold text-purple-600 mt-0.5 sm:mt-1">24</p>
|
| 257 |
</div>
|
| 258 |
+
<div className="w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 bg-purple-500/20 rounded-full flex items-center justify-center flex-shrink-0">
|
| 259 |
+
<svg className="w-4 h-4 sm:w-5 sm:h-5 md:w-6 md:h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 260 |
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
| 261 |
</svg>
|
| 262 |
</div>
|
|
|
|
| 264 |
</div>
|
| 265 |
</div>
|
| 266 |
|
| 267 |
+
<h3 className="text-base sm:text-lg md:text-xl font-semibold text-gray-800 mb-3 sm:mb-4 flex items-center">
|
| 268 |
+
<svg className="w-4 h-4 sm:w-5 sm:h-5 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 269 |
<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" />
|
| 270 |
</svg>
|
| 271 |
Learning Modules
|
| 272 |
</h3>
|
| 273 |
|
| 274 |
+
<div className="space-y-3 sm:space-y-4">
|
| 275 |
{modules.map((module) => (
|
| 276 |
<div
|
| 277 |
key={module.id}
|
| 278 |
+
className={`bg-white/50 backdrop-blur-sm rounded-lg sm:rounded-xl p-3 sm:p-4 md:p-5 border transition-all duration-300 hover:shadow-lg ${
|
| 279 |
module.status === 'locked'
|
| 280 |
? 'opacity-60 border-gray-200'
|
| 281 |
: module.status === 'in-progress'
|
|
|
|
| 283 |
: 'border-green-200 shadow-md'
|
| 284 |
}`}
|
| 285 |
>
|
| 286 |
+
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-3 sm:gap-0">
|
| 287 |
<div className="flex-1">
|
| 288 |
+
<div className="flex flex-wrap items-center gap-2 mb-2">
|
| 289 |
+
<h4 className="font-bold text-sm sm:text-base md:text-lg text-gray-800">{module.name}</h4>
|
| 290 |
{module.status === 'completed' && (
|
| 291 |
+
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
|
| 292 |
<svg className="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 293 |
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
| 294 |
</svg>
|
| 295 |
+
<span className="hidden xs:inline">Completed</span>
|
| 296 |
+
<span className="xs:hidden">Done</span>
|
| 297 |
</span>
|
| 298 |
)}
|
| 299 |
{module.status === 'in-progress' && (
|
| 300 |
+
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
| 301 |
<svg className="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 302 |
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
| 303 |
</svg>
|
| 304 |
+
<span className="hidden xs:inline">In Progress</span>
|
| 305 |
+
<span className="xs:hidden">Active</span>
|
| 306 |
</span>
|
| 307 |
)}
|
| 308 |
</div>
|
| 309 |
+
<p className="text-gray-600 text-xs sm:text-sm mb-2 sm:mb-3 line-clamp-2">{module.description}</p>
|
| 310 |
|
| 311 |
+
<div className="flex items-center justify-between mb-1 sm:mb-2 text-xs sm:text-sm">
|
| 312 |
+
<span className="font-medium text-gray-700">Progress: {module.progress}%</span>
|
| 313 |
+
<span className="font-medium text-gray-700">Mastery: {module.masteryLevel}%</span>
|
| 314 |
</div>
|
| 315 |
|
| 316 |
+
<div className="w-full bg-gray-200 rounded-full h-2 mb-2">
|
| 317 |
<div
|
| 318 |
+
className={`h-2 rounded-full ${
|
| 319 |
module.status === 'completed' ? 'bg-green-500' :
|
| 320 |
module.status === 'in-progress' ? 'bg-blue-500' : 'bg-gray-400'
|
| 321 |
}`}
|
|
|
|
| 326 |
|
| 327 |
<Link
|
| 328 |
href={`/student/learn?module=${module.id}`}
|
| 329 |
+
className={`w-full sm:w-auto sm:ml-4 px-3 sm:px-4 py-2 rounded-lg text-sm font-medium transition-all duration-300 transform hover:scale-105 text-center ${
|
| 330 |
module.status === 'locked'
|
| 331 |
? 'bg-gray-300 text-gray-500 cursor-not-allowed'
|
| 332 |
: module.status === 'completed'
|
|
|
|
| 342 |
</Link>
|
| 343 |
</div>
|
| 344 |
|
| 345 |
+
<div className="mt-2 sm:mt-3 flex items-center">
|
| 346 |
+
<span className={`text-xs px-2 sm:px-3 py-1 rounded-full font-medium ${
|
| 347 |
module.masteryLevel >= 90 ? 'bg-gradient-to-r from-blue-100 to-blue-200 text-blue-800' :
|
| 348 |
module.masteryLevel >= 70 ? 'bg-gradient-to-r from-green-100 to-green-200 text-green-800' :
|
| 349 |
module.masteryLevel >= 40 ? 'bg-gradient-to-r from-yellow-100 to-yellow-200 text-yellow-800' :
|
|
|
|
| 361 |
</div>
|
| 362 |
|
| 363 |
{/* Sidebar */}
|
| 364 |
+
<div className="space-y-4 sm:space-y-6">
|
| 365 |
{/* Recent Activity */}
|
| 366 |
+
<div className="bg-white/70 backdrop-blur-sm rounded-xl sm:rounded-2xl shadow-xl border border-white/20 p-4 sm:p-6">
|
| 367 |
+
<h3 className="text-base sm:text-lg md:text-xl font-bold text-gray-800 mb-3 sm:mb-4 flex items-center">
|
| 368 |
+
<svg className="w-4 h-4 sm:w-5 sm:h-5 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 369 |
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
| 370 |
</svg>
|
| 371 |
Recent Activity
|
| 372 |
</h3>
|
| 373 |
+
<ul className="space-y-3 sm:space-y-4">
|
| 374 |
{recentActivities.map((activity) => (
|
| 375 |
+
<li key={activity.id} className="border-b border-gray-100 pb-3 sm:pb-4 last:border-0 last:pb-0">
|
| 376 |
<div className="flex items-start">
|
| 377 |
+
<div className="flex-shrink-0 mt-0.5">
|
| 378 |
+
<div className="w-7 h-7 sm:w-8 sm:h-8 bg-gradient-to-r from-blue-100 to-purple-100 rounded-full flex items-center justify-center">
|
| 379 |
+
<svg className="w-3.5 h-3.5 sm:w-4 sm:h-4 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 380 |
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
| 381 |
</svg>
|
| 382 |
</div>
|
| 383 |
</div>
|
| 384 |
+
<div className="ml-2 sm:ml-3 flex-1 min-w-0">
|
| 385 |
+
<p className="text-xs sm:text-sm font-medium text-gray-800 line-clamp-2">{activity.activity}</p>
|
| 386 |
+
<p className="text-xs text-gray-500 mt-0.5 sm:mt-1">{activity.timestamp}</p>
|
| 387 |
{activity.score && (
|
| 388 |
+
<span className="inline-block mt-1 px-2 py-0.5 text-xs bg-green-100 text-green-800 rounded-full">
|
| 389 |
Score: {activity.score}%
|
| 390 |
</span>
|
| 391 |
)}
|
| 392 |
{activity.result && (
|
| 393 |
+
<span className="inline-block mt-1 px-2 py-0.5 text-xs bg-blue-100 text-blue-800 rounded-full">
|
| 394 |
{activity.result}
|
| 395 |
</span>
|
| 396 |
)}
|
|
|
|
| 402 |
</div>
|
| 403 |
|
| 404 |
{/* Continue Learning */}
|
| 405 |
+
<div className="bg-gradient-to-br from-blue-500 to-purple-600 rounded-xl sm:rounded-2xl shadow-xl p-4 sm:p-6 text-white">
|
| 406 |
+
<h3 className="text-base sm:text-lg md:text-xl font-bold mb-3 sm:mb-4 flex items-center">
|
| 407 |
+
<svg className="w-4 h-4 sm:w-5 sm:h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 408 |
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
| 409 |
</svg>
|
| 410 |
Continue Learning
|
| 411 |
</h3>
|
| 412 |
+
<div className="bg-white/20 backdrop-blur-sm rounded-lg sm:rounded-xl p-3 sm:p-4 mb-3 sm:mb-4">
|
| 413 |
+
<p className="font-semibold text-sm sm:text-base">Control Flow Module</p>
|
| 414 |
+
<p className="text-xs sm:text-sm opacity-90 mt-1">75% complete • 65% mastery</p>
|
| 415 |
+
<div className="w-full bg-white/30 rounded-full h-1.5 sm:h-2 mt-2 sm:mt-3">
|
| 416 |
+
<div className="bg-white h-1.5 sm:h-2 rounded-full" style={{ width: '75%' }}></div>
|
| 417 |
</div>
|
| 418 |
</div>
|
| 419 |
<Link
|
| 420 |
href="/student/learn?module=mod2"
|
| 421 |
+
className="w-full bg-white text-blue-600 hover:bg-gray-100 font-semibold py-2.5 sm:py-3 px-4 rounded-lg text-sm sm:text-base transition-all duration-300 transform hover:scale-105 block text-center"
|
| 422 |
prefetch={false}
|
| 423 |
>
|
| 424 |
Continue Learning
|
|
|
|
| 426 |
</div>
|
| 427 |
|
| 428 |
{/* Quick Actions */}
|
| 429 |
+
<div className="bg-white/70 backdrop-blur-sm rounded-xl sm:rounded-2xl shadow-xl border border-white/20 p-4 sm:p-6">
|
| 430 |
+
<h3 className="text-base sm:text-lg md:text-xl font-bold text-gray-800 mb-3 sm:mb-4 flex items-center">
|
| 431 |
+
<svg className="w-4 h-4 sm:w-5 sm:h-5 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 432 |
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
| 433 |
</svg>
|
| 434 |
Quick Actions
|
| 435 |
</h3>
|
| 436 |
+
<div className="space-y-2 sm:space-y-3">
|
| 437 |
<Link
|
| 438 |
href="/student/quiz"
|
| 439 |
+
className="flex items-center p-3 sm:p-4 bg-gradient-to-r from-gray-50 to-gray-100 hover:from-blue-50 hover:to-purple-50 rounded-lg sm:rounded-xl transition-all duration-300 transform hover:scale-[1.02] border border-gray-100 hover:border-blue-200"
|
| 440 |
prefetch={false}
|
| 441 |
>
|
| 442 |
+
<div className="w-8 h-8 sm:w-10 sm:h-10 bg-gradient-to-r from-blue-100 to-blue-200 rounded-lg flex items-center justify-center mr-2 sm:mr-3 flex-shrink-0">
|
| 443 |
+
<svg className="w-4 h-4 sm:w-5 sm:h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 444 |
<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" />
|
| 445 |
</svg>
|
| 446 |
</div>
|
| 447 |
+
<span className="font-medium text-sm sm:text-base text-gray-800">Take a Quiz</span>
|
| 448 |
</Link>
|
| 449 |
|
| 450 |
<Link
|
| 451 |
href="/student/progress"
|
| 452 |
+
className="flex items-center p-3 sm:p-4 bg-gradient-to-r from-gray-50 to-gray-100 hover:from-green-50 hover:to-teal-50 rounded-lg sm:rounded-xl transition-all duration-300 transform hover:scale-[1.02] border border-gray-100 hover:border-green-200"
|
| 453 |
prefetch={false}
|
| 454 |
>
|
| 455 |
+
<div className="w-8 h-8 sm:w-10 sm:h-10 bg-gradient-to-r from-green-100 to-green-200 rounded-lg flex items-center justify-center mr-2 sm:mr-3 flex-shrink-0">
|
| 456 |
+
<svg className="w-4 h-4 sm:w-5 sm:h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 457 |
<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" />
|
| 458 |
</svg>
|
| 459 |
</div>
|
| 460 |
+
<span className="font-medium text-sm sm:text-base text-gray-800">View Progress</span>
|
| 461 |
</Link>
|
| 462 |
|
| 463 |
<Link
|
| 464 |
href="/student/chat"
|
| 465 |
+
className="flex items-center p-3 sm:p-4 bg-gradient-to-r from-gray-50 to-gray-100 hover:from-purple-50 hover:to-pink-50 rounded-lg sm:rounded-xl transition-all duration-300 transform hover:scale-[1.02] border border-gray-100 hover:border-purple-200"
|
| 466 |
prefetch={false}
|
| 467 |
>
|
| 468 |
+
<div className="w-8 h-8 sm:w-10 sm:h-10 bg-gradient-to-r from-purple-100 to-purple-200 rounded-lg flex items-center justify-center mr-2 sm:mr-3 flex-shrink-0">
|
| 469 |
+
<svg className="w-4 h-4 sm:w-5 sm:h-5 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 470 |
<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" />
|
| 471 |
</svg>
|
| 472 |
</div>
|
| 473 |
+
<span className="font-medium text-sm sm:text-base text-gray-800">Chat with Tutor</span>
|
| 474 |
</Link>
|
| 475 |
</div>
|
| 476 |
</div>
|
app/student/learn/page.tsx
CHANGED
|
@@ -3,6 +3,7 @@
|
|
| 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 |
|
|
@@ -27,7 +28,15 @@ export default function LearnPage() {
|
|
| 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);
|
|
@@ -52,11 +61,9 @@ export default function LearnPage() {
|
|
| 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 |
}
|
|
@@ -65,7 +72,6 @@ export default function LearnPage() {
|
|
| 65 |
};
|
| 66 |
|
| 67 |
const simulateExecution = () => {
|
| 68 |
-
// Simple Python simulation for common operations
|
| 69 |
const lines = code.split('\n');
|
| 70 |
let output = '';
|
| 71 |
|
|
@@ -75,7 +81,6 @@ export default function LearnPage() {
|
|
| 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';
|
|
@@ -110,18 +115,12 @@ for i in range(5):
|
|
| 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')) {
|
|
@@ -130,21 +129,12 @@ Would you like me to explain more or try it in the code editor?`;
|
|
| 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 |
-
|
| 148 |
\`\`\`
|
| 149 |
|
| 150 |
Try writing a function in the code editor!`;
|
|
@@ -156,22 +146,9 @@ Try writing a function in the code editor!`;
|
|
| 156 |
Lists are ordered, mutable collections:
|
| 157 |
|
| 158 |
\`\`\`python
|
| 159 |
-
# Create a list
|
| 160 |
numbers = [1, 2, 3, 4, 5]
|
| 161 |
-
|
| 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!`;
|
|
@@ -180,8 +157,6 @@ Try creating a list in the code editor!`;
|
|
| 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 |
|
|
@@ -191,16 +166,9 @@ 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
|
| 204 |
}
|
| 205 |
|
| 206 |
return `Great question about "${question}"!
|
|
@@ -209,9 +177,8 @@ 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
|
| 215 |
};
|
| 216 |
|
| 217 |
const handleSendMessage = async () => {
|
|
@@ -248,14 +215,12 @@ Try asking about a specific topic, or write some code in the editor and I'll hel
|
|
| 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',
|
|
@@ -272,230 +237,298 @@ Try asking about a specific topic, or write some code in the editor and I'll hel
|
|
| 272 |
|
| 273 |
return (
|
| 274 |
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
| 275 |
-
{/* Animated background
|
| 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
|
| 279 |
</div>
|
| 280 |
|
| 281 |
<div className="relative">
|
| 282 |
-
|
| 283 |
-
|
|
|
|
| 284 |
<div className="flex items-center justify-between">
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 290 |
</div>
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
|
|
|
|
|
|
| 294 |
</div>
|
| 295 |
-
<
|
| 296 |
-
|
|
|
|
|
|
|
|
|
|
| 297 |
</div>
|
| 298 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 300 |
</div>
|
| 301 |
</header>
|
| 302 |
|
| 303 |
-
|
| 304 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 305 |
{/* Chat Interface */}
|
| 306 |
-
<div className=
|
| 307 |
-
<div className="
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
<
|
| 311 |
-
<
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
|
|
|
|
|
|
|
|
|
| 316 |
</div>
|
| 317 |
-
</div>
|
| 318 |
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
<
|
| 333 |
-
<
|
| 334 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
|
|
|
| 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 |
-
|
| 368 |
</>
|
| 369 |
) : (
|
| 370 |
<>
|
| 371 |
-
<svg className="w-4 h-4 mr-1" fill="
|
| 372 |
-
<path
|
| 373 |
</svg>
|
| 374 |
-
|
| 375 |
</>
|
| 376 |
)}
|
| 377 |
-
</
|
| 378 |
-
</
|
| 379 |
</div>
|
| 380 |
-
</div>
|
| 381 |
-
</div>
|
| 382 |
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 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 |
-
|
| 419 |
-
<
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
|
| 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/
|
| 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
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
<
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 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 |
-
|
| 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>
|
|
@@ -505,17 +538,22 @@ Try asking about a specific topic, or write some code in the editor and I'll hel
|
|
| 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 |
-
|
| 516 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 517 |
}
|
| 518 |
`}</style>
|
| 519 |
</div>
|
| 520 |
);
|
| 521 |
-
}
|
|
|
|
| 3 |
import { useState, useRef, useEffect } from 'react';
|
| 4 |
import { useRouter } from 'next/navigation';
|
| 5 |
import dynamic from 'next/dynamic';
|
| 6 |
+
import Link from 'next/link';
|
| 7 |
|
| 8 |
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
|
| 9 |
|
|
|
|
| 28 |
{ role: 'assistant', content: 'Hello! I\'m your Python tutor. What would you like to learn today?' }
|
| 29 |
]);
|
| 30 |
const [inputMessage, setInputMessage] = useState<string>('');
|
| 31 |
+
const [activeTab, setActiveTab] = useState<'chat' | 'code'>('chat');
|
| 32 |
+
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
| 33 |
const editorRef = useRef<any>(null);
|
| 34 |
+
const chatEndRef = useRef<HTMLDivElement>(null);
|
| 35 |
+
|
| 36 |
+
// Auto scroll to bottom of chat
|
| 37 |
+
useEffect(() => {
|
| 38 |
+
chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
| 39 |
+
}, [messages]);
|
| 40 |
|
| 41 |
const handleRunCode = async () => {
|
| 42 |
setIsLoading(true);
|
|
|
|
| 61 |
setOutput(data.output || 'Code executed successfully (no output)');
|
| 62 |
}
|
| 63 |
} else {
|
|
|
|
| 64 |
simulateExecution();
|
| 65 |
}
|
| 66 |
} catch (error) {
|
|
|
|
| 67 |
console.log('Using simulated execution (API not available)');
|
| 68 |
simulateExecution();
|
| 69 |
}
|
|
|
|
| 72 |
};
|
| 73 |
|
| 74 |
const simulateExecution = () => {
|
|
|
|
| 75 |
const lines = code.split('\n');
|
| 76 |
let output = '';
|
| 77 |
|
|
|
|
| 81 |
const match = trimmed.match(/print\((.*)\)/);
|
| 82 |
if (match) {
|
| 83 |
let content = match[1];
|
|
|
|
| 84 |
if ((content.startsWith('"') && content.endsWith('"')) ||
|
| 85 |
(content.startsWith("'") && content.endsWith("'"))) {
|
| 86 |
output += content.slice(1, -1) + '\n';
|
|
|
|
| 115 |
fruits = ['apple', 'banana', 'cherry']
|
| 116 |
for fruit in fruits:
|
| 117 |
print(fruit)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
\`\`\`
|
| 119 |
|
| 120 |
**Key points:**
|
| 121 |
- \`range(n)\` generates numbers 0 to n-1
|
| 122 |
- Use \`break\` to exit early
|
| 123 |
+
- Use \`continue\` to skip to next iteration`;
|
|
|
|
|
|
|
| 124 |
}
|
| 125 |
|
| 126 |
if (lowerQuestion.includes('function') || lowerQuestion.includes('def')) {
|
|
|
|
| 129 |
Functions are reusable blocks of code:
|
| 130 |
|
| 131 |
\`\`\`python
|
|
|
|
| 132 |
def greet(name):
|
| 133 |
return f"Hello, {name}!"
|
| 134 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
# Calling functions
|
| 136 |
message = greet("Alice")
|
| 137 |
+
print(message) # Hello, Alice!
|
| 138 |
\`\`\`
|
| 139 |
|
| 140 |
Try writing a function in the code editor!`;
|
|
|
|
| 146 |
Lists are ordered, mutable collections:
|
| 147 |
|
| 148 |
\`\`\`python
|
|
|
|
| 149 |
numbers = [1, 2, 3, 4, 5]
|
| 150 |
+
numbers.append(6)
|
| 151 |
+
print(numbers) # [1, 2, 3, 4, 5, 6]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
\`\`\`
|
| 153 |
|
| 154 |
Try creating a list in the code editor!`;
|
|
|
|
| 157 |
if (lowerQuestion.includes('if') || lowerQuestion.includes('condition')) {
|
| 158 |
return `**If Statements in Python**
|
| 159 |
|
|
|
|
|
|
|
| 160 |
\`\`\`python
|
| 161 |
age = 18
|
| 162 |
|
|
|
|
| 166 |
print("Teenager")
|
| 167 |
else:
|
| 168 |
print("Adult")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
\`\`\`
|
| 170 |
|
| 171 |
+
Try writing an if statement!`;
|
| 172 |
}
|
| 173 |
|
| 174 |
return `Great question about "${question}"!
|
|
|
|
| 177 |
- **Python syntax** - variables, operators, data types
|
| 178 |
- **Control flow** - if/else, loops, functions
|
| 179 |
- **Data structures** - lists, dictionaries, tuples
|
|
|
|
| 180 |
|
| 181 |
+
Try asking about a specific topic!`;
|
| 182 |
};
|
| 183 |
|
| 184 |
const handleSendMessage = async () => {
|
|
|
|
| 215 |
content: data.response
|
| 216 |
}]);
|
| 217 |
} else {
|
|
|
|
| 218 |
setMessages(prev => [...prev, {
|
| 219 |
role: 'assistant',
|
| 220 |
content: generateLocalResponse(currentInput)
|
| 221 |
}]);
|
| 222 |
}
|
| 223 |
} catch (error) {
|
|
|
|
| 224 |
console.log('Using local response (API not available)');
|
| 225 |
setMessages(prev => [...prev, {
|
| 226 |
role: 'assistant',
|
|
|
|
| 237 |
|
| 238 |
return (
|
| 239 |
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
| 240 |
+
{/* Animated background - hidden on mobile for performance */}
|
| 241 |
+
<div className="hidden sm:block absolute inset-0 overflow-hidden pointer-events-none">
|
| 242 |
<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>
|
| 243 |
+
<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"></div>
|
| 244 |
</div>
|
| 245 |
|
| 246 |
<div className="relative">
|
| 247 |
+
{/* Responsive Header */}
|
| 248 |
+
<header className="bg-white/90 backdrop-blur-md shadow-lg border-b border-white/20 sticky top-0 z-20">
|
| 249 |
+
<div className="max-w-7xl mx-auto px-3 py-3 sm:px-4 sm:py-4 lg:px-8">
|
| 250 |
<div className="flex items-center justify-between">
|
| 251 |
+
{/* Logo & Title */}
|
| 252 |
+
<div className="flex items-center min-w-0">
|
| 253 |
+
<Link href="/student/dashboard" className="flex items-center">
|
| 254 |
+
<div className="w-8 h-8 sm:w-10 sm:h-10 bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg flex items-center justify-center flex-shrink-0">
|
| 255 |
+
<span className="text-white font-bold text-sm sm:text-lg">PY</span>
|
| 256 |
+
</div>
|
| 257 |
+
<div className="ml-2 sm:ml-3 hidden xs:block">
|
| 258 |
+
<h1 className="text-lg sm:text-xl lg:text-2xl font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent truncate">
|
| 259 |
+
Python Studio
|
| 260 |
+
</h1>
|
| 261 |
+
<p className="text-xs sm:text-sm text-gray-500 hidden sm:block">AI-powered tutoring</p>
|
| 262 |
+
</div>
|
| 263 |
+
</Link>
|
| 264 |
</div>
|
| 265 |
+
|
| 266 |
+
{/* Desktop Navigation */}
|
| 267 |
+
<div className="hidden md:flex items-center space-x-3">
|
| 268 |
+
<div className="bg-gradient-to-r from-blue-100 to-purple-100 rounded-lg px-3 py-1.5">
|
| 269 |
+
<span className="text-xs sm:text-sm font-medium text-gray-700">Module: Basics</span>
|
| 270 |
</div>
|
| 271 |
+
<Link href="/student/dashboard" className="text-gray-600 hover:text-blue-600 px-3 py-2 text-sm">
|
| 272 |
+
Dashboard
|
| 273 |
+
</Link>
|
| 274 |
+
<div className="w-8 h-8 sm:w-10 sm:h-10 bg-gradient-to-r from-green-400 to-blue-500 rounded-full flex items-center justify-center">
|
| 275 |
+
<span className="text-white font-bold text-xs sm:text-sm">A</span>
|
| 276 |
</div>
|
| 277 |
</div>
|
| 278 |
+
|
| 279 |
+
{/* Mobile Menu Button */}
|
| 280 |
+
<button
|
| 281 |
+
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
| 282 |
+
className="md:hidden p-2 rounded-lg hover:bg-gray-100"
|
| 283 |
+
>
|
| 284 |
+
<svg className="w-6 h-6 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 285 |
+
{mobileMenuOpen ? (
|
| 286 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
| 287 |
+
) : (
|
| 288 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
| 289 |
+
)}
|
| 290 |
+
</svg>
|
| 291 |
+
</button>
|
| 292 |
</div>
|
| 293 |
+
|
| 294 |
+
{/* Mobile Menu */}
|
| 295 |
+
{mobileMenuOpen && (
|
| 296 |
+
<div className="md:hidden mt-3 pt-3 border-t border-gray-200">
|
| 297 |
+
<div className="flex flex-col space-y-2">
|
| 298 |
+
<Link href="/student/dashboard" className="text-gray-600 hover:text-blue-600 px-3 py-2 rounded-lg hover:bg-gray-100">
|
| 299 |
+
Dashboard
|
| 300 |
+
</Link>
|
| 301 |
+
<Link href="/resources" className="text-gray-600 hover:text-blue-600 px-3 py-2 rounded-lg hover:bg-gray-100">
|
| 302 |
+
Resources
|
| 303 |
+
</Link>
|
| 304 |
+
<div className="px-3 py-2 bg-blue-50 rounded-lg">
|
| 305 |
+
<span className="text-sm text-blue-700">Module: Python Basics</span>
|
| 306 |
+
</div>
|
| 307 |
+
</div>
|
| 308 |
+
</div>
|
| 309 |
+
)}
|
| 310 |
</div>
|
| 311 |
</header>
|
| 312 |
|
| 313 |
+
{/* Mobile Tab Switcher */}
|
| 314 |
+
<div className="lg:hidden sticky top-[57px] sm:top-[65px] z-10 bg-white/90 backdrop-blur-md border-b border-gray-200 px-3 py-2">
|
| 315 |
+
<div className="flex space-x-2">
|
| 316 |
+
<button
|
| 317 |
+
onClick={() => setActiveTab('chat')}
|
| 318 |
+
className={`flex-1 py-2.5 px-4 rounded-xl font-medium text-sm transition-all ${
|
| 319 |
+
activeTab === 'chat'
|
| 320 |
+
? 'bg-blue-600 text-white shadow-md'
|
| 321 |
+
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
|
| 322 |
+
}`}
|
| 323 |
+
>
|
| 324 |
+
<span className="flex items-center justify-center">
|
| 325 |
+
<svg className="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 326 |
+
<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" />
|
| 327 |
+
</svg>
|
| 328 |
+
AI Tutor
|
| 329 |
+
</span>
|
| 330 |
+
</button>
|
| 331 |
+
<button
|
| 332 |
+
onClick={() => setActiveTab('code')}
|
| 333 |
+
className={`flex-1 py-2.5 px-4 rounded-xl font-medium text-sm transition-all ${
|
| 334 |
+
activeTab === 'code'
|
| 335 |
+
? 'bg-green-600 text-white shadow-md'
|
| 336 |
+
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
|
| 337 |
+
}`}
|
| 338 |
+
>
|
| 339 |
+
<span className="flex items-center justify-center">
|
| 340 |
+
<svg className="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 341 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
|
| 342 |
+
</svg>
|
| 343 |
+
Code Editor
|
| 344 |
+
</span>
|
| 345 |
+
</button>
|
| 346 |
+
</div>
|
| 347 |
+
</div>
|
| 348 |
+
|
| 349 |
+
<main className="max-w-7xl mx-auto px-3 py-4 sm:px-4 sm:py-6 lg:px-8 lg:py-8">
|
| 350 |
+
{/* Main Content Grid - Side by side on desktop, tabs on mobile */}
|
| 351 |
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 sm:gap-6 lg:gap-8 mb-6 sm:mb-8">
|
| 352 |
+
|
| 353 |
{/* Chat Interface */}
|
| 354 |
+
<div className={`${activeTab === 'chat' ? 'block' : 'hidden'} lg:block`}>
|
| 355 |
+
<div className="bg-white/80 backdrop-blur-sm rounded-xl sm:rounded-2xl shadow-xl border border-white/20 overflow-hidden h-[calc(100vh-200px)] sm:h-[calc(100vh-220px)] lg:h-auto flex flex-col">
|
| 356 |
+
{/* Chat Header */}
|
| 357 |
+
<div className="p-3 sm:p-4 lg:p-6 border-b border-gray-100 flex-shrink-0">
|
| 358 |
+
<div className="flex items-center">
|
| 359 |
+
<div className="w-2.5 h-2.5 sm:w-3 sm:h-3 bg-green-500 rounded-full mr-2 animate-pulse"></div>
|
| 360 |
+
<h2 className="text-base sm:text-lg lg:text-xl font-bold text-gray-800 flex items-center">
|
| 361 |
+
<svg className="w-4 h-4 sm:w-5 sm:h-5 mr-1.5 sm:mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 362 |
+
<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" />
|
| 363 |
+
</svg>
|
| 364 |
+
AI Tutor Chat
|
| 365 |
+
</h2>
|
| 366 |
+
</div>
|
| 367 |
</div>
|
|
|
|
| 368 |
|
| 369 |
+
{/* Chat Messages */}
|
| 370 |
+
<div className="flex-1 overflow-y-auto p-3 sm:p-4 lg:p-6 bg-gradient-to-b from-gray-50 to-gray-100 lg:h-80">
|
| 371 |
+
{messages.map((msg, index) => (
|
| 372 |
+
<div
|
| 373 |
+
key={index}
|
| 374 |
+
className={`mb-3 sm:mb-4 p-3 sm:p-4 rounded-xl sm:rounded-2xl max-w-[90%] sm:max-w-[85%] ${
|
| 375 |
+
msg.role === 'user'
|
| 376 |
+
? 'ml-auto bg-gradient-to-r from-blue-500 to-purple-600 text-white shadow-lg'
|
| 377 |
+
: 'mr-auto bg-white text-gray-800 shadow-md border border-gray-100'
|
| 378 |
+
}`}
|
| 379 |
+
>
|
| 380 |
+
<div className="flex items-start">
|
| 381 |
+
{msg.role === 'assistant' && (
|
| 382 |
+
<div className="w-5 h-5 sm:w-6 sm:h-6 bg-gradient-to-r from-green-400 to-blue-500 rounded-full flex items-center justify-center mr-2 flex-shrink-0">
|
| 383 |
+
<svg className="w-2.5 h-2.5 sm:w-3 sm:h-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 384 |
+
<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" />
|
| 385 |
+
</svg>
|
| 386 |
+
</div>
|
| 387 |
+
)}
|
| 388 |
+
<div className={`text-sm sm:text-base whitespace-pre-wrap ${msg.role === 'user' ? 'text-white' : 'text-gray-800'}`}>
|
| 389 |
+
{msg.content}
|
| 390 |
</div>
|
|
|
|
|
|
|
|
|
|
| 391 |
</div>
|
| 392 |
</div>
|
| 393 |
+
))}
|
| 394 |
+
<div ref={chatEndRef} />
|
| 395 |
+
</div>
|
| 396 |
+
|
| 397 |
+
{/* Chat Input */}
|
| 398 |
+
<div className="p-3 sm:p-4 border-t border-gray-100 bg-white flex-shrink-0">
|
| 399 |
+
<div className="flex space-x-2">
|
| 400 |
+
<input
|
| 401 |
+
type="text"
|
| 402 |
+
value={inputMessage}
|
| 403 |
+
onChange={(e) => setInputMessage(e.target.value)}
|
| 404 |
+
onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()}
|
| 405 |
+
placeholder="Ask a Python question..."
|
| 406 |
+
className="flex-1 bg-gray-50 border border-gray-200 rounded-xl px-3 sm:px-4 py-2.5 sm:py-3 text-sm sm:text-base focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
|
| 407 |
+
/>
|
| 408 |
+
<button
|
| 409 |
+
onClick={handleSendMessage}
|
| 410 |
+
disabled={isSending || !inputMessage.trim()}
|
| 411 |
+
className="bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white px-4 sm:px-6 py-2.5 sm:py-3 rounded-xl disabled:opacity-50 disabled:cursor-not-allowed transition-all font-medium text-sm sm:text-base flex items-center"
|
| 412 |
+
>
|
| 413 |
+
{isSending ? (
|
| 414 |
+
<svg className="animate-spin w-4 h-4 sm:w-5 sm:h-5" fill="none" viewBox="0 0 24 24">
|
| 415 |
+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
| 416 |
+
<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>
|
| 417 |
+
</svg>
|
| 418 |
+
) : (
|
| 419 |
+
<svg className="w-4 h-4 sm:w-5 sm:h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 420 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
|
| 421 |
+
</svg>
|
| 422 |
+
)}
|
| 423 |
+
<span className="ml-1.5 hidden sm:inline">{isSending ? 'Sending...' : 'Send'}</span>
|
| 424 |
+
</button>
|
| 425 |
</div>
|
| 426 |
+
</div>
|
| 427 |
</div>
|
| 428 |
+
</div>
|
| 429 |
|
| 430 |
+
{/* Code Editor */}
|
| 431 |
+
<div className={`${activeTab === 'code' ? 'block' : 'hidden'} lg:block`}>
|
| 432 |
+
<div className="bg-white/80 backdrop-blur-sm rounded-xl sm:rounded-2xl shadow-xl border border-white/20 overflow-hidden h-[calc(100vh-200px)] sm:h-[calc(100vh-220px)] lg:h-auto flex flex-col">
|
| 433 |
+
{/* Editor Header */}
|
| 434 |
+
<div className="p-3 sm:p-4 lg:p-5 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-gray-100 flex-shrink-0">
|
| 435 |
+
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-2 sm:gap-0">
|
| 436 |
+
<h2 className="text-base sm:text-lg lg:text-xl font-bold text-gray-800 flex items-center">
|
| 437 |
+
<svg className="w-4 h-4 sm:w-5 sm:h-5 mr-1.5 sm:mr-2 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 438 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
|
| 439 |
+
</svg>
|
| 440 |
+
Python Editor
|
| 441 |
+
</h2>
|
| 442 |
+
<button
|
| 443 |
+
onClick={handleRunCode}
|
| 444 |
+
disabled={isLoading}
|
| 445 |
+
className="bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 text-white px-4 sm:px-6 py-2 sm:py-2.5 rounded-lg sm:rounded-xl disabled:opacity-50 transition-all font-medium text-sm sm:text-base flex items-center justify-center"
|
| 446 |
+
>
|
| 447 |
+
{isLoading ? (
|
| 448 |
<>
|
| 449 |
+
<svg className="animate-spin w-4 h-4 mr-1.5" fill="none" viewBox="0 0 24 24">
|
| 450 |
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
| 451 |
<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>
|
| 452 |
</svg>
|
| 453 |
+
Running...
|
| 454 |
</>
|
| 455 |
) : (
|
| 456 |
<>
|
| 457 |
+
<svg className="w-4 h-4 mr-1.5" fill="currentColor" viewBox="0 0 24 24">
|
| 458 |
+
<path d="M8 5v14l11-7z" />
|
| 459 |
</svg>
|
| 460 |
+
Run Code
|
| 461 |
</>
|
| 462 |
)}
|
| 463 |
+
</button>
|
| 464 |
+
</div>
|
| 465 |
</div>
|
|
|
|
|
|
|
| 466 |
|
| 467 |
+
{/* Editor */}
|
| 468 |
+
<div className="flex-1 min-h-[200px] sm:min-h-[250px] lg:h-72">
|
| 469 |
+
<MonacoEditor
|
| 470 |
+
height="100%"
|
| 471 |
+
language="python"
|
| 472 |
+
value={code}
|
| 473 |
+
onChange={(value) => setCode(value || '')}
|
| 474 |
+
onMount={handleEditorDidMount}
|
| 475 |
+
theme="vs-dark"
|
| 476 |
+
options={{
|
| 477 |
+
minimap: { enabled: false },
|
| 478 |
+
fontSize: 13,
|
| 479 |
+
scrollBeyondLastLine: false,
|
| 480 |
+
automaticLayout: true,
|
| 481 |
+
fontFamily: 'Consolas, "Courier New", monospace',
|
| 482 |
+
padding: { top: 10, bottom: 10 },
|
| 483 |
+
lineNumbers: 'on',
|
| 484 |
+
wordWrap: 'on',
|
| 485 |
+
}}
|
| 486 |
+
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 487 |
</div>
|
|
|
|
| 488 |
|
| 489 |
+
{/* Output */}
|
| 490 |
+
<div className="p-3 sm:p-4 border-t border-gray-100 bg-gray-50 flex-shrink-0">
|
| 491 |
+
<h3 className="font-bold text-gray-800 mb-2 flex items-center text-sm sm:text-base">
|
| 492 |
+
<svg className="w-4 h-4 mr-1.5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 493 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
| 494 |
+
</svg>
|
| 495 |
+
Output
|
| 496 |
+
</h3>
|
| 497 |
+
<div className="bg-gray-900 text-green-400 p-3 sm:p-4 rounded-lg font-mono text-xs sm:text-sm h-20 sm:h-24 lg:h-28 overflow-auto">
|
| 498 |
+
{output || 'Click "Run Code" to execute your Python code...'}
|
| 499 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 500 |
</div>
|
| 501 |
</div>
|
| 502 |
</div>
|
| 503 |
</div>
|
| 504 |
|
| 505 |
+
{/* Learning Resources - Responsive Grid */}
|
| 506 |
+
<div className="bg-white/80 backdrop-blur-sm rounded-xl sm:rounded-2xl shadow-xl border border-white/20 p-4 sm:p-6">
|
| 507 |
+
<h2 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-800 mb-4 sm:mb-6 flex items-center">
|
| 508 |
+
<svg className="w-5 h-5 sm:w-6 sm:h-6 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 509 |
<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" />
|
| 510 |
</svg>
|
| 511 |
Learning Resources
|
| 512 |
</h2>
|
| 513 |
|
| 514 |
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6">
|
| 515 |
+
{[
|
| 516 |
+
{ title: 'Python Basics', desc: 'Variables, data types, operators', color: 'blue', icon: '🐍' },
|
| 517 |
+
{ title: 'Control Flow', desc: 'If statements, loops, functions', color: 'purple', icon: '🔄' },
|
| 518 |
+
{ title: 'Practice', desc: 'Coding challenges and projects', color: 'green', icon: '💪' },
|
| 519 |
+
].map((item, index) => (
|
| 520 |
+
<div
|
| 521 |
+
key={index}
|
| 522 |
+
className={`bg-gradient-to-br from-${item.color}-50 to-${item.color}-100 rounded-xl p-4 sm:p-6 border border-white/50 hover:shadow-lg transition-all duration-300 cursor-pointer active:scale-[0.98]`}
|
| 523 |
+
>
|
| 524 |
+
<div className="text-3xl sm:text-4xl mb-3">{item.icon}</div>
|
| 525 |
+
<h3 className="font-bold text-base sm:text-lg text-gray-800 mb-1 sm:mb-2">{item.title}</h3>
|
| 526 |
+
<p className="text-gray-600 text-sm mb-3 sm:mb-4">{item.desc}</p>
|
| 527 |
+
<button className={`w-full sm:w-auto bg-${item.color}-500 hover:bg-${item.color}-600 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors`}>
|
| 528 |
+
Start Learning
|
| 529 |
+
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 530 |
</div>
|
| 531 |
+
))}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 532 |
</div>
|
| 533 |
</div>
|
| 534 |
</main>
|
|
|
|
| 538 |
.animation-delay-2000 {
|
| 539 |
animation-delay: 2s;
|
| 540 |
}
|
|
|
|
|
|
|
|
|
|
| 541 |
@keyframes pulse {
|
| 542 |
0%, 100% { opacity: 0.2; }
|
| 543 |
50% { opacity: 0.3; }
|
| 544 |
}
|
| 545 |
+
/* Custom scrollbar for chat */
|
| 546 |
+
.overflow-y-auto::-webkit-scrollbar {
|
| 547 |
+
width: 6px;
|
| 548 |
+
}
|
| 549 |
+
.overflow-y-auto::-webkit-scrollbar-track {
|
| 550 |
+
background: transparent;
|
| 551 |
+
}
|
| 552 |
+
.overflow-y-auto::-webkit-scrollbar-thumb {
|
| 553 |
+
background: #cbd5e1;
|
| 554 |
+
border-radius: 3px;
|
| 555 |
}
|
| 556 |
`}</style>
|
| 557 |
</div>
|
| 558 |
);
|
| 559 |
+
}
|