diff --git a/public/images/avatar/blanca-arcilia-florez-urrutia.jpeg b/public/images/avatar/blanca-arcilia-florez-urrutia.jpeg new file mode 100644 index 0000000..492aea5 Binary files /dev/null and b/public/images/avatar/blanca-arcilia-florez-urrutia.jpeg differ diff --git a/public/images/avatar/carolina-monsalve-delgado.jpeg b/public/images/avatar/carolina-monsalve-delgado.jpeg new file mode 100644 index 0000000..1a9501e Binary files /dev/null and b/public/images/avatar/carolina-monsalve-delgado.jpeg differ diff --git a/public/images/avatar/gladys-agudelo-patino.jpeg b/public/images/avatar/gladys-agudelo-patino.jpeg new file mode 100644 index 0000000..9e38a94 Binary files /dev/null and b/public/images/avatar/gladys-agudelo-patino.jpeg differ diff --git a/public/images/avatar/gustavo-diaz-jaimes.jpg b/public/images/avatar/gustavo-diaz-jaimes.jpg new file mode 100644 index 0000000..10ab0b3 Binary files /dev/null and b/public/images/avatar/gustavo-diaz-jaimes.jpg differ diff --git a/public/images/avatar/jose-angel-botero-cuervo.jpeg b/public/images/avatar/jose-angel-botero-cuervo.jpeg new file mode 100644 index 0000000..bd69d6f Binary files /dev/null and b/public/images/avatar/jose-angel-botero-cuervo.jpeg differ diff --git a/public/images/avatar/simon-joel-torres-castaneda.jpeg b/public/images/avatar/simon-joel-torres-castaneda.jpeg new file mode 100644 index 0000000..2e46c43 Binary files /dev/null and b/public/images/avatar/simon-joel-torres-castaneda.jpeg differ diff --git a/src/assets/data/schedule.ts b/src/assets/data/schedule.ts index b69eb09..1a37612 100644 --- a/src/assets/data/schedule.ts +++ b/src/assets/data/schedule.ts @@ -1118,7 +1118,7 @@ export const scheduleEvents: ScheduleEvent[] = [ date: "2026-07-26", title: "Sponsor Space: Aimpoint", displayTitle: "Sponsor Space: Aimpoint", - speaker: "Aimpoint", + speaker: "Felipe Sánchez", label: "Sponsor Space", }, { diff --git a/src/assets/data/team.ts b/src/assets/data/team.ts index 3db820f..3546bf2 100644 --- a/src/assets/data/team.ts +++ b/src/assets/data/team.ts @@ -104,11 +104,40 @@ export const teamMembers: TeamMember[] = [ export const volunteerMembers: VolunteerMember[] = [ { - slug: "valerie-arismendy", - name: "Valerie Arismendy", - role: "Co-host de auditorio", - image: "/images/avatar/valerie-arismendy.webp", - linkedin: "https://www.linkedin.com/in/valerie-arismendy/", + slug: "alejandro-alvarez", + name: "Alejandro Alvarez", + role: "Redes Sociales", + image: "/images/avatar/alejandro-alvarez.jpg", + }, + { + slug: "blanca-arcilia-florez-urrutia", + name: "Blanca Arcilia Flórez Urrutia", + role: "Logistics", + image: "/images/avatar/blanca-arcilia-florez-urrutia.jpeg", + }, + { + slug: "carolina-monsalve-delgado", + name: "Carolina Monsalve Delgado", + role: "Logistics", + image: "/images/avatar/carolina-monsalve-delgado.jpeg", + }, + { + slug: "gladys-agudelo-patino", + name: "Gladys Agudelo Patiño", + role: "Logistics", + image: "/images/avatar/gladys-agudelo-patino.jpeg", + }, + { + slug: "gustavo-diaz-jaimes", + name: "Gustavo Diaz Jaimes", + role: "Auditorium", + image: "/images/avatar/gustavo-diaz-jaimes.jpg", + }, + { + slug: "jenny-acuna", + name: "Jenny Acuña", + role: "Logística", + image: "/images/avatar/jenny-acuna.jpeg", }, { slug: "jesus-mena", @@ -118,18 +147,10 @@ export const volunteerMembers: VolunteerMember[] = [ linkedin: "https://www.linkedin.com/in/yisuslinkon", }, { - slug: "yurley-sanchez-florez", - name: "Yurley Katterine Sanchez Florez", - role: "Co-host de auditorio", - image: "/images/avatar/yurley-sanchez.jpg", - linkedin: "https://www.linkedin.com/in/yursksf1/", - }, - { - slug: "veruzka-borges", - name: "Veruzka Borges", - role: "Co-host de auditorio", - image: "/images/avatar/veruzka-borges.png", - linkedin: "https://www.linkedin.com/in/veruzkab/", + slug: "jose-angel-botero-cuervo", + name: "José Ángel Botero Cuervo", + role: "Logistics", + image: "/images/avatar/jose-angel-botero-cuervo.jpeg", }, { slug: "juan-camilo-velasquez", @@ -147,15 +168,30 @@ export const volunteerMembers: VolunteerMember[] = [ linkedin: "https://www.linkedin.com/in/mateousmevalencia/", }, { - slug: "alejandro-alvarez", - name: "Alejandro Alvarez", - role: "Redes Sociales", - image: "/images/avatar/alejandro-alvarez.jpg", + slug: "simon-joel-torres-castaneda", + name: "Simón Joel Torres Castañeda", + role: "Logistics", + image: "/images/avatar/simon-joel-torres-castaneda.jpeg", }, { - slug: "jenny-acuna", - name: "Jenny Acuña", - role: "Logística", - image: "/images/avatar/jenny-acuna.jpeg", + slug: "valerie-arismendy", + name: "Valerie Arismendy", + role: "Co-host de auditorio", + image: "/images/avatar/valerie-arismendy.webp", + linkedin: "https://www.linkedin.com/in/valerie-arismendy/", + }, + { + slug: "veruzka-borges", + name: "Veruzka Borges", + role: "Co-host de auditorio", + image: "/images/avatar/veruzka-borges.png", + linkedin: "https://www.linkedin.com/in/veruzkab/", + }, + { + slug: "yurley-sanchez-florez", + name: "Yurley Katterine Sanchez Florez", + role: "Co-host de auditorio", + image: "/images/avatar/yurley-sanchez.jpg", + linkedin: "https://www.linkedin.com/in/yursksf1/", }, ]; diff --git a/src/assets/data/workshop-speakers.ts b/src/assets/data/workshop-speakers.ts index 0e65644..1ade93b 100644 --- a/src/assets/data/workshop-speakers.ts +++ b/src/assets/data/workshop-speakers.ts @@ -366,7 +366,7 @@ const workshopSpeakerListBase: SpeakerInput[] = [ { slug: "felipe-sanchez", talkKey: "fastmcp-pokedex", - name: "Felipe Sanchez", + name: "Felipe Sánchez", linkedin: "https://www.linkedin.com/in/felipe-sánchez-7765b8206", tracks: ["artificial-intelligence", "core-python"] as SpeakerTrack[], country: "Colombia", diff --git a/src/components/shadcn-studio/blocks/user-schedule-02/user-schedule-02.tsx b/src/components/shadcn-studio/blocks/user-schedule-02/user-schedule-02.tsx index 76c06d3..5750642 100644 --- a/src/components/shadcn-studio/blocks/user-schedule-02/user-schedule-02.tsx +++ b/src/components/shadcn-studio/blocks/user-schedule-02/user-schedule-02.tsx @@ -9,7 +9,7 @@ import { } from "lucide-react"; import Image from "next/image"; import Link from "next/link"; -import { useMemo, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { type DaySchedule, @@ -54,6 +54,11 @@ import { isSponsorSpaceEvent, resolveSponsorForScheduleEvent, } from "@/lib/schedule-sponsors"; +import { + getScheduleDateTime, + isScheduleEventNow, + parseScheduleNowOverride, +} from "@/lib/schedule-time"; import { resolveSpeakerImageUrl } from "@/lib/speaker-image-url"; import type { SponsorWithTier } from "@/lib/sponsors"; import { getTalkHref } from "@/lib/talks"; @@ -133,6 +138,7 @@ type ScheduleEventCardProps = { event: ScheduleEvent; t: (key: string) => string; locale: "en" | "es"; + isNow: boolean; }; type ScheduleSpeakerRowProps = { @@ -223,7 +229,12 @@ function ScheduleSponsorRow({ sponsor }: { sponsor: SponsorWithTier }) { ); } -function ScheduleEventCard({ event, t, locale }: ScheduleEventCardProps) { +function ScheduleEventCard({ + event, + t, + locale, + isNow, +}: ScheduleEventCardProps) { const category = getScheduleEventCategory(event); const sponsor = resolveSponsorForScheduleEvent(event); const keynote = event.keynoteSlug @@ -274,7 +285,18 @@ function ScheduleEventCard({ event, t, locale }: ScheduleEventCardProps) { const eventTitle = getLocalizedScheduleEventTitle(event, locale); return ( - + + {isNow ? ( +
+ {t("blocks.scheduleUi.now")} +
+ ) : null}
@@ -353,12 +375,51 @@ function ScheduleEventCard({ event, t, locale }: ScheduleEventCardProps) { const ScheduleCard = ({ scheduleData }: ScheduleCardProps) => { const { locale } = useLanguage(); const { t } = useTranslations(); + const [scheduleNow, setScheduleNow] = useState(); const [selectedDate, setSelectedDate] = useState( scheduleDays[0].date, ); const [activeTab, setActiveTab] = useState("all"); const [searchQuery, setSearchQuery] = useState(""); const [sortBy, setSortBy] = useState<"time" | "name" | "default">("default"); + const currentScheduleDate = scheduleNow + ? getScheduleDateTime(scheduleNow).date + : undefined; + + useEffect(() => { + const queryOverride = new URLSearchParams(window.location.search).get( + "scheduleNow", + ); + const override = parseScheduleNowOverride( + queryOverride ?? process.env.NEXT_PUBLIC_SCHEDULE_NOW, + ); + + if (override) { + setScheduleNow(override); + return; + } + + const updateNow = () => setScheduleNow(new Date()); + updateNow(); + + const interval = window.setInterval(updateNow, 30_000); + + return () => window.clearInterval(interval); + }, []); + + useEffect(() => { + if (!currentScheduleDate) { + return; + } + + const matchingDay = scheduleDays.find( + (day) => day.date === currentScheduleDate, + ); + + if (matchingDay) { + setSelectedDate(matchingDay.date); + } + }, [currentScheduleDate]); const selectedDay = scheduleDays.find((day) => day.date === selectedDate); @@ -579,8 +640,9 @@ const ScheduleCard = ({ scheduleData }: ScheduleCardProps) => { key={group.map((event) => event.id).join("-")} className={cn( "gap-4", - group.length > 1 && - "grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3", + group.length === 1 + ? "grid sm:grid-cols-2" + : "grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3", )} > {group.map((event) => ( @@ -589,6 +651,11 @@ const ScheduleCard = ({ scheduleData }: ScheduleCardProps) => { event={event} t={t} locale={locale} + isNow={ + scheduleNow + ? isScheduleEventNow(event, scheduleNow) + : false + } /> ))}
diff --git a/src/lib/i18n/blocks-en.ts b/src/lib/i18n/blocks-en.ts index a00eb5e..2147372 100644 --- a/src/lib/i18n/blocks-en.ts +++ b/src/lib/i18n/blocks-en.ts @@ -116,6 +116,12 @@ export const blocksEn = { "mateo-usme-valencia": "Keynotes Godfather", "alejandro-alvarez": "Social Networks", "jenny-acuna": "Logistics", + "blanca-arcilia-florez-urrutia": "Logistics", + "carolina-monsalve-delgado": "Logistics", + "gladys-agudelo-patino": "Logistics", + "gustavo-diaz-jaimes": "Auditorium", + "jose-angel-botero-cuervo": "Logistics", + "simon-joel-torres-castaneda": "Logistics", }, }, }, @@ -389,6 +395,7 @@ export const blocksEn = { keynote: "Keynote", workshop: "Workshop", other: "Event", + now: "Now", viewSpeaker: "View speaker", viewWorkshop: "View workshop", viewKeynote: "View keynote", diff --git a/src/lib/i18n/blocks-es.ts b/src/lib/i18n/blocks-es.ts index 2424206..9be0b89 100644 --- a/src/lib/i18n/blocks-es.ts +++ b/src/lib/i18n/blocks-es.ts @@ -120,6 +120,12 @@ export const blocksEs: BlocksMessages = { "mateo-usme-valencia": "Padrino Keynotes", "alejandro-alvarez": "Redes Sociales", "jenny-acuna": "Logística", + "blanca-arcilia-florez-urrutia": "Logística", + "carolina-monsalve-delgado": "Logística", + "gladys-agudelo-patino": "Logística", + "gustavo-diaz-jaimes": "Auditorio", + "jose-angel-botero-cuervo": "Logística", + "simon-joel-torres-castaneda": "Logística", }, }, }, @@ -394,6 +400,7 @@ export const blocksEs: BlocksMessages = { keynote: "Keynote", workshop: "Taller", other: "Evento", + now: "Ahora", viewSpeaker: "Ver ponente", viewWorkshop: "Ver taller", viewKeynote: "Ver keynote", diff --git a/src/lib/schedule-time.ts b/src/lib/schedule-time.ts new file mode 100644 index 0000000..3ddc394 --- /dev/null +++ b/src/lib/schedule-time.ts @@ -0,0 +1,79 @@ +import type { ScheduleEvent } from "@/assets/data/schedule"; + +const SCHEDULE_TIME_ZONE = "America/Bogota"; + +type ScheduleDateTime = { + date: string; + minutes: number; +}; + +function getPart( + parts: Intl.DateTimeFormatPart[], + type: Intl.DateTimeFormatPartTypes, +) { + return parts.find((part) => part.type === type)?.value ?? ""; +} + +export function getScheduleDateTime(now: Date): ScheduleDateTime { + const parts = new Intl.DateTimeFormat("en-CA", { + timeZone: SCHEDULE_TIME_ZONE, + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + hourCycle: "h23", + }).formatToParts(now); + const hour = Number.parseInt(getPart(parts, "hour"), 10); + const minute = Number.parseInt(getPart(parts, "minute"), 10); + + return { + date: `${getPart(parts, "year")}-${getPart(parts, "month")}-${getPart(parts, "day")}`, + minutes: hour * 60 + minute, + }; +} + +function parseHourRange(hour: string) { + const match = hour.match(/^\s*(\d{1,2}):(\d{2})\s*-\s*(\d{1,2}):(\d{2})\s*$/); + + if (!match) { + return undefined; + } + + return { + start: Number.parseInt(match[1], 10) * 60 + Number.parseInt(match[2], 10), + end: Number.parseInt(match[3], 10) * 60 + Number.parseInt(match[4], 10), + }; +} + +export function isScheduleEventNow(event: ScheduleEvent, now: Date) { + const range = parseHourRange(event.hour); + + if (!range) { + return false; + } + + const scheduleNow = getScheduleDateTime(now); + + return ( + event.date === scheduleNow.date && + scheduleNow.minutes >= range.start && + scheduleNow.minutes < range.end + ); +} + +export function parseScheduleNowOverride(value?: string | null) { + const trimmed = value?.trim(); + + if (!trimmed) { + return undefined; + } + + const date = new Date( + /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2})?$/.test(trimmed) + ? `${trimmed}-05:00` + : trimmed, + ); + + return Number.isNaN(date.getTime()) ? undefined : date; +}