From a84cd5caa80e8a724f515c06966858201bd489ed Mon Sep 17 00:00:00 2001 From: Anuraj Upadhyay Date: Mon, 24 Aug 2026 14:36:17 +0530 Subject: [PATCH 1/2] vtool button in event details and hero UI changes --- src/components/ui/EventCard.tsx | 87 +++++++----- src/components/ui/EventDetailsModal.tsx | 175 ++++++++++++++++++++++++ src/data/events.ts | 19 ++- src/pages/events/EventsPage.tsx | 13 +- src/pages/home/components/Hero.tsx | 6 +- src/types/index.ts | 1 + src/utils/vtool.ts | 6 + 7 files changed, 268 insertions(+), 39 deletions(-) create mode 100644 src/components/ui/EventDetailsModal.tsx create mode 100644 src/utils/vtool.ts diff --git a/src/components/ui/EventCard.tsx b/src/components/ui/EventCard.tsx index f751acd..0c729d9 100644 --- a/src/components/ui/EventCard.tsx +++ b/src/components/ui/EventCard.tsx @@ -6,11 +6,16 @@ import type { Event } from '../../types'; interface EventCardProps { event: Event; className?: string; + onOpenDetails?: () => void; } -const EventCard: React.FC = ({ event, className = '' }) => { +const EventCard: React.FC = ({ event, className = '', onOpenDetails }) => { const isCompleted = String((event && event.status) || '').toLowerCase() === 'completed'; const handlePrimary = () => { + if (onOpenDetails) { + onOpenDetails(); + return; + } const registerLink = event.actionLinks ?.find((l: string) => l.startsWith('register:')) ?.split('register:')[1] @@ -40,7 +45,10 @@ const EventCard: React.FC = ({ event, className = '' }) => { return (
= ({ event, className = '' }) => {
- - {isCompleted - ? 'Event Ended' - : event.actionLinks?.some((l: string) => l.startsWith('register:')) - ? 'Register' - : event.actionLinks?.some((l: string) => l.startsWith('livestream:')) - ? 'Join Livestream' - : 'View Details'} - + { + e.stopPropagation(); + if (onOpenDetails) onOpenDetails(); + else if (!isCompleted) handlePrimary(); + }} + disabled={!onOpenDetails && isCompleted} + > + {onOpenDetails + ? 'View Details' + : isCompleted + ? 'Event Ended' + : event.actionLinks?.some((l: string) => l.startsWith('register:')) + ? 'Register' + : event.actionLinks?.some((l: string) => l.startsWith('livestream:')) + ? 'Join Livestream' + : 'View Details'} + - - - + { + e.stopPropagation(); + if (!isCompleted) handleShare(); + }} + aria-disabled={isCompleted} + disabled={isCompleted} + > + +
diff --git a/src/components/ui/EventDetailsModal.tsx b/src/components/ui/EventDetailsModal.tsx new file mode 100644 index 0000000..ff5ba6f --- /dev/null +++ b/src/components/ui/EventDetailsModal.tsx @@ -0,0 +1,175 @@ +import React, { useEffect } from 'react'; +import { createPortal } from 'react-dom'; +import { Clock, MapPin, Tag, Users, X, ExternalLink } from 'lucide-react'; +import type { Event } from '../../types'; +import { getVtoolHref } from '../../utils/vtool'; + +interface EventDetailsModalProps { + event: Event; + onClose: () => void; +} + +const EventDetailsModal: React.FC = ({ event, onClose }) => { + const vtoolHref = getVtoolHref(event.vtool); + + useEffect(() => { + const previousOverflow = document.body.style.overflow; + document.body.style.overflow = 'hidden'; + + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose(); + }; + window.addEventListener('keydown', onKeyDown); + + return () => { + document.body.style.overflow = previousOverflow; + window.removeEventListener('keydown', onKeyDown); + }; + }, [onClose]); + + const contacts = + event.actionLinks?.filter((link) => link.toLowerCase().startsWith('contact:')) ?? []; + const otherLinks = + event.actionLinks?.filter((link) => !link.toLowerCase().startsWith('contact:')) ?? []; + + return createPortal( +
+ +
+

+ {event.type} + {event.status ? ` · ${event.status}` : ''} +

+

+ {event.title} +

+
+
+ +
+

{event.description}

+ +
+
+ + + {event.startDate && event.endDate && event.startDate !== event.endDate + ? `${event.startDate} – ${event.endDate}` + : event.endDate || event.startDate || 'Date TBA'} + +
+
+ + {event.venue || 'Venue TBA'} +
+
+ + {event.category || 'General'} +
+
+ + + {Array.isArray(event.organisedBy) + ? event.organisedBy.join(', ') + : event.organisedBy || 'IEEE MSIT'} + +
+
+ + {event.prizes && event.prizes.length > 0 && ( +
+

Prizes

+
    + {event.prizes.map((prize) => ( +
  • {prize}
  • + ))} +
+
+ )} + + {contacts.length > 0 && ( +
+

Contacts

+
    + {contacts.map((contact) => ( +
  • {contact.replace(/^contact:\s*/i, '')}
  • + ))} +
+
+ )} + + {otherLinks.length > 0 && ( +
+ {otherLinks.map((link) => { + const registerUrl = link.startsWith('register:') + ? link.split('register:')[1]?.trim() + : null; + const livestreamUrl = link.startsWith('livestream:') + ? link.split('livestream:')[1]?.trim() + : null; + const href = registerUrl || livestreamUrl || (/^https?:\/\//i.test(link) ? link : null); + if (!href) return null; + return ( + + {registerUrl ? 'Register' : livestreamUrl ? 'Livestream' : 'Open link'} + + + ); + })} +
+ )} + + {vtoolHref ? ( + + Open in vTools + + + ) : ( +

vTools link not available yet.

+ )} +
+ + , + document.body + ); +}; + +export default EventDetailsModal; diff --git a/src/data/events.ts b/src/data/events.ts index e53b834..8747e8d 100644 --- a/src/data/events.ts +++ b/src/data/events.ts @@ -6,6 +6,7 @@ export const events = [ status: 'completed', startDate: '22 April 2026', endDate: '22 April 2026', + vtool: '', venue: 'MSIT, Room No. 406', registrationType: 'free', actionLinks: [ @@ -26,6 +27,7 @@ export const events = [ status: 'completed', startDate: '23 June 2026', endDate: '23 June 2026', + vtool: '', venue: 'Online', registrationType: 'free', actionLinks: [ @@ -47,6 +49,7 @@ export const events = [ status: 'completed', startDate: '13th May 2026', endDate: '13th May 2026', + vtool: '', venue: 'Google Meet', registrationType: 'free', actionLinks: [ @@ -68,6 +71,7 @@ export const events = [ status: 'completed', startDate: '14th April 2026', endDate: '14th April 2026', + vtool: '', venue: 'MSIT', registrationType: 'free', actionLinks: ['contact: Mehak : +91 8368215595', 'contact: Satyam : +91 7451005000'], @@ -86,6 +90,7 @@ export const events = [ status: 'completed', startDate: '13 April, 2026', endDate: '13 April, 2026', + vtool: '', venue: 'Google Meet', registrationType: 'free', actionLinks: ['join: Online Webinar'], @@ -104,6 +109,7 @@ export const events = [ status: 'completed', startDate: '16 April 2026', endDate: '16 April 2026', + vtool: '', venue: 'Room No 206', registrationType: 'free', actionLinks: ['contact: JAYANT : 95999 18990', 'contact: RADHIKA : 96504 22130'], @@ -122,6 +128,7 @@ export const events = [ status: 'completed', startDate: '17th April 2026', endDate: '17th April 2026', + vtool: '', venue: 'Room no. 206', registrationType: 'free', actionLinks: ['contact: Sumit Roy : +91 8510020963', 'contact: Lakshay : +91 7988944892'], @@ -140,6 +147,7 @@ export const events = [ status: 'completed', startDate: '18th April, 2026', endDate: '18th April, 2026', + vtool: '', venue: 'Online', registrationType: 'free', actionLinks: [ @@ -161,6 +169,7 @@ export const events = [ status: 'completed', startDate: '21st April 2026', endDate: '22nd April 2026', + vtool: '', venue: 'Online', registrationType: 'free', actionLinks: ['contact: Vanisha Raj (SIG Lead) : +91 85058 26381'], @@ -182,6 +191,7 @@ export const events = [ status: 'completed', startDate: 'July 13th, 2025', endDate: 'July 13th, 2025', + vtool: '', venue: 'Live Workshop Session (Online)', registrationType: null, actionLinks: [ @@ -205,6 +215,7 @@ export const events = [ status: 'completed', startDate: 'Aug 19th 2026', endDate: 'Aug 21st 2026', + vtool: 'https://events.vtools.ieee.org/m/567513', venue: 'MSIT', registrationType: 'free', actionLinks: [ @@ -226,6 +237,7 @@ export const events = [ status: 'completed', startDate: '18 July 2026', endDate: '18 July 2026', + vtool: '', venue: 'Google Meet', registrationType: 'free', actionLinks: [ @@ -246,6 +258,7 @@ export const events = [ status: 'completed', startDate: '11th July 2026', endDate: '11th July 2026', + vtool: '', venue: 'Google Meet', registrationType: 'free', actionLinks: [ @@ -266,6 +279,7 @@ export const events = [ status: 'completed', startDate: '04th July 2026', endDate: '04th July 2026', + vtool: '', venue: 'Online Webinar', registrationType: 'free', actionLinks: [ @@ -286,7 +300,8 @@ export const events = [ // status: 'completed', // startDate: '2nd July 2026', // endDate: '2nd July 2026', - // venue: 'Online', + // vtool:'', + // venue: 'Online', // registrationType: 'free', // actionLinks: ['join: Online Session'], // prizes: null, @@ -303,6 +318,7 @@ export const events = [ status: 'completed', startDate: '27 June 2026', endDate: '27 June 2026', + vtool: '', venue: 'Online', registrationType: 'free', actionLinks: [ @@ -323,6 +339,7 @@ export const events = [ status: 'completed', startDate: '23 June 2026', endDate: '30 June 2026', + vtool: '', venue: 'Online', registrationType: 'free', actionLinks: ['https://wie.ieee.org'], diff --git a/src/pages/events/EventsPage.tsx b/src/pages/events/EventsPage.tsx index bb24c95..bb7b023 100644 --- a/src/pages/events/EventsPage.tsx +++ b/src/pages/events/EventsPage.tsx @@ -1,12 +1,15 @@ import React, { useState, useMemo, useEffect } from 'react'; import { events } from '../../data/data'; import EventCard from '../../components/ui/EventCard'; +import EventDetailsModal from '../../components/ui/EventDetailsModal'; import { ArrowLeft } from 'lucide-react'; import { HOME_PATH } from '../../constants/paths'; import { Link } from 'react-router-dom'; +import type { Event } from '../../types'; const EventsPage: React.FC = () => { const [activeFilter, setActiveFilter] = useState('All'); + const [selectedEvent, setSelectedEvent] = useState(null); const societies = useMemo(() => { const set = new Set(); @@ -106,7 +109,11 @@ const EventsPage: React.FC = () => {
{paginatedEvents.map((event, idx) => (
- + setSelectedEvent(event)} + />
))}
@@ -158,6 +165,10 @@ const EventsPage: React.FC = () => { + + {selectedEvent && ( + setSelectedEvent(null)} /> + )} ); }; diff --git a/src/pages/home/components/Hero.tsx b/src/pages/home/components/Hero.tsx index 4e4316c..4d6109f 100644 --- a/src/pages/home/components/Hero.tsx +++ b/src/pages/home/components/Hero.tsx @@ -106,15 +106,15 @@ const Hero = () => {

We're a community of engineering students who organize{' '} - + workshops, hackathons, and tech events {' '} to help you learn{' '} - + cutting-edge technology , build amazing projects, and connect with{' '} - + like-minded innovators . diff --git a/src/types/index.ts b/src/types/index.ts index 8c76e15..e5b8591 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -8,6 +8,7 @@ export interface Event { status: EventStatusType; startDate: string | null; endDate: string | null; + vtool?: string | null; venue: string | null; registrationType: RegistrationTypeType | null; actionLinks: string[] | null; diff --git a/src/utils/vtool.ts b/src/utils/vtool.ts new file mode 100644 index 0000000..7344ddd --- /dev/null +++ b/src/utils/vtool.ts @@ -0,0 +1,6 @@ +export const getVtoolHref = (vtool?: string | null): string | null => { + const value = vtool?.trim(); + if (!value) return null; + if (/^https?:\/\//i.test(value)) return value; + return `https://${value}`; +}; From 158406c069751dc3822fbe35d468b4323a48b8f5 Mon Sep 17 00:00:00 2001 From: Anuraj Upadhyay Date: Mon, 24 Aug 2026 15:06:14 +0530 Subject: [PATCH 2/2] everything prettier --- src/components/ui/EventCard.tsx | 84 ++++++++++++------------- src/components/ui/EventDetailsModal.tsx | 3 +- 2 files changed, 44 insertions(+), 43 deletions(-) diff --git a/src/components/ui/EventCard.tsx b/src/components/ui/EventCard.tsx index 0c729d9..24ca922 100644 --- a/src/components/ui/EventCard.tsx +++ b/src/components/ui/EventCard.tsx @@ -103,50 +103,50 @@ const EventCard: React.FC = ({ event, className = '', onOpenDeta

- { - e.stopPropagation(); - if (onOpenDetails) onOpenDetails(); - else if (!isCompleted) handlePrimary(); - }} - disabled={!onOpenDetails && isCompleted} - > - {onOpenDetails - ? 'View Details' + l.startsWith('register:')) - ? 'Register' - : event.actionLinks?.some((l: string) => l.startsWith('livestream:')) - ? 'Join Livestream' - : 'View Details'} - + ? 'bg-gray-200 text-gray-600 cursor-default' + : 'bg-primary text-white hover:bg-primary-hover hover:shadow-md' + }`} + onClick={(e) => { + e.stopPropagation(); + if (onOpenDetails) onOpenDetails(); + else if (!isCompleted) handlePrimary(); + }} + disabled={!onOpenDetails && isCompleted} + > + {onOpenDetails + ? 'View Details' + : isCompleted + ? 'Event Ended' + : event.actionLinks?.some((l: string) => l.startsWith('register:')) + ? 'Register' + : event.actionLinks?.some((l: string) => l.startsWith('livestream:')) + ? 'Join Livestream' + : 'View Details'} + - { - e.stopPropagation(); - if (!isCompleted) handleShare(); - }} - aria-disabled={isCompleted} - disabled={isCompleted} - > - - + { + e.stopPropagation(); + if (!isCompleted) handleShare(); + }} + aria-disabled={isCompleted} + disabled={isCompleted} + > + +
diff --git a/src/components/ui/EventDetailsModal.tsx b/src/components/ui/EventDetailsModal.tsx index ff5ba6f..fda2433 100644 --- a/src/components/ui/EventDetailsModal.tsx +++ b/src/components/ui/EventDetailsModal.tsx @@ -134,7 +134,8 @@ const EventDetailsModal: React.FC = ({ event, onClose }) const livestreamUrl = link.startsWith('livestream:') ? link.split('livestream:')[1]?.trim() : null; - const href = registerUrl || livestreamUrl || (/^https?:\/\//i.test(link) ? link : null); + const href = + registerUrl || livestreamUrl || (/^https?:\/\//i.test(link) ? link : null); if (!href) return null; return (