mirror of
https://git.meshkee.com/novintrades/website.git
synced 2026-08-11 20:50:58 +04:30
80 lines
2.5 KiB
TypeScript
80 lines
2.5 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { Link } from "react-router-dom";
|
|
import { MapPin } from "lucide-react";
|
|
import { fetchLatestBrands } from "../lib/services";
|
|
import type { Brand } from "../lib/types";
|
|
import "./LatestBrands.css";
|
|
|
|
export default function LatestBrands() {
|
|
const [brands, setBrands] = useState<Brand[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
let active = true;
|
|
fetchLatestBrands(4)
|
|
.then((items) => {
|
|
if (active) setBrands(items);
|
|
})
|
|
.catch((err: unknown) => {
|
|
if (active) {
|
|
setError(err instanceof Error ? err.message : "Could not load brands");
|
|
}
|
|
})
|
|
.finally(() => {
|
|
if (active) setLoading(false);
|
|
});
|
|
return () => {
|
|
active = false;
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<section className="section brands" id="brands">
|
|
<div className="container">
|
|
<div className="section__head">
|
|
<span className="section__eyebrow">Partner directory</span>
|
|
<h2 className="section__title">Latest brands</h2>
|
|
<p className="section__lead">
|
|
Newly featured companies ready to connect across markets and industries.
|
|
</p>
|
|
</div>
|
|
|
|
{loading ? <p className="brands__status">Loading brands…</p> : null}
|
|
{error ? (
|
|
<p className="brands__status brands__status--error">{error}</p>
|
|
) : null}
|
|
{!loading && !error && brands.length === 0 ? (
|
|
<p className="brands__status">No published brands yet.</p>
|
|
) : null}
|
|
|
|
<div className="brands__grid">
|
|
{brands.map((brand) => (
|
|
<Link
|
|
key={brand.id}
|
|
className="brands__item"
|
|
to={`/brands/${brand.slug}`}
|
|
>
|
|
<div className="brands__media">
|
|
{brand.imageUrl ? (
|
|
<img src={brand.imageUrl} alt="" loading="lazy" />
|
|
) : (
|
|
<span className="brands__placeholder" aria-hidden="true" />
|
|
)}
|
|
</div>
|
|
<div className="brands__body">
|
|
<h3>{brand.title}</h3>
|
|
{brand.abstract ? <p>{brand.abstract}</p> : null}
|
|
<span className="brands__loc">
|
|
<MapPin size={14} strokeWidth={2} />
|
|
{[brand.city, brand.country].filter(Boolean).join(", ")}
|
|
</span>
|
|
</div>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|