diff --git a/scripts/clear-cache.sh b/scripts/clear-cache.sh new file mode 100755 index 0000000..a7a402a --- /dev/null +++ b/scripts/clear-cache.sh @@ -0,0 +1,88 @@ +#!/bin/bash + +# Clear Cache Script for BCT Development +# This script clears all types of caches that might interfere with development + +echo "๐Ÿงน Starting comprehensive cache clearing..." + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to print colored output +print_step() { + echo -e "${BLUE}$1${NC}" +} + +print_success() { + echo -e "${GREEN}โœ… $1${NC}" +} + +print_warning() { + echo -e "${YELLOW}โš ๏ธ $1${NC}" +} + +print_error() { + echo -e "${RED}โŒ $1${NC}" +} + +# 1. Clear Node.js module cache +print_step "1. Clearing Node.js module cache..." +rm -rf node_modules/.cache 2>/dev/null || true +print_success "Node.js cache cleared" + +# 2. Clear npm cache +print_step "2. Clearing npm cache..." +npm cache clean --force 2>/dev/null || true +print_success "npm cache cleared" + +# 3. Clear Astro build cache +print_step "3. Clearing Astro build cache..." +rm -rf .astro 2>/dev/null || true +rm -rf dist 2>/dev/null || true +print_success "Astro cache cleared" + +# 4. Clear Docker build cache +print_step "4. Clearing Docker build cache..." +docker builder prune -a -f 2>/dev/null || print_warning "Docker not available or permission denied" +print_success "Docker build cache cleared" + +# 5. Clear Docker containers and images +print_step "5. Stopping and removing Docker containers..." +docker-compose down 2>/dev/null || true +docker system prune -a -f 2>/dev/null || print_warning "Docker system prune failed" +print_success "Docker containers and images cleared" + +# 6. Clear any temporary files +print_step "6. Clearing temporary files..." +rm -rf tmp/ 2>/dev/null || true +rm -rf .tmp/ 2>/dev/null || true +rm -rf logs/*.log 2>/dev/null || true +print_success "Temporary files cleared" + +# 7. Clear Vite cache +print_step "7. Clearing Vite cache..." +rm -rf node_modules/.vite 2>/dev/null || true +print_success "Vite cache cleared" + +# 8. Clear TypeScript cache +print_step "8. Clearing TypeScript cache..." +rm -rf node_modules/.cache/typescript 2>/dev/null || true +print_success "TypeScript cache cleared" + +echo "" +print_success "๐ŸŽ‰ All caches cleared successfully!" +echo "" +echo -e "${YELLOW}Next steps:${NC}" +echo "1. Hard refresh your browser (Ctrl+Shift+R or Cmd+Shift+R)" +echo "2. Open browser dev tools and disable cache (Network tab)" +echo "3. Run: npm run docker:build --no-cache" +echo "4. Run: npm run docker:up" +echo "" +echo -e "${BLUE}For persistent cache issues, also try:${NC}" +echo "โ€ข Clear browser data/cookies for localhost:3000" +echo "โ€ข Use incognito/private browsing mode" +echo "โ€ข Add ?v=\$(date +%s) to URLs for cache busting" \ No newline at end of file diff --git a/src/pages/api/admin/activity.ts b/src/pages/api/admin/activity.ts new file mode 100644 index 0000000..ea42fc6 --- /dev/null +++ b/src/pages/api/admin/activity.ts @@ -0,0 +1,93 @@ +import type { APIRoute } from 'astro'; +import { createSupabaseServerClient } from '../../../lib/supabase-ssr'; +import { createSupabaseAdmin } from '../../../lib/supabase-admin'; + +export const GET: APIRoute = async ({ request, cookies }) => { + try { + // Check authentication + const supabase = createSupabaseServerClient(cookies); + const { data: { session }, error: sessionError } = await supabase.auth.getSession(); + + if (sessionError || !session) { + return new Response(JSON.stringify({ + success: false, + error: 'Authentication required' + }), { + status: 401, + headers: { 'Content-Type': 'application/json' } + }); + } + + // Use admin client to bypass RLS + const serviceClient = createSupabaseAdmin(); + + // Get recent activity data + const [eventsResult, ticketsResult, usersResult] = await Promise.all([ + serviceClient.from('events').select('*, organizations(name)').order('created_at', { ascending: false }).limit(5), + serviceClient.from('tickets').select('*, events(title)').order('created_at', { ascending: false }).limit(10), + serviceClient.from('users').select('*, organizations(name)').order('created_at', { ascending: false }).limit(5) + ]); + + const activities = []; + + // Add recent events + if (eventsResult.data) { + eventsResult.data.forEach(event => { + activities.push({ + type: 'event', + title: `New event created: ${event.title}`, + subtitle: `by ${event.organizations?.name || 'Unknown'}`, + time: new Date(event.created_at), + icon: '๐Ÿ“…' + }); + }); + } + + // Add recent users + if (usersResult.data) { + usersResult.data.forEach(user => { + activities.push({ + type: 'user', + title: `New user registered: ${user.name || user.email}`, + subtitle: `Organization: ${user.organizations?.name || 'None'}`, + time: new Date(user.created_at), + icon: '๐Ÿ‘ค' + }); + }); + } + + // Add recent tickets + if (ticketsResult.data) { + ticketsResult.data.slice(0, 5).forEach(ticket => { + activities.push({ + type: 'ticket', + title: `Ticket sold: $${ticket.price}`, + subtitle: `for ${ticket.events?.title || 'Unknown Event'}`, + time: new Date(ticket.created_at), + icon: '๐ŸŽซ' + }); + }); + } + + // Sort by time and take the most recent 10 + activities.sort((a, b) => b.time - a.time); + const recentActivities = activities.slice(0, 10); + + return new Response(JSON.stringify({ + success: true, + data: recentActivities + }), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }); + } catch (error) { + console.error('Admin activity error:', error); + return new Response(JSON.stringify({ + success: false, + error: 'Failed to load recent activity' + }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }); + } +}; \ No newline at end of file diff --git a/src/pages/api/admin/admin-events.ts b/src/pages/api/admin/admin-events.ts new file mode 100644 index 0000000..308f653 --- /dev/null +++ b/src/pages/api/admin/admin-events.ts @@ -0,0 +1,73 @@ +import type { APIRoute } from 'astro'; +import { createSupabaseServerClient } from '../../../lib/supabase-ssr'; +import { createSupabaseAdmin } from '../../../lib/supabase-admin'; + +export const GET: APIRoute = async ({ request, cookies }) => { + try { + // Check authentication + const supabase = createSupabaseServerClient(cookies); + const { data: { session }, error: sessionError } = await supabase.auth.getSession(); + + if (sessionError || !session) { + return new Response(JSON.stringify({ + success: false, + error: 'Authentication required' + }), { + status: 401, + headers: { 'Content-Type': 'application/json' } + }); + } + + // Use admin client to bypass RLS + const serviceClient = createSupabaseAdmin(); + + // Get events with organization and user details + const { data: events, error } = await serviceClient + .from('events') + .select(` + *, + organizations(name), + users!events_created_by_fkey(name, email) + `) + .order('created_at', { ascending: false }); + + if (error) { + console.error('Events query error:', error); + return new Response(JSON.stringify({ + success: false, + error: 'Failed to fetch events' + }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }); + } + + // Get ticket type counts for each event + if (events) { + for (const event of events) { + const { data: ticketTypes } = await serviceClient + .from('ticket_types') + .select('id') + .eq('event_id', event.id); + event.ticket_type_count = ticketTypes ? ticketTypes.length : 0; + } + } + + return new Response(JSON.stringify({ + success: true, + data: events || [] + }), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }); + } catch (error) { + console.error('Admin events error:', error); + return new Response(JSON.stringify({ + success: false, + error: 'Failed to load events' + }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }); + } +}; \ No newline at end of file diff --git a/src/pages/api/admin/admin-tickets.ts b/src/pages/api/admin/admin-tickets.ts new file mode 100644 index 0000000..a2d16ba --- /dev/null +++ b/src/pages/api/admin/admin-tickets.ts @@ -0,0 +1,73 @@ +import type { APIRoute } from 'astro'; +import { createSupabaseServerClient } from '../../../lib/supabase-ssr'; +import { createSupabaseAdmin } from '../../../lib/supabase-admin'; + +export const GET: APIRoute = async ({ request, cookies }) => { + try { + // Check authentication + const supabase = createSupabaseServerClient(cookies); + const { data: { session }, error: sessionError } = await supabase.auth.getSession(); + + if (sessionError || !session) { + return new Response(JSON.stringify({ + success: false, + error: 'Authentication required' + }), { + status: 401, + headers: { 'Content-Type': 'application/json' } + }); + } + + // Use admin client to bypass RLS + const serviceClient = createSupabaseAdmin(); + + // Get tickets with event and organization details + const { data: tickets, error } = await serviceClient + .from('tickets') + .select(` + *, + ticket_types ( + name, + price + ), + events ( + title, + venue, + start_time, + organizations ( + name + ) + ) + `) + .order('created_at', { ascending: false }) + .limit(100); + + if (error) { + console.error('Tickets query error:', error); + return new Response(JSON.stringify({ + success: false, + error: 'Failed to fetch tickets' + }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }); + } + + return new Response(JSON.stringify({ + success: true, + data: tickets || [] + }), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }); + } catch (error) { + console.error('Admin tickets error:', error); + return new Response(JSON.stringify({ + success: false, + error: 'Failed to load tickets' + }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }); + } +}; \ No newline at end of file diff --git a/src/pages/api/admin/organizations.ts b/src/pages/api/admin/organizations.ts new file mode 100644 index 0000000..5c18e5e --- /dev/null +++ b/src/pages/api/admin/organizations.ts @@ -0,0 +1,69 @@ +import type { APIRoute } from 'astro'; +import { createSupabaseServerClient } from '../../../lib/supabase-ssr'; +import { createSupabaseAdmin } from '../../../lib/supabase-admin'; + +export const GET: APIRoute = async ({ request, cookies }) => { + try { + // Check authentication + const supabase = createSupabaseServerClient(cookies); + const { data: { session }, error: sessionError } = await supabase.auth.getSession(); + + if (sessionError || !session) { + return new Response(JSON.stringify({ + success: false, + error: 'Authentication required' + }), { + status: 401, + headers: { 'Content-Type': 'application/json' } + }); + } + + // Use admin client to bypass RLS + const serviceClient = createSupabaseAdmin(); + + // Get organizations + const { data: orgs, error } = await serviceClient + .from('organizations') + .select('*') + .order('created_at', { ascending: false }); + + if (error) { + console.error('Organizations query error:', error); + return new Response(JSON.stringify({ + success: false, + error: 'Failed to fetch organizations' + }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }); + } + + // Get user counts for each organization + if (orgs) { + for (const org of orgs) { + const { data: users } = await serviceClient + .from('users') + .select('id') + .eq('organization_id', org.id); + org.user_count = users ? users.length : 0; + } + } + + return new Response(JSON.stringify({ + success: true, + data: orgs || [] + }), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }); + } catch (error) { + console.error('Admin organizations error:', error); + return new Response(JSON.stringify({ + success: false, + error: 'Failed to load organizations' + }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }); + } +}; \ No newline at end of file diff --git a/src/pages/api/admin/stats.ts b/src/pages/api/admin/stats.ts new file mode 100644 index 0000000..15ed870 --- /dev/null +++ b/src/pages/api/admin/stats.ts @@ -0,0 +1,64 @@ +import type { APIRoute } from 'astro'; +import { createSupabaseServerClient } from '../../../lib/supabase-ssr'; +import { createSupabaseAdmin } from '../../../lib/supabase-admin'; + +export const GET: APIRoute = async ({ request, cookies }) => { + try { + // Check authentication + const supabase = createSupabaseServerClient(cookies); + const { data: { session }, error: sessionError } = await supabase.auth.getSession(); + + if (sessionError || !session) { + return new Response(JSON.stringify({ + success: false, + error: 'Authentication required' + }), { + status: 401, + headers: { 'Content-Type': 'application/json' } + }); + } + + // Use admin client to bypass RLS + const serviceClient = createSupabaseAdmin(); + + // Get platform statistics + const [organizationsResult, eventsResult, ticketsResult, usersResult] = await Promise.all([ + serviceClient.from('organizations').select('id'), + serviceClient.from('events').select('id'), + serviceClient.from('tickets').select('price'), + serviceClient.from('users').select('id') + ]); + + const organizations = organizationsResult.data?.length || 0; + const events = eventsResult.data?.length || 0; + const tickets = ticketsResult.data || []; + const users = usersResult.data?.length || 0; + const ticketCount = tickets.length; + const revenue = tickets.reduce((sum, ticket) => sum + (ticket.price || 0), 0); + const platformFees = revenue * 0.05; // Assuming 5% platform fee + + return new Response(JSON.stringify({ + success: true, + data: { + organizations, + events, + tickets: ticketCount, + revenue, + platformFees, + users + } + }), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }); + } catch (error) { + console.error('Admin stats error:', error); + return new Response(JSON.stringify({ + success: false, + error: 'Failed to load platform statistics' + }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }); + } +}; \ No newline at end of file diff --git a/src/pages/api/user/events.ts b/src/pages/api/user/events.ts new file mode 100644 index 0000000..3117870 --- /dev/null +++ b/src/pages/api/user/events.ts @@ -0,0 +1,89 @@ +import type { APIRoute } from 'astro'; +import { verifyAuth } from '../../../lib/auth'; +import { createSupabaseServerClient } from '../../../lib/supabase-ssr'; + +export const GET: APIRoute = async ({ cookies }) => { + try { + // Verify authentication using server-side auth + const auth = await verifyAuth(cookies); + + if (!auth) { + return new Response(JSON.stringify({ + error: 'Not authenticated' + }), { + status: 401, + headers: { 'Content-Type': 'application/json' } + }); + } + + // Create Supabase client for server-side queries + const supabase = createSupabaseServerClient(cookies); + + // Get user profile to check if they're admin + const { data: userProfile, error: profileError } = await supabase + .from('users') + .select('role, organization_id') + .eq('id', auth.user.id) + .single(); + + if (profileError) { + console.error('Error loading user profile:', profileError); + return new Response(JSON.stringify({ + error: 'Error loading user profile' + }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }); + } + + // Check if user is admin or has organization_id + const isAdmin = userProfile?.role === 'admin'; + if (!isAdmin && !userProfile?.organization_id) { + return new Response(JSON.stringify({ + success: true, + data: [] + }), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }); + } + + // Load events based on user permissions + let query = supabase.from('events').select('*'); + + if (!isAdmin) { + // Regular users see only their organization's events + query = query.eq('organization_id', userProfile.organization_id); + } + // Admins see all events (no filter) + + const { data: events, error } = await query.order('created_at', { ascending: false }); + + if (error) { + console.error('Error loading events:', error); + return new Response(JSON.stringify({ + error: 'Error loading events' + }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }); + } + + return new Response(JSON.stringify({ + success: true, + data: events || [] + }), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }); + + } catch (error) { + console.error('User events API error:', error); + return new Response(JSON.stringify({ + error: 'Internal server error' + }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }); + } +}; \ No newline at end of file diff --git a/src/pages/calendar.astro b/src/pages/calendar.astro index 1d9cd65..b632d05 100644 --- a/src/pages/calendar.astro +++ b/src/pages/calendar.astro @@ -23,7 +23,7 @@ const search = url.searchParams.get('search'); --- -
+
@@ -406,6 +406,9 @@ const search = url.searchParams.get('search');