Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
433 changes: 39 additions & 394 deletions app/(pages)/(hackers)/(hub)/schedule/page.tsx

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useEvents } from '@hooks/useEvents';
import CalendarItem from '../Schedule/CalendarItem';
import Event from '@typeDefs/event';
import TimeTracker from './TimeTracker';
import { getScheduleEventEndTime } from '../Schedule/scheduleTime';
import star_icon from '@public/hackers/hero/star.svg';

import styles from './NextSchedule.module.scss';
Expand Down Expand Up @@ -47,8 +48,9 @@ export default function NextSchedule() {
personalEvents.length > 0
) {
const now = new Date();
// Include events that haven't ended yet (covers both currently happening and future events)
const upcomingEvents = personalEvents.filter(
(event) => new Date(event.start_time) > now
(event) => getScheduleEventEndTime(event).getTime() > now.getTime()
);

if (upcomingEvents.length > 0) {
Expand Down
70 changes: 60 additions & 10 deletions app/(pages)/(hackers)/_components/HomeHacking/ScheduleSneakPeek.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client';

import { useState, useEffect, useMemo } from 'react';
import Link from 'next/link';
import Image from 'next/image';
import CalendarItem from '@pages/(hackers)/_components/Schedule/CalendarItem';
Expand Down Expand Up @@ -32,6 +33,43 @@ function SectionLabel({ label }: { label: string }) {
);
}

function CountdownLabel({ targetTime }: { targetTime: number }) {
const [timeLeft, setTimeLeft] = useState({
hours: 0,
minutes: 0,
seconds: 0,
});

useEffect(() => {
const calculateTimeLeft = () => {
const difference = targetTime - new Date().getTime();
if (difference <= 0) {
return { hours: 0, minutes: 0, seconds: 0 };
}
return {
hours: Math.floor(difference / (1000 * 60 * 60)),
minutes: Math.floor((difference / (1000 * 60)) % 60),
seconds: Math.floor((difference / 1000) % 60),
};
};

setTimeLeft(calculateTimeLeft());
const timer = setInterval(() => {
setTimeLeft(calculateTimeLeft());
}, 1000);

return () => clearInterval(timer);
}, [targetTime]);

const label = `IN ${timeLeft.hours
.toString()
.padStart(2, '0')}:${timeLeft.minutes
.toString()
.padStart(2, '0')}:${timeLeft.seconds.toString().padStart(2, '0')}`;

return <SectionLabel label={label} />;
}

function Panel({
title,
liveEvents,
Expand Down Expand Up @@ -63,6 +101,20 @@ function Panel({
/>
));

const upcomingGroups = useMemo(() => {
const groups: { startTime: number; entries: EventEntry[] }[] = [];
for (const entry of upcomingEvents) {
const startTime = new Date(entry.event.start_time).getTime();
const existing = groups.find((g) => g.startTime === startTime);
if (existing) {
existing.entries.push(entry);
} else {
groups.push({ startTime, entries: [entry] });
}
}
return groups.sort((a, b) => a.startTime - b.startTime);
}, [upcomingEvents]);

return (
<div className="rounded-[16px] bg-[#FFFFFF] p-5 lg:p-6">
<h2 className="font-jakarta text-[clamp(1.1rem,3vw,2.25rem)] font-semibold leading-tight tracking-[0.64px] text-[#3F3F3F] mb-4">
Expand All @@ -81,16 +133,14 @@ function Panel({
)}
</div>

<SectionLabel label="IN 0:30:00" />
<div className="space-y-3">
{upcomingEvents.length > 0 ? (
renderEventItems(upcomingEvents, 'upcoming')
) : (
<p className="font-jakarta text-sm text-[#7C7C85] mt-3">
No events starting in the next 30 minutes.
</p>
)}
</div>
{upcomingGroups.map((group) => (
<div key={group.startTime}>
<CountdownLabel targetTime={group.startTime} />
<div className="space-y-3">
{renderEventItems(group.entries, `upcoming-${group.startTime}`)}
</div>
</div>
))}
</div>
);
}
Expand Down
51 changes: 51 additions & 0 deletions app/(pages)/(hackers)/_components/Schedule/DayNavButtons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { useState } from 'react';
import { DAY_KEYS, DAY_LABELS, DayKey } from './constants';

interface DayNavButtonsProps {
activeDay: DayKey;
onSelectDay: (day: DayKey) => void;
className?: string;
buttonClassName?: string;
}

export default function DayNavButtons({
activeDay,
onSelectDay,
className,
buttonClassName,
}: DayNavButtonsProps) {
const [hoveredDay, setHoveredDay] = useState<DayKey | null>(null);
const previewDay =
hoveredDay && hoveredDay !== activeDay ? hoveredDay : activeDay;

return (
<div className={className}>
{DAY_KEYS.map((dayKey) => (
<button
key={dayKey}
onClick={() => onSelectDay(dayKey)}
onMouseEnter={() => setHoveredDay(dayKey)}
onMouseLeave={() => setHoveredDay(null)}
onFocus={() => setHoveredDay(dayKey)}
onBlur={() => setHoveredDay(null)}
type="button"
className={`relative w-fit bg-transparent border-none p-0 text-left font-dm-mono text-base md:text-lg font-medium tracking-[0.36px] leading-[100%] inline-flex items-center ${
activeDay === dayKey ? 'text-[#3F3F3F]' : 'text-[#ACACB9]'
} ${buttonClassName ?? ''}`}
>
<span
className={`absolute left-0 top-1/2 -translate-y-1/2 block origin-left transition-all duration-200 ease-out ${
previewDay === dayKey
? 'scale-x-100 opacity-100'
: 'scale-x-0 opacity-0'
}`}
aria-hidden
>
{'\u2022'}
</span>
<span className="font-dm-mono pl-5">{DAY_LABELS[dayKey]}</span>
</button>
))}
</div>
);
}
87 changes: 87 additions & 0 deletions app/(pages)/(hackers)/_components/Schedule/DaySection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Button } from '@pages/_globals/components/ui/button';
import CalendarItem from './CalendarItem';
import { DAY_LABELS, DayKey } from './constants';
import { GroupedDayEntries } from './types';

interface DaySectionProps {
dayKey: DayKey;
entries: GroupedDayEntries;
activeTab: 'schedule' | 'personal';
onSwitchToScheduleTab: () => void;
onAddToSchedule: (eventId: string) => void;
onRemoveFromSchedule: (eventId: string) => void;
}

export default function DaySection({
dayKey,
entries,
activeTab,
onSwitchToScheduleTab,
onAddToSchedule,
onRemoveFromSchedule,
}: DaySectionProps) {
const dayTitle = DAY_LABELS[dayKey].replace(/^MAY/, 'May');

return (
<section
id={`day-${dayKey}`}
className="scroll-mt-24 mb-8 last:mb-0 bg-white rounded-[16px] p-4 md:p-6"
>
<div className="font-jakarta font-bold text-[clamp(2rem,5vw,2.75rem)] leading-normal tracking-[0.96px] text-[#3F3F3F]">
{dayTitle}
</div>
<div className="w-[90%] border-b border-[#E9E9E7] mt-3 mb-6" />

{entries.length > 0 ? (
entries.map(([timeKey, events]) => (
<div
key={`${dayKey}-${timeKey}`}
className="relative mb-[24px] last:mb-0"
>
<div className="font-dm-mono text-sm md:text-lg font-normal leading-[145%] tracking-[0.36px] text-[#7C7C85] mt-[16px] mb-[6px]">
{timeKey}
</div>
<div>
{events.map((eventDetail) => (
<CalendarItem
key={`${dayKey}-${eventDetail.event._id}`}
event={eventDetail.event}
attendeeCount={eventDetail.attendeeCount}
inPersonalSchedule={eventDetail.inPersonalSchedule}
tags={eventDetail.event.tags}
host={eventDetail.event.host}
onAddToSchedule={() =>
onAddToSchedule(eventDetail.event._id || '')
}
onRemoveFromSchedule={() =>
onRemoveFromSchedule(eventDetail.event._id || '')
}
/>
))}
</div>
</div>
))
) : (
<div className="text-center py-10">
{activeTab === 'personal' ? (
<div>
<p className="mb-4">No events in your personal schedule yet.</p>
<Button
onClick={onSwitchToScheduleTab}
className="w-full sm:w-fit px-8 py-2 border-2 border-black rounded-3xl border-dashed hover:border-solid cursor-pointer relative group"
variant="ghost"
>
<div className="absolute inset-0 rounded-3xl transition-all duration-300 ease-out cursor-pointer bg-black w-0 group-hover:w-full" />
<p className="font-semibold relative z-10 transition-colors duration-300 text-black group-hover:text-white">
Browse the schedule to add events
</p>
</Button>
</div>
) : (
'No events found for this day and filter(s).'
)}
</div>
)}
</section>
);
}
Loading