refactor: remove unused components and styles; consolidate password handling in new components
- Deleted ProtectedRoute, ContentWrapper, CustomCarousel, CustomContainer, CustomModal, Footer, Header, and Building components as they were no longer needed. - Removed associated CSS files for the deleted components. - Introduced PasswordInput and PasswordModal components to handle password input and modal display for protected pastes. - Updated PastePanel to utilize new PasswordInput and PasswordModal components for better password management. - Refactored Home component to streamline data fetching and improve readability. - Enhanced error handling in useData hook and improved session management logic.
This commit is contained in:
49
src/components/Pastes/PasswordInput.jsx
Normal file
49
src/components/Pastes/PasswordInput.jsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { useState } from 'react';
|
||||
import { Form, FloatingLabel, Button } from 'react-bootstrap';
|
||||
import '@/css/PasswordInput.css';
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faEye, faEyeSlash, faKey } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
const PasswordInput = ({ disabled, value, onChange, name = "password" }) => {
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
const toggleShow = () => setShow(prev => !prev);
|
||||
|
||||
return (
|
||||
<div className="position-relative w-100">
|
||||
<FloatingLabel
|
||||
controlId="passwordInput"
|
||||
label={
|
||||
<>
|
||||
<FontAwesomeIcon icon={faKey} className="me-2" />
|
||||
Contraseña
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Form.Control
|
||||
type={show ? "text" : "password"}
|
||||
name={name}
|
||||
value={value}
|
||||
placeholder=""
|
||||
onChange={onChange}
|
||||
className="rounded-4 pe-5"
|
||||
disabled={disabled}
|
||||
/>
|
||||
</FloatingLabel>
|
||||
|
||||
<Button
|
||||
variant="link"
|
||||
className="show-button position-absolute end-0 top-50 translate-middle-y me-2"
|
||||
onClick={toggleShow}
|
||||
aria-label="Mostrar contraseña"
|
||||
tabIndex={-1}
|
||||
style={{ zIndex: 2 }}
|
||||
>
|
||||
<FontAwesomeIcon icon={show ? faEyeSlash : faEye} className='fa-lg' />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PasswordInput;
|
||||
75
src/components/Pastes/PasswordModal.jsx
Normal file
75
src/components/Pastes/PasswordModal.jsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { Modal, Button, Form } from 'react-bootstrap';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faLock } from '@fortawesome/free-solid-svg-icons';
|
||||
import { useState } from 'react';
|
||||
import PasswordInput from '@/components/Pastes/PasswordInput';
|
||||
import '@/css/PasswordModal.css';
|
||||
|
||||
const PasswordModal = ({
|
||||
show,
|
||||
onClose,
|
||||
onSubmit,
|
||||
error = null,
|
||||
loading = false
|
||||
}) => {
|
||||
const [password, setPassword] = useState("");
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (password.trim() === "") return;
|
||||
onSubmit(password);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal show={show} onHide={onClose} centered>
|
||||
<Modal.Header
|
||||
style={{ backgroundColor: "var(--modal-bg)" }}
|
||||
>
|
||||
<Modal.Title>
|
||||
<FontAwesomeIcon icon={faLock} className="me-2" />
|
||||
Paste protegida
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
|
||||
<Modal.Body style={{ backgroundColor: "var(--modal-body-bg)" }}>
|
||||
<p className="mb-3">
|
||||
Esta paste está protegida con contraseña. Introduce la clave para continuar.
|
||||
</p>
|
||||
|
||||
<PasswordInput
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
</Modal.Body>
|
||||
|
||||
<Modal.Footer>
|
||||
<Button variant="secondary" onClick={onClose} className='dialog-btn'>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button
|
||||
className='dialog-btn'
|
||||
variant="primary"
|
||||
onClick={handleSubmit}
|
||||
disabled={loading || password.trim() === ""}
|
||||
style={{
|
||||
backgroundColor: "var(--btn-bg)",
|
||||
borderColor: "var(--btn-bg)",
|
||||
color: "var(--btn-text)"
|
||||
}}
|
||||
>
|
||||
{loading ? "Verificando..." : "Acceder"}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
PasswordModal.propTypes = {
|
||||
show: PropTypes.bool.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
error: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
loading: PropTypes.bool
|
||||
};
|
||||
|
||||
export default PasswordModal;
|
||||
@@ -1,21 +1,33 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { Form, Button, Row, Col, FloatingLabel, Alert } from "react-bootstrap";
|
||||
import { Form, Button, Row, Col, FloatingLabel } from "react-bootstrap";
|
||||
import '@/css/PastePanel.css';
|
||||
import PasswordInput from "@/components/Auth/PasswordInput";
|
||||
import PasswordInput from "@/components/Pastes/PasswordInput";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faCircle, faCode, faHeader } from "@fortawesome/free-solid-svg-icons";
|
||||
import CodeEditor from "./CodeEditor";
|
||||
import PublicPasteItem from "./PublicPasteItem";
|
||||
import { useParams, useNavigate } from "react-router-dom";
|
||||
import { useDataContext } from "@/hooks/useDataContext";
|
||||
import PasswordModal from "@/components/Auth/PasswordModal.jsx";
|
||||
import { useError } from '@/context/ErrorContext';
|
||||
import PasswordModal from "@/components/Pastes/PasswordModal.jsx";
|
||||
import { Client } from "@stomp/stompjs";
|
||||
import SockJS from 'sockjs-client';
|
||||
|
||||
const INITIAL_FORM_DATA = {
|
||||
title: "",
|
||||
content: "",
|
||||
syntax: "",
|
||||
burnAfter: false,
|
||||
isPrivate: false,
|
||||
isRt: false,
|
||||
password: ""
|
||||
};
|
||||
|
||||
const PastePanel = ({ onSubmit, publicPastes, mode, pasteKey: propKey, onConnectChange }) => {
|
||||
const { pasteKey: urlPasteKey, rtKey } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const { getData } = useDataContext();
|
||||
const { showError } = useError();
|
||||
|
||||
const activeKey = propKey || urlPasteKey || rtKey;
|
||||
|
||||
@@ -26,32 +38,30 @@ const PastePanel = ({ onSubmit, publicPastes, mode, pasteKey: propKey, onConnect
|
||||
const [stompClient, setStompClient] = useState(null);
|
||||
const [connected, setConnected] = useState(null);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [formData, setFormData] = useState({
|
||||
title: "",
|
||||
content: "",
|
||||
syntax: "",
|
||||
burnAfter: false,
|
||||
isPrivate: false,
|
||||
isRt: false,
|
||||
password: ""
|
||||
});
|
||||
const [formData, setFormData] = useState({ ...INITIAL_FORM_DATA });
|
||||
|
||||
const lastSavedContent = useRef(formData.content);
|
||||
|
||||
const isReadOnly = !!selectedPaste || mode === 'rt';
|
||||
const isRemoteChange = useRef(false);
|
||||
|
||||
// Sincroniza el panel cuando cambia el modo o la clave activa:
|
||||
// - modo static: intenta cargar la paste seleccionada
|
||||
// - modo create: reinicia todo el formulario y errores
|
||||
useEffect(() => {
|
||||
if (mode === 'static' && activeKey) {
|
||||
fetchPaste(activeKey);
|
||||
} else if (mode === 'create') {
|
||||
setSelectedPaste(null);
|
||||
setFormData({ title: "", content: "", syntax: "", burnAfter: false, isPrivate: false, password: "" });
|
||||
setFormData({ ...INITIAL_FORM_DATA });
|
||||
setFieldErrors({});
|
||||
setEditorErrors([]);
|
||||
}
|
||||
}, [activeKey, mode]);
|
||||
|
||||
// Gestiona el ciclo de vida del WebSocket en tiempo real:
|
||||
// conecta al entrar en modo rt y limpia la conexión al salir.
|
||||
// Los cambios remotos marcan `isRemoteChange` para no disparar autosave en bucle.
|
||||
useEffect(() => {
|
||||
if (mode === 'rt' && activeKey) {
|
||||
const socketUrl = import.meta.env.MODE === 'production'
|
||||
@@ -98,6 +108,8 @@ const PastePanel = ({ onSubmit, publicPastes, mode, pasteKey: propKey, onConnect
|
||||
}
|
||||
}, [mode, activeKey]);
|
||||
|
||||
// Autosave con debounce en sesiones RT:
|
||||
// solo guarda cuando el contenido local cambia y evita guardar cambios que vienen del socket.
|
||||
useEffect(() => {
|
||||
if (mode === 'rt' && connected && formData.content) {
|
||||
|
||||
@@ -132,6 +144,7 @@ const PastePanel = ({ onSubmit, publicPastes, mode, pasteKey: propKey, onConnect
|
||||
}
|
||||
}, [formData.content, mode, connected, activeKey]);
|
||||
|
||||
// Actualiza estado local y, si hay sesión RT activa, propaga el cambio al resto de clientes.
|
||||
const handleChange = (key, value) => {
|
||||
const updatedData = { ...formData, [key]: value, isRt: mode === 'rt' };
|
||||
|
||||
@@ -150,8 +163,16 @@ const PastePanel = ({ onSubmit, publicPastes, mode, pasteKey: propKey, onConnect
|
||||
setFieldErrors({});
|
||||
setEditorErrors([]);
|
||||
|
||||
const normalizedTitle = (formData.title ?? "").trim();
|
||||
const payload = {
|
||||
...formData,
|
||||
title: formData.isPrivate
|
||||
? (normalizedTitle || "Sin título")
|
||||
: formData.title
|
||||
};
|
||||
|
||||
try {
|
||||
if (onSubmit) await onSubmit(formData);
|
||||
if (onSubmit) await onSubmit(payload);
|
||||
} catch (error) {
|
||||
if (error.status === 422 && error.errors) {
|
||||
const newFieldErrors = {};
|
||||
@@ -171,6 +192,10 @@ const PastePanel = ({ onSubmit, publicPastes, mode, pasteKey: propKey, onConnect
|
||||
|
||||
const handleSelectPaste = (key) => navigate(`/s/${key}`);
|
||||
|
||||
// Lookup de paste estática:
|
||||
// - 403: pide contraseña
|
||||
// - 404: redirige al inicio
|
||||
// Se hace en modo silencioso para que no abra el modal global en errores esperados.
|
||||
const fetchPaste = async (key, pwd = "") => {
|
||||
const url = import.meta.env.MODE === 'production'
|
||||
? `https://api.miarma.net/v2/mpaste/pastes/s/${key}`
|
||||
@@ -179,18 +204,23 @@ const PastePanel = ({ onSubmit, publicPastes, mode, pasteKey: propKey, onConnect
|
||||
const headers = pwd ? { "X-Paste-Password": pwd } : {};
|
||||
|
||||
try {
|
||||
const response = await getData(url, null, false, headers, true);
|
||||
const response = await getData(url, {
|
||||
params: null,
|
||||
refresh: false,
|
||||
headers,
|
||||
silent: true,
|
||||
});
|
||||
|
||||
if (response) {
|
||||
setSelectedPaste(response);
|
||||
setShowPasswordModal(false);
|
||||
setFormData({
|
||||
title: response.title ?? "",
|
||||
...INITIAL_FORM_DATA,
|
||||
title: (response.title ?? "").trim() || "Sin título",
|
||||
content: response.content ?? "",
|
||||
syntax: response.syntax || "plaintext",
|
||||
burnAfter: response.burnAfter || false,
|
||||
isPrivate: response.isPrivate || false,
|
||||
password: ""
|
||||
isPrivate: response.isPrivate || false
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -311,7 +341,7 @@ const PastePanel = ({ onSubmit, publicPastes, mode, pasteKey: propKey, onConnect
|
||||
</FloatingLabel>
|
||||
|
||||
<div className="d-flex align-items-center ms-1">
|
||||
{isSaving ? (
|
||||
{connected && (isSaving ? (
|
||||
<span className="text-muted" style={{ fontSize: '0.8rem' }}>
|
||||
<FontAwesomeIcon icon={faCircle} className="pulse-animation me-2" style={{ color: '#ffc107', fontSize: '8px' }} />
|
||||
Guardando cambios...
|
||||
@@ -320,7 +350,7 @@ const PastePanel = ({ onSubmit, publicPastes, mode, pasteKey: propKey, onConnect
|
||||
<span className="text-success" style={{ fontSize: '0.8rem' }}>
|
||||
Cambios guardados
|
||||
</span>
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Form.Check
|
||||
|
||||
@@ -9,7 +9,7 @@ const trimContent = (text, maxLength = 80) => {
|
||||
const PublicPasteItem = ({ paste, onSelect }) => {
|
||||
return (
|
||||
<div className="public-paste-item p-2 mb-2 rounded custom-border" style={{ cursor: "pointer" }} onClick={() => onSelect(paste.pasteKey)}>
|
||||
<h5 className="m-0">{paste.title}</h5>
|
||||
<h5 className="m-0">{(paste.title ?? "").trim() || "Sin título"}</h5>
|
||||
<p className="m-0 text-truncate">{trimContent(paste.content, 100)}</p>
|
||||
<small className="custom-text-muted">
|
||||
{new Date(paste.createdAt).toLocaleString()}
|
||||
|
||||
Reference in New Issue
Block a user