Skip to content

Commit de36565

Browse files
authored
Update query.gg banners for Black Friday sale (#538)
* update query.gg banners for Black Friday sale * fix linting issue * fix linting issue
1 parent 4235416 commit de36565

File tree

7 files changed

+298
-53
lines changed

7 files changed

+298
-53
lines changed

src/components/CountdownTimer.tsx

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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 && (
74+
<span className="h-[2rem] grid place-content-center">:</span>
75+
)}
76+
77+
<div className={`${unit} grid grid-cols-2 gap-x-1 gap-y-1.5`}>
78+
<span className="h-[2.3rem] aspect-[6/7] grid place-content-center rounded-sm bg-[#f9f4da] bg-opacity-10 font-semibold">
79+
{formatNumber(timeLeft[unit as keyof TimeLeft]).charAt(0)}
80+
</span>
81+
<span className="h-[2.3rem] aspect-[6/7] grid place-content-center rounded-sm bg-[#f9f4da] bg-opacity-10 font-semibold">
82+
{formatNumber(timeLeft[unit as keyof TimeLeft]).charAt(1)}
83+
</span>
84+
<p className="col-span-full text-xs">{unit}</p>
85+
</div>
86+
</Fragment>
87+
))}
88+
</div>
89+
)
90+
}
91+
92+
export default Countdown
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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 (timeLeft.days === 0 && timeLeft.hours === 0 && timeLeft.minutes === 0) {
57+
return null
58+
}
59+
60+
return (
61+
<div className="mb-4 countdown flex gap-1.5 justify-center">
62+
{['days', 'hours', 'minutes'].map((unit, index) => (
63+
<Fragment key={unit}>
64+
{index > 0 && (
65+
<span className="h-[1.4em] grid place-content-center">:</span>
66+
)}
67+
68+
<div className={`${unit} grid grid-cols-2 gap-x-1 gap-y-1.5`}>
69+
<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">
70+
{formatNumber(timeLeft[unit as keyof TimeLeft]).charAt(0)}
71+
</span>
72+
<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">
73+
{formatNumber(timeLeft[unit as keyof TimeLeft]).charAt(1)}
74+
</span>
75+
<p className="col-span-full text-[.65rem]">{unit}</p>
76+
</div>
77+
</Fragment>
78+
))}
79+
</div>
80+
)
81+
}
82+
83+
export default Countdown

src/components/DocsCalloutQueryGG.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { LogoQueryGGSmall } from '~/components/LogoQueryGGSmall'
22
import { useQueryGGPPPDiscount } from '~/hooks/useQueryGGPPPDiscount'
3+
import CountdownTimerSmall from '~/components/CountdownTimerSmall'
34

45
export function DocsCalloutQueryGG() {
56
const ppp = useQueryGGPPPDiscount()
@@ -17,13 +18,22 @@ 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> */}
2526

26-
<div className="grid justify-center bg-gray-800 dark:bg-gray-100 text-gray-100 dark:text-gray-800 z-10"></div>
27+
{/* <div className="grid justify-center bg-gray-800 dark:bg-gray-100 text-gray-100 dark:text-gray-800 z-10"></div> */}
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+
Black Friday Sale
31+
</h2>
32+
<p className="normal-case mb-4 text-sm text-balance">
33+
Get 30% off through December 6th
34+
</p>
35+
<CountdownTimerSmall targetDate="2025-12-06" />
36+
</div>
2737

2838
{ppp && (
2939
<>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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
10+
{...props}
11+
className="mx-auto w-full max-w-[1200px] p-8 -mt-32 flex justify-between items-center"
12+
>
13+
<div className="w-full xl:flex xl:gap-6 bg-[#f9f4da] border-4 border-[#231f20]">
14+
<a
15+
href="https://query.gg?s=tanstack"
16+
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]"
17+
>
18+
<img src={cornerTopLeft} alt="sun" className="" />
19+
<img
20+
src={headerCourse}
21+
alt="Query.gg - The Official React Query Course"
22+
className="-mt-[1px] w-10/12 max-w-[400px] justify-self-center"
23+
/>
24+
<img src={cornerTopRight} alt="moon" className="xl:hidden" />
25+
</a>
26+
<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>
27+
<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">
28+
<div className="my-2 uppercase text-center place-self-center">
29+
{/* <h2 className="mt-1 mb-3 px-2 text-sm font-semibold">Launch sale happening now</h2> */}
30+
<h2 className="mb-1 text-xl lg:text-2xl xl:text-3xl font-semibold">
31+
Black Friday sale
32+
</h2>
33+
<p className="normal-case mb-4">Get 30% off through December 6th</p>
34+
<CountdownTimer targetDate="2025-12-06" />
35+
<a
36+
href="https://query.gg?s=tanstack"
37+
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"
38+
>
39+
Join now
40+
</a>
41+
</div>
42+
<img
43+
src={cornerFishBottomRight}
44+
alt="mutated fish"
45+
className="hidden xl:block self-end"
46+
/>
47+
</div>
48+
</div>
49+
</aside>
50+
)
51+
}

