Modulo makers3d desarrollado con codex V 0.0.1
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { browserFetch } from "../lib/api";
|
||||
|
||||
type FavoriteMaker = {
|
||||
id: string;
|
||||
slug: string;
|
||||
business_name: string;
|
||||
city: string | null;
|
||||
province: string | null;
|
||||
main_image_url: string | null;
|
||||
availability: string | null;
|
||||
avg_rating: number | string;
|
||||
review_count: number;
|
||||
};
|
||||
|
||||
type FavoriteWork = {
|
||||
id: string;
|
||||
slug: string;
|
||||
title: string;
|
||||
summary: string;
|
||||
technology: string;
|
||||
material: string;
|
||||
image_url: string;
|
||||
maker_slug: string;
|
||||
maker_name: string;
|
||||
};
|
||||
|
||||
type FavoritesResponse = {
|
||||
makers: FavoriteMaker[];
|
||||
works: FavoriteWork[];
|
||||
};
|
||||
|
||||
function rating(value: number | string) {
|
||||
const parsed = typeof value === "number" ? value : Number(value);
|
||||
return Number.isFinite(parsed) ? parsed.toFixed(1) : "0.0";
|
||||
}
|
||||
|
||||
export default function FavoritesClient() {
|
||||
const [activeTab, setActiveTab] = useState<"makers" | "works">("makers");
|
||||
const [favorites, setFavorites] = useState<FavoritesResponse>({ makers: [], works: [] });
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [needsLogin, setNeedsLogin] = useState(false);
|
||||
const [status, setStatus] = useState("");
|
||||
|
||||
async function loadFavorites() {
|
||||
setStatus("");
|
||||
try {
|
||||
const response = await browserFetch<FavoritesResponse>("/favorites");
|
||||
setFavorites(response);
|
||||
setNeedsLogin(false);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "";
|
||||
if (message.includes("Authentication")) {
|
||||
setNeedsLogin(true);
|
||||
return;
|
||||
}
|
||||
setStatus("No se pudieron cargar tus favoritos.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
void loadFavorites();
|
||||
}, []);
|
||||
|
||||
async function removeFavorite(type: "maker" | "work", id: string) {
|
||||
const endpoint = type === "maker" ? `/favorites/makers/${id}` : `/favorites/works/${id}`;
|
||||
await browserFetch(endpoint, { method: "DELETE" });
|
||||
await loadFavorites();
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<section className="favorites-shell">
|
||||
<div className="profile-account-card stack">
|
||||
<span className="eyebrow">Favoritos</span>
|
||||
<h1 className="section-title">Cargando guardados</h1>
|
||||
<p className="muted">Estamos preparando tus makers y trabajos favoritos.</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
if (needsLogin) {
|
||||
return (
|
||||
<section className="favorites-shell">
|
||||
<div className="profile-account-card stack">
|
||||
<span className="eyebrow">Favoritos</span>
|
||||
<h1 className="section-title">Inicia sesion para guardar favoritos</h1>
|
||||
<p className="muted">Tus makers y trabajos guardados quedan asociados a tu cuenta para recuperarlos desde cualquier pestana.</p>
|
||||
<Link href="/login?returnTo=%2Ffavorites" className="button button-primary">Entrar</Link>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
const visibleMakers = activeTab === "makers";
|
||||
const empty = visibleMakers ? favorites.makers.length === 0 : favorites.works.length === 0;
|
||||
|
||||
return (
|
||||
<section className="favorites-shell">
|
||||
<div className="favorites-header">
|
||||
<div>
|
||||
<span className="eyebrow">Favoritos</span>
|
||||
<h1 className="section-title">Guardados para volver rapido</h1>
|
||||
<p className="muted">Separados por makers y trabajos para que puedas comparar proveedores o ejemplos concretos.</p>
|
||||
</div>
|
||||
<div className="favorites-counts">
|
||||
<span>{favorites.makers.length} makers</span>
|
||||
<span>{favorites.works.length} trabajos</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="favorites-tabs" role="tablist" aria-label="Secciones de favoritos">
|
||||
<button className={activeTab === "makers" ? "is-active" : ""} type="button" onClick={() => setActiveTab("makers")}>
|
||||
Makers
|
||||
</button>
|
||||
<button className={activeTab === "works" ? "is-active" : ""} type="button" onClick={() => setActiveTab("works")}>
|
||||
Trabajos
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{status ? <p className="messages-status">{status}</p> : null}
|
||||
|
||||
{empty ? (
|
||||
<div className="profile-account-card stack">
|
||||
<h2 className="section-title">{visibleMakers ? "Todavia no guardaste makers" : "Todavia no guardaste trabajos"}</h2>
|
||||
<p className="muted">Usa el boton Guardar desde un perfil de maker o desde el detalle de un trabajo.</p>
|
||||
<Link href="/discover" className="button button-primary">Descubrir makers</Link>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{visibleMakers ? (
|
||||
<div className="favorites-grid">
|
||||
{favorites.makers.map((maker) => (
|
||||
<article key={maker.id} className="favorite-card">
|
||||
<img className="thumb" src={maker.main_image_url || "/demo/maker-hero-1.svg"} alt={maker.business_name} />
|
||||
<div className="favorite-card-body">
|
||||
<div>
|
||||
<strong>{maker.business_name}</strong>
|
||||
<span className="mini-note">{maker.city || "Cordoba"}, {maker.province || "Argentina"}</span>
|
||||
</div>
|
||||
<div className="badges">
|
||||
<span className="badge">Rating {rating(maker.avg_rating)}</span>
|
||||
<span className="badge">{maker.review_count} resenas</span>
|
||||
<span className="badge">{maker.availability || "available"}</span>
|
||||
</div>
|
||||
<div className="favorite-card-actions">
|
||||
<Link href={`/makers/${maker.slug}`} className="button button-primary">Ver perfil</Link>
|
||||
<button className="button button-secondary" type="button" onClick={() => removeFavorite("maker", maker.id)}>Quitar</button>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="favorites-grid">
|
||||
{favorites.works.map((work) => (
|
||||
<article key={work.id} className="favorite-card">
|
||||
<img className="thumb" src={work.image_url || "/demo/work-custom.svg"} alt={work.title} />
|
||||
<div className="favorite-card-body">
|
||||
<div>
|
||||
<strong>{work.title}</strong>
|
||||
<span className="mini-note">{work.maker_name}</span>
|
||||
</div>
|
||||
<p className="muted">{work.summary}</p>
|
||||
<div className="badges">
|
||||
<span className="badge">{work.technology}</span>
|
||||
<span className="badge">{work.material}</span>
|
||||
</div>
|
||||
<div className="favorite-card-actions">
|
||||
<Link href={`/works/${work.slug}`} className="button button-primary">Ver trabajo</Link>
|
||||
<button className="button button-secondary" type="button" onClick={() => removeFavorite("work", work.id)}>Quitar</button>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user