146 lines
5.3 KiB
TypeScript
146 lines
5.3 KiB
TypeScript
"use client";
|
|
import { useState, useEffect, useRef } from 'react';
|
|
import { EventCardProps } from '@/types/space';
|
|
import { useRouter } from 'next/navigation';
|
|
import { Sparkles } from 'lucide-react';
|
|
|
|
export default function EventCard({ id, title, targetDate, endDate, icon }: EventCardProps) {
|
|
const [isLive, setIsLive] = useState(false);
|
|
const router = useRouter();
|
|
const [timeLeft, setTimeLeft] = useState({ d: "00", h: "00", m: "00", s: "00" });
|
|
|
|
const lastRefreshedRef = useRef<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
const updateTimer = () => {
|
|
if (!targetDate || !endDate) return;
|
|
const now = new Date();
|
|
const timeToStart = targetDate.getTime() - now.getTime();
|
|
const timeToEnd = endDate.getTime() - now.getTime();
|
|
|
|
if (timeToStart <= 0 && timeToEnd > 0) {
|
|
if (!isLive) setIsLive(true);
|
|
return;
|
|
}
|
|
|
|
if (timeToEnd <= 0) {
|
|
if (isLive) setIsLive(false);
|
|
|
|
const passId = endDate.toISOString();
|
|
|
|
if (lastRefreshedRef.current !== passId) {
|
|
|
|
const isStaleDataFromServer = endDate.getTime() < (new Date().getTime() - 5000);
|
|
|
|
if (!isStaleDataFromServer) {
|
|
console.log("🛰️ Pass complete. Syncing with Mission Control...");
|
|
lastRefreshedRef.current = passId;
|
|
router.refresh();
|
|
} else {
|
|
console.warn("⚠️ Server returned stale ISS data. Standing by for worker update...");
|
|
lastRefreshedRef.current = passId;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
const distance = timeToStart;
|
|
const d = Math.floor(distance / (1000 * 60 * 60 * 24));
|
|
const h = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
|
const m = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
|
|
const s = Math.floor((distance % (1000 * 60)) / 1000);
|
|
|
|
setTimeLeft({
|
|
d: d.toString().padStart(2, '0'),
|
|
h: h.toString().padStart(2, '0'),
|
|
m: m.toString().padStart(2, '0'),
|
|
s: s.toString().padStart(2, '0')
|
|
});
|
|
};
|
|
|
|
const interval = setInterval(updateTimer, 1000);
|
|
updateTimer();
|
|
|
|
return () => clearInterval(interval);
|
|
}, [targetDate, endDate, isLive, router]);
|
|
|
|
if (!targetDate) {
|
|
return (
|
|
<div className="relative p-6 rounded-xl border border-slate-800 bg-slate-900/20 backdrop-blur-md overflow-hidden opacity-60">
|
|
<div className="flex items-center gap-4 mb-4">
|
|
<div className="p-2 bg-slate-700/30 rounded-lg text-slate-500">
|
|
{icon}
|
|
</div>
|
|
<h3 className="text-slate-500 font-mono tracking-widest uppercase text-xs">
|
|
{title}
|
|
</h3>
|
|
</div>
|
|
<div className="py-2">
|
|
<div className="h-8 w-48 bg-slate-800 animate-pulse rounded" />
|
|
<p className="text-[10px] text-slate-600 mt-4 font-mono uppercase tracking-widest italic">
|
|
Waiting for orbital telemetry...
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className={`relative p-6 rounded-xl border transition-all duration-500 ${isLive ? 'border-blue-500 bg-blue-900/20 shadow-[0_0_20px_rgba(59,130,246,0.3)]' : 'border-slate-800 bg-slate-900/40'}`}>
|
|
{isLive ? (
|
|
<div className="flex flex-col items-center justify-center py-4 animate-pulse">
|
|
<Sparkles className="text-yellow-400 mb-2" size={32} />
|
|
<h3 className="text-xl font-bold text-white text-center">
|
|
{id === 'iss' ? 'Look Up!' : 'Event in Progress'}
|
|
</h3>
|
|
<p className="text-blue-300 text-sm text-center mt-1">
|
|
{id === 'iss'
|
|
? 'The ISS is directly above. Give them a wave! 👋'
|
|
: `The ${title} is occurring right now.`}
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<>
|
|
<div className="flex items-center gap-4 mb-4">
|
|
<div className="p-2 bg-blue-500/10 rounded-lg text-blue-400">
|
|
{icon}
|
|
</div>
|
|
<h3 className="text-slate-300 font-mono tracking-widest uppercase text-xs">
|
|
{title}
|
|
</h3>
|
|
</div>
|
|
|
|
<div className="text-4xl font-mono text-white flex items-baseline gap-2">
|
|
{parseInt(timeLeft.d) >= 2 ? (
|
|
/* Case 1: More than 2 days - Keep it super simple */
|
|
<>
|
|
<span>{timeLeft.d}</span>
|
|
<span className="text-slate-500 text-xl lowercase">Days</span>
|
|
</>
|
|
) : parseInt(timeLeft.d) === 1 ? (
|
|
/* Case 2: Between 24 and 48 hours - Show Day + Hours */
|
|
<>
|
|
<span>01</span><span className="text-slate-500 text-sm">d</span>
|
|
<span className="text-slate-500">:</span>
|
|
<span>{timeLeft.h}</span><span className="text-slate-500 text-sm">h</span>
|
|
</>
|
|
) : (
|
|
/* Case 3: Under 24 hours - The full high-precision clock */
|
|
<>
|
|
<span>{timeLeft.h}</span>
|
|
<span className="text-slate-500">:</span>
|
|
<span>{timeLeft.m}</span>
|
|
<span className="text-slate-500">:</span>
|
|
<span>{timeLeft.s}</span>
|
|
</>
|
|
)}
|
|
</div>
|
|
|
|
<p className="text-[10px] text-slate-500 mt-2 font-mono uppercase tracking-widest leading-none">
|
|
{parseInt(timeLeft.d) > 0 ? "Until event" : "T-Minus to Horizon"}
|
|
</p>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
} |