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
+45 -6
View File
@@ -12,12 +12,6 @@
"description1": "Innovationen vorantreiben, wo andere zögern",
"description2": "RF-Lösungen der nächsten Generation mit unübertroffener Präzision und europäischer Exzellenz."
},
"SpareParts": {
"title1": "Komponenten-",
"title2": "Matrix.",
"description": "Original FLUX Ersatzteile und Upgrades. Gewährleisten Sie maximale Leistung und Langlebigkeit für Ihre RF-Anlagen.",
"quoteBased": "Auf Anfrage"
},
"CartDrawer": {
"titleSupport": "Technischer Support",
"titleCart": "Warenkorb",
@@ -165,5 +159,50 @@
"companyStory": "Unsere Geschichte",
"companyMap": "Globales Netzwerk",
"companyNews": "Inside Flux"
},
"SpareParts": {
"title1": "Komponenten-",
"title2": "Matrix.",
"description": "Original FLUX Ersatzteile und Upgrades. Gewährleisten Sie maximale Leistung und Langlebigkeit für Ihre RF-Anlagen.",
"quoteBased": "Auf Anfrage",
"searchPlaceholder": "Suche nach SKU, Name oder Spezifikation...",
"componentsFound": "Komponenten gefunden",
"componentFound": "Komponente gefunden",
"noComponents": "Keine Komponenten gefunden",
"clearSearch": "Suche löschen",
"accessRestricted": "Zugriff eingeschränkt",
"accessDescription": "Die FLUX Komponenten-Matrix ist ein exklusives B2B-Portal. Bitte melden Sie sich mit Ihrem Geschäftskonto an oder fordern Sie Zugriff an, um Komponenten, Preise und technische Datenblätter einzusehen.",
"signIn": "Anmelden",
"requestAccess": "Zugriff anfordern",
"b2bLogin": "B2B-Login",
"addToCart": "Zum Warenkorb hinzufügen",
"unitPrice": "Stückpreis",
"productOverview": "Produktübersicht",
"techSpecs": "Technische Spezifikationen",
"noMedia": "Keine Medien verfügbar",
"mediaGallery": "Mediengalerie",
"page": "Seite",
"of": "von"
},
"AuthModal": {
"b2bPortal": "B2B-Portal",
"signIn": "Anmelden",
"requestAccess": "Zugriff anfordern",
"corporateEmail": "Geschäftliche E-Mail",
"password": "Passwort",
"accessPortal": "Sicheres Portal betreten",
"fullName": "Vollständiger Name",
"companyName": "Firmenname",
"workEmail": "Arbeits-E-Mail",
"createPassword": "Passwort erstellen",
"submitRequest": "Anfrage absenden",
"successMessage": "Zugriff erfolgreich angefordert. Wir benachrichtigen Sie per E-Mail nach technischer Prüfung.",
"updateCredentials": "Sicherheitsdaten aktualisieren",
"currentPassword": "Aktuelles Passwort",
"newPassword": "Neues Passwort",
"changePassword": "Passwort ändern",
"passwordUpdated": "Passwort sicher aktualisiert.",
"secureLogout": "Sicherer Logout",
"invalidCredentials": "Ungültige Anmeldedaten."
}
}
+46 -7
View File
@@ -12,12 +12,6 @@
"description1": "Driving innovation where others hesitate",
"description2": "Crafting Next-Gen RF Solutions With Unmatched Precision and European Excellence."
},
"SpareParts": {
"title1": "Component",
"title2": "Matrix.",
"description": "Original FLUX replacement parts and upgrades. Ensure maximum performance and longevity for your RF equipment.",
"quoteBased": "Quote Based"
},
"CartDrawer": {
"titleSupport": "Tech Support",
"titleCart": "Operations Cart",
@@ -165,5 +159,50 @@
"companyStory": "Our Story",
"companyMap": "Global Network",
"companyNews": "Inside Flux"
}
},
"SpareParts": {
"title1": "Component",
"title2": "Matrix.",
"description": "Original FLUX replacement parts and upgrades. Ensure maximum performance and longevity for your RF equipment.",
"quoteBased": "Quote Based",
"searchPlaceholder": "Search by SKU, name or spec...",
"componentsFound": "components found",
"componentFound": "component found",
"noComponents": "No components found",
"clearSearch": "Clear search",
"accessRestricted": "Access Restricted",
"accessDescription": "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.",
"signIn": "Sign In",
"requestAccess": "Request Access",
"b2bLogin": "B2B Login",
"addToCart": "Add to Operations Cart",
"unitPrice": "Unit Price",
"productOverview": "Product Overview",
"techSpecs": "Technical Specifications",
"noMedia": "No Media Available",
"mediaGallery": "Media Gallery",
"page": "Page",
"of": "of"
},
"AuthModal": {
"b2bPortal": "B2B Portal",
"signIn": "Sign In",
"requestAccess": "Request Access",
"corporateEmail": "Corporate Email",
"password": "Password",
"accessPortal": "Access Secure Portal",
"fullName": "Full Name",
"companyName": "Company Name",
"workEmail": "Work Email",
"createPassword": "Create Password",
"submitRequest": "Submit Request",
"successMessage": "Access requested successfully. We will notify you via email upon engineering approval.",
"updateCredentials": "Update Security Credentials",
"currentPassword": "Current Password",
"newPassword": "New Password",
"changePassword": "Change Password",
"passwordUpdated": "Password updated securely.",
"secureLogout": "Secure Logout",
"invalidCredentials": "Invalid credentials."
}
}
+45 -6
View File
@@ -12,12 +12,6 @@
"description1": "Impulsando la innovación donde otros dudan",
"description2": "Creando soluciones de RF de última generación con precisión inigualable y excelencia europea."
},
"SpareParts": {
"title1": "Matriz de",
"title2": "Componentes.",
"description": "Repuestos y actualizaciones originales de FLUX. Asegure el máximo rendimiento y longevidad para su equipo de RF.",
"quoteBased": "Bajo Cotización"
},
"CartDrawer": {
"titleSupport": "Soporte Técnico",
"titleCart": "Carrito de Operaciones",
@@ -165,5 +159,50 @@
"companyStory": "Nuestra Historia",
"companyMap": "Red Global",
"companyNews": "Inside Flux"
},
"SpareParts": {
"title1": "Matriz de",
"title2": "Componentes.",
"description": "Repuestos y actualizaciones originales de FLUX. Asegure el máximo rendimiento y longevidad para su equipo de RF.",
"quoteBased": "Bajo Cotización",
"searchPlaceholder": "Buscar por SKU, nombre o especificación...",
"componentsFound": "componentes encontrados",
"componentFound": "componente encontrado",
"noComponents": "No se encontraron componentes",
"clearSearch": "Limpiar búsqueda",
"accessRestricted": "Acceso Restringido",
"accessDescription": "La Matriz de Componentes FLUX es un portal exclusivo B2B. Por favor, inicie sesión con su cuenta corporativa o solicite acceso para ver componentes, precios y fichas técnicas.",
"signIn": "Iniciar Sesión",
"requestAccess": "Solicitar Acceso",
"b2bLogin": "Login B2B",
"addToCart": "Añadir al Carrito de Operaciones",
"unitPrice": "Precio Unitario",
"productOverview": "Descripción del Producto",
"techSpecs": "Especificaciones Técnicas",
"noMedia": "No hay archivos multimedia",
"mediaGallery": "Galería de Medios",
"page": "Página",
"of": "de"
},
"AuthModal": {
"b2bPortal": "Portal B2B",
"signIn": "Iniciar Sesión",
"requestAccess": "Solicitar Acceso",
"corporateEmail": "Correo Corporativo",
"password": "Contraseña",
"accessPortal": "Acceder al Portal Seguro",
"fullName": "Nombre Completo",
"companyName": "Nombre de la Empresa",
"workEmail": "Correo de Trabajo",
"createPassword": "Crear Contraseña",
"submitRequest": "Enviar Solicitud",
"successMessage": "Acceso solicitado con éxito. Le notificaremos por correo tras la aprobación de ingeniería.",
"updateCredentials": "Actualizar Credenciales de Seguridad",
"currentPassword": "Contraseña Actual",
"newPassword": "Nueva Contraseña",
"changePassword": "Cambiar Contraseña",
"passwordUpdated": "Contraseña actualizada de forma segura.",
"secureLogout": "Cierre de Sesión Seguro",
"invalidCredentials": "Credenciales inválidas."
}
}
+45 -6
View File
@@ -12,12 +12,6 @@
"description1": "Guidare l'innovazione dove altri esitano",
"description2": "Creare soluzioni RF di prossima generazione con precisione impareggiabile ed eccellenza europea."
},
"SpareParts": {
"title1": "Matrice",
"title2": "Componenti.",
"description": "Ricambi e aggiornamenti originali FLUX. Garantite la massima resa e longevità alle vostre apparecchiature RF.",
"quoteBased": "Su Preventivo"
},
"CartDrawer": {
"titleSupport": "Supporto Tecnico",
"titleCart": "Carrello Operazioni",
@@ -165,5 +159,50 @@
"companyStory": "La nostra Storia",
"companyMap": "Rete Globale",
"companyNews": "Dentro Flux"
},
"SpareParts": {
"title1": "Matrice",
"title2": "Componenti.",
"description": "Ricambi e aggiornamenti originali FLUX. Garantite la massima resa e longevità alle vostre apparecchiature RF.",
"quoteBased": "Su Preventivo",
"searchPlaceholder": "Cerca per SKU, nome o specifica...",
"componentsFound": "componenti trovati",
"componentFound": "componente trovato",
"noComponents": "Nessun componente trovato",
"clearSearch": "Cancella ricerca",
"accessRestricted": "Accesso Limitato",
"accessDescription": "La Matrice Componenti FLUX è un portale esclusivo B2B. Accedi con il tuo account aziendale o richiedi l'accesso per visualizzare componenti, prezzi e schede tecniche.",
"signIn": "Accedi",
"requestAccess": "Richiedi Accesso",
"b2bLogin": "Login B2B",
"addToCart": "Aggiungi al Carrello Operazioni",
"unitPrice": "Prezzo Unitario",
"productOverview": "Panoramica Prodotto",
"techSpecs": "Specifiche Tecniche",
"noMedia": "Nessun file multimediale",
"mediaGallery": "Galleria Media",
"page": "Pagina",
"of": "di"
},
"AuthModal": {
"b2bPortal": "Portale B2B",
"signIn": "Accedi",
"requestAccess": "Richiedi Accesso",
"corporateEmail": "Email Aziendale",
"password": "Password",
"accessPortal": "Accedi al Portale Sicuro",
"fullName": "Nome Completo",
"companyName": "Ragione Sociale",
"workEmail": "Email di Lavoro",
"createPassword": "Crea Password",
"submitRequest": "Invia Richiesta",
"successMessage": "Accesso richiesto con successo. Ti informeremo via email previa approvazione tecnica.",
"updateCredentials": "Aggiorna Credenziali di Sicurezza",
"currentPassword": "Password Attuale",
"newPassword": "Nuova Password",
"changePassword": "Cambia Password",
"passwordUpdated": "Password aggiornata con successo.",
"secureLogout": "Logout Sicuro",
"invalidCredentials": "Credenziali non valide."
}
}
+45 -6
View File
@@ -12,12 +12,6 @@
"description1": "Spingemo l'inovaçion dove i altri i se ferma",
"description2": "Solusion RF de nova generasiòn con na preciçion mai vista e l'ecełensa de casa nostra."
},
"SpareParts": {
"title1": "Matriçe dei",
"title2": "Pessi.",
"description": "Pessi de ricambio e agiornamenti par omini veri firmà FLUX. Par far ndar la machina de RF sempre a tura e par tanto tempo.",
"quoteBased": "Dimanda el preso"
},
"CartDrawer": {
"titleSupport": "Asistensa Tecnica",
"titleCart": "Careło Operasion",
@@ -165,5 +159,50 @@
"companyStory": "La nostra Storia",
"companyMap": "Rede Global",
"companyNews": "Drento Flux"
},
"SpareParts": {
"title1": "Matriçe dei",
"title2": "Pessi.",
"description": "Pessi de ricambio e agiornamenti par omini veri firmà FLUX. Par far ndar la machina de RF sempre a tura e par tanto tempo.",
"quoteBased": "Dimanda el preso",
"searchPlaceholder": "Serca par SKU, nome o detajo...",
"componentsFound": "pessi catà",
"componentFound": "pesso catà",
"noComponents": "No go catà gnente",
"clearSearch": "Scanceła la riserca",
"accessRestricted": "No te poi entrar",
"accessDescription": "La Matriçe dei Pessi FLUX la xe solo par i laoradori. Par piaçer, meti drento i to dati o dimanda de entrar par vedar i pessi, i presi e le carte tecniche.",
"signIn": "Entra chive",
"requestAccess": "Dimanda de entrar",
"b2bLogin": "Login par ditte",
"addToCart": "Meti drento el careło",
"unitPrice": "Preso par pesso",
"productOverview": "Cossa che el xe",
"techSpecs": "Detaji tecnici",
"noMedia": "No go foto da mostrare",
"mediaGallery": "Gałeria de foto",
"page": "Pagina",
"of": "de"
},
"AuthModal": {
"b2bPortal": "Portal par ditte",
"signIn": "Entra chive",
"requestAccess": "Dimanda de entrar",
"corporateEmail": "Email de la ditta",
"password": "Ciave (Password)",
"accessPortal": "Entra nel portal sicuro",
"fullName": "Nome e cognome",
"companyName": "Nome de la ditta",
"workEmail": "Email de lavoro",
"createPassword": "Fà na ciave nova",
"submitRequest": "Manda la dimanda",
"successMessage": "Te go meso in lista. Te mandemo na mail quando che i ingegneri i ga dito de sì.",
"updateCredentials": "Inpissà i dati de sicuressa",
"currentPassword": "Ciave de desso",
"newPassword": "Ciave nova",
"changePassword": "Cambia la ciave",
"passwordUpdated": "Ciave agiornà al sicuro.",
"secureLogout": "Va fora col seguro",
"invalidCredentials": "Dati no giusti."
}
}
@@ -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>