diff --git a/src/components/CountdownTimer.tsx b/src/components/CountdownTimer.tsx new file mode 100644 index 00000000..b62d17c2 --- /dev/null +++ b/src/components/CountdownTimer.tsx @@ -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 = ({ targetDate }) => { + const [timeLeft, setTimeLeft] = useState( + 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 ( +
+ {["days", "hours", "minutes", "seconds"].map((unit, index) => ( + + {index > 0 && :} + +
+ + {formatNumber(timeLeft[unit as keyof TimeLeft]).charAt(0)} + + + {formatNumber(timeLeft[unit as keyof TimeLeft]).charAt(1)} + +

{unit}

+
+
+ ))} +
+ ); +}; + +export default Countdown; \ No newline at end of file diff --git a/src/components/CountdownTimerSmall.tsx b/src/components/CountdownTimerSmall.tsx new file mode 100644 index 00000000..e618972c --- /dev/null +++ b/src/components/CountdownTimerSmall.tsx @@ -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 = ({ targetDate }) => { + const [timeLeft, setTimeLeft] = useState( + 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 ( +
+ {["days", "hours", "minutes"].map((unit, index) => ( + + {index > 0 && :} + +
+ + {formatNumber(timeLeft[unit as keyof TimeLeft]).charAt(0)} + + + {formatNumber(timeLeft[unit as keyof TimeLeft]).charAt(1)} + +

{unit}

+
+
+ ))} +
+ ); +}; + +export default Countdown; \ No newline at end of file diff --git a/src/components/DocsCalloutQueryGG.tsx b/src/components/DocsCalloutQueryGG.tsx index 1e0643c1..2351ad1d 100644 --- a/src/components/DocsCalloutQueryGG.tsx +++ b/src/components/DocsCalloutQueryGG.tsx @@ -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() @@ -17,14 +18,23 @@ export function DocsCalloutQueryGG() { -
+ {/*
“If you’re serious about *really* understanding React Query, there’s no better way than with query.gg” —Tanner Linsley -
- -
+
*/} + {/*
*/} +
+

+ Black Friday Sale +

+

+ Get 30% off through December 6th +

+ +
+ {ppp && ( <>

diff --git a/src/components/QueryGGBannerSale.tsx b/src/components/QueryGGBannerSale.tsx new file mode 100644 index 00000000..f6c261de --- /dev/null +++ b/src/components/QueryGGBannerSale.tsx @@ -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) { + return ( +

+ ) +} diff --git a/src/routes/_libraries/query.$version.index.tsx b/src/routes/_libraries/query.$version.index.tsx index 99d031a9..6fe70428 100644 --- a/src/routes/_libraries/query.$version.index.tsx +++ b/src/routes/_libraries/query.$version.index.tsx @@ -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' @@ -56,7 +57,8 @@ function VersionIndex() { }} />
- + {/* */} +