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
90 changes: 90 additions & 0 deletions src/components/CountdownTimer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Fragment, useEffect, useState } from "react";

interface CountdownProps {
targetDate: string; // YYYY-MM-DD format
}

interface TimeLeft {
days: number;
hours: number;
minutes: number;
seconds: number;
}

function calculateTimeLeft(targetDate: string): TimeLeft {
const target = new Date(`${targetDate}T00:00:00-08:00`);
const now = new Date();
const difference = +target - +now;

if (difference <= 0) {
return {
days: 0,
hours: 0,
minutes: 0,
seconds: 0,
};
}

return {
days: Math.floor(difference / (1000 * 60 * 60 * 24)),
hours: Math.floor((difference / (1000 * 60 * 60)) % 24),
minutes: Math.floor((difference / 1000 / 60) % 60),
seconds: Math.floor((difference / 1000) % 60),
};
}

const formatNumber = (number: number) => number.toString().padStart(2, "0");

const Countdown: React.FC<CountdownProps> = ({ targetDate }) => {
const [timeLeft, setTimeLeft] = useState<TimeLeft>(
calculateTimeLeft(targetDate),
);

useEffect(() => {
const timer = setInterval(() => {
const newTimeLeft = calculateTimeLeft(targetDate);
setTimeLeft(newTimeLeft);
if (
newTimeLeft.days === 0 &&
newTimeLeft.hours === 0 &&
newTimeLeft.minutes === 0 &&
newTimeLeft.seconds === 0
) {
clearInterval(timer);
}
}, 1000);

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

if (
timeLeft.days === 0 &&
timeLeft.hours === 0 &&
timeLeft.minutes === 0 &&
timeLeft.seconds === 0
) {
return null;
}

return (
<div className="flex gap-2 justify-center">
{["days", "hours", "minutes", "seconds"].map((unit, index) => (
<Fragment key={unit}>
{index > 0 && <span className="h-[2rem] grid place-content-center">:</span>}

<div className={`${unit} grid grid-cols-2 gap-x-1 gap-y-1.5`}>
<span className="h-[2.3rem] aspect-[6/7] grid place-content-center rounded-sm bg-[#f9f4da] bg-opacity-10 font-semibold">
{formatNumber(timeLeft[unit as keyof TimeLeft]).charAt(0)}
</span>
<span className="h-[2.3rem] aspect-[6/7] grid place-content-center rounded-sm bg-[#f9f4da] bg-opacity-10 font-semibold">
{formatNumber(timeLeft[unit as keyof TimeLeft]).charAt(1)}
</span>
<p className="col-span-full text-xs">{unit}</p>
</div>
</Fragment>
))}
</div>
);
};

export default Countdown;
85 changes: 85 additions & 0 deletions src/components/CountdownTimerSmall.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { Fragment, useEffect, useState } from "react";

interface CountdownProps {
targetDate: string; // YYYY-MM-DD format
}

interface TimeLeft {
days: number;
hours: number;
minutes: number;
}

function calculateTimeLeft(targetDate: string): TimeLeft {
const target = new Date(`${targetDate}T00:00:00-08:00`);
const now = new Date();
const difference = +target - +now;

if (difference <= 0) {
return {
days: 0,
hours: 0,
minutes: 0,
};
}

return {
days: Math.floor(difference / (1000 * 60 * 60 * 24)),
hours: Math.floor((difference / (1000 * 60 * 60)) % 24),
minutes: Math.floor((difference / 1000 / 60) % 60),
};
}

const formatNumber = (number: number) => number.toString().padStart(2, "0");

const Countdown: React.FC<CountdownProps> = ({ targetDate }) => {
const [timeLeft, setTimeLeft] = useState<TimeLeft>(
calculateTimeLeft(targetDate),
);

useEffect(() => {
const timer = setInterval(() => {
const newTimeLeft = calculateTimeLeft(targetDate);
setTimeLeft(newTimeLeft);
if (
newTimeLeft.days === 0 &&
newTimeLeft.hours === 0 &&
newTimeLeft.minutes === 0
) {
clearInterval(timer);
}
}, 1000);

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

if (
timeLeft.days === 0 &&
timeLeft.hours === 0 &&
timeLeft.minutes === 0
) {
return null;
}

return (
<div className="mb-4 countdown flex gap-1.5 justify-center">
{["days", "hours", "minutes"].map((unit, index) => (
<Fragment key={unit}>
{index > 0 && <span className="h-[1.4em] grid place-content-center">:</span>}

<div className={`${unit} grid grid-cols-2 gap-x-1 gap-y-1.5`}>
<span className="h-[1.8em] w-[1.7em] grid place-content-center rounded-sm bg-gray-100 bg-opacity-10 dark:bg-gray-800 dark:bg-opacity-10 text-sm font-semibold">
{formatNumber(timeLeft[unit as keyof TimeLeft]).charAt(0)}
</span>
<span className="h-[1.8em] w-[1.7em] grid place-content-center rounded-sm bg-gray-100 bg-opacity-10 dark:bg-gray-800 dark:bg-opacity-10 text-sm font-semibold">
{formatNumber(timeLeft[unit as keyof TimeLeft]).charAt(1)}
</span>
<p className="col-span-full text-[.65rem]">{unit}</p>
</div>
</Fragment>
))}
</div>
);
};

export default Countdown;
18 changes: 14 additions & 4 deletions src/components/DocsCalloutQueryGG.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { LogoQueryGGSmall } from '~/components/LogoQueryGGSmall'
import { useQueryGGPPPDiscount } from '~/hooks/useQueryGGPPPDiscount'
import CountdownTimerSmall from '~/components/CountdownTimerSmall'

export function DocsCalloutQueryGG() {
const ppp = useQueryGGPPPDiscount()
Expand All @@ -17,14 +18,23 @@ export function DocsCalloutQueryGG() {
</h6>
<LogoQueryGGSmall className="w-full" />

<blockquote className="text-sm -indent-[.45em] pl-2">
{/* <blockquote className="text-sm -indent-[.45em] pl-2">
“If you’re serious about *really* understanding React Query, there’s
no better way than with query.gg”
<cite className="italic block text-right">—Tanner Linsley</cite>
</blockquote>

<div className="grid justify-center bg-gray-800 dark:bg-gray-100 text-gray-100 dark:text-gray-800 z-10"></div>
</blockquote> */}

{/* <div className="grid justify-center bg-gray-800 dark:bg-gray-100 text-gray-100 dark:text-gray-800 z-10"></div> */}
<div className="p-2 uppercase text-center place-self-center">
<h2 className="mt-1 mb-1 px-2 text-md font-semibold">
Black Friday Sale
</h2>
<p className="normal-case mb-4 text-sm text-balance">
Get 30% off through December 6th
</p>
<CountdownTimerSmall targetDate="2025-12-06" />
</div>

{ppp && (
<>
<p className="text-sm pl-2 py-2">
Expand Down
48 changes: 48 additions & 0 deletions src/components/QueryGGBannerSale.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import headerCourse from '~/images/query-header-course.svg';
import cornerTopLeft from '~/images/query-corner-top-left.svg';
import cornerTopRight from '~/images/query-corner-top-right.svg';
import cornerFishBottomRight from '~/images/query-corner-fish-bottom-right.svg';
import CountdownTimer from '~/components/CountdownTimer'

export function QueryGGBannerSale(props: React.HTMLProps<HTMLDivElement>) {
return (
<aside {...props} className="mx-auto w-full max-w-[1200px] p-8 -mt-32 flex justify-between items-center">
<div className="w-full xl:flex xl:gap-6 bg-[#f9f4da] border-4 border-[#231f20]">
<a href="https://query.gg?s=tanstack" className="xl:w-[55%] pb-4 grid grid-cols-[70px_1fr_70px] sm:grid-cols-[100px_1fr_100px] md:grid-cols-[140px_1fr_140px] xl:grid-cols-[110px_1fr] 2xl:grid-cols-[150px_1fr]">
<img
src={cornerTopLeft}
alt="sun"
className=""
/>
<img
src={headerCourse}
alt="Query.gg - The Official React Query Course"
className="-mt-[1px] w-10/12 max-w-[400px] justify-self-center"
/>
<img
src={cornerTopRight}
alt="moon"
className="xl:hidden"
/>
</a>
<div className="hidden xl:block w-[80px] mr-[-55px] bg-[#231f20] border-4 border-r-0 border-[#f9f4da] border-s-[#f9f4da] shadow-[-4px_0_0_#231f20] -skew-x-[15deg] z-0"></div>
<div className="xl:w-[45%] py-2 xl:pb-0 grid xl:grid-cols-[1fr_90px] 2xl:grid-cols-[1fr_120px] justify-center bg-[#231f20] border-2 xl:border-4 xl:border-l-0 border-[#f9f4da] text-[#f9f4da] z-10">
<div className="my-2 uppercase text-center place-self-center">
{/* <h2 className="mt-1 mb-3 px-2 text-sm font-semibold">Launch sale happening now</h2> */}
<h2 className="mb-1 text-xl lg:text-2xl xl:text-3xl font-semibold">
Black Friday sale
</h2>
<p className="normal-case mb-4">Get 30% off through December 6th</p>
<CountdownTimer targetDate="2025-12-06" />
<a href="https://query.gg?s=tanstack" className="mt-4 mb-1 xl:mb-2 px-6 py-2 inline-block bg-[#fcba28] text-[#231f20] rounded-full uppercase border border-black cursor-pointer font-black">Join now</a>
</div>
<img
src={cornerFishBottomRight}
alt="mutated fish"
className="hidden xl:block self-end"
/>
</div>
</div>
</aside>
)
}
6 changes: 4 additions & 2 deletions src/routes/_libraries/query.$version.index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { LazySponsorSection } from '~/components/LazySponsorSection'
import { PartnersSection } from '~/components/PartnersSection'
import { BottomCTA } from '~/components/BottomCTA'
import { StackBlitzEmbed } from '~/components/StackBlitzEmbed'
import { QueryGGBanner } from '~/components/QueryGGBanner'
// import { QueryGGBanner } from '~/components/QueryGGBanner'
import { QueryGGBannerSale } from '~/components/QueryGGBannerSale'
import { queryProject } from '~/libraries/query'
import { Framework, getBranch, getLibrary } from '~/libraries'
import { seo } from '~/utils/seo'
Expand Down Expand Up @@ -56,7 +57,8 @@ function VersionIndex() {
}}
/>
<div className="px-4">
<QueryGGBanner />
{/* <QueryGGBanner /> */}
<QueryGGBannerSale />
</div>

<div className="w-fit mx-auto px-4">
Expand Down
Loading