diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 03b0f28..e2d3b5e 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -3,7 +3,8 @@ "allow": [ "Bash(npm run dev:*)", "Bash(npm run build:*)", - "Bash(timeout 30 npm run dev:*)" + "Bash(timeout 30 npm run dev:*)", + "Bash(cat:*)" ], "deny": [], "ask": [] diff --git a/DATABASE_SETUP_REQUIRED.md b/DATABASE_SETUP_REQUIRED.md new file mode 100644 index 0000000..d885997 --- /dev/null +++ b/DATABASE_SETUP_REQUIRED.md @@ -0,0 +1,119 @@ +# 🚨 DATABASE SETUP REQUIRED + +## Current Problem + +Your application shows these errors: +- ❌ "Error fetching submissions" +- ❌ "Error fetching task resources" +- ❌ File uploads are broken + +**Root cause:** Database tables `submissions` and `task_resources` don't exist yet. + +--- + +## Solution: Run Migrations in Supabase Dashboard + +### Step-by-Step Instructions: + +1. **Open Supabase Dashboard** + - Go to: https://app.supabase.com + - Select your project + - Click on **SQL Editor** in the left menu + +2. **Run These Migrations in Order:** + + **Migration 003** - Submissions Table + ``` + 📁 Open: supabase/migrations/003_add_submissions.sql + ➡️ Copy all contents → Paste in SQL Editor → Click "Run" + ``` + + **Migration 004** - Enrollment Fields + ``` + 📁 Open: supabase/migrations/004_add_enrollment_fields.sql + ➡️ Copy all contents → Paste in SQL Editor → Click "Run" + ``` + + **Migration 005** - Task Resources Table + ``` + 📁 Open: supabase/migrations/005_add_task_resources.sql + ➡️ Copy all contents → Paste in SQL Editor → Click "Run" + ``` + + **Migration 006** - Storage Setup + Cleanup ⭐ **IMPORTANT** + ``` + 📁 Open: supabase/migrations/006_setup_storage_and_cleanup.sql + ➡️ Copy all contents → Paste in SQL Editor → Click "Run" + ``` + +3. **Verify Everything Worked** + ``` + 📁 Open: supabase/verify_migrations.sql + ➡️ Copy all contents → Paste in SQL Editor → Click "Run" + ✅ You should see all checks showing green checkmarks + ``` + +4. **Refresh Your Application** + - Reload the page in your browser + - Errors should be gone + - File uploads should work + +--- + +## What Gets Fixed: + +✅ Creates `submissions` table for student file uploads +✅ Creates `task_resources` table for teacher resource uploads +✅ Creates storage buckets: `submissions` and `task-resources` +✅ Sets up all security policies automatically +✅ Removes deadline functionality (cleaned up) +✅ Adds submission fields to enrollments +✅ Enables file upload error handling with detailed feedback + +--- + +## After Setup: + +### Test File Uploads: + +**As a Teacher:** +1. Create a new homework/task +2. Upload resource files (PDF, images, etc.) +3. ✅ Should see success message with file name + +**As a Student:** +1. Enroll in a homework +2. Upload submission files +3. ✅ Should see success message with file name + +--- + +## Need Help? + +**If migrations fail:** +- Make sure you're using the project owner account in Supabase +- Try running one migration at a time +- Check the error message in SQL Editor + +**If storage buckets don't create:** +- Go to Storage section in Supabase dashboard +- Manually create two buckets: + - Name: `submissions` (make it public) + - Name: `task-resources` (make it public) +- Then re-run migration 006 + +**Still having issues?** +- Check `supabase/apply-migrations.md` for detailed troubleshooting +- Run `supabase/verify_migrations.sql` to see what's missing + +--- + +## Files Created: + +- ✅ `supabase/migrations/006_setup_storage_and_cleanup.sql` - New migration +- ✅ `supabase/verify_migrations.sql` - Verification script +- ✅ `supabase/apply-migrations.md` - Updated with latest instructions +- ✅ File upload error handling improved in: + - `frontend/app/dashboard/teacher/create-homework/page.tsx` + - `frontend/app/dashboard/teacher/homework/[id]/page.tsx` + - `frontend/app/dashboard/student/homework/[id]/page.tsx` diff --git a/frontend/app/dashboard/page.tsx b/frontend/app/dashboard/page.tsx index 0922fe6..5f18248 100644 --- a/frontend/app/dashboard/page.tsx +++ b/frontend/app/dashboard/page.tsx @@ -65,8 +65,18 @@ export default function DashboardPage() { if (loading) { return ( -
- +
+
+
+
+
+
+
+ +
+
+

