From 748056dcd8073998175dd98f93db8ec65331e11b Mon Sep 17 00:00:00 2001 From: Lynn Fisher Date: Fri, 2 May 2025 09:40:56 -0700 Subject: [PATCH 1/2] add launch banners to query page and docs --- app/components/CountdownTimer.tsx | 90 +++++++++++++++++++ app/components/CountdownTimerSmall.tsx | 85 ++++++++++++++++++ app/components/DocsCalloutQueryGG.tsx | 14 ++- app/components/QueryGGBannerSale.tsx | 48 ++++++++++ .../_libraries/query.$version.index.tsx | 8 +- 5 files changed, 240 insertions(+), 5 deletions(-) create mode 100644 app/components/CountdownTimer.tsx create mode 100644 app/components/CountdownTimerSmall.tsx create mode 100644 app/components/QueryGGBannerSale.tsx diff --git a/app/components/CountdownTimer.tsx b/app/components/CountdownTimer.tsx new file mode 100644 index 000000000..b62d17c2c --- /dev/null +++ b/app/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/app/components/CountdownTimerSmall.tsx b/app/components/CountdownTimerSmall.tsx new file mode 100644 index 000000000..70c629ac7 --- /dev/null +++ b/app/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; diff --git a/app/components/DocsCalloutQueryGG.tsx b/app/components/DocsCalloutQueryGG.tsx index 5ab6aa71c..b5357ba26 100644 --- a/app/components/DocsCalloutQueryGG.tsx +++ b/app/components/DocsCalloutQueryGG.tsx @@ -1,4 +1,5 @@ import { LogoQueryGGSmall } from '~/components/LogoQueryGGSmall' +import CountdownTimerSmall from '~/components/CountdownTimerSmall' import { useQueryGGPPPDiscount } from '~/hooks/useQueryGGPPPDiscount' export function DocsCalloutQueryGG() { @@ -17,11 +18,20 @@ export function DocsCalloutQueryGG() { -
+ {/*
“If you're serious about *really* understanding React Query, there's no better way than with query.gg” —Tanner Linsley -
+
*/} + + {/*
*/} +
+

+ Launch week sale +

+

Up to 25% off through May 10th

+ +
{ppp && ( <> diff --git a/app/components/QueryGGBannerSale.tsx b/app/components/QueryGGBannerSale.tsx new file mode 100644 index 000000000..f37a6aaf9 --- /dev/null +++ b/app/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/app/routes/_libraries/query.$version.index.tsx b/app/routes/_libraries/query.$version.index.tsx index 9e1831a61..797d78b7d 100644 --- a/app/routes/_libraries/query.$version.index.tsx +++ b/app/routes/_libraries/query.$version.index.tsx @@ -7,7 +7,8 @@ import { Carbon } from '~/components/Carbon' import { Footer } from '~/components/Footer' import { TbHeartHandshake } from 'react-icons/tb' import SponsorPack from '~/components/SponsorPack' -import { QueryGGBanner } from '~/components/QueryGGBanner' +// import { QueryGGBanner } from '~/components/QueryGGBanner' +import { QueryGGBannerSale } from '~/components/QueryGGBannerSale' import { queryProject } from '~/libraries/query' import { createFileRoute } from '@tanstack/react-router' import { Framework, getBranch, getLibrary } from '~/libraries' @@ -48,6 +49,7 @@ export default function VersionIndex() {
+

TanStack Query @@ -83,9 +85,9 @@ export default function VersionIndex() { > Read the Docs -

(or check out our official course 👇)

+

(or check out our official course. It’s on sale!)

- + {/* */}
Date: Sun, 4 May 2025 02:13:45 +1200 Subject: [PATCH 2/2] style: prettier formatting --- app/components/CountdownTimer.tsx | 54 ++++++++--------- app/components/CountdownTimerSmall.tsx | 58 +++++++++---------- app/components/DocsCalloutQueryGG.tsx | 4 +- app/components/QueryGGBannerSale.tsx | 39 +++++++------ .../_libraries/query.$version.index.tsx | 11 +++- 5 files changed, 90 insertions(+), 76 deletions(-) diff --git a/app/components/CountdownTimer.tsx b/app/components/CountdownTimer.tsx index b62d17c2c..1969e0576 100644 --- a/app/components/CountdownTimer.tsx +++ b/app/components/CountdownTimer.tsx @@ -1,20 +1,20 @@ -import { Fragment, useEffect, useState } from "react"; +import { Fragment, useEffect, useState } from 'react' interface CountdownProps { - targetDate: string; // YYYY-MM-DD format + targetDate: string // YYYY-MM-DD format } interface TimeLeft { - days: number; - hours: number; - minutes: number; - seconds: number; + 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; + const target = new Date(`${targetDate}T00:00:00-08:00`) + const now = new Date() + const difference = +target - +now if (difference <= 0) { return { @@ -22,7 +22,7 @@ function calculateTimeLeft(targetDate: string): TimeLeft { hours: 0, minutes: 0, seconds: 0, - }; + } } return { @@ -30,32 +30,32 @@ function calculateTimeLeft(targetDate: string): TimeLeft { 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 formatNumber = (number: number) => number.toString().padStart(2, '0') const Countdown: React.FC = ({ targetDate }) => { const [timeLeft, setTimeLeft] = useState( - calculateTimeLeft(targetDate), - ); + calculateTimeLeft(targetDate) + ) useEffect(() => { const timer = setInterval(() => { - const newTimeLeft = calculateTimeLeft(targetDate); - setTimeLeft(newTimeLeft); + const newTimeLeft = calculateTimeLeft(targetDate) + setTimeLeft(newTimeLeft) if ( newTimeLeft.days === 0 && newTimeLeft.hours === 0 && newTimeLeft.minutes === 0 && newTimeLeft.seconds === 0 ) { - clearInterval(timer); + clearInterval(timer) } - }, 1000); + }, 1000) - return () => clearInterval(timer); - }, [targetDate]); + return () => clearInterval(timer) + }, [targetDate]) if ( timeLeft.days === 0 && @@ -63,14 +63,16 @@ const Countdown: React.FC = ({ targetDate }) => { timeLeft.minutes === 0 && timeLeft.seconds === 0 ) { - return null; + return null } return (
- {["days", "hours", "minutes", "seconds"].map((unit, index) => ( + {['days', 'hours', 'minutes', 'seconds'].map((unit, index) => ( - {index > 0 && :} + {index > 0 && ( + : + )}
@@ -84,7 +86,7 @@ const Countdown: React.FC = ({ targetDate }) => { ))}
- ); -}; + ) +} -export default Countdown; \ No newline at end of file +export default Countdown diff --git a/app/components/CountdownTimerSmall.tsx b/app/components/CountdownTimerSmall.tsx index 70c629ac7..8693b2cbc 100644 --- a/app/components/CountdownTimerSmall.tsx +++ b/app/components/CountdownTimerSmall.tsx @@ -1,71 +1,69 @@ -import { Fragment, useEffect, useState } from "react"; +import { Fragment, useEffect, useState } from 'react' interface CountdownProps { - targetDate: string; // YYYY-MM-DD format + targetDate: string // YYYY-MM-DD format } interface TimeLeft { - days: number; - hours: number; - minutes: number; + 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; + 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 formatNumber = (number: number) => number.toString().padStart(2, '0') const Countdown: React.FC = ({ targetDate }) => { const [timeLeft, setTimeLeft] = useState( - calculateTimeLeft(targetDate), - ); + calculateTimeLeft(targetDate) + ) useEffect(() => { const timer = setInterval(() => { - const newTimeLeft = calculateTimeLeft(targetDate); - setTimeLeft(newTimeLeft); + const newTimeLeft = calculateTimeLeft(targetDate) + setTimeLeft(newTimeLeft) if ( newTimeLeft.days === 0 && newTimeLeft.hours === 0 && newTimeLeft.minutes === 0 ) { - clearInterval(timer); + clearInterval(timer) } - }, 1000); + }, 1000) - return () => clearInterval(timer); - }, [targetDate]); + return () => clearInterval(timer) + }, [targetDate]) - if ( - timeLeft.days === 0 && - timeLeft.hours === 0 && - timeLeft.minutes === 0 - ) { - return null; + if (timeLeft.days === 0 && timeLeft.hours === 0 && timeLeft.minutes === 0) { + return null } return (
- {["days", "hours", "minutes"].map((unit, index) => ( + {['days', 'hours', 'minutes'].map((unit, index) => ( - {index > 0 && :} + {index > 0 && ( + : + )}
@@ -79,7 +77,7 @@ const Countdown: React.FC = ({ targetDate }) => { ))}
- ); -}; + ) +} -export default Countdown; +export default Countdown diff --git a/app/components/DocsCalloutQueryGG.tsx b/app/components/DocsCalloutQueryGG.tsx index b5357ba26..39ecfcafa 100644 --- a/app/components/DocsCalloutQueryGG.tsx +++ b/app/components/DocsCalloutQueryGG.tsx @@ -29,7 +29,9 @@ export function DocsCalloutQueryGG() {

Launch week sale

-

Up to 25% off through May 10th

+

+ Up to 25% off through May 10th +

diff --git a/app/components/QueryGGBannerSale.tsx b/app/components/QueryGGBannerSale.tsx index f37a6aaf9..1b42214e8 100644 --- a/app/components/QueryGGBannerSale.tsx +++ b/app/components/QueryGGBannerSale.tsx @@ -1,40 +1,43 @@ -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 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 ( -