Add sale countdown timer expiring next Sunday
- Created SaleCountdown component with real-time countdown - Displays days, hours, minutes, seconds until sale ends - Responsive design with fire emoji and urgency messaging - Auto-calculates next Sunday at 23:59:59 - Positioned prominently above filament table
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { FilamentTableV2 } from '../src/components/FilamentTableV2';
|
import { FilamentTableV2 } from '../src/components/FilamentTableV2';
|
||||||
|
import { SaleCountdown } from '../src/components/SaleCountdown';
|
||||||
import { Filament } from '../src/types/filament';
|
import { Filament } from '../src/types/filament';
|
||||||
import { filamentService } from '../src/services/api';
|
import { filamentService } from '../src/services/api';
|
||||||
import { trackEvent } from '../src/components/MatomoAnalytics';
|
import { trackEvent } from '../src/components/MatomoAnalytics';
|
||||||
@@ -174,6 +175,8 @@ export default function Home() {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<SaleCountdown />
|
||||||
|
|
||||||
<FilamentTableV2
|
<FilamentTableV2
|
||||||
key={resetKey}
|
key={resetKey}
|
||||||
filaments={filaments}
|
filaments={filaments}
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
echo "🔍 Running pre-commit checks..."
|
echo "🔍 Running pre-commit checks..."
|
||||||
|
|
||||||
|
# Check for forbidden author mentions
|
||||||
|
/Users/dax/Documents/GitHub/Filamenteka/.git/hooks/pre-commit-author-check
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Run security check
|
# Run security check
|
||||||
echo "🔐 Checking for credential leaks..."
|
echo "🔐 Checking for credential leaks..."
|
||||||
npm run security:check
|
npm run security:check
|
||||||
|
|||||||
99
src/components/SaleCountdown.tsx
Normal file
99
src/components/SaleCountdown.tsx
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
interface TimeLeft {
|
||||||
|
days: number;
|
||||||
|
hours: number;
|
||||||
|
minutes: number;
|
||||||
|
seconds: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SaleCountdown() {
|
||||||
|
const [timeLeft, setTimeLeft] = useState<TimeLeft>({ days: 0, hours: 0, minutes: 0, seconds: 0 });
|
||||||
|
const [mounted, setMounted] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
const calculateTimeLeft = (): TimeLeft => {
|
||||||
|
// Get next Sunday at 23:59:59
|
||||||
|
const now = new Date();
|
||||||
|
const nextSunday = new Date();
|
||||||
|
const daysUntilSunday = (7 - now.getDay()) % 7;
|
||||||
|
const targetDay = daysUntilSunday === 0 ? 7 : daysUntilSunday; // If today is Sunday, target next Sunday
|
||||||
|
|
||||||
|
nextSunday.setDate(now.getDate() + targetDay);
|
||||||
|
nextSunday.setHours(23, 59, 59, 999);
|
||||||
|
|
||||||
|
const difference = nextSunday.getTime() - now.getTime();
|
||||||
|
|
||||||
|
if (difference > 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)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { days: 0, hours: 0, minutes: 0, seconds: 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
setTimeLeft(calculateTimeLeft());
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
// Initial calculation
|
||||||
|
setTimeLeft(calculateTimeLeft());
|
||||||
|
|
||||||
|
return () => clearInterval(timer);
|
||||||
|
}, [mounted]);
|
||||||
|
|
||||||
|
if (!mounted) {
|
||||||
|
return <div className="h-24" />; // Placeholder to prevent hydration mismatch
|
||||||
|
}
|
||||||
|
|
||||||
|
const isActive = timeLeft.days > 0 || timeLeft.hours > 0 || timeLeft.minutes > 0 || timeLeft.seconds > 0;
|
||||||
|
|
||||||
|
if (!isActive) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-gradient-to-r from-red-500 to-red-600 text-white py-4 px-6 rounded-lg shadow-lg mb-8 animate-pulse">
|
||||||
|
<div className="text-center">
|
||||||
|
<h3 className="text-lg sm:text-xl font-bold mb-2">🔥 AKCIJA SE ZAVRŠAVA!</h3>
|
||||||
|
<p className="text-sm sm:text-base mb-3">Popust od 5% važi još:</p>
|
||||||
|
|
||||||
|
<div className="flex justify-center gap-2 sm:gap-4">
|
||||||
|
<div className="bg-white/20 backdrop-blur rounded-lg px-2 sm:px-3 py-2 min-w-[60px] sm:min-w-[70px]">
|
||||||
|
<div className="text-xl sm:text-2xl font-bold">{timeLeft.days}</div>
|
||||||
|
<div className="text-xs sm:text-sm opacity-90">dana</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white/20 backdrop-blur rounded-lg px-2 sm:px-3 py-2 min-w-[60px] sm:min-w-[70px]">
|
||||||
|
<div className="text-xl sm:text-2xl font-bold">{timeLeft.hours.toString().padStart(2, '0')}</div>
|
||||||
|
<div className="text-xs sm:text-sm opacity-90">sati</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white/20 backdrop-blur rounded-lg px-2 sm:px-3 py-2 min-w-[60px] sm:min-w-[70px]">
|
||||||
|
<div className="text-xl sm:text-2xl font-bold">{timeLeft.minutes.toString().padStart(2, '0')}</div>
|
||||||
|
<div className="text-xs sm:text-sm opacity-90">min</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white/20 backdrop-blur rounded-lg px-2 sm:px-3 py-2 min-w-[60px] sm:min-w-[70px]">
|
||||||
|
<div className="text-xl sm:text-2xl font-bold">{timeLeft.seconds.toString().padStart(2, '0')}</div>
|
||||||
|
<div className="text-xs sm:text-sm opacity-90">sek</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-xs sm:text-sm mt-3 opacity-90">
|
||||||
|
Poruči sada i uštedi na Bambu Lab filamentima!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user