CoolFace
Apppublic

DataScope26/WEB

sourceHugging Faceupdated 7d agoView on Hugging Face
0likes
Settings.jsx996 linesDownload Raw Back to components
1import { useAuth } from "../context/AuthContext.jsx";2import { useTheme } from "../context/ThemeContext.jsx";3import { useNavigate } from "react-router-dom";4import { useState, useRef, useEffect, useCallback } from "react";5import "./Settings.css";6import perfilGenerico from "../assets/perfilGenerico.png";7import ButtonPrimary from "./ButtonPrimary.jsx";8import ButtonAlert from "./ButtonAlert.jsx";9import { Loader, Sun, Moon, Pencil, Camera, AlertTriangle, Save, Trash2 } from "lucide-react";10 11// Tamaño (en px) del área de recorte cuadrada que se muestra en el popup12const CROP_SIZE = 280;13 14// Tamaño (en px) del canvas de salida que se envía al backend15const OUTPUT_SIZE = 500;16 17export default function Settings() {18    const { user, logout, updateUserData, uploadPfp, deleteProfile, changePassword, changeEmail } = useAuth();19    const { theme, setTheme } = useTheme();20    const navigate = useNavigate();21 22    const [username, setUsername] = useState(user.username || "");23    const [email, setEmail] = useState(user.email || "");24    const [originalUsername, setOriginalUsername] = useState(user.username || "");25    const [isEditingUsername, setIsEditingUsername] = useState(false);26    const usernameInputRef = useRef(null);27 28    const [usernameError, setUsernameError] = useState("");29    const [emailError, setEmailError] = useState("");30    const [message, setMessage] = useState("");31 32    useEffect(() => {33        const nextUsername = user.username || "";34        setUsername(nextUsername);35        setOriginalUsername(nextUsername);36 37        if (!isEditingUsername) {38            setUsername(nextUsername);39        }40    }, [user.username, isEditingUsername]);41 42    useEffect(() => {43        if (isEditingUsername) {44            usernameInputRef.current?.focus();45            usernameInputRef.current?.select();46        }47    }, [isEditingUsername]);48 49    // --- Estado del popup de foto de perfil ---50    const [showPfpPopup, setShowPfpPopup] = useState(false);51    const [selectedImageUrl, setSelectedImageUrl] = useState(null);52    const [scale, setScale] = useState(1);53    const [minScale, setMinScale] = useState(1);54    const [offset, setOffset] = useState({ x: 0, y: 0 });55    const [isSaving, setIsSaving] = useState(false);56    const [showDeleteModal, setShowDeleteModal] = useState(false);57 58    const fileInputRef = useRef(null);59    const canvasRef = useRef(null);60    const imgElRef = useRef(null);61    const isDraggingRef = useRef(false);62    const lastPosRef = useRef({ x: 0, y: 0 });63 64    // =========================65    // VALIDACIONES66    // =========================67 68    const validateUsername = (value) => {69        if (!value.trim()) {70            return "El nombre no puede estar vacío.";71        }72 73        if (value.trim().length > 50) {74            return "El nombre no puede superar los 50 caracteres.";75        }76 77        if (!/[A-Za-zÀ-ÿ]/.test(value.trim())) {78            return "El nombre debe contener al menos una letra.";79        }80 81        if (!/^[a-zA-ZÀ-ÿ0-9 _-]+$/.test(value.trim())) {82            return "El nombre solo puede contener letras, números, espacios, guiones y guiones bajos.";83        }84 85        return "";86    };87 88    const validateEmail = (value) => {89        const normalizedValue = value.trim();90 91        if (!normalizedValue) {92            return "El correo electrónico no puede estar vacío.";93        }94 95        if (!/[A-Za-z]/.test(normalizedValue)) {96            return "El email debe tener al menos una letra.";97        }98 99        const parts = normalizedValue.split("@");100 101        if (parts.length !== 2) {102            return "Falta el símbolo @.";103        }104 105        const [localPart, domain] = parts;106 107        const localPartRegex =108            /^[A-Za-z0-9](?:[A-Za-z0-9._%+-]*[A-Za-z0-9])?$/;109 110        if (!localPartRegex.test(localPart)) {111            return "Parte del correo antes del @ inválida.";112        }113 114        const domainRegex = /^[a-zA-Z]{2,10}\.[A-Za-z]{2,5}(\.[A-Za-z]{2,3})?$/;115 116        if (!domainRegex.test(domain)) {117            return "Dominio del correo inválido.";118        }119 120        return "";121    };122 123    const handleUsernameChange = (e) => {124        const value = e.target.value;125 126        setUsername(value);127        setUsernameError(validateUsername(value));128        setMessage("");129    };130 131    const validateProfileFields = () => {132        const usernameErr = validateUsername(username);133        const emailErr = validateEmail(email);134 135        setUsernameError(usernameErr);136        setEmailError(emailErr);137 138        if (usernameErr || emailErr) {139            setMessage("");140            return false;141        }142 143        return true;144    };145 146    const handleUsernameSave = () => {147        const trimmedValue = username.trim();148        const validationError = validateUsername(trimmedValue);149 150        if (validationError) {151            setUsernameError(validationError);152            return;153        }154 155        setUsernameError("");156        updateUserData(trimmedValue);157        setOriginalUsername(trimmedValue);158        setIsEditingUsername(false);159        setMessage("");160    };161 162    const handleUsernameRevert = () => {163        setUsername(originalUsername);164        setUsernameError("");165        setIsEditingUsername(false);166        setMessage("");167    };168 169    const handleUsernameKeyDown = (e) => {170        if (e.key === "Enter") {171            e.preventDefault();172            handleUsernameSave();173        }174    };175 176    // =========================177    // PERFIL178    // =========================179 180    181 182    const handleLogout = () => {183        logout();184        navigate("/");185    };186 187    const handleDeleteProfile = async () => {188        try {189            await deleteProfile();190            navigate("/");191        } catch (err) {192            console.error(err);193        }194    };195 196    const handleThemeChange = (nextTheme) => {197        setTheme(nextTheme);198        setMessage("");199    };200 201    // --- change password/email modals ---202    const [showChangePassword, setShowChangePassword] = useState(false);203    const [showChangeEmail, setShowChangeEmail] = useState(false);204 205    const [cp_current, setCpCurrent] = useState("");206    const [cp_new, setCpNew] = useState("");207    const [cp_confirm, setCpConfirm] = useState("");208    const [cp_error, setCpError] = useState("");209    const [cp_loading, setCpLoading] = useState(false);210 211    const [ce_current, setCeCurrent] = useState("");212    const [ce_new, setCeNew] = useState("");213    const [ce_confirm, setCeConfirm] = useState("");214    const [ce_error, setCeError] = useState("");215    const [ce_loading, setCeLoading] = useState(false);216 217 218 219    const resetPasswordModal = () => {220        setCpCurrent(""); setCpNew(""); setCpConfirm(""); setCpError(""); setCpLoading(false);221    };222 223    const resetEmailModal = () => {224        setCeCurrent(""); setCeNew(""); setCeConfirm(""); setCeError(""); setCeLoading(false);225    };226 227    const submitChangePassword = async () => {228        setCpError("");229        if (!cp_current || !cp_new || !cp_confirm) { setCpError('Completa todos los campos'); return; }230        if (cp_new !== cp_confirm) { setCpError('La nueva contraseña no coincide'); return; }231        if (cp_new.length < 6) { setCpError('La contraseña debe tener al menos 6 caracteres'); return; }232 233        try {234            setCpLoading(true);235            await changePassword(cp_current, cp_new);236            resetPasswordModal();237            setShowChangePassword(false);238            setMessage('Contraseña cambiada correctamente');239        } catch (err) {240            setCpError(err?.response?.data?.error || err.message || 'Error al cambiar la contraseña');241        } finally {242            setCpLoading(false);243        }244    };245 246    const validateEmailLocal = (value) => {247        if (!value || !/[A-Za-z]/.test(value)) return 'El email debe contener al menos una letra.';248        if (!value.includes('@')) return 'Formato de email inválido.';249        return null;250    };251 252    const submitChangeEmail = async () => {253        setCeError("");254        const v = validateEmailLocal(ce_new);255        if (!ce_current || !ce_new || !ce_confirm) { setCeError('Completa todos los campos'); return; }256        if (ce_new !== ce_confirm) { setCeError('El nuevo email no coincide'); return; }257        if (v) { setCeError(v); return; }258 259        try {260            setCeLoading(true);261            await changeEmail(ce_current, ce_new);262            resetEmailModal();263            setShowChangeEmail(false);264            setMessage('Email cambiado correctamente');265        } catch (err) {266            setCeError(err?.response?.data?.error || err.message || 'Error al cambiar el email');267        } finally {268            setCeLoading(false);269        }270    };271 272    // =========================273    // FOTO DE PERFIL274    // =========================275 276    // Ahora, en lugar de abrir el popup primero, abrimos directamente277    // el explorador de archivos. El popup de recorte se muestra recién278    // cuando el usuario ya eligió una imagen (ver handleFileChosen).279    const openPfpPopup = () => {280        setSelectedImageUrl(null);281        setScale(1);282        setMinScale(1);283        setOffset({ x: 0, y: 0 });284        triggerFileSelect();285    };286 287    const closePfpPopup = () => {288        if (isSaving) return;289 290        if (selectedImageUrl) {291            URL.revokeObjectURL(selectedImageUrl);292        }293 294        setSelectedImageUrl(null);295        setShowPfpPopup(false);296    };297 298    const triggerFileSelect = () => {299        fileInputRef.current?.click();300    };301 302    const handleFileChosen = (e) => {303        const file = e.target.files[0];304 305        if (!file) return;306 307        // Solo permitir imágenes308        if (!file.type.startsWith("image/")) {309            return;310        }311 312        const url = URL.createObjectURL(file);313 314        // Liberar la URL anterior si existía315        if (selectedImageUrl) {316            URL.revokeObjectURL(selectedImageUrl);317        }318 319        setSelectedImageUrl(url);320        setScale(1);321        setMinScale(1);322        setOffset({ x: 0, y: 0 });323 324        // Una vez elegida la imagen, recién ahí mostramos el popup de recorte325        setShowPfpPopup(true);326 327        // Permite volver a seleccionar el mismo archivo328        e.target.value = "";329    };330 331    // =========================332    // CARGA DE IMAGEN333    // =========================334 335    useEffect(() => {336        if (!selectedImageUrl) {337            imgElRef.current = null;338            return;339        }340 341        const img = new Image();342 343        img.onload = () => {344            imgElRef.current = img;345 346            // Escala mínima para cubrir todo el cuadrado347            const coverScale = Math.max(348                CROP_SIZE / img.width,349                CROP_SIZE / img.height350            );351 352            setMinScale(coverScale);353            setScale(coverScale);354 355            // Centrar imagen356            const drawWidth = img.width * coverScale;357            const drawHeight = img.height * coverScale;358 359            setOffset({360                x: (CROP_SIZE - drawWidth) / 2,361                y: (CROP_SIZE - drawHeight) / 2,362            });363        };364 365        img.src = selectedImageUrl;366    }, [selectedImageUrl]);367 368    // =========================369    // LIMITAR MOVIMIENTO370    // =========================371 372    const clampOffset = useCallback((nextOffset, currentScale) => {373        const img = imgElRef.current;374 375        if (!img) {376            return nextOffset;377        }378 379        const drawWidth = img.width * currentScale;380        const drawHeight = img.height * currentScale;381 382        const minX = CROP_SIZE - drawWidth;383        const minY = CROP_SIZE - drawHeight;384 385        return {386            x: Math.min(0, Math.max(minX, nextOffset.x)),387            y: Math.min(0, Math.max(minY, nextOffset.y)),388        };389    }, []);390 391    // =========================392    // CANVAS PREVIEW393    // =========================394 395    useEffect(() => {396        const canvas = canvasRef.current;397        const img = imgElRef.current;398 399        if (!canvas || !img || !selectedImageUrl) {400            return;401        }402 403        const ctx = canvas.getContext("2d");404 405        ctx.clearRect(0, 0, CROP_SIZE, CROP_SIZE);406 407        ctx.drawImage(408            img,409            offset.x,410            offset.y,411            img.width * scale,412            img.height * scale413        );414    }, [scale, offset, selectedImageUrl]);415 416    // =========================417    // ZOOM418    // =========================419 420    const handleZoomChange = (e) => {421        const newScale = parseFloat(e.target.value);422        const center = CROP_SIZE / 2;423 424        // Punto de la imagen que actualmente está en el centro del círculo de recorte.425        const imgCenterX = (center - offset.x) / scale;426        const imgCenterY = (center - offset.y) / scale;427 428        // Recalculamos el offset para que ese mismo punto siga en el centro429        // después de aplicar el nuevo scale (zoom centrado, no desde la esquina).430        const nextOffset = {431            x: center - imgCenterX * newScale,432            y: center - imgCenterY * newScale,433        };434 435        setScale(newScale);436        setOffset(clampOffset(nextOffset, newScale));437    };438 439    // =========================440    // DRAG / PAN441    // =========================442 443    const handlePointerDown = (e) => {444        if (!imgElRef.current) return;445 446        isDraggingRef.current = true;447 448        const point = e.touches ? e.touches[0] : e;449 450        lastPosRef.current = {451            x: point.clientX,452            y: point.clientY,453        };454    };455 456    const handlePointerMove = (e) => {457        if (!isDraggingRef.current || !imgElRef.current) {458            return;459        }460 461        const point = e.touches ? e.touches[0] : e;462 463        const dx = point.clientX - lastPosRef.current.x;464        const dy = point.clientY - lastPosRef.current.y;465 466        lastPosRef.current = {467            x: point.clientX,468            y: point.clientY,469        };470 471        setOffset((prev) =>472            clampOffset(473                {474                    x: prev.x + dx,475                    y: prev.y + dy,476                },477                scale478            )479        );480    };481 482    const handlePointerUp = () => {483        isDraggingRef.current = false;484    };485 486    // =========================487    // GUARDAR FOTO RECORTADA488    // =========================489 490    const handleSaveCroppedPhoto = async () => {491        const img = imgElRef.current;492 493        if (!img) return;494 495        setIsSaving(true);496 497        try {498            const outputCanvas = document.createElement("canvas");499 500            outputCanvas.width = OUTPUT_SIZE;501            outputCanvas.height = OUTPUT_SIZE;502 503            const ctx = outputCanvas.getContext("2d");504 505            ctx.imageSmoothingEnabled = true;506            ctx.imageSmoothingQuality = "high";507 508            const ratio = OUTPUT_SIZE / CROP_SIZE;509 510            ctx.drawImage(511                img,512                offset.x * ratio,513                offset.y * ratio,514                img.width * scale * ratio,515                img.height * scale * ratio516            );517 518            // Convertir directamente a WebP519            const webpBlob = await new Promise((resolve, reject) => {520                outputCanvas.toBlob(521                    (blob) => {522                        if (!blob) {523                            reject(524                                new Error("Failed to create WebP image")525                            );526                            return;527                        }528 529                        resolve(blob);530                    },531                    "image/webp",532                    0.9533                );534            });535 536            const formData = new FormData();537 538            formData.append(539                "image",540                webpBlob,541                "profile-picture.webp"542            );543 544            await uploadPfp(formData);545 546            if (selectedImageUrl) {547                URL.revokeObjectURL(selectedImageUrl);548            }549 550            setSelectedImageUrl(null);551            setShowPfpPopup(false);552 553            navigate("/");554        } catch (err) {555            console.error(err);556        } finally {557            setIsSaving(false);558        }559    };560 561    return (562        <>563            {/* Input de archivo global: debe existir siempre en el DOM para564                poder abrirse desde el botón "Cambiar foto" antes de que el565                popup de recorte se muestre. */}566            <input567                ref={fileInputRef}568                type="file"569                accept="image/*"570                onChange={handleFileChosen}571                style={{ display: "none" }}572            />573 574            <div className="profile-settings-layout">575                <div className="perfil-card">576                    <div className="card-heading">577                        <span className="card-heading-icon">578                            <svg579                                width="16"580                                height="16"581                                viewBox="0 0 24 24"582                                fill="none"583                                stroke="currentColor"584                                strokeWidth="2"585                                strokeLinecap="round"586                                strokeLinejoin="round"587                            >588                                <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />589                                <circle cx="12" cy="7" r="4" />590                            </svg>591                        </span>592 593                        <h2 className="card-title">Datos de tu perfil</h2>594                    </div>595 596                    <div className="form-grid">597                        <div className="profile-picture-container">598                            <div className="profile-picture-shell">599                                <img600                                    className="change-profile-pic"601                                    src={602                                        user.profile_pic?.trim()603                                            ? user.profile_pic604                                            : perfilGenerico605                                    }606                                    alt="foto perfil"607                                />608                            </div>609 610                            <ButtonPrimary611                                type="button"612                                className="profile-picture-action"613                                onClick={openPfpPopup}614                            >615                                <Camera size={14} />616                                <span>Cambiar foto</span>617                            </ButtonPrimary>618                        </div>619 620                        <div className="form-left">621                            <div className="form-field">622                                <label className="form-label">NOMBRE</label>623                                <div className="username-editor-row">624                                    <input625                                        ref={usernameInputRef}626                                        className="form-input"627                                        type="text"628                                        value={username}629                                        onChange={handleUsernameChange}630                                        onKeyDown={handleUsernameKeyDown}631                                        maxLength={50}632                                        readOnly={!isEditingUsername}633                                        style={634                                            usernameError635                                                ? {636                                                    borderColor: "#e53e3e",637                                                }638                                                : {}639                                        }640                                    />641 642                                    {isEditingUsername ? (643                                        <div className="username-edit-actions">644                                            <ButtonPrimary645                                                type="button"646                                                className="icon-only-button"647                                                onClick={handleUsernameSave}648                                                aria-label="Guardar nombre"649                                                title="Guardar nombre"650                                            >651                                                <Save size={15} />652                                            </ButtonPrimary>653                                            <ButtonAlert654                                                type="button"655                                                className="icon-only-button"656                                                onClick={handleUsernameRevert}657                                                aria-label="Revertir nombre"658                                                title="Revertir nombre"659                                            >660                                                <Trash2 size={15} />661                                            </ButtonAlert>662                                        </div>663                                    ) : (664                                        <ButtonPrimary665                                            type="button"666                                            className="icon-only-button"667                                            onClick={() => setIsEditingUsername(true)}668                                            aria-label="Editar nombre"669                                            title="Editar nombre"670                                        >671                                            <Pencil size={15} />672                                        </ButtonPrimary>673                                    )}674                                </div>675                                {usernameError && <span className="field-error">{usernameError}</span>}676                            </div>677 678                            <div className="form-field">679                                <label className="form-label">CORREO ELECTRÓNICO</label>680                                <div className="email-editor-row">681                                    <div className="form-input form-readonly" aria-readonly="true">682                                        {user.email || "Sin correo asociado"}683                                    </div>684                                    <ButtonPrimary685                                        type="button"686                                        className="icon-only-button"687                                        onClick={() => setShowChangeEmail(true)}688                                        aria-label="Cambiar correo"689                                        title="Cambiar correo"690                                    >691                                        <Pencil size={15} />692                                    </ButtonPrimary>693                                </div>694                                {emailError && <span className="field-error">{emailError}</span>}695                            </div>696                        </div>697                    </div>698 699                    <div className="card-actions" style={{ display: "flex", gap: 10, alignItems: "center", justifyContent: "flex-end" }}>700                        <div style={{ marginLeft: "auto" }}>701                            <ButtonAlert className="btn-logout" onClick={handleLogout}>Logout</ButtonAlert>702                        </div>703                    </div>704 705                    {message && (706                        <p707                            className="form-error"708                            style={{709                                margin: 0,710                                color: "var(--color-danger)",711                                fontSize: "12px",712                                fontWeight: 600,713                            }}714                        >715                            {message}716                        </p>717                    )}718                </div>719 720                <div className="settings-side-column">721                    <div className="profile-aspect-card">722                        <div className="profile-security-header">723                            <div className="profile-security-title-wrap">724 725                                <h3 className="profile-security-title">Opciones de aspecto</h3>726                            </div>727                        </div>728 729                        <div className="theme-picker" aria-label="Selector de tema">730                            <div className="theme-switch-row">731                                <Sun732                                    size={18}733                                    className={`theme-icon ${theme !== "dark" ? "theme-icon--active" : ""}`}734                                />735                                <div736                                    role="switch"737                                    tabIndex={0}738                                    className={`theme-switch ${theme === "dark" ? "theme-switch--dark" : ""}`}739                                    onClick={() => handleThemeChange(theme === "dark" ? "light" : "dark")}740                                    onKeyDown={(e) => {741                                        if (e.key === "Enter" || e.key === " ") {742                                            handleThemeChange(theme === "dark" ? "light" : "dark");743                                        }744                                    }}745                                    aria-checked={theme === "dark"}746                                >747                                    <div className="theme-switch__knob" />748                                </div>749                                <Moon750                                    size={18}751                                    className={`theme-icon ${theme === "dark" ? "theme-icon--active" : ""}`}752                                />753                            </div>754                        </div>755                    </div>756 757                    <div className="profile-security-card">758                        <div className="profile-security-header">759                            <div className="profile-security-title-wrap">760                                <AlertTriangle size={16} className="profile-security-icon" />761                                <h3 className="profile-security-title">Zona de Peligro</h3>762                            </div>763                        </div>764 765                        <p className="profile-security-text">766                            Actualiza tu contraseña cuando lo necesites. Esta acción ayuda a mantener tus credenciales protegidas y tus accesos bajo control.767                        </p>768 769                        <div className="profile-security-actions">770                            <ButtonPrimary771                                className="profile-password-button"772                                onClick={() => setShowChangePassword(true)}773                            >774                                Cambiar contraseña775                            </ButtonPrimary>776                        </div>777                    </div>778                </div>779            </div>780 781            {/* =========================782                POPUP FOTO DE PERFIL (RECORTE)783                Solo se muestra una vez que el usuario ya eligió una imagen784                desde el explorador de archivos.785            ========================= */}786 787            {showPfpPopup && selectedImageUrl && (788                <div789                    className="pfp-modal-overlay"790                    onClick={closePfpPopup}791                >792                    <div793                        className="pfp-modal-content"794                        onClick={(e) => e.stopPropagation()}795                    >796                        <h3 className="pfp-modal-title">797                            Foto de perfil798                        </h3>799 800                        <div801                            className="pfp-crop-container"802                            onMouseDown={handlePointerDown}803                            onMouseMove={handlePointerMove}804                            onMouseUp={handlePointerUp}805                            onMouseLeave={handlePointerUp}806                            onTouchStart={handlePointerDown}807                            onTouchMove={handlePointerMove}808                            onTouchEnd={handlePointerUp}809                        >810                            <canvas811                                ref={canvasRef}812                                width={CROP_SIZE}813                                height={CROP_SIZE}814                            />815 816                            <div className="pfp-crop-grid"></div>817                        </div>818 819                        {/* ZOOM */}820 821                        <div className="pfp-zoom-controls">822                            <span className="pfp-zoom-label">823                                -824                            </span>825 826                            <input827                                className="pfp-zoom-slider"828                                type="range"829                                min={minScale}830                                max={minScale * 4}831                                step={0.01}832                                value={scale}833                                onChange={handleZoomChange}834                            />835 836                            <span className="pfp-zoom-label">837                                +838                            </span>839                        </div>840 841                        <ButtonPrimary className="btn-change-photo" type="button" onClick={triggerFileSelect}>842                            Elegir otra foto843                        </ButtonPrimary>844 845                        {/* ACCIONES */}846 847                        <div className="pfp-modal-actions">848                            <ButtonPrimary849                                className="btn-save"850                                type="button"851                                disabled={852                                    !selectedImageUrl ||853                                    isSaving854                                }855                                onClick={handleSaveCroppedPhoto}856                            >857                                {isSaving ? "Guardando..." : "Guardar Cambios"}858                            </ButtonPrimary>859                            <ButtonAlert className="btn-logout" type="button" onClick={closePfpPopup} disabled={isSaving}>860                                Cancelar861                            </ButtonAlert>862                        </div>863                    </div>864                </div>865            )}866 867            {/* =========================868                MODAL CAMBIAR EMAIL869            ========================= */}870 871            {showChangeEmail && (872                <div className="pfp-modal-overlay" onClick={() => { setShowChangeEmail(false); resetEmailModal(); }}>873                    <div className="pfp-modal-content" style={{ width: 620 }} onClick={(e) => e.stopPropagation()}>874                        <h3 className="pfp-modal-title">Cambiar correo</h3>875 876                        <div className="form-field">877                            <label>Contraseña actual</label>878                            <input className="form-input" type="password" value={ce_current} onChange={(e) => setCeCurrent(e.target.value)} />879                        </div>880 881                        <div className="form-field">882                            <label>Nuevo correo</label>883                            <input className="form-input" type="text" value={ce_new} onChange={(e) => setCeNew(e.target.value)} />884                        </div>885 886                        <div className="form-field">887                            <label>Confirmar nuevo correo</label>888                            <input className="form-input" type="text" value={ce_confirm} onChange={(e) => setCeConfirm(e.target.value)} />889                        </div>890 891                        {ce_error && <p className="field-error">{ce_error}</p>}892 893                        <div className="pfp-modal-actions" style={{ display: 'flex', gap: 10 }}>894                            {ce_loading ? (895                                <ButtonPrimary className="btn-save" style={{ cursor: 'default', minWidth: 160 }}>896                                    <Loader size={16} className="loader-icon-animation" />897                                </ButtonPrimary>898                            ) : (899                                <ButtonPrimary className="btn-save" onClick={submitChangeEmail} >Cambiar correo</ButtonPrimary>900                            )}901 902                            <ButtonAlert className="btn-logout" onClick={() => { setShowChangeEmail(false); resetEmailModal(); }}>Cancelar</ButtonAlert>903                        </div>904                    </div>905                </div>906            )}907 908            {/* =========================909                MODAL CAMBIAR CONTRASEÑA910            ========================= */}911 912            {showChangePassword && (913                <div className="pfp-modal-overlay" onClick={() => { setShowChangePassword(false); resetPasswordModal(); }}>914                    <div className="pfp-modal-content" style={{ width: 620 }} onClick={(e) => e.stopPropagation()}>915                        <h3 className="pfp-modal-title">Cambiar contraseña</h3>916 917                        <div className="form-field">918                            <label>Contraseña actual</label>919                            <input className="form-input" type="password" value={cp_current} onChange={(e) => setCpCurrent(e.target.value)} />920                        </div>921 922                        <div className="form-field">923                            <label>Nueva contraseña</label>924                            <input className="form-input" type="password" value={cp_new} onChange={(e) => setCpNew(e.target.value)} />925                        </div>926 927                        <div className="form-field">928                            <label>Confirmar nueva contraseña</label>929                            <input className="form-input" type="password" value={cp_confirm} onChange={(e) => setCpConfirm(e.target.value)} />930                        </div>931 932                        {cp_error && <p className="field-error">{cp_error}</p>}933 934                        <div className="pfp-modal-actions" style={{ display: 'flex', gap: 10 }}>935                            {cp_loading ? (936                                <ButtonPrimary className="btn-save" style={{ cursor: 'default', minWidth: 160 }}>937                                    <Loader size={16} className="loader-icon-animation" />938                                </ButtonPrimary>939                            ) : (940                                <ButtonPrimary className="btn-save" onClick={submitChangePassword}>Cambiar contraseña</ButtonPrimary>941                            )}942 943                            <ButtonAlert className="btn-logout" onClick={() => { setShowChangePassword(false); resetPasswordModal(); }}>Cancelar</ButtonAlert>944                        </div>945                    </div>946                </div>947            )}948 949            {/* =========================950                MODAL BORRAR PERFIL951            ========================= */}952 953            {showDeleteModal && (954                <div955                    className="pfp-modal-overlay"956                    onClick={() =>957                        setShowDeleteModal(false)958                    }959                >960                    <div961                        className="delete-modal-content"962                        onClick={(e) => e.stopPropagation()}963                    >964                        <h3 className="pfp-modal-title">965                            Borrar Perfil966                        </h3>967 968                        <p className="delete-modal-text">969                            ¿Estas totalmente seguro de que970                            quieres borrar tu cuenta?971 972                            <br />973                            <br />974 975                            <strong>976                                Esta acción no se puede deshacer.977                            </strong>978 979                            Todos los datos de tu cuenta se980                            eliminarán de forma permanente.981                        </p>982 983                        {/** 984                        <div className="pfp-modal-actions" style={{ display: 'flex', gap: 10 }}>985                            <ButtonAlert className="btn-delete-profile delete-confirm-btn" onClick={handleDeleteProfile}>BORRAR</ButtonAlert>986 987                            <ButtonPrimary className="btn-logout" onClick={() => setShowDeleteModal(false)}>Cancelar</ButtonPrimary>988                        </div>989                        */}990                        991                    </div>992                </div>993            )}994        </>995    );996}