Enhance Login and Register components with new UI elements and improved layout. Added demo visualization in Login and refined registration flow in Register, including error handling and user guidance.

This commit is contained in:
Alexis
2026-05-28 12:37:07 +02:00
parent 2c400543e9
commit f4d46080a6
2 changed files with 277 additions and 54 deletions
+208 -3
View File
@@ -3,7 +3,195 @@ 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 { API_BASE_URL } from '../config'; import { API_BASE_URL } from '../config';
import { FiEye, FiEyeOff } from 'react-icons/fi'; import { FiArrowLeft, FiEye, FiEyeOff } from 'react-icons/fi';
import { ComposedChart, Line, XAxis, YAxis, CartesianGrid, ReferenceArea, ReferenceLine, ResponsiveContainer } from 'recharts';
const DEMO_TERMS = [
{ name: 'Muy Bajo', xVal: 0.10, mf: { supportStart: 0.00, coreStart: 0.00, coreEnd: 0.10, supportEnd: 0.22 } },
{ name: 'Bajo', xVal: 0.28, mf: { supportStart: 0.12, coreStart: 0.22, coreEnd: 0.33, supportEnd: 0.44 } },
{ name: 'Medio', xVal: 0.50, mf: { supportStart: 0.35, coreStart: 0.44, coreEnd: 0.56, supportEnd: 0.65 } },
{ name: 'Alto', xVal: 0.72, mf: { supportStart: 0.56, coreStart: 0.67, coreEnd: 0.78, supportEnd: 0.88 } },
{ name: 'Muy Alto', xVal: 0.90, mf: { supportStart: 0.78, coreStart: 0.90, coreEnd: 1.00, supportEnd: 1.00 } },
];
const DEMO_COLORS = ['#ef4444', '#f59e0b', '#10b981', '#3b82f6', '#d946ef'];
function FakeDemoPanel() {
const [visibleCount, setVisibleCount] = useState(0);
const [fading, setFading] = useState(false);
useEffect(() => {
let timeout;
if (visibleCount < DEMO_TERMS.length) {
timeout = setTimeout(() => setVisibleCount(v => v + 1), 1300);
} else {
timeout = setTimeout(() => {
setFading(true);
setTimeout(() => {
setVisibleCount(0);
setFading(false);
}, 500);
}, 2400);
}
return () => clearTimeout(timeout);
}, [visibleCount]);
const visibleTerms = DEMO_TERMS.slice(0, visibleCount);
const activeIndex = visibleCount - 1;
return (
<div
className="mt-6 w-full flex flex-col gap-3 transition-opacity duration-500"
style={{ opacity: fading ? 0 : 1 }}
>
{/* Mini header badge */}
<div className="flex items-center gap-2">
<span className="text-[10px] font-black uppercase tracking-widest text-slate-400">Paso 2</span>
<span className="text-[10px] text-slate-300">·</span>
<span className="text-[10px] font-semibold text-slate-400">Modelar Conceptos Difusos</span>
<span className="ml-auto flex items-center gap-1">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse" />
<span className="text-[10px] text-emerald-500 font-bold">En vivo</span>
</span>
</div>
{/* Term pills */}
<div className="flex flex-wrap gap-1.5">
{DEMO_TERMS.map((term, index) => {
const color = DEMO_COLORS[index % DEMO_COLORS.length];
const isVisible = index < visibleCount;
const isActive = index === activeIndex;
return (
<span
key={term.name}
className="px-3 py-1 rounded-lg text-[11px] font-bold border-2 transition-all duration-500"
style={
isActive
? { backgroundColor: color, borderColor: color, color: '#fff', transform: 'scale(1.08)' }
: isVisible
? { borderColor: color, color: '#475569', backgroundColor: 'white', opacity: 0.85 }
: { borderColor: '#e2e8f0', color: '#cbd5e1', backgroundColor: 'white' }
}
>
{term.name}
</span>
);
})}
</div>
{/* Chart */}
<div
className="w-full rounded-2xl border border-slate-200 p-2 transition-all duration-300"
style={{ backgroundColor: '#f8fafc' }}
>
<ResponsiveContainer width="99%" height={188}>
<ComposedChart margin={{ top: 14, right: 18, left: 0, bottom: 6 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#e2e8f0" />
<XAxis
type="number"
dataKey="x"
domain={[0, 1]}
ticks={[0, 0.25, 0.5, 0.75, 1]}
tick={{ fill: '#94a3b8', fontSize: 10, fontWeight: 600 }}
/>
<YAxis
domain={[0, 1]}
ticks={[0, 0.5, 1]}
tick={{ fill: '#94a3b8', fontSize: 10 }}
width={26}
/>
{visibleTerms.map((term, index) => {
const color = DEMO_COLORS[index % DEMO_COLORS.length];
const isActive = index === activeIndex;
return (
<ReferenceLine
key={`ref-${term.name}`}
x={term.xVal}
stroke={color}
strokeDasharray="4 4"
strokeWidth={isActive ? 2 : 1}
label={{ position: 'top', value: term.name, fill: color, fontWeight: isActive ? '900' : '600', fontSize: 10 }}
/>
);
})}
{visibleTerms.map((term, index) => {
const color = DEMO_COLORS[index % DEMO_COLORS.length];
const isActive = index === activeIndex;
return (
<ReferenceArea
key={`area-${term.name}`}
x1={term.mf.supportStart}
x2={term.mf.supportEnd}
fill={color}
fillOpacity={isActive ? 0.22 : 0.07}
/>
);
})}
{visibleTerms.map((term, index) => {
const color = DEMO_COLORS[index % DEMO_COLORS.length];
const isActive = index === activeIndex;
const trapezeData = [
{ x: term.mf.supportStart, y: 0 },
{ x: term.mf.coreStart, y: 1 },
{ x: term.mf.coreEnd, y: 1 },
{ x: term.mf.supportEnd, y: 0 },
];
return (
<Line
key={`line-${term.name}`}
data={trapezeData}
dataKey="y"
type="linear"
stroke={color}
strokeWidth={isActive ? 3 : 2}
dot={isActive ? { r: 4, fill: color, stroke: '#fff', strokeWidth: 2 } : false}
activeDot={false}
isAnimationActive={isActive}
animationDuration={550}
animationEasing="ease-out"
/>
);
})}
</ComposedChart>
</ResponsiveContainer>
</div>
{/* Terms table */}
<div className="w-full rounded-xl border border-slate-200 bg-white/80 overflow-hidden">
<div className="px-4 py-2 border-b border-slate-100 flex items-center justify-between bg-slate-50/60">
<span className="text-[10px] font-black uppercase tracking-widest text-slate-400">Términos modelados</span>
<span className="text-[10px] font-bold text-blue-500">{visibleCount} / {DEMO_TERMS.length}</span>
</div>
<div className="divide-y divide-slate-100">
{DEMO_TERMS.map((term, index) => {
const isVisible = index < visibleCount;
const isActive = index === activeIndex;
const color = DEMO_COLORS[index % DEMO_COLORS.length];
return (
<div
key={term.name}
className={`flex items-center px-4 py-2 transition-all duration-500 ${isActive ? 'bg-blue-50/60' : ''}`}
style={{ opacity: isVisible ? 1 : 0 }}
>
<span className="w-2 h-2 rounded-full mr-3 shrink-0 transition-all duration-300" style={{ backgroundColor: isVisible ? color : '#e2e8f0', transform: isActive ? 'scale(1.4)' : 'scale(1)' }} />
<span className={`text-xs flex-1 transition-all duration-300 ${isActive ? 'font-black text-slate-800' : 'font-semibold text-slate-500'}`}>
{term.name}
</span>
{isVisible && (
<span className="text-[11px] text-slate-400 font-mono">
[{term.mf.coreStart.toFixed(2)},&nbsp;{term.mf.coreEnd.toFixed(2)}]
</span>
)}
{isActive && (
<span className="ml-2 w-1.5 h-1.5 rounded-full bg-blue-400 animate-pulse" />
)}
</div>
);
})}
</div>
</div>
</div>
);
}
export default function Login() { export default function Login() {
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
@@ -71,9 +259,25 @@ export default function Login() {
}; };
return ( return (
<div className="flex-1 flex items-center justify-center py-4"> <div className="w-full flex items-start justify-center">
<div className="max-w-md w-full bg-white p-10 rounded-3xl shadow-sm border border-slate-200"> <div className="w-full grid gap-6 lg:gap-8 lg:grid-cols-[minmax(0,1fr)_26rem]">
<div className="hidden lg:flex flex-col justify-start rounded-3xl border border-blue-100 bg-linear-to-br from-blue-50 via-indigo-50 to-sky-50 p-10 self-stretch">
<p className="text-xs font-black uppercase tracking-[0.2em] text-blue-500">Deck of Cards</p>
<h1 className="mt-4 text-4xl font-black tracking-tight text-slate-800">Modela y compara de forma visual</h1>
<p className="mt-3 text-slate-500 text-sm leading-relaxed">
Construye funciones de pertenencia difusa, guarda tu historial y vuelve a trabajar donde lo dejaste.
</p>
<Link
to="/editor"
className="mt-6 inline-flex w-fit items-center rounded-xl bg-slate-900 px-5 py-3 text-sm font-bold text-white transition-colors hover:bg-slate-800"
>
<FiArrowLeft className="mr-2 h-4 w-4" />
Ir al editor principal
</Link>
<FakeDemoPanel />
</div>
<div className="w-full bg-white p-8 sm:p-10 rounded-3xl shadow-sm border border-slate-200">
<div className="text-center mb-8"> <div className="text-center mb-8">
<h2 className="text-3xl font-black text-slate-800 tracking-tight">Deck of Cards</h2> <h2 className="text-3xl font-black text-slate-800 tracking-tight">Deck of Cards</h2>
<p className="text-slate-500 mt-2">Accede a tu historial y gráficas guardadas</p> <p className="text-slate-500 mt-2">Accede a tu historial y gráficas guardadas</p>
@@ -136,5 +340,6 @@ export default function Login() {
<p className="mt-8 text-center text-sm text-slate-500 font-medium">¿Nuevo por aquí? <Link to="/register" className="text-blue-600 hover:underline font-extrabold">Crea una cuenta</Link></p> <p className="mt-8 text-center text-sm text-slate-500 font-medium">¿Nuevo por aquí? <Link to="/register" className="text-blue-600 hover:underline font-extrabold">Crea una cuenta</Link></p>
</div> </div>
</div> </div>
</div>
); );
} }
+21 -3
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 { FiEye, FiEyeOff } from 'react-icons/fi'; import { FiArrowLeft, FiEye, FiEyeOff } from 'react-icons/fi';
export default function Register() { export default function Register() {
const [username, setUsername] = useState(''); const [username, setUsername] = useState('');
@@ -95,9 +95,26 @@ export default function Register() {
}; };
return ( return (
<div className="flex-1 flex items-center justify-center py-4"> <div className="w-full flex items-start justify-center">
<div className="max-w-md w-full bg-white p-10 rounded-3xl shadow-sm border border-slate-200"> <div className="w-full grid gap-6 lg:gap-8 lg:grid-cols-[minmax(0,1fr)_26rem]">
<div className="hidden lg:flex flex-col justify-center rounded-3xl border border-indigo-100 bg-linear-to-br from-indigo-50 via-violet-50 to-blue-50 p-10">
<p className="text-xs font-black uppercase tracking-[0.2em] text-indigo-500">Deck of Cards</p>
<h1 className="mt-4 text-4xl font-black tracking-tight text-slate-800">
Crea tu cuenta y guarda cada modelo
</h1>
<p className="mt-4 text-slate-600 leading-relaxed">
Registra tus criterios, conserva resultados en el historial y retoma tus análisis cuando quieras.
</p>
<Link
to="/editor"
className="mt-8 inline-flex w-fit items-center rounded-xl bg-slate-900 px-5 py-3 text-sm font-bold text-white transition-colors hover:bg-slate-800"
>
<FiArrowLeft className="mr-2 h-4 w-4" />
Ir al editor principal
</Link>
</div>
<div className="w-full bg-white p-8 sm:p-10 rounded-3xl shadow-sm border border-slate-200">
<div className="text-center mb-8"> <div className="text-center mb-8">
<h2 className="text-3xl font-black text-slate-800 tracking-tight"> <h2 className="text-3xl font-black text-slate-800 tracking-tight">
{verificationRequired ? 'Verifica tu email' : 'Crear Cuenta'} {verificationRequired ? 'Verifica tu email' : 'Crear Cuenta'}
@@ -238,5 +255,6 @@ export default function Register() {
</p> </p>
</div> </div>
</div> </div>
</div>
); );
} }