agregar idiomas a parts
Deploy to VPS / deploy (push) Has been cancelled

This commit is contained in:
2026-04-08 12:47:26 -05:00
parent 20976432c9
commit 86ef0e2d75
8 changed files with 270 additions and 76 deletions
@@ -5,6 +5,7 @@ import { motion, AnimatePresence } from "framer-motion";
import { X, KeyRound, Building2, User, Mail, LogOut, ShieldCheck, Sparkles, Loader2, Lock } from "lucide-react";
import { loginClient, registerClientRequest, logoutClient, updateClientPassword } from "@/app/actions/clientAuth";
import { useRouter } from "next/navigation";
import { useTranslations } from "next-intl";
export default function AuthModal({ session }: { session: any }) {
const [isOpen, setIsOpen] = useState(false);
@@ -13,6 +14,7 @@ export default function AuthModal({ session }: { session: any }) {
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<string | null>(null);
const router = useRouter();
const t = useTranslations("AuthModal");
useEffect(() => {
const handleOpen = (e: Event) => {
@@ -57,7 +59,7 @@ export default function AuthModal({ session }: { session: any }) {
if (res.error) {
setError(res.error);
} else {
setSuccess("Access requested successfully. We will notify you via email upon engineering approval.");
setSuccess(t("successMessage"));
form.reset();
}
setIsLoading(false);
@@ -75,7 +77,7 @@ export default function AuthModal({ session }: { session: any }) {
if (res.error) {
setError(res.error);
} else {
setSuccess("Password updated securely.");
setSuccess(t("passwordUpdated"));
form.reset();
}
setIsLoading(false);
@@ -104,7 +106,6 @@ export default function AuthModal({ session }: { session: any }) {
exit={{ opacity: 0, scale: 0.95 }}
className="relative w-full max-w-md bg-white dark:bg-[#111] rounded-[2rem] shadow-2xl overflow-hidden border border-black/5 dark:border-white/10"
>
{/* 🔥 FIX: Z-INDEX 100 PARA EVITAR BLOQUEOS */}
<button
onClick={() => setIsOpen(false)}
className="absolute top-4 right-4 p-2 bg-black/5 dark:bg-white/10 hover:bg-black/10 dark:hover:bg-white/20 rounded-full transition-colors z-[100]"
@@ -120,13 +121,13 @@ export default function AuthModal({ session }: { session: any }) {
</div>
<h2 className="text-2xl font-light text-[#1D1D1F] dark:text-white relative z-10 flex items-center justify-center gap-2">
<Lock size={20} className="text-[#86868B]" /> B2B Portal
<Lock size={20} className="text-[#86868B]" /> {t("b2bPortal")}
</h2>
{!session && (
<div className="flex bg-black/5 dark:bg-white/5 rounded-xl p-1 mt-6 relative z-10">
<button onClick={() => {setMode("LOGIN"); setError(null); setSuccess(null);}} className={`flex-1 py-2 text-xs font-semibold rounded-lg transition-all ${mode === "LOGIN" ? "bg-white dark:bg-[#1D1D1F] shadow text-[#1D1D1F] dark:text-white" : "text-[#86868B]"}`}>Sign In</button>
<button onClick={() => {setMode("REGISTER"); setError(null); setSuccess(null);}} className={`flex-1 py-2 text-xs font-semibold rounded-lg transition-all ${mode === "REGISTER" ? "bg-white dark:bg-[#1D1D1F] shadow text-[#1D1D1F] dark:text-white" : "text-[#86868B]"}`}>Request Access</button>
<button onClick={() => {setMode("LOGIN"); setError(null); setSuccess(null);}} className={`flex-1 py-2 text-xs font-semibold rounded-lg transition-all ${mode === "LOGIN" ? "bg-white dark:bg-[#1D1D1F] shadow text-[#1D1D1F] dark:text-white" : "text-[#86868B]"}`}>{t("signIn")}</button>
<button onClick={() => {setMode("REGISTER"); setError(null); setSuccess(null);}} className={`flex-1 py-2 text-xs font-semibold rounded-lg transition-all ${mode === "REGISTER" ? "bg-white dark:bg-[#1D1D1F] shadow text-[#1D1D1F] dark:text-white" : "text-[#86868B]"}`}>{t("requestAccess")}</button>
</div>
)}
</div>
@@ -138,15 +139,15 @@ export default function AuthModal({ session }: { session: any }) {
{mode === "LOGIN" && !session && (
<form onSubmit={handleLogin} className="space-y-4">
<div>
<label className="text-[10px] font-bold uppercase tracking-widest text-[#86868B] mb-1 flex items-center gap-1.5"><Mail size={12}/> Corporate Email</label>
<label className="text-[10px] font-bold uppercase tracking-widest text-[#86868B] mb-1 flex items-center gap-1.5"><Mail size={12}/> {t("corporateEmail")}</label>
<input name="email" type="email" required className="w-full bg-black/5 dark:bg-white/5 border border-transparent focus:border-[#0066CC] rounded-xl px-4 py-3 text-sm outline-none text-[#1D1D1F] dark:text-white transition-colors" />
</div>
<div>
<label className="text-[10px] font-bold uppercase tracking-widest text-[#86868B] mb-1 flex items-center gap-1.5"><KeyRound size={12}/> Password</label>
<label className="text-[10px] font-bold uppercase tracking-widest text-[#86868B] mb-1 flex items-center gap-1.5"><KeyRound size={12}/> {t("password")}</label>
<input name="password" type="password" required className="w-full bg-black/5 dark:bg-white/5 border border-transparent focus:border-[#0066CC] rounded-xl px-4 py-3 text-sm outline-none text-[#1D1D1F] dark:text-white transition-colors" />
</div>
<button disabled={isLoading} className="w-full mt-2 bg-[#1D1D1F] dark:bg-white text-white dark:text-black py-3.5 rounded-xl text-sm font-semibold flex justify-center items-center gap-2 active:scale-[0.98] transition-transform">
{isLoading ? <Loader2 size={16} className="animate-spin" /> : "Access Secure Portal"}
{isLoading ? <Loader2 size={16} className="animate-spin" /> : t("accessPortal")}
</button>
</form>
)}
@@ -154,23 +155,23 @@ export default function AuthModal({ session }: { session: any }) {
{mode === "REGISTER" && !session && (
<form onSubmit={handleRegister} className="space-y-3">
<div>
<label className="text-[10px] font-bold uppercase tracking-widest text-[#86868B] mb-1 flex items-center gap-1.5"><User size={12}/> Full Name</label>
<label className="text-[10px] font-bold uppercase tracking-widest text-[#86868B] mb-1 flex items-center gap-1.5"><User size={12}/> {t("fullName")}</label>
<input name="fullName" required className="w-full bg-black/5 dark:bg-white/5 border border-transparent focus:border-[#0066CC] rounded-xl px-4 py-2.5 text-sm outline-none text-[#1D1D1F] dark:text-white transition-colors" />
</div>
<div>
<label className="text-[10px] font-bold uppercase tracking-widest text-[#86868B] mb-1 flex items-center gap-1.5"><Building2 size={12}/> Company Name</label>
<label className="text-[10px] font-bold uppercase tracking-widest text-[#86868B] mb-1 flex items-center gap-1.5"><Building2 size={12}/> {t("companyName")}</label>
<input name="companyName" required className="w-full bg-black/5 dark:bg-white/5 border border-transparent focus:border-[#0066CC] rounded-xl px-4 py-2.5 text-sm outline-none text-[#1D1D1F] dark:text-white transition-colors" />
</div>
<div>
<label className="text-[10px] font-bold uppercase tracking-widest text-[#86868B] mb-1 flex items-center gap-1.5"><Mail size={12}/> Work Email</label>
<label className="text-[10px] font-bold uppercase tracking-widest text-[#86868B] mb-1 flex items-center gap-1.5"><Mail size={12}/> {t("workEmail")}</label>
<input name="email" type="email" required className="w-full bg-black/5 dark:bg-white/5 border border-transparent focus:border-[#0066CC] rounded-xl px-4 py-2.5 text-sm outline-none text-[#1D1D1F] dark:text-white transition-colors" />
</div>
<div>
<label className="text-[10px] font-bold uppercase tracking-widest text-[#86868B] mb-1 flex items-center gap-1.5"><KeyRound size={12}/> Create Password</label>
<label className="text-[10px] font-bold uppercase tracking-widest text-[#86868B] mb-1 flex items-center gap-1.5"><KeyRound size={12}/> {t("createPassword")}</label>
<input name="password" type="password" required className="w-full bg-black/5 dark:bg-white/5 border border-transparent focus:border-[#0066CC] rounded-xl px-4 py-2.5 text-sm outline-none text-[#1D1D1F] dark:text-white transition-colors" />
</div>
<button disabled={isLoading} className="w-full mt-4 bg-[#1D1D1F] dark:bg-white text-white dark:text-black py-3.5 rounded-xl text-sm font-semibold flex justify-center items-center gap-2 active:scale-[0.98] transition-transform">
{isLoading ? <Loader2 size={16} className="animate-spin" /> : "Submit Request"}
{isLoading ? <Loader2 size={16} className="animate-spin" /> : t("submitRequest")}
</button>
</form>
)}
@@ -188,15 +189,15 @@ export default function AuthModal({ session }: { session: any }) {
</div>
</div>
<form onSubmit={handleChangePassword} className="space-y-3 pt-4 border-t border-black/5 dark:border-white/10">
<h3 className="text-xs font-semibold text-[#1D1D1F] dark:text-white mb-2">Update Security Credentials</h3>
<input name="currentPassword" type="password" placeholder="Current Password" required className="w-full bg-black/5 dark:bg-white/5 border border-transparent focus:border-[#0066CC] rounded-xl px-4 py-2.5 text-sm outline-none text-[#1D1D1F] dark:text-white transition-colors" />
<input name="newPassword" type="password" placeholder="New Password" required minLength={8} className="w-full bg-black/5 dark:bg-white/5 border border-transparent focus:border-[#0066CC] rounded-xl px-4 py-2.5 text-sm outline-none text-[#1D1D1F] dark:text-white transition-colors" />
<h3 className="text-xs font-semibold text-[#1D1D1F] dark:text-white mb-2">{t("updateCredentials")}</h3>
<input name="currentPassword" type="password" placeholder={t("currentPassword")} required className="w-full bg-black/5 dark:bg-white/5 border border-transparent focus:border-[#0066CC] rounded-xl px-4 py-2.5 text-sm outline-none text-[#1D1D1F] dark:text-white transition-colors" />
<input name="newPassword" type="password" placeholder={t("newPassword")} required minLength={8} className="w-full bg-black/5 dark:bg-white/5 border border-transparent focus:border-[#0066CC] rounded-xl px-4 py-2.5 text-sm outline-none text-[#1D1D1F] dark:text-white transition-colors" />
<button disabled={isLoading} className="w-full bg-black/5 dark:bg-white/10 hover:bg-black/10 text-[#1D1D1F] dark:text-white py-2.5 rounded-xl text-sm font-medium transition-colors">
{isLoading ? <Loader2 size={16} className="animate-spin mx-auto" /> : "Change Password"}
{isLoading ? <Loader2 size={16} className="animate-spin mx-auto" /> : t("changePassword")}
</button>
</form>
<button onClick={handleLogout} className="w-full flex items-center justify-center gap-2 text-rose-500 hover:bg-rose-500/10 py-3.5 rounded-xl text-sm font-semibold transition-colors">
<LogOut size={16}/> Secure Logout
<LogOut size={16}/> {t("secureLogout")}
</button>
</div>
)}
@@ -16,7 +16,7 @@ interface ComponentGridProps {
currentPage: number;
totalPages: number;
totalItems: number;
session: any | null; // 🔥 LA SESIÓN DETERMINA TODO
session: any | null;
}
export default function ComponentGrid({ initialParts, locale, query, currentPage, totalPages, totalItems, session }: ComponentGridProps) {
@@ -33,7 +33,7 @@ export default function ComponentGrid({ initialParts, locale, query, currentPage
useEffect(() => {
const timeoutId = setTimeout(() => {
if (searchTerm !== query && session) { // Solo busca si hay sesión
if (searchTerm !== query && session) {
const params = new URLSearchParams(searchParams.toString());
if (searchTerm) { params.set('q', searchTerm); params.set('page', '1'); }
else { params.delete('q'); }
@@ -57,7 +57,6 @@ export default function ComponentGrid({ initialParts, locale, query, currentPage
router.push(`${pathname}?${params.toString()}`);
};
// 🔥 EVENTO PARA ABRIR MODAL
const openAuth = (mode: "LOGIN" | "REGISTER") => {
window.dispatchEvent(new CustomEvent('flux:open-auth', { detail: { mode } }));
};
@@ -66,48 +65,48 @@ export default function ComponentGrid({ initialParts, locale, query, currentPage
<>
<AuthModal session={session} />
{/* 🔥 BOTÓN FLOTANTE DE PERFIL / LOGIN (Arriba a la derecha) */}
{/* Floating profile / login button */}
<div className="absolute top-32 right-6 md:right-12 z-50">
<button
onClick={() => window.dispatchEvent(new CustomEvent('flux:open-auth'))}
className="flex items-center gap-2 px-4 py-2 bg-white dark:bg-[#111] border border-black/10 dark:border-white/10 rounded-full text-sm font-medium text-[#1D1D1F] dark:text-white shadow-sm hover:shadow-md transition-all"
>
{session ? <><UserCircle size={16}/> {session.name}</> : <><Lock size={16}/> B2B Login</>}
{session ? <><UserCircle size={16}/> {session.name}</> : <><Lock size={16}/> {t("b2bLogin")}</>}
</button>
</div>
{/* 🛑 ESTADO BLOQUEADO (Visitantes sin sesn) */}
{/* Locked state (visitors without session) */}
{!session ? (
<div className="mt-8 py-24 flex flex-col items-center justify-center text-center border border-dashed border-black/10 dark:border-white/10 rounded-[2rem] bg-white/30 dark:bg-[#111]/30">
<div className="w-20 h-20 bg-black/5 dark:bg-white/5 rounded-full flex items-center justify-center mb-6">
<Lock size={32} className="text-[#1D1D1F] dark:text-white" />
</div>
<h3 className="text-2xl font-light text-[#1D1D1F] dark:text-white mb-3">Access Restricted</h3>
<h3 className="text-2xl font-light text-[#1D1D1F] dark:text-white mb-3">{t("accessRestricted")}</h3>
<p className="text-[#86868B] max-w-md mx-auto mb-8">
The FLUX Component Matrix is an exclusive B2B portal. Please sign in with your corporate account or request access to view components, pricing, and technical datasheets.
{t("accessDescription")}
</p>
<div className="flex flex-col sm:flex-row gap-4">
<button onClick={() => openAuth("LOGIN")} className="px-8 py-3.5 rounded-xl text-sm font-semibold bg-[#1D1D1F] dark:bg-white text-white dark:text-black flex items-center justify-center gap-2 transition-transform active:scale-95"><KeyRound size={16}/> Sign In</button>
<button onClick={() => openAuth("REGISTER")} className="px-8 py-3.5 rounded-xl text-sm font-semibold bg-black/5 dark:bg-white/10 text-[#1D1D1F] dark:text-white hover:bg-black/10 dark:hover:bg-white/20 transition-all active:scale-95">Request Access</button>
<button onClick={() => openAuth("LOGIN")} className="px-8 py-3.5 rounded-xl text-sm font-semibold bg-[#1D1D1F] dark:bg-white text-white dark:text-black flex items-center justify-center gap-2 transition-transform active:scale-95"><KeyRound size={16}/> {t("signIn")}</button>
<button onClick={() => openAuth("REGISTER")} className="px-8 py-3.5 rounded-xl text-sm font-semibold bg-black/5 dark:bg-white/10 text-[#1D1D1F] dark:text-white hover:bg-black/10 dark:hover:bg-white/20 transition-all active:scale-95">{t("requestAccess")}</button>
</div>
</div>
) : (
/* ✅ ESTADO DESBLOQUEADO (Catálogo Completo) */
/* Unlocked state (full catalog) */
<>
<div className="flex flex-col md:flex-row justify-between items-center gap-4 mb-8">
<div className="relative w-full md:w-96">
<div className="absolute inset-y-0 left-4 flex items-center pointer-events-none"><Search size={18} className="text-[#86868B]" /></div>
<input type="text" value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} placeholder="Search by SKU, name or spec..." className="w-full bg-white dark:bg-[#111] border border-black/10 dark:border-white/10 rounded-full pl-12 pr-10 py-3.5 text-sm text-[#1D1D1F] dark:text-white outline-none focus:border-[#0066CC] dark:focus:border-amber-500 transition-colors shadow-sm" />
<input type="text" value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} placeholder={t("searchPlaceholder")} className="w-full bg-white dark:bg-[#111] border border-black/10 dark:border-white/10 rounded-full pl-12 pr-10 py-3.5 text-sm text-[#1D1D1F] dark:text-white outline-none focus:border-[#0066CC] dark:focus:border-amber-500 transition-colors shadow-sm" />
{searchTerm && <button onClick={() => setSearchTerm("")} className="absolute inset-y-0 right-4 flex items-center text-[#86868B] hover:text-[#1D1D1F] dark:hover:text-white transition-colors"><X size={16} /></button>}
</div>
<div className="text-sm text-[#86868B] font-medium">{totalItems} {totalItems === 1 ? "component found" : "components found"}</div>
<div className="text-sm text-[#86868B] font-medium">{totalItems} {totalItems === 1 ? t("componentFound") : t("componentsFound")}</div>
</div>
{initialParts.length === 0 ? (
<div className="py-20 flex flex-col items-center justify-center text-center border border-dashed border-black/10 dark:border-white/10 rounded-[2rem] bg-white/30 dark:bg-[#111]/30">
<Wrench size={48} className="text-[#86868B]/30 mb-4" />
<h3 className="text-lg font-medium text-[#1D1D1F] dark:text-white mb-2">No components found</h3>
<button onClick={() => setSearchTerm("")} className="mt-4 text-sm text-[#0066CC] dark:text-amber-500 font-medium hover:underline">Clear search</button>
<h3 className="text-lg font-medium text-[#1D1D1F] dark:text-white mb-2">{t("noComponents")}</h3>
<button onClick={() => setSearchTerm("")} className="mt-4 text-sm text-[#0066CC] dark:text-amber-500 font-medium hover:underline">{t("clearSearch")}</button>
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
@@ -137,7 +136,7 @@ export default function ComponentGrid({ initialParts, locale, query, currentPage
{totalPages > 1 && (
<div className="flex items-center justify-center gap-4 mt-12">
<button onClick={() => handlePageChange(currentPage - 1)} disabled={currentPage === 1} className="p-3 rounded-full bg-white dark:bg-[#111] border border-black/10 dark:border-white/10 text-[#1D1D1F] dark:text-white disabled:opacity-30 disabled:cursor-not-allowed hover:bg-black/5 dark:hover:bg-white/5 transition-colors"><ChevronLeft size={20} /></button>
<span className="text-sm font-medium text-[#86868B]">Page <strong className="text-[#1D1D1F] dark:text-white">{currentPage}</strong> of {totalPages}</span>
<span className="text-sm font-medium text-[#86868B]">{t("page")} <strong className="text-[#1D1D1F] dark:text-white">{currentPage}</strong> {t("of")} {totalPages}</span>
<button onClick={() => handlePageChange(currentPage + 1)} disabled={currentPage === totalPages} className="p-3 rounded-full bg-white dark:bg-[#111] border border-black/10 dark:border-white/10 text-[#1D1D1F] dark:text-white disabled:opacity-30 disabled:cursor-not-allowed hover:bg-black/5 dark:hover:bg-white/5 transition-colors"><ChevronRight size={20} /></button>
</div>
)}
@@ -97,7 +97,7 @@ export default function PartDetailsModal({ part, isOpen, onClose }: PartDetailsM
) : (
<div className="flex flex-col items-center text-[#86868B]/40">
<Wrench size={64} className="mb-4" />
<p className="text-sm font-medium uppercase tracking-widest">No Media Available</p>
<p className="text-sm font-medium uppercase tracking-widest">{t("noMedia")}</p>
</div>
)}
</div>
@@ -140,7 +140,7 @@ export default function PartDetailsModal({ part, isOpen, onClose }: PartDetailsM
</h2>
<div className="flex items-end justify-between">
<div>
<p className="text-[10px] uppercase tracking-widest text-[#86868B] font-semibold mb-1">Unit Price</p>
<p className="text-[10px] uppercase tracking-widest text-[#86868B] font-semibold mb-1">{t("unitPrice")}</p>
{part.showPrice && part.price ? (
<p className="text-3xl font-mono font-medium text-[#1D1D1F] dark:text-white">{part.price.toFixed(2)}</p>
) : (
@@ -157,10 +157,9 @@ export default function PartDetailsModal({ part, isOpen, onClose }: PartDetailsM
{part.description && part.description !== "Draft description..." && (
<div>
<h3 className="text-xs uppercase tracking-widest text-[#86868B] font-semibold mb-4 flex items-center gap-2">
<Info size={14} /> Product Overview
<Info size={14} /> {t("productOverview")}
</h3>
<div className="max-w-none">
{/* 🔥 APLICAMOS TU PARSER AQUÍ 🔥 */}
{renderMarkdown(part.description)}
</div>
</div>
@@ -170,7 +169,7 @@ export default function PartDetailsModal({ part, isOpen, onClose }: PartDetailsM
{specs.length > 0 && (
<div>
<h3 className="text-xs uppercase tracking-widest text-[#86868B] font-semibold mb-4 flex items-center gap-2">
<Tag size={14} /> Technical Specifications
<Tag size={14} /> {t("techSpecs")}
</h3>
<div className="bg-white dark:bg-black/40 rounded-2xl overflow-hidden border border-black/5 dark:border-white/5 shadow-sm">
{specs.map((spec, idx) => (
@@ -190,7 +189,7 @@ export default function PartDetailsModal({ part, isOpen, onClose }: PartDetailsM
onClick={handleAddToCart}
className="w-full bg-[#1D1D1F] dark:bg-amber-500 text-white dark:text-black py-4 rounded-xl font-semibold flex items-center justify-center gap-2 transition-transform active:scale-[0.98] hover:shadow-lg dark:hover:shadow-[0_0_20px_rgba(245,158,11,0.3)]"
>
<ShoppingBag size={18} /> Add to Operations Cart
<ShoppingBag size={18} /> {t("addToCart")}
</button>
</div>