Update frontend components and integrate firebase backend

This commit is contained in:
SACHIN
2026-06-25 08:13:09 +05:30
parent 8af314c629
commit 0b1053a4e8
14 changed files with 923 additions and 513 deletions

View File

@@ -55,7 +55,11 @@ const AppContent: React.FC = () => {
...event, ...event,
eventName: event.title, eventName: event.title,
venue: event.location, venue: event.location,
category: event.category,
eventType: event.type,
socialProfile: event.guestSocialProfile, socialProfile: event.guestSocialProfile,
maxParticipants: event["total capacity"] || (event.maxParticipants ?? ''),
sponsors: event.sponsors ?? [],
isEditMode: true isEditMode: true
}); });
setActiveItem('propose'); setActiveItem('propose');

View File

@@ -4,12 +4,10 @@ import { motion } from 'framer-motion';
import { import {
Calendar, Calendar,
MapPin, MapPin,
Building2,
Clock, Clock,
ChevronRight,
Filter, Filter,
Search, Search,
MoreVertical, Trash2,
FileSpreadsheet FileSpreadsheet
} from 'lucide-react'; } from 'lucide-react';
import { cn } from '../../lib/utils'; import { cn } from '../../lib/utils';
@@ -54,6 +52,21 @@ export const AllEvents: React.FC<AllEventsProps> = ({ onEditEvent }) => {
} }
}; };
const deleteEvent = async (eventId: number) => {
if (!window.confirm('Are you sure you want to permanently delete this event? This action cannot be undone.')) return;
try {
const res = await fetch(`${API_BASE_URL}/api/events/${eventId}`, { method: 'DELETE' });
if (res.ok) {
setEvents(prev => prev.filter(e => e.id !== eventId));
} else {
alert('Failed to delete event. Please try again.');
}
} catch (err) {
console.error('Delete failed:', err);
alert('An error occurred while deleting the event.');
}
};
const getStatusColor = (status: Event['status']) => { const getStatusColor = (status: Event['status']) => {
switch (status) { switch (status) {
case 'REQUESTED': return 'bg-brand-glow text-brand-indigo border-brand-indigo/20'; case 'REQUESTED': return 'bg-brand-glow text-brand-indigo border-brand-indigo/20';
@@ -204,9 +217,15 @@ export const AllEvents: React.FC<AllEventsProps> = ({ onEditEvent }) => {
Edit Edit
</button> </button>
)} )}
<button className="p-2 rounded-lg hover:bg-white hover:premium-shadow transition-all text-slate-300 hover:text-brand-indigo"> {user?.role === 'ADMIN' && (
<MoreVertical className="w-5 h-5" /> <button
</button> onClick={() => deleteEvent(event.id)}
className="p-2 rounded-lg hover:bg-red-50 transition-all text-slate-300 hover:text-red-500"
title="Delete Event"
>
<Trash2 className="w-4 h-4" />
</button>
)}
</div> </div>
</td> </td>
</motion.tr> </motion.tr>

View File

@@ -20,7 +20,8 @@ import {
Building2, Building2,
Users, Users,
Wallet, Wallet,
Ticket Ticket,
Layers
} from 'lucide-react'; } from 'lucide-react';
import { cn } from '../../lib/utils'; import { cn } from '../../lib/utils';
import { format } from 'date-fns'; import { format } from 'date-fns';
@@ -41,13 +42,27 @@ interface Event {
institution: string; institution: string;
category: string; category: string;
academicYears: string[]; academicYears: string[];
targetDepartments?: string[];
targetedSections?: string[];
proposer?: { proposer?: {
fullName: string; fullName: string;
email?: string;
department?: string;
role?: string;
}; };
description?: string; description?: string;
sponsors?: string[]; sponsors?: string[];
hasRegistrationFee: boolean; hasRegistrationFee: boolean;
registrationFee: number; registrationFee: number;
maxParticipants?: number | null;
requirements?: string[];
refreshment_expense?: number;
transportation_expense?: number;
session_coverage_fee?: number;
total_expense?: number;
paymentLink?: string;
isClubEvent?: boolean;
eventScope?: string;
} }
export const EventHistory: React.FC = () => { export const EventHistory: React.FC = () => {
@@ -294,38 +309,129 @@ export const EventHistory: React.FC = () => {
</div> </div>
)} )}
<div className="grid grid-cols-2 gap-8"> {/* Event Classification */}
<div className="space-y-6"> <div className="grid grid-cols-2 gap-4">
<div className="space-y-1"> <div className="p-4 bg-slate-50 rounded-2xl space-y-1">
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted"> <div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
<Calendar className="w-3.5 h-3.5" /> <FileText className="w-3.5 h-3.5" />
Date & Time Event Type
</div>
<p className="text-sm font-bold text-text-dark">{new Date(selectedEvent.startDate).toLocaleString()}</p>
</div>
<div className="space-y-1">
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
<MapPin className="w-3.5 h-3.5" />
Location
</div>
<p className="text-sm font-bold text-text-dark">{selectedEvent.location}</p>
</div> </div>
<p className="text-sm font-bold text-text-dark">{selectedEvent.type || 'N/A'}</p>
</div> </div>
<div className="p-4 bg-slate-50 rounded-2xl space-y-1">
<div className="space-y-6"> <div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
<div className="space-y-1"> <Layers className="w-3.5 h-3.5" />
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted"> Hub Category
<Building2 className="w-3.5 h-3.5" />
Department
</div>
<p className="text-sm font-bold text-text-dark">{selectedEvent.department}</p>
</div> </div>
<div className="space-y-1"> <p className="text-sm font-bold text-text-dark capitalize">
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted"> {selectedEvent.category === 'TECHNICAL' ? 'Technical'
<Users className="w-3.5 h-3.5" /> : selectedEvent.category === 'NON-TECHNICAL' ? 'Non-Technical'
Target Batches : selectedEvent.category === 'WORKSHOP' ? 'Workshop'
</div> : selectedEvent.category === 'CENTRE-ACTIVITY' ? 'Centre Based Activity'
<p className="text-sm font-bold text-text-dark">{selectedEvent.academicYears?.join(', ') || 'N/A'}</p> : selectedEvent.category || 'N/A'}
</p>
</div>
</div>
{/* Dates */}
<div className="grid grid-cols-2 gap-4">
<div className="p-4 bg-slate-50 rounded-2xl space-y-1">
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
<Calendar className="w-3.5 h-3.5" />
Start Date &amp; Time
</div>
<p className="text-sm font-bold text-text-dark">
{selectedEvent.startDate
? new Date(selectedEvent.startDate).toLocaleString('en-IN', { timeZone: 'Asia/Kolkata', day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false }) + ' IST'
: 'N/A'}
</p>
</div>
<div className="p-4 bg-slate-50 rounded-2xl space-y-1">
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
<Calendar className="w-3.5 h-3.5" />
End Date &amp; Time
</div>
<p className="text-sm font-bold text-text-dark">
{selectedEvent.endDate
? new Date(selectedEvent.endDate).toLocaleString('en-IN', { timeZone: 'Asia/Kolkata', day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false }) + ' IST'
: 'N/A'}
</p>
</div>
</div>
{/* Location & Capacity */}
<div className="grid grid-cols-2 gap-4">
<div className="p-4 bg-slate-50 rounded-2xl space-y-1">
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
<MapPin className="w-3.5 h-3.5" />
Location
</div>
<p className="text-sm font-bold text-text-dark">{selectedEvent.location || 'N/A'}</p>
</div>
<div className="p-4 bg-slate-50 rounded-2xl space-y-1">
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
<Users className="w-3.5 h-3.5" />
Total Capacity
</div>
<p className="text-sm font-bold text-text-dark">
{selectedEvent.maxParticipants ? selectedEvent.maxParticipants.toLocaleString() : 'Unlimited'}
</p>
</div>
</div>
{/* Scope */}
<div className="grid grid-cols-2 gap-4">
<div className="p-4 bg-slate-50 rounded-2xl space-y-1">
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
<Building2 className="w-3.5 h-3.5" />
Event Scope
</div>
<p className="text-sm font-bold text-text-dark">
{selectedEvent.department === 'Institutional' ? 'Institutional Event' : 'Departmental Event'}
</p>
</div>
<div className="p-4 bg-slate-50 rounded-2xl space-y-1">
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
<Building2 className="w-3.5 h-3.5" />
Proposing Department
</div>
<p className="text-sm font-bold text-text-dark">
{(selectedEvent.proposer?.department && selectedEvent.proposer?.department !== 'N/A')
? selectedEvent.proposer.department
: (selectedEvent.proposer?.role === 'ADMIN' ? 'System Administrator' : selectedEvent.department)
|| 'N/A'}
</p>
</div>
</div>
{/* Target Departments & Batches */}
<div className="space-y-3">
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
<Users className="w-3.5 h-3.5" />
Target Audience
</div>
<div className="grid grid-cols-3 gap-3">
<div className="p-3 bg-slate-50 rounded-xl">
<p className="text-[9px] font-black uppercase tracking-widest text-text-muted mb-1">Departments</p>
<p className="text-xs font-bold text-text-dark">
{(selectedEvent.targetDepartments && selectedEvent.targetDepartments.length > 0)
? selectedEvent.targetDepartments.join(', ')
: selectedEvent.department || 'N/A'}
</p>
</div>
<div className="p-3 bg-slate-50 rounded-xl">
<p className="text-[9px] font-black uppercase tracking-widest text-text-muted mb-1">Batches</p>
<p className="text-xs font-bold text-text-dark">
{selectedEvent.academicYears?.length > 0 ? selectedEvent.academicYears.join(', ') : 'All'}
</p>
</div>
<div className="p-3 bg-slate-50 rounded-xl">
<p className="text-[9px] font-black uppercase tracking-widest text-text-muted mb-1">Sections</p>
<p className="text-xs font-bold text-text-dark">
{(selectedEvent.targetedSections && selectedEvent.targetedSections.length > 0)
? selectedEvent.targetedSections.join(', ')
: 'All Sections'}
</p>
</div> </div>
</div> </div>
</div> </div>
@@ -347,16 +453,77 @@ export const EventHistory: React.FC = () => {
</div> </div>
)} )}
<div className="grid grid-cols-2 gap-8 pt-4 border-t border-slate-50"> {/* Event Amenities / Requirements */}
<div className="p-4 bg-slate-50 rounded-2xl"> {selectedEvent.requirements && selectedEvent.requirements.length > 0 && (
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted mb-1"> <div className="space-y-3">
<Wallet className="w-3.5 h-3.5" /> <div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
Budget <CheckCircle2 className="w-3.5 h-3.5 text-emerald-500" />
Event Amenities
</div>
<div className="flex flex-wrap gap-2">
{selectedEvent.requirements.map((req, idx) => (
<span key={idx} className="px-3 py-1 bg-emerald-50 text-emerald-700 rounded-lg text-[10px] font-black uppercase tracking-widest border border-emerald-100">
{req}
</span>
))}
</div> </div>
<p className="text-lg font-black text-text-dark">{selectedEvent.budget?.toLocaleString() || '0'}</p>
</div> </div>
<div className="p-4 bg-slate-50 rounded-2xl"> )}
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted mb-1">
{/* Financial Details */}
<div className="space-y-3 border-t border-slate-50 pt-4">
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
<Wallet className="w-3.5 h-3.5" />
Financial Details
</div>
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
<div className="p-3 bg-slate-50 rounded-xl">
<p className="text-[9px] font-black uppercase tracking-widest text-text-muted mb-1">Budget</p>
<p className="text-sm font-black text-text-dark">{selectedEvent.budget?.toLocaleString() || '0'}</p>
</div>
{(selectedEvent.refreshment_expense || 0) > 0 && (
<div className="p-3 bg-slate-50 rounded-xl">
<p className="text-[9px] font-black uppercase tracking-widest text-text-muted mb-1">Refreshments</p>
<p className="text-sm font-black text-text-dark">{selectedEvent.refreshment_expense?.toLocaleString()}</p>
</div>
)}
{(selectedEvent.transportation_expense || 0) > 0 && (
<div className="p-3 bg-slate-50 rounded-xl">
<p className="text-[9px] font-black uppercase tracking-widest text-text-muted mb-1">Transportation</p>
<p className="text-sm font-black text-text-dark">{selectedEvent.transportation_expense?.toLocaleString()}</p>
</div>
)}
{(selectedEvent.session_coverage_fee || 0) > 0 && (
<div className="p-3 bg-slate-50 rounded-xl">
<p className="text-[9px] font-black uppercase tracking-widest text-text-muted mb-1">Session Coverage</p>
<p className="text-sm font-black text-text-dark">{selectedEvent.session_coverage_fee?.toLocaleString()}</p>
</div>
)}
<div className="p-3 bg-brand-navy rounded-xl">
<p className="text-[9px] font-black uppercase tracking-widest text-white/60 mb-1">Total Expense</p>
<p className="text-sm font-black text-white">{(selectedEvent.total_expense || selectedEvent.budget || 0).toLocaleString()}</p>
</div>
</div>
</div>
{/* Registration / Free Event */}
<div className="grid grid-cols-2 gap-4">
<div className="p-4 bg-slate-50 rounded-2xl space-y-1">
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
<Ticket className="w-3.5 h-3.5" />
Registration
</div>
<p className="text-sm font-bold text-text-dark">
{selectedEvent.hasRegistrationFee
? `Paid — ₹${selectedEvent.registrationFee?.toLocaleString()}`
: 'Free Event'}
</p>
{selectedEvent.hasRegistrationFee && selectedEvent.paymentLink && (
<a href={selectedEvent.paymentLink} target="_blank" rel="noopener noreferrer" className="text-[10px] text-brand-indigo font-bold underline">Payment Link </a>
)}
</div>
<div className="p-4 bg-slate-50 rounded-2xl space-y-1">
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
<Ticket className="w-3.5 h-3.5" /> <Ticket className="w-3.5 h-3.5" />
Status Status
</div> </div>