Loading your dashboard...

+
); } @@ -77,8 +87,18 @@ export default function DashboardPage() { // Show loading while redirecting return ( -
- +
+
+
+
+
+
+
+ +
+
+

Redirecting...

+
); } diff --git a/frontend/app/dashboard/student/homework/[id]/page.tsx b/frontend/app/dashboard/student/homework/[id]/page.tsx index 26d50f4..a685825 100644 --- a/frontend/app/dashboard/student/homework/[id]/page.tsx +++ b/frontend/app/dashboard/student/homework/[id]/page.tsx @@ -19,6 +19,8 @@ import { Upload, FileText, CheckCircle, MessageCircle, Loader2, ArrowLeft, Downl import { Input } from '@/components/ui/input'; import Link from 'next/link'; import { createSupabaseBrowserClient } from '@/lib/supabase/client'; +import { useToast } from '@/components/ui/toast'; +import { LoadingPage } from '@/components/ui/loading-spinner'; const supabase = createSupabaseBrowserClient(); @@ -26,6 +28,7 @@ export default function StudentHomeworkPage() { const { address, isConnected } = useAccount(); const router = useRouter(); const params = useParams(); + const toast = useToast(); const homeworkId = params.id as string; const [profile, setProfile] = useState(null); @@ -74,7 +77,7 @@ export default function StudentHomeworkPage() { }); if (enrollmentsData.length === 0) { - alert('You are not enrolled in this task'); + toast.warning('Not enrolled', 'You are not enrolled in this task'); router.push('/dashboard/student'); return; } @@ -105,7 +108,7 @@ export default function StudentHomeworkPage() { setTaskResources(resourcesData); } catch (error: any) { console.error('Error loading data:', error); - alert('Error loading task details'); + toast.error('Loading failed', 'Error loading task details'); router.push('/dashboard/student'); } finally { setLoading(false); @@ -113,7 +116,7 @@ export default function StudentHomeworkPage() { } loadData(); - }, [address, isConnected, router, homeworkId]); + }, [address, isConnected, router, homeworkId, toast]); async function handleSubmitSolution() { if (!enrollment || !submissionText.trim()) return; @@ -139,10 +142,10 @@ export default function StudentHomeworkPage() { }); setEnrollment(enrollmentsData[0]); - alert('Solution submitted successfully! ✅ Your teacher will review it soon.'); + toast.success('Solution submitted!', 'Your teacher will review it soon.'); } catch (error: any) { console.error('Error submitting solution:', error); - alert('Error submitting solution. Please try again.'); + toast.error('Submission failed', 'Error submitting solution. Please try again.'); } finally { setSubmitting(false); } @@ -155,14 +158,28 @@ export default function StudentHomeworkPage() { try { // Upload file to Supabase Storage const fileExt = uploadedFile.name.split('.').pop(); - const fileName = `${profile.id}/${enrollment.id}/${Date.now()}.${fileExt}`; + const fileName = `${profile.id}/${enrollment.id}/${Date.now()}_${Math.random().toString(36).substring(7)}.${fileExt}`; const { data: uploadData, error: uploadError } = await supabase .storage .from('submissions') .upload(fileName, uploadedFile); - if (uploadError) throw uploadError; + if (uploadError) { + console.error('Storage upload error:', uploadError); + let errorMessage = 'Failed to upload file to storage.'; + + if (uploadError.message.includes('row-level security')) { + errorMessage = 'Storage access denied. Please check your permissions.'; + } else if (uploadError.message.includes('size')) { + errorMessage = 'File is too large. Maximum file size is 50MB.'; + } else if (uploadError.message) { + errorMessage = `Upload failed: ${uploadError.message}`; + } + + alert(`❌ ${errorMessage}\n\nFile: ${uploadedFile.name}\n\nPlease try again or contact your teacher if the problem persists.`); + return; + } // Get public URL const { data: { publicUrl } } = supabase @@ -171,14 +188,22 @@ export default function StudentHomeworkPage() { .getPublicUrl(fileName); // Create submission record - await createSubmission({ - enrollment_id: enrollment.id, - student_id: profile.id, - homework_id: homeworkId, - file_url: publicUrl, - file_name: uploadedFile.name, - file_type: uploadedFile.type, - }); + try { + await createSubmission({ + enrollment_id: enrollment.id, + student_id: profile.id, + homework_id: homeworkId, + file_url: publicUrl, + file_name: uploadedFile.name, + file_type: uploadedFile.type, + }); + } catch (dbError: any) { + console.error('Database error:', dbError); + // If DB insert fails, try to clean up the uploaded file + await supabase.storage.from('submissions').remove([fileName]); + alert(`❌ Failed to save submission to database.\n\nFile: ${uploadedFile.name}\nError: ${dbError.message || 'Unknown error'}\n\nPlease try again or contact your teacher.`); + return; + } // Reload submissions const submissionsData = await getSubmissions({ @@ -187,10 +212,33 @@ export default function StudentHomeworkPage() { setSubmissions(submissionsData); setUploadedFile(null); - alert('File uploaded successfully! ✅'); +<<<<<<< HEAD +<<<<<<< HEAD + toast.success('File uploaded!', 'Your file has been uploaded successfully.'); + } catch (error: any) { + console.error('Error uploading file:', error); + toast.error('Upload failed', 'Error uploading file. Please try again.'); +======= +======= +>>>>>>> c89133b (css & animations) + // Reset file input + const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement; + if (fileInput) fileInput.value = ''; + + alert(`✅ File uploaded successfully!\n\n${uploadedFile.name}\n\nYour teacher will be able to see this file when reviewing your submission.`); + } catch (error: any) { + console.error('Unexpected error uploading file:', error); + alert(`❌ Unexpected error uploading file.\n\nFile: ${uploadedFile.name}\nPlease try again or contact your teacher if the problem persists.`); +<<<<<<< HEAD +>>>>>>> 71a7763 (versiune care merge) +======= +======= + toast.success('File uploaded!', 'Your file has been uploaded successfully.'); } catch (error: any) { console.error('Error uploading file:', error); - alert('Error uploading file. Please try again.'); + toast.error('Upload failed', 'Error uploading file. Please try again.'); +>>>>>>> 8ec33f1 (css & animations) +>>>>>>> c89133b (css & animations) } finally { setUploading(false); } diff --git a/frontend/app/dashboard/student/page.tsx b/frontend/app/dashboard/student/page.tsx index 824e706..5c879f5 100644 --- a/frontend/app/dashboard/student/page.tsx +++ b/frontend/app/dashboard/student/page.tsx @@ -24,15 +24,20 @@ import { CheckCircle, Award, Eye, + User, } from 'lucide-react'; import Link from 'next/link'; import { createSupabaseBrowserClient } from '@/lib/supabase/client'; +import { useToast } from '@/components/ui/toast'; +import { LoadingPage } from '@/components/ui/loading-spinner'; +import { cn } from '@/lib/utils'; const supabase = createSupabaseBrowserClient(); export default function StudentDashboard() { const { address, isConnected } = useAccount(); const router = useRouter(); + const toast = useToast(); const [profile, setProfile] = useState(null); const [availableHomeworks, setAvailableHomeworks] = useState([]); const [myEnrollments, setMyEnrollments] = useState([]); @@ -111,13 +116,13 @@ export default function StudentDashboard() { const homeworksData = await getAvailableHomeworks(); setAvailableHomeworks(homeworksData); - alert('Enrolled successfully! ✅'); + toast.success('Enrolled successfully!', 'You can now start working on this task.'); } catch (error: any) { console.error('Error enrolling:', error); if (error.message?.includes('duplicate')) { - alert('You are already enrolled in this task!'); + toast.warning('Already enrolled', 'You are already enrolled in this task!'); } else { - alert('Error enrolling. Please try again.'); + toast.error('Enrollment failed', 'Error enrolling. Please try again.'); } } finally { setEnrolling(null); @@ -125,7 +130,7 @@ export default function StudentDashboard() { } if (loading) { - return
Loading...
; + return ; } if (!profile) { @@ -135,96 +140,107 @@ export default function StudentDashboard() { const unansweredQuestions = myQuestions.filter(q => !q.is_answered).length; return ( -
-
-
-

Student Dashboard

-

- Browse tasks, ask questions, and learn! -

-
+
+ {/* Animated background blobs */} +
+
+
+
+ +
+
+
+

+ Student Dashboard +

+

+ Browse tasks, ask questions, and learn! 📚 +

+
- {/* Mentor Eligibility Banner */} - {canBecomeMentor && ( - - -
-
- -
-

- 🎉 You're eligible to become a Mentor! -

-

- You have {profile?.rating?.toFixed(1) || '0.0'}+ stars and {profile?.completed_count || 0}+ - completed tasks -

+ {/* Mentor Eligibility Banner */} + {canBecomeMentor && ( + + +
+
+
+ +
+
+

+ 🎉 You're eligible to become a Mentor! +

+

+ You have {profile?.rating?.toFixed(1) || '0.0'}+ stars and {profile?.completed_count || 0}+ + completed tasks +

+
+ + +
- - - -
- - - )} + + + )} - {/* Stats Cards */} -
- - - My Enrollments - - - -
{myEnrollments.length}
-

active tasks

-
-
+ {/* Stats Cards */} +
+ + + My Enrollments + + + +
{myEnrollments.length}
+

active tasks

+
+
- - - Completed - - - -
{profile?.completed_count || 0}
-

tasks finished

-
-
+ + + Completed + + + +
{profile?.completed_count || 0}
+

tasks finished

+
+
- - - My Rating - - - -
{profile?.rating?.toFixed(1) || '0.0'}/5
-

- {profile?.total_reviews || 0} {(profile?.total_reviews || 0) === 1 ? 'review' : 'reviews'} -

-
-
+ + + My Rating + + + +
{profile?.rating?.toFixed(1) || '0.0'}/5
+

+ {profile?.total_reviews || 0} {(profile?.total_reviews || 0) === 1 ? 'review' : 'reviews'} +

+
+
- - - Token Balance - - - -
{profile?.token_balance || 0}
-

tokens

-
-
-
+ + + Token Balance + + + +
{profile?.token_balance || 0}
+

tokens

+
+
+
- {/* My Enrollments */} - {myEnrollments.length > 0 && ( -
-

My Enrollments

+ {/* My Enrollments */} + {myEnrollments.length > 0 && ( +
+

My Enrollments

{myEnrollments.map((enrollment) => { return ( @@ -283,9 +299,9 @@ export default function StudentDashboard() {
)} - {/* Available Tasks */} -
-

Available Tasks

+ {/* Available Tasks */} +
+

Available Tasks

{availableHomeworks.length === 0 ? ( @@ -345,7 +361,12 @@ export default function StudentDashboard() {
+
); } diff --git a/frontend/app/dashboard/student/task/[id]/page.tsx b/frontend/app/dashboard/student/task/[id]/page.tsx index 2cee15d..4d4a411 100644 --- a/frontend/app/dashboard/student/task/[id]/page.tsx +++ b/frontend/app/dashboard/student/task/[id]/page.tsx @@ -15,6 +15,8 @@ import type { Homework, TaskResource } from '@/lib/types/database'; import { ArrowLeft, Download, FileText, Users, Loader2 } from 'lucide-react'; import Link from 'next/link'; import { createSupabaseBrowserClient } from '@/lib/supabase/client'; +import { useToast } from '@/components/ui/toast'; +import { LoadingPage } from '@/components/ui/loading-spinner'; const supabase = createSupabaseBrowserClient(); @@ -22,6 +24,7 @@ export default function StudentTaskViewPage() { const { address, isConnected } = useAccount(); const router = useRouter(); const params = useParams(); + const toast = useToast(); const homeworkId = params.id as string; const [profile, setProfile] = useState(null); @@ -74,7 +77,7 @@ export default function StudentTaskViewPage() { setTaskResources(resourcesData); } catch (error: any) { console.error('Error loading data:', error); - alert('Error loading task details'); + toast.error('Loading failed', 'Error loading task details'); router.push('/dashboard/student'); } finally { setLoading(false); @@ -82,7 +85,7 @@ export default function StudentTaskViewPage() { } loadData(); - }, [address, isConnected, router, homeworkId]); + }, [address, isConnected, router, homeworkId, toast]); async function handleEnroll() { if (!profile || !homework) return; @@ -90,14 +93,14 @@ export default function StudentTaskViewPage() { setEnrolling(true); try { await enrollInHomework(profile.id, homework.id); - alert('Enrolled successfully! ✅'); + toast.success('Enrolled successfully!', 'Redirecting to your task...'); router.push(`/dashboard/student/homework/${homework.id}`); } catch (error: any) { console.error('Error enrolling:', error); if (error.message?.includes('duplicate')) { - alert('You are already enrolled in this task!'); + toast.warning('Already enrolled', 'You are already enrolled in this task!'); } else { - alert('Error enrolling. Please try again.'); + toast.error('Enrollment failed', 'Error enrolling. Please try again.'); } } finally { setEnrolling(false); @@ -105,7 +108,7 @@ export default function StudentTaskViewPage() { } if (loading) { - return
Loading...
; + return ; } if (!profile || !homework) { diff --git a/frontend/app/dashboard/teacher/homework/[id]/page.tsx b/frontend/app/dashboard/teacher/homework/[id]/page.tsx index 5379408..139179c 100644 --- a/frontend/app/dashboard/teacher/homework/[id]/page.tsx +++ b/frontend/app/dashboard/teacher/homework/[id]/page.tsx @@ -137,14 +137,28 @@ export default function HomeworkDetailPage() { try { // Upload file to Supabase Storage const fileExt = uploadedFile.name.split('.').pop(); - const fileName = `${profile.id}/${homework.id}/${Date.now()}.${fileExt}`; + const fileName = `${profile.id}/${homework.id}/${Date.now()}_${Math.random().toString(36).substring(7)}.${fileExt}`; const { data: uploadData, error: uploadError } = await supabase .storage .from('task-resources') .upload(fileName, uploadedFile); - if (uploadError) throw uploadError; + if (uploadError) { + console.error('Storage upload error:', uploadError); + let errorMessage = 'Failed to upload file to storage.'; + + if (uploadError.message.includes('row-level security')) { + errorMessage = 'Storage access denied. Please check your permissions.'; + } else if (uploadError.message.includes('size')) { + errorMessage = 'File is too large. Maximum file size is 50MB.'; + } else if (uploadError.message) { + errorMessage = `Upload failed: ${uploadError.message}`; + } + + alert(`❌ ${errorMessage}\n\nFile: ${uploadedFile.name}`); + return; + } // Get public URL const { data: { publicUrl } } = supabase @@ -153,13 +167,21 @@ export default function HomeworkDetailPage() { .getPublicUrl(fileName); // Create task resource record - await createTaskResource({ - homework_id: homework.id, - teacher_id: profile.id, - file_url: publicUrl, - file_name: uploadedFile.name, - file_type: uploadedFile.type, - }); + try { + await createTaskResource({ + homework_id: homework.id, + teacher_id: profile.id, + file_url: publicUrl, + file_name: uploadedFile.name, + file_type: uploadedFile.type, + }); + } catch (dbError: any) { + console.error('Database error:', dbError); + // If DB insert fails, try to clean up the uploaded file + await supabase.storage.from('task-resources').remove([fileName]); + alert(`❌ Failed to save file metadata to database.\n\nFile: ${uploadedFile.name}\nError: ${dbError.message || 'Unknown error'}`); + return; + } // Reload task resources const resourcesData = await getTaskResources({ homeworkId: homework.id }); @@ -170,10 +192,10 @@ export default function HomeworkDetailPage() { const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement; if (fileInput) fileInput.value = ''; - alert('Resource file uploaded successfully!'); + alert(`✅ Resource file uploaded successfully!\n\n${uploadedFile.name}`); } catch (error: any) { - console.error('Error uploading file:', error); - alert('Error uploading file. Please try again.'); + console.error('Unexpected error uploading file:', error); + alert(`❌ Unexpected error uploading file.\n\nFile: ${uploadedFile.name}\nPlease try again or contact support if the problem persists.`); } finally { setUploading(false); } diff --git a/frontend/app/dashboard/teacher/page.tsx b/frontend/app/dashboard/teacher/page.tsx index 68975be..c68a863 100644 --- a/frontend/app/dashboard/teacher/page.tsx +++ b/frontend/app/dashboard/teacher/page.tsx @@ -103,36 +103,51 @@ export default function TeacherDashboard() { } return ( -
-
-
-

Teacher Dashboard

-

- Manage tasks, answer questions, and review students -

-
- - - +
+ {/* Animated background blobs */} +
+
+
- {/* Token Balance Warning */} - {profile.token_balance < 1 && ( - - -
- -

- Insufficient tokens! You need at least 1 token to create a task. - Each task costs 1 token. -

-
-
-
- )} +
+
+
+

+ Teacher Dashboard +

+

+ Manage tasks, answer questions, and review students 👨‍🏫 +

+
+ + + +
+ + {/* Token Balance Warning */} + {profile.token_balance < 1 && ( + + +
+
+ +
+

+ Insufficient tokens! You need at least 1 token to create a task. + Each task costs 1 token. +

+
+
+
+ )} {/* Stats Cards */}
@@ -290,5 +305,6 @@ export default function TeacherDashboard() { )}
+
); } diff --git a/frontend/app/globals.css b/frontend/app/globals.css index 4fced5d..6bbf83c 100644 --- a/frontend/app/globals.css +++ b/frontend/app/globals.css @@ -120,3 +120,193 @@ @apply bg-background text-foreground; } } + +@layer utilities { + /* MetaMask-inspired animations */ + @keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + + @keyframes slideIn { + from { + opacity: 0; + transform: translateX(-20px); + } + to { + opacity: 1; + transform: translateX(0); + } + } + + @keyframes scaleIn { + from { + opacity: 0; + transform: scale(0.95); + } + to { + opacity: 1; + transform: scale(1); + } + } + + @keyframes shimmer { + 0% { + background-position: -1000px 0; + } + 100% { + background-position: 1000px 0; + } + } + + @keyframes pulse-glow { + 0%, 100% { + box-shadow: 0 0 20px rgba(59, 130, 246, 0.3); + } + 50% { + box-shadow: 0 0 30px rgba(59, 130, 246, 0.5); + } + } + + @keyframes progress { + from { + width: 100%; + } + to { + width: 0%; + } + } + + @keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } + + @keyframes bounce-in { + 0% { + transform: scale(0.3); + opacity: 0; + } + 50% { + transform: scale(1.05); + } + 70% { + transform: scale(0.9); + } + 100% { + transform: scale(1); + opacity: 1; + } + } + + .animate-fade-in { + animation: fadeIn 0.5s ease-out; + } + + .animate-slide-in { + animation: slideIn 0.4s ease-out; + } + + .animate-scale-in { + animation: scaleIn 0.3s ease-out; + } + + .animate-shimmer { + background: linear-gradient( + to right, + transparent 0%, + rgba(255, 255, 255, 0.1) 50%, + transparent 100% + ); + background-size: 1000px 100%; + animation: shimmer 2s infinite; + } + + .animate-spin { + animation: spin 1s linear infinite; + } + + .animate-bounce-in { + animation: bounce-in 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55); + } + + /* Glass morphism effect */ + .glass { + background: rgba(255, 255, 255, 0.05); + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.1); + } + + .glass-dark { + background: rgba(0, 0, 0, 0.2); + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.1); + } + + /* Gradient borders */ + .gradient-border { + position: relative; + background: linear-gradient(var(--background), var(--background)) padding-box, + linear-gradient(135deg, #667eea 0%, #764ba2 100%) border-box; + border: 2px solid transparent; + } + + /* Hover effects */ + .card-hover { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + } + + .card-hover:hover { + transform: translateY(-4px); + box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); + } + + /* MetaMask-style button */ + .metamask-button { + background: linear-gradient(135deg, #f6851b 0%, #e2761b 100%); + box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1); + transition: all 0.2s ease; + } + + .metamask-button:hover { + background: linear-gradient(135deg, #e2761b 0%, #cd6116 100%); + box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.2); + transform: translateY(-1px); + } + + /* Smooth transitions */ + .smooth-transition { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + } + + /* Loading skeleton */ + @keyframes skeleton-loading { + 0% { + background-position: -200px 0; + } + 100% { + background-position: calc(200px + 100%) 0; + } + } + + .skeleton { + background: linear-gradient( + 90deg, + rgba(226, 232, 240, 0.2) 0px, + rgba(226, 232, 240, 0.3) 40px, + rgba(226, 232, 240, 0.2) 80px + ); + background-size: 200px 100%; + animation: skeleton-loading 1.4s ease-in-out infinite; + } +} diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx index b5e569c..d023e9a 100644 --- a/frontend/app/page.tsx +++ b/frontend/app/page.tsx @@ -42,46 +42,52 @@ export default function Home() { return (
{/* Hero Section */} -
-
-
+
+ {/* Animated background blobs */} +
+
+
+
+
+ +
- - + + Education in the Era of Technology -

+

Learn, Stake, and Earn in Web3

-

+

A decentralized platform where teachers create educational tasks and students solve them. - Fair incentives through staking, AI-powered personalization, and complete transparency. + Fair incentives through staking, AI-powered personalization, and complete transparency. ✨

-
+
- -
{/* Stats */} -
- {stats.map((stat) => ( -
-
+
+ {stats.map((stat, index) => ( +
+
{stat.value}
-
+
{stat.label}
@@ -102,16 +108,25 @@ export default function Home() {
- {features.map((feature) => { + {features.map((feature, index) => { const Icon = feature.icon; + const gradients = [ + 'from-blue-500 to-cyan-500', + 'from-green-500 to-emerald-500', + 'from-purple-500 to-pink-500', + 'from-orange-500 to-red-500' + ]; return ( - - -
- + +
+ +
+
- {feature.title} - + + {feature.title} + + {feature.description}
@@ -133,31 +148,34 @@ export default function Home() {
-
-
- 1 +
+
+
+ 1
-

Connect Wallet

+

Connect Wallet

Connect your MetaMask or any Web3 wallet to get started

-
-
- 2 +
+
+
+ 2
-

Choose Your Role

+

Choose Your Role

Be a teacher creating tasks or a student solving challenges

-
-
- 3 +
+
+
+ 3
-

Stake & Learn

+

Stake & Learn

Stake tokens, complete tasks, and earn rewards through learning

@@ -213,15 +231,21 @@ export default function Home() {
{/* CTA */} -
-
-

Ready to Start Learning?

-

+

+
+
+
+
+
+
+

Ready to Start Learning? 🚀

+

Join thousands of students and teachers already on the platform

-
diff --git a/frontend/app/providers.tsx b/frontend/app/providers.tsx index bf8f519..9b6158f 100644 --- a/frontend/app/providers.tsx +++ b/frontend/app/providers.tsx @@ -4,6 +4,7 @@ import { WagmiProvider } from 'wagmi'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { RainbowKitProvider } from '@rainbow-me/rainbowkit'; import { wagmiConfig } from '@/lib/wagmi'; +import { ToastProvider } from '@/components/ui/toast'; import React from 'react'; const queryClient = new QueryClient(); @@ -13,7 +14,9 @@ export function Providers({ children }: { children: React.ReactNode }) { - {children} + + {children} + diff --git a/frontend/app/settings/page.tsx b/frontend/app/settings/page.tsx index c5afe25..76c4c1f 100644 --- a/frontend/app/settings/page.tsx +++ b/frontend/app/settings/page.tsx @@ -13,11 +13,13 @@ import { Select } from '@/components/ui/select'; import { Textarea } from '@/components/ui/textarea'; import { User, Settings as SettingsIcon } from 'lucide-react'; import { createSupabaseBrowserClient } from '@/lib/supabase/client'; +import { useToast } from '@/components/ui/toast'; const supabase = createSupabaseBrowserClient(); export default function SettingsPage() { const { address } = useAccount(); + const toast = useToast(); const [profile, setProfile] = useState(null); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); @@ -60,10 +62,10 @@ export default function SettingsPage() { setSaving(true); try { await updateProfile(profile.id, formData); - alert('Profile updated successfully!'); + toast.success('Profile updated successfully!', 'Your changes have been saved.'); } catch (error) { console.error('Error updating profile:', error); - alert('Error updating profile. Please try again.'); + toast.error('Error updating profile', 'Please try again later.'); } finally { setSaving(false); } @@ -86,21 +88,31 @@ export default function SettingsPage() { } return ( -
-
-

Settings

-

- Manage your profile, privacy settings, and understand how your data is used +

+ {/* Animated background blobs */} +
+
+
+
+ +
+

+ Settings +

+

+ Manage your profile, privacy settings, and understand how your data is used ⚙️

-
+
{/* Profile Settings */} - +
- - Profile Settings +
+ +
+ Profile Settings
Update your personal information and preferences
@@ -123,7 +135,7 @@ export default function SettingsPage() { value={formData.username} onChange={(e) => setFormData({ ...formData, username: e.target.value })} placeholder="Enter your username" - className="w-full px-4 py-2 rounded-lg border border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 focus:outline-none focus:ring-2 focus:ring-blue-500" + className="w-full px-4 py-2 rounded-lg border border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 focus:outline-none focus:ring-2 focus:ring-blue-500 smooth-transition hover:border-blue-400" />
@@ -132,7 +144,7 @@ export default function SettingsPage() {