From 4a73bfc17a316137b1b39b9ad4bd9095161a5d66 Mon Sep 17 00:00:00 2001 From: Deepak Pandey Date: Fri, 12 Sep 2025 14:32:19 +0530 Subject: [PATCH 1/2] fix: resolve username update database function error - Fix update_username function to use proper schema references (public.profiles) - Add comprehensive database migration for profiles table and functions - Improve error handling in UsernameField component for database issues - Add database diagnostic and testing SQL scripts Fixes: relation 'profiles' does not exist error in username updates Resolves: Username update functionality not working due to missing schema references --- check-database-status.sql | 57 +++ check-schema-permissions.sql | 55 +++ components/UsernameField.tsx | 6 +- fix-username-function.sql | 59 +++ ...03_create_profiles_table_and_functions.sql | 351 ++++++++++++++++++ test-fixed-function.sql | 26 ++ test-username-function.sql | 37 ++ 7 files changed, 590 insertions(+), 1 deletion(-) create mode 100644 check-database-status.sql create mode 100644 check-schema-permissions.sql create mode 100644 fix-username-function.sql create mode 100644 supabase/migrations/20241201000003_create_profiles_table_and_functions.sql create mode 100644 test-fixed-function.sql create mode 100644 test-username-function.sql diff --git a/check-database-status.sql b/check-database-status.sql new file mode 100644 index 00000000..be637ca3 --- /dev/null +++ b/check-database-status.sql @@ -0,0 +1,57 @@ +-- ===================================================== +-- DATABASE STATUS CHECK +-- ===================================================== +-- Copy and paste this entire query into your Supabase SQL Editor +-- and share the results with me + +-- 1. Check if profiles table exists +SELECT + 'Table Check' as check_type, + table_name, + CASE + WHEN table_name = 'profiles' THEN 'EXISTS' + ELSE 'OTHER TABLE' + END as status +FROM information_schema.tables +WHERE table_schema = 'public' +AND table_name = 'profiles'; + +-- 2. Check if update_username function exists +SELECT + 'Function Check' as check_type, + proname as function_name, + CASE + WHEN proname = 'update_username' THEN 'EXISTS' + ELSE 'OTHER FUNCTION' + END as status +FROM pg_proc +WHERE proname = 'update_username'; + +-- 3. Check if set_username function exists +SELECT + 'Function Check' as check_type, + proname as function_name, + CASE + WHEN proname = 'set_username' THEN 'EXISTS' + ELSE 'OTHER FUNCTION' + END as status +FROM pg_proc +WHERE proname = 'set_username'; + +-- 4. List all tables in public schema +SELECT + 'All Tables' as check_type, + table_name, + 'EXISTS' as status +FROM information_schema.tables +WHERE table_schema = 'public' +ORDER BY table_name; + +-- 5. List all functions in public schema +SELECT + 'All Functions' as check_type, + proname as function_name, + 'EXISTS' as status +FROM pg_proc +WHERE pronamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public') +ORDER BY proname; diff --git a/check-schema-permissions.sql b/check-schema-permissions.sql new file mode 100644 index 00000000..92b340d8 --- /dev/null +++ b/check-schema-permissions.sql @@ -0,0 +1,55 @@ +-- ===================================================== +-- CHECK SCHEMA AND PERMISSIONS +-- ===================================================== +-- Copy and paste this query to check schema and permissions + +-- 1. Check current schema +SELECT + 'Current Schema' as check_type, + current_schema() as schema_name, + 'Current schema being used' as info; + +-- 2. Check if profiles table exists in different schemas +SELECT + 'Table Schema Check' as check_type, + table_schema, + table_name, + 'EXISTS' as status +FROM information_schema.tables +WHERE table_name = 'profiles' +ORDER BY table_schema; + +-- 3. Check table permissions for current user +SELECT + 'Table Permissions' as check_type, + table_name, + privilege_type, + grantee +FROM information_schema.table_privileges +WHERE table_name = 'profiles' +AND table_schema = 'public'; + +-- 4. Check function permissions +SELECT + 'Function Permissions' as check_type, + routine_name, + privilege_type, + grantee +FROM information_schema.routine_privileges +WHERE routine_name = 'update_username' +AND routine_schema = 'public'; + +-- 5. Check if we can access profiles table directly +SELECT + 'Direct Access Test' as check_type, + COUNT(*) as row_count, + 'Can access profiles table' as info +FROM public.profiles; + +-- 6. Check search_path +SELECT + 'Search Path' as check_type, + setting as search_path, + 'Current search path' as info +FROM pg_settings +WHERE name = 'search_path'; diff --git a/components/UsernameField.tsx b/components/UsernameField.tsx index f3ddca7c..eed22e43 100644 --- a/components/UsernameField.tsx +++ b/components/UsernameField.tsx @@ -109,7 +109,11 @@ export function UsernameField({ }) if (error) { - if (error.message.includes('Username can only be changed once')) { + // Handle specific database errors + if (error.message.includes('relation "profiles" does not exist')) { + setError("Database setup incomplete. Please contact support.") + console.error('Profiles table does not exist. Database migration needed.') + } else if (error.message.includes('Username can only be changed once')) { setError("You can only change your username once") } else if (error.message.includes('Username is already taken')) { setError("Username is already taken") diff --git a/fix-username-function.sql b/fix-username-function.sql new file mode 100644 index 00000000..53c468b1 --- /dev/null +++ b/fix-username-function.sql @@ -0,0 +1,59 @@ +-- ===================================================== +-- FIX UPDATE_USERNAME FUNCTION +-- ===================================================== +-- Copy and paste this query to fix the update_username function + +-- Drop and recreate the function with proper schema references +DROP FUNCTION IF EXISTS update_username(UUID, TEXT); + +CREATE OR REPLACE FUNCTION update_username( + user_id UUID, + new_username TEXT +) +RETURNS BOOLEAN +LANGUAGE plpgsql +SECURITY DEFINER +AS $$ +DECLARE + can_edit BOOLEAN; + username_exists BOOLEAN; +BEGIN + -- Check if user can still edit username + SELECT username_editable INTO can_edit + FROM public.profiles + WHERE id = user_id; + + IF NOT FOUND THEN + RETURN FALSE; + END IF; + + IF NOT can_edit THEN + RETURN FALSE; -- Username already edited once + END IF; + + -- Check if new username already exists + SELECT EXISTS(SELECT 1 FROM public.profiles WHERE username = new_username AND id != user_id) + INTO username_exists; + + IF username_exists THEN + RETURN FALSE; -- Username already taken + END IF; + + -- Update username and mark as non-editable + UPDATE public.profiles + SET username = new_username, username_editable = FALSE + WHERE id = user_id; + + RETURN TRUE; +END; +$$; + +-- Grant execute permission to authenticated users +GRANT EXECUTE ON FUNCTION update_username(UUID, TEXT) TO authenticated; +GRANT EXECUTE ON FUNCTION update_username(UUID, TEXT) TO anon; +GRANT EXECUTE ON FUNCTION update_username(UUID, TEXT) TO service_role; + +-- Test the function +SELECT + 'Function Fixed' as status, + 'update_username function has been updated with proper schema references' as message; diff --git a/supabase/migrations/20241201000003_create_profiles_table_and_functions.sql b/supabase/migrations/20241201000003_create_profiles_table_and_functions.sql new file mode 100644 index 00000000..3b45e044 --- /dev/null +++ b/supabase/migrations/20241201000003_create_profiles_table_and_functions.sql @@ -0,0 +1,351 @@ +-- ===================================================== +-- CREATE PROFILES TABLE AND USERNAME FUNCTIONS +-- ===================================================== +-- This migration creates the profiles table and the required +-- username management functions that the application expects + +-- ===================================================== +-- STEP 1: CREATE PROFILES TABLE +-- ===================================================== + +CREATE TABLE IF NOT EXISTS profiles ( + id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + + -- Basic profile info + first_name TEXT, + last_name TEXT, + bio TEXT, + + -- Contact information + phone TEXT, + email TEXT, + + -- Social links + github_url TEXT, + linkedin_url TEXT, + twitter_url TEXT, + + -- Professional info + current_position TEXT, + company TEXT, + location TEXT, + skills TEXT[], + + -- Settings + is_public BOOLEAN DEFAULT true, + email_notifications BOOLEAN DEFAULT true, + + -- Metadata + profile_completion_percentage INTEGER DEFAULT 0, + + -- Username and Codeunia ID system + username TEXT UNIQUE, + username_editable BOOLEAN DEFAULT true, + codeunia_id TEXT UNIQUE, + username_set BOOLEAN DEFAULT false, + profile_complete BOOLEAN DEFAULT false, + + -- Unified setup flow fields + email_confirmed_at TIMESTAMPTZ, + auth_provider TEXT, + setup_completed_at TIMESTAMPTZ, + + -- Premium membership fields + is_premium BOOLEAN DEFAULT false, + premium_expires_at TIMESTAMPTZ, + premium_plan TEXT, + premium_purchased_at TIMESTAMPTZ, + points_multiplier DECIMAL DEFAULT 1.0, + + -- Membership card email fields + membership_card_sent BOOLEAN DEFAULT false, + membership_card_sent_at TIMESTAMPTZ, + + -- Admin access + is_admin BOOLEAN DEFAULT false +); + +-- ===================================================== +-- STEP 2: CREATE INDEXES +-- ===================================================== + +-- Username index for fast lookups +CREATE INDEX IF NOT EXISTS idx_profiles_username ON profiles(username); +CREATE INDEX IF NOT EXISTS idx_profiles_codeunia_id ON profiles(codeunia_id); +CREATE INDEX IF NOT EXISTS idx_profiles_email ON profiles(email); +CREATE INDEX IF NOT EXISTS idx_profiles_is_admin ON profiles(is_admin); +CREATE INDEX IF NOT EXISTS idx_profiles_username_editable ON profiles(username_editable); +CREATE INDEX IF NOT EXISTS idx_profiles_profile_complete ON profiles(profile_complete); + +-- ===================================================== +-- STEP 3: CREATE UPDATED_AT TRIGGER +-- ===================================================== + +-- Function to update the updated_at column +CREATE OR REPLACE FUNCTION update_updated_at_column() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ language 'plpgsql'; + +-- Trigger to automatically update updated_at +DROP TRIGGER IF EXISTS update_profiles_updated_at ON profiles; +CREATE TRIGGER update_profiles_updated_at + BEFORE UPDATE ON profiles + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); + +-- ===================================================== +-- STEP 4: CREATE USERNAME MANAGEMENT FUNCTIONS +-- ===================================================== + +-- Function to update username (for existing users) +CREATE OR REPLACE FUNCTION update_username( + user_id UUID, + new_username TEXT +) +RETURNS BOOLEAN +LANGUAGE plpgsql +SECURITY DEFINER +AS $$ +DECLARE + profile_record RECORD; + username_exists BOOLEAN; +BEGIN + -- Validate input + IF user_id IS NULL OR new_username IS NULL THEN + RAISE EXCEPTION 'User ID and username are required'; + END IF; + + -- Check if username is valid format + IF NOT (new_username ~ '^[a-zA-Z0-9_-]+$') THEN + RAISE EXCEPTION 'Username can only contain letters, numbers, underscores, and hyphens'; + END IF; + + -- Check username length + IF LENGTH(new_username) < 3 OR LENGTH(new_username) > 30 THEN + RAISE EXCEPTION 'Username must be between 3 and 30 characters'; + END IF; + + -- Get the user's profile + SELECT * INTO profile_record + FROM profiles + WHERE id = user_id; + + -- Check if user exists + IF NOT FOUND THEN + RAISE EXCEPTION 'User not found'; + END IF; + + -- Check if username is already editable + IF NOT profile_record.username_editable THEN + RAISE EXCEPTION 'Username can only be changed once'; + END IF; + + -- Check if username is already taken (case-insensitive) + SELECT EXISTS( + SELECT 1 FROM profiles + WHERE LOWER(username) = LOWER(new_username) + AND id != user_id + ) INTO username_exists; + + IF username_exists THEN + RAISE EXCEPTION 'Username is already taken'; + END IF; + + -- Update the username + UPDATE profiles + SET + username = new_username, + username_editable = false, + username_set = true, + updated_at = NOW() + WHERE id = user_id; + + RETURN true; +END; +$$; + +-- Function to set username (for new users during setup) +CREATE OR REPLACE FUNCTION set_username( + user_id UUID, + new_username TEXT +) +RETURNS BOOLEAN +LANGUAGE plpgsql +SECURITY DEFINER +AS $$ +DECLARE + profile_record RECORD; + username_exists BOOLEAN; +BEGIN + -- Validate input + IF user_id IS NULL OR new_username IS NULL THEN + RAISE EXCEPTION 'User ID and username are required'; + END IF; + + -- Check if username is valid format + IF NOT (new_username ~ '^[a-zA-Z0-9_-]+$') THEN + RAISE EXCEPTION 'Username can only contain letters, numbers, underscores, and hyphens'; + END IF; + + -- Check username length + IF LENGTH(new_username) < 3 OR LENGTH(new_username) > 30 THEN + RAISE EXCEPTION 'Username must be between 3 and 30 characters'; + END IF; + + -- Check if username is already taken (case-insensitive) + SELECT EXISTS( + SELECT 1 FROM profiles + WHERE LOWER(username) = LOWER(new_username) + ) INTO username_exists; + + IF username_exists THEN + RAISE EXCEPTION 'Username is already taken'; + END IF; + + -- Insert or update the profile + INSERT INTO profiles ( + id, username, username_editable, username_set, + profile_complete, setup_completed_at, updated_at + ) + VALUES ( + user_id, new_username, false, true, + true, NOW(), NOW() + ) + ON CONFLICT (id) + DO UPDATE SET + username = EXCLUDED.username, + username_editable = EXCLUDED.username_editable, + username_set = EXCLUDED.username_set, + profile_complete = EXCLUDED.profile_complete, + setup_completed_at = EXCLUDED.setup_completed_at, + updated_at = EXCLUDED.updated_at; + + RETURN true; +END; +$$; + +-- Function to generate a safe random username +CREATE OR REPLACE FUNCTION generate_safe_username() +RETURNS TEXT +LANGUAGE plpgsql +AS $$ +DECLARE + random_username TEXT; + username_exists BOOLEAN; + attempts INTEGER := 0; + max_attempts INTEGER := 10; +BEGIN + LOOP + -- Generate a random username + random_username := 'user' || LPAD(FLOOR(RANDOM() * 1000000)::TEXT, 6, '0'); + + -- Check if it exists + SELECT EXISTS( + SELECT 1 FROM profiles + WHERE LOWER(username) = LOWER(random_username) + ) INTO username_exists; + + -- If it doesn't exist, return it + IF NOT username_exists THEN + RETURN random_username; + END IF; + + -- Increment attempts + attempts := attempts + 1; + + -- If we've tried too many times, raise an exception + IF attempts >= max_attempts THEN + RAISE EXCEPTION 'Unable to generate unique username after % attempts', max_attempts; + END IF; + END LOOP; +END; +$$; + +-- Function to mark email as confirmed +CREATE OR REPLACE FUNCTION mark_email_confirmed(user_id UUID) +RETURNS BOOLEAN +LANGUAGE plpgsql +SECURITY DEFINER +AS $$ +BEGIN + UPDATE profiles + SET + email_confirmed_at = NOW(), + updated_at = NOW() + WHERE id = user_id; + + RETURN true; +END; +$$; + +-- Function to create email profile +CREATE OR REPLACE FUNCTION create_email_profile( + user_id UUID, + user_email TEXT, + user_metadata JSONB +) +RETURNS BOOLEAN +LANGUAGE plpgsql +SECURITY DEFINER +AS $$ +BEGIN + INSERT INTO profiles ( + id, email, first_name, last_name, + is_public, email_notifications, profile_completion_percentage, + username_editable, username_set, profile_complete, is_admin + ) + VALUES ( + user_id, + user_email, + COALESCE(user_metadata->>'first_name', user_metadata->>'given_name', ''), + COALESCE(user_metadata->>'last_name', user_metadata->>'family_name', ''), + true, true, 0, + true, false, false, false + ) + ON CONFLICT (id) DO NOTHING; + + RETURN true; +END; +$$; + +-- ===================================================== +-- STEP 5: ENABLE ROW LEVEL SECURITY +-- ===================================================== + +-- Enable RLS on profiles table +ALTER TABLE profiles ENABLE ROW LEVEL SECURITY; + +-- Policy: Users can view their own profile and public profiles +CREATE POLICY "Users can view own profile and public profiles" ON profiles + FOR SELECT USING ( + auth.uid() = id OR is_public = true + ); + +-- Policy: Users can update their own profile +CREATE POLICY "Users can update own profile" ON profiles + FOR UPDATE USING (auth.uid() = id); + +-- Policy: Users can insert their own profile +CREATE POLICY "Users can insert own profile" ON profiles + FOR INSERT WITH CHECK (auth.uid() = id); + +-- ===================================================== +-- STEP 6: GRANT PERMISSIONS +-- ===================================================== + +-- Grant necessary permissions +GRANT SELECT, INSERT, UPDATE ON profiles TO authenticated; +GRANT USAGE ON SCHEMA public TO authenticated; + +-- ===================================================== +-- MIGRATION COMPLETE +-- ===================================================== + +-- Add a comment to track this migration +COMMENT ON TABLE profiles IS 'User profiles table created by migration 20241201000003'; diff --git a/test-fixed-function.sql b/test-fixed-function.sql new file mode 100644 index 00000000..d25275a8 --- /dev/null +++ b/test-fixed-function.sql @@ -0,0 +1,26 @@ +-- ===================================================== +-- TEST THE FIXED FUNCTION +-- ===================================================== +-- Copy and paste this query to test the fixed function + +-- 1. Test the function with a dummy call (this should work now) +SELECT + 'Function Test' as test_type, + update_username(auth.uid(), 'test_username_456') as result, + 'Should return true or false, not error' as expected; + +-- 2. Check if the function definition is correct now +SELECT + 'Function Definition' as test_type, + proname as function_name, + pg_get_function_identity_arguments(oid) as arguments, + 'Function should reference public.profiles' as expected +FROM pg_proc +WHERE proname = 'update_username'; + +-- 3. Verify we can still access the profiles table +SELECT + 'Table Access' as test_type, + COUNT(*) as row_count, + 'Should show number of profiles' as expected +FROM public.profiles; diff --git a/test-username-function.sql b/test-username-function.sql new file mode 100644 index 00000000..e708ef8c --- /dev/null +++ b/test-username-function.sql @@ -0,0 +1,37 @@ +-- ===================================================== +-- TEST USERNAME FUNCTION +-- ===================================================== +-- Copy and paste this query to test the update_username function + +-- 1. First, let's see the current user and their profile +SELECT + 'Current User' as test_type, + auth.uid() as user_id, + 'User ID from auth' as info; + +-- 2. Check if current user has a profile +SELECT + 'Profile Check' as test_type, + id, + username, + username_editable, + username_set, + first_name, + last_name +FROM profiles +WHERE id = auth.uid(); + +-- 3. Test the update_username function with a dummy call +-- (This will show us the exact error if any) +SELECT + 'Function Test' as test_type, + update_username(auth.uid(), 'test_username_123') as result; + +-- 4. Check function definition +SELECT + 'Function Definition' as test_type, + proname as function_name, + pg_get_function_identity_arguments(oid) as arguments, + prosrc as function_source +FROM pg_proc +WHERE proname = 'update_username'; From 886c3205d9e2f26cbab9d7119dd53daa50ccb036 Mon Sep 17 00:00:00 2001 From: Deepak Pandey Date: Fri, 12 Sep 2025 14:43:21 +0530 Subject: [PATCH 2/2] feat: comprehensive updates - events system, database fixes, and admin improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🚀 Major Features Added: - Complete events management system with registration and payment - Master registration system for unified user tracking - Enhanced admin dashboard with events management - Improved user profile and dashboard functionality 🐛 Database Fixes: - Fixed username update function schema references (public.profiles) - Resolved 'relation profiles does not exist' error - Added comprehensive database migration and diagnostic tools 📁 New API Endpoints: - /api/events/* - Complete events CRUD operations - /api/events/[slug]/register - Event registration with payment - /api/events/[slug]/payment - Payment processing for events - /api/registrations/* - Master registration system - /api/user/events/* - User-specific event data - /api/register - User registration endpoint 🎨 New Pages & Components: - /events - Public events listing page - /events/[slug] - Individual event pages with registration - /protected/events - User's registered events - /protected/hackathons - Enhanced hackathons page - Enhanced admin events management interface 🔧 Services & Hooks: - useEvents - Events data management hook - useEventRegistrations - Event registration management - useMasterRegistrations - Master registration system - events.ts - Events service layer - master-registrations.ts - Registration service layer 📊 Admin Improvements: - Enhanced events management in admin panel - Improved dashboard content and user experience - Better webhook handling for payments 🛠️ Technical Improvements: - TypeScript types for events system - Improved error handling and validation - Better database schema and function management - Enhanced security and permissions This comprehensive update includes all recent development work including the critical database function fix for username updates. --- app/admin/events/page.tsx | 416 +++++++++++- app/api/admin/events/route.ts | 242 ++++--- app/api/events/[slug]/payment/route.ts | 365 ++++++++++ app/api/events/[slug]/register/route.ts | 169 +++++ app/api/events/[slug]/route.ts | 50 ++ app/api/events/featured/route.ts | 31 + app/api/events/route.ts | 158 +++++ app/api/register/route.ts | 87 +++ .../[activityType]/[activityId]/route.ts | 116 ++++ app/api/registrations/route.ts | 115 ++++ app/api/user/events/route.ts | 85 +++ app/api/user/registrations/route.ts | 158 +++++ app/api/webhooks/razorpay/route.ts | 62 +- app/events/[slug]/page.tsx | 639 ++++++++++++++++++ app/events/[slug]/register/page.tsx | 455 +++++++++++++ app/events/page.tsx | 542 +++++++++++++++ app/protected/events/page.tsx | 286 ++++++++ app/protected/hackathons/page.tsx | 349 ++++++++++ components/dashboard/DashboardContent.tsx | 32 +- hooks/useEventRegistrations.ts | 126 ++++ hooks/useEvents.ts | 146 ++++ hooks/useMasterRegistrations.ts | 268 ++++++++ lib/services/events.ts | 214 ++++++ lib/services/master-registrations.ts | 317 +++++++++ types/events.ts | 68 ++ 25 files changed, 5405 insertions(+), 91 deletions(-) create mode 100644 app/api/events/[slug]/payment/route.ts create mode 100644 app/api/events/[slug]/register/route.ts create mode 100644 app/api/events/[slug]/route.ts create mode 100644 app/api/events/featured/route.ts create mode 100644 app/api/events/route.ts create mode 100644 app/api/register/route.ts create mode 100644 app/api/registrations/[activityType]/[activityId]/route.ts create mode 100644 app/api/registrations/route.ts create mode 100644 app/api/user/events/route.ts create mode 100644 app/api/user/registrations/route.ts create mode 100644 app/events/[slug]/page.tsx create mode 100644 app/events/[slug]/register/page.tsx create mode 100644 app/events/page.tsx create mode 100644 app/protected/events/page.tsx create mode 100644 app/protected/hackathons/page.tsx create mode 100644 hooks/useEventRegistrations.ts create mode 100644 hooks/useEvents.ts create mode 100644 hooks/useMasterRegistrations.ts create mode 100644 lib/services/events.ts create mode 100644 lib/services/master-registrations.ts create mode 100644 types/events.ts diff --git a/app/admin/events/page.tsx b/app/admin/events/page.tsx index 6c8ae20f..5b911f89 100644 --- a/app/admin/events/page.tsx +++ b/app/admin/events/page.tsx @@ -6,26 +6,59 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { Input } from "@/components/ui/input" -import { Search, Plus, Calendar, MapPin } from "lucide-react" +import { Search, Plus, Calendar, MapPin, Edit, Trash2 } from "lucide-react" import { toast } from "sonner" import { apiFetch } from "@/lib/api-fetch" +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog" +import { Label } from "@/components/ui/label" +import { Textarea } from "@/components/ui/textarea" +import { Switch } from "@/components/ui/switch" interface Event { id: number slug: string title: string excerpt: string + description: string organizer: string date: string + time: string + duration: string + category: string location: string + capacity: number + price: string + payment: string status: string featured: boolean + registration_required: boolean } export default function AdminEvents() { const [events, setEvents] = useState([]) const [loading, setLoading] = useState(true) const [searchTerm, setSearchTerm] = useState("") + const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false) + const [editingEvent, setEditingEvent] = useState(null) + const [formData, setFormData] = useState({ + title: "", + slug: "", + excerpt: "", + description: "", + organizer: "", + date: "", + time: "", + duration: "", + category: "", + location: "", + capacity: 0, + price: "Free", + payment: "Not Required", + status: "draft", + featured: false, + registration_required: true + }) + const [formLoading, setFormLoading] = useState(false) const fetchEvents = async () => { try { @@ -50,7 +83,7 @@ export default function AdminEvents() { return } const data = await response.json() - setEvents(data.events || []) + setEvents(data || []) } catch (error) { toast.error("Failed to fetch events") console.error('Fetch error:', error) @@ -76,6 +109,135 @@ export default function AdminEvents() { featured: events.filter(e => e.featured).length } + const handleCreateEvent = async () => { + try { + setFormLoading(true) + + // Generate slug from title if not provided + const slug = formData.slug || formData.title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') + + const eventData = { + ...formData, + slug, + categories: [formData.category], + tags: [], + locations: [formData.location], + registered: 0, + event_type: ["Offline"], + team_size: { min: 1, max: 1 }, + user_types: ["Professionals", "College Students"], + rules: [], + schedule: {}, + faq: {}, + socials: {}, + sponsors: [] + } + + let response + if (editingEvent) { + // Update existing event + response = await apiFetch('/api/admin/events', { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + slug: editingEvent.slug, + data: eventData + }) + }) + } else { + // Create new event + response = await apiFetch('/api/admin/events', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(eventData) + }) + } + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + const errorMessage = errorData.error || (editingEvent ? 'Failed to update event' : 'Failed to create event') + throw new Error(errorMessage) + } + + toast.success(editingEvent ? 'Event updated successfully!' : 'Event created successfully!') + setIsCreateDialogOpen(false) + setEditingEvent(null) + setFormData({ + title: "", + slug: "", + excerpt: "", + description: "", + organizer: "", + date: "", + time: "", + duration: "", + category: "", + location: "", + capacity: 0, + price: "Free", + payment: "Not Required", + status: "draft", + featured: false, + registration_required: true + }) + fetchEvents() + } catch (error) { + toast.error(editingEvent ? 'Failed to update event' : 'Failed to create event') + console.error('Create/Update event error:', error) + } finally { + setFormLoading(false) + } + } + + const handleEditEvent = (event: Event) => { + setEditingEvent(event) + setFormData({ + title: event.title, + slug: event.slug, + excerpt: event.excerpt, + description: event.description, + organizer: event.organizer, + date: event.date, + time: event.time, + duration: event.duration, + category: event.category, + location: event.location, + capacity: event.capacity, + price: event.price, + payment: event.payment, + status: event.status, + featured: event.featured, + registration_required: event.registration_required + }) + setIsCreateDialogOpen(true) + } + + const handleDeleteEvent = async (slug: string) => { + if (!confirm('Are you sure you want to delete this event?')) { + return + } + + try { + const response = await apiFetch(`/api/admin/events?slug=${slug}`, { + method: 'DELETE' + }) + + if (!response.ok) { + throw new Error('Failed to delete event') + } + + toast.success('Event deleted successfully!') + fetchEvents() + } catch (error) { + toast.error('Failed to delete event') + console.error('Delete event error:', error) + } + } + return (
{/* Header */} @@ -95,7 +257,7 @@ export default function AdminEvents() { - @@ -171,6 +333,7 @@ export default function AdminEvents() { Location Status Featured + Actions @@ -207,6 +370,24 @@ export default function AdminEvents() { )} + +
+ + +
+
))}
@@ -214,6 +395,235 @@ export default function AdminEvents() { )} + + {/* Create Event Dialog */} + { + setIsCreateDialogOpen(open) + if (!open) { + setEditingEvent(null) + setFormData({ + title: "", + slug: "", + excerpt: "", + description: "", + organizer: "", + date: "", + time: "", + duration: "", + category: "", + location: "", + capacity: 0, + price: "Free", + payment: "Not Required", + status: "draft", + featured: false, + registration_required: true + }) + } + }}> + + + {editingEvent ? 'Edit Event' : 'Create New Event'} + + +
+
+
+ + setFormData({...formData, title: e.target.value})} + placeholder="Enter event title" + /> +
+
+ + setFormData({...formData, slug: e.target.value})} + placeholder="auto-generated from title" + /> +
+
+ +
+ +