Skip to content

Commit 748056d

Browse files
committed
add launch banners to query page and docs
1 parent 0ca7a9c commit 748056d

File tree

5 files changed

+240
-5
lines changed

5 files changed

+240
-5
lines changed

app/components/CountdownTimer.tsx

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { Fragment, useEffect, useState } from "react";
2+
3+
interface CountdownProps {
4+
targetDate: string; // YYYY-MM-DD format
5+
}
6+
7+
interface TimeLeft {
8+
days: number;
9+
hours: number;
10+
minutes: number;
11+
seconds: number;
12+
}
13+
14+
function calculateTimeLeft(targetDate: string): TimeLeft {
15+
const target = new Date(`${targetDate}T00:00:00-08:00`);
16+
const now = new Date();
17+
const difference = +target - +now;
18+
19+
if (difference <= 0) {
20+
return {
21+
days: 0,
22+
hours: 0,
23+
minutes: 0,
24+
seconds: 0,
25+
};
26+
}
27+
28+
return {
29+
days: Math.floor(difference / (1000 * 60 * 60 * 24)),
30+
hours: Math.floor((difference / (1000 * 60 * 60)) % 24),
31+
minutes: Math.floor((difference / 1000 / 60) % 60),
32+
seconds: Math.floor((difference / 1000) % 60),
33+
};
34+
}
35+
36+
const formatNumber = (number: number) => number.toString().padStart(2, "0");
37+
38+
const Countdown: React.FC<CountdownProps> = ({ targetDate }) => {
39+
const [timeLeft, setTimeLeft] = useState<TimeLeft>(
40+
calculateTimeLeft(targetDate),
41+
);
42+
43+
useEffect(() => {
44+
const timer = setInterval(() => {
45+
const newTimeLeft = calculateTimeLeft(targetDate);
46+
setTimeLeft(newTimeLeft);
47+
if (
48+
newTimeLeft.days === 0 &&
49+
newTimeLeft.hours === 0 &&
50+
newTimeLeft.minutes === 0 &&
51+
newTimeLeft.seconds === 0
52+
) {
53+
clearInterval(timer);
54+
}
55+
}, 1000);
56+
57+
return () => clearInterval(timer);
58+
}, [targetDate]);
59+
60+
if (
61+
timeLeft.days === 0 &&
62+
timeLeft.hours === 0 &&
63+
timeLeft.minutes === 0 &&
64+
timeLeft.seconds === 0
65+
) {
66+
return null;
67+
}
68+
69+
return (
70+
<div className="flex gap-2 justify-center">
71+
{["days", "hours", "minutes", "seconds"].map((unit, index) => (
72+
<Fragment key={unit}>
73+
{index > 0 && <span className="h-[2rem] grid place-content-center">:</span>}
74+
75+
<div className={`${unit} grid grid-cols-2 gap-x-1 gap-y-1.5`}>
76+
<span className="h-[2.3rem] aspect-[6/7] grid place-content-center rounded-sm bg-[#f9f4da] bg-opacity-10 font-semibold">
77+
{formatNumber(timeLeft[unit as keyof TimeLeft]).charAt(0)}
78+
</span>
79+
<span className="h-[2.3rem] aspect-[6/7] grid place-content-center rounded-sm bg-[#f9f4da] bg-opacity-10 font-semibold">
80+
{formatNumber(timeLeft[unit as keyof TimeLeft]).charAt(1)}
81+
</span>
82+
<p className="col-span-full text-xs">{unit}</p>
83+
</div>
84+
</Fragment>
85+
))}
86+
</div>
87+
);
88+
};
89+
90+
export default Countdown;
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { Fragment, useEffect, useState } from "react";
2+
3+
interface CountdownProps {
4+
targetDate: string; // YYYY-MM-DD format
5+
}
6+
7+
interface TimeLeft {
8+
days: number;
9+
hours: number;
10+
minutes: number;
11+
}
12+
13+
function calculateTimeLeft(targetDate: string): TimeLeft {
14+
const target = new Date(`${targetDate}T00:00:00-08:00`);
15+
const now = new Date();
16+
const difference = +target - +now;
17+
18+
if (difference <= 0) {
19+
return {
20+
days: 0,
21+
hours: 0,
22+
minutes: 0,
23+
};
24+
}
25+
26+
return {
27+
days: Math.floor(difference / (1000 * 60 * 60 * 24)),
28+
hours: Math.floor((difference / (1000 * 60 * 60)) % 24),
29+
minutes: Math.floor((difference / 1000 / 60) % 60),
30+
};
31+
}
32+
33+
const formatNumber = (number: number) => number.toString().padStart(2, "0");
34+
35+
const Countdown: React.FC<CountdownProps> = ({ targetDate }) => {
36+
const [timeLeft, setTimeLeft] = useState<TimeLeft>(
37+
calculateTimeLeft(targetDate),
38+
);
39+
40+
useEffect(() => {
41+
const timer = setInterval(() => {
42+
const newTimeLeft = calculateTimeLeft(targetDate);
43+
setTimeLeft(newTimeLeft);
44+
if (
45+
newTimeLeft.days === 0 &&
46+
newTimeLeft.hours === 0 &&
47+
newTimeLeft.minutes === 0
48+
) {
49+
clearInterval(timer);
50+
}
51+
}, 1000);
52+
53+
return () => clearInterval(timer);
54+
}, [targetDate]);
55+
56+
if (
57+
timeLeft.days === 0 &&
58+
timeLeft.hours === 0 &&
59+
timeLeft.minutes === 0
60+
) {
61+
return null;
62+
}
63+
64+
return (
65+
<div className="countdown flex gap-1.5 justify-center">
66+
{["days", "hours", "minutes"].map((unit, index) => (
67+
<Fragment key={unit}>
68+
{index > 0 && <span className="h-[1.4em] grid place-content-center">:</span>}
69+
70+
<div className={`${unit} grid grid-cols-2 gap-x-1 gap-y-1.5`}>
71+
<span className="h-[1.8em] w-[1.7em] grid place-content-center rounded-sm bg-gray-200 bg-opacity-75 dark:bg-gray-600 dark:bg-opacity-50 text-sm font-semibold">
72+
{formatNumber(timeLeft[unit as keyof TimeLeft]).charAt(0)}
73+
</span>
74+
<span className="h-[1.8em] w-[1.7em] grid place-content-center rounded-sm bg-gray-200 bg-opacity-75 dark:bg-gray-600 dark:bg-opacity-50 text-sm font-semibold">
75+
{formatNumber(timeLeft[unit as keyof TimeLeft]).charAt(1)}
76+
</span>
77+
<p className="col-span-full text-[.65rem]">{unit}</p>
78+
</div>
79+
</Fragment>
80+
))}
81+
</div>
82+
);
83+
};
84+
85+
export default Countdown;

