fix, and animations

This commit is contained in:
jalvarado
2025-04-08 10:28:56 -06:00
parent e0ed64548a
commit 8d8299b71a
8 changed files with 419 additions and 493 deletions
+36 -29
View File
@@ -1,39 +1,46 @@
'use client';
import FormularioCard from '@/components/formulario-card';
import { GetCuestionario } from '@/types/cuestionario';
import React from 'react';
import React, { useEffect, useState } from 'react';
async function getEventos(): Promise<GetCuestionario[]> {
try {
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/cuestionario`, {
method: 'GET',
cache: 'no-store', // para que no lo cachee ni en build ni entre requests
headers: {
'Content-Type': 'application/json',
},
});
export default function Page() {
const [eventos, setEventos] = useState<GetCuestionario[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
if (!res.ok) {
throw new Error(`Error ${res.status}: ${res.statusText}`);
useEffect(() => {
async function getEventos() {
try {
const res = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/cuestionario`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}
);
if (!res.ok) {
throw new Error(`Error ${res.status}: ${res.statusText}`);
}
const data: GetCuestionario[] = await res.json();
setEventos(data);
} catch (err) {
console.error('Error en getEventos:', err);
setError('Error al cargar los eventos');
} finally {
setLoading(false);
}
}
const data: GetCuestionario[] = await res.json();
getEventos();
}, []);
return data;
} catch (error) {
console.error('Error en getEventos:', error);
throw error;
}
}
export default async function Page() {
let eventos;
try {
eventos = await getEventos();
} catch (error) {
console.error('Error al cargar los eventos:', error);
return <div>Error al cargar los eventos</div>;
}
if (loading) return <div>Cargando formularios...</div>;
if (error) return <div>{error}</div>;
return (
<>
+17 -13
View File
@@ -25,34 +25,38 @@ export default function Page() {
if (username === 'user-admin' && password === '@dm1nP@ss') {
Cookies.set('token', 'staff1');
router.push('/staff');
router.push('/administrador');
} else {
setError('Usuario o contraseña incorrectos');
}
};
return (
<div className='d-flex flex-column justify-content-center align-items-center'>
<h1 className='text-dorado'>Iniciar sesión</h1>
<h2 className='text-azul'>Administradores</h2>
<div className="d-flex flex-column justify-content-center align-items-center">
<h1 className="text-dorado">Iniciar sesión</h1>
<h2 className="text-azul">Administradores</h2>
<form className='w-300px' onSubmit={handleOnSubmit}>
<form className="w-300px" onSubmit={handleOnSubmit}>
<Input
name='username'
label='Usuario'
name="username"
label="Usuario"
value={formData.username}
onChange={handleChange}
/>
<Input
name='password'
type='password'
label='Contraseña'
name="password"
type="password"
label="Contraseña"
value={formData.password}
onChange={handleChange}
/>
{error && <div className='alert alert-danger text-center' role='alert'>{error}</div>}
<div className='mb-3'>
<Button className='w-100' type='submit'>
{error && (
<div className="alert alert-danger text-center" role="alert">
{error}
</div>
)}
<div className="mb-3">
<Button className="w-100" type="submit">
Iniciar sesión
</Button>
</div>
+53 -41
View File
@@ -1,71 +1,83 @@
'use client';
import ClientCarousel from '@/client-components/client-carousel';
import FormularioCard from '@/components/formulario-card';
import { GetCuestionario } from '@/types/cuestionario';
import React from 'react';
import { useEffect, useState } from 'react';
async function getEventos(): Promise<GetCuestionario[]> {
try {
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/cuestionario`, {
method: 'GET',
cache: 'no-store', // para que no lo cachee ni en build ni entre requests
headers: {
'Content-Type': 'application/json',
},
});
export default function Page() {
const [eventos, setEventos] = useState<GetCuestionario[]>([]);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
if (!res.ok) {
throw new Error(`Error ${res.status}: ${res.statusText}`);
useEffect(() => {
async function getEventos() {
try {
const res = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/cuestionario`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}
);
if (!res.ok) {
throw new Error(`Error ${res.status}: ${res.statusText}`);
}
const data: GetCuestionario[] = await res.json();
setEventos(data);
} catch (err) {
console.error('Error en getEventos:', err);
setError('No se pudieron cargar los cuestionarios.');
} finally {
setLoading(false);
}
}
const data: GetCuestionario[] = await res.json();
return data;
} catch (error) {
console.error('Error en getEventos:', error);
throw error;
}
}
export default async function Page() {
let eventos;
try {
eventos = await getEventos();
} catch (error) {
console.error('Error al cargar los eventos:', error);
return <div>Error al cargar los eventos</div>;
}
getEventos();
}, []);
function slugify(text: string): string {
return text
.toString()
.normalize('NFD') // Quita tildes
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase()
.trim()
.replace(/[^a-z0-9]+/g, '-') // Reemplaza cualquier cosa que no sea alfanumérica por guiones
.replace(/^-+|-+$/g, ''); // Quita guiones al inicio o final
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
}
if (loading) return <div>Cargando cuestionarios...</div>;
if (error) return <div>{error}</div>;
return (
<>
<div className="mx-auto my-5">
<div className="mx-auto my-5 fade-in-down-bounce">
<ClientCarousel />
</div>
<div className="cards-1 mt-5">
<div className="container">
<div className="row">
{eventos.map((cuestionario) => {
const slug = slugify(cuestionario.nombre_form); // Asumiendo que el nombre del evento está en `nombre`
{eventos.map((cuestionario, index) => {
const slug = slugify(cuestionario.nombre_form);
const url = `/registro/${slug}-${cuestionario.id_cuestionario}`;
const fadeClass = `delay-${(index % 5) + 1}`;
return (
<FormularioCard
<div
className={`col-md-4 my-4 fade-in-up-bounce ${fadeClass}`}
key={cuestionario.id_cuestionario}
cuestionario={cuestionario}
link={url}
button_message="Registrarse"
/>
>
<FormularioCard
cuestionario={cuestionario}
link={url}
button_message="Registrarse"
/>
</div>
);
})}
</div>
+39 -41
View File
@@ -29,50 +29,48 @@ export default function FormularioCard({
: descripcion;
return (
<div className="col-md-4 my-4">
<div className="card card-blog d-flex flex-column justify-content-between">
<div>
<div className="card-image">
<Link href={link}>
<Image
width={400}
height={300}
className="img"
src="/feriasex.jpg"
alt="Banner formulario"
/>
<div className="card-caption">{cuestionario.nombre_form}</div>
</Link>
<div className="ripple-cont"></div>
</div>
<div className="card card-blog d-flex flex-column justify-content-between">
<div>
<div className="card-image">
<Link href={link}>
<Image
width={400}
height={300}
className="img"
src="/feriasex.jpg"
alt="Banner formulario"
/>
<div className="card-caption">{cuestionario.nombre_form}</div>
</Link>
<div className="ripple-cont"></div>
</div>
<div className="table px-3 pt-3 mb-0">
<h6 className="category text-info">
{cuestionario.id_tipo_cuestionario === 1 ? 'Feria' : 'Evento'}
</h6>
<p className="card-description mb-0">
{descripcionRecortada}
{descripcion.length > limite && (
<button
onClick={() => setVerMas(!verMas)}
className="btn btn-link btn-sm p-0 ms-1 align-baseline"
style={{ fontSize: '0.875rem' }}
>
{verMas ? 'Ver menos' : 'Ver más'}
</button>
)}
</p>
{button_message && (
<Button
onClick={() => router.push(link)}
outline
className="mt-3 w-100 py-1 rounded"
variant="primary"
<div className="table px-3 pt-3 mb-0">
<h6 className="category text-info">
{cuestionario.id_tipo_cuestionario === 1 ? 'Feria' : 'Evento'}
</h6>
<p className="card-description mb-0">
{descripcionRecortada}
{descripcion.length > limite && (
<button
onClick={() => setVerMas(!verMas)}
className="btn btn-link btn-sm p-0 ms-1 align-baseline"
style={{ fontSize: '0.875rem' }}
>
{button_message}
</Button>
{verMas ? 'Ver menos' : 'Ver más'}
</button>
)}
</div>
</p>
{button_message && (
<Button
onClick={() => router.push(link)}
outline
className="mt-3 w-100 py-1 rounded"
variant="primary"
>
{button_message}
</Button>
)}
</div>
</div>
</div>
+161
View File
@@ -0,0 +1,161 @@
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeInScale {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes slideInLeft {
from {
opacity: 0;
transform: translateX(-30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes fadeInRotate {
from {
opacity: 0;
transform: rotate(-5deg) scale(0.95);
}
to {
opacity: 1;
transform: rotate(0) scale(1);
}
}
@keyframes fadeInUpBounce {
0% {
opacity: 0;
transform: translateY(20px);
}
60% {
opacity: 1;
transform: translateY(-5px); // se pasa ligeramente hacia arriba
}
80% {
transform: translateY(3px); // rebota hacia abajo
}
100% {
transform: translateY(0); // posición final
}
}
@keyframes fadeInDownBounce {
0% {
opacity: 0;
transform: translateY(-20px);
}
60% {
opacity: 1;
transform: translateY(5px); // se pasa un poco hacia abajo
}
80% {
transform: translateY(-3px); // rebota hacia arriba
}
100% {
transform: translateY(0); // se estabiliza
}
}
// Clase base para aplicar la animación
.fade-in {
animation: fadeIn 1s ease-in-out;
animation-fill-mode: both;
}
.fade-in-up {
animation: fadeInUp 0.8s ease-out;
animation-fill-mode: both;
}
.fade-in-down {
animation: fadeInDown 0.8s ease-out;
animation-fill-mode: both;
}
.fade-in-scale {
animation: fadeInScale 0.8s ease-out;
animation-fill-mode: both;
}
.slide-in-left {
animation: slideInLeft 0.8s ease-out;
animation-fill-mode: both;
}
.slide-in-right {
animation: slideInRight 0.8s ease-out;
animation-fill-mode: both;
}
.fade-in-rotate {
animation: fadeInRotate 0.8s ease-out;
animation-fill-mode: both;
}
.fade-in-up-bounce {
animation: fadeInUpBounce 0.8s ease-out;
animation-fill-mode: both;
}
.fade-in-down-bounce {
animation: fadeInDownBounce 0.8s ease-out;
animation-fill-mode: both;
}
// Variantes con delays más suaves
@for $i from 1 through 5 {
.delay-#{$i} {
animation-delay: #{($i * 0.3)}s;
}
}
+1
View File
@@ -27,6 +27,7 @@ $theme-colors: map-merge($theme-colors, $custom-colors);
@import 'sidebar';
@import 'hover';
@import 'tabs';
@import 'animation';
//Add additional custom code here