158 lines
5.4 KiB
TypeScript
158 lines
5.4 KiB
TypeScript
import { useEffect, useState } from "react";
|
||
import { useNavigate } from "react-router-dom";
|
||
import { fetchTopDeals, type TopRange } from "../../api/deal/getTopDeals";
|
||
import { scoreToHeat } from "../../utils/heat";
|
||
|
||
const LIMIT = 6;
|
||
|
||
const TABS: { key: TopRange; label: string }[] = [
|
||
{ key: "day", label: "Günlük" },
|
||
{ key: "week", label: "Haftalık" },
|
||
{ key: "month", label: "Aylık" },
|
||
];
|
||
|
||
export default function HotDealsSidebar() {
|
||
const navigate = useNavigate();
|
||
const [range, setRange] = useState<TopRange>("day");
|
||
const [items, setItems] = useState<any[]>([]); // Initialize with the correct type
|
||
const [loading, setLoading] = useState(false);
|
||
const [err, setErr] = useState<string | null>(null);
|
||
|
||
useEffect(() => {
|
||
let canceled = false;
|
||
|
||
const load = async () => {
|
||
setLoading(true);
|
||
setErr(null);
|
||
try {
|
||
const data = await fetchTopDeals(range, LIMIT);
|
||
if (canceled) return;
|
||
setItems(Array.isArray(data) ? data.slice(0, LIMIT) : []);
|
||
} catch (e: any) {
|
||
if (canceled) return;
|
||
setErr(e?.message ?? "Bir hata oluştu");
|
||
setItems([]);
|
||
} finally {
|
||
if (!canceled) setLoading(false);
|
||
}
|
||
};
|
||
|
||
load();
|
||
return () => {
|
||
canceled = true;
|
||
};
|
||
}, [range]);
|
||
|
||
return (
|
||
<div className="rounded shadow-lg bg-surface p-4">
|
||
{/* Title and Tabs */}
|
||
<div className="text-center">
|
||
<div className="text-sm font-semibold text-grey">En İyi İlanlar</div>
|
||
|
||
<div className="mt-3 flex justify-center">
|
||
<div className="relative inline-flex items-center rounded-full bg-background shadow-md p-1">
|
||
{/* Sliding indicator */}
|
||
<div
|
||
className="absolute top-1 bottom-1 rounded-full transition-all bg-background/50 border border-white/10 shadow-sm duration-200"
|
||
style={{}}
|
||
/>
|
||
|
||
{TABS.map((t) => {
|
||
const isActive = t.key === range;
|
||
return (
|
||
<button
|
||
key={t.key}
|
||
type="button"
|
||
onClick={() => setRange(t.key)}
|
||
className={[
|
||
"relative z-10 px-3 py-1.5 rounded-full text-xs font-semibold transition-colors cursor-pointer",
|
||
isActive ? "text-primary" : "text-text-muted hover:text-text",
|
||
].join(" ")}
|
||
>
|
||
{t.label}
|
||
</button>
|
||
);
|
||
})}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{err && (
|
||
<div className="mt-3 rounded-xl shadow-md bg-[linear-gradient(90deg,var(--color-notice-danger-soft),var(--color-surface))] px-3 py-2 text-xs text-text">
|
||
{err}
|
||
</div>
|
||
)}
|
||
|
||
{/* Items - Vertical Layout (1 item per row) */}
|
||
<div className="mt-4 flex flex-col gap-4">
|
||
{loading && items.length === 0
|
||
? Array.from({ length: LIMIT }).map((_, i) => (
|
||
<div key={i} className="h-[200px] rounded-2xl shadow-md bg-background/40 animate-pulse" />
|
||
))
|
||
: items.map((d) => {
|
||
const img = d.imageUrl || "/placeholder.png";
|
||
const price = d.price ? `${d.price}₺` : "";
|
||
const title = d.title || "Ürün adı";
|
||
const { degree, color } = scoreToHeat(d.score ?? 0);
|
||
|
||
return (
|
||
<button
|
||
key={d.id}
|
||
type="button"
|
||
onClick={() => navigate(`/deal/${d.id}`)}
|
||
className="group text-left w-full hover:bg-background/20 transition-all"
|
||
>
|
||
<div className="flex items-center space-x-4">
|
||
{/* Left: Image */}
|
||
<div className="w-1/3">
|
||
<img
|
||
src={img}
|
||
alt={title}
|
||
className="w-full h-full object-cover rounded-lg"
|
||
loading="lazy"
|
||
/>
|
||
</div>
|
||
|
||
{/* Right: Details */}
|
||
<div className="w-2/3">
|
||
<div className="text-xs font-semibold text-text line-clamp-2 group-hover:text-primary transition">
|
||
{title}
|
||
</div>
|
||
|
||
<div className="mt-2 text-sm font-bold text-primary">{price}</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Heat Indicator */}
|
||
<div
|
||
className="absolute top-2 right-2 px-2 py-1 text-[12px] font-bold text-white rounded-full"
|
||
style={{ backgroundColor: color }}
|
||
>
|
||
{degree}°
|
||
</div>
|
||
</button>
|
||
);
|
||
})}
|
||
|
||
{/* No items or error */}
|
||
{!loading && items.length === 0 && !err && (
|
||
<div className="rounded-2xl shadow-md bg-background/40 p-4 text-center">
|
||
<div className="text-sm font-semibold text-text">Gösterilecek fırsat yok</div>
|
||
<div className="text-xs text-text-muted mt-1">Daha sonra tekrar dene.</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* View All Button */}
|
||
<div className="text-center mt-4">
|
||
<button
|
||
onClick={() => navigate(`/deals/${range}`)}
|
||
className="btn btn-primary w-full py-2 rounded-md text-white"
|
||
>
|
||
Hepsini Gör
|
||
</button>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|