This commit is contained in:
+45
-6
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 sesión) */}
|
||||
{/* 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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user