fix: arreglar bug de recarga cuando login con credenciales incorrectas. arreglar espaciados y estilos específicos.

Cambiar emojis por iconos para mantener coherencia visual. Eliminado SVG de iconos de ojos para la contraseña e implementados iconos de la misma librería.
This commit is contained in:
Alexis
2026-04-15 11:03:08 +02:00
parent ecc82011ea
commit 0c1d446139
9 changed files with 45 additions and 36 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ export default function CardEditor({ index, level, handleLevelChange, handleRemo
<span className="absolute bottom-3 right-4 text-sm font-black text-slate-300 rotate-180">{index + 1}</span> <span className="absolute bottom-3 right-4 text-sm font-black text-slate-300 rotate-180">{index + 1}</span>
<input type="text" placeholder="Término..." value={level} onChange={(e) => handleLevelChange(index, e.target.value)} className={`w-10/12 text-center text-lg font-bold text-slate-700 bg-transparent border-b-2 border-dashed outline-none pb-1 ${error ? 'border-red-300 focus:border-red-500 placeholder:text-red-200' : 'border-slate-300 focus:border-blue-500'}`} /> <input type="text" placeholder="Término..." value={level} onChange={(e) => handleLevelChange(index, e.target.value)} className={`w-10/12 text-center text-lg font-bold text-slate-700 bg-transparent border-b-2 border-dashed outline-none pb-1 ${error ? 'border-red-300 focus:border-red-500 placeholder:text-red-200' : 'border-slate-300 focus:border-blue-500'}`} />
</div> </div>
<div className="h-6 mt-2">{error && <p className="text-red-500 text-xs font-semibold animate-pulse">Escribe un término</p>}</div> <div className="h-6 mt-2">{error && <p className="text-red-500 text-xs font-semibold">Escribe un término</p>}</div>
</div> </div>
); );
} }
+3 -3
View File
@@ -1,6 +1,6 @@
export default function CriterionInput({ criterionName, setCriterionName, error }) { export default function CriterionInput({ criterionName, setCriterionName, error }) {
return ( return (
<div className="flex flex-row items-center justify-center gap-3 w-full z-30 relative mb-2"> <div className="flex flex-row items-center justify-center gap-3 w-full z-30 relative mt-4">
<label className="text-sm font-bold text-slate-600 uppercase tracking-wide whitespace-nowrap"> <label className="text-sm font-bold text-slate-600 uppercase tracking-wide whitespace-nowrap">
Nombre del Criterio: Nombre del Criterio:
</label> </label>
@@ -19,8 +19,8 @@ export default function CriterionInput({ criterionName, setCriterionName, error
/> />
{error && ( {error && (
<span className="absolute top-1/2 -right-24 -translate-y-1/2 text-xs font-bold text-red-500 animate-pulse whitespace-nowrap"> <span className="absolute top-1/2 -right-18 -translate-y-1/2 text-red-500 text-xs font-semibold">
* Obligatorio Obligatorio
</span> </span>
)} )}
</div> </div>
-12
View File
@@ -1,12 +0,0 @@
export default function EyeIcon({ isOpen }) {
return isOpen ? (
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor" className="w-5 h-5">
<path strokeLinecap="round" strokeLinejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" />
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
) : (
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor" className="w-5 h-5">
<path strokeLinecap="round" strokeLinejoin="round" d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88" />
</svg>
);
}
@@ -3,6 +3,7 @@ import CriterionInput from '../CriterionInput';
import CardEditor from '../CardEditor'; import CardEditor from '../CardEditor';
import BlankCardsCounter from '../BlankCardsCounter'; import BlankCardsCounter from '../BlankCardsCounter';
import AddLevelButton from '../AddLevelButton'; import AddLevelButton from '../AddLevelButton';
import { FiZoomIn, FiMaximize } from 'react-icons/fi';
export default function Step1BaseScale({ export default function Step1BaseScale({
criterionName, handleCriterionChange, criterionName, handleCriterionChange,
@@ -51,15 +52,15 @@ export default function Step1BaseScale({
}} }}
className={`flex items-center gap-2 px-3 py-1.5 rounded-lg font-bold transition-all shadow-sm border text-sm ${isZoomActive ? 'bg-blue-50 border-blue-200 text-blue-700' : 'bg-white border-slate-200 text-slate-600'}`} className={`flex items-center gap-2 px-3 py-1.5 rounded-lg font-bold transition-all shadow-sm border text-sm ${isZoomActive ? 'bg-blue-50 border-blue-200 text-blue-700' : 'bg-white border-slate-200 text-slate-600'}`}
> >
<span>{isZoomActive ? '🔍' : '🖼️'}</span> <span>{isZoomActive ? <FiZoomIn className="w-4 h-4" strokeWidth={2.5} /> : <FiMaximize className="w-4 h-4" strokeWidth={2.5}></FiMaximize> }</span>
{isZoomActive ? 'Ver de cerca (Scroll)' : 'Ajustar mesa'} {isZoomActive ? 'Ver de cerca' : 'Ajustar mesa'}
</button> </button>
)} )}
</div> </div>
<CriterionInput criterionName={criterionName} setCriterionName={handleCriterionChange} error={errors.criterion} /> <CriterionInput criterionName={criterionName} setCriterionName={handleCriterionChange} error={errors.criterion} />
<div ref={containerRef} className={`w-full mt-2 transition-all relative ${!isZoomActive && needsZoom ? 'overflow-x-auto flex justify-start pb-12 pt-4 px-4 custom-scrollbar' : 'overflow-visible flex justify-center pb-12 pt-4'}`}> <div ref={containerRef} className={`w-full mt-2 transition-all relative ${!isZoomActive && needsZoom ? 'overflow-x-auto flex justify-start pt-4 px-4 custom-scrollbar' : 'overflow-visible flex justify-center pt-4'}`}>
<div className={`flex flex-row items-start min-w-max transition-transform duration-500 ease-out px-4 origin-top`} style={{ transform: `scale(${currentScale})`, marginBottom: isZoomActive && currentScale < 1 ? `-${(1 - currentScale) * 300}px` : '0px' }}> <div className={`flex flex-row items-start min-w-max transition-transform duration-500 ease-out px-4 origin-top`} style={{ transform: `scale(${currentScale})`, marginBottom: isZoomActive && currentScale < 1 ? `-${(1 - currentScale) * 300}px` : '0px' }}>
<div ref={tableRef} className="flex flex-row items-start relative px-10 overflow-visible"> <div ref={tableRef} className="flex flex-row items-start relative px-10 overflow-visible">
@@ -100,7 +101,7 @@ export default function Step1BaseScale({
</div> </div>
{/* Generar Gráfica Continua */} {/* Generar Gráfica Continua */}
<div className="w-full max-w-lg mt-8 pt-6 border-t border-slate-200 flex flex-col items-center z-20 relative bg-white"> <div className="w-full max-w-lg mt-6 mb-2 pt-6 border-t border-slate-200 flex flex-col items-center z-20 relative bg-white">
<button onClick={handleGenerateBaseScale} disabled={isLoading} className={`w-full py-3 text-white text-lg font-bold rounded-xl shadow-md transition-all active:scale-[0.98] ${isLoading ? 'bg-slate-400 cursor-not-allowed' : 'bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700'}`}> <button onClick={handleGenerateBaseScale} disabled={isLoading} className={`w-full py-3 text-white text-lg font-bold rounded-xl shadow-md transition-all active:scale-[0.98] ${isLoading ? 'bg-slate-400 cursor-not-allowed' : 'bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700'}`}>
{isLoading ? 'Calculando...' : 'Generar Gráfica Continua'} {isLoading ? 'Calculando...' : 'Generar Gráfica Continua'}
</button> </button>
+1 -1
View File
@@ -1,6 +1,6 @@
export default function Footer() { export default function Footer() {
return ( return (
<footer className="bg-white border-t border-slate-200 mt-auto shrink-0 w-full pt-12 pb-6"> <footer className="bg-white border-t border-slate-200 mt-auto shrink-0 w-full pt-8 pb-8">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-12 gap-8 lg:gap-6"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-12 gap-8 lg:gap-6">
+6
View File
@@ -22,12 +22,18 @@ api.interceptors.response.use(
return response; return response;
}, },
(error) => { (error) => {
// Si es un error 401 (No autorizado)
if (error.response && error.response.status === 401) { if (error.response && error.response.status === 401) {
localStorage.removeItem('token'); localStorage.removeItem('token');
localStorage.removeItem('user'); localStorage.removeItem('user');
// SOLUCIÓN: Solo recargamos y redirigimos si NO estamos ya en /login
if (window.location.pathname !== '/login') {
window.location.href = '/login'; window.location.href = '/login';
} }
}
// Propagamos el error para que los componentes puedan leer backendData
if (error.response && error.response.data) { if (error.response && error.response.data) {
return Promise.reject({ return Promise.reject({
...error, ...error,
+7 -5
View File
@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { getUserHistory, deleteHistoryItem } from '../services/docService'; import { getUserHistory, deleteHistoryItem } from '../services/docService';
import Step3FinalGraph from '../components/editor/Step3FinalGraph'; import Step3FinalGraph from '../components/editor/Step3FinalGraph';
import { FiEye, FiTrash2, FiBarChart2, FiInbox, FiClock } from 'react-icons/fi';
export default function History() { export default function History() {
const [historyItems, setHistoryItems] = useState([]); const [historyItems, setHistoryItems] = useState([]);
@@ -64,12 +65,13 @@ export default function History() {
{/* Lista de Historial */} {/* Lista de Historial */}
{isLoading ? ( {isLoading ? (
<div className="bg-white p-12 rounded-3xl shadow-sm border border-slate-200 flex flex-col items-center justify-center text-slate-400 border-dashed"> <div className="bg-white p-12 rounded-3xl shadow-sm border border-slate-200 flex flex-col items-center justify-center text-slate-400 border-dashed">
<div className="animate-spin text-4xl mb-4"></div> <div className="text-6xl"><FiClock className="w-16 h-16 mb-4 opacity-50" strokeWidth={1.5} /></div>
<p className="font-medium">Cargando tus gráficas...</p> <p className="font-medium text-lg">Cargando tus gráficas...</p>
</div> </div>
) : historyItems.length === 0 ? ( ) : historyItems.length === 0 ? (
<div className="bg-white p-12 rounded-3xl shadow-sm border border-slate-200 flex flex-col items-center justify-center text-slate-400 border-dashed"> <div className="bg-white p-12 rounded-3xl shadow-sm border border-slate-200 flex flex-col items-center justify-center text-slate-400 border-dashed">
<span className="text-6xl mb-4">📭</span> <span className="text-6xl"><FiInbox className="w-16 h-16 mb-4 opacity-50" strokeWidth={1.5} /></span>
<p className="font-medium text-lg">Aún no has guardado ningún modelo.</p> <p className="font-medium text-lg">Aún no has guardado ningún modelo.</p>
<p className="text-sm mt-2">Ve al editor, crea una gráfica y dale a "Guardar".</p> <p className="text-sm mt-2">Ve al editor, crea una gráfica y dale a "Guardar".</p>
</div> </div>
@@ -86,7 +88,7 @@ export default function History() {
<div className="p-6 flex flex-col sm:flex-row justify-between items-center gap-4 bg-slate-50/50 rounded-t-2xl"> <div className="p-6 flex flex-col sm:flex-row justify-between items-center gap-4 bg-slate-50/50 rounded-t-2xl">
<div className="flex items-center gap-4 w-full sm:w-auto"> <div className="flex items-center gap-4 w-full sm:w-auto">
<div className="w-12 h-12 bg-blue-100 text-blue-600 rounded-full flex items-center justify-center text-xl shadow-inner"> <div className="w-12 h-12 bg-blue-100 text-blue-600 rounded-full flex items-center justify-center text-xl shadow-inner">
📊 <FiBarChart2 className="w-5 h-5" strokeWidth={2.5} />
</div> </div>
<div> <div>
<h3 className="text-xl font-bold text-slate-800">{item.name || 'Modelo sin título'}</h3> <h3 className="text-xl font-bold text-slate-800">{item.name || 'Modelo sin título'}</h3>
@@ -111,7 +113,7 @@ export default function History() {
className="px-4 py-2.5 bg-white border border-red-200 text-red-500 font-bold rounded-xl hover:bg-red-50 transition-colors shadow-sm" className="px-4 py-2.5 bg-white border border-red-200 text-red-500 font-bold rounded-xl hover:bg-red-50 transition-colors shadow-sm"
title="Borrar modelo" title="Borrar modelo"
> >
Borrar <FiTrash2 className="w-5 h-5" strokeWidth={2.5} />
</button> </button>
</div> </div>
</div> </div>
+7 -3
View File
@@ -2,7 +2,7 @@ import { useState, useEffect, useRef } from 'react';
import { Link, useNavigate, useSearchParams } from 'react-router-dom'; import { Link, useNavigate, useSearchParams } from 'react-router-dom';
import { useAuth } from '../context/AuthContext'; import { useAuth } from '../context/AuthContext';
import { authService } from '../services/authService'; import { authService } from '../services/authService';
import EyeIcon from '../components/EyeIcon'; import { FiEye, FiEyeOff } from 'react-icons/fi';
export default function Login() { export default function Login() {
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
@@ -70,7 +70,7 @@ export default function Login() {
}; };
return ( return (
<div className="flex-1 flex items-center justify-center"> <div className="flex-1 flex items-center justify-center py-4">
<div className="max-w-md w-full bg-white p-10 rounded-3xl shadow-sm border border-slate-200"> <div className="max-w-md w-full bg-white p-10 rounded-3xl shadow-sm border border-slate-200">
<div className="text-center mb-8"> <div className="text-center mb-8">
@@ -108,7 +108,11 @@ export default function Login() {
onClick={() => setShowPassword(!showPassword)} onClick={() => setShowPassword(!showPassword)}
className="absolute right-4 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 transition-colors focus:outline-none" className="absolute right-4 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 transition-colors focus:outline-none"
> >
<EyeIcon isOpen={showPassword} /> {showPassword ? (
<FiEye className="w-5 h-5" strokeWidth={2} />
) : (
<FiEyeOff className="w-5 h-5" strokeWidth={2} />
)}
</button> </button>
</div> </div>
</div> </div>
+13 -5
View File
@@ -2,7 +2,7 @@ import { useState } from 'react';
import { useNavigate, Link } from 'react-router-dom'; import { useNavigate, Link } from 'react-router-dom';
import { useAuth } from '../context/AuthContext'; import { useAuth } from '../context/AuthContext';
import { authService } from '../services/authService'; import { authService } from '../services/authService';
import EyeIcon from '../components/EyeIcon'; import { FiEye, FiEyeOff } from 'react-icons/fi';
export default function Register() { export default function Register() {
const [username, setUsername] = useState(''); const [username, setUsername] = useState('');
@@ -37,7 +37,7 @@ export default function Register() {
}; };
return ( return (
<div className="flex-1 flex items-center justify-center"> <div className="flex-1 flex items-center justify-center py-4">
<div className="max-w-md w-full bg-white p-10 rounded-3xl shadow-sm border border-slate-200"> <div className="max-w-md w-full bg-white p-10 rounded-3xl shadow-sm border border-slate-200">
<div className="text-center mb-8"> <div className="text-center mb-8">
@@ -58,7 +58,7 @@ export default function Register() {
type="text" required autoComplete="username" type="text" required autoComplete="username"
className="w-full px-5 py-3 rounded-2xl border border-slate-200 focus:ring-2 focus:ring-blue-500 outline-none transition-all bg-slate-50 focus:bg-white" className="w-full px-5 py-3 rounded-2xl border border-slate-200 focus:ring-2 focus:ring-blue-500 outline-none transition-all bg-slate-50 focus:bg-white"
value={username} onChange={(e) => setUsername(e.target.value)} value={username} onChange={(e) => setUsername(e.target.value)}
placeholder="Ej: usuario99" placeholder="Ej: alexis99"
/> />
</div> </div>
@@ -86,7 +86,11 @@ export default function Register() {
type="button" onClick={() => setShowPassword(!showPassword)} type="button" onClick={() => setShowPassword(!showPassword)}
className="absolute right-4 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 transition-colors focus:outline-none" className="absolute right-4 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 transition-colors focus:outline-none"
> >
<EyeIcon isOpen={showPassword} /> {showPassword ? (
<FiEye className="w-5 h-5" strokeWidth={2} />
) : (
<FiEyeOff className="w-5 h-5" strokeWidth={2} />
)}
</button> </button>
</div> </div>
</div> </div>
@@ -105,7 +109,11 @@ export default function Register() {
type="button" onClick={() => setShowConfirmPassword(!showConfirmPassword)} type="button" onClick={() => setShowConfirmPassword(!showConfirmPassword)}
className="absolute right-4 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 transition-colors focus:outline-none" className="absolute right-4 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 transition-colors focus:outline-none"
> >
<EyeIcon isOpen={showConfirmPassword} /> {showConfirmPassword ? (
<FiEye className="w-5 h-5" strokeWidth={2} />
) : (
<FiEyeOff className="w-5 h-5" strokeWidth={2} />
)}
</button> </button>
</div> </div>
</div> </div>