Files

175 lines
5.7 KiB
TypeScript

"use client";
import Link from "next/link";
import { useEffect, useState } from "react";
import { browserFetch } from "../lib/api";
export type MapHomeMakerListItem = {
id: string;
slug: string;
name: string;
city: string;
province: string;
imageUrl: string;
distance: string;
rating: string;
reviewCount: number;
satisfaction: string;
tags: string[];
};
type FavoritesResponse = {
makers: Array<{ id: string; slug: string }>;
};
function loginForCurrentPage() {
const currentPath = `${window.location.pathname}${window.location.search}`;
window.location.assign(`/login?returnTo=${encodeURIComponent(currentPath)}`);
}
export function MapHomeMakerList({ makers }: { makers: MapHomeMakerListItem[] }) {
const [favoriteSlugs, setFavoriteSlugs] = useState<Set<string>>(new Set());
const [favoriteIds, setFavoriteIds] = useState<Set<string>>(new Set());
const [loadingSlug, setLoadingSlug] = useState("");
const [needsLogin, setNeedsLogin] = useState(false);
useEffect(() => {
let active = true;
async function loadFavorites() {
try {
const response = await browserFetch<FavoritesResponse>("/favorites");
if (!active) {
return;
}
setFavoriteSlugs(new Set(response.makers.map((maker) => maker.slug)));
setFavoriteIds(new Set(response.makers.map((maker) => maker.id)));
setNeedsLogin(false);
} catch (error) {
const apiError = error as Error & { status?: number };
if (active && (apiError.status === 401 || apiError.status === 403 || apiError.message.includes("Authentication"))) {
setNeedsLogin(true);
}
}
}
void loadFavorites();
return () => {
active = false;
};
}, []);
async function toggleFavorite(maker: MapHomeMakerListItem) {
if (needsLogin) {
loginForCurrentPage();
return;
}
const isFavorite = favoriteSlugs.has(maker.slug) || favoriteIds.has(maker.id);
setLoadingSlug(maker.slug);
try {
await browserFetch<{ favorited: boolean }>(`/favorites/makers/${encodeURIComponent(maker.slug)}`, {
method: isFavorite ? "DELETE" : "POST"
});
setFavoriteSlugs((current) => {
const next = new Set(current);
if (isFavorite) {
next.delete(maker.slug);
} else {
next.add(maker.slug);
}
return next;
});
setFavoriteIds((current) => {
const next = new Set(current);
if (isFavorite) {
next.delete(maker.id);
} else {
next.add(maker.id);
}
return next;
});
} catch (error) {
const apiError = error as Error & { status?: number };
if (apiError.status === 401 || apiError.status === 403 || apiError.message.includes("Authentication")) {
loginForCurrentPage();
}
} finally {
setLoadingSlug("");
}
}
if (makers.length === 0) {
return (
<div className="empty-state">
No encontramos makers con esa busqueda. Prueba otra categoria o abre la vista completa de descubrir.
</div>
);
}
return (
<div className="map-home-maker-list">
{makers.map((maker) => {
const isFavorite = favoriteSlugs.has(maker.slug) || favoriteIds.has(maker.id);
return (
<article key={maker.id} className="map-home-maker-card">
<Link
href={`/makers/${maker.slug}`}
className="map-home-maker-card-main map-home-maker-card-link"
aria-label={`Abrir perfil de ${maker.name}`}
>
<div className="map-home-maker-media">
<img className="thumb" src={maker.imageUrl} alt={maker.name} />
<span className="map-home-distance-pill">{maker.distance}</span>
</div>
<div className="map-home-maker-body">
<div className="map-home-maker-topline">
<div className="map-home-maker-brand">
<span className="map-home-maker-badge">{maker.name.slice(0, 2).toUpperCase()}</span>
<div className="map-home-maker-copy">
<div className="map-home-maker-heading">
<strong>{maker.name}</strong>
<span className="map-home-online-dot" />
</div>
<span className="map-home-maker-subline">{maker.city}, {maker.province}</span>
</div>
</div>
</div>
<div className="map-home-maker-score">
<span className="map-home-star">*</span>
<span>{maker.rating} ({maker.reviewCount})</span>
<span>|</span>
<span>{maker.satisfaction}</span>
</div>
<div className="map-home-tag-row">
{maker.tags.map((tag) => (
<span key={`${maker.id}-${tag}`} className="map-home-tag">{tag}</span>
))}
</div>
</div>
</Link>
<button
className={`map-home-bookmark ${isFavorite ? "is-active" : ""}`}
type="button"
aria-label={isFavorite ? `Quitar ${maker.name} de favoritos` : `Guardar ${maker.name} en favoritos`}
aria-pressed={isFavorite}
disabled={loadingSlug === maker.slug}
onClick={() => void toggleFavorite(maker)}
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
<path d="M7 4.5h10a1 1 0 0 1 1 1V20l-6-3.2L6 20V5.5a1 1 0 0 1 1-1Z" />
</svg>
</button>
</article>
);
})}
</div>
);
}