-
Ready to Start Learning?
-
+
+
+
+
+
Ready to Start Learning? 🚀
+
Join thousands of students and teachers already on the platform
-
- Launch App
+
+
+ Launch App Now
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() {
setFormData({ ...formData, role: e.target.value as any })}
- 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"
>
Student
Teacher
@@ -140,7 +152,11 @@ export default function SettingsPage() {
-
+
{saving ? 'Saving...' : 'Save Changes'}
Reset
@@ -159,38 +176,40 @@ export default function SettingsPage() {
{/* Stats Overview */}
-
+
-
-
Your Statistics
+
+
+
+
Your Statistics
Overview of your activity and reputation
-
-
{profile.rating.toFixed(1)}
-
Rating
+
+
{profile.rating.toFixed(1)}
+
Rating
-
-
{profile.completed_count}
-
Completed
+
+
{profile.completed_count}
+
Completed
-
-
{profile.total_reviews}
-
Total Reviews
+
+
{profile.total_reviews}
+
Total Reviews
-
-
{profile.token_balance}
-
Tokens
+
+
{profile.token_balance}
+
Tokens
-
+
- How your rating grows: Your rating increases through active participation,
+ How your rating grows: Your rating increases through active participation,
receiving positive reviews, and helping other students. Earn tokens by answering questions
and completing tasks.
diff --git a/frontend/app/test-toast/page.tsx b/frontend/app/test-toast/page.tsx
new file mode 100644
index 0000000..b246a43
--- /dev/null
+++ b/frontend/app/test-toast/page.tsx
@@ -0,0 +1,106 @@
+'use client';
+
+import { Button } from '@/components/ui/button';
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
+import { useToast } from '@/components/ui/toast';
+
+export default function TestToastPage() {
+ const toast = useToast();
+
+ return (
+
+
+
+
+ Toast Notifications Test
+
+
+ Test all types of toast notifications
+
+
+
+
+
+
+ Success Toast
+ Shows a success message with green gradient
+
+
+ toast.success('Success!', 'Your action was completed successfully.')}
+ className="w-full bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700"
+ >
+ Show Success Toast
+
+
+
+
+
+
+ Error Toast
+ Shows an error message with red gradient
+
+
+ toast.error('Error!', 'Something went wrong. Please try again.')}
+ className="w-full bg-gradient-to-r from-red-500 to-pink-600 hover:from-red-600 hover:to-pink-700"
+ >
+ Show Error Toast
+
+
+
+
+
+
+ Warning Toast
+ Shows a warning message with orange gradient
+
+
+ toast.warning('Warning!', 'Please check your input before continuing.')}
+ className="w-full bg-gradient-to-r from-orange-500 to-amber-600 hover:from-orange-600 hover:to-amber-700"
+ >
+ Show Warning Toast
+
+
+
+
+
+
+ Info Toast
+ Shows an info message with blue gradient
+
+
+ toast.info('Information', 'Here is some useful information for you.')}
+ className="w-full bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700"
+ >
+ Show Info Toast
+
+
+
+
+
+
+
+ All Toasts at Once
+ Test multiple toasts appearing simultaneously
+
+
+ {
+ toast.success('Task 1 Complete', 'First task finished successfully!');
+ setTimeout(() => toast.info('Processing...', 'Working on task 2...'), 200);
+ setTimeout(() => toast.warning('Attention Needed', 'Please review task 3.'), 400);
+ setTimeout(() => toast.error('Task 4 Failed', 'Something went wrong with task 4.'), 600);
+ }}
+ className="w-full bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700"
+ >
+ Show Multiple Toasts
+
+
+
+
+
+ );
+}
diff --git a/frontend/components/DataPrivacyPanel.tsx b/frontend/components/DataPrivacyPanel.tsx
index e7899bb..cb1b37f 100644
--- a/frontend/components/DataPrivacyPanel.tsx
+++ b/frontend/components/DataPrivacyPanel.tsx
@@ -6,6 +6,7 @@ import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Download, Trash2, Eye, Shield, Info } from 'lucide-react';
import { createSupabaseBrowserClient } from '@/lib/supabase/client';
+import { useToast } from '@/components/ui/toast';
const supabase = createSupabaseBrowserClient();
@@ -14,6 +15,7 @@ interface DataPrivacyPanelProps {
}
export function DataPrivacyPanel({ userId }: DataPrivacyPanelProps) {
+ const toast = useToast();
const [loading, setLoading] = useState(false);
const [showExplanation, setShowExplanation] = useState(false);
@@ -44,10 +46,10 @@ export function DataPrivacyPanel({ userId }: DataPrivacyPanelProps) {
// Display data in a modal or new page
console.log('User Data:', { profile, submissions, homeworks });
- alert('Your data has been loaded. Check the console for details. In production, this would show a detailed view.');
+ toast.success('Data loaded successfully', 'Check the console for details. In production, this would show a detailed view.');
} catch (error) {
console.error('Error viewing data:', error);
- alert('Error loading your data. Please try again.');
+ toast.error('Error loading your data', 'Please try again later.');
} finally {
setLoading(false);
}
@@ -115,9 +117,10 @@ export function DataPrivacyPanel({ userId }: DataPrivacyPanelProps) {
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
+ toast.success('Data exported successfully', `Your data has been downloaded to my-data-${new Date().toISOString()}.json`);
} catch (error) {
console.error('Error exporting data:', error);
- alert('Error exporting your data. Please try again.');
+ toast.error('Error exporting your data', 'Please try again later.');
} finally {
setLoading(false);
}
@@ -138,7 +141,7 @@ export function DataPrivacyPanel({ userId }: DataPrivacyPanelProps) {
const finalConfirm = prompt('Type DELETE to confirm:');
if (finalConfirm !== 'DELETE') {
- alert('Deletion cancelled.');
+ toast.info('Deletion cancelled', 'Your data is safe.');
return;
}
@@ -152,11 +155,11 @@ export function DataPrivacyPanel({ userId }: DataPrivacyPanelProps) {
// Delete user data (RLS policies will handle cascade)
await supabase.from('profiles').delete().eq('id', userId);
- alert('Your data has been deleted. You will be logged out.');
+ toast.success('Data deleted successfully', 'Your data has been deleted. You will be logged out.');
// In production, handle logout and redirect
} catch (error) {
console.error('Error deleting data:', error);
- alert('Error deleting your data. Please contact support.');
+ toast.error('Error deleting your data', 'Please contact support.');
} finally {
setLoading(false);
}
@@ -164,11 +167,13 @@ export function DataPrivacyPanel({ userId }: DataPrivacyPanelProps) {
return (
-
+
-
-
Data Privacy & Transparency
+
+
+
+
Data Privacy & Transparency
You have full control over your personal data. View, export, or delete your information at any time.
@@ -179,7 +184,7 @@ export function DataPrivacyPanel({ userId }: DataPrivacyPanelProps) {
@@ -189,7 +194,7 @@ export function DataPrivacyPanel({ userId }: DataPrivacyPanelProps) {
@@ -199,7 +204,7 @@ export function DataPrivacyPanel({ userId }: DataPrivacyPanelProps) {
@@ -211,14 +216,14 @@ export function DataPrivacyPanel({ userId }: DataPrivacyPanelProps) {
setShowExplanation(!showExplanation)}
- className="flex items-center gap-2 text-sm text-blue-600 hover:text-blue-700"
+ className="flex items-center gap-2 text-sm font-medium bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent hover:from-blue-700 hover:to-purple-700 smooth-transition"
>
-
+
{showExplanation ? 'Hide' : 'Show'} what data we collect
{showExplanation && (
-
+
Personal Information
@@ -256,9 +261,14 @@ export function DataPrivacyPanel({ userId }: DataPrivacyPanelProps) {
-
+
- Algorithmic Transparency
+
+
+
+
+
Algorithmic Transparency
+
Understand how our algorithms make decisions about recommendations and evaluations
@@ -266,9 +276,9 @@ export function DataPrivacyPanel({ userId }: DataPrivacyPanelProps) {
-
+
-
Recommendations
+
Recommendations
How tasks are recommended to you:
@@ -282,9 +292,9 @@ export function DataPrivacyPanel({ userId }: DataPrivacyPanelProps) {
-
+
-
Reputation
+
Reputation
How reputation is calculated:
diff --git a/frontend/components/ui/loading-spinner.tsx b/frontend/components/ui/loading-spinner.tsx
new file mode 100644
index 0000000..fedb954
--- /dev/null
+++ b/frontend/components/ui/loading-spinner.tsx
@@ -0,0 +1,56 @@
+'use client';
+
+import { cn } from '@/lib/utils';
+import { Loader2 } from 'lucide-react';
+
+interface LoadingSpinnerProps {
+ className?: string;
+ size?: 'sm' | 'md' | 'lg' | 'xl';
+}
+
+const sizeClasses = {
+ sm: 'w-4 h-4',
+ md: 'w-6 h-6',
+ lg: 'w-8 h-8',
+ xl: 'w-12 h-12',
+};
+
+export function LoadingSpinner({ className, size = 'md' }: LoadingSpinnerProps) {
+ return (
+
+ );
+}
+
+export function LoadingPage() {
+ return (
+
+ );
+}
+
+export function LoadingCard() {
+ return (
+
+ );
+}
+
+export function LoadingSkeleton({ count = 3 }: { count?: number }) {
+ return (
+
+ {Array.from({ length: count }).map((_, i) => (
+
+ ))}
+
+ );
+}
diff --git a/frontend/components/ui/toast.tsx b/frontend/components/ui/toast.tsx
new file mode 100644
index 0000000..5f10345
--- /dev/null
+++ b/frontend/components/ui/toast.tsx
@@ -0,0 +1,170 @@
+'use client';
+
+import * as React from 'react';
+import { X, CheckCircle, AlertCircle, Info, AlertTriangle } from 'lucide-react';
+import { cn } from '@/lib/utils';
+
+export type ToastType = 'success' | 'error' | 'info' | 'warning';
+
+export interface Toast {
+ id: string;
+ type: ToastType;
+ title: string;
+ description?: string;
+ duration?: number;
+}
+
+interface ToastProps {
+ toast: Toast;
+ onClose: (id: string) => void;
+}
+
+const toastIcons = {
+ success: CheckCircle,
+ error: AlertCircle,
+ info: Info,
+ warning: AlertTriangle,
+};
+
+const toastStyles = {
+ success: 'bg-gradient-to-r from-green-500 to-emerald-600 text-white border-green-400',
+ error: 'bg-gradient-to-r from-red-500 to-pink-600 text-white border-red-400',
+ info: 'bg-gradient-to-r from-blue-500 to-purple-600 text-white border-blue-400',
+ warning: 'bg-gradient-to-r from-orange-500 to-amber-600 text-white border-orange-400',
+};
+
+function ToastItem({ toast, onClose }: ToastProps) {
+ const Icon = toastIcons[toast.type];
+
+ React.useEffect(() => {
+ const timer = setTimeout(() => {
+ onClose(toast.id);
+ }, toast.duration || 5000);
+
+ return () => clearTimeout(timer);
+ }, [toast.id, toast.duration, onClose]);
+
+ return (
+
+
+
+
+
+
{toast.title}
+ {toast.description && (
+
{toast.description}
+ )}
+
+
onClose(toast.id)}
+ className="ml-auto flex-shrink-0 rounded-md p-1 hover:bg-white/20 smooth-transition hover:scale-110"
+ >
+
+
+
+ {/* Progress bar */}
+
+
+ );
+}
+
+interface ToastContainerProps {
+ toasts: Toast[];
+ onClose: (id: string) => void;
+}
+
+export function ToastContainer({ toasts, onClose }: ToastContainerProps) {
+ return (
+
+ {toasts.map((toast) => (
+
+ ))}
+
+ );
+}
+
+// Context and Hook
+interface ToastContextType {
+ toasts: Toast[];
+ addToast: (toast: Omit) => void;
+ removeToast: (id: string) => void;
+ success: (title: string, description?: string) => void;
+ error: (title: string, description?: string) => void;
+ info: (title: string, description?: string) => void;
+ warning: (title: string, description?: string) => void;
+}
+
+const ToastContext = React.createContext(undefined);
+
+export function ToastProvider({ children }: { children: React.ReactNode }) {
+ const [toasts, setToasts] = React.useState([]);
+
+ const addToast = React.useCallback((toast: Omit) => {
+ const id = Math.random().toString(36).substring(2, 9);
+ setToasts((prev) => [...prev, { ...toast, id }]);
+ }, []);
+
+ const removeToast = React.useCallback((id: string) => {
+ setToasts((prev) => prev.filter((toast) => toast.id !== id));
+ }, []);
+
+ const success = React.useCallback(
+ (title: string, description?: string) => {
+ addToast({ type: 'success', title, description });
+ },
+ [addToast]
+ );
+
+ const error = React.useCallback(
+ (title: string, description?: string) => {
+ addToast({ type: 'error', title, description });
+ },
+ [addToast]
+ );
+
+ const info = React.useCallback(
+ (title: string, description?: string) => {
+ addToast({ type: 'info', title, description });
+ },
+ [addToast]
+ );
+
+ const warning = React.useCallback(
+ (title: string, description?: string) => {
+ addToast({ type: 'warning', title, description });
+ },
+ [addToast]
+ );
+
+ return (
+
+ {children}
+
+
+ );
+}
+
+export function useToast() {
+ const context = React.useContext(ToastContext);
+ if (!context) {
+ throw new Error('useToast must be used within a ToastProvider');
+ }
+ return context;
+}
diff --git a/frontend/lib/animations.ts b/frontend/lib/animations.ts
new file mode 100644
index 0000000..ea5f13a
--- /dev/null
+++ b/frontend/lib/animations.ts
@@ -0,0 +1,43 @@
+// Animation utilities and variants for framer-motion
+export const fadeIn = {
+ initial: { opacity: 0, y: 20 },
+ animate: { opacity: 1, y: 0 },
+ exit: { opacity: 0, y: -20 },
+ transition: { duration: 0.3 }
+};
+
+export const slideIn = {
+ initial: { opacity: 0, x: -20 },
+ animate: { opacity: 1, x: 0 },
+ exit: { opacity: 0, x: 20 },
+ transition: { duration: 0.3 }
+};
+
+export const scaleIn = {
+ initial: { opacity: 0, scale: 0.95 },
+ animate: { opacity: 1, scale: 1 },
+ exit: { opacity: 0, scale: 0.95 },
+ transition: { duration: 0.2 }
+};
+
+export const staggerContainer = {
+ animate: {
+ transition: {
+ staggerChildren: 0.1
+ }
+ }
+};
+
+export const cardHover = {
+ rest: { scale: 1, y: 0 },
+ hover: {
+ scale: 1.02,
+ y: -4,
+ transition: { duration: 0.2 }
+ }
+};
+
+// CSS class helpers for smooth transitions
+export const transitionClasses = 'transition-all duration-300 ease-in-out';
+export const hoverLift = 'hover:-translate-y-1 hover:shadow-lg transition-all duration-200';
+export const smoothShadow = 'shadow-sm hover:shadow-md transition-shadow duration-200';
diff --git a/supabase/apply-migrations.md b/supabase/apply-migrations.md
index e1b4ac4..49d9323 100644
--- a/supabase/apply-migrations.md
+++ b/supabase/apply-migrations.md
@@ -1,78 +1,113 @@
-# Apply Database Migrations
+# Apply Database Migrations - UPDATED
-To apply all new migrations, follow these steps:
+## ⚠️ IMPORTANT: Run these migrations in your Supabase Dashboard NOW!
-1. **Open Supabase Dashboard**: Go to your Supabase project dashboard
+Your application is currently **broken** because these database tables don't exist yet.
-2. **SQL Editor**: Navigate to the SQL Editor section
+---
-3. **Run Migrations in Order**:
- - First: `migrations/003_add_submissions.sql` (Submissions table)
- - Second: `migrations/004_add_enrollment_fields.sql` (Enrollment fields)
- - Third: `migrations/005_add_task_resources.sql` (Task resources + deadline field)
+## Quick Steps:
-4. **Create Storage Buckets**:
+### 1. **Open Supabase Dashboard**
+ - Go to your Supabase project: https://app.supabase.com
+ - Navigate to **SQL Editor**
- **Bucket 1: submissions** (for student file uploads)
- - Go to Storage section
- - Create a new bucket named `submissions`
- - Make it public
- - Add the following policies:
+### 2. **Copy & Run Migration 003** (Submissions Table)
+ - Open: `migrations/003_add_submissions.sql`
+ - Copy the entire contents
+ - Paste into SQL Editor
+ - Click **Run**
-```sql
--- Allow public SELECT access
-CREATE POLICY "Public Access"
-ON storage.objects FOR SELECT
-USING (bucket_id = 'submissions');
-
--- Allow authenticated INSERT
-CREATE POLICY "Authenticated users can upload"
-ON storage.objects FOR INSERT
-WITH CHECK (bucket_id = 'submissions');
-
--- Allow authenticated UPDATE (for their own files)
-CREATE POLICY "Users can update own files"
-ON storage.objects FOR UPDATE
-USING (bucket_id = 'submissions');
-
--- Allow authenticated DELETE (for their own files)
-CREATE POLICY "Users can delete own files"
-ON storage.objects FOR DELETE
-USING (bucket_id = 'submissions');
-```
+### 3. **Copy & Run Migration 004** (Enrollment Fields)
+ - Open: `migrations/004_add_enrollment_fields.sql`
+ - Copy the entire contents
+ - Paste into SQL Editor
+ - Click **Run**
+
+### 4. **Copy & Run Migration 005** (Task Resources)
+ - Open: `migrations/005_add_task_resources.sql`
+ - Copy the entire contents
+ - Paste into SQL Editor
+ - Click **Run**
+
+### 5. **Copy & Run Migration 006** (Storage Buckets + Cleanup) ⭐ NEW!
+ - Open: `migrations/006_setup_storage_and_cleanup.sql`
+ - Copy the entire contents
+ - Paste into SQL Editor
+ - Click **Run**
+ - This will:
+ - ✅ Create storage buckets (submissions, task-resources)
+ - ✅ Set up all storage policies automatically
+ - ✅ Remove deadline column (no longer used)
+ - ✅ Clean up enrollment statuses
- **Bucket 2: task-resources** (for teacher resource uploads)
- - Create a new bucket named `task-resources`
- - Make it public
- - Add the following policies:
+---
+
+## 6. **Verify Everything Works**
+
+Run this query in SQL Editor to verify:
```sql
--- Allow public SELECT access
-CREATE POLICY "Public Access"
-ON storage.objects FOR SELECT
-USING (bucket_id = 'task-resources');
-
--- Allow teachers to upload
-CREATE POLICY "Teachers can upload"
-ON storage.objects FOR INSERT
-WITH CHECK (bucket_id = 'task-resources');
-
--- Allow teachers to delete
-CREATE POLICY "Teachers can delete"
-ON storage.objects FOR DELETE
-USING (bucket_id = 'task-resources');
+-- Check tables exist
+SELECT table_name
+FROM information_schema.tables
+WHERE table_schema = 'public'
+AND table_name IN ('submissions', 'task_resources', 'homeworks', 'enrollments');
+
+-- Check storage buckets exist
+SELECT id, name, public
+FROM storage.buckets
+WHERE id IN ('submissions', 'task-resources');
+
+-- Check homeworks does NOT have deadline column (should return 0 rows)
+SELECT column_name
+FROM information_schema.columns
+WHERE table_name = 'homeworks'
+AND column_name = 'deadline';
```
-5. **Verify**: Check that all tables have been created successfully:
- - `submissions`
- - `task_resources`
- - Check that `homeworks` has `deadline` field
- - Check that `enrollments` supports 'missed' status
+Expected results:
+- ✅ 4 tables found: submissions, task_resources, homeworks, enrollments
+- ✅ 2 buckets found: submissions, task-resources
+- ✅ 0 rows for deadline column (it should be removed)
+
+---
+
+## What These Migrations Do:
+
+1. ✅ **003**: Creates `submissions` table for student file uploads
+2. ✅ **004**: Adds `submission_text`, `review_score`, `review_comment`, `completed_at` to enrollments
+3. ✅ **005**: Creates `task_resources` table for teacher resource uploads
+4. ✅ **006**:
+ - Creates storage buckets automatically
+ - Sets up all storage policies
+ - Removes deadline functionality
+ - Cleans up enrollment statuses
+
+---
+
+## After Running Migrations:
+
+1. **Refresh your application** - The errors should disappear
+2. **Test file uploads**:
+ - As a teacher: Create a homework and upload resource files
+ - As a student: Enroll in homework and upload submission files
+3. **All file upload error handling is now in place** with detailed user feedback
+
+---
+
+## Troubleshooting:
+
+**If you get "permission denied" errors:**
+- Make sure you're logged in as the database owner
+- Try running each migration separately
-## What was changed:
+**If storage buckets fail to create:**
+- Go to Storage section in dashboard
+- Manually create buckets: `submissions` and `task-resources`
+- Make both public
+- Then re-run migration 006 (just the policy parts will execute)
-1. ✅ Added submissions table with file upload support
-2. ✅ Added student file upload functionality
-3. ✅ Added teacher submission review page
-4. ✅ Added "Unreviewed Work" section in teacher dashboard
-5. ✅ Dashboard now redirects to role-specific pages after login
+**If you see "already exists" errors:**
+- This is OK! It means the migration was already partially applied
+- The migrations use `IF NOT EXISTS` and `ON CONFLICT` to be safe
diff --git a/supabase/migrations/006_setup_storage_and_cleanup.sql b/supabase/migrations/006_setup_storage_and_cleanup.sql
new file mode 100644
index 0000000..5ad9577
--- /dev/null
+++ b/supabase/migrations/006_setup_storage_and_cleanup.sql
@@ -0,0 +1,79 @@
+-- ============================================
+-- Setup Storage Buckets and Cleanup
+-- ============================================
+
+-- ============================================
+-- 1. CREATE STORAGE BUCKETS
+-- ============================================
+
+-- Create submissions bucket (for student file uploads)
+INSERT INTO storage.buckets (id, name, public)
+VALUES ('submissions', 'submissions', true)
+ON CONFLICT (id) DO NOTHING;
+
+-- Create task-resources bucket (for teacher file uploads)
+INSERT INTO storage.buckets (id, name, public)
+VALUES ('task-resources', 'task-resources', true)
+ON CONFLICT (id) DO NOTHING;
+
+-- ============================================
+-- 2. STORAGE POLICIES - SUBMISSIONS BUCKET
+-- ============================================
+
+-- Allow everyone to read submissions
+CREATE POLICY IF NOT EXISTS "Public Access to Submissions"
+ON storage.objects FOR SELECT
+USING (bucket_id = 'submissions');
+
+-- Allow authenticated users to upload submissions
+CREATE POLICY IF NOT EXISTS "Authenticated users can upload submissions"
+ON storage.objects FOR INSERT
+WITH CHECK (bucket_id = 'submissions' AND auth.role() = 'authenticated');
+
+-- Allow users to delete their own submissions
+CREATE POLICY IF NOT EXISTS "Users can delete own submissions"
+ON storage.objects FOR DELETE
+USING (bucket_id = 'submissions' AND auth.role() = 'authenticated');
+
+-- Allow users to update their own submissions
+CREATE POLICY IF NOT EXISTS "Users can update own submissions"
+ON storage.objects FOR UPDATE
+USING (bucket_id = 'submissions' AND auth.role() = 'authenticated');
+
+-- ============================================
+-- 3. STORAGE POLICIES - TASK-RESOURCES BUCKET
+-- ============================================
+
+-- Allow everyone to read task resources
+CREATE POLICY IF NOT EXISTS "Public Access to Task Resources"
+ON storage.objects FOR SELECT
+USING (bucket_id = 'task-resources');
+
+-- Allow authenticated users to upload task resources
+CREATE POLICY IF NOT EXISTS "Authenticated users can upload task resources"
+ON storage.objects FOR INSERT
+WITH CHECK (bucket_id = 'task-resources' AND auth.role() = 'authenticated');
+
+-- Allow users to delete their own task resources
+CREATE POLICY IF NOT EXISTS "Users can delete own task resources"
+ON storage.objects FOR DELETE
+USING (bucket_id = 'task-resources' AND auth.role() = 'authenticated');
+
+-- Allow users to update their own task resources
+CREATE POLICY IF NOT EXISTS "Users can update own task resources"
+ON storage.objects FOR UPDATE
+USING (bucket_id = 'task-resources' AND auth.role() = 'authenticated');
+
+-- ============================================
+-- 4. CLEANUP - Remove deadline column (no longer needed)
+-- ============================================
+ALTER TABLE homeworks
+DROP COLUMN IF EXISTS deadline;
+
+-- Update enrollments status constraint (remove 'missed' since no deadline)
+ALTER TABLE enrollments
+DROP CONSTRAINT IF EXISTS enrollments_status_check;
+
+ALTER TABLE enrollments
+ADD CONSTRAINT enrollments_status_check
+CHECK (status IN ('active', 'completed', 'reviewed'));
diff --git a/supabase/verify_migrations.sql b/supabase/verify_migrations.sql
new file mode 100644
index 0000000..ca37cda
--- /dev/null
+++ b/supabase/verify_migrations.sql
@@ -0,0 +1,70 @@
+-- ============================================
+-- QUICK VERIFICATION SCRIPT
+-- Run this after applying all migrations
+-- ============================================
+
+-- 1. Check all required tables exist
+SELECT
+ CASE
+ WHEN COUNT(*) = 4 THEN '✅ ALL TABLES EXIST'
+ ELSE '❌ MISSING TABLES: ' || (4 - COUNT(*))::text
+ END as table_status,
+ array_agg(table_name) as found_tables
+FROM information_schema.tables
+WHERE table_schema = 'public'
+AND table_name IN ('submissions', 'task_resources', 'homeworks', 'enrollments');
+
+-- 2. Check storage buckets exist
+SELECT
+ CASE
+ WHEN COUNT(*) = 2 THEN '✅ ALL BUCKETS EXIST'
+ ELSE '❌ MISSING BUCKETS: ' || (2 - COUNT(*))::text
+ END as bucket_status,
+ array_agg(name) as found_buckets
+FROM storage.buckets
+WHERE id IN ('submissions', 'task-resources');
+
+-- 3. Verify deadline column is REMOVED
+SELECT
+ CASE
+ WHEN COUNT(*) = 0 THEN '✅ DEADLINE REMOVED (CORRECT)'
+ ELSE '❌ DEADLINE STILL EXISTS (NEEDS CLEANUP)'
+ END as deadline_status
+FROM information_schema.columns
+WHERE table_name = 'homeworks'
+AND column_name = 'deadline';
+
+-- 4. Check enrollment fields
+SELECT
+ CASE
+ WHEN COUNT(*) = 4 THEN '✅ ALL ENROLLMENT FIELDS EXIST'
+ ELSE '❌ MISSING FIELDS: ' || (4 - COUNT(*))::text
+ END as enrollment_fields_status,
+ array_agg(column_name) as found_fields
+FROM information_schema.columns
+WHERE table_name = 'enrollments'
+AND column_name IN ('submission_text', 'completed_at', 'review_score', 'review_comment');
+
+-- 5. Check submissions table structure
+SELECT
+ CASE
+ WHEN COUNT(*) >= 8 THEN '✅ SUBMISSIONS TABLE COMPLETE'
+ ELSE '❌ SUBMISSIONS TABLE INCOMPLETE'
+ END as submissions_status,
+ array_agg(column_name) as found_columns
+FROM information_schema.columns
+WHERE table_name = 'submissions';
+
+-- 6. Check task_resources table structure
+SELECT
+ CASE
+ WHEN COUNT(*) >= 6 THEN '✅ TASK_RESOURCES TABLE COMPLETE'
+ ELSE '❌ TASK_RESOURCES TABLE INCOMPLETE'
+ END as task_resources_status,
+ array_agg(column_name) as found_columns
+FROM information_schema.columns
+WHERE table_name = 'task_resources';
+
+-- ============================================
+-- SUMMARY: If all checks show ✅, you're good to go!
+-- ============================================