1'use client'
2import React, { useCallback, useEffect, useState } from 'react'
3
4export function Countdown01() {
5 const targetDate = React.useMemo(() => {
6 const now = new Date()
7 now.setDate(now.getDate() + 12)
8 return now.getTime()
9 }, [])
10
11 const calculateTimeLeft = useCallback(() => {
12 const now = Date.now()
13 const diff = targetDate - now
14
15 if (diff <= 0) {
16 return { days: 0, hours: 0, minutes: 0, seconds: 0 }
17 }
18
19 return {
20 days: Math.floor(diff / (1000 * 60 * 60 * 24)),
21 hours: Math.floor((diff / (1000 * 60 * 60)) % 24),
22 minutes: Math.floor((diff / (1000 * 60)) % 60),
23 seconds: Math.floor((diff / 1000) % 60),
24 }
25 }, [targetDate])
26
27 const [mounted, setMounted] = useState(false)
28 const [timeLeft, setTimeLeft] = useState({
29 days: 0,
30 hours: 0,
31 minutes: 0,
32 seconds: 0,
33 })
34
35 useEffect(() => {
36 setMounted(true)
37 setTimeLeft(calculateTimeLeft())
38
39 const timer = setInterval(() => {
40 setTimeLeft(calculateTimeLeft())
41 }, 1000)
42
43 return () => clearInterval(timer)
44 }, [calculateTimeLeft])
45
46 if (!mounted) return null
47
48 return (
49 <div className="flex gap-4">
50 {Object.entries(timeLeft).map(([label, value]) => (
51 <div
52 key={label}
53 className="bg-border text-gray flex h-20 w-20 flex-col items-center justify-center rounded-lg"
54 >
55 <span className="text-2xl font-bold">{value}</span>
56 <span className="text-xs uppercase">{label}</span>
57 </div>
58 ))}
59 </div>
60 )
61}
621'use client'
2import React, { useCallback, useEffect, useState } from 'react'
3
4export function Countdown01() {
5 const targetDate = React.useMemo(() => {
6 const now = new Date()
7 now.setDate(now.getDate() + 12)
8 return now.getTime()
9 }, [])
10
11 const calculateTimeLeft = useCallback(() => {
12 const now = Date.now()
13 const diff = targetDate - now
14
15 if (diff <= 0) {
16 return { days: 0, hours: 0, minutes: 0, seconds: 0 }
17 }
18
19 return {
20 days: Math.floor(diff / (1000 * 60 * 60 * 24)),
21 hours: Math.floor((diff / (1000 * 60 * 60)) % 24),
22 minutes: Math.floor((diff / (1000 * 60)) % 60),
23 seconds: Math.floor((diff / 1000) % 60),
24 }
25 }, [targetDate])
26
27 const [mounted, setMounted] = useState(false)
28 const [timeLeft, setTimeLeft] = useState({
29 days: 0,
30 hours: 0,
31 minutes: 0,
32 seconds: 0,
33 })
34
35 useEffect(() => {
36 setMounted(true)
37 setTimeLeft(calculateTimeLeft())
38
39 const timer = setInterval(() => {
40 setTimeLeft(calculateTimeLeft())
41 }, 1000)
42
43 return () => clearInterval(timer)
44 }, [calculateTimeLeft])
45
46 if (!mounted) return null
47
48 return (
49 <div className="flex gap-4">
50 {Object.entries(timeLeft).map(([label, value]) => (
51 <div
52 key={label}
53 className="bg-border text-gray flex h-20 w-20 flex-col items-center justify-center rounded-lg"
54 >
55 <span className="text-2xl font-bold">{value}</span>
56 <span className="text-xs uppercase">{label}</span>
57 </div>
58 ))}
59 </div>
60 )
61}
621'use client'
2import { useEffect, useState } from 'react'
3import { useForm } from 'react-hook-form'
4import { z } from 'zod'
5
6import { Button } from '@/components/ui/button'
7import { Card, CardContent } from '@/components/ui/card'
8import {
9 Form,
10 FormControl,
11 FormDescription,
12 FormField,
13 FormItem,
14 FormLabel,
15 FormMessage,
16} from '@/components/ui/form'
17import {
18 InputOTP,
19 InputOTPGroup,
20 InputOTPSlot,
21} from '@/components/ui/input-otp'
22import { zodResolver } from '@hookform/resolvers/zod'
23
24const otpSchema = z.object({
25 otp: z
26 .string()
27 .min(6, { message: 'Please enter the 6-digit code.' })
28 .max(6),
29})
30
31export function Countdown02({ ...props }: React.ComponentProps<'div'>) {
32 const form = useForm<z.infer<typeof otpSchema>>({
33 resolver: zodResolver(otpSchema),
34 defaultValues: { otp: '' },
35 })
36
37 const [secondsLeft, setSecondsLeft] = useState(30)
38 const [canResend, setCanResend] = useState(false)
39
40 const AUTO_RESET_DELAY = 10
41
42 useEffect(() => {
43 if (canResend) return
44
45 const timer = setInterval(() => {
46 setSecondsLeft((prev) => {
47 if (prev <= 1) {
48 setCanResend(true)
49 return 0
50 }
51 return prev - 1
52 })
53 }, 1000)
54
55 return () => clearInterval(timer)
56 }, [canResend])
57
58 useEffect(() => {
59 if (!canResend) return
60
61 const autoResetTimer = setTimeout(() => {
62 setSecondsLeft(30)
63 setCanResend(false)
64 }, AUTO_RESET_DELAY * 1000)
65
66 return () => clearTimeout(autoResetTimer)
67 }, [canResend])
68
69 const handleResend = () => {
70 console.log('🔁 Resending OTP...')
71 setSecondsLeft(30)
72 setCanResend(false)
73 }
74
75 const onSubmit = (values: z.infer<typeof otpSchema>) => {
76 console.log('✅ OTP Submitted:', values)
77 }
78 return (
79 <Card {...props}>
80 <CardContent>
81 <Form {...form}>
82 <form
83 onSubmit={form.handleSubmit(onSubmit)}
84 className="space-y-3"
85 >
86 <FormField
87 control={form.control}
88 name="otp"
89 render={({ field }) => (
90 <FormItem className="flex flex-col items-center justify-center">
91 <FormLabel>Verification code</FormLabel>
92 <FormControl>
93 <InputOTP
94 maxLength={6}
95 value={field.value}
96 onChange={field.onChange}
97 id="otp"
98 required
99 >
100 <InputOTPGroup>
101 <InputOTPSlot index={0} />
102 <InputOTPSlot index={1} />
103 <InputOTPSlot index={2} />
104 <InputOTPSlot index={3} />
105 <InputOTPSlot index={4} />
106 <InputOTPSlot index={5} />
107 </InputOTPGroup>
108 </InputOTP>
109 </FormControl>
110 <FormDescription>
111 Enter the 6-digit code sent to your
112 email.
113 </FormDescription>
114 <FormMessage />
115 </FormItem>
116 )}
117 />
118
119 <div className="border-border flex items-center justify-end gap-2 border-t pt-2">
120 <Button
121 variant="outline"
122 disabled={!canResend}
123 onClick={handleResend}
124 >
125 {canResend
126 ? 'Resend'
127 : `Resend in ${secondsLeft}s`}
128 </Button>
129 <Button type="submit">Verify</Button>
130 </div>
131 </form>
132 </Form>
133 </CardContent>
134 </Card>
135 )
136}
1371'use client'
2import { useEffect, useState } from 'react'
3import { useForm } from 'react-hook-form'
4import { z } from 'zod'
5
6import { Button } from '@/components/ui/button'
7import { Card, CardContent } from '@/components/ui/card'
8import {
9 Form,
10 FormControl,
11 FormDescription,
12 FormField,
13 FormItem,
14 FormLabel,
15 FormMessage,
16} from '@/components/ui/form'
17import {
18 InputOTP,
19 InputOTPGroup,
20 InputOTPSlot,
21} from '@/components/ui/input-otp'
22import { zodResolver } from '@hookform/resolvers/zod'
23
24const otpSchema = z.object({
25 otp: z
26 .string()
27 .min(6, { message: 'Please enter the 6-digit code.' })
28 .max(6),
29})
30
31export function Countdown02({ ...props }: React.ComponentProps<'div'>) {
32 const form = useForm<z.infer<typeof otpSchema>>({
33 resolver: zodResolver(otpSchema),
34 defaultValues: { otp: '' },
35 })
36
37 const [secondsLeft, setSecondsLeft] = useState(30)
38 const [canResend, setCanResend] = useState(false)
39
40 const AUTO_RESET_DELAY = 10
41
42 useEffect(() => {
43 if (canResend) return
44
45 const timer = setInterval(() => {
46 setSecondsLeft((prev) => {
47 if (prev <= 1) {
48 setCanResend(true)
49 return 0
50 }
51 return prev - 1
52 })
53 }, 1000)
54
55 return () => clearInterval(timer)
56 }, [canResend])
57
58 useEffect(() => {
59 if (!canResend) return
60
61 const autoResetTimer = setTimeout(() => {
62 setSecondsLeft(30)
63 setCanResend(false)
64 }, AUTO_RESET_DELAY * 1000)
65
66 return () => clearTimeout(autoResetTimer)
67 }, [canResend])
68
69 const handleResend = () => {
70 console.log('🔁 Resending OTP...')
71 setSecondsLeft(30)
72 setCanResend(false)
73 }
74
75 const onSubmit = (values: z.infer<typeof otpSchema>) => {
76 console.log('✅ OTP Submitted:', values)
77 }
78 return (
79 <Card {...props}>
80 <CardContent>
81 <Form {...form}>
82 <form
83 onSubmit={form.handleSubmit(onSubmit)}
84 className="space-y-3"
85 >
86 <FormField
87 control={form.control}
88 name="otp"
89 render={({ field }) => (
90 <FormItem className="flex flex-col items-center justify-center">
91 <FormLabel>Verification code</FormLabel>
92 <FormControl>
93 <InputOTP
94 maxLength={6}
95 value={field.value}
96 onChange={field.onChange}
97 id="otp"
98 required
99 >
100 <InputOTPGroup>
101 <InputOTPSlot index={0} />
102 <InputOTPSlot index={1} />
103 <InputOTPSlot index={2} />
104 <InputOTPSlot index={3} />
105 <InputOTPSlot index={4} />
106 <InputOTPSlot index={5} />
107 </InputOTPGroup>
108 </InputOTP>
109 </FormControl>
110 <FormDescription>
111 Enter the 6-digit code sent to your
112 email.
113 </FormDescription>
114 <FormMessage />
115 </FormItem>
116 )}
117 />
118
119 <div className="border-border flex items-center justify-end gap-2 border-t pt-2">
120 <Button
121 variant="outline"
122 disabled={!canResend}
123 onClick={handleResend}
124 >
125 {canResend
126 ? 'Resend'
127 : `Resend in ${secondsLeft}s`}
128 </Button>
129 <Button type="submit">Verify</Button>
130 </div>
131 </form>
132 </Form>
133 </CardContent>
134 </Card>
135 )
136}
1371'use client'
2
3import React, { useCallback, useEffect, useRef, useState } from 'react'
4
5import { Card, CardContent } from '@/components/ui/card'
6
7const StatCard = ({
8 end,
9 label,
10 prefix = '+',
11 duration = 20000,
12}: {
13 end: number
14 label: string
15 prefix?: string
16 duration?: number
17}) => {
18 const [count, setCount] = useState(0)
19 const elementRef = useRef<HTMLDivElement>(null)
20 const hasAnimatedRef = useRef(false)
21
22 const animateCount = useCallback(() => {
23 let startTime: number | null = null
24 let animationFrameId: number
25
26 const easeOutQuart = (t: number) => 1 - Math.pow(1 - t, 4)
27
28 const animate = (time: number) => {
29 if (!startTime) startTime = time
30
31 const progress = Math.min((time - (startTime ?? 0)) / duration, 1)
32 const easedProgress = easeOutQuart(progress)
33
34 setCount(Math.floor(easedProgress * end))
35
36 if (progress < 1) {
37 animationFrameId = requestAnimationFrame(animate)
38 }
39 }
40
41 animationFrameId = requestAnimationFrame(animate)
42
43 return () => cancelAnimationFrame(animationFrameId)
44 }, [duration, end])
45
46 useEffect(() => {
47 if (!elementRef.current) return
48
49 const observer = new IntersectionObserver(
50 ([entry]) => {
51 if (entry.isIntersecting && !hasAnimatedRef.current) {
52 hasAnimatedRef.current = true
53 animateCount()
54 }
55 },
56 { threshold: 0.2 },
57 )
58
59 observer.observe(elementRef.current)
60
61 return () => {
62 observer.disconnect()
63 }
64 }, [animateCount])
65
66 return (
67 <div
68 ref={elementRef}
69 className="flex flex-col items-center justify-center"
70 >
71 <div className="text-primary mb-4 text-5xl font-semibold">
72 {prefix}
73 {count}
74 </div>
75 <div className="text-primary text-xl font-semibold">{label}</div>
76 </div>
77 )
78}
79
80export function Countdown03() {
81 return (
82 <Card className="relative flex items-center justify-center overflow-hidden rounded-2xl border border-gray-200 bg-white py-10 shadow-sm">
83 <CardContent>
84 <div className="relative z-10 w-full max-w-7xl px-4">
85 <div className="grid grid-cols-2 gap-8 lg:grid-cols-4 lg:gap-12">
86 <StatCard end={25} label="Partners" />
87 <StatCard end={55} label="Clients" />
88 <StatCard end={110} label="Projects" />
89 <StatCard end={10} label="Experience" prefix="+" />
90 </div>
91 </div>
92 </CardContent>
93 </Card>
94 )
95}
961'use client'
2
3import React, { useCallback, useEffect, useRef, useState } from 'react'
4
5import { Card, CardContent } from '@/components/ui/card'
6
7const StatCard = ({
8 end,
9 label,
10 prefix = '+',
11 duration = 20000,
12}: {
13 end: number
14 label: string
15 prefix?: string
16 duration?: number
17}) => {
18 const [count, setCount] = useState(0)
19 const elementRef = useRef<HTMLDivElement>(null)
20 const hasAnimatedRef = useRef(false)
21
22 const animateCount = useCallback(() => {
23 let startTime: number | null = null
24 let animationFrameId: number
25
26 const easeOutQuart = (t: number) => 1 - Math.pow(1 - t, 4)
27
28 const animate = (time: number) => {
29 if (!startTime) startTime = time
30
31 const progress = Math.min((time - (startTime ?? 0)) / duration, 1)
32 const easedProgress = easeOutQuart(progress)
33
34 setCount(Math.floor(easedProgress * end))
35
36 if (progress < 1) {
37 animationFrameId = requestAnimationFrame(animate)
38 }
39 }
40
41 animationFrameId = requestAnimationFrame(animate)
42
43 return () => cancelAnimationFrame(animationFrameId)
44 }, [duration, end])
45
46 useEffect(() => {
47 if (!elementRef.current) return
48
49 const observer = new IntersectionObserver(
50 ([entry]) => {
51 if (entry.isIntersecting && !hasAnimatedRef.current) {
52 hasAnimatedRef.current = true
53 animateCount()
54 }
55 },
56 { threshold: 0.2 },
57 )
58
59 observer.observe(elementRef.current)
60
61 return () => {
62 observer.disconnect()
63 }
64 }, [animateCount])
65
66 return (
67 <div
68 ref={elementRef}
69 className="flex flex-col items-center justify-center"
70 >
71 <div className="text-primary mb-4 text-5xl font-semibold">
72 {prefix}
73 {count}
74 </div>
75 <div className="text-primary text-xl font-semibold">{label}</div>
76 </div>
77 )
78}
79
80export function Countdown03() {
81 return (
82 <Card className="relative flex items-center justify-center overflow-hidden rounded-2xl border border-gray-200 bg-white py-10 shadow-sm">
83 <CardContent>
84 <div className="relative z-10 w-full max-w-7xl px-4">
85 <div className="grid grid-cols-2 gap-8 lg:grid-cols-4 lg:gap-12">
86 <StatCard end={25} label="Partners" />
87 <StatCard end={55} label="Clients" />
88 <StatCard end={110} label="Projects" />
89 <StatCard end={10} label="Experience" prefix="+" />
90 </div>
91 </div>
92 </CardContent>
93 </Card>
94 )
95}
961'use client'
2import React, { useEffect, useState } from 'react'
3
4export function Countdown04() {
5 const START_VALUE = 8957
6 const [flipping, setFlipping] = useState<Record<number, boolean>>({})
7
8 const [number, setNumber] = useState(START_VALUE)
9 const [prevNumber, setPrevNumber] = useState(START_VALUE)
10
11 useEffect(() => {
12 if (number <= 0) return
13
14 const id = setTimeout(() => {
15 setPrevNumber(number)
16 setNumber((n) => n - 1)
17 }, 1000)
18
19 return () => clearTimeout(id)
20 }, [number])
21
22 useEffect(() => {
23 const digits = number.toString().padStart(4, '0').split('')
24 const prevDigits = prevNumber.toString().padStart(4, '0').split('')
25 const newFlips: Record<number, boolean> = {}
26
27 digits.forEach((digit, index) => {
28 if (digit !== prevDigits[index]) {
29 newFlips[index] = true
30 }
31 })
32
33 if (Object.keys(newFlips).length) {
34 setFlipping((prev) => ({ ...prev, ...newFlips }))
35
36 const timeout = setTimeout(() => {
37 setFlipping({})
38 }, 450)
39
40 return () => clearTimeout(timeout)
41 }
42 }, [number, prevNumber])
43
44 const digits = number.toString().padStart(4, '0').split('')
45 const prevDigits = prevNumber.toString().padStart(4, '0').split('')
46
47 return (
48 <>
49 <style>{`
50 .digit {
51 position: relative;
52 width: 1.6em;
53 height: 2.4em;
54 overflow: hidden;
55 }
56
57 .digit-inner {
58 position: absolute;
59 width: 100%;
60 height: 200%;
61 top: 0;
62 transition: transform 0.45s ease-in-out;
63 will-change: transform;
64 }
65
66 .digit.flip .digit-inner {
67 transform: translateY(-50%);
68 }
69
70 .face {
71 height: 50%;
72 display: flex;
73 align-items: center;
74 justify-content: center;
75 }
76 `}</style>
77
78 <div className="flex items-center justify-center gap-2 text-3xl">
79 <span>$</span>
80
81 {digits.map((digit, index) => {
82 const prev = prevDigits[index]
83
84 return (
85 <div
86 key={index}
87 className={`digit ${flipping[index] ? 'flip' : ''} bg-border rounded-lg text-3xl font-bold`}
88 >
89 <div className="digit-inner">
90 <div className="face">{prev}</div>
91 <div className="face">{digit}</div>
92 </div>
93 </div>
94 )
95 })}
96 </div>
97 </>
98 )
99}
1001'use client'
2import React, { useEffect, useState } from 'react'
3
4export function Countdown04() {
5 const START_VALUE = 8957
6 const [flipping, setFlipping] = useState<Record<number, boolean>>({})
7
8 const [number, setNumber] = useState(START_VALUE)
9 const [prevNumber, setPrevNumber] = useState(START_VALUE)
10
11 useEffect(() => {
12 if (number <= 0) return
13
14 const id = setTimeout(() => {
15 setPrevNumber(number)
16 setNumber((n) => n - 1)
17 }, 1000)
18
19 return () => clearTimeout(id)
20 }, [number])
21
22 useEffect(() => {
23 const digits = number.toString().padStart(4, '0').split('')
24 const prevDigits = prevNumber.toString().padStart(4, '0').split('')
25 const newFlips: Record<number, boolean> = {}
26
27 digits.forEach((digit, index) => {
28 if (digit !== prevDigits[index]) {
29 newFlips[index] = true
30 }
31 })
32
33 if (Object.keys(newFlips).length) {
34 setFlipping((prev) => ({ ...prev, ...newFlips }))
35
36 const timeout = setTimeout(() => {
37 setFlipping({})
38 }, 450)
39
40 return () => clearTimeout(timeout)
41 }
42 }, [number, prevNumber])
43
44 const digits = number.toString().padStart(4, '0').split('')
45 const prevDigits = prevNumber.toString().padStart(4, '0').split('')
46
47 return (
48 <>
49 <style>{`
50 .digit {
51 position: relative;
52 width: 1.6em;
53 height: 2.4em;
54 overflow: hidden;
55 }
56
57 .digit-inner {
58 position: absolute;
59 width: 100%;
60 height: 200%;
61 top: 0;
62 transition: transform 0.45s ease-in-out;
63 will-change: transform;
64 }
65
66 .digit.flip .digit-inner {
67 transform: translateY(-50%);
68 }
69
70 .face {
71 height: 50%;
72 display: flex;
73 align-items: center;
74 justify-content: center;
75 }
76 `}</style>
77
78 <div className="flex items-center justify-center gap-2 text-3xl">
79 <span>$</span>
80
81 {digits.map((digit, index) => {
82 const prev = prevDigits[index]
83
84 return (
85 <div
86 key={index}
87 className={`digit ${flipping[index] ? 'flip' : ''} bg-border rounded-lg text-3xl font-bold`}
88 >
89 <div className="digit-inner">
90 <div className="face">{prev}</div>
91 <div className="face">{digit}</div>
92 </div>
93 </div>
94 )
95 })}
96 </div>
97 </>
98 )
99}
1001'use client'
2import React, { useEffect, useState } from 'react'
3
4import { Button } from '@/components/ui/button'
5import { Card, CardContent } from '@/components/ui/card'
6import { Progress } from '@/components/ui/progress'
7
8export function Countdown05() {
9 const TOTAL_DURATION = 108 * 60 + 38
10 const TIME_LEFT = 100 * 60 + 38
11 const [timeLeft, setTimeLeft] = useState(TIME_LEFT)
12
13 useEffect(() => {
14 if (timeLeft <= 0) return
15
16 const interval = setInterval(() => {
17 setTimeLeft((prev) => {
18 if (prev <= 1) {
19 clearInterval(interval)
20 return 0
21 }
22 return prev - 1
23 })
24 }, 100)
25
26 return () => clearInterval(interval)
27 }, [timeLeft])
28
29 const hours = Math.floor((timeLeft % (24 * 60 * 60)) / (60 * 60))
30 const minutes = Math.floor((timeLeft % (60 * 60)) / 60)
31 const seconds = timeLeft % 60
32
33 const progressPercentage = (timeLeft / TOTAL_DURATION) * 100
34
35 const formatNumber = (num: number): string =>
36 num.toString().padStart(2, '0')
37
38 return (
39 <Card className="flex items-center justify-center">
40 <CardContent>
41 <div className="space-y-4 text-center">
42 <h2 className="text-xl font-bold sm:text-2xl">
43 HURRY! ONLY <span className="text-danger">16</span> LEFT
44 IN STOCK.
45 </h2>
46
47 <Progress value={progressPercentage} />
48
49 <div className="flex items-center justify-center gap-4 sm:gap-8">
50 <div className="flex flex-col items-center">
51 <div className="flex gap-1">
52 {formatNumber(hours)
53 .split('')
54 .map((digit: string, index: number) => (
55 <div
56 key={index}
57 className="border-border flex h-16 w-12 items-center justify-center rounded-lg border bg-gray-200 shadow-md sm:h-20 sm:w-16"
58 >
59 <span className="text-4xl font-bold sm:text-5xl">
60 {digit}
61 </span>
62 </div>
63 ))}
64 </div>
65 <span className="mt-3 text-sm font-medium">
66 Hours
67 </span>
68 </div>
69
70 <div className="flex flex-col items-center">
71 <div className="flex gap-1">
72 {formatNumber(minutes)
73 .split('')
74 .map((digit: string, index: number) => (
75 <div
76 key={index}
77 className="border-border flex h-16 w-12 items-center justify-center rounded-lg border bg-gray-200 shadow-md sm:h-20 sm:w-16"
78 >
79 <span className="text-4xl font-bold sm:text-5xl">
80 {digit}
81 </span>
82 </div>
83 ))}
84 </div>
85 <span className="mt-3 text-sm font-medium">
86 Mins
87 </span>
88 </div>
89
90 <div className="flex flex-col items-center">
91 <div className="flex gap-1">
92 {formatNumber(seconds)
93 .split('')
94 .map((digit: string, index: number) => (
95 <div
96 key={index}
97 className="border-border flex h-16 w-12 items-center justify-center rounded-lg border bg-gray-200 shadow-md sm:h-20 sm:w-16"
98 >
99 <span className="text-4xl font-bold sm:text-5xl">
100 {digit}
101 </span>
102 </div>
103 ))}
104 </div>
105 <span className="mt-3 text-sm font-medium">
106 Secs
107 </span>
108 </div>
109 </div>
110
111 <div className="text-center">
112 <Button>Order Now</Button>
113 </div>
114 </div>
115
116 <style jsx>{`
117 @keyframes shimmer {
118 0% {
119 transform: translateX(-100%);
120 }
121 100% {
122 transform: translateX(100%);
123 }
124 }
125
126 .animate-shimmer {
127 animation: shimmer 2s infinite;
128 }
129 `}</style>
130 </CardContent>
131 </Card>
132 )
133}
1341'use client'
2import React, { useEffect, useState } from 'react'
3
4import { Button } from '@/components/ui/button'
5import { Card, CardContent } from '@/components/ui/card'
6import { Progress } from '@/components/ui/progress'
7
8export function Countdown05() {
9 const TOTAL_DURATION = 108 * 60 + 38
10 const TIME_LEFT = 100 * 60 + 38
11 const [timeLeft, setTimeLeft] = useState(TIME_LEFT)
12
13 useEffect(() => {
14 if (timeLeft <= 0) return
15
16 const interval = setInterval(() => {
17 setTimeLeft((prev) => {
18 if (prev <= 1) {
19 clearInterval(interval)
20 return 0
21 }
22 return prev - 1
23 })
24 }, 100)
25
26 return () => clearInterval(interval)
27 }, [timeLeft])
28
29 const hours = Math.floor((timeLeft % (24 * 60 * 60)) / (60 * 60))
30 const minutes = Math.floor((timeLeft % (60 * 60)) / 60)
31 const seconds = timeLeft % 60
32
33 const progressPercentage = (timeLeft / TOTAL_DURATION) * 100
34
35 const formatNumber = (num: number): string =>
36 num.toString().padStart(2, '0')
37
38 return (
39 <Card className="flex items-center justify-center">
40 <CardContent>
41 <div className="space-y-4 text-center">
42 <h2 className="text-xl font-bold sm:text-2xl">
43 HURRY! ONLY <span className="text-danger">16</span> LEFT
44 IN STOCK.
45 </h2>
46
47 <Progress value={progressPercentage} />
48
49 <div className="flex items-center justify-center gap-4 sm:gap-8">
50 <div className="flex flex-col items-center">
51 <div className="flex gap-1">
52 {formatNumber(hours)
53 .split('')
54 .map((digit: string, index: number) => (
55 <div
56 key={index}
57 className="border-border flex h-16 w-12 items-center justify-center rounded-lg border bg-gray-200 shadow-md sm:h-20 sm:w-16"
58 >
59 <span className="text-4xl font-bold sm:text-5xl">
60 {digit}
61 </span>
62 </div>
63 ))}
64 </div>
65 <span className="mt-3 text-sm font-medium">
66 Hours
67 </span>
68 </div>
69
70 <div className="flex flex-col items-center">
71 <div className="flex gap-1">
72 {formatNumber(minutes)
73 .split('')
74 .map((digit: string, index: number) => (
75 <div
76 key={index}
77 className="border-border flex h-16 w-12 items-center justify-center rounded-lg border bg-gray-200 shadow-md sm:h-20 sm:w-16"
78 >
79 <span className="text-4xl font-bold sm:text-5xl">
80 {digit}
81 </span>
82 </div>
83 ))}
84 </div>
85 <span className="mt-3 text-sm font-medium">
86 Mins
87 </span>
88 </div>
89
90 <div className="flex flex-col items-center">
91 <div className="flex gap-1">
92 {formatNumber(seconds)
93 .split('')
94 .map((digit: string, index: number) => (
95 <div
96 key={index}
97 className="border-border flex h-16 w-12 items-center justify-center rounded-lg border bg-gray-200 shadow-md sm:h-20 sm:w-16"
98 >
99 <span className="text-4xl font-bold sm:text-5xl">
100 {digit}
101 </span>
102 </div>
103 ))}
104 </div>
105 <span className="mt-3 text-sm font-medium">
106 Secs
107 </span>
108 </div>
109 </div>
110
111 <div className="text-center">
112 <Button>Order Now</Button>
113 </div>
114 </div>
115
116 <style jsx>{`
117 @keyframes shimmer {
118 0% {
119 transform: translateX(-100%);
120 }
121 100% {
122 transform: translateX(100%);
123 }
124 }
125
126 .animate-shimmer {
127 animation: shimmer 2s infinite;
128 }
129 `}</style>
130 </CardContent>
131 </Card>
132 )
133}
134