175 lines
5.7 KiB
TypeScript
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>
|
|
);
|
|
}
|