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 ;
0 commit comments