app/components/DocsCalloutQueryGG.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { LogoQueryGGSmall } from '~/components/LogoQueryGGSmall'
2+
import CountdownTimerSmall from '~/components/CountdownTimerSmall'
23
import { useQueryGGPPPDiscount } from '~/hooks/useQueryGGPPPDiscount'
34

45
export function DocsCalloutQueryGG() {
@@ -17,11 +18,20 @@ export function DocsCalloutQueryGG() {
1718
</h6>
1819
<LogoQueryGGSmall className="w-full" />
1920

20-
<blockquote className="text-sm -indent-[.45em] pl-2">
21+
{/*<blockquote className="text-sm -indent-[.45em] pl-2">
2122
“If you're serious about *really* understanding React Query, there's
2223
no better way than with query.gg”
2324
<cite className="italic block text-right">—Tanner Linsley</cite>
24-
</blockquote>
25+
</blockquote>*/}
26+
27+
{/*<div className="grid justify-center bg-gray-800 dark:bg-gray-100 text-gray-100 dark:text-gray-800 z-10">*/}
28+
<div className="p-2 uppercase text-center place-self-center">
29+
<h2 className="mt-1 mb-1 px-2 text-md font-semibold">
30+
Launch week sale
31+
</h2>
32+
<p className="normal-case mb-4 text-sm text-balance">Up to 25% off through May 10th</p>
33+
<CountdownTimerSmall targetDate="2025-05-10" />
34+
</div>
2535

2636
{ppp && (
2737
<>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import headerCourse from '~/images/query-header-course.svg';
2+
import cornerTopLeft from '~/images/query-corner-top-left.svg';
3+
import cornerTopRight from '~/images/query-corner-top-right.svg';
4+
import cornerFishBottomRight from '~/images/query-corner-fish-bottom-right.svg';
5+
import CountdownTimer from '~/components/CountdownTimer'
6+
7+
export function QueryGGBannerSale(props: React.HTMLProps<HTMLDivElement>) {
8+
return (
9+
<aside {...props} className="mx-auto w-full max-w-[1200px] p-8 -mt-32 flex justify-between items-center">
10+
<div className="w-full xl:flex xl:gap-6 bg-[#f9f4da] border-4 border-[#231f20]">
11+
<a href="https://query.gg?s=tanstack" className="xl:w-[50%] 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]">
12+
<img
13+
src={cornerTopLeft}
14+
alt="sun"
15+
className=""
16+
/>
17+
<img
18+
src={headerCourse}
19+
alt="Query.gg - The Official React Query Course"
20+
className="-mt-[1px] w-10/12 max-w-[400px] justify-self-center"
21+
/>
22+
<img
23+
src={cornerTopRight}
24+
alt="moon"
25+
className="xl:hidden"
26+
/>
27+
</a>
28+
<div className="hidden xl:block w-[80px] mr-[-60px] bg-[#231f20] border-4 border-r-0 border-[#f9f4da] border-s-[#f9f4da] shadow-[-4px_0_0_#231f20] -skew-x-[15deg] z-0"></div>
29+
<div className="xl:w-[50%] 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">
30+
<div className="my-2 uppercase text-center place-self-center">
31+
{/* <h2 className="mt-1 mb-3 px-2 text-sm font-semibold">Launch sale happening now</h2> */}
32+
<h2 className="mb-1 text-lg lg:text-2xl xl:text-3xl font-semibold">
33+
Launch week sale
34+
</h2>
35+
<p className="normal-case mb-4">Up to 25% off through May 10th</p>
36+
<CountdownTimer targetDate="2025-05-10" />
37+
<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>
38+
</div>
39+
<img
40+
src={cornerFishBottomRight}
41+
alt="mutated fish"
42+
className="hidden xl:block self-end"
43+
/>
44+
</div>
45+
</div>
46+
</aside>
47+
)
48+
}

app/routes/_libraries/query.$version.index.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import { Carbon } from '~/components/Carbon'
77
import { Footer } from '~/components/Footer'
88
import { TbHeartHandshake } from 'react-icons/tb'
99
import SponsorPack from '~/components/SponsorPack'
10-
import { QueryGGBanner } from '~/components/QueryGGBanner'
10+
// import { QueryGGBanner } from '~/components/QueryGGBanner'
11+
import { QueryGGBannerSale } from '~/components/QueryGGBannerSale'
1112
import { queryProject } from '~/libraries/query'
1213
import { createFileRoute } from '@tanstack/react-router'
1314
import { Framework, getBranch, getLibrary } from '~/libraries'
@@ -48,6 +49,7 @@ export default function VersionIndex() {
4849
<div className="flex flex-1 min-h-0 relative justify-center overflow-x-hidden">
4950
<div className="flex flex-col gap-20 md:gap-32 max-w-full py-32">
5051
<div className="flex flex-col items-center gap-8 text-center px-4">
52+
<QueryGGBannerSale />
5153
<h1 className="font-black flex gap-3 items-center text-4xl md:text-6xl lg:text-7xl xl:text-8xl uppercase [letter-spacing:-.05em]">
5254
<span>TanStack</span>
5355
<span className={twMerge(gradientText)}>Query</span>
@@ -83,9 +85,9 @@ export default function VersionIndex() {
8385
>
8486
Read the Docs
8587
</Link>
86-
<p>(or check out our official course 👇)</p>
88+
<p>(or <a href="https://query.gg?s=tanstack" className="font-semibold underline">check out our official course</a>. It’s on sale!)</p>
8789
</div>
88-
<QueryGGBanner />
90+
{/* <QueryGGBanner /> */}
8991
</div>
9092
<LibraryFeatureHighlights
9193
featureHighlights={library.featureHighlights}

0 commit comments

Comments
 (0)