Modulo makers3d desarrollado con codex V 0.0.1
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { browserFetch } from "../lib/api";
|
||||
|
||||
type FavoriteToggleProps = {
|
||||
targetId: string;
|
||||
targetType: "maker" | "work";
|
||||
targetSlug?: string;
|
||||
compact?: boolean;
|
||||
icon?: "heart" | "bookmark";
|
||||
};
|
||||
|
||||
const favoriteSyncEvent = "makers3d:favorite-updated";
|
||||
|
||||
function loginForCurrentPage() {
|
||||
const currentPath = `${window.location.pathname}${window.location.search}`;
|
||||
window.location.assign(`/login?returnTo=${encodeURIComponent(currentPath)}`);
|
||||
}
|
||||
|
||||
function BookmarkIcon({ active }: { active: boolean }) {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill={active ? "currentColor" : "none"} stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
||||
<path d="M7 4.8A1.8 1.8 0 0 1 8.8 3h6.4A1.8 1.8 0 0 1 17 4.8V21l-5-3-5 3V4.8Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default function FavoriteToggle({ targetId, targetType, targetSlug, compact = false, icon = "heart" }: FavoriteToggleProps) {
|
||||
const [favorited, setFavorited] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [status, setStatus] = useState("");
|
||||
const syncKey = `${targetType}:${targetSlug || targetId}`;
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
|
||||
async function loadFavorite() {
|
||||
try {
|
||||
const statusQuery = targetType === "maker" && targetSlug
|
||||
? `/favorites/status?targetType=maker&targetSlug=${encodeURIComponent(targetSlug)}`
|
||||
: `/favorites/status?targetType=${targetType}&targetId=${targetId}`;
|
||||
const response = await browserFetch<{ favorited: boolean }>(
|
||||
statusQuery
|
||||
);
|
||||
if (active) {
|
||||
setFavorited(response.favorited);
|
||||
}
|
||||
} catch {
|
||||
if (active) {
|
||||
setFavorited(false);
|
||||
}
|
||||
} finally {
|
||||
if (active) {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loadFavorite();
|
||||
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
}, [targetId, targetSlug, targetType]);
|
||||
|
||||
useEffect(() => {
|
||||
function syncFavorite(event: Event) {
|
||||
const detail = (event as CustomEvent<{ key: string; favorited: boolean }>).detail;
|
||||
if (detail?.key === syncKey) {
|
||||
setFavorited(detail.favorited);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener(favoriteSyncEvent, syncFavorite);
|
||||
return () => window.removeEventListener(favoriteSyncEvent, syncFavorite);
|
||||
}, [syncKey]);
|
||||
|
||||
async function toggleFavorite() {
|
||||
setStatus("");
|
||||
try {
|
||||
const identifier = targetSlug || targetId || "current";
|
||||
const endpoint = targetType === "maker"
|
||||
? `/favorites/makers/${encodeURIComponent(identifier)}`
|
||||
: `/favorites/works/${encodeURIComponent(identifier)}`;
|
||||
const response = await browserFetch<{ favorited: boolean }>(endpoint, {
|
||||
method: favorited ? "DELETE" : "POST"
|
||||
});
|
||||
setFavorited(response.favorited);
|
||||
window.dispatchEvent(new CustomEvent(favoriteSyncEvent, {
|
||||
detail: {
|
||||
key: syncKey,
|
||||
favorited: response.favorited
|
||||
}
|
||||
}));
|
||||
} catch (error) {
|
||||
const apiError = error as Error & { status?: number };
|
||||
const message = apiError.message || "";
|
||||
if (apiError.status === 401 || apiError.status === 403 || message.includes("Authentication")) {
|
||||
loginForCurrentPage();
|
||||
return;
|
||||
}
|
||||
setStatus(apiError.status ? `No se pudo guardar (${apiError.status}).` : "No se pudo actualizar favoritos.");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`favorite-toggle-wrap ${compact ? "is-compact" : ""} favorite-icon-${icon}`}>
|
||||
<button
|
||||
className={`favorite-toggle ${favorited ? "is-active" : ""}`}
|
||||
type="button"
|
||||
onClick={toggleFavorite}
|
||||
disabled={loading}
|
||||
aria-pressed={favorited}
|
||||
>
|
||||
<span className="favorite-heart" aria-hidden="true">
|
||||
{icon === "bookmark" ? <BookmarkIcon active={favorited} /> : (favorited ? "\u2764\uFE0F" : "\u2661")}
|
||||
</span>
|
||||
<span>{favorited ? "Guardado" : "Guardar"}</span>
|
||||
</button>
|
||||
{status ? <span className="mini-note">{status}</span> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user