114 lines
3.3 KiB
TypeScript
114 lines
3.3 KiB
TypeScript
// src/pages/Home.tsx
|
||
import { useEffect, useState, useRef } from "react"
|
||
import MainLayout from "../layouts/MainLayout"
|
||
import DealCardMain from "../components/Shared/DealCardMain"
|
||
import { getDeals } from "../api/deal/getDeal"
|
||
import { mapDealCardResponseToDeal } from "../adapters/responses/dealCardAdapter"
|
||
import { timeAgo } from "../utils/timeAgo"
|
||
import type { DealCard } from "../models/deal/DealCard"
|
||
|
||
type HomeProps = {
|
||
onRequireLogin: () => void
|
||
}
|
||
|
||
export default function HomePage({ onRequireLogin }: HomeProps) {
|
||
const [deals, setDeals] = useState<DealCard[]>([])
|
||
const [page, setPage] = useState(1)
|
||
const [hasMore, setHasMore] = useState(true)
|
||
const [loading, setLoading] = useState(false)
|
||
const [error, setError] = useState<string | null>(null)
|
||
const observerRef = useRef<HTMLDivElement | null>(null)
|
||
|
||
useEffect(() => {
|
||
const loadDeals = async () => {
|
||
if (loading || !hasMore) return
|
||
setLoading(true)
|
||
|
||
try {
|
||
const apiDeals = await getDeals(page)
|
||
if (apiDeals.length === 0) {
|
||
setHasMore(false)
|
||
} else {
|
||
const mappedDeals = apiDeals.map(mapDealCardResponseToDeal)
|
||
|
||
setDeals((prev) => {
|
||
const existingIds = new Set(prev.map((d) => d.id))
|
||
const filtered = mappedDeals.filter(
|
||
(d) => !existingIds.has(d.id)
|
||
)
|
||
return [...prev, ...filtered]
|
||
})
|
||
}
|
||
} catch (err: any) {
|
||
setError(err.message ?? "Bir hata oluştu")
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
loadDeals()
|
||
}, [page])
|
||
|
||
useEffect(() => {
|
||
const observer = new IntersectionObserver(
|
||
(entries) => {
|
||
if (entries[0].isIntersecting && hasMore && !loading) {
|
||
setPage((prev) => prev + 1)
|
||
}
|
||
},
|
||
{ threshold: 1 }
|
||
)
|
||
|
||
if (observerRef.current) observer.observe(observerRef.current)
|
||
return () => observer.disconnect()
|
||
}, [hasMore, loading])
|
||
|
||
if (error) {
|
||
return <p className="p-4 text-red-600">{error}</p>
|
||
}
|
||
|
||
return (
|
||
<MainLayout>
|
||
<div className="max-w-[1400px] mx-auto px-4 py-8 grid grid-cols-1 lg:grid-cols-4 gap-6">
|
||
{/* SOL: Deal listesi */}
|
||
<div className="lg:col-span-3 space-y-4">
|
||
{deals.map((deal) => (
|
||
<DealCardMain
|
||
key={deal.id}
|
||
id={deal.id}
|
||
image={deal.imageUrl || "/placeholder.png"}
|
||
title={deal.title}
|
||
price={deal.price ? `${deal.price}₺` : ""}
|
||
store={deal.seller.name}
|
||
postedBy={deal.user?.username ?? "unknown"}
|
||
score={deal.score}
|
||
comments={deal.commentsCount}
|
||
postedAgo={timeAgo(deal.createdAt)}
|
||
onRequireLogin={onRequireLogin}
|
||
/>
|
||
))}
|
||
|
||
{loading && (
|
||
<p className="text-center py-4">Yükleniyor...</p>
|
||
)}
|
||
|
||
{!hasMore && (
|
||
<p className="text-center py-4 text-muted-foreground">
|
||
Tüm fırsatlar yüklendi.
|
||
</p>
|
||
)}
|
||
|
||
<div ref={observerRef} className="h-8" />
|
||
</div>
|
||
|
||
{/* SAĞ: sidebar */}
|
||
<aside className="hidden lg:block bg-surface/50 rounded-lg p-4">
|
||
<p className="text-sm text-muted-foreground">
|
||
Yan içerik / filtre / reklam alanı
|
||
</p>
|
||
</aside>
|
||
</div>
|
||
</MainLayout>
|
||
)
|
||
}
|