restructuring layout

This commit is contained in:
2025-09-26 16:56:07 -06:00
parent dce377b586
commit 3bd6d6b7c2
64 changed files with 187 additions and 195 deletions
@@ -0,0 +1,57 @@
@keyframes slideInLeft {
0% {
opacity: 0;
transform: translateX(-100%);
}
100% {
opacity: 0.9;
transform: translateX(0);
}
}
@keyframes slideOutRight {
0% {
opacity: 0.9;
transform: translateX(0);
}
100% {
opacity: 0;
transform: translateX(100%);
}
}
.messageBox {
display: flex;
position: absolute;
padding: 0.5rem 1.25rem;
border-radius: 0 4px 4px 0;
font-weight: bold;
font-size: 2rem;
text-align: center;
z-index: 100;
top: 0;
left: 0;
max-width: 500px;
max-height: min-content;
opacity: 0;
}
.messageBox:not(.hidden) {
animation: slideInLeft 0.5s ease forwards;
}
.messageBox.hidden {
animation: slideOutRight 0.5s ease forwards;
}
.success {
background-color: #d1f7c4;
color: #000000;
border: 1px solid #a5d6a7;
}
.error {
background-color: #ffcdd2;
color: #000000;
border: 1px solid #ef9a9a;
}
@@ -0,0 +1,41 @@
"use client";
import { useEffect, useState } from "react";
import "./AlertBox.css";
interface AlertBoxProps {
message: string | null;
type: "error" | "success";
duration?: number;
}
export default function AlertBox({
message,
type,
duration = 6000,
}: AlertBoxProps) {
const [visible, setVisible] = useState(false);
const [text, setText] = useState("");
useEffect(() => {
if (message) {
setText(message);
setVisible(true);
const timeout = setTimeout(() => {
setVisible(false);
setTimeout(() => setText(""), 500);
}, duration);
return () => clearTimeout(timeout);
}
}, [message, duration]);
if (!text) return null;
return (
<div className={`messageBox ${type} ${!visible ? "hidden" : ""}`}>
{text}
</div>
);
}
@@ -0,0 +1,27 @@
"use client";
import { useEffect } from "react";
interface ClearParamsProps {
paramsToClear: string[];
}
export default function ClearParams({ paramsToClear }: ClearParamsProps) {
useEffect(() => {
const url = new URL(window.location.href);
let changed = false;
paramsToClear.forEach((param) => {
if (url.searchParams.has(param)) {
url.searchParams.delete(param);
changed = true;
}
});
if (changed) {
window.history.replaceState({}, "", url.toString());
}
}, [paramsToClear]);
return null;
}
@@ -0,0 +1,25 @@
'use client';
interface InformationProps {
[key: string]: string | number | undefined;
}
export default function Information(props: InformationProps) {
// Obtenemos las entradas (key + value) y filtramos los que sean undefined
const entries = Object.entries(props).filter(([_, value]) => value !== undefined);
if (entries.length === 0) return null; // no mostrar nada si no hay datos
return (
<div className="information">
<ul>
{entries.map(([key, value]) => (
<li key={key}>
<b>{key}: </b>{value}
</li>
))}
</ul>
</div>
);
}
//IO
@@ -0,0 +1,57 @@
'use client'
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
interface urlProp{
urlBase:string
value:string|null
}
function SearchUser(props:urlProp) {
const [numAcount, setnumAcount] = useState("");
const router = useRouter();
useEffect(() => {
if (props.value) {
setnumAcount(props.value);
}
}, [props.value]);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (numAcount) {
router.push(`/${props.urlBase}?numAcount=${numAcount}`);
}
};
return (
<>
<form className="containerForm"
onSubmit={handleSubmit}>
<label className="label">No.Cuenta</label>
<div className="groupInput">
<input
type="text"
value={numAcount}
onChange={(e) => {
const value = e.target.value;
if (/^\d*$/.test(value) && value.length <= 9) {
setnumAcount(value);
}
}}
placeholder="Coloca un número de cuenta..."
inputMode="numeric"
pattern="[0-9]*"
/>
<button className="button buttonSearch" type="submit">
Buscar
</button>
</div>
</form>
</>
);
}
export default SearchUser;
@@ -0,0 +1,12 @@
.stepNavigator {
position: relative;
width: 100%;
max-width: 600px;
margin-top: 1rem;
border: 1px solid #e5e7eb;
border-radius: 4px;
background-color: #f9fafb;
padding: 1rem;
display: flex;
flex-direction: column;
}
@@ -0,0 +1,50 @@
'use client'
import { useState, ReactNode } from "react";
import "./StepNavigator.css"
interface StepNavigatorProps {
totalSteps: number;
children: ReactNode[];
onFinish?: () => void;
}
export default function StepNavigator({ totalSteps, children, onFinish }: StepNavigatorProps) {
const [step, setStep] = useState(1);
const handleNext = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
if (step < totalSteps) setStep(step + 1);
else if (onFinish) onFinish();
};
const handlePrev = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
if (step > 1) setStep(step - 1);
};
return (
<section className="stepNavigator">
{children[step - 1]}
<div className="absoluteButton">
{step > 1 && (
<button
onClick={handlePrev}
className="button buttonSearch">
Atrás
</button>
)}
{step < totalSteps &&
<button
onClick={handleNext}
className="button buttonSearch"
>
Siguiente
</button>
}
</div>
</section>
);
}
+38
View File
@@ -0,0 +1,38 @@
"use client";
import { useState, ReactNode } from "react";
interface ToggleOption {
key: string;
label: string;
content: ReactNode;
}
interface ToggleProps {
options: ToggleOption[];
defaultView?: string;
}
export default function Toggle({ options, defaultView }: ToggleProps) {
const [view, setView] = useState(defaultView || options[0].key);
return (
<section className="toggleSection">
<div className="toggleGroup">
{options.map((opt) => (
<button
key={opt.key}
className={`toggleButton ${view === opt.key ? "active" : ""}`}
onClick={() => setView(opt.key)}
>
{opt.label}
</button>
))}
</div>
<div className="padding">
{options.find((opt) => opt.key === view)?.content}
</div>
</section>
);
}
@@ -0,0 +1,12 @@
import "./style.css"
export default function FallingSquares() {
return (
<div className="falling-container">
{Array.from({ length: 10 }).map((_, i) => (
<div key={i} className="square"></div>
))}
</div>
);
}
//Chat
//IO
@@ -0,0 +1,119 @@
.falling-container {
position: absolute;
width: 100%;
height: 100vh;
overflow: hidden;
z-index: -1;
}
.square {
position: absolute;
top: -100px;
opacity: 0.1;
animation: fall linear infinite;
border-radius: 6px;
}
@keyframes fall {
0% {
transform: translateY(0) rotate(0deg);
}
100% {
transform: translateY(110vh) rotate(1080deg);
}
}
.square:nth-child(1) {
left: 10%;
width: 40px;
height: 40px;
background: linear-gradient(45deg, #f6ff7e, #e6ff75);
animation-duration: 11s;
animation-delay: 0s;
}
.square:nth-child(2) {
left: 25%;
width: 60px;
height: 60px;
background: linear-gradient(45deg, #438bce, #185a9d);
animation-duration: 12s;
animation-delay: 2s;
}
.square:nth-child(3) {
left: 40%;
width: 50px;
height: 50px;
background: linear-gradient(45deg, #f7d71e, #ffd200);
animation-duration: 15s;
animation-delay: 1s;
}
.square:nth-child(4) {
left: 55%;
width: 70px;
height: 70px;
background: linear-gradient(45deg, #ffe600, #ffd000);
animation-duration: 17s;
animation-delay: 3s;
}
.square:nth-child(5) {
left: 70%;
width: 45px;
height: 45px;
background: linear-gradient(45deg, #00c6ff, #0072ff);
animation-duration: 14.5s;
animation-delay: 1.5s;
}
.square:nth-child(6) {
left: 85%;
width: 55px;
height: 55px;
background: linear-gradient(45deg, #535ef9, #201db9);
animation-duration: 16.5s;
animation-delay: 2.5s;
}
.square:nth-child(7) {
left: 15%;
width: 65px;
height: 65px;
background: linear-gradient(45deg, #115799, #384aef);
animation-duration: 15.5s;
animation-delay: 1.2s;
}
.square:nth-child(8) {
left: 35%;
width: 40px;
height: 40px;
background: linear-gradient(45deg, #ffdf4e, #f9d423);
animation-duration: 17.5s;
animation-delay: 0.5s;
}
.square:nth-child(9) {
left: 60%;
width: 75px;
height: 75px;
background: linear-gradient(45deg, #b09000, #98c93d);
animation-duration: 14.2s;
animation-delay: 2.8s;
}
.square:nth-child(10) {
left: 80%;
width: 28px;
height: 28px;
background: linear-gradient(45deg, #ffdb66, #f4ff5e);
animation-duration: 16.8s;
animation-delay: 1.8s;
}
@media (min-width: 768px) {
.square {
animation: fall linear infinite;
}
}
@media (max-width: 767px) {
.square {
animation: none;
}
}
@@ -0,0 +1,35 @@
"use client";
import { useEffect, useState } from "react";
import AlertBox from "@/app/Components/Global/AlertBox/AlertBox";
import ClearParams from "@/app/Components/Global/ClearParams/ClearParams";
export default function GlobalAlert() {
const [showSuccess, setShowSuccess] = useState<string | null>(null);
const [showError, setShowError] = useState<string | null>(null);
useEffect(() => {
const url = new URL(window.location.href);
const success = url.searchParams.get("success");
const error = url.searchParams.get("error");
if (success) setShowSuccess(success);
if (error) setShowError(error);
}, []);
return (
<section className="containerAlerts">
{showError && (
<>
<AlertBox key={Date.now()} message={showError} type="error" />
</>
)}
{showSuccess && (
<>
<AlertBox key={Date.now()} message={showSuccess} type="success" />
</>
)}
</section>
);
}
@@ -0,0 +1,57 @@
.ocean {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 30vh;
overflow: hidden;
opacity: 0.4;
background: #4fc3f7; /* color del mar */
z-index: -1; /* para que quede atrás del contenido */
}
.wave {
position: absolute;
top: 0;
left: 0;
width: 160%;
height: 130%;
background: rgba(255, 255, 255, 0.5);
border-radius: 50% 50%;
animation: waveMove 6s linear infinite;
}
.wave:nth-child(2) {
top: -10;
background: rgba(255, 255, 255, 0.3);
animation-duration: 8s;
animation-delay: -2s;
}
.wave:nth-child(3) {
background: rgba(255, 255, 255, 0.2);
animation-duration: 10s;
animation-delay: -4s;
}
@keyframes waveMove {
0% {
transform: translateX(0) translateY(0);
}
50% {
transform: translateX(-25%) translateY(5%);
}
100% {
transform: translateX(0) translateY(0);
}
}
@media (max-width: 800px) {
.ocean {
background: transparent;
}
.wave {
display: none;
animation: none;
}
}
@@ -0,0 +1,14 @@
'use client';
import styles from './wave.module.css';
export default function WavesBackground() {
return (
<div className={styles.ocean}>
<div className={styles.wave}></div>
<div className={styles.wave}></div>
<div className={styles.wave}></div>
</div>
);
}
//Chat
//IO