src/routes/_libraries/index.tsx

Lines changed: 47 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -391,51 +391,58 @@ function Index() {
391391
</a>
392392
</h3>
393393
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
394-
{recentPosts.map(({ slug, title, published, excerpt, authors }) => {
395-
return (
396-
<Link
397-
key={slug}
398-
to="/blog/$"
399-
params={{ _splat: slug }}
400-
className={`flex flex-col gap-3 justify-between
394+
{recentPosts.map(
395+
({ slug, title, published, excerpt, authors }) => {
396+
return (
397+
<Link
398+
key={slug}
399+
to="/blog/$"
400+
params={{ _splat: slug }}
401+
className={`flex flex-col gap-3 justify-between
401402
border-2 border-transparent rounded-lg p-4
402403
transition-all bg-white/90 dark:bg-black/40
403404
shadow-md dark:shadow-lg dark:shadow-blue-500/20
404405
hover:border-blue-500 hover:shadow-xl
405406
`}
406-
>
407-
<div>
408-
<div className={`text-base font-bold`}>{title}</div>
409-
<div className={`text-xs italic font-light mt-1 text-gray-600 dark:text-gray-400`}>
410-
<p>
411-
by {formatAuthors(authors)}
412-
{published ? (
413-
<time
414-
dateTime={published}
415-
title={format(new Date(published), 'MMM dd, yyyy')}
416-
>
417-
{' '}
418-
on {format(new Date(published), 'MMM dd, yyyy')}
419-
</time>
420-
) : null}
421-
</p>
422-
</div>
423-
{excerpt && (
407+
>
408+
<div>
409+
<div className={`text-base font-bold`}>{title}</div>
424410
<div
425-
className={`text-xs mt-3 text-gray-700 dark:text-gray-300 line-clamp-2 leading-relaxed`}
411+
className={`text-xs italic font-light mt-1 text-gray-600 dark:text-gray-400`}
426412
>
427-
<Markdown rawContent={excerpt} />
413+
<p>
414+
by {formatAuthors(authors)}
415+
{published ? (
416+
<time
417+
dateTime={published}
418+
title={format(
419+
new Date(published),
420+
'MMM dd, yyyy'
421+
)}
422+
>
423+
{' '}
424+
on {format(new Date(published), 'MMM dd, yyyy')}
425+
</time>
426+
) : null}
427+
</p>
428+
</div>
429+
{excerpt && (
430+
<div
431+
className={`text-xs mt-3 text-gray-700 dark:text-gray-300 line-clamp-2 leading-relaxed`}
432+
>
433+
<Markdown rawContent={excerpt} />
434+
</div>
435+
)}
436+
</div>
437+
<div>
438+
<div className="text-blue-500 uppercase font-bold text-xs">
439+
Read More →
428440
</div>
429-
)}
430-
</div>
431-
<div>
432-
<div className="text-blue-500 uppercase font-bold text-xs">
433-
Read More →
434441
</div>
435-
</div>
436-
</Link>
437-
)
438-
})}
442+
</Link>
443+
)
444+
}
445+
)}
439446
</div>
440447
<div className="text-center mt-6">
441448
<Link
@@ -517,7 +524,10 @@ function Index() {
517524
</div>
518525

519526
<div className="px-4 lg:max-w-(--breakpoint-lg) md:mx-auto">
520-
<h3 id="maintainers" className={`text-4xl font-light mb-6 scroll-mt-24`}>
527+
<h3
528+
id="maintainers"
529+
className={`text-4xl font-light mb-6 scroll-mt-24`}
530+
>
521531
<a
522532
href="#maintainers"
523533
className="hover:underline decoration-gray-400 dark:decoration-gray-600"

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import { LazySponsorSection } from '~/components/LazySponsorSection'
77
import { PartnersSection } from '~/components/PartnersSection'
88
import { BottomCTA } from '~/components/BottomCTA'
99
import { StackBlitzEmbed } from '~/components/StackBlitzEmbed'
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 { Framework, getBranch, getLibrary } from '~/libraries'
1314
import { seo } from '~/utils/seo'
@@ -56,7 +57,8 @@ function VersionIndex() {
5657
}}
5758
/>
5859
<div className="px-4">
59-
<QueryGGBanner />
60+
{/* <QueryGGBanner /> */}
61+
<QueryGGBannerSale />
6062
</div>
6163

6264
<div className="w-fit mx-auto px-4">

0 commit comments

Comments
 (0)