Enhance ProfilePage with editable user profile and local storage support
Added functionality to edit and save user profiles in `ProfilePage`. Introduced local storage integration to persist updates. Implemented a modal for profile updates, including basic validations and password change simulation. Improved UI to dynamically render profile fields and enable updates.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import '../../styles/components.css';
|
import '../../styles/components.css';
|
||||||
|
|
||||||
const ProfileRow = ({ label, value, icon }) => (
|
const ProfileRow = ({ label, value, icon }) => (
|
||||||
@@ -15,14 +15,103 @@ const ProfileRow = ({ label, value, icon }) => (
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const storageKey = 'user.profile.v1';
|
||||||
|
|
||||||
|
const loadProfile = () => {
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem(storageKey);
|
||||||
|
if (!raw) return null;
|
||||||
|
return JSON.parse(raw);
|
||||||
|
} catch (_e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveProfile = (profile) => {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(storageKey, JSON.stringify(profile));
|
||||||
|
} catch (_e) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const computeInitials = (firstName = '', lastName = '') => {
|
||||||
|
const a = (firstName || '').trim().charAt(0).toUpperCase();
|
||||||
|
const b = (lastName || '').trim().charAt(0).toUpperCase();
|
||||||
|
return `${a}${b || ''}` || 'U';
|
||||||
|
};
|
||||||
|
|
||||||
const ProfilePage = () => {
|
const ProfilePage = () => {
|
||||||
// En esta versión demo, la información del usuario es estática.
|
const defaultProfile = useMemo(() => ({
|
||||||
// En una integración real, estos datos vendrían del backend o del contexto de autenticación.
|
username: 'jperez',
|
||||||
const user = {
|
firstName: 'Juan',
|
||||||
initials: 'JP',
|
lastName: 'Pérez',
|
||||||
name: 'Juan Pérez',
|
|
||||||
role: 'Administrador',
|
|
||||||
email: 'juan.perez@empresa.com',
|
email: 'juan.perez@empresa.com',
|
||||||
|
role: 'Administrador',
|
||||||
|
}), []);
|
||||||
|
|
||||||
|
const [profile, setProfile] = useState(() => loadProfile() || defaultProfile);
|
||||||
|
const [showModal, setShowModal] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
saveProfile(profile);
|
||||||
|
}, [profile]);
|
||||||
|
|
||||||
|
const initials = computeInitials(profile.firstName, profile.lastName);
|
||||||
|
|
||||||
|
const [form, setForm] = useState({
|
||||||
|
username: profile.username,
|
||||||
|
firstName: profile.firstName,
|
||||||
|
lastName: profile.lastName,
|
||||||
|
password: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (showModal) {
|
||||||
|
setForm({
|
||||||
|
username: profile.username,
|
||||||
|
firstName: profile.firstName,
|
||||||
|
lastName: profile.lastName,
|
||||||
|
password: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [showModal, profile]);
|
||||||
|
|
||||||
|
const openModal = () => setShowModal(true);
|
||||||
|
const closeModal = () => setShowModal(false);
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
setForm((prev) => ({ ...prev, [name]: value }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Validaciones básicas
|
||||||
|
if (!form.username.trim() || !form.firstName.trim() || !form.lastName.trim()) {
|
||||||
|
alert('Por favor, completa los campos: usuario, nombre y apellido.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (form.password && form.password.length < 6) {
|
||||||
|
alert('La contraseña debe tener al menos 6 caracteres.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// En una app real, aquí llamarías a la API para actualizar perfil y contraseña.
|
||||||
|
setProfile((prev) => ({
|
||||||
|
...prev,
|
||||||
|
username: form.username.trim(),
|
||||||
|
firstName: form.firstName.trim(),
|
||||||
|
lastName: form.lastName.trim(),
|
||||||
|
// La contraseña NO se guarda en localStorage; solo se simula el flujo.
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (form.password) {
|
||||||
|
// Simulación de actualización de contraseña
|
||||||
|
console.log('Contraseña actualizada (simulado).');
|
||||||
|
}
|
||||||
|
|
||||||
|
setShowModal(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -33,12 +122,17 @@ const ProfilePage = () => {
|
|||||||
<i className="fas fa-user" style={{ marginRight: 8 }}></i>
|
<i className="fas fa-user" style={{ marginRight: 8 }}></i>
|
||||||
Perfil de usuario
|
Perfil de usuario
|
||||||
</h3>
|
</h3>
|
||||||
|
<div>
|
||||||
|
<button className="btn btn-primary" onClick={openModal}>
|
||||||
|
<i className="fas fa-edit"></i> Actualizar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
||||||
<div className="user-avatar" aria-hidden="true">{user.initials}</div>
|
<div className="user-avatar" aria-hidden="true">{initials}</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="user-name" style={{ margin: 0 }}>{user.name}</div>
|
<div className="user-name" style={{ margin: 0 }}>{profile.firstName} {profile.lastName}</div>
|
||||||
<div className="user-role" style={{ margin: 0 }}>{user.role}</div>
|
<div className="user-role" style={{ margin: 0 }}>{profile.role}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -51,11 +145,108 @@ const ProfilePage = () => {
|
|||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<ProfileRow label="Nombre" value={user.name} icon="fas fa-user" />
|
<ProfileRow label="Usuario" value={profile.username} icon="fas fa-user-circle" />
|
||||||
<ProfileRow label="Correo" value={user.email} icon="fas fa-envelope" />
|
<ProfileRow label="Nombre" value={`${profile.firstName} ${profile.lastName}`} icon="fas fa-user" />
|
||||||
<ProfileRow label="Rol" value={user.role} icon="fas fa-user-shield" />
|
<ProfileRow label="Correo" value={profile.email} icon="fas fa-envelope" />
|
||||||
|
<ProfileRow label="Rol" value={profile.role} icon="fas fa-user-shield" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{showModal && (
|
||||||
|
<div className="modal-overlay" onClick={closeModal}>
|
||||||
|
<div className="modal" onClick={(e) => e.stopPropagation()}>
|
||||||
|
<div className="modal-header">
|
||||||
|
<h3>
|
||||||
|
<i className="fas fa-user-cog"></i>
|
||||||
|
Actualizar perfil
|
||||||
|
</h3>
|
||||||
|
<button className="btn btn-outline btn-sm" onClick={closeModal}>
|
||||||
|
<i className="fas fa-times"></i> Cerrar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="modal-body">
|
||||||
|
<form className="form-container" onSubmit={handleSave}>
|
||||||
|
<div className="form-row">
|
||||||
|
<div className="form-group" style={{ flex: 1 }}>
|
||||||
|
<label>
|
||||||
|
<i className="fas fa-user-circle" aria-hidden="true"></i> Usuario
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className="input"
|
||||||
|
type="text"
|
||||||
|
name="username"
|
||||||
|
value={form.username}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="Nombre de usuario"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-row">
|
||||||
|
<div className="form-group" style={{ flex: 1 }}>
|
||||||
|
<label>
|
||||||
|
<i className="fas fa-user" aria-hidden="true"></i> Nombre
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className="input"
|
||||||
|
type="text"
|
||||||
|
name="firstName"
|
||||||
|
value={form.firstName}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="Nombre"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-group" style={{ flex: 1 }}>
|
||||||
|
<label>
|
||||||
|
<i className="fas fa-user" aria-hidden="true"></i> Apellido
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className="input"
|
||||||
|
type="text"
|
||||||
|
name="lastName"
|
||||||
|
value={form.lastName}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="Apellido"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-row">
|
||||||
|
<div className="form-group" style={{ flex: 1 }}>
|
||||||
|
<label>
|
||||||
|
<i className="fas fa-lock" aria-hidden="true"></i> Contraseña (opcional)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className="input"
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
value={form.password}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="Nueva contraseña"
|
||||||
|
minLength={6}
|
||||||
|
/>
|
||||||
|
<small style={{ color: 'var(--gray)' }}>
|
||||||
|
Déjala vacía si no deseas cambiarla.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-row" style={{ justifyContent: 'flex-end', gap: 8 }}>
|
||||||
|
<button type="button" className="btn btn-outline" onClick={closeModal}>
|
||||||
|
Cancelar
|
||||||
|
</button>
|
||||||
|
<button type="submit" className="btn btn-primary">
|
||||||
|
<i className="fas fa-save"></i> Guardar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user