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
8 changes: 7 additions & 1 deletion src/features/hackathon/constants/hackathons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,10 @@ export const HACKATHONS: {
label: string;
slug: string;
logo: string;
}[] = [];
}[] = [
{
label: "Crypto World's Fair",
slug: 'crypto-worlds-fair',
logo: '/hackathon/crypto-world-fair/logo.png',
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import Link from 'next/link';
import posthog from 'posthog-js';

import { ExternalImage } from '@/components/ui/cloudinary-image';

export function HomeCryptoWorldFairHackathonBanner() {
return (
<Link
href="/earn/hackathon/crypto-worlds-fair"
className="group relative mx-auto flex h-full min-h-64 w-full flex-col overflow-hidden rounded-lg p-5 text-white focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-[#65496F] md:p-10"
prefetch={false}
onClick={() => {
posthog.capture('crypto_world_fair_hackathon_banner');
}}
>
<div className="absolute inset-0 overflow-hidden bg-[#65496F]">
{/* <ExternalImage
src="hackathon/crypto-world-fair/sidebar.png"
alt=""
className="h-full w-full object-cover object-center md:hidden"
loading="eager"
/> */}
<ExternalImage
src="hackathon/crypto-world-fair/bg-wo-logo.png"
alt=""
className="h-full w-full object-cover object-center md:block"
loading="eager"
/>
</div>
<div className="absolute inset-0 bg-black/45" aria-hidden="true" />

<h1 className="relative z-10 max-w-[22rem] text-2xl leading-[120%] font-bold md:max-w-[30rem] md:text-[28px]">
Build the onchain future at Crypto World&apos;s Fair
</h1>
<p className="relative z-10 mt-2.5 max-w-[30rem] text-sm leading-[130%] [text-shadow:_0_1px_2px_rgb(0_0_0_/1)] md:mt-4 md:text-lg">
Join the ideas, technologies, and communities shaping what comes next
onchain.
</p>
<div className="relative z-10 mt-auto flex pt-4">
<span className="ph-no-capture inline-flex min-h-10 w-full items-center justify-center rounded-md bg-[#65496F] px-9 py-3 text-sm font-medium text-white transition-colors group-hover:bg-[#533D5B] group-focus-visible:bg-[#533D5B] md:w-auto">
View Tracks
</span>
</div>
</Link>
);
}
4 changes: 4 additions & 0 deletions src/features/home/components/Banner/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
popupsShowedAtom,
} from '@/features/conversion-popups/atoms';

import { HomeCryptoWorldFairHackathonBanner } from './CryptoWorldFairHackathonBanner';
import { HomeSponsorBanner } from './SponsorBanner';
import { HomeTalentBanner } from './TalentBanner';

Expand Down Expand Up @@ -68,6 +69,9 @@ export function BannerCarousel({
setApi={setCarouselApi}
>
<CarouselContent>
<CarouselItem>
<HomeCryptoWorldFairHackathonBanner />
</CarouselItem>
<CarouselItem>
<HomeTalentBanner totalUsers={totalUsers} />
</CarouselItem>
Expand Down
5 changes: 3 additions & 2 deletions src/features/home/components/SideBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { userStatsQuery } from '../queries/user-stats';
import { HowItWorks } from './HowItWorks';
import { RecentActivity } from './RecentActivity';
import { RecentEarners } from './RecentEarners';
import { SidebarBanner } from './SidebarBanner';
import { SponsorBanner } from './SponsorBanner';
import { SponsorFeatures } from './SponsorFeatures';
import { SponsorResources } from './SponsorResources';
Expand Down Expand Up @@ -85,7 +86,7 @@ interface FeedSidebarContentProps {
const FeedSidebarContent = ({ recentEarners }: FeedSidebarContentProps) => (
<>
<VibeCard />
{/* <SidebarBanner /> */}
<SidebarBanner />
<LiveListings>
<SectionHeader title="LIVE LISTINGS" href="/earn" />
</LiveListings>
Expand Down Expand Up @@ -134,7 +135,7 @@ const NonSponsorSidebarContent = ({
TVE={totals?.totalInUSD}
/>
</div>
{/* <SidebarBanner /> */}
<SidebarBanner />
<HowItWorks />
{currentPath !== '/earn/bookmarks' && !!bookmarks?.length && (
<YourBookmarks>
Expand Down
207 changes: 26 additions & 181 deletions src/features/home/components/SidebarBanner.tsx
Original file line number Diff line number Diff line change
@@ -1,206 +1,51 @@
import { useQuery } from '@tanstack/react-query';
import localFont from 'next/font/local';
import Link from 'next/link';
import { type ReactNode, useEffect, useState } from 'react';
import Countdown, { type CountdownRenderProps } from 'react-countdown';
import posthog from 'posthog-js';

import { Button } from '@/components/ui/button';
import { ExternalImage } from '@/components/ui/cloudinary-image';
import { statsDataQuery } from '@/queries/hackathon';
import { dayjs } from '@/utils/dayjs';
import { roundToNearestTenThousand } from '@/utils/number';
import { cn } from '@/utils/cn';

interface SidebarPosterProps {
className?: string;
}

type CountdownMode = 'Begins' | 'Left' | 'Review';

const digital7Italic = localFont({
src: '../../../../public/fonts/digital-7-italic.ttf',
display: 'swap',
});

export function SidebarBanner({ className }: SidebarPosterProps) {
const { data: stats } = useQuery(statsDataQuery('world-cup'));
const CLOSE_DATE = stats?.deadline;
const START_DATE = stats?.startDate;
return (
<Link href="/earn/hackathon/world-cup" className="mt-5 block">
<Link
href="/earn/hackathon/crypto-worlds-fair"
className="group mt-5 block rounded-lg focus-visible:ring-2 focus-visible:ring-[#65496F] focus-visible:ring-offset-2"
prefetch={false}
onClick={() => {
posthog.capture('crypto_world_fair_sidebar_banner');
}}
>
<div
className={`bg-brand-purple/5 border-brand-purple/4 relative flex h-fit w-full flex-col items-center rounded-lg border p-2 ${className}`}
className={cn(
'relative flex h-fit w-full flex-col items-center rounded-lg border border-[#65496F]/10 bg-[#65496F]/5 p-2',
className,
)}
>
<SidebarDigitalTimer deadline={CLOSE_DATE} startDate={START_DATE} />

<div className="w-full overflow-hidden rounded-md">
<ExternalImage
src={'/hackathon/world-cup/sidebar-banner'}
alt="World Cup Hackathon"
className="top-0 left-0 w-full object-contain"
src="hackathon/crypto-world-fair/sidebar.png"
alt="Crypto World's Fair"
className="w-full object-contain"
/>
</div>

<div className="relative z-10 flex h-full w-full flex-col px-4 pt-2 pb-5 text-black">
<p className="relative z-10 mt-2 text-lg leading-[120%] font-semibold text-slate-800">
Don't just watch, win prizes worth $
{roundToNearestTenThousand(
stats?.totalRewardAmount ?? 0,
true,
)?.toLocaleString('en-us') || '0'}{' '}
this World Cup!
</p>
<p className="relative z-10 mt-3 text-sm leading-[130%] text-[#62748E] md:text-base">
Build products or agents using TxODDS’ live World Cup data API on
Solana. $50K across three tracks: markets, trading agents, and fan
experiences.
<h2 className="mt-2 text-lg leading-[120%] font-semibold text-slate-800">
Build the onchain future at Crypto World&apos;s Fair
</h2>
<p className="mt-3 text-sm leading-[130%] text-slate-600 md:text-base">
Join the ideas, technologies, and communities shaping what comes
next onchain.
</p>

<Button className={`mt-4 text-base`}>View Tracks</Button>
<span className="mt-4 inline-flex min-h-10 items-center justify-center rounded-md bg-[#65496F] px-4 text-base font-medium text-white transition-colors group-hover:bg-[#533D5B] group-focus-visible:bg-[#533D5B]">
View Tracks
</span>
</div>
</div>
</Link>
);
}

function SidebarDigitalTimer({
deadline,
startDate,
}: {
deadline?: string | Date;
startDate?: string | Date;
}) {
const [countdownMode, setCountdownMode] = useState<CountdownMode>('Left');
const [countdownDate, setCountdownDate] = useState<Date | null>(null);
const [isCountdownReady, setIsCountdownReady] = useState(false);

useEffect(() => {
if (!deadline || !startDate) return;

setIsCountdownReady(true);

function updateTimer() {
const now = dayjs();

if (now.isAfter(dayjs(deadline))) {
setCountdownMode('Review');
setCountdownDate(dayjs.utc(deadline).toDate());
} else if (now.isBefore(dayjs(startDate))) {
setCountdownMode('Begins');
setCountdownDate(dayjs.utc(startDate).toDate());
} else {
setCountdownMode('Left');
setCountdownDate(dayjs.utc(deadline).toDate());
}
}

updateTimer();

const intervalId = window.setInterval(updateTimer, 1000);

return () => window.clearInterval(intervalId);
}, [deadline, startDate]);

if (!deadline || !startDate || !countdownDate) return null;

return (
<div className="absolute top-0 left-1/2 z-20 -translate-x-1/2 -translate-y-1/2">
{isCountdownReady ? (
<Countdown
date={countdownDate}
renderer={(props) => (
<SidebarTimerRenderer {...props} mode={countdownMode} />
)}
zeroPadTime={2}
zeroPadDays={2}
/>
) : (
<SidebarTimerShell isReview={countdownMode === 'Review'}>
{countdownMode === 'Begins' && (
<span className="mr-4">BEGINS IN</span>
)}
<SidebarTimerUnit value="00" unit="D" />
<SidebarTimerSeparator />
<SidebarTimerUnit value="00" unit="H" />
<SidebarTimerSeparator />
<SidebarTimerUnit value="00" unit="M" />
<SidebarTimerSeparator />
<SidebarTimerUnit value="00" unit="S" />
{countdownMode === 'Left' && <span className="ml-4">LEFT</span>}
</SidebarTimerShell>
)}
</div>
);
}

function SidebarTimerRenderer({
days,
hours,
minutes,
seconds,
mode,
}: CountdownRenderProps & {
mode: CountdownMode;
}) {
if (mode === 'Review') {
return <SidebarTimerShell isReview>In Review</SidebarTimerShell>;
}

return (
<SidebarTimerShell>
{mode === 'Begins' && <span className="mr-4">BEGINS IN</span>}
<SidebarTimerUnit value={days} unit="D" />
<SidebarTimerSeparator />
<SidebarTimerUnit value={padTimeUnit(hours)} unit="H" />
<SidebarTimerSeparator />
<SidebarTimerUnit value={padTimeUnit(minutes)} unit="M" />
<SidebarTimerSeparator />
<SidebarTimerUnit value={padTimeUnit(seconds)} unit="S" />
{mode === 'Left' && <span className="ml-4">LEFT</span>}
</SidebarTimerShell>
);
}

function SidebarTimerShell({
children,
isReview = false,
}: {
children: ReactNode;
isReview?: boolean;
}) {
return (
<div
className={`${digital7Italic.className} flex w-[16rem] items-center justify-center border-2 border-[#B7B7AB] bg-[#101010] px-3 py-3 text-[1.5rem] leading-none tracking-normal whitespace-nowrap uppercase shadow-[0_8px_12px_rgba(15,23,42,0.35),inset_0_0_14px_rgba(0,0,0,0.5),inset_0_1px_10px_rgba(255,255,255,0.5)] [&_.timer-unit]:text-[0.58em] ${
isReview ? 'text-[#FDFFA4]' : 'text-[#FF0404]'
}`}
style={{
fontStyle: 'italic',
WebkitTextStroke: '0.5px #510000',
}}
>
{children}
</div>
);
}

function SidebarTimerUnit({
value,
unit,
}: {
value: string | number;
unit: string;
}) {
return (
<span className="inline-flex items-baseline">
{value}
<span className="timer-unit">{unit}</span>
</span>
);
}

function SidebarTimerSeparator() {
return <span className="mx-[5px]">:</span>;
}

function padTimeUnit(value: number) {
return value.toString().padStart(2, '0');
}
2 changes: 1 addition & 1 deletion src/features/listings/components/ListingTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export const ListingTabs = ({
<ExternalImage
src={hackathon.logo}
alt={hackathon.label}
className="my-[0.10rem] h-4 scale-130 object-contain"
className="my-[0.10rem] h-3 scale-90 object-contain"
/>
</ListingTabTrigger>
))}
Expand Down
2 changes: 1 addition & 1 deletion src/features/navbar/components/DesktopNavbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ export const DesktopNavbar = ({
<Link
href={`/earn/hackathon/${hackathon.slug}`}
key={hackathon.slug}
className={cn('flex items-center py-2 font-medium', 'h-11')}
className={cn('flex items-center py-2 font-medium', 'h-7')}
prefetch={false}
>
<ExternalImage
Expand Down
2 changes: 1 addition & 1 deletion src/features/navbar/components/MobileDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ export const MobileDrawer = ({
<ExternalImage
src={hackathon.logo}
alt={hackathon.label}
className="h-5 scale-200 p-1"
className="h-6 scale-100 p-1"
/>
</div>
}
Expand Down
Loading