Se agregaró inicio de Google y contraseña

This commit is contained in:
2025-09-02 14:00:07 -06:00
parent 5d5210e48a
commit a9fb5d5a88
7 changed files with 431 additions and 266 deletions
+1 -1
View File
@@ -33,7 +33,7 @@ const FormularioCargaIndividual = () => {
const camposTrabajador = [
"num_cuenta", "nombre", "a_paterno", "a_materno", "rfc",
"fecha_nacimiento", "genero", "carrera", "clave_carrera",
"fecha_nacimiento", "genero", "clave_carrera",
];
const camposVisibles =
+29
View File
@@ -0,0 +1,29 @@
"use client";
import { useEffect } from "react";
import { useRouter } from "next/navigation";
export default function OAuthCallback() {
const router = useRouter();
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const token = params.get("token");
if (token) {
// Guardar token en localStorage
localStorage.setItem("token", token);
// Disparar evento para que useSession se actualice
window.dispatchEvent(new Event("tokenChanged"));
// Redirigir al Home
router.replace("/");
} else {
// No hay token: redirigir a login
router.replace("/");
}
}, [router]);
return <p>Procesando inicio de sesión...</p>;
}
+69 -42
View File
@@ -10,10 +10,14 @@ import LoginPage from "@/containers/LoginPage";
import FormularioCargaIndividual from "./alta/alta";
import { useSession } from "@/hooks/use-session";
import { useEffect, useState } from "react";
import path from "path";
import { motion } from "framer-motion";
export type PageKey = "landing" | "login" | "carga" | "consulta" | "carga individual";
export type PageKey =
| "landing"
| "login"
| "carga"
| "consulta"
| "carga individual";
export interface HeaderItem {
label: string;
@@ -23,62 +27,81 @@ export interface HeaderItem {
const Home = () => {
const [page, setPage] = useState<PageKey>("landing");
const { data: user, loading } = useSession(); // usuario viene de useSession()
const [tokenProcessed, setTokenProcessed] = useState(false); // ✅ Esperar token
const { data: user, loading } = useSession();
const [isCarga, setIsCarga] = useState("Carga");
const [saludo, setSaludo] = useState("");
// 🔹 Capturar token antes de montar la UI
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const token = params.get("token");
if (token) {
localStorage.setItem("token", token);
window.dispatchEvent(new Event("tokenChanged"));
// Limpiar token de URL
const cleanUrl = window.location.origin + window.location.pathname;
window.history.replaceState({}, document.title, cleanUrl);
}
setTokenProcessed(true); // ✅ Ya procesamos token
}, []);
// 🔹 Saludo
useEffect(() => {
const origen = user?.tipo;
if (origen === "LOAD") {
setSaludo("Bienvenido al módulo de carga de datos");
} else if (origen === "RED") {
setSaludo("Bienvenido usuario de red");
} else if (origen === "AT") {
setSaludo("Bienvenido usuario de AT");
} else if (origen === "CORREO") {
setSaludo("Bienvenido usuario de correo");
} else if (origen === "SOLICITA") {
if (origen === "LOAD") setSaludo("Bienvenido al módulo de carga de datos");
else if (origen === "RED") setSaludo("Bienvenido usuario de red");
else if (origen === "AT") setSaludo("Bienvenido usuario de AT");
else if (origen === "CORREO") setSaludo("Bienvenido usuario de correo");
else if (origen === "SOLICITA")
setSaludo("Bienvenido usuario de préstamos PC Puma");
}
}, [user]);
// 🔹 Label carga/descarga
useEffect(() => {
if (user?.tipo === "LOAD") {
setIsCarga("Carga");
} else {
setIsCarga("Descarga");
}
if (user?.tipo === "LOAD") setIsCarga("Carga");
else setIsCarga("Descarga");
}, [user]);
// 🔹 Redirigir automáticamente si ya hay sesión
useEffect(() => {
if (!loading && user && page === "login") setPage("landing");
}, [loading, user, page]);
const handleLogout = () => {
localStorage.removeItem("token");
window.dispatchEvent(new Event("tokenChanged"));
location.reload();
};
const items: HeaderItem[] = user
? [
{ label: "Inicio", page: "landing" },
{ label: "Consulta", page: "consulta" },
{ label: isCarga, page: "carga" },
...(user.tipo === "LOAD"
? [
{ label: "Carga individual", page: "carga individual" as PageKey },
]
: []),
{ label: "Cerrar sesión", onClick: handleLogout },
]
{ label: "Inicio", page: "landing" },
{ label: "Consulta", page: "consulta" },
{ label: isCarga, page: "carga" },
...(user.tipo === "LOAD"
? [{ label: "Carga individual", page: "carga individual" as PageKey }]
: []),
{ label: "Cerrar sesión", onClick: handleLogout },
]
: [
{ label: "Consulta", page: "consulta" },
{ label: "Inicio", page: "landing" },
{ label: "Login", page: "login" },
];
{ label: "Consulta", page: "consulta" },
{ label: "Inicio", page: "landing" },
{ label: "Login", page: "login" },
];
// 🔹 Esperamos hasta que se procese el token
if (!tokenProcessed) {
return <p>Cargando sesión...</p>;
}
return (
<>
<Header items={items} onClickItem={setPage} />
{/* Saludo en la esquina superior derecha */}
{saludo && (
<motion.div
className="w-full flex justify-end pr-6 mb-5"
@@ -88,17 +111,21 @@ const Home = () => {
>
<h2 className="text-dorado text-sm text-right">{saludo}</h2>
</motion.div>
)}
<div className="flex-grow-1 d-flex flex-column align-items-center justify-content-center text-center">
{page === "landing" && <LandingBody />}
{page === "login" && <LoginPage />}
{page === "consulta" && <Consulta />}
{page === "carga" && <Dashboard />}
{page === "carga individual" && user?.tipo === "LOAD" && (
<FormularioCargaIndividual />
{loading && <p>Cargando sesión...</p>}
{!loading && (
<>
{page === "landing" && <LandingBody />}
{page === "login" && <LoginPage />}
{page === "consulta" && <Consulta />}
{page === "carga" && <Dashboard />}
{page === "carga individual" && user?.tipo === "LOAD" && (
<FormularioCargaIndividual />
)}
</>
)}
</div>
+134
View File
@@ -0,0 +1,134 @@
"use client";
import SimpleInput from "@/components/input";
import "../landing/globals.css";
import Footer from "@/components/layout/footer";
import { motion } from "framer-motion";
import PasswordInput from "@/components/password";
import Button from "@/components/button";
import { useEffect, useState } from "react";
import axios from "axios";
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
const SetPasswordPage = () => {
const [email, setEmail] = useState(""); // email desde URL
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
// Captura email desde query param
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const emailParam = params.get("email");
if (emailParam) setEmail(emailParam);
}, []);
const handleSetPassword = async () => {
if (!password || !confirmPassword) {
setError("Por favor ingresa la contraseña y confírmala");
return;
}
if (password !== confirmPassword) {
setError("Las contraseñas no coinciden");
return;
}
setLoading(true);
setError("");
try {
const response = await axios.post(`${apiUrl}/set-password`, {
email,
password,
});
const token = response.data.access_token;
localStorage.setItem("token", token);
window.dispatchEvent(new Event("tokenChanged"));
// Redirigir al Home
window.location.href = "/";
} catch (err: any) {
console.error(err);
setError(
err.response?.data?.message || "Error al establecer la contraseña"
);
} finally {
setLoading(false);
}
};
return (
<>
<motion.div
className="w-full flex justify-end pr-6 mb-5"
initial={{ x: -100, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
transition={{ duration: 1.5, ease: "easeOut" }}
>
</motion.div>
<div className="flex-grow-1 d-flex flex-column align-items-center justify-content-center text-center">
<motion.main
className="auth-page flex-fill flex-grow-1 d-flex flex-column justify-content-center align-items-center"
initial={{ y: 50, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ delay: 0, duration: 0.5 }}
>
<h1 className="text-dorado">Establece tu contraseña para iniciar sesión</h1>
<SimpleInput
label="Usuario"
className={{
container: "w-300px mb-3",
}}
value={email}
readOnly
/>
<PasswordInput
label="Nueva contraseña"
className={{
container: "w-300px mb-3",
input: "form-control",
button: "btn btn-outline-secondary",
}}
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<PasswordInput
label="Confirmar contraseña"
className={{
container: "w-300px mb-3",
input: "form-control",
button: "btn btn-outline-secondary",
}}
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
/>
{error && <p className="text-danger text-center mb-3">{error}</p>}
<div className="d-flex flex-column gap-3 w-300px">
<Button
onClick={handleSetPassword}
disabled={loading}
variant="azul"
>
{loading ? "Guardando..." : "Establecer contraseña"}
</Button>
</div>
</motion.main>
</div>
<Footer />
</>
);
};
export default SetPasswordPage;
+31 -8
View File
@@ -10,7 +10,6 @@ import { motion } from "framer-motion";
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
export default function Page() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
@@ -21,14 +20,11 @@ export default function Page() {
const handleLogin = async () => {
setLoading(true);
try {
const response = await axios.post(`${apiUrl}/login`, {
email: email,
password: password,
});
const token = response.data.access_token;
localStorage.setItem("token", token);
@@ -40,8 +36,6 @@ export default function Page() {
setPassword("");
setError("");
window.location.reload();
// Redirigir al usuario
} catch (err: any) {
const message =
"credenciales incorrectas, por favor verifica tu usuario y contraseña";
@@ -51,6 +45,18 @@ export default function Page() {
}
};
// 🔑 Captura Enter en cualquier input
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
handleLogin();
}
};
// 👉 Iniciar sesión con Google
const handleGoogleLogin = () => {
window.location.href = `${apiUrl}/google`;
};
return (
<>
<motion.main
@@ -69,6 +75,7 @@ export default function Page() {
}}
value={email}
onChange={(e) => setEmail(e.target.value)}
onKeyDown={handleKeyDown}
/>
<PasswordInput
@@ -80,14 +87,30 @@ export default function Page() {
}}
value={password}
onChange={(e) => setPassword(e.target.value)}
onKeyDown={handleKeyDown}
/>
{error && <p className="text-danger text-center mb-3">{error}</p>}
<div className="d-flex gap-3">
<Button onClick={handleLogin} disabled={loading} variant="azul" >
<div className="d-flex flex-column gap-3 w-300px">
{/* Botón normal */}
<Button onClick={handleLogin} disabled={loading} variant="azul">
{loading ? "Iniciando sesión..." : "Iniciar Sesión"}
</Button>
{/* Botón Google */}
<Button
onClick={handleGoogleLogin}
variant="rojo"
className="d-flex align-items-center justify-content-center gap-2"
>
<img
src="https://www.svgrepo.com/show/355037/google.svg"
alt="Google"
className="w-5 h-5"
/>
Iniciar sesión con Google
</Button>
</div>
</motion.main>
</>