Convert backends to Firebase and combine projects
This commit is contained in:
335
RIT-EVENT-MANAGEMENT--main/components/Dashboard.tsx
Normal file
335
RIT-EVENT-MANAGEMENT--main/components/Dashboard.tsx
Normal file
@@ -0,0 +1,335 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { UserRole, DashboardView, Event, Announcement, Ticket } from '../types';
|
||||
import { supabase } from '../supabase';
|
||||
import Navbar from './Navbar';
|
||||
import Hero from './Hero';
|
||||
import HomeDashboard from './HomeDashboard';
|
||||
import EventList from './EventList';
|
||||
import EventCard from './EventCard';
|
||||
import ProfileView from './ProfileView';
|
||||
import StatusTrackerView from './StatusTrackerView';
|
||||
import RegistrationsView from './RegistrationsView';
|
||||
import UpcomingEventsSlider from './UpcomingEventsSlider';
|
||||
import AboutHubSection from './AboutHubSection';
|
||||
import StatsSection from './StatsSection';
|
||||
import AboutSection from './AboutSection';
|
||||
import ContactSection from './ContactSection';
|
||||
import Footer from './Footer';
|
||||
|
||||
const Portal: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
return createPortal(children, document.body);
|
||||
};
|
||||
|
||||
interface DashboardProps {
|
||||
userRole: UserRole;
|
||||
events: Event[];
|
||||
announcements: Announcement[];
|
||||
bookedEventIds: string[];
|
||||
userRegistrations: any[];
|
||||
onToggleBooking: (id: string) => void;
|
||||
onLogout: () => void;
|
||||
onUploadCertificate: (eventId: string, data: string) => Promise<string>;
|
||||
studentDocuments: Record<string, string>;
|
||||
onUploadDoc: (studentId: string, data: string) => void;
|
||||
currentUserName: string;
|
||||
onSupabaseError?: () => void;
|
||||
onBackToCoordinatorHub?: () => void;
|
||||
specialEvents: any[];
|
||||
}
|
||||
|
||||
const Dashboard: React.FC<DashboardProps> = ({
|
||||
userRole,
|
||||
events,
|
||||
announcements,
|
||||
bookedEventIds,
|
||||
userRegistrations,
|
||||
onToggleBooking,
|
||||
onLogout,
|
||||
onUploadCertificate,
|
||||
studentDocuments,
|
||||
onUploadDoc,
|
||||
currentUserName,
|
||||
onSupabaseError,
|
||||
onBackToCoordinatorHub,
|
||||
specialEvents
|
||||
}) => {
|
||||
const [activeView, setActiveView] = useState<DashboardView>('HOME');
|
||||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
||||
const [trackedEvent, setTrackedEvent] = useState<Event | null>(null);
|
||||
|
||||
// Team Management States
|
||||
const [showCreateModal, setShowCreateModal] = useState<{eventId: string, title: string} | null>(null);
|
||||
const [showJoinModal, setShowJoinModal] = useState<{eventId: string, title: string} | null>(null);
|
||||
const [teamName, setTeamName] = useState('');
|
||||
const [teamCode, setTeamCode] = useState('');
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
|
||||
const generateCode = () => Math.random().toString(36).substring(2, 8).toUpperCase();
|
||||
|
||||
const handleCreateTeam = async (eventId: string) => {
|
||||
if (!teamName.trim()) return alert("Enter team name");
|
||||
setIsProcessing(true);
|
||||
try {
|
||||
const code = generateCode();
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) return;
|
||||
|
||||
const registrationId = `${user.id}_${eventId}`;
|
||||
const { error } = await supabase.from('registrations').update({
|
||||
team_code: code,
|
||||
team_name: teamName.trim(),
|
||||
is_team_leader: true
|
||||
}).eq('id', registrationId);
|
||||
|
||||
if (error) throw error;
|
||||
setShowCreateModal(null);
|
||||
setTeamName('');
|
||||
window.location.reload();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert("Failed to create team");
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleJoinTeam = async (eventId: string) => {
|
||||
if (!teamCode.trim()) return alert("Enter team code");
|
||||
setIsProcessing(true);
|
||||
try {
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) return;
|
||||
|
||||
const { data: teamRegs, error: fetchErr } = await supabase
|
||||
.from('registrations')
|
||||
.select('*')
|
||||
.eq('event_id', eventId)
|
||||
.eq('team_code', teamCode.trim().toUpperCase());
|
||||
|
||||
if (fetchErr) throw fetchErr;
|
||||
if (!teamRegs || teamRegs.length === 0) return alert("Invalid team code for this event");
|
||||
|
||||
const leader = teamRegs.find(r => r.is_team_leader);
|
||||
const event = events.find(e => e.id === eventId);
|
||||
|
||||
if (event?.teamSizeLimit && teamRegs.length >= event.teamSizeLimit) {
|
||||
return alert("Team is already full");
|
||||
}
|
||||
|
||||
const { data: myProfile } = await supabase.from('Studentusers').select('department').eq('id', user.id).single();
|
||||
if (event?.teamComposition === 'INTER_DEPT' && leader && myProfile?.department !== leader.dept) {
|
||||
return alert(`This event requires INTER-DEPARTMENT teams. You must join a team from ${leader.dept}`);
|
||||
}
|
||||
|
||||
const registrationId = `${user.id}_${eventId}`;
|
||||
const { error: joinErr } = await supabase.from('registrations').update({
|
||||
team_code: teamCode.trim().toUpperCase(),
|
||||
team_name: leader?.team_name || 'Team',
|
||||
is_team_leader: false
|
||||
}).eq('id', registrationId);
|
||||
|
||||
if (joinErr) throw joinErr;
|
||||
setShowJoinModal(null);
|
||||
setTeamCode('');
|
||||
window.location.reload();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert("Failed to join team");
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (activeView !== 'EVENTS' && activeView !== 'STATUS_TRACKER') {
|
||||
setSelectedCategory(null);
|
||||
}
|
||||
}, [activeView]);
|
||||
|
||||
const handleTrackStatus = (event: Event) => {
|
||||
setTrackedEvent(event);
|
||||
setActiveView('STATUS_TRACKER');
|
||||
};
|
||||
|
||||
const renderContent = () => {
|
||||
switch (activeView) {
|
||||
case 'HOME':
|
||||
return (
|
||||
<>
|
||||
<Hero events={events} />
|
||||
<UpcomingEventsSlider events={events} />
|
||||
<AboutHubSection />
|
||||
<StatsSection events={events} />
|
||||
<HomeDashboard
|
||||
events={events}
|
||||
announcements={announcements}
|
||||
onNavigateToEvents={() => setActiveView('EVENTS')}
|
||||
specialEvents={specialEvents}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
case 'ABOUT':
|
||||
return (
|
||||
<div className="pt-16 min-h-screen bg-white">
|
||||
<AboutSection />
|
||||
</div>
|
||||
);
|
||||
case 'CONTACT':
|
||||
return (
|
||||
<div className="pt-16 min-h-screen bg-white">
|
||||
<ContactSection />
|
||||
</div>
|
||||
);
|
||||
case 'EVENTS':
|
||||
return (
|
||||
<EventList
|
||||
events={events}
|
||||
selectedCategory={selectedCategory}
|
||||
bookedEventIds={bookedEventIds}
|
||||
onToggleBooking={onToggleBooking}
|
||||
onSelectCategory={setSelectedCategory}
|
||||
onTrackStatus={handleTrackStatus}
|
||||
currentUserName={currentUserName}
|
||||
userRole={userRole}
|
||||
userRegistrations={userRegistrations}
|
||||
/>
|
||||
);
|
||||
case 'REGISTRATIONS':
|
||||
return (
|
||||
<RegistrationsView
|
||||
events={events}
|
||||
bookedEventIds={bookedEventIds}
|
||||
userRegistrations={userRegistrations}
|
||||
onToggleBooking={onToggleBooking}
|
||||
onTrackStatus={handleTrackStatus}
|
||||
currentUserName={currentUserName}
|
||||
userRole={userRole}
|
||||
/>
|
||||
);
|
||||
case 'PROFILE':
|
||||
return <ProfileView onLogout={onLogout} onSupabaseError={onSupabaseError} />;
|
||||
case 'STATUS_TRACKER':
|
||||
const registration = userRegistrations.find(r => String(r.event_id) === String(trackedEvent?.id));
|
||||
return trackedEvent ? (
|
||||
<StatusTrackerView
|
||||
event={trackedEvent}
|
||||
registration={registration}
|
||||
onBack={() => setActiveView('REGISTRATIONS')}
|
||||
onUploadCertificate={(data) => onUploadCertificate(trackedEvent.id, data)}
|
||||
onShowCreateTeam={() => setShowCreateModal({ eventId: trackedEvent.id, title: trackedEvent.title })}
|
||||
onShowJoinTeam={() => setShowJoinModal({ eventId: trackedEvent.id, title: trackedEvent.title })}
|
||||
/>
|
||||
) : null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#F3F4F6]">
|
||||
<Navbar
|
||||
activeView={activeView}
|
||||
onViewChange={setActiveView}
|
||||
onLogout={onLogout}
|
||||
onBackToCoordinatorHub={onBackToCoordinatorHub}
|
||||
/>
|
||||
<main className="pb-20">
|
||||
{renderContent()}
|
||||
</main>
|
||||
<Footer />
|
||||
|
||||
{/* Team Management Modals */}
|
||||
{showCreateModal && (
|
||||
<Portal>
|
||||
<div className="fixed inset-0 z-[10000] flex items-center justify-center p-6 bg-slate-900/60 backdrop-blur-sm animate-in fade-in duration-300">
|
||||
<div className="bg-white rounded-[3rem] w-full max-w-md p-10 md:p-12 shadow-2xl animate-in zoom-in-95 duration-500 relative overflow-hidden">
|
||||
<div className="absolute top-0 left-0 w-full h-2 bg-gradient-to-r from-orange-400 to-amber-400"></div>
|
||||
<div className="w-16 h-16 flex items-center justify-center mx-auto mb-8">
|
||||
<img
|
||||
src="https://www.facultyplus.com/wp-content/uploads/2020/11/Rit-logo.png"
|
||||
alt="RIT Logo"
|
||||
className="w-full h-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
<h3 className="text-2xl font-black text-slate-900 text-center uppercase mb-2 tracking-tight">Form a Team</h3>
|
||||
<p className="text-[10px] text-gray-400 text-center font-bold uppercase tracking-widest mb-10 line-clamp-1 px-4">{showCreateModal.title}</p>
|
||||
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<label className="block text-[9px] font-black text-slate-400 uppercase tracking-[0.2em] mb-3 px-2">Team Identity</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter team name..."
|
||||
className="w-full bg-slate-50 border border-slate-200 rounded-[2rem] px-8 py-5 text-sm font-bold outline-none focus:ring-4 focus:ring-orange-100 focus:border-[#f97316] transition-all placeholder:text-slate-300"
|
||||
value={teamName}
|
||||
onChange={e => setTeamName(e.target.value)}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-3">
|
||||
<button
|
||||
onClick={() => handleCreateTeam(showCreateModal.eventId)}
|
||||
disabled={isProcessing}
|
||||
className="w-full py-5 bg-[#f97316] text-white rounded-[2rem] font-black uppercase text-[11px] tracking-[0.2em] shadow-xl shadow-orange-200 hover:bg-[#ea580c] transition-all active:scale-95 disabled:opacity-50"
|
||||
>
|
||||
{isProcessing ? 'INITIALIZING...' : 'Establish Team & Code'}
|
||||
</button>
|
||||
<button onClick={() => setShowCreateModal(null)} className="w-full py-4 text-gray-400 font-black uppercase text-[9px] tracking-widest hover:text-slate-600 transition-colors">Dismiss</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Portal>
|
||||
)}
|
||||
|
||||
{showJoinModal && (
|
||||
<Portal>
|
||||
<div className="fixed inset-0 z-[10000] flex items-center justify-center p-6 bg-slate-900/60 backdrop-blur-sm animate-in fade-in duration-300">
|
||||
<div className="bg-white rounded-[3rem] w-full max-w-md p-10 md:p-12 shadow-2xl animate-in zoom-in-95 duration-500 relative overflow-hidden">
|
||||
<div className="absolute top-0 left-0 w-full h-2 bg-gradient-to-r from-blue-400 to-indigo-400"></div>
|
||||
<div className="w-16 h-16 flex items-center justify-center mx-auto mb-8">
|
||||
<img
|
||||
src="https://www.facultyplus.com/wp-content/uploads/2020/11/Rit-logo.png"
|
||||
alt="RIT Logo"
|
||||
className="w-full h-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
<h3 className="text-2xl font-black text-slate-900 text-center uppercase mb-2 tracking-tight">Join Alliance</h3>
|
||||
<p className="text-[10px] text-gray-400 text-center font-bold uppercase tracking-widest mb-10 line-clamp-1 px-4">{showJoinModal.title}</p>
|
||||
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<label className="block text-[9px] font-black text-slate-400 uppercase tracking-[0.2em] mb-3 px-2 text-center">Team Access Token</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="TOKEN"
|
||||
className="w-full bg-slate-50 border border-slate-200 rounded-[2rem] px-5 py-6 text-center text-3xl font-black tracking-[0.4em] outline-none focus:ring-4 focus:ring-blue-100 focus:border-blue-500 uppercase transition-all placeholder:text-slate-200"
|
||||
value={teamCode}
|
||||
onChange={e => setTeamCode(e.target.value)}
|
||||
maxLength={6}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-3">
|
||||
<button
|
||||
onClick={() => handleJoinTeam(showJoinModal.eventId)}
|
||||
disabled={isProcessing}
|
||||
className="w-full py-5 bg-slate-900 text-white rounded-[2rem] font-black uppercase text-[11px] tracking-[0.2em] shadow-xl shadow-gray-200 hover:bg-black transition-all active:scale-95 disabled:opacity-50"
|
||||
>
|
||||
{isProcessing ? 'AUTHENTICATING...' : 'Validate & Join'}
|
||||
</button>
|
||||
<button onClick={() => setShowJoinModal(null)} className="w-full py-4 text-gray-400 font-black uppercase text-[9px] tracking-widest hover:text-slate-600 transition-colors">Dismiss</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Portal>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
Reference in New Issue
Block a user