Files
Event-Management-System/RIT-EVENT-MANAGEMENT--main/App.tsx

826 lines
33 KiB
TypeScript

import React, { useState, useEffect, useCallback } from 'react';
import WelcomeScreen from './components/WelcomeScreen';
import LoginForm from './components/LoginForm';
import Dashboard from './components/Dashboard';
import AdminLandingPage from './components/AdminLandingPage';
import FacultyDashboard from './components/FacultyDashboard';
import TicketVerificationView from './components/TicketVerificationView';
import { AppState, UserRole, Event, EventSchedule, Announcement, Ticket } from './types';
import { supabase } from './supabase';
const App: React.FC = () => {
const [appState, setAppState] = useState<AppState>('WELCOME');
const [userRole, setUserRole] = useState<UserRole | null>(null);
const [events, setEvents] = useState<Event[]>([]);
const [announcements, setAnnouncements] = useState<Announcement[]>([]);
const [specialEvents, setSpecialEvents] = useState<any[]>([]);
const [domains, setDomains] = useState<any[]>([]);
const [userRegistrations, setUserRegistrations] = useState<any[]>([]);
const [allRegistrations, setAllRegistrations] = useState<any[]>([]);
const [bookedEventIds, setBookedEventIds] = useState<string[]>([]);
const [currentUserName, setCurrentUserName] = useState<string>('User');
const [currentUserDept, setCurrentUserDept] = useState<string>('');
const [currentUserEmail, setCurrentUserEmail] = useState<string>('');
const [currentUserPhone, setCurrentUserPhone] = useState<string>('');
const [currentUserPhoto, setCurrentUserPhoto] = useState<string>('');
const [currentUserId, setCurrentUserId] = useState<string>('');
const [currentUserFacultyRole, setCurrentUserFacultyRole] = useState<string>('');
const [ticketToVerify, setTicketToVerify] = useState<{ ticket: Ticket, event: Event } | null>(null);
const [isAuthenticating, setIsAuthenticating] = useState(false);
const [globalError, setGlobalError] = useState<string | null>(null);
const [studentDocuments, setStudentDocuments] = useState<Record<string, string>>({});
const [showStudentHubForAdmin, setShowStudentHubForAdmin] = useState(false);
const [intendedRole, setIntendedRole] = useState<UserRole | null>(null);
const mapDbToEvent = useCallback((dbEvent: any, registrations: any[] = []): Event => {
const eventRegs = registrations.filter(r => String(r.event_id) === String(dbEvent.id));
const deptCounts: Record<string, number> = {};
const deptSectionCounts: Record<string, Record<string, number>> = {};
eventRegs.forEach(reg => {
if (reg.dept) {
deptCounts[reg.dept] = (deptCounts[reg.dept] || 0) + 1;
if (reg.section) {
if (!deptSectionCounts[reg.dept]) {
deptSectionCounts[reg.dept] = {};
}
deptSectionCounts[reg.dept][reg.section] = (deptSectionCounts[reg.dept][reg.section] || 0) + 1;
}
}
});
return {
id: String(dbEvent.id),
title: dbEvent.title,
location: dbEvent.location,
date: dbEvent.date,
category: dbEvent.category,
domain: dbEvent.domain,
pricingType: dbEvent.pricing_type,
coordinator: dbEvent.coordinator,
image: dbEvent.image,
status: dbEvent.status,
maxParticipants: dbEvent.max_participants,
registrationDeadline: dbEvent.registration_deadline,
durationDays: dbEvent.duration_days,
club: dbEvent.club,
deptLimits: dbEvent.deptLimits || {},
deptSectionLimits: dbEvent.deptSectionLimits || {},
event_summary: dbEvent.event_summary,
isTeamEvent: dbEvent.is_team_event,
teamSizeLimit: dbEvent.team_size_limit,
teamComposition: dbEvent.team_composition,
currentParticipants: eventRegs.length,
currentDeptCounts: deptCounts,
currentDeptSectionCounts: deptSectionCounts,
created_by: dbEvent.created_by,
participantType: dbEvent.participant_type as any,
verificationStatus: dbEvent.verification_status as any,
refreshment_expense: dbEvent.refreshment_expense ?? 0,
transportation_expense: dbEvent.transportation_expense ?? 0,
session_coverage_fee: dbEvent.session_coverage_fee ?? 0,
total_expense: dbEvent.total_expense ?? 0,
conducting_dept: dbEvent.conducting_dept ?? null,
request_by_faculty: dbEvent.request_by_faculty ?? null,
request_by_HOD: dbEvent.request_by_hod ?? null,
};
}, []);
const fetchAnnouncements = useCallback(async () => {
try {
const { data, error } = await supabase
.from('announcements')
.select('*')
.order('timestamp', { ascending: false });
if (data) {
setAnnouncements(data.map(ann => ({
id: String(ann.id),
title: ann.title,
message: ann.message,
type: ann.type,
eventId: ann.event_id ? String(ann.event_id) : undefined,
timestamp: ann.timestamp || new Date().toISOString(),
expiresAt: ann.expires_at
})));
}
} catch (err) {
console.error("Announcement Sync Error:", err);
}
}, []);
const fetchSpecialEvents = useCallback(async () => {
try {
let query = supabase.from('special_events').select('*').eq('is_active', true);
// SECURITY: Public users and Students only see Approved special events
if (!userRole || userRole === 'STUDENT') {
query = query.eq('verification_status', 'APPROVED');
}
const { data } = await query.order('created_at', { ascending: false });
if (data) {
setSpecialEvents(data.map((se: any) => ({
...se,
verificationStatus: se.verification_status
})));
}
} catch (err) {
console.error("Special Events Sync Error:", err);
}
}, [userRole]);
const fetchDomains = useCallback(async () => {
try {
const { data } = await supabase.from('domains').select('*').order('name', { ascending: true });
if (data) setDomains(data);
} catch (err) {
console.error("Domains Sync Error:", err);
}
}, []);
const fetchEvents = useCallback(async (registrations: any[] = []) => {
const { data } = await supabase.from('events').select('*').order('date', { ascending: true });
if (data) {
// Fetch all schedule + resource_persons + dept limits in bulk
const { data: scheduleData } = await supabase.from('event_schedule').select('*').order('day_idx', { ascending: true }).order('batch_idx', { ascending: true });
const { data: rpData } = await supabase.from('resource_persons').select('*');
const { data: deptLimitsData } = await supabase.from('event_dept_limits').select('*');
const mapped = data.map(e => {
const event = mapDbToEvent(e, registrations);
// Attach schedule rows for this event
const eventSchedule: EventSchedule[] = (scheduleData || []).filter(s => s.event_id === e.id).map(s => {
const rp = (rpData || []).find(r => r.schedule_id === s.id);
return {
...s,
resource_person: rp ? { id: rp.id, type: rp.type, name: rp.name, dept: rp.dept, college_name: rp.college_name, phone: rp.phone, email: rp.email } : undefined,
};
});
event.schedule = eventSchedule;
// Attach department limits for this event
const eventDeptLimits: Record<string, number> = {};
const eventDeptSectionLimits: Record<string, Record<string, number>> = {};
(deptLimitsData || []).filter(dl => dl.event_id === e.id).forEach(dl => {
eventDeptLimits[dl.department] = dl.max_seats;
eventDeptSectionLimits[dl.department] = dl.section_limits || {};
});
event.deptLimits = eventDeptLimits;
event.deptSectionLimits = eventDeptSectionLimits;
return event;
});
// SECURITY: Public users and Students only see Approved events
if (!userRole || userRole === 'STUDENT') {
setEvents(mapped.filter(e => e.verificationStatus === 'APPROVED'));
} else {
setEvents(mapped);
}
}
}, [mapDbToEvent, userRole]);
const fetchAllRegistrations = useCallback(async () => {
const { data } = await supabase.from('registrations').select('*').order('registered_at', { ascending: false });
if (data) {
setAllRegistrations(data);
fetchEvents(data);
}
}, [fetchEvents]);
const fetchMyRegs = useCallback(async (userId: string) => {
const { data } = await supabase.from('registrations').select('*').eq('user_id', userId);
if (data) {
setUserRegistrations(data);
setBookedEventIds(data.map((r: any) => String(r.event_id)));
}
}, []);
const handleAuthChange = useCallback(async (session: any) => {
if (!session?.user) {
setAppState(prev => {
if (prev !== 'WELCOME' && prev !== 'LOGIN') return 'WELCOME';
return prev;
});
setIsAuthenticating(false);
return;
}
setIsAuthenticating(true);
const user = session.user;
const HIGH_AUTH_ADMINS = [
'adrit1highauth@gmail.com',
'adrit2highauth@gmail.com',
'adrit3highauth@gmail.com',
'adrit4highauth@gmail.com',
'adrit5highauth@gmail.com'
];
try {
let profile = null;
let finalRole: UserRole | null = null;
// Check for fixed high-auth admins first
if (user.email && HIGH_AUTH_ADMINS.includes(user.email)) {
const { data: admin } = await supabase.from('Adminusers').select('*').eq('id', user.id).single();
if (admin) {
profile = admin;
} else {
// If they exist in auth but not in Adminusers yet, we create and insert a profile
const newProfile = {
id: user.id,
name: 'System Admin',
email: user.email,
dept: 'Administration',
updated_at: new Date().toISOString()
};
const { data: inserted, error: insertError } = await supabase.from('Adminusers').insert(newProfile).select().single();
if (!insertError) {
profile = inserted;
} else {
profile = newProfile; // Fallback to local object if insert fails
}
}
finalRole = 'ADMIN'; // Principal/Director is ADMIN
} else {
// Check Adminusers (Admin Hub / System Admins)
const { data: adminProfile } = await supabase.from('Adminusers').select('*').eq('id', user.id).single();
if (adminProfile) {
profile = adminProfile;
finalRole = 'ADMIN';
} else {
// Check Facultyusers (Event Coordinator Hub)
const { data: facultyProfile } = await supabase.from('Facultyusers').select('*').eq('id', user.id).single();
if (facultyProfile) {
profile = facultyProfile;
finalRole = 'COORDINATOR';
} else {
const { data: student } = await supabase.from('Studentusers').select('*').eq('id', user.id).single();
if (student) {
profile = student;
finalRole = 'STUDENT';
} else {
const { data: externalStudent } = await supabase.from('externalusers').select('*').eq('id', user.id).single();
if (externalStudent) {
profile = externalStudent;
finalRole = 'STUDENT';
} else {
// FALLBACK: Auto-sync profile from metadata
const meta = user.user_metadata;
// ... (rest of metadata sync logic)
if (meta && (meta.role === 'STUDENT' || meta.regNo)) {
const isExternal = meta.signUpType === 'EXTERNAL';
const table = isExternal ? 'externalusers' : 'Studentusers';
const syncData: any = {
id: user.id,
name: meta.name || user.email?.split('@')[0],
email: user.email,
reg_no: meta.regNo || meta.reg_no,
phone: meta.phone,
department: meta.department || meta.dept,
year: meta.year,
section: meta.section,
updated_at: new Date().toISOString()
};
if (isExternal) {
syncData.college = meta.collegeName || meta.college;
syncData.college_location = meta.collegeLocation;
syncData.gender = meta.gender;
}
const { data: synced, error: syncError } = await supabase
.from(table)
.upsert(syncData)
.select()
.single();
if (!syncError && synced) {
profile = synced;
finalRole = 'STUDENT';
} else {
console.warn("Auto-sync profile failed:", syncError?.message);
profile = syncData; // Use metadata-based object as semi-functional fallback
finalRole = 'STUDENT';
}
} else if (meta && (meta.role === 'ADMIN' || meta.role === 'COORDINATOR')) {
// If metadata says Admin/Coordinator but they weren't in tables, check HIGH_AUTH_ADMINS
if (user.email && HIGH_AUTH_ADMINS.includes(user.email)) {
finalRole = 'ADMIN';
} else {
finalRole = 'COORDINATOR';
}
// Try to find in Facultyusers or Adminusers as fallback
const { data: f } = await supabase.from('Facultyusers').select('*').eq('id', user.id).single();
if (f) profile = f;
else {
const { data: a } = await supabase.from('Adminusers').select('*').eq('id', user.id).single();
if (a) profile = a;
}
}
}
}
}
}
}
if (profile && finalRole) {
// PORTAL SECURITY GUARD: Ensure actual role matches the intended portal
if (intendedRole && finalRole !== intendedRole) {
// Exception: ADMINs can access COORDINATOR portal if needed, but not vice-versa
const isPermittedOverload = (intendedRole === 'COORDINATOR' && finalRole === 'ADMIN');
if (!isPermittedOverload) {
setGlobalError(`Access Denied: Your account (${finalRole}) is not authorized for the ${intendedRole} Portal.`);
await supabase.auth.signOut();
setIsAuthenticating(false);
return;
}
}
setCurrentUserName(profile.name || 'User');
setCurrentUserDept(profile.department || profile.dept || '');
setCurrentUserEmail(profile.email || user.email || '');
setCurrentUserPhone(profile.phone || '');
setCurrentUserPhoto(profile.profile_photo || '');
setCurrentUserId(user.id);
setCurrentUserFacultyRole(profile.role || '');
setUserRole(finalRole);
// Only redirect if we are in a state that warrants it (e.g. just logged in)
setAppState(prev => {
if (prev === 'WELCOME' || prev === 'LOGIN' || prev === 'VERIFY') {
if (finalRole === 'ADMIN') {
if (intendedRole === 'COORDINATOR') {
return 'ADMIN_LANDING'; // Event Co Hub for Admins who chose it
}
return 'FACULTY_DASHBOARD'; // Default Admin Hub
}
if (finalRole === 'COORDINATOR') return 'ADMIN_LANDING';
return 'DASHBOARD';
}
return prev;
});
// Fetch data but don't let it block the UI if it takes too long
Promise.all([
fetchAllRegistrations(),
fetchMyRegs(user.id),
fetchAnnouncements(),
fetchSpecialEvents(),
fetchDomains()
]).catch(err => console.error("Post-auth fetch error:", err));
} else {
setGlobalError("Access Denied: Account structure invalid.");
await supabase.auth.signOut();
}
} catch (err) {
console.error("Auth routing error:", err);
} finally {
setIsAuthenticating(false);
}
}, [fetchAllRegistrations, fetchMyRegs, fetchAnnouncements, fetchSpecialEvents, fetchDomains, intendedRole]);
useEffect(() => {
// Initial public data fetch
fetchAllRegistrations();
fetchAnnouncements();
fetchSpecialEvents();
fetchDomains();
// 1. Initial Session Recovery & Error Handling
const initAuth = async () => {
try {
const { data: { session }, error } = await supabase.auth.getSession();
if (error) {
console.warn("[Auth System] Session recovery failed:", error.message);
// Broaden the error detection for refresh token issues
const isRefreshTokenError =
error.message.includes('Refresh Token') ||
error.message.includes('not found') ||
error.message.includes('Invalid Refresh Token') ||
error.status === 400 ||
error.status === 401;
if (isRefreshTokenError) {
console.log("[Auth System] Stale or invalid session detected. Clearing storage...");
// Explicitly clear the storage key used in supabase.ts
localStorage.removeItem('rit-events-hub-auth');
// Attempt a clean sign out
await supabase.auth.signOut().catch(() => {});
setAppState('WELCOME');
}
}
if (session) {
handleAuthChange(session);
}
} catch (err) {
console.error("[Auth System] Critical recovery error:", err);
}
};
initAuth();
// 2. Real-time Auth State Subscription
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
handleAuthChange(session);
});
return () => subscription.unsubscribe();
}, [handleAuthChange]);
// NOTE: CreateEventForm already handles the Supabase insert.
// This callback only refreshes the local events list.
const handleAddEvent = async (_ev: Event) => {
await fetchAllRegistrations();
};
const handleDeleteEvent = async (id: string) => {
// Step 1: Delete all registrations for this event first (FK constraint: NO ACTION)
const { error: regError } = await supabase
.from('registrations')
.delete()
.eq('event_id', id);
if (regError) {
console.error('[Delete Event] Failed to delete registrations:', regError);
}
// Step 2: Delete any announcements linked to this event
await supabase.from('announcements').delete().eq('event_id', id);
// Step 3: Now delete the event itself
const { error } = await supabase.from('events').delete().eq('id', id);
if (error) {
console.error('[Delete Event] Supabase error:', error);
alert(`Failed to delete event: ${error.message}`);
} else {
await fetchAllRegistrations(); // Refresh so all hubs update
}
};
// NOTE: CreateEventForm handles event_schedule + resource_persons writes.
// This only updates the events table columns.
const handleUpdateEvent = async (ev: Event) => {
const { error } = await supabase.from('events').update({
title: ev.title,
location: ev.location,
date: ev.date,
category: ev.category,
domain: ev.domain,
pricing_type: ev.pricingType,
coordinator: ev.coordinator,
image: ev.image,
status: ev.status,
max_participants: ev.maxParticipants,
registration_deadline: ev.registrationDeadline,
duration_days: ev.durationDays,
club: ev.club,
event_summary: ev.event_summary,
is_team_event: ev.isTeamEvent,
team_size_limit: ev.teamSizeLimit,
team_composition: ev.teamComposition,
participant_type: ev.participantType,
verification_status: ev.verificationStatus,
refreshment_expense: ev.refreshment_expense || 0,
transportation_expense: ev.transportation_expense || 0,
session_coverage_fee: (ev as any).session_coverage_fee || 0,
total_expense: ev.total_expense || 0,
request_by_faculty: ev.request_by_faculty || null,
request_by_hod: ev.request_by_HOD || null
}).eq('id', ev.id);
if (error) {
console.error('[Update Event] Supabase error:', error);
alert(`Failed to update event: ${error.message}`);
} else {
await fetchAllRegistrations();
}
};
const handleUpdateSpecialEvent = async (se: any) => {
const { error } = await supabase.from('special_events').update({
title: se.title,
description: se.description,
link: se.link,
is_active: se.is_active,
verification_status: se.verificationStatus
}).eq('id', se.id);
if (error) {
console.error('[Update Special Event] Supabase error:', error);
alert(`Failed to update special event: ${error.message}`);
} else {
await fetchSpecialEvents();
}
};
const handleAddAnnouncement = async (ann: Announcement) => {
const { error } = await supabase.from('announcements').insert({
title: ann.title,
message: ann.message,
type: ann.type,
event_id: ann.eventId,
expires_at: ann.expiresAt,
timestamp: new Date().toISOString()
});
if (!error) {
if (ann.type === 'ONGOING' && ann.eventId) {
// Sync participants table when event starts
const { data: regs } = await supabase
.from('registrations')
.select('*')
.eq('event_id', ann.eventId)
.eq('payment_status', 'COMPLETED');
if (regs && regs.length > 0) {
const participantData = regs.map(r => ({
user_id: r.user_id,
event_id: r.event_id,
user_name: r.user_name,
reg_no: r.reg_no,
dept: r.dept,
year: r.year,
section: r.section,
participation_date: new Date().toISOString()
}));
await supabase.from('participants').upsert(participantData);
}
}
await fetchAnnouncements();
}
};
const handleUpdateAnnouncement = async (ann: Announcement) => {
const { error } = await supabase.from('announcements')
.update({
title: ann.title,
message: ann.message,
type: ann.type,
event_id: ann.eventId,
expires_at: ann.expiresAt
})
.eq('id', ann.id);
if (!error) {
await fetchAnnouncements();
} else {
console.error("[Database Hub] Update Rejection:", error);
alert(`Update failed: ${error.message}`);
}
};
const handleDeleteAnnouncement = async (id: string) => {
// 1. Retrieve notice details before deletion to check type
const { data: targetAnn } = await supabase.from('announcements').select('*').eq('id', id).single();
const { error } = await supabase.from('announcements')
.delete()
.eq('id', id);
if (!error) {
// 2. If it was an 'ENDED' or 'ONGOING' notice, revert event status and wipe records
if (targetAnn && (targetAnn.type === 'ENDED' || targetAnn.type === 'ONGOING') && targetAnn.event_id) {
await supabase.from('events').update({ status: 'Scheduled' }).eq('id', targetAnn.event_id);
// Wipe attendance and participants for a clean restart
await supabase.from('attendance_records').delete().eq('event_id', targetAnn.event_id);
await supabase.from('participants').delete().eq('event_id', targetAnn.event_id);
await fetchAllRegistrations(); // Sync the event list
}
setAnnouncements(prev => prev.filter(ann => ann.id !== id));
await fetchAnnouncements();
} else {
console.error("[Database Hub] Delete Rejection:", error);
alert(`Delete rejected: ${error.message}`);
}
};
const toggleBooking = async (eventId: string) => {
if (userRole !== 'STUDENT') {
alert("Event Coordinators and Admins cannot register for events.");
return;
}
try {
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
alert("You must be logged in to register.");
return;
}
const registrationId = `${user.id}_${eventId}`;
if (bookedEventIds.includes(eventId)) {
const { error } = await supabase.from('registrations').delete().eq('id', registrationId);
if (error) throw error;
setBookedEventIds(prev => prev.filter(id => id !== eventId));
await fetchAllRegistrations();
} else {
// Block registration if there is any unfinalized completed event (missing certificate upload or missing OD approval)
const unfinalized = userRegistrations.find(reg => {
const ev = events.find(e => String(e.id) === String(reg.event_id));
if (ev && ev.status === 'Completed') {
const hasUploadedCert = !!reg.certification_url;
const hasOd = !!(reg.od_url || reg.od);
return !hasUploadedCert || !hasOd;
}
return false;
});
if (unfinalized) {
const ev = events.find(e => String(e.id) === String(unfinalized.event_id));
alert(`Registration Blocked: You must upload your certificate and obtain OD approval for your completed event "${ev?.title || 'Past Event'}" before you can register for another event.`);
return;
}
const targetEvent = events.find(e => e.id === eventId);
if (!targetEvent) {
alert("Event not found.");
return;
}
// Try to fetch from Studentusers first, then externalusers
let student = null;
const { data: internalStudent } = await supabase.from('Studentusers').select('*').eq('id', user.id).single();
if (internalStudent) {
student = internalStudent;
} else {
const { data: externalStudent } = await supabase.from('externalusers').select('*').eq('id', user.id).single();
if (externalStudent) student = externalStudent;
}
if (!student) {
console.error("Student profile not found for user:", user.id);
alert("Your student profile was not found. Please contact support.");
return;
}
// Validate overall registration capacity
if (targetEvent.maxParticipants && (targetEvent.currentParticipants || 0) >= targetEvent.maxParticipants) {
alert("This event has reached its maximum registration limit.");
return;
}
// Validate department/section quota limits (Internal only, external has no sections)
if (student.department && targetEvent.deptLimits?.[student.department]) {
const sectionLimits = targetEvent.deptSectionLimits?.[student.department];
const hasSectionLimits = sectionLimits && Object.keys(sectionLimits).length > 0;
if (hasSectionLimits) {
const studentSection = student.section || '';
const limit = sectionLimits[studentSection];
if (!limit || limit <= 0) {
alert(`Registration Blocked: Section ${studentSection} of ${student.department} department is not allowed to register for this event.`);
return;
}
const currentSectionCount = targetEvent.currentDeptSectionCounts?.[student.department]?.[studentSection] || 0;
if (currentSectionCount >= limit) {
alert(`Registration Blocked: The quota for Section ${studentSection} of department ${student.department} is full.`);
return;
}
} else {
const currentDeptCount = targetEvent.currentDeptCounts?.[student.department] || 0;
if (currentDeptCount >= targetEvent.deptLimits[student.department]) {
alert(`Registration Blocked: The quota for department ${student.department} is full.`);
return;
}
}
}
const { error } = await supabase.from('registrations').insert({
id: registrationId,
user_id: user.id,
event_id: eventId,
user_email: user.email,
user_name: student.name,
reg_no: student.reg_no,
phone: student.phone,
gender: student.gender,
dept: student.department,
section: student.section,
year: student.year,
college: student.college_name || student.college || 'Rajalakshmi Institute of Technology',
payment_status: targetEvent?.pricingType === 'FREE' ? 'COMPLETED' : 'PENDING'
});
if (error) {
if (error.code === '23505') {
alert("You are already registered for this event.");
} else {
throw error;
}
} else {
await fetchMyRegs(user.id);
await fetchAllRegistrations();
}
}
} catch (err: any) {
console.error("Booking Error:", err);
alert(`Registration failed: ${err.message || 'Unknown error'}`);
}
};
const handleLogout = async () => {
await supabase.auth.signOut();
setAppState('WELCOME');
setUserRole(null);
setCurrentUserId('');
setCurrentUserFacultyRole('');
};
return (
<div className="min-h-screen relative">
{isAuthenticating && (
<div className="fixed inset-0 z-[9999] bg-white/95 backdrop-blur-xl flex flex-col items-center justify-center animate-in fade-in duration-300">
<div className="w-16 h-16 border-4 border-slate-200 border-t-[#f97316] rounded-full animate-spin mb-6 shadow-2xl"></div>
<h2 className="text-xl font-black text-slate-800 uppercase tracking-widest">Verifying Portal</h2>
<p className="text-[10px] font-bold text-slate-400 uppercase tracking-[0.4em] mt-2">Authenticating Academic Credentials</p>
</div>
)}
{globalError && (
<div className="fixed top-10 left-1/2 -translate-x-1/2 z-[10000] bg-rose-600 text-white px-8 py-4 rounded-2xl shadow-2xl font-black text-xs uppercase tracking-widest flex items-center gap-3 animate-in slide-in-from-top-10">
<i className="fas fa-exclamation-triangle"></i>
{globalError}
<button onClick={() => setGlobalError(null)} className="ml-4 hover:opacity-50"><i className="fas fa-times"></i></button>
</div>
)}
{appState === 'WELCOME' && <WelcomeScreen onSelectRole={(role) => { setIntendedRole(role); setUserRole(role); setAppState('LOGIN'); }} events={events} />}
{appState === 'LOGIN' && userRole && (
<LoginForm
role={userRole}
onSuccess={() => setIsAuthenticating(true)}
onBack={() => setAppState('WELCOME')}
/>
)}
{appState === 'DASHBOARD' && (userRole === 'STUDENT' || userRole === 'COORDINATOR' || userRole === 'ADMIN') && (
<Dashboard
userRole={userRole}
events={events}
announcements={announcements}
bookedEventIds={bookedEventIds}
userRegistrations={userRegistrations}
onToggleBooking={toggleBooking}
onLogout={handleLogout}
currentUserName={currentUserName}
onUploadCertificate={async () => ""}
studentDocuments={studentDocuments}
onUploadDoc={(studentId, data) => setStudentDocuments(prev => ({ ...prev, [studentId]: data }))}
onBackToCoordinatorHub={(userRole === 'COORDINATOR' || userRole === 'ADMIN') ? () => setAppState('ADMIN_LANDING') : undefined}
specialEvents={specialEvents}
/>
)}
{appState === 'ADMIN_LANDING' && (userRole === 'COORDINATOR' || userRole === 'ADMIN') && (
<AdminLandingPage
onBack={userRole === 'ADMIN' ? () => setAppState('FACULTY_DASHBOARD') : handleLogout}
onAddEvent={handleAddEvent}
onUpdateEvent={handleUpdateEvent}
onDeleteEvent={handleDeleteEvent}
events={events} announcements={announcements}
onAddAnnouncement={handleAddAnnouncement}
onDeleteAnnouncement={handleDeleteAnnouncement}
onUpdateAnnouncement={handleUpdateAnnouncement}
studentDocuments={studentDocuments} onApproveCertificate={async (id) => { await supabase.from('registrations').update({ certification_status: 'APPROVED' }).eq('id', id); fetchAllRegistrations(); }}
localRegistrations={allRegistrations}
onViewStudentHub={() => setAppState('DASHBOARD')}
currentUserDept={currentUserDept}
currentUserEmail={currentUserEmail}
currentUserName={currentUserName}
currentUserPhone={currentUserPhone}
currentUserPhoto={currentUserPhoto}
currentUserId={currentUserId}
currentUserFacultyRole={currentUserFacultyRole}
domains={domains}
/>
)}
{appState === 'FACULTY_DASHBOARD' && userRole === 'ADMIN' && (
<FacultyDashboard
onLogout={handleLogout}
events={events}
specialEvents={specialEvents}
domains={domains}
announcements={announcements}
studentDocuments={studentDocuments}
onApproveCertificate={async (id) => { await supabase.from('registrations').update({ certification_status: 'APPROVED' }).eq('id', id); fetchAllRegistrations(); }}
onUpdateEvent={handleUpdateEvent}
onUpdateSpecialEvent={handleUpdateSpecialEvent}
localRegistrations={allRegistrations}
onViewCoordinatorHub={() => setAppState('ADMIN_LANDING')}
onViewStudentHub={() => setAppState('DASHBOARD')}
currentUserName={currentUserName}
currentUserDept={currentUserDept}
currentUserEmail={currentUserEmail}
/>
)}
</div>
);
};
export default App;