Files
Makers3D/apps/web/app/makers/[slug]/page.tsx
T

286 lines
12 KiB
TypeScript

import Link from "next/link";
import FavoriteToggle from "../../components/FavoriteToggle";
import { serverFetch } from "../../lib/api";
function toText(value: unknown, fallback = "") {
return typeof value === "string" && value ? value : fallback;
}
function toNumber(value: unknown, fallback = 0) {
return typeof value === "number" ? value : Number(value) || fallback;
}
function asArray(value: unknown): string[] {
return Array.isArray(value) ? value.map((item) => String(item)) : [];
}
function availabilityLabel(value: string) {
if (value === "available") {
return "Aceptando trabajos";
}
if (value === "limited") {
return "Disponibilidad limitada";
}
if (value === "unavailable") {
return "No acepta trabajos";
}
return value || "Aceptando trabajos";
}
function workImage(work: Record<string, unknown> | undefined, fallback = "/demo/work-custom.svg") {
return work ? toText(work.image_url, fallback) : fallback;
}
export default async function MakerPage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const data = await serverFetch<{
maker: Record<string, unknown>;
services: Array<Record<string, unknown>>;
showcases: Array<Record<string, unknown>>;
works: Array<Record<string, unknown>>;
reviews: Array<Record<string, unknown>>;
}>(`/makers/${slug}`);
const { maker, services, showcases, works, reviews } = data;
const makerName = toText(maker.business_name, "Maker");
const makerImage = toText(maker.main_image_url, workImage(works[0], "/demo/maker-hero-1.svg"));
const rating = toNumber(maker.avg_rating, 4.9).toFixed(1);
const allowVerifiedReviews = maker.allow_verified_reviews !== false;
const showSatisfactionScore = maker.show_satisfaction_score !== false;
const showReviewTags = maker.show_review_tags !== false;
const showPastReviews = maker.show_past_reviews !== false;
const visibleReviews = showPastReviews ? reviews : [];
const reviewCount = showPastReviews ? Math.max(toNumber(maker.review_count, reviews.length), reviews.length) : 0;
const contactHref = `/consultas/nueva?makerId=${encodeURIComponent(String(maker.id))}&sourceType=profile&makerName=${encodeURIComponent(makerName)}&contextTitle=${encodeURIComponent(makerName)}&returnTo=${encodeURIComponent(`/makers/${slug}`)}`;
const tabs = [
"Destacado",
`Trabajos (${works.length})`,
`Servicios (${services.length})`,
`Escaparates (${showcases.length})`,
`Opiniones (${visibleReviews.length})`,
"Informacion"
];
const statCards = [
{ value: rating, label: "Reputacion", icon: "*" },
...(showSatisfactionScore ? [{ value: "96%", label: "Satisfaccion", icon: "ok" }] : []),
{ value: "~1 h", label: "Respuesta", icon: "rz" },
{ value: "Alta", label: "Confianza", icon: "cf" }
];
const publicBadges = [
...(reviewCount ? ["Trabajo verificado"] : []),
...(reviewCount >= 2 ? ["Cliente recurrente"] : []),
"Respuesta rapida",
"Confianza + Transparencia"
];
const socialLinks = [
["Web", toText(maker.website_url)],
["Instagram", toText(maker.instagram_url)],
["TikTok", toText(maker.tiktok_url)],
["X", toText(maker.twitter_url)],
["Facebook", toText(maker.facebook_url)],
["YouTube", toText(maker.youtube_url)],
["LinkedIn", toText(maker.linkedin_url)]
].filter(([, href]) => href);
return (
<main className="page-shell maker-public-page">
<section className="maker-public-phone">
<section className="maker-public-hero">
<img src={makerImage} alt={makerName} />
<div className="maker-public-top-actions">
<Link href="/discover" className="maker-public-circle-action" aria-label="Volver a descubrir">&lt;</Link>
<div className="maker-public-action-pair">
<FavoriteToggle targetType="maker" targetId={String(maker.id)} targetSlug={slug} compact />
<button className="maker-public-circle-action" type="button" aria-label="Compartir">sh</button>
</div>
</div>
<span className="maker-public-gallery-count">1/6</span>
</section>
<section className="maker-public-main-card">
<div className="maker-public-identity">
<div className="maker-public-logo">
<span>{makerName.slice(0, 2).toUpperCase()}</span>
</div>
<div className="maker-public-title-block">
<div className="maker-public-name-row">
<h1>{makerName}</h1>
<span className="maker-public-online-dot" />
<span className="maker-public-availability">{availabilityLabel(toText(maker.availability, "available"))}</span>
</div>
<div className="maker-public-rating">
<span className="map-home-star">*</span>
<strong>{rating}</strong>
<span>({reviewCount} resenas verificadas)</span>
</div>
<p>{toText(maker.city, "Cordoba")}, {toText(maker.province, "Argentina")} | 1,2 km</p>
</div>
</div>
<div className="maker-public-stats">
{statCards.map((stat) => (
<article key={stat.label}>
<strong>{stat.value}</strong>
<span>{stat.label}</span>
<small>{stat.icon}</small>
</article>
))}
</div>
{showReviewTags ? (
<div className="maker-public-trust-badges">
{publicBadges.map((badge) => <span key={badge}>{badge}</span>)}
</div>
) : null}
<div className="maker-public-tags">
{services.slice(0, 5).map((service) => (
<span key={String(service.id)}>{toText(service.category, "Servicio")}</span>
))}
{services.length === 0 ? <span>Impresion 3D</span> : null}
</div>
<div className="maker-public-actions-row">
<a href={contactHref} className="button button-primary">Escribir al maker</a>
<a href={`https://wa.me/${toText(maker.public_whatsapp, "")}`} className="maker-public-whatsapp" aria-label="WhatsApp">wa</a>
</div>
<section className="maker-public-recommendation">
<span className="maker-public-section-icon">i</span>
<div>
<strong>Por que te lo recomendamos?</strong>
<ul>
<li>A 1,2 km de tu ubicacion</li>
<li>Especialista en trabajos funcionales</li>
<li>{works.length} trabajos verificados</li>
<li>Excelente calidad/precio</li>
</ul>
</div>
</section>
</section>
<nav className="maker-public-tabs" aria-label="Secciones del perfil">
{tabs.map((tab, index) => (
<a key={tab} href={index === 0 ? "#destacado" : index === 1 ? "#trabajos" : index === 2 ? "#servicios" : index === 3 ? "#escaparates" : index === 4 ? "#opiniones" : "#informacion"}>
{tab}
</a>
))}
</nav>
<section id="destacado" className="maker-public-section">
<span className="eyebrow">Destacado</span>
<h2>Especialidades</h2>
<div className="maker-public-chip-grid">
{["Repuestos", "Ingenieria", "Automotor", "Diseno CAD", "Prototipos"].map((item) => (
<span key={item}>{item}</span>
))}
</div>
</section>
<section className="maker-public-section">
<div className="maker-public-section-head">
<h2>Escaparates</h2>
<Link href="#escaparates">Ver todos</Link>
</div>
<div id="escaparates" className="maker-public-showcases">
{showcases.slice(0, 4).map((showcase) => (
<Link key={String(showcase.id)} href={`/showcases/${toText(showcase.slug)}`} style={{ backgroundImage: `url(${toText(showcase.cover_image_url, workImage(works[0], "/demo/feed-print-technical.svg"))})` }}>
<strong>{toText(showcase.title, "Escaparate")}</strong>
<span>{Array.isArray(showcase.selected_work_ids) ? showcase.selected_work_ids.length : 0} trabajos</span>
</Link>
))}
{showcases.length === 0 ? (
<Link href={works[0] ? `/works/${toText(works[0].slug)}` : "#"} style={{ backgroundImage: `url(${workImage(works[0], "/demo/feed-print-technical.svg")})` }}>
<strong>Trabajos destacados</strong>
<span>{Math.max(works.length, 1)} trabajos</span>
</Link>
) : null}
</div>
</section>
<section id="trabajos" className="maker-public-section">
<div className="maker-public-section-head">
<h2>Trabajos destacados</h2>
<Link href="/discover">Ver todos</Link>
</div>
<div className="maker-public-work-rail">
{works.map((work) => (
<Link key={String(work.id)} href={`/works/${toText(work.slug)}`} className="maker-public-work-card">
<img src={workImage(work)} alt={toText(work.title)} />
<strong>{toText(work.title)}</strong>
<span>{toText(work.technology, "FDM")} | {toText(work.material, "PLA")}</span>
<small><span className="map-home-star">*</span> {rating}</small>
</Link>
))}
</div>
</section>
<section id="servicios" className="maker-public-section">
<div className="maker-public-section-head">
<h2>Servicios y precios</h2>
<Link href="#servicios">Ver todos</Link>
</div>
<div className="maker-public-service-list">
{services.map((service) => (
<Link key={String(service.id)} href={`/services/${toText(service.slug)}`}>
<img src={workImage(works[0], "/demo/feed-print-gear.svg")} alt={toText(service.title)} />
<div>
<strong>{toText(service.title)}</strong>
<span>{asArray(service.materials).slice(0, 3).join(" | ") || toText(service.category)}</span>
<small>Desde ${Math.round(toNumber(service.price_from_cents, 0) / 100).toLocaleString("es-AR")}</small>
</div>
<b>&gt;</b>
</Link>
))}
</div>
</section>
<section id="opiniones" className="maker-public-section">
<div className="maker-public-section-head">
<h2>Opinion destacada</h2>
<Link href="#opiniones">Ver todas</Link>
</div>
{!allowVerifiedReviews ? (
<div className="empty-state">Este maker pauso temporalmente las nuevas reseñas verificadas.</div>
) : null}
{!showPastReviews ? (
<div className="empty-state">Este maker mantiene privadas sus resenas anteriores.</div>
) : null}
{showPastReviews ? visibleReviews.slice(0, 2).map((review) => (
<article key={String(review.id)} className="maker-public-review">
<div>
<strong>Trabajo verificado</strong>
<span>{toNumber(review.rating_overall, 5).toFixed(1)} / 5</span>
</div>
<p>{toText(review.comment, "Excelente comunicacion y calidad. Cumplio con el plazo y el resultado fue perfecto.")}</p>
</article>
)) : null}
{showPastReviews && visibleReviews.length === 0 ? <div className="empty-state">Todavia no hay opiniones publicadas.</div> : null}
</section>
<section id="informacion" className="maker-public-section maker-public-info-bottom">
<h2>Informacion del maker</h2>
<p>{toText(maker.description, "Especialista en piezas funcionales, prototipos y trabajos con contexto real.")}</p>
{socialLinks.length ? (
<div className="maker-public-social-links">
{socialLinks.map(([label, href]) => {
const safeHref = href.startsWith("http") ? href : `https://${href}`;
return (
<a key={label} href={safeHref} target="_blank" rel="noreferrer">
{label}
</a>
);
})}
</div>
) : null}
</section>
</section>
<div className="maker-public-bottom-cta">
<a href={contactHref} className="button button-primary">Escribir al maker</a>
</div>
</main>
);
}