Modulo makers3d desarrollado con codex V 0.0.1
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user