File diff suppressed because it is too large Load Diff

View File

@@ -65,6 +65,36 @@ export const ManageNoticesView: React.FC = () => {
const [eventLink, setEventLink] = useState(''); const [eventLink, setEventLink] = useState('');
const [eventImage, setEventImage] = useState<string | null>(null); const [eventImage, setEventImage] = useState<string | null>(null);
const compressImage = (base64Str: string): Promise<string> => {
return new Promise((resolve) => {
const img = new Image();
img.src = base64Str;
img.onload = () => {
const canvas = document.createElement('canvas');
const MAX_WIDTH = 800;
const MAX_HEIGHT = 600;
let width = img.width;
let height = img.height;
if (width > height) {
if (width > MAX_WIDTH) {
height *= MAX_WIDTH / width;
width = MAX_WIDTH;
}
} else {
if (height > MAX_HEIGHT) {
width *= MAX_HEIGHT / height;
height = MAX_HEIGHT;
}
}
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx?.drawImage(img, 0, 0, width, height);
resolve(canvas.toDataURL('image/jpeg', 0.7));
};
});
};
const handleEventImageUpload = (e: React.ChangeEvent<HTMLInputElement>) => { const handleEventImageUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]; const file = e.target.files?.[0];
if (file) { if (file) {
@@ -73,8 +103,9 @@ export const ManageNoticesView: React.FC = () => {
return; return;
} }
const reader = new FileReader(); const reader = new FileReader();
reader.onloadend = () => { reader.onloadend = async () => {
setEventImage(reader.result as string); const compressed = await compressImage(reader.result as string);
setEventImage(compressed);
}; };
reader.readAsDataURL(file); reader.readAsDataURL(file);
} }
@@ -133,8 +164,9 @@ export const ManageNoticesView: React.FC = () => {
return; return;
} }
const reader = new FileReader(); const reader = new FileReader();
reader.onloadend = () => { reader.onloadend = async () => {
setNoticeImage(reader.result as string); const compressed = await compressImage(reader.result as string);
setNoticeImage(compressed);
}; };
reader.readAsDataURL(file); reader.readAsDataURL(file);
} }
@@ -163,6 +195,9 @@ export const ManageNoticesView: React.FC = () => {
setIsNoticeModalOpen(false); setIsNoticeModalOpen(false);
fetchInitialData(); fetchInitialData();
showAlert('Success', 'Notice posted successfully to Campus Notice Board.', 'success'); showAlert('Success', 'Notice posted successfully to Campus Notice Board.', 'success');
} else {
const errData = await response.json();
showAlert('Error', errData.message || 'Failed to save notice. Image may be too large.', 'error');
} }
} catch (err) { } catch (err) {
console.error('Failed to post notice:', err); console.error('Failed to post notice:', err);
@@ -222,6 +257,9 @@ export const ManageNoticesView: React.FC = () => {
setEventImage(null); setEventImage(null);
fetchInitialData(); fetchInitialData();
showAlert('Success', 'Special event added to the registry successfully.', 'success'); showAlert('Success', 'Special event added to the registry successfully.', 'success');
} else {
const errData = await response.json();
showAlert('Error', errData.message || 'Failed to create special event. Image may be too large.', 'error');
} }
} catch (err) { } catch (err) {
console.error('Post failed:', err); console.error('Post failed:', err);

View File

@@ -8,7 +8,7 @@ interface CategoryGridProps {
} }
const CategoryGrid: React.FC<CategoryGridProps> = ({ onSelectCategory }) => { const CategoryGrid: React.FC<CategoryGridProps> = ({ onSelectCategory }) => {
const threeCategories = CATEGORIES.filter(c => c.id !== 'CENTRE-ACTIVITY' && c.id !== 'WORKWORK' && c.id !== 'WORKSHOP'); const threeCategories = CATEGORIES.filter(c => c.id !== 'CENTRE-ACTIVITY');
const centreActivityCategory = CATEGORIES.find(c => c.id === 'CENTRE-ACTIVITY'); const centreActivityCategory = CATEGORIES.find(c => c.id === 'CENTRE-ACTIVITY');
return ( return (
@@ -18,7 +18,7 @@ const CategoryGrid: React.FC<CategoryGridProps> = ({ onSelectCategory }) => {
</h2> </h2>
{/* Three Standard Categories */} {/* Three Standard Categories */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 gap-8 max-w-5xl mx-auto mb-8"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 max-w-7xl mx-auto mb-8">
{threeCategories.map((cat) => ( {threeCategories.map((cat) => (
<div <div
key={cat.id} key={cat.id}

View File

@@ -31,13 +31,12 @@ const EventList: React.FC<EventListProps> = ({
const [selectedDomainName, setSelectedDomainName] = useState<string | null>(null); const [selectedDomainName, setSelectedDomainName] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
if (selectedCategory === 'TECHNICAL' || selectedCategory === 'NON-TECHNICAL') { setSelectedDomain('ALL');
setSelectedDomain('ALL'); if (selectedCategory === 'TECHNICAL') setSelectedDomainName('Technical Events');
setSelectedDomainName(selectedCategory === 'TECHNICAL' ? 'Technical Events' : 'Non-Technical Events'); else if (selectedCategory === 'NON-TECHNICAL') setSelectedDomainName('Non-Technical Events');
} else { else if (selectedCategory === 'WORKSHOP') setSelectedDomainName('Workshops');
setSelectedDomain(null); else if (selectedCategory === 'CENTRE-ACTIVITY') setSelectedDomainName('Centre Activities');
setSelectedDomainName(null); else setSelectedDomainName('All Events');
}
}, [selectedCategory]); }, [selectedCategory]);
useEffect(() => { useEffect(() => {
@@ -82,17 +81,11 @@ const EventList: React.FC<EventListProps> = ({
return ( return (
<div className="pt-40 pb-16 px-6 md:px-12 lg:px-24 animate-in fade-in duration-500"> <div className="pt-40 pb-16 px-6 md:px-12 lg:px-24 animate-in fade-in duration-500">
<button <button
onClick={() => { onClick={() => onSelectCategory(null)}
if (selectedCategory === 'TECHNICAL' || selectedCategory === 'NON-TECHNICAL') {
onSelectCategory(null);
} else {
setSelectedDomain(null);
}
}}
className="flex items-center gap-2 text-[#f97316] font-black uppercase tracking-widest mb-10 group hover:translate-x-[-5px] transition-transform" className="flex items-center gap-2 text-[#f97316] font-black uppercase tracking-widest mb-10 group hover:translate-x-[-5px] transition-transform"
> >
<i className="fas fa-arrow-left"></i> <i className="fas fa-arrow-left"></i>
{(selectedCategory === 'TECHNICAL' || selectedCategory === 'NON-TECHNICAL') ? 'BACK TO CATEGORIES' : 'BACK TO DOMAINS'} BACK TO CATEGORIES
</button> </button>
<div className="flex flex-col md:flex-row md:items-end justify-between mb-12 gap-6"> <div className="flex flex-col md:flex-row md:items-end justify-between mb-12 gap-6">

View File

@@ -103,7 +103,17 @@ const EventsHeroSlider: React.FC<EventsHeroSliderProps> = ({ events }) => {
<div className="flex items-center gap-4 mb-6"> <div className="flex items-center gap-4 mb-6">
<div className="w-12 h-[2px] bg-[#f97316]"></div> <div className="w-12 h-[2px] bg-[#f97316]"></div>
<span className="text-white font-bold tracking-[0.3em] uppercase text-sm"> <span className="text-white font-bold tracking-[0.3em] uppercase text-sm">
JOIN US <span className="text-[#f97316] ml-2">{event.date}</span> JOIN US <span className="text-[#f97316] ml-2">
{new Date(event.date).toLocaleString('en-IN', {
timeZone: 'Asia/Kolkata',
year: 'numeric',
month: 'short',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hour12: false
}) + ' IST'}
</span>
</span> </span>
</div> </div>

View File

@@ -20,6 +20,7 @@ const HomeDashboard: React.FC<HomeDashboardProps> = ({
settings = {} settings = {}
}) => { }) => {
const [now, setNow] = useState(new Date()); const [now, setNow] = useState(new Date());
const [zoomedImage, setZoomedImage] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
const timer = setInterval(() => setNow(new Date()), 30000); const timer = setInterval(() => setNow(new Date()), 30000);
@@ -129,11 +130,24 @@ const HomeDashboard: React.FC<HomeDashboardProps> = ({
</h3> </h3>
{(ann as any).image && ( {(ann as any).image && (
<img <div className="relative rounded-xl overflow-hidden mb-4 shrink-0 shadow-sm border border-black/10 group/img">
src={(ann as any).image} <img
alt={ann.title} src={(ann as any).image}
className="w-full h-40 object-cover rounded-xl mb-4 shrink-0 shadow-sm border border-black/10" alt={ann.title}
/> className="w-full h-40 object-cover"
/>
<button
type="button"
onClick={(e) => {
e.stopPropagation();
setZoomedImage((ann as any).image);
}}
className="absolute top-2 right-2 bg-black/70 hover:bg-black text-white w-7 h-7 rounded-full opacity-100 sm:opacity-0 group-hover/img:opacity-100 transition-opacity duration-200 z-20 flex items-center justify-center shadow-md cursor-pointer border border-white/20"
title="Inspect Image"
>
<i className="fas fa-search-plus text-xs"></i>
</button>
</div>
)} )}
<p className="font-sans text-gray-800 text-base flex-grow mb-6 leading-relaxed opacity-90"> <p className="font-sans text-gray-800 text-base flex-grow mb-6 leading-relaxed opacity-90">
@@ -174,6 +188,28 @@ const HomeDashboard: React.FC<HomeDashboardProps> = ({
<AccreditationsSection /> <AccreditationsSection />
<SpecialEventsBanner specialEvents={specialEvents} /> <SpecialEventsBanner specialEvents={specialEvents} />
{/* Image Zoom Modal */}
{zoomedImage && (
<div
className="fixed inset-0 z-[99999] flex items-center justify-center p-4 bg-black/90 backdrop-blur-sm animate-in fade-in duration-200"
onClick={() => setZoomedImage(null)}
>
<button
type="button"
className="absolute top-6 right-6 text-white/70 hover:text-white text-2xl bg-white/10 hover:bg-white/20 w-12 h-12 rounded-full flex items-center justify-center transition-all cursor-pointer border border-white/10"
onClick={() => setZoomedImage(null)}
>
<i className="fas fa-times"></i>
</button>
<img
src={zoomedImage}
alt="Inspected Notice"
className="max-w-full max-h-[90vh] object-contain rounded-lg shadow-2xl animate-in zoom-in-95 duration-200"
onClick={(e) => e.stopPropagation()}
/>
</div>
)}
</div> </div>
); );
}; };

View File

@@ -224,24 +224,7 @@ const ProfileView: React.FC<ProfileViewProps> = ({ onLogout }) => {
<div className="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden sticky top-32"> <div className="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden sticky top-32">
<div className="h-32 bg-gradient-to-r from-orange-400 to-rose-500"></div> <div className="h-32 bg-gradient-to-r from-orange-400 to-rose-500"></div>
<div className="px-6 pb-8"> <div className="px-6 pb-8">
<div className="relative -mt-16 mb-6 flex justify-center">
<div className="w-32 h-32 rounded-full border-4 border-white shadow-lg overflow-hidden bg-gray-100 relative group">
<img
src={avatarUrl || `https://api.dicebear.com/7.x/avataaars/svg?seed=${user?.fullName}`}
alt="Profile"
className="w-full h-full object-cover"
/>
{isEditing && (
<div
onClick={() => fileInputRef.current?.click()}
className="absolute inset-0 bg-black/50 flex items-center justify-center cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity"
>
<i className="fas fa-camera text-white text-xl"></i>
</div>
)}
</div>
<input type="file" ref={fileInputRef} className="hidden" onChange={handlePhotoChange} accept="image/*" />
</div>
<div className="text-center mb-8"> <div className="text-center mb-8">
<h2 className="text-2xl font-bold text-gray-900 mb-1">{user?.fullName}</h2> <h2 className="text-2xl font-bold text-gray-900 mb-1">{user?.fullName}</h2>

View File

@@ -23,12 +23,12 @@ const SpecialEventsBanner: React.FC<SpecialEventsBannerProps> = ({ specialEvents
const displayEvents = isLooping ? loopEvents : specialEvents; const displayEvents = isLooping ? loopEvents : specialEvents;
const getCardWidth = () => { const getCardWidth = () => {
if (isLooping) return '300px'; if (isLooping) return '330px';
const count = specialEvents.length; const count = specialEvents.length;
if (count === 1) return '480px'; if (count === 1) return '510px';
if (count === 2) return '400px'; if (count === 2) return '430px';
if (count === 3) return '340px'; if (count === 3) return '370px';
return '290px'; // 4 events return '310px'; // 4 events
}; };
const cardWidth = getCardWidth(); const cardWidth = getCardWidth();
@@ -110,13 +110,10 @@ const SpecialEventsBanner: React.FC<SpecialEventsBannerProps> = ({ specialEvents
} }
.seb-card-img { .seb-card-img {
transition: transform 0.7s ease; transition: transform 0.7s cubic-bezier(0.25, 0.46, 0.45, 0.94);
will-change: transform;
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
} }
.seb-card:hover .seb-card-img { .seb-card:hover .seb-card-img {
transform: skewX(5deg) skewY(3deg) scale(1.35) translateZ(0) !important; transform: skewX(5deg) skewY(3deg) scale(1.35) !important;
} }
.seb-card:hover { .seb-card:hover {
box-shadow: 0 0 50px 6px rgba(250,204,21,0.20); box-shadow: 0 0 50px 6px rgba(250,204,21,0.20);
@@ -203,7 +200,7 @@ const SpecialEventsBanner: React.FC<SpecialEventsBannerProps> = ({ specialEvents
{/* ── CARD SLIDER (Slanted, inherits skewY(-3deg) from parent) ── */} {/* ── CARD SLIDER (Slanted, inherits skewY(-3deg) from parent) ── */}
<div <div
className="seb-slider-container" className="seb-slider-container"
style={{ padding: '16px 0 20px' }} style={{ padding: '40px 0 40px' }}
> >
<div className={isLooping ? "seb-track" : "seb-static-track"}> <div className={isLooping ? "seb-track" : "seb-static-track"}>
{displayEvents.map((event, idx) => ( {displayEvents.map((event, idx) => (
@@ -214,10 +211,10 @@ const SpecialEventsBanner: React.FC<SpecialEventsBannerProps> = ({ specialEvents
style={{ style={{
width: cardWidth, width: cardWidth,
maxWidth: '85vw', maxWidth: '85vw',
height: '400px', height: '440px',
marginRight: idx === displayEvents.length - 1 ? '0px' : '-28px', marginRight: idx === displayEvents.length - 1 ? '0px' : '-28px',
clipPath: 'polygon(9% 0%,100% 0%,91% 100%,0% 100%)', clipPath: 'polygon(9% 0%,100% 0%,91% 100%,0% 100%)',
transform: 'skewX(-5deg)', transform: `skewX(-5deg) translateY(${idx % 2 === 0 ? '20px' : '-20px'})`,
position: 'relative', position: 'relative',
}} }}
> >
@@ -226,7 +223,7 @@ const SpecialEventsBanner: React.FC<SpecialEventsBannerProps> = ({ specialEvents
className="seb-card-img absolute inset-0 bg-cover bg-center" className="seb-card-img absolute inset-0 bg-cover bg-center"
style={{ style={{
backgroundImage: 'url(' + (event.image || defaultImage) + ')', backgroundImage: 'url(' + (event.image || defaultImage) + ')',
transform: 'skewX(5deg) skewY(3deg) scale(1.22) translateZ(0)', transform: 'skewX(5deg) skewY(3deg) scale(1.22)',
}} }}
/> />

View File

@@ -18,7 +18,7 @@ const StatsSection: React.FC<StatsSectionProps> = ({ events }) => {
if (events) { if (events) {
const nonTech = events.filter(e => e.category === 'NON-TECHNICAL').length; const nonTech = events.filter(e => e.category === 'NON-TECHNICAL').length;
const tech = events.filter(e => e.category === 'TECHNICAL').length; const tech = events.filter(e => e.category === 'TECHNICAL').length;
const workshops = events.filter(e => e.category === 'WORKSHOP').length; const workshops = events.filter(e => e.category === 'WORKSHOP' || e.category === 'CENTRE-ACTIVITY').length;
setCounts({ setCounts({
nonTechnical: nonTech, nonTechnical: nonTech,
@@ -43,7 +43,7 @@ const StatsSection: React.FC<StatsSectionProps> = ({ events }) => {
delay: 0.2 delay: 0.2
}, },
{ {
label: "Workshops", label: "Centre-Based Activities",
value: counts.workshops, value: counts.workshops,
suffix: "+", suffix: "+",
delay: 0.3 delay: 0.3

View File

@@ -32,26 +32,121 @@ const UpcomingEventsSlider: React.FC<UpcomingEventsSliderProps> = ({
}); });
}, [events]); }, [events]);
// Duplicate events multiple times to ensure the track is always filled, then double it for the 50% translation loop const displayEvents = useMemo(() => {
const minItems = 10; if (sortedEvents.length === 0) return [];
const repeats = Math.max(1, Math.ceil(minItems / Math.max(1, sortedEvents.length))); if (sortedEvents.length === 1) {
const repeatedSegment = Array(repeats).fill(sortedEvents).flat(); return [
const loopEvents = [...repeatedSegment, ...repeatedSegment]; ...sortedEvents,
{
id: 'coming-soon-1',
title: 'Experience Coming Soon',
location: 'To Be Announced',
date: 'Stay Tuned',
category: 'UPCOMING',
image: 'https://images.unsplash.com/photo-1504384308090-c894fdcc538d?auto=format&fit=crop&q=80&w=800',
isComingSoon: true,
club: 'RIT Events Team',
coordinator: 'Hub Admins',
event_summary: 'We are curating the next exciting experience for you. Keep an eye out for upcoming announcements and registrations!',
status: 'APPROVED',
pricingType: 'FREE',
maxParticipants: 100,
registrationDeadline: '',
durationDays: 1,
deptLimits: {},
deptSectionLimits: {},
currentParticipants: 0,
currentDeptCounts: {},
currentDeptSectionCounts: {},
created_by: '',
participantType: 'BOTH',
verificationStatus: 'APPROVED'
} as any,
{
id: 'coming-soon-2',
title: 'More Events Unveiling Soon',
location: 'To Be Announced',
date: 'Stay Tuned',
category: 'STAY TUNED',
image: 'https://images.unsplash.com/photo-1540575467063-178a50c2df87?auto=format&fit=crop&q=80&w=800',
isComingSoon: true,
club: 'RIT Events Team',
coordinator: 'Hub Admins',
event_summary: 'We are preparing more engaging workshops and activities. Check back later for new updates!',
status: 'APPROVED',
pricingType: 'FREE',
maxParticipants: 100,
registrationDeadline: '',
durationDays: 1,
deptLimits: {},
deptSectionLimits: {},
currentParticipants: 0,
currentDeptCounts: {},
currentDeptSectionCounts: {},
created_by: '',
participantType: 'BOTH',
verificationStatus: 'APPROVED'
} as any
];
}
if (sortedEvents.length === 2) {
return [
...sortedEvents,
{
id: 'coming-soon-1',
title: 'Experience Coming Soon',
location: 'To Be Announced',
date: 'Stay Tuned',
category: 'UPCOMING',
image: 'https://images.unsplash.com/photo-1504384308090-c894fdcc538d?auto=format&fit=crop&q=80&w=800',
isComingSoon: true,
club: 'RIT Events Team',
coordinator: 'Hub Admins',
event_summary: 'We are curating the next exciting experience for you. Keep an eye out for upcoming announcements and registrations!',
status: 'APPROVED',
pricingType: 'FREE',
maxParticipants: 100,
registrationDeadline: '',
durationDays: 1,
deptLimits: {},
deptSectionLimits: {},
currentParticipants: 0,
currentDeptCounts: {},
currentDeptSectionCounts: {},
created_by: '',
participantType: 'BOTH',
verificationStatus: 'APPROVED'
} as any
];
}
return sortedEvents;
}, [sortedEvents]);
if (sortedEvents.length === 0) return null; const isMarquee = sortedEvents.length >= 3;
const loopEvents = useMemo(() => {
if (!isMarquee) return displayEvents;
const minItems = 10;
const repeats = Math.max(1, Math.ceil(minItems / Math.max(1, displayEvents.length)));
const repeatedSegment = Array(repeats).fill(displayEvents).flat();
return [...repeatedSegment, ...repeatedSegment];
}, [displayEvents, isMarquee]);
if (displayEvents.length === 0) return null;
return ( return (
<div className="py-24 px-6 md:px-12 lg:px-24 bg-white overflow-hidden"> <div className="py-24 px-6 md:px-12 lg:px-24 bg-white overflow-hidden">
<style> <style>
{` {`
@keyframes merryGoRound { @keyframes merryGoRound {
0% { transform: translateX(0); } 0% { transform: translate3d(0, 0, 0); }
100% { transform: translateX(-50%); } 100% { transform: translate3d(-50%, 0, 0); }
} }
.animate-merry-go-round { .animate-merry-go-round {
animation: merryGoRound 45s linear infinite; animation: merryGoRound 45s linear infinite;
display: flex; display: flex;
width: max-content; width: max-content;
will-change: transform;
} }
.animate-merry-go-round:hover { .animate-merry-go-round:hover {
animation-play-state: paused; animation-play-state: paused;
@@ -67,21 +162,32 @@ const UpcomingEventsSlider: React.FC<UpcomingEventsSliderProps> = ({
<div className="relative overflow-hidden w-full"> <div className="relative overflow-hidden w-full">
{/* Gradient masks removed to eliminate fogginess */} {/* Gradient masks removed to eliminate fogginess */}
<div className="animate-merry-go-round gap-6 md:gap-8"> <div className={`${isMarquee ? 'animate-merry-go-round' : 'flex flex-wrap justify-center'} gap-6 md:gap-8`}>
{loopEvents.map((event, idx) => ( {loopEvents.map((event, idx) => (
<div <div
key={`${event.id}-${idx}`} key={`${event.id}-${idx}`}
className="w-[280px] md:w-[350px] lg:w-[400px] flex-shrink-0 bg-white rounded-[2.5rem] p-5 group transition-all shadow-xl shadow-gray-900/5 hover:-translate-y-1 border border-gray-200" className={`w-[280px] md:w-[350px] lg:w-[400px] flex-shrink-0 bg-white rounded-[2.5rem] p-5 group transition-all shadow-xl shadow-gray-900/5 hover:-translate-y-1 border border-gray-200 transform-gpu ${
(event as any).isComingSoon ? 'opacity-80 hover:opacity-100' : ''
}`}
> >
<div className="h-48 md:h-56 w-full rounded-[2rem] overflow-hidden mb-6 md:mb-8 relative"> <div className="h-48 md:h-56 w-full rounded-[2rem] overflow-hidden mb-6 md:mb-8 relative bg-slate-900">
<img <img
src={event.image} src={event.image}
alt={event.title} alt={event.title}
className="w-full h-full object-cover transition-transform duration-1000 group-hover:scale-110" className={`w-full h-full object-cover transition-transform duration-1000 ${
(event as any).isComingSoon ? 'opacity-40 grayscale group-hover:scale-105' : 'group-hover:scale-110'
}`}
/> />
<div className="absolute top-4 right-4 bg-white px-4 py-2 rounded-full border border-gray-200"> <div className="absolute top-4 right-4 bg-white px-4 py-2 rounded-full border border-gray-200">
<span className="text-[9px] font-black text-[#1A202C] uppercase tracking-widest">{event.category}</span> <span className="text-[9px] font-black text-[#1A202C] uppercase tracking-widest">{event.category}</span>
</div> </div>
{(event as any).isComingSoon && (
<div className="absolute inset-0 flex items-center justify-center">
<span className="px-5 py-2.5 bg-amber-500/90 text-slate-950 text-[10px] font-black uppercase tracking-[0.2em] rounded-xl shadow-lg border border-amber-400/20 backdrop-blur-xs flex items-center gap-2">
<i className="fas fa-lock text-[8px]"></i> Coming Soon
</span>
</div>
)}
</div> </div>
<div className="px-2 pb-2 md:pb-4"> <div className="px-2 pb-2 md:pb-4">
<h4 className="text-xl md:text-2xl font-serif text-[#1A202C] mb-4 md:mb-6 group-hover:text-[#f97316] transition-colors line-clamp-1"> <h4 className="text-xl md:text-2xl font-serif text-[#1A202C] mb-4 md:mb-6 group-hover:text-[#f97316] transition-colors line-clamp-1">
@@ -91,9 +197,21 @@ const UpcomingEventsSlider: React.FC<UpcomingEventsSliderProps> = ({
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<i className="far fa-calendar-alt text-[#f97316]"></i> <i className="far fa-calendar-alt text-[#f97316]"></i>
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">
{event.date} {new Date(event.date).toLocaleString('en-IN', {
<span className="w-1 h-1 bg-gray-300 rounded-full"></span> timeZone: 'Asia/Kolkata',
<span className="text-[#f97316]">{event.schedule?.[0]?.start_time || ''}</span> year: 'numeric',
month: 'short',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hour12: false
}) + ' IST'}
{event.schedule?.[0]?.start_time && (
<>
<span className="w-1 h-1 bg-gray-300 rounded-full"></span>
<span className="text-[#f97316]">{event.schedule[0].start_time}</span>
</>
)}
</span> </span>
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
@@ -102,12 +220,14 @@ const UpcomingEventsSlider: React.FC<UpcomingEventsSliderProps> = ({
</div> </div>
</div> </div>
<div className="mt-6 md:mt-8 pt-4 md:pt-6 border-t border-gray-200/30 flex justify-between items-center"> <div className="mt-6 md:mt-8 pt-4 md:pt-6 border-t border-gray-200/30 flex justify-between items-center">
<span className="text-[8px] md:text-[9px] text-gray-400 font-black uppercase tracking-widest">Limited Access</span> <span className="text-[8px] md:text-[9px] text-gray-400 font-black uppercase tracking-widest">
{(event as any).isComingSoon ? 'Stay Tuned' : 'Limited Access'}
</span>
<button <button
onClick={() => setSelectedEvent(event)} onClick={() => setSelectedEvent(event)}
className="text-[#1A202C] text-[9px] md:text-[10px] font-black uppercase tracking-[0.3em] hover:text-[#f97316] transition-colors flex items-center gap-2" className="text-[#1A202C] text-[9px] md:text-[10px] font-black uppercase tracking-[0.3em] hover:text-[#f97316] transition-colors flex items-center gap-2 cursor-pointer"
> >
Details <i className="fas fa-arrow-right text-[8px]"></i> {(event as any).isComingSoon ? 'Sneak Peek' : 'Details'} <i className="fas fa-arrow-right text-[8px]"></i>
</button> </button>
</div> </div>
</div> </div>
@@ -118,11 +238,11 @@ const UpcomingEventsSlider: React.FC<UpcomingEventsSliderProps> = ({
{selectedEvent && ( {selectedEvent && (
<Portal> <Portal>
<div className="fixed inset-0 z-[10000] flex items-center justify-center p-4 sm:p-6 bg-slate-900/60 backdrop-blur-sm animate-in fade-in duration-300"> <div className="fixed inset-0 z-[10000] flex items-center justify-center p-4 sm:p-6 bg-slate-900/60 backdrop-blur-sm animate-in fade-in duration-300" onClick={() => setSelectedEvent(null)}>
<div className="bg-white rounded-[2rem] w-full max-w-lg shadow-2xl animate-in zoom-in-95 duration-500 max-h-[85vh] flex flex-col relative"> <div className="bg-white rounded-[2rem] w-full max-w-lg shadow-2xl animate-in zoom-in-95 duration-300 max-h-[85vh] flex flex-col relative" onClick={e => e.stopPropagation()}>
<button <button
onClick={() => setSelectedEvent(null)} onClick={() => setSelectedEvent(null)}
className="absolute top-6 right-6 w-8 h-8 rounded-full bg-slate-100 text-slate-500 hover:bg-slate-200 hover:text-slate-800 transition-all flex items-center justify-center z-10" className="absolute top-6 right-6 w-8 h-8 rounded-full bg-slate-100 text-slate-500 hover:bg-slate-200 hover:text-slate-800 transition-all flex items-center justify-center z-10 cursor-pointer"
> >
<i className="fas fa-times text-sm"></i> <i className="fas fa-times text-sm"></i>
</button> </button>
@@ -144,7 +264,9 @@ const UpcomingEventsSlider: React.FC<UpcomingEventsSliderProps> = ({
<div className="flex items-center justify-between gap-2 mb-2"> <div className="flex items-center justify-between gap-2 mb-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="w-1 h-3 bg-[#f97316] rounded-full"></div> <div className="w-1 h-3 bg-[#f97316] rounded-full"></div>
<h4 className="text-[10px] font-black text-slate-800 uppercase tracking-[0.2em]">Summary</h4> <h4 className="text-[10px] font-black text-slate-800 uppercase tracking-[0.2em]">
{(selectedEvent as any).isComingSoon ? 'Experience Preview' : 'Summary'}
</h4>
</div> </div>
{userRole === 'STUDENT' && bookedEventIds.includes(selectedEvent.id) && ( {userRole === 'STUDENT' && bookedEventIds.includes(selectedEvent.id) && (
<span className="px-2.5 py-1 bg-emerald-50 text-emerald-600 border border-emerald-200 text-[8px] font-black rounded-lg uppercase tracking-wider"> <span className="px-2.5 py-1 bg-emerald-50 text-emerald-600 border border-emerald-200 text-[8px] font-black rounded-lg uppercase tracking-wider">
@@ -161,36 +283,10 @@ const UpcomingEventsSlider: React.FC<UpcomingEventsSliderProps> = ({
</div> </div>
<div className="mt-4 shrink-0 border-t border-gray-100 pt-6 flex flex-col gap-3"> <div className="mt-4 shrink-0 border-t border-gray-100 pt-6 flex flex-col gap-3">
{userRole === 'STUDENT' && onToggleBooking && (
<>
{bookedEventIds.includes(selectedEvent.id) ? (
<button
onClick={() => {
if (window.confirm(`Are you sure you want to cancel your registration for "${selectedEvent.title}"?`)) {
onToggleBooking(selectedEvent.id);
setSelectedEvent(null);
}
}}
className="w-full py-4 bg-rose-600 text-white rounded-2xl font-black uppercase text-[10px] tracking-widest hover:bg-rose-700 transition-all shadow-xl shadow-rose-200 focus:outline-none focus:ring-4 focus:ring-rose-100"
>
Cancel Registration
</button>
) : (
<button
onClick={() => {
onToggleBooking(selectedEvent.id);
setSelectedEvent(null);
}}
className="w-full py-4 bg-emerald-600 text-white rounded-2xl font-black uppercase text-[10px] tracking-widest hover:bg-emerald-700 transition-all shadow-xl shadow-emerald-200 focus:outline-none focus:ring-4 focus:ring-emerald-100"
>
Get Tickets
</button>
)}
</>
)}
<button <button
onClick={() => setSelectedEvent(null)} onClick={() => setSelectedEvent(null)}
className="w-full py-4 bg-slate-900 text-white rounded-2xl font-black uppercase text-[10px] tracking-widest hover:bg-black transition-all shadow-xl shadow-slate-200 focus:outline-none focus:ring-4 focus:ring-slate-100" className="w-full py-4 bg-slate-900 text-white rounded-2xl font-black uppercase text-[10px] tracking-widest hover:bg-black transition-all shadow-xl shadow-slate-200 focus:outline-none focus:ring-4 focus:ring-slate-100 cursor-pointer"
> >
Close Summary Close Summary
</button> </button>

View File

@@ -618,6 +618,9 @@ window.fetch = async function(input: RequestInfo | URL, init?: RequestInit): Pro
for (const event of events) { for (const event of events) {
const count = registrations.filter(r => String(r.eventId || r.event_id) === String(event.id)).length; const count = registrations.filter(r => String(r.eventId || r.event_id) === String(event.id)).length;
event.currentParticipants = count; event.currentParticipants = count;
if (event["total capacity"] !== undefined) {
event.maxParticipants = event["total capacity"];
}
if (!['APPROVED', 'COMPLETED', 'CANCELLED'].includes(event.status)) { if (!['APPROVED', 'COMPLETED', 'CANCELLED'].includes(event.status)) {
const conflictMsg = await getConflictMessage(event); const conflictMsg = await getConflictMessage(event);
@@ -741,6 +744,7 @@ window.fetch = async function(input: RequestInfo | URL, init?: RequestInit): Pro
guestSocialProfile: payload.socialProfile || null, guestSocialProfile: payload.socialProfile || null,
requirements: payload.requirements || [], requirements: payload.requirements || [],
targetedSections: payload.targetedSections || [], targetedSections: payload.targetedSections || [],
targetDepartments: payload.targetDepartments || [],
groupRequestId: payload.groupRequestId || null, groupRequestId: payload.groupRequestId || null,
sponsors: payload.sponsors || [], sponsors: payload.sponsors || [],
budget: budgetStr === "" ? 0.0 : parseFloat(budgetStr), budget: budgetStr === "" ? 0.0 : parseFloat(budgetStr),
@@ -751,11 +755,14 @@ window.fetch = async function(input: RequestInfo | URL, init?: RequestInit): Pro
isPublicEvent: !!payload.isPublicEvent, isPublicEvent: !!payload.isPublicEvent,
image: payload.image || null, image: payload.image || null,
targetedBatch: payload.targetedBatch || null, targetedBatch: payload.targetedBatch || null,
"total capacity": payload.maxParticipants === '' || payload.maxParticipants === undefined ? null : Number(payload.maxParticipants),
maxParticipants: payload.maxParticipants === '' || payload.maxParticipants === undefined ? null : Number(payload.maxParticipants),
proposer: { proposer: {
id: proposer.id, id: proposer.id,
fullName: proposer.fullName, fullName: proposer.fullName,
email: proposer.email, email: proposer.email,
role: proposer.role role: proposer.role,
department: proposer.department || "N/A"
}, },
dayConfigs: payload.dayConfigs || [], dayConfigs: payload.dayConfigs || [],
deptLimits: payload.deptLimits || {}, deptLimits: payload.deptLimits || {},
@@ -995,6 +1002,12 @@ window.fetch = async function(input: RequestInfo | URL, init?: RequestInit): Pro
if (payload.institution !== undefined) updatedFields.institution = payload.institution; if (payload.institution !== undefined) updatedFields.institution = payload.institution;
if (payload.department !== undefined) updatedFields.department = payload.department; if (payload.department !== undefined) updatedFields.department = payload.department;
if (payload.venue !== undefined) updatedFields.location = payload.venue; if (payload.venue !== undefined) updatedFields.location = payload.venue;
if (payload.category !== undefined) updatedFields.category = payload.category;
if (payload.maxParticipants !== undefined) {
const val = payload.maxParticipants === '' ? null : Number(payload.maxParticipants);
updatedFields["total capacity"] = val;
updatedFields.maxParticipants = val;
}
if (payload.guestName !== undefined) updatedFields.guestName = payload.guestName; if (payload.guestName !== undefined) updatedFields.guestName = payload.guestName;
if (payload.socialProfile !== undefined) updatedFields.guestSocialProfile = payload.socialProfile; if (payload.socialProfile !== undefined) updatedFields.guestSocialProfile = payload.socialProfile;
if (payload.academicYears !== undefined) updatedFields.academicYears = payload.academicYears; if (payload.academicYears !== undefined) updatedFields.academicYears = payload.academicYears;