diff --git a/README.md b/README.md index 48c7715..c7a83f4 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Compatible with React v18.0.0+. - [useDocumentTitle](https://usehooks.com/usedocumenttitle) - [useFavicon](https://usehooks.com/usefavicon) - [useGeolocation](https://usehooks.com/usegeolocation) -- [useHistoryState](https://usehooks.com/usehistoryState) +- [useHistoryState](https://usehooks.com/usehistorystate) - [useHover](https://usehooks.com/usehover) - [useIdle](https://usehooks.com/useidle) - [useIntersectionObserver](https://usehooks.com/useintersectionobserver) diff --git a/usehooks.com/public/img/banner-sale-reactgg.svg b/usehooks.com/public/img/banner-sale-reactgg.svg new file mode 100644 index 0000000..9aee0dd --- /dev/null +++ b/usehooks.com/public/img/banner-sale-reactgg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/usehooks.com/src/components/CountdownTimer.tsx b/usehooks.com/src/components/CountdownTimer.tsx new file mode 100644 index 0000000..72ea4d4 --- /dev/null +++ b/usehooks.com/src/components/CountdownTimer.tsx @@ -0,0 +1,89 @@ +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; diff --git a/usehooks.com/src/components/QueryGGBanner.astro b/usehooks.com/src/components/QueryGGBanner.astro new file mode 100644 index 0000000..451e6c2 --- /dev/null +++ b/usehooks.com/src/components/QueryGGBanner.astro @@ -0,0 +1,138 @@ +--- +import Button from "./Button.astro"; +import CountdownTimer from "./CountdownTimer"; +--- + + + +