3 Commits

Author SHA1 Message Date
Your Name 43502cb234 componentes en la pagina landing 2025-06-24 12:49:40 -06:00
Your Name 1628ab796e landing page y login listos en estilos 2025-06-20 21:29:52 -06:00
Your Name 2b22368d5e header y footer a layout de carga y visualizacion, bg azul removido 2025-06-20 10:04:20 -06:00
14 changed files with 551 additions and 23 deletions
+43
View File
@@ -11,6 +11,7 @@
"axios": "^1.9.0",
"bootstrap": "^5.3.6",
"bootstrap-icons": "^1.13.1",
"framer-motion": "^12.18.1",
"next": "15.3.3",
"react": "^19.0.0",
"react-dom": "^19.0.0"
@@ -987,6 +988,33 @@
"node": ">= 6"
}
},
"node_modules/framer-motion": {
"version": "12.18.1",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.18.1.tgz",
"integrity": "sha512-6o4EDuRPLk4LSZ1kRnnEOurbQ86MklVk+Y1rFBUKiF+d2pCdvMjWVu0ZkyMVCTwl5UyTH2n/zJEJx+jvTYuxow==",
"license": "MIT",
"dependencies": {
"motion-dom": "^12.18.1",
"motion-utils": "^12.18.1",
"tslib": "^2.4.0"
},
"peerDependencies": {
"@emotion/is-prop-valid": "*",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/is-prop-valid": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -1195,6 +1223,21 @@
"node": ">= 0.6"
}
},
"node_modules/motion-dom": {
"version": "12.18.1",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.18.1.tgz",
"integrity": "sha512-dR/4EYT23Snd+eUSLrde63Ws3oXQtJNw/krgautvTfwrN/2cHfCZMdu6CeTxVfRRWREW3Fy1f5vobRDiBb/q+w==",
"license": "MIT",
"dependencies": {
"motion-utils": "^12.18.1"
}
},
"node_modules/motion-utils": {
"version": "12.18.1",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.18.1.tgz",
"integrity": "sha512-az26YDU4WoDP0ueAkUtABLk2BIxe28d8NH1qWT8jPGhPyf44XTdDUh8pDk9OPphaSrR9McgpcJlgwSOIw/sfkA==",
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+1
View File
@@ -12,6 +12,7 @@
"axios": "^1.9.0",
"bootstrap": "^5.3.6",
"bootstrap-icons": "^1.13.1",
"framer-motion": "^12.18.1",
"next": "15.3.3",
"react": "^19.0.0",
"react-dom": "^19.0.0"
+16 -2
View File
@@ -3,9 +3,22 @@ import SimpleInput from "@/components/input";
import PasswordInput from "@/components/password";
import React from "react";
import { motion } from "framer-motion";
export default function Page() {
return (
<div className="flex-grow-1 d-flex flex-column justify-content-center align-items-center">
<motion.main
className="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: .5 }}
>
<h2 className="text-dorado">Bienvenido al sistema de</h2>
<h1 className="text-azul">Carga Masiva</h1>
<SimpleInput
@@ -21,6 +34,7 @@ export default function Page() {
}}
/>
<Button>Iniciar Sesión</Button>
</div>
</motion.main>
);
}
+4 -6
View File
@@ -13,19 +13,19 @@ const fuentes = [
];
export default function Dashboard() {
const loading = useAuthRedirect();
/* const loading = useAuthRedirect();
if (loading) {
return <p></p>;
}
} */
const handleDownload = async () => {
const token = localStorage.getItem("token");
if (!token) {
/* if (!token) {
alert("Token no encontrado. Inicia sesión.");
return;
}
} */
try {
const response = await fetch("http://localhost:4000/excel/download", {
@@ -64,13 +64,11 @@ export default function Dashboard() {
display: "flex",
justifyContent: "center",
alignItems: "center",
background: "#063970",
}}
>
<div
className="flex gap-4 flex-wrap mb-6"
style={{
background: "#f3f4f6",
padding: "2rem",
borderRadius: "8px",
boxShadow: "0 4px 6px rgba(253, 253, 253, 0.1)",
+45
View File
@@ -0,0 +1,45 @@
* {
padding: none;
margin: none;
}
.icon-circle {
position: relative;
width: 300px;
height: 300px;
margin: 2rem auto;
border-radius: 50%;
}
.central-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 2rem;
background: white;
padding: 1rem;
border-radius: 50%;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
z-index: 1;
}
.floating-icon {
position: absolute;
width: 40px;
height: 40px;
background: white;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
top: 50%;
left: 50%;
transform: rotate(calc(45deg * var(--i))) translate(130px) rotate(calc(-45deg * var(--i)));
box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
}
.floating-icon i {
font-size: 1.25rem;
color: #7c3aed; /* Morado como el diseño */
}
+60
View File
@@ -0,0 +1,60 @@
// IconCircle.tsx
'use client';
import React from 'react';
import { motion } from 'framer-motion';
import './IconCircle.css';
const iconClasses = [
'bi bi-graph-up',
'bi bi-lightning-fill',
'bi bi-currency-exchange',
'bi bi-globe2',
'bi bi-bar-chart-line-fill',
'bi bi-arrow-left-right',
'bi bi-bank',
'bi bi-boxes',
];
interface IconCircleProps {
delay?: number;
}
const IconCircle: React.FC<IconCircleProps> = ({ delay = 0 }) => {
return (
<div className="icon-circle">
<motion.div
className="central-icon"
initial={{ scale: 0 }}
animate={{ scale: 1, rotate: 1080 }}
transition={{ duration: 1, delay }}
>
<i className="bi bi-stars"></i>
</motion.div>
{iconClasses.map((iconClass, i) => (
<motion.div
key={i}
className="floating-icon"
initial={{
transform: 'translate(0, 0) scale(0)',
opacity: 0,
}}
animate={{
transform: `rotate(${i * 45}deg) translate(130px) rotate(-${i * 45}deg)`,
scale: 1,
opacity: 1,
}}
transition={{
delay: delay + 0.5 + i * 0.1, // empieza después del centro
duration: 0.5,
type: 'spring',
}}
>
<i className={iconClass}></i>
</motion.div>
))}
</div>
);
};
export default IconCircle;
+152
View File
@@ -0,0 +1,152 @@
* {
padding: none;
margin: none;
}
/* Fondo general y tipografía */
body {
margin: 0;
font-family: 'Segoe UI', sans-serif;
background: radial-gradient(circle at center, #f8f4ff, hsl(218, 45%, 37%));
/* background: radial-gradient(circle at center, #f8f4ff, hsl(217, 100%, 89%)); */
color: #1a1a1a;
text-align: center;
}
/* Header */
header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.5rem 2rem;
background: transparent;
font-weight: bold;
}
header div:first-child {
font-size: 1.5rem;
color: #5b2edd;
}
.nav-links {
background-color: white;
padding: 10px;
border-radius: 50px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); /* sombra suave */
display: flex;
gap: 1rem; /* espacio entre los <a> */
}
header a {
margin: 0 0.75rem;
text-decoration: none;
color: #333;
font-weight: 500;
}
header button {
margin-left: 0.5rem;
padding: 0.4rem 1rem;
border-radius: 20px;
border: none;
font-weight: 600;
cursor: pointer;
}
header button:first-of-type {
background-color: white;
border: 1px solid #ccc;
color: #555;
}
header button:last-of-type {
background-color: #0033ff;
color: white;
}
/* Texto principal */
strong {
display: block;
font-size: 2.5rem;
margin: 2rem 0 1rem;
}
p {
font-size: 1rem;
color: #555;
/* max-width: 600px; */
margin: 0 auto 1rem;
}
/* Botón principal */
div > button {
margin-top: 1rem;
background-color: #5b2edd;
color: white;
padding: 0.7rem 2rem;
border: none;
border-radius: 25px;
font-size: 1rem;
cursor: pointer;
}
/* Texto de "years of reliability" */
div > div {
/* display: flex; */
justify-content: center;
align-items: center;
gap: 0.5rem;
margin-top: 0.5rem;
}
div > div p {
margin: 0;
font-size: 0.9rem;
color: #888;
}
/* Scroll hint */
body > p:last-of-type {
margin-top: 2rem;
font-size: 0.85rem;
color: #aaa;
letter-spacing: 1px;
}
.circle-background {
margin-top: 0;
padding-top: 0;
position: fixed;
top: none;
left: none;
width: 100vw;
height: 100vh;
z-index: -1; /* para que esté detrás de todo */
pointer-events: none; /* no interfiere con el contenido */
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
.circle {
position: absolute;
background: radial-gradient(circle, rgb(255, 255, 255), transparent);
border-radius: 50%;
pointer-events: none;
backdrop-filter: blur(2px);
}
.invert {
filter: invert(1);
}
+31
View File
@@ -0,0 +1,31 @@
// components/LandingBody.tsx
"use client";
import { motion } from "framer-motion";
import IconCircle from "./IconCircle";
export default function LandingBody() {
return (
<motion.main
className="flex-fill"
initial={{ y: 50, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ delay: 1, duration: 1 }}
>
<IconCircle delay={2} />
<strong>Explore markets ready for trading</strong>
<p>
Explore 300+ trading instruments across 10+ asset classes with Alchemy
Markets. Trade Forex, Stocks, Indices, and more!
</p>
<div>
<button>Start Trading</button>
<div>
<i></i>
</div>
</div>
</motion.main>
);
}
+80
View File
@@ -0,0 +1,80 @@
"use client";
import { motion } from "framer-motion";
import Image from "next/image";
import Link from "next/link";
interface HeaderProps {
onNavClick: (page: string) => void;
}
export default function Header({ onNavClick }: HeaderProps) {
return (
<motion.header
initial={{ x: -100, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
transition={{ duration: 1.5, ease: "easeOut" }}
>
<div className="first-child">
<Link
href={"https://www.unam.mx/"}
target="_blank"
className="d-none d-sm-block"
>
<Image
src="/logo-unam.png"
alt="Logo de la UNAM"
width={280}
height={84}
className="invert"
/>
</Link>
</div>
{/* <div className="nav-links">
<a href="">Trading</a>
<a href="">Platforms</a>
<a href="">Tools & Education</a>
<a href="">About Us</a>
<a href="">Partners</a>
<a href="">Login</a>
</div> */}
<nav className="nav-links">
{[
{ label: "Landing", page: "landing" },
{ label: "Login", page: "login" },
{ label: "carga", page: "carga" },
{ label: "consulta", page: "consulta" },
{ label: "Público", page: "publico" },
].map((item) => (
<a
key={item.page}
onClick={() => onNavClick(item.page)}
>
{item.label}
</a>
))}
</nav>
<div className="first-child">
<Link href={"https://acatlan.unam.mx/"} target="_blank">
<Image
src="/logo-fes.png"
alt="Logo de la FES Acatlán"
width={250}
height={60}
className="invert"
/>
</Link>
</div>
</motion.header>
);
}
+93
View File
@@ -0,0 +1,93 @@
"use client";
import "./globals.css";
import "bootstrap-icons/font/bootstrap-icons.css";
import IconCircle from "./IconCircle";
import { motion } from "framer-motion";
import Image from "next/image";
import Link from "next/link";
import Header from "./new_header"; // ajusta la ruta si estás en otra carpeta
import Footer from "@/components/layout/footer";
import LandingBody from "./landing_body";
import { useState } from "react";
import Page from "../(auth)/page";
import Dashboard from "../carga/page";
import Consulta from "../visualizacion/page";
type PageKey = "landing" | "login" | "carga" | "consulta" | "publico";
const Home = () => {
const [page, setPage] = useState<PageKey>("landing");
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 1 }}
className="page-wrapper d-flex flex-column min-vh-100"
>
<div className="circle-background">
{[150, 300, 450, 600, 900].map((size, i) => (
<motion.div
key={i}
className="circle"
initial={{ scale: 0 }}
animate={{ scale: 3 }}
transition={{
delay: 1 + i * 0.3, // empieza después de la animación del texto
duration: 2,
ease: "easeOut",
}}
style={{
width: size,
height: size,
}}
/>
))}
</div>
{/* ⬇️ Tu contenido principal */}
<Header onNavClick={setPage} />
{/* <LandingBody />*/}
{/* renderiza el body según el estado `page` */}
{page === "landing" && <LandingBody />}
{page === "login" && <Page />}
{page === "carga" && <Dashboard />}
{page === "consulta" && <Consulta />}
{/* <Login />
<contenidoAdmin1 />
<contenidoWorker />
<contenidoPublico />
*/}
<Footer></Footer>
</motion.div>
);
};
export default Home;
+2 -2
View File
@@ -18,8 +18,8 @@ export default function RootLayout({
}>) {
return (
<html lang="en">
<body className={`${roboto.className}`}>
<BootstrapClient />
<body >
{children}
</body>
</html>
+1 -3
View File
@@ -4,7 +4,7 @@ import React, { useState } from "react";
import Input from "@/components/input";
import Button from "@/components/button";
export default function Dashboard() {
export default function Consulta() {
const [tipo, setTipo] = useState("");
const [value, setValue] = useState("");
const [resultados, setResultados] = useState<any[]>([]);
@@ -48,7 +48,6 @@ export default function Dashboard() {
display: "flex",
justifyContent: "center",
alignItems: "center",
background: "#063970",
flexDirection: "column",
padding: "2rem",
}}
@@ -56,7 +55,6 @@ export default function Dashboard() {
<div
className="flex gap-4 flex-wrap mb-6"
style={{
background: "#f3f4f6",
padding: "2rem",
borderRadius: "8px",
boxShadow: "0 4px 6px rgba(253, 253, 253, 0.1)",
+21 -8
View File
@@ -1,20 +1,33 @@
import React from "react";
import { motion } from "framer-motion";
export default function Footer() {
const date = new Date();
const year = date.getFullYear();
return (
<footer className="bg-azul p-3 text-center text-white small">
<p className="m-0">
Hecho en México. Todos los derechos reservados {year}.
return (
<motion.footer
// Empieza 50px abajo y totalmente transparente
initial={{ y: 50, opacity: 0 }}
// Termina en su posición natural y opacidad 1
animate={{ y: 0, opacity: 1 }}
// Retraso para que aparezca justo después del main
transition={{ delay: 2, duration: 1, ease: "easeOut" }}
className="p-3 text-center small text-muted opacity-50"
>
<p className="mb-1">
Hecho en México. Todos los derechos reservados 2025.
</p>
<p className="m-0">
<p className="mb-0">
Esta página puede ser reproducida con fines no lucrativos, siempre y
cuando no se mutile, se cite la fuente completa y su dirección
electrónica. De otra forma, requiere permiso previo por escrito de la
institución.
</p>
</footer>
);
}
</motion.footer>
)
}
+2 -2
View File
@@ -11,12 +11,12 @@ export function useAuthRedirect() {
const token = localStorage.getItem("token");
// Simula verificación (puedes hacer una llamada real)
if (!token) {
/* if (!token) {
router.replace("/");
} else {
// Aquí podrías verificar validez con una API, jwt-decode, etc.
setLoading(false); // Ya está autenticado, muestra contenido
}
} */
}, [router]);
return loading;