Skip to content

Commit 1d25ac9

Browse files
393 hero waiting (#437)
* first cut at hero waiting desktop view * added mobile view of hero waiting and working links * font,padding,mobile breakpoint fix * fix hackers choice & display all rollouts --------- Co-authored-by: michelleyeoh <michellew.yeoh@gmail.com>
1 parent 7e8c788 commit 1d25ac9

7 files changed

Lines changed: 562 additions & 143 deletions

File tree

app/(pages)/(hackers)/(hub)/page.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import MDHelp from '@pages/(hackers)/_components/HomeHacking/MDHelp';
1111
import ScheduleSneakPeek from '@pages/(hackers)/_components/HomeHacking/ScheduleSneakPeek';
1212
import HeroJudging from '../_components/HomeJudging/HeroJudging';
1313
import HackerChoiceAward from '../_components/HomeJudging/HackersChoiceAwards';
14-
import HeroWaiting from '../_components/HomeJudging/HeroWaiting';
1514
import HeroHacking from '../_components/HomeHacking/HeroHacking';
1615
import { useRollout } from '@pages/_hooks/useRollout';
1716

@@ -31,8 +30,7 @@ export default function Page() {
3130
<MDHelp />
3231
</ClientTimeProtectedDisplay>
3332
{/* temporarilty set featureId below to "hero-hacking" to test */}
34-
<ClientTimeProtectedDisplay featureId="hero-judging">
35-
<HeroWaiting />
33+
<ClientTimeProtectedDisplay featureId="hero-hacking">
3634
<HeroJudging />
3735
<HackerChoiceAward />
3836
</ClientTimeProtectedDisplay>

app/(pages)/(hackers)/_components/HomeJudging/HackersChoiceAwards.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import TextCard from '../HomeHacking/_components/TextCard';
55
export default function HackerChoiceAward() {
66
return (
77
<div className="bg-[#F1FFCC]" id="hackers-choice-awards">
8-
<div className="flex flex-col md:flex-row items-center justify-between px-[5%] p-[10%] gap-12 md:gap-0">
8+
<div className="flex flex-col md:flex-row items-center justify-between px-[5%] p-[10%] gap-12">
99
<div className="flex-1 flex justify-center md:justify-start">
1010
<Image
1111
src={hackers_choice_mascots}
Lines changed: 239 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,241 @@
1+
'use client';
2+
3+
import Image, { type StaticImageData } from 'next/image';
4+
import Link from 'next/link';
5+
import { type MouseEvent, useEffect, useRef, useState } from 'react';
6+
import useTableNumberContext from '@pages/_hooks/useTableNumberContext';
7+
8+
import arrowRight from '@public/icons/arrow-right.svg';
9+
import hackersChoiceAsset from '@public/hackers/hero/waiting-hero/hackers-choice-asset.svg';
10+
import judgingAsset from '@public/hackers/hero/waiting-hero/judging-asset.svg';
11+
import pitchAsset from '@public/hackers/hero/waiting-hero/pitch-asset.svg';
12+
13+
type WaitingCardProps = {
14+
imageSrc: StaticImageData | string;
15+
imageAlt: string;
16+
title: string;
17+
description: string;
18+
linkLabel?: string;
19+
href?: string;
20+
};
21+
22+
function WaitingCard({
23+
imageSrc,
24+
imageAlt,
25+
title,
26+
description,
27+
linkLabel,
28+
href,
29+
}: WaitingCardProps) {
30+
const handleLinkClick = (event: MouseEvent<HTMLAnchorElement>) => {
31+
if (!href?.startsWith('#')) {
32+
return;
33+
}
34+
35+
const target = document.getElementById(href.slice(1));
36+
if (!target) {
37+
return;
38+
}
39+
40+
event.preventDefault();
41+
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
42+
window.history.replaceState(null, '', href);
43+
};
44+
45+
return (
46+
<article className="flex h-full flex-col overflow-hidden rounded-[16px] bg-white">
47+
<div className="relative w-full aspect-[16/9]">
48+
<Image src={imageSrc} alt={imageAlt} fill className="object-cover" />
49+
</div>
50+
<div className="flex min-h-[210px] flex-1 flex-col p-5 md:min-h-[190px]">
51+
<h3 className="font-jakarta text-[1.375rem] font-semibold text-[#3F3F3F] sm:text-[1.3rem] lg:text-[2rem]">
52+
{title}
53+
</h3>
54+
<p className="mt-2 mb-3 font-jakarta text-[1rem] text-[#5E5E65] md:mt-[1vw] md:mb-[2vw]">
55+
{description}
56+
</p>
57+
{linkLabel && href ? (
58+
<Link
59+
href={href}
60+
onClick={handleLinkClick}
61+
className="mt-auto mb-[1vw] inline-flex items-center gap-2 pt-7 font-dm-mono text-[1rem] text-[#3F3F3F] underline decoration-[1px] underline-offset-4 md:text-[1.125rem]"
62+
>
63+
<Image
64+
src={arrowRight}
65+
alt=""
66+
aria-hidden="true"
67+
className="h-4 w-4"
68+
/>
69+
{linkLabel}
70+
</Link>
71+
) : null}
72+
</div>
73+
</article>
74+
);
75+
}
76+
77+
const waitingCards: WaitingCardProps[] = [
78+
{
79+
imageSrc: pitchAsset,
80+
imageAlt: 'Practice your pitch',
81+
title: 'Practice your pitch',
82+
description:
83+
'Your pitch is more important than you think! These 5 minutes determine how much your work in the last 24 hours are worth.',
84+
},
85+
{
86+
imageSrc: hackersChoiceAsset,
87+
imageAlt: 'Submit your vote',
88+
title: 'Submit your vote',
89+
description:
90+
'While you wait, put in your choice for your favorite hack! You are allowed 1 vote, and you cannot vote for your own team. ',
91+
linkLabel: 'HACKERS CHOICE AWARD',
92+
href: '#hackers-choice-awards',
93+
},
94+
{
95+
imageSrc: judgingAsset,
96+
imageAlt: 'Learn about judging',
97+
title: 'Learn about Judging',
98+
description:
99+
'Ask our AI chatbot any questions regarding the judging rubric, process, and timeline! ',
100+
linkLabel: 'LEARN OUR JUDGING PROCESS',
101+
href: '/project-info',
102+
},
103+
];
104+
1105
export default function HeroWaiting() {
2-
return <div>Hero Waiting</div>;
106+
const { storedValue: tableNumber } = useTableNumberContext();
107+
const [activeCardIndex, setActiveCardIndex] = useState(0);
108+
const carouselRef = useRef<HTMLDivElement>(null);
109+
const isAutoScrollingRef = useRef(false);
110+
const autoScrollTimeoutRef = useRef<number | null>(null);
111+
112+
useEffect(() => {
113+
const interval = window.setInterval(() => {
114+
setActiveCardIndex((prevIndex) => (prevIndex + 1) % waitingCards.length);
115+
}, 5000);
116+
117+
return () => window.clearInterval(interval);
118+
}, []);
119+
120+
useEffect(() => {
121+
return () => {
122+
if (autoScrollTimeoutRef.current !== null) {
123+
window.clearTimeout(autoScrollTimeoutRef.current);
124+
}
125+
};
126+
}, []);
127+
128+
useEffect(() => {
129+
const carousel = carouselRef.current;
130+
if (!carousel) {
131+
return;
132+
}
133+
134+
const targetCard = carousel.children[activeCardIndex] as
135+
| HTMLElement
136+
| undefined;
137+
if (!targetCard) {
138+
return;
139+
}
140+
141+
isAutoScrollingRef.current = true;
142+
carousel.scrollTo({
143+
left: targetCard.offsetLeft,
144+
behavior: 'smooth',
145+
});
146+
147+
if (autoScrollTimeoutRef.current !== null) {
148+
window.clearTimeout(autoScrollTimeoutRef.current);
149+
}
150+
autoScrollTimeoutRef.current = window.setTimeout(() => {
151+
isAutoScrollingRef.current = false;
152+
}, 450);
153+
}, [activeCardIndex]);
154+
155+
const handleCarouselScroll = () => {
156+
if (isAutoScrollingRef.current) {
157+
return;
158+
}
159+
160+
const carousel = carouselRef.current;
161+
if (!carousel) {
162+
return;
163+
}
164+
165+
const cards = Array.from(carousel.children) as HTMLElement[];
166+
if (!cards.length) {
167+
return;
168+
}
169+
170+
const currentScroll = carousel.scrollLeft;
171+
let nearestIndex = 0;
172+
let smallestDistance = Number.POSITIVE_INFINITY;
173+
174+
cards.forEach((card, index) => {
175+
const distance = Math.abs(card.offsetLeft - currentScroll);
176+
if (distance < smallestDistance) {
177+
smallestDistance = distance;
178+
nearestIndex = index;
179+
}
180+
});
181+
182+
setActiveCardIndex((prevIndex) =>
183+
prevIndex === nearestIndex ? prevIndex : nearestIndex
184+
);
185+
};
186+
187+
return (
188+
<section className="h-screen w-full box-border p-4 md:p-10">
189+
<div className="flex flex-col mx-auto h-full w-[90vw] max-w-[1440px] rounded-[32px] bg-[#FAFAFF] justify-center">
190+
<div className="mx-auto flex w-[92%] max-w-[1120px] flex-col py-8 font-jakarta text-[#3F3F3F] md:py-16">
191+
<h2 className="text-[1.375rem] md:text-[2.5rem]">
192+
While you wait at{' '}
193+
<span className="underline decoration-[1.25px]">
194+
Table {tableNumber ?? '---'}
195+
</span>
196+
...
197+
</h2>
198+
199+
<div
200+
ref={carouselRef}
201+
onScroll={handleCarouselScroll}
202+
className="mt-10 flex items-stretch snap-x snap-mandatory gap-4 overflow-x-auto md:hidden [&::-webkit-scrollbar]:hidden [scrollbar-width:none]"
203+
>
204+
{waitingCards.map((card) => (
205+
<div
206+
key={card.title}
207+
className="flex w-full shrink-0 snap-center"
208+
>
209+
<WaitingCard {...card} />
210+
</div>
211+
))}
212+
</div>
213+
214+
<div className="mt-6 flex items-center justify-center gap-3 md:hidden">
215+
{waitingCards.map((card, index) => (
216+
<button
217+
key={card.title}
218+
type="button"
219+
aria-label={`Go to card ${index + 1}`}
220+
onClick={() => setActiveCardIndex(index)}
221+
className={`h-3 w-3 rounded-full transition-colors ${
222+
index === activeCardIndex ? 'bg-[#3F3F3F]' : 'bg-[#D9D9DF]'
223+
}`}
224+
/>
225+
))}
226+
</div>
227+
228+
<div className="mt-10 hidden gap-4 md:grid md:grid-cols-3">
229+
{waitingCards.map((card) => (
230+
<WaitingCard key={card.title} {...card} />
231+
))}
232+
</div>
233+
234+
<p className="mt-8 font-jakarta text-[0.875rem] font-semibold text-[#5E5E65] md:mt-10 md:text-[1.25rem]">
235+
Give us a moment while we assign your team judges.
236+
</p>
237+
</div>
238+
</div>
239+
</section>
240+
);
3241
}

0 commit comments

Comments
 (0)