Mapa de procesos!

Inicio sesión y carrito de compras

¡Hola, equipo! Vamos a llevar nuestro inicio de sesión al siguiente nivel. En esta actualización, haremos que la sesión persista (¡no se cerrará al recargar!) y permitiremos a los usuarios subir su propia foto de perfil. ¡Vamos a ello!

1 Actualizar Base de Datos de Usuarios

Primero, actualicemos la lista de usuarios. Hemos cambiado los datos del "profe" para este ejemplo.

Acción: Copia esta Bases de Datos pero puedes reemplazar los valores recuerda que username es el que necesitas y password para iniciar sesión, y el nombre es que aparecerá en el MainHeader. user.js con este código.
Ubicación: src/data/user.js


export const users = [
    { id: 1, username: 'estudiante1', password: 'password123', name: 'Ana García' },
    { id: 2, username: 'profe', password: 'profe24', name: 'Samuel Profe' },
    { id: 3, username: 'maria', password: 'password789', name: 'María López' },
    { id: 4, username: 'david', password: 'password101', name: 'David Gómez' },
    { id: 5, username: 'sofia', password: 'password112', name: 'Sofía Rodríguez' },
];

2 Ajustar NavLinkHeader.jsx

Un pequeño ajuste de estilos en el NavLinkHeader para que la línea de "hover" se alinee correctamente con el nuevo header "sticky".

Acción: Reemplaza el contenido de NavLinkHeader.jsx.
Ubicación: src/assets/components/header/NavLinkHeader.jsx


import React from 'react';

export default ({ text, onClick }) => {
    const handleClick = (e) => {
        e.preventDefault();
        if (onClick) {
            onClick();
        }
    };

    return (
        <a href="#" onClick={handleClick} className="relative py-[1.69rem] group">
            <span className="group-hover:text-orange-400 transition-all duration-300">{text}</span>
            <span className="absolute left-0 bottom-[-2px] block h-1 w-full scale-x-0 group-hover:scale-x-100 group-hover:bg-orange-400 transition-all duration-300"></span>
        </a>
    );
};

3 Crearemos LoginModal.jsx (Es lo que nos permite iniciar sesion)

Vamos a copiar este código

Acción: Copia este código en LoginModal.jsx anterior por este código.
Ubicación: src/assets/components/modals/LoginModal.jsx
    
import React, { useState } from 'react'; const LoginModal = ({ onLogin, onClose }) => { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState(''); const [isLoading, setIsLoading] = useState(false); const [showPassword, setShowPassword] = useState(false); const handleSubmit = async (e) => { e.preventDefault(); setError(''); setIsLoading(true); // Simulamos una pequeña demora de red setTimeout(() => { const loginSuccess = onLogin(username, password); if (loginSuccess) { onClose(); } else { setError('Usuario o contraseña incorrectos.'); } setIsLoading(false); }, 600); }; return ( <div className="fixed inset-0 bg-gradient-to-br from-black/60 via-black/50 to-black/60 backdrop-blur-sm flex justify-center items-center z-50 animate-fadeIn px-4" onClick={onClose} > <div className="bg-white rounded-2xl shadow-2xl w-full max-w-md transform transition-all duration-300 animate-slideUp" onClick={(e) => e.stopPropagation()} > {/* Header con gradiente */} <div className="bg-gradient-to-r from-orange-500 via-pink-500 to-purple-500 rounded-t-2xl p-6 relative overflow-hidden"> <div className="absolute inset-0 bg-black/10"></div> {/* Botón X corregido */} <button type="button" onClick={onClose} className="absolute top-4 right-4 text-white/90 hover:text-white hover:bg-white/20 rounded-full p-1.5 transition-all duration-200 z-20 cursor-pointer" > <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2.5}> <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" /> </svg> </button> <h2 className="text-3xl font-bold text-white relative z-10 !border-none !pb-0 !mb-1"> Bienvenido </h2> <p className="text-white/90 mt-1 relative z-10 !mb-0">Inicia sesión para continuar</p> </div> {/* Formulario */} <form onSubmit={handleSubmit} className="p-8"> {/* Campo Usuario */} <div className="mb-6"> <label className="block text-gray-700 text-sm font-semibold mb-2" htmlFor="username"> Usuario </label> <div className="relative"> <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> <svg className="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /> </svg> </div> <input id="username" type="text" value={username} onChange={(e) => setUsername(e.target.value)} required className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-transparent transition-all duration-200 outline-none bg-gray-50 hover:bg-white" placeholder="tu_usuario" /> </div> </div> {/* Campo Contraseña */} <div className="mb-6"> <label className="block text-gray-700 text-sm font-semibold mb-2" htmlFor="password"> Contraseña </label> <div className="relative"> <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> <svg className="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /> </svg> </div> <input id="password" type={showPassword ? "text" : "password"} value={password} onChange={(e) => setPassword(e.target.value)} required className="w-full pl-10 pr-12 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-transparent transition-all duration-200 outline-none bg-gray-50 hover:bg-white" placeholder="••••••••" /> <button type="button" onClick={() => setShowPassword(!showPassword)} className="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600 transition-colors" > {showPassword ? ( <svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" /> </svg> ) : ( <svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /> </svg> )} </button> </div> </div> {/* Mensaje de Error */} {error && ( <div className="mb-6 p-4 bg-red-50 border-l-4 border-red-500 rounded-r-lg animate-shake"> <div className="flex items-center"> <svg className="h-5 w-5 text-red-500 mr-2" fill="currentColor" viewBox="0 0 20 20"> <path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" /> </svg> <p className="text-red-700 text-sm font-medium">{error}</p> </div> </div> )} {/* Botones */} <div className="flex flex-col gap-3"> <button type="submit" disabled={isLoading} className="w-full bg-gradient-to-r from-orange-500 to-pink-500 hover:from-orange-600 hover:to-pink-600 text-white font-bold py-3 px-6 rounded-lg transition-all duration-300 transform hover:scale-[1.02] active:scale-[0.98] shadow-lg hover:shadow-xl disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center" > {isLoading ? ( <> <svg className="animate-spin h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24"> <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle> <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> </svg> Iniciando sesión... </> ) : ( <> <svg className="h-5 w-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1" /> </svg> Iniciar Sesión </> )} </button> <button type="button" onClick={onClose} className="w-full bg-gray-100 hover:bg-gray-200 text-gray-700 font-semibold py-3 px-6 rounded-lg transition-all duration-200" > Cancelar </button> </div> {/* Link adicional */} <div className="mt-6 text-center"> <a href="#" className="text-sm text-gray-600 hover:text-orange-500 transition-colors duration-200 font-medium"> ¿Olvidaste tu contraseña? </a> </div> </form> </div> </div> ); }; export default LoginModal;

4 Crear ProfilePhotoModal.jsx

Este es un componente completamente nuevo. Se encargará de mostrar la foto de perfil, permitir al usuario seleccionar una nueva imagen, previsualizarla y guardarla (o eliminarla).

Acción: ¡Este es un archivo nuevo! Créalo en la carpeta modals.
Ubicación: src/assets/components/modals/ProfilePhotoModal.jsx


// src/assets/components/modals/ProfilePhotoModal.jsx
import React, { useState } from 'react';

const ProfilePhotoModal = ({ currentUser, onUpdatePhoto, onClose }) => {
    const [preview, setPreview] = useState(currentUser.profilePhoto || null);
    const [selectedFile, setSelectedFile] = useState(null);

    const handleFileSelect = (e) => {
        const file = e.target.files[0];
        if (file && file.type.startsWith('image/')) {
            setSelectedFile(file);
            // Crear preview usando URL.createObjectURL
            const previewURL = URL.createObjectURL(file);
            setPreview(previewURL);
        }
    };

    const handleSave = () => {
        if (preview) {
            // Nota: En una app real, aquí subirías el 'selectedFile' a un servidor
            // y obtendrías una URL. Para este kit, guardamos la URL de la *preview*
            // (que es un 'blob:') en localStorage. Esto funcionará mientras la
            // pestaña esté abierta, pero se romperá al cerrar el navegador.
            // Para una persistencia real entre sesiones, necesitaríamos 
            // leer el archivo como Base64, pero lo mantenemos simple.
            
            // *** Corrección para persistencia real con Base64 ***
            // Leemos el archivo como DataURL (Base64) para guardarlo en localStorage
            const reader = new FileReader();
            reader.onloadend = () => {
                const base64String = reader.result;
                onUpdatePhoto(base64String); // Guardar Base64 en localStorage
                onClose();
            };
            reader.readAsDataURL(selectedFile);
        }
    };

    const handleRemove = () => {
        setPreview(null);
        setSelectedFile(null);
        onUpdatePhoto(null); // Pasa null para eliminar la foto
    };
    
    // Si el usuario guardó un archivo (selectedFile) pero
    // quiere cancelar, debemos revocar la URL de la preview
    // para evitar fugas de memoria.
    const handleClose = () => {
        if (selectedFile) {
            URL.revokeObjectURL(preview);
        }
        onClose();
    }

    return (
        <div 
            className="fixed inset-0 bg-black/60 backdrop-blur-sm flex justify-center items-center z-50 animate-fadeIn px-4"
            onClick={handleClose}
        >
            <div 
                className="bg-white rounded-2xl shadow-2xl w-full max-w-md transform transition-all duration-300"
                onClick={(e) => e.stopPropagation()}
            >
                {/* Header */}
                <div className="bg-gradient-to-r from-orange-500 via-pink-500 to-purple-500 rounded-t-2xl p-6 relative">
                    <button 
                        type="button"
                        onClick={handleClose}
                        className="absolute top-4 right-4 text-white/90 hover:text-white hover:bg-white/20 rounded-full p-1.5 transition-all duration-200"
                    >
                        <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2.5}>
                            <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
                        </svg>
                    </button>
                    <h2 className="text-2xl font-bold text-white !border-none !pb-0 !mb-1">Foto de Perfil</h2>
                    <p className="text-white/90 mt-1 text-sm !mb-0">Sube o actualiza tu foto</p>
                </div>

                {/* Contenido */}
                <div className="p-8">
                    {/* Preview de la foto */}
                    <div className="flex flex-col items-center mb-6">
                        <div className="relative">
                            {preview ? (
                                <img 
                                    src={preview} 
                                    alt="Profile Preview" 
                                    className="w-32 h-32 rounded-full object-cover border-4 border-gray-200 shadow-lg"
                                />
                            ) : (
                                <div className="w-32 h-32 rounded-full bg-gradient-to-r from-orange-400 to-pink-400 flex items-center justify-center text-white text-4xl font-bold border-4 border-gray-200 shadow-lg">
                                    {currentUser.name.charAt(0).toUpperCase()}
                                </div>
                            )}
                            {/* Ícono de cámara */}
                            <label htmlFor="file-upload" className="absolute bottom-0 right-0 bg-gradient-to-r from-orange-500 to-pink-500 rounded-full p-2 shadow-lg cursor-pointer hover:scale-110 transition-transform">
                                <svg className="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" />
                                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" />
                                </svg>
                            </label>
                        </div>
                        <p className="text-gray-600 text-sm mt-4 !mb-0">
                            Formatos: JPG, PNG • Máximo 5MB
                        </p>
                    </div>

                    {/* Input de archivo (oculto) */}
                    <input 
                        id="file-upload"
                        type="file" 
                        accept="image/*"
                        onChange={handleFileSelect}
                        className="hidden"
                    />

                    {/* Botones de acción */}
                    <div className="space-y-3">
                        
                        {/* Botón para guardar (solo si se seleccionó un archivo nuevo) */}
                        {selectedFile && (
                            <button 
                                onClick={handleSave}
                                className="w-full bg-green-500 hover:bg-green-600 text-white font-semibold py-3 px-6 rounded-lg transition-all duration-300 flex items-center justify-center shadow-md hover:shadow-lg"
                            >
                                <svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
                                </svg>
                                Guardar Foto
                            </button>
                        )}

                        {/* Botón para eliminar foto (solo si hay una foto) */}
                        {preview && (
                            <button 
                                onClick={handleRemove}
                                className="w-full bg-red-100 hover:bg-red-200 text-red-600 font-semibold py-3 px-6 rounded-lg transition-all duration-200 flex items-center justify-center"
                            >
                                <svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
                                </svg>
                                Eliminar Foto
                            </button>
                        )}

                        {/* Botón cancelar */}
                        <button 
                            onClick={handleClose}
                            className="w-full bg-gray-100 hover:bg-gray-200 text-gray-700 font-semibold py-3 px-6 rounded-lg transition-all duration-200"
                        >
                            {selectedFile ? 'Cancelar' : 'Cerrar'}
                        </button>
                    </div>
                </div>
            </div>
        </div>
    );
};

export default ProfilePhotoModal;

5 Actualizar MainHeader.jsx (¡Con Menú!)

Este es un gran cambio. El header ahora será "sticky" (fijo arriba), y el botón de "Iniciar Sesión" se reemplaza por un avatar. Al hacer clic en el avatar, se abre un menú desplegable con opciones para "Cambiar foto" y "Cerrar sesión".

Acción: Reemplaza el contenido de MainHeader.jsx. ¡RECUERDA LA ADVERTENCIA INICIAL SOBRE GUARDAR TUS ENLACES!
Ubicación: src/assets/components/header/MainHeader.jsx


// src/assets/components/header/MainHeader.jsx
import React, { useState } from 'react';
import MenuIcon from '../icons/MenuIcons.jsx';
import CartIcon from '../icons/CartIcon.jsx';
import CloseIcon from '../icons/CloseIcon.jsx';
import NavLinkHeader from './NavLinkHeader.jsx';

const LoginIcon = () => (
    <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
        <path strokeLinecap="round" strokeLinejoin="round" d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1" />
    </svg>
);

const LogoutIcon = () => (
    <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
        <path strokeLinecap="round" strokeLinejoin="round" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1" />
    </svg>
);

const MainHeader = ({ navigateTo, currentUser, onLogout, onShowLogin, onShowProfilePhoto }) => {
    const COMPANY_NAME = "shoreline";
    const [navClass, setnavClass] = useState("hidden font-bold md:mr-auto md:gap-4 md:flex md:flex-row top-0 left-0 p-8 md:static md:p-0 md:h-auto");
    const [showUserMenu, setShowUserMenu] = useState(false);

    const handleOpenMenu = () => {
        setnavClass("absolute w-4/5 font-bold flex flex-col md:mr-auto md:gap-4 md:flex md:flex-row top-0 left-0 bg-white h-full p-8 gap-y-[21px] md:static md:p-0 md:h-auto z-10 shadow-2xl");
    };
    
    const handleClosedMenu = () => {
        setnavClass("hidden font-bold md:mr-auto md:gap-4 md:flex md:flex-row top-0 left-0 p-8 md:static md:p-0 md:h-auto");
    };

    return (
        <>
            <header className='container mx-auto flex items-center px-4 gap-8 bg-white h-20 border-b border-gray-200 sticky top-0 z-40 shadow-sm'>
                <button className='md:hidden hover:bg-gray-100 p-2 rounded-lg transition-colors duration-200' onClick={handleOpenMenu}>
                    <MenuIcon />
                </button>
                
                <div 
                    onClick={() => navigateTo('home')} 
                    className='mr-auto md:mr-0 font-black text-3xl text-gray-900 tracking-wider cursor-pointer hover:text-gray-700 transition-colors duration-200'
                >
                    {COMPANY_NAME}
                </div>
                
                <nav className={navClass}>
                    <button className='mb-12 md:hidden hover:bg-gray-100 p-2 rounded-lg transition-colors duration-200 self-start' onClick={handleClosedMenu}>
                        <CloseIcon />
                    </button>
                    {/* ===== ¡AQUÍ DEBES PONER TUS ENLACES! ===== */}
                    <NavLinkHeader text="Hombre" onClick={() => navigateTo('men')} />
                    <NavLinkHeader text="Mujer" onClick={() => navigateTo('women')} />
                    <NavLinkHeader text="Acerca de" onClick={() => navigateTo('about')} />
                    {/* ============================================= */}
                </nav>
                
                <div className='flex items-center gap-4 ml-auto'>
                    <button className="relative hover:bg-gray-100 p-2 rounded-lg transition-all duration-200 group text-gray-600 hover:text-gray-900">
                        <CartIcon />
                        <span className="absolute -top-1 -right-1 bg-gray-800 text-white text-xs w-5 h-5 rounded-full flex items-center justify-center font-bold shadow-lg group-hover:scale-110 transition-transform">
                            0
                        </span>
                    </button>
                    
                    {currentUser ? (
                        <div className="relative">
                            {/* Avatar clickeable */}
                            <button 
                                onClick={() => setShowUserMenu(!showUserMenu)}
                                className="flex items-center gap-3 hover:bg-gray-50 px-3 py-2 rounded-full transition-all duration-200"
                            >
                                {/* Foto de perfil o inicial */}
                                {currentUser.profilePhoto ? (
                                    <img 
                                        src={currentUser.profilePhoto} 
                                        alt={currentUser.name}
                                        className="w-10 h-10 rounded-full object-cover border-2 border-gray-200 hover:border-orange-400 transition-all duration-200 shadow-sm"
                                    />
                                ) : (
                                    <div className="w-10 h-10 bg-gradient-to-r from-orange-400 to-pink-400 rounded-full flex items-center justify-center text-white font-bold text-sm border-2 border-gray-200 hover:border-orange-400 transition-all duration-200 shadow-sm">
                                        {currentUser.name.charAt(0).toUpperCase()}
                                    </div>
                                )}
                                <span className="hidden sm:block font-semibold text-gray-700 text-sm">
                                    ¡Hola, {currentUser.name.split(' ')[0]}!
                                </span>
                                {/* Flecha dropdown */}
                                <svg className={`w-4 h-4 text-gray-500 transition-transform duration-200 ${showUserMenu ? 'rotate-180' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
                                </svg>
                            </button>

                            {/* Menú dropdown */}
                            {showUserMenu && (
                                <div className="absolute right-0 mt-2 w-56 bg-white rounded-lg shadow-xl border border-gray-200 py-2 z-50 animate-slideUp">
                                    <div className="px-4 py-3 border-b border-gray-100">
                                        <p className="text-sm font-semibold text-gray-900 !mb-1">{currentUser.name}</p>
                                        <p className="text-xs text-gray-500 !mb-0">{currentUser.email || 'usuario@email.com'}</p>
                                    </div>
                                    
                                    <button 
                                        onClick={() => {
                                            setShowUserMenu(false);
                                            onShowProfilePhoto();
                                        }}
                                        className="w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-50 flex items-center gap-3 transition-colors duration-150"
                                    >
                                        <svg className="w-5 h-5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" />
                                            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" />
                                        </svg>
                                        Cambiar foto de perfil
                                    </button>

                                    <button 
                                        onClick={() => {
                                            setShowUserMenu(false);
                                            onLogout();
                                        }}
                                        className="w-full px-4 py-2 text-left text-sm text-red-600 hover:bg-red-50 flex items-center gap-3 transition-colors duration-150 border-t border-gray-100 mt-2 pt-2"
                                    >
                                        <LogoutIcon />
                                        Cerrar sesión
                                    </button>
                                </div>
                            )}
                        </div>
                    ) : (
                        <button 
                            onClick={onShowLogin} 
                            className="flex items-center gap-2 text-white font-semibold bg-gradient-to-r from-orange-500 to-pink-500 hover:from-orange-600 hover:to-pink-600 px-5 py-2.5 rounded-lg transition-all duration-300 transform hover:scale-105 active:scale-95 shadow-md hover:shadow-lg group"
                        >
                            <LoginIcon />
                            <span className="hidden sm:inline">Iniciar Sesión</span>
                        </button>
                    )}
                </div>
            </header>
        </>
    );
};

export default MainHeader;

6 Actualizar App.jsx (¡El Cerebro!)

Este es el cambio más importante. Añadimos useEffect para guardar y cargar el usuario desde localStorage, un estado de carga (isLoading), y la lógica para manejar el nuevo modal de foto de perfil.

Acción: Reemplaza todo el contenido de App.jsx.
Ubicación: src/App.jsx


// src/App.jsx
import React, { useState, useEffect } from 'react';
import MainHeader from './assets/components/header/MainHeader.jsx';
import CategoryPage from './assets/components/pages/CategoryPage.jsx';
import MainProduct from './assets/components/product/MainProduct.jsx';
import PromocionBanner from './assets/components/product/PromocionBanner.jsx';
import FeaturedProducts from './assets/components/product/FeaturedProducts.jsx';
import FooterProduct from './assets/components/product/FooterProduct.jsx';
import LoginModal from './assets/components/modals/LoginModal.jsx';
import ProfilePhotoModal from './assets/components/modals/ProfilePhotoModal.jsx';
import { users } from '../src/data/user.js'; // Asegúrate que la ruta a user.js sea correcta

const HomePage = () => (
    <>
        <MainProduct />
        <PromocionBanner />
        <FeaturedProducts />
        <FooterProduct />
    </>
);

const App = () => {
    const [currentPage, setCurrentPage] = useState('home');
    const [currentUser, setCurrentUser] = useState(null);
    const [isLoginModalOpen, setIsLoginModalOpen] = useState(false);
    const [isProfilePhotoModalOpen, setIsProfilePhotoModalOpen] = useState(false);
    const [isLoading, setIsLoading] = useState(true); // Estado de carga inicial

    // ===== RESTAURAR SESIÓN AL CARGAR LA APP =====
    useEffect(() => {
        const restoreSession = () => {
            try {
                // Buscar usuario guardado en localStorage
                const savedUser = localStorage.getItem('currentUser');
                
                if (savedUser) {
                    const userData = JSON.parse(savedUser);
                    
                    // Cargar también la foto de perfil (guardada como Base64)
                    const savedPhoto = localStorage.getItem(`profilePhoto_${userData.username}`);
                    
                    setCurrentUser({
                        ...userData,
                        profilePhoto: savedPhoto || null
                    });
                    
                    console.log('Sesión restaurada:', userData.name);
                }
            } catch (error) {
                console.error('Error al restaurar sesión:', error);
                // Si hay error, limpiar localStorage
                localStorage.removeItem('currentUser');
            } finally {
                setIsLoading(false); // Termina la carga
            }
        };

        restoreSession();
    }, []); // El array vacío [] asegura que esto solo se ejecute UNA VEZ al montar

    // ===== GUARDAR DATOS DEL USUARIO CUANDO CAMBIA =====
    // Este useEffect se dispara CADA VEZ que 'currentUser' cambia
    useEffect(() => {
        if (currentUser) {
            // Guardar usuario en localStorage (sin la foto, esa se guarda separada)
            const userToSave = {
                id: currentUser.id, // Guardamos ID por si acaso
                username: currentUser.username,
                name: currentUser.name,
                email: currentUser.email || `${currentUser.username}@shoreline.com`
            };
            localStorage.setItem('currentUser', JSON.stringify(userToSave));
        } else {
            // Si no hay usuario (logout), eliminar del localStorage
            localStorage.removeItem('currentUser');
        }
    }, [currentUser]); // Se ejecuta cuando 'currentUser' cambia

    const login = (username, password) => {
        const foundUser = users.find(u => u.username === username && u.password === password);
        if (foundUser) {
            // Cargar foto guardada (Base64) si existe
            const savedPhoto = localStorage.getItem(`profilePhoto_${username}`);
            
            const userWithPhoto = {
                ...foundUser,
                email: foundUser.email || `${username}@shoreline.com`,
                profilePhoto: savedPhoto || null
            };
            
            setCurrentUser(userWithPhoto);
            console.log('Login exitoso:', userWithPhoto.name);
            return true;
        }
        return false;
    };

    const logout = () => { 
        // Limpiar sesión de localStorage (el useEffect de arriba lo hará, pero
        // somos explícitos por si acaso)
        localStorage.removeItem('currentUser');
        console.log('Sesión cerrada');
        
        setCurrentUser(null); 
        navigateTo('home'); 
    };

    const updateProfilePhoto = (photoDataURL) => {
        if (currentUser) {
            // Guardar o eliminar foto en localStorage
            if (photoDataURL) {
                // Guardamos la imagen como un string Base64
                localStorage.setItem(`profilePhoto_${currentUser.username}`, photoDataURL);
            } else {
                localStorage.removeItem(`profilePhoto_${currentUser.username}`);
            }
            
            // Actualizar estado
            setCurrentUser(prev => ({
                ...prev,
                profilePhoto: photoDataURL
            }));
        }
    };

    const navigateTo = (page) => { setCurrentPage(page); };

    const renderPage = () => {
        switch (currentPage) {
            case 'women':
                return <CategoryPage title="Colección Mujer" category="Mujer" />;
            case 'men':
                return <CategoryPage title="Colección Hombre" category="Hombre" />;
            case 'about':
                // Puedes reemplazar esto por tu componente AboutPage si lo tienes
                return <div className="container mx-auto py-20 px-4"><h1 className="text-4xl font-bold">Acerca de Nosotros</h1></div>;
            default:
                return <HomePage />;
        }
    };

    const openLoginModal = () => setIsLoginModalOpen(true);
    const closeLoginModal = () => setIsLoginModalOpen(false);
    const openProfilePhotoModal = () => setIsProfilePhotoModalOpen(true);
    const closeProfilePhotoModal = () => setIsProfilePhotoModalOpen(false);

    // Mostrar un "loading" mientras se restaura la sesión
    if (isLoading) {
        return (
            <div className="flex items-center justify-center min-h-screen bg-gray-50">
                <div className="text-center">
                    <div className="inline-block animate-spin rounded-full h-12 w-12 border-b-2 border-orange-500 mb-4"></div>
                    <p className="text-gray-600 font-semibold">Cargando Tienda...</p>
                </div>
            </div>
        );
    }

    // App principal
    return (
        <div>
            <MainHeader
                navigateTo={navigateTo}
                currentUser={currentUser}
                onLogout={logout}
                onShowLogin={openLoginModal}
                onShowProfilePhoto={openProfilePhotoModal} // Nueva prop
            />
            
            {renderPage()}
            
            {/* --- Modales --- */}
            {isLoginModalOpen && <LoginModal onLogin={login} onClose={closeLoginModal} />}
            
            {isProfilePhotoModalOpen && currentUser && (
                <ProfilePhotoModal 
                    currentUser={currentUser}
                    onUpdatePhoto={updateProfilePhoto}
                    onClose={closeProfilePhotoModal}
                />
            )}
        </div>
    );
};

export default App;

¡Prueba Final!

¡Felicidades! Has implementado un sistema de sesión completo. Es hora de probarlo: