Compare commits

...

2 Commits

Author SHA1 Message Date
Jose
69140e6da1 improved: error handling, form state, paste fetching and monaco editor config 2026-03-07 21:51:47 +01:00
Jose
4d0c4d3f26 Add: migration to new backend 2026-02-24 03:39:49 +01:00
13 changed files with 4771 additions and 297 deletions

4524
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
{ {
"apiConfig": { "apiConfig": {
"baseUrl": "https://api.miarma.net/mpaste", "baseUrl": "http://localhost:8081/v2/mpaste",
"endpoints": { "endpoints": {
"pastes": { "pastes": {
"all": "/raw/v1/pastes", "all": "/pastes",
"byId": "/raw/v1/pastes/:paste_id", "byId": "/:pasteId",
"byKey": "/v1/pastes/:paste_key" "byKey": "/:pasteKey"
} }
} }
} }

View File

@@ -1,11 +1,11 @@
{ {
"apiConfig": { "apiConfig": {
"baseUrl": "https://api.miarma.net/mpaste", "baseUrl": "https://api.miarma.net/v2/mpaste",
"endpoints": { "endpoints": {
"pastes": { "pastes": {
"all": "/raw/v1/pastes", "all": "/pastes",
"byId": "/raw/v1/pastes/:paste_id", "byId": "/:pasteId",
"byKey": "/v1/pastes/:paste_key" "byKey": "/:pasteKey"
} }
} }
} }

View File

@@ -9,7 +9,7 @@ function App() {
<div className="fill d-flex flex-column"> <div className="fill d-flex flex-column">
<Routes> <Routes>
<Route path="/" element={<Home />} /> <Route path="/" element={<Home />} />
<Route path="/:paste_key" element={<Home />} /> <Route path="/:pasteKey" element={<Home />} />
</Routes> </Routes>
</div> </div>
</> </>

View File

@@ -1,23 +1,29 @@
import Editor from "@monaco-editor/react"; import Editor from "@monaco-editor/react";
import { useTheme } from "@/hooks/useTheme"; import { useTheme } from "@/hooks/useTheme";
import { useRef } from "react"; import { useEffect, useRef } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { loader } from '@monaco-editor/react'; import * as monaco from "monaco-editor";
loader.config({ const CodeEditor = ({ className = "", syntax, readOnly, onChange, value, editorErrors = [] }) => {
'vs/nls': {
availableLanguages: { '*': 'es' },
},
});
const CodeEditor = ({ className = "", syntax, readOnly, onChange, value }) => {
const { theme } = useTheme(); const { theme } = useTheme();
const editorRef = useRef(null); const editorRef = useRef(null);
const onMount = (editor) => { useEffect(() => {
editorRef.current = editor; if (!editorRef.current) return;
editor.focus(); const model = editorRef.current.getModel();
} if (!model) return;
monaco.editor.setModelMarkers(model, "owner", editorErrors.map(err => ({
startLineNumber: err.lineNumber,
startColumn: 1,
endLineNumber: err.lineNumber,
endColumn: model.getLineLength(err.lineNumber) + 1,
message: err.message,
severity: monaco.MarkerSeverity.Error
})));
}, [editorErrors]);
const onMount = (editor) => { editorRef.current = editor; editor.focus(); }
return ( return (
<div className={`code-editor ${className}`}> <div className={`code-editor ${className}`}>
@@ -25,7 +31,7 @@ const CodeEditor = ({ className = "", syntax, readOnly, onChange, value }) => {
language={syntax || "plaintext"} language={syntax || "plaintext"}
value={value || ""} value={value || ""}
theme={theme === "dark" ? "vs-dark" : "vs-light"} theme={theme === "dark" ? "vs-dark" : "vs-light"}
onChange={(value) => onChange?.(value)} onChange={onChange}
onMount={onMount} onMount={onMount}
options={{ options={{
minimap: { enabled: false }, minimap: { enabled: false },
@@ -37,10 +43,6 @@ const CodeEditor = ({ className = "", syntax, readOnly, onChange, value }) => {
scrollbar: { verticalScrollbarSize: 0 }, scrollbar: { verticalScrollbarSize: 0 },
wordWrap: "on", wordWrap: "on",
formatOnPaste: true, formatOnPaste: true,
suggest: {
showFields: true,
showFunctions: true,
},
readOnly: readOnly || false, readOnly: readOnly || false,
}} }}
/> />
@@ -54,6 +56,7 @@ CodeEditor.propTypes = {
readOnly: PropTypes.bool, readOnly: PropTypes.bool,
onChange: PropTypes.func, onChange: PropTypes.func,
value: PropTypes.string, value: PropTypes.string,
editorErrors: PropTypes.array,
}; };
export default CodeEditor; export default CodeEditor;

View File

@@ -11,78 +11,79 @@ import { useDataContext } from "@/hooks/useDataContext";
import PasswordModal from "@/components/Auth/PasswordModal.jsx"; import PasswordModal from "@/components/Auth/PasswordModal.jsx";
const PastePanel = ({ onSubmit, publicPastes }) => { const PastePanel = ({ onSubmit, publicPastes }) => {
const { paste_key } = useParams(); const { pasteKey } = useParams();
const navigate = useNavigate(); const navigate = useNavigate();
const { getData } = useDataContext(); const { getData } = useDataContext();
const [title, setTitle] = useState("");
const [content, setContent] = useState(""); const [formData, setFormData] = useState({
const [syntax, setSyntax] = useState(""); title: "",
const [burnAfter, setBurnAfter] = useState(false); content: "",
const [isPrivate, setIsPrivate] = useState(false); syntax: "",
const [password, setPassword] = useState(""); burnAfter: false,
isPrivate: false,
password: ""
});
const [selectedPaste, setSelectedPaste] = useState(null); const [selectedPaste, setSelectedPaste] = useState(null);
const [error, setError] = useState(null); const [editorErrors, setEditorErrors] = useState([]);
const [fieldErrors, setFieldErrors] = useState({});
const [showPasswordModal, setShowPasswordModal] = useState(false); const [showPasswordModal, setShowPasswordModal] = useState(false);
const handleSubmit = (e) => { const handleSubmit = async (e) => {
e.preventDefault(); e.preventDefault();
const paste = { setFieldErrors({});
title, setEditorErrors([]);
content,
syntax,
burn_after: burnAfter,
is_private: isPrivate,
password: password || null,
};
if (onSubmit) onSubmit(paste);
};
const handleSelectPaste = async (key) => { try {
navigate(`/${key}`); if (onSubmit) await onSubmit(formData);
}; } catch (error) {
if (error.status === 422 && error.errors) {
const fetchPaste = async (key, pwd = "") => { const newFieldErrors = {};
const url = `https://api.miarma.net/mpaste/v1/pastes/${key}`; Object.entries(error.errors).forEach(([field, msg]) => {
const { data, error } = await getData(url, {}, { if (field === "content") {
'X-Paste-Password': pwd setEditorErrors([{ lineNumber: 1, message: msg }]);
}); } else {
newFieldErrors[field] = msg;
if (error) { }
if (error?.status === 401) { });
setShowPasswordModal(true); setFieldErrors(newFieldErrors);
return;
} else { } else {
setError(error); showError(error);
setSelectedPaste(null);
return;
} }
} }
setError(null);
setSelectedPaste(data);
setTitle(data.title);
setContent(data.content);
setSyntax(data.syntax || "plaintext");
}; };
useEffect(() => { const handleSelectPaste = async (key) => navigate(`/${key}`);
if (paste_key) fetchPaste(paste_key);
}, [paste_key]); const fetchPaste = async (key, pwd = "") => {
const url = import.meta.env.MODE === 'production'
? `https://api.miarma.net/v2/mpaste/pastes/${key}`
: `http://localhost:8081/v2/mpaste/pastes/${key}`;
const data = await getData(url, { password: pwd }, false);
if (!data) return;
setSelectedPaste(data);
setFormData({
title: data.title ?? "",
content: data.content ?? "",
syntax: data.syntax || "plaintext",
burnAfter: data.burnAfter || false,
isPrivate: data.isPrivate || false,
password: ""
});
};
useEffect(() => { if (pasteKey) fetchPaste(pasteKey); }, [pasteKey]);
const handleChange = (key, value) => {
setFormData(prev => ({ ...prev, [key]: value }));
};
return ( return (
<> <>
<div className="paste-panel border-0 flex-fill d-flex flex-column min-h-0 p-3"> <div className="paste-panel border-0 flex-fill d-flex flex-column min-h-0 p-3">
{error &&
<Alert variant="danger" onClose={() => setError(null)} dismissible>
<strong>
<span className="text-danger">{
error.status == 404 ? "404: Paste no encontrada." :
"Ha ocurrido un error al cargar la paste."
}</span>
</strong>
</Alert>
}
<Form onSubmit={handleSubmit} className="flex-fill d-flex flex-column min-h-0"> <Form onSubmit={handleSubmit} className="flex-fill d-flex flex-column min-h-0">
<Row className="g-3 flex-fill min-h-0"> <Row className="g-3 flex-fill min-h-0">
<Col xs={12} lg={2} className="order-last order-lg-first d-flex flex-column flex-fill min-h-0 overflow-hidden"> <Col xs={12} lg={2} className="order-last order-lg-first d-flex flex-column flex-fill min-h-0 overflow-hidden">
@@ -93,7 +94,7 @@ const PastePanel = ({ onSubmit, publicPastes }) => {
{publicPastes && publicPastes.length > 0 ? ( {publicPastes && publicPastes.length > 0 ? (
publicPastes.map((paste) => ( publicPastes.map((paste) => (
<PublicPasteItem <PublicPasteItem
key={paste.paste_key} key={paste.pasteKey}
paste={paste} paste={paste}
onSelect={handleSelectPaste} onSelect={handleSelectPaste}
/> />
@@ -108,10 +109,11 @@ const PastePanel = ({ onSubmit, publicPastes }) => {
<Col xs={12} lg={7} className="d-flex flex-column flex-fill min-h-0 overflow-hidden"> <Col xs={12} lg={7} className="d-flex flex-column flex-fill min-h-0 overflow-hidden">
<CodeEditor <CodeEditor
className="flex-fill custom-border rounded-4 overflow-hidden pt-4 pe-4" className="flex-fill custom-border rounded-4 overflow-hidden pt-4 pe-4"
syntax={syntax} syntax={formData.syntax}
readOnly={!!selectedPaste} readOnly={!!selectedPaste}
onChange={selectedPaste ? undefined : setContent} onChange={(val) => handleChange("content", val)}
value={content} value={formData.content ?? ""}
editorErrors={editorErrors}
/> />
</Col> </Col>
@@ -129,10 +131,11 @@ const PastePanel = ({ onSubmit, publicPastes }) => {
<Form.Control <Form.Control
disabled={!!selectedPaste} disabled={!!selectedPaste}
type="text" type="text"
placeholder="Título de la paste" value={formData.title}
value={title} onChange={(e) => handleChange("title", e.target.value)}
onChange={(e) => setTitle(e.target.value)} isInvalid={!!fieldErrors.title}
/> />
<Form.Control.Feedback type="invalid">{fieldErrors.title}</Form.Control.Feedback>
</FloatingLabel> </FloatingLabel>
<FloatingLabel <FloatingLabel
@@ -146,8 +149,8 @@ const PastePanel = ({ onSubmit, publicPastes }) => {
> >
<Form.Select <Form.Select
disabled={!!selectedPaste} disabled={!!selectedPaste}
value={syntax} value={formData.syntax}
onChange={(e) => setSyntax(e.target.value)} onChange={(e) => handleChange("syntax", e.target.value)}
> >
<option value="">Sin resaltado</option> <option value="">Sin resaltado</option>
<option value="javascript">JavaScript</option> <option value="javascript">JavaScript</option>
@@ -186,8 +189,8 @@ const PastePanel = ({ onSubmit, publicPastes }) => {
disabled={!!selectedPaste} disabled={!!selectedPaste}
id="burnAfter" id="burnAfter"
label="volátil" label="volátil"
checked={burnAfter} checked={formData.burnAfter}
onChange={(e) => setBurnAfter(e.target.checked)} onChange={(e) => handleChange("burnAfter", e.target.checked)}
className="ms-1 d-flex gap-2 align-items-center" className="ms-1 d-flex gap-2 align-items-center"
/> />
@@ -196,13 +199,13 @@ const PastePanel = ({ onSubmit, publicPastes }) => {
disabled={!!selectedPaste} disabled={!!selectedPaste}
id="isPrivate" id="isPrivate"
label="privado" label="privado"
checked={isPrivate} checked={formData.isPrivate}
onChange={(e) => setIsPrivate(e.target.checked)} onChange={(e) => handleChange("isPrivate", e.target.checked)}
className="ms-1 d-flex gap-2 align-items-center" className="ms-1 d-flex gap-2 align-items-center"
/> />
{isPrivate && ( {formData.isPrivate && (
<PasswordInput onChange={(e) => setPassword(e.target.value)} /> <PasswordInput onChange={(e) => handleChange("password", e.target.value)} />
)} )}
<div className="d-flex justify-content-end"> <div className="d-flex justify-content-end">
@@ -224,7 +227,7 @@ const PastePanel = ({ onSubmit, publicPastes }) => {
onClose={() => setShowPasswordModal(false)} onClose={() => setShowPasswordModal(false)}
onSubmit={(pwd) => { onSubmit={(pwd) => {
setShowPasswordModal(false); setShowPasswordModal(false);
fetchPaste(paste_key, pwd); // reintentas con la pass fetchPaste(pasteKey, pwd);
}} }}
/> />
</> </>

View File

@@ -8,11 +8,11 @@ const trimContent = (text, maxLength = 80) => {
const PublicPasteItem = ({ paste, onSelect }) => { const PublicPasteItem = ({ paste, onSelect }) => {
return ( return (
<div className="public-paste-item p-2 mb-2 rounded custom-border" style={{ cursor: "pointer" }} onClick={() => onSelect(paste.paste_key)}> <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}</h5>
<p className="m-0 text-truncate">{trimContent(paste.content, 100)}</p> <p className="m-0 text-truncate">{trimContent(paste.content, 100)}</p>
<small className="custom-text-muted"> <small className="custom-text-muted">
{new Date(paste.created_at).toLocaleString()} {new Date(paste.createdAt).toLocaleString()}
</small> </small>
</div> </div>
); );
@@ -22,7 +22,7 @@ PublicPasteItem.propTypes = {
paste: PropTypes.shape({ paste: PropTypes.shape({
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
content: PropTypes.string.isRequired, content: PropTypes.string.isRequired,
created_at: PropTypes.string.isRequired, createdAt: PropTypes.string.isRequired,
}).isRequired, }).isRequired,
}; };

View File

@@ -1,98 +0,0 @@
import { useState, useEffect, createContext } from "react";
import createAxiosInstance from "@/api/axiosInstance";
import { useConfig } from "@/hooks/useConfig";
export const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const axios = createAxiosInstance();
const { config } = useConfig();
const [user, setUser] = useState(() => JSON.parse(localStorage.getItem("user")) || null);
const [token, setToken] = useState(() => localStorage.getItem("token"));
const [authStatus, setAuthStatus] = useState("checking");
const [error, setError] = useState(null);
useEffect(() => {
if (!config) return;
if (!token) {
setAuthStatus("unauthenticated");
return;
}
const BASE_URL = config.apiConfig.baseUrl;
const VALIDATE_URL = `${BASE_URL}${config.apiConfig.endpoints.auth.validateToken}`;
const checkAuth = async () => {
try {
const res = await axios.get(VALIDATE_URL, {
headers: { Authorization: `Bearer ${token}` },
});
if (res.status === 200) {
setAuthStatus("authenticated");
} else {
logout();
}
} catch (err) {
console.error("Error validando token:", err);
logout();
}
};
checkAuth();
}, [token, config]);
const login = async (formData) => {
setError(null);
const BASE_URL = config.apiConfig.baseUrl;
const LOGIN_URL = `${BASE_URL}${config.apiConfig.endpoints.auth.login}`;
try {
const res = await axios.post(LOGIN_URL, formData);
const { token, member, tokenTime } = res.data.data;
localStorage.setItem("token", token);
localStorage.setItem("user", JSON.stringify(member));
localStorage.setItem("tokenTime", tokenTime);
setToken(token);
setUser(member);
setAuthStatus("authenticated");
} catch (err) {
console.error("Error al iniciar sesión:", err);
let message = "Ha ocurrido un error inesperado.";
if (err.response) {
const { status, data } = err.response;
if (status === 400) {
message = "Usuario o contraseña incorrectos.";
} else if (status === 403) {
message = "Tu cuenta está inactiva o ha sido suspendida.";
} else if (status === 404) {
message = "Usuario no encontrado.";
} else if (data?.message) {
message = data.message;
}
}
setError(message);
throw new Error(message);
}
};
const logout = () => {
localStorage.clear();
setUser(null);
setToken(null);
setAuthStatus("unauthenticated");
};
return (
<AuthContext.Provider value={{ user, token, authStatus, login, logout, error }}>
{children}
</AuthContext.Provider>
);
};

View File

@@ -4,8 +4,8 @@ import { useData } from "../hooks/useData";
export const DataContext = createContext(); export const DataContext = createContext();
export const DataProvider = ({ config, children }) => { export const DataProvider = ({ config, onError, children }) => {
const data = useData(config); const data = useData(config, onError);
return ( return (
<DataContext.Provider value={data}> <DataContext.Provider value={data}>

View File

@@ -0,0 +1,36 @@
import { createContext, useState, useContext } from 'react';
import NotificationModal from '../components/NotificationModal';
const ErrorContext = createContext();
export const ErrorProvider = ({ children }) => {
const [error, setError] = useState(null);
const showError = (err) => {
setError({
title: err.status ? `Error ${err.status}` : "Error",
message: err.message,
variant: 'danger'
});
};
const closeError = () => setError(null);
return (
<ErrorContext.Provider value={{ showError }}>
{children}
{error && (
<NotificationModal
show={true}
onClose={closeError}
title={error.title}
message={error.message}
variant='danger'
buttons={[{ label: "Aceptar", variant: "danger", onClick: closeError }]}
/>
)}
</ErrorContext.Provider>
);
};
export const useError = () => useContext(ErrorContext);

View File

@@ -1,7 +1,7 @@
import { useState, useEffect, useCallback, useRef } from "react"; import { useState, useEffect, useCallback, useRef } from "react";
import axios from "axios"; import axios from "axios";
export const useData = (config) => { export const useData = (config, onError) => {
const [data, setData] = useState(null); const [data, setData] = useState(null);
const [dataLoading, setLoading] = useState(true); const [dataLoading, setLoading] = useState(true);
const [dataError, setError] = useState(null); const [dataError, setError] = useState(null);
@@ -13,10 +13,59 @@ export const useData = (config) => {
} }
}, [config]); }, [config]);
const getAuthHeaders = () => ({ const getAuthHeaders = (isFormData = false) => {
"Content-Type": "application/json", const token = localStorage.getItem("token");
"Authorization": `Bearer ${localStorage.getItem("token")}`,
}); const headers = {};
if (token) headers.Authorization = `Bearer ${token}`;
if (!isFormData) {
headers["Content-Type"] = "application/json";
}
return headers;
};
const handleAxiosError = (err) => {
if (err.response && err.response.data) {
const data = err.response.data;
if (data.status === 422 && data.errors) {
return {
status: 422,
errors: data.errors,
path: data.path ?? null,
timestamp: data.timestamp ?? null,
};
}
return {
status: data.status ?? err.response.status,
error: data.error ?? null,
message: data.message ?? err.response.statusText ?? "Error desconocido",
path: data.path ?? null,
timestamp: data.timestamp ?? null,
};
}
if (err.request) {
return {
status: null,
error: "Network Error",
message: "No se pudo conectar al servidor",
path: null,
timestamp: new Date().toISOString(),
};
}
return {
status: null,
error: "Client Error",
message: err.message || "Error desconocido",
path: null,
timestamp: new Date().toISOString(),
};
};
const fetchData = useCallback(async () => { const fetchData = useCallback(async () => {
const current = configRef.current; const current = configRef.current;
@@ -30,104 +79,62 @@ export const useData = (config) => {
headers: getAuthHeaders(), headers: getAuthHeaders(),
params: current.params, params: current.params,
}); });
setData(response.data.data); setData(response.data);
} catch (err) { } catch (err) {
setError(err.response?.data?.message || err.message); const error = handleAxiosError(err);
setError(error);
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, []); }, []);
useEffect(() => { useEffect(() => {
if (config?.baseUrl) { if (config?.baseUrl) fetchData();
fetchData();
}
}, [config, fetchData]); }, [config, fetchData]);
const getData = async (url, params = {}, headers = {}) => { const requestWrapper = async (method, endpoint, payload = null, refresh = false) => {
try { try {
const response = await axios.get(url, { const isFormData = payload instanceof FormData;
headers: { ...getAuthHeaders(), ...headers }, const headers = getAuthHeaders(isFormData);
params, const cfg = { headers };
}); let response;
return { data: response.data.data, error: null };
} catch (err) {
return {
data: null,
error: {
status: err.response?.data?.status || err.response?.status,
message: err.response?.data?.message || err.message,
},
};
}
};
const postData = async (endpoint, payload) => { if (method === "get") {
const headers = { if (payload) cfg.params = payload;
Authorization: `Bearer ${localStorage.getItem("token")}`, response = await axios.get(endpoint, cfg);
...(payload instanceof FormData ? {} : { "Content-Type": "application/json" }), } else if (method === "delete") {
}; if (payload) cfg.data = payload;
const response = await axios.post(endpoint, payload, { headers }); response = await axios.delete(endpoint, cfg);
await fetchData(); } else {
return response.data.data; response = await axios[method](endpoint, payload, cfg);
};
const postDataValidated = async (endpoint, payload) => {
try {
const headers = {
Authorization: `Bearer ${localStorage.getItem("token")}`,
...(payload instanceof FormData ? {} : { "Content-Type": "application/json" }),
};
const response = await axios.post(endpoint, payload, { headers });
return { data: response.data.data, errors: null };
} catch (err) {
const raw = err.response?.data?.message;
let parsed = {};
try {
parsed = JSON.parse(raw);
} catch {
return { data: null, errors: { general: raw || err.message } };
} }
return { data: null, errors: parsed }; if (refresh) await fetchData();
return response.data;
} catch (err) {
const error = handleAxiosError(err);
if (error.status !== 422 && onError) {
onError(error);
}
setError(error);
throw error;
} }
}; };
const putData = async (endpoint, payload) => { const clearError = () => setError(null);
const response = await axios.put(endpoint, payload, {
headers: getAuthHeaders(),
});
await fetchData();
return response.data.data;
};
const deleteData = async (endpoint) => {
const response = await axios.delete(endpoint, {
headers: getAuthHeaders(),
});
await fetchData();
return response.data.data;
};
const deleteDataWithBody = async (endpoint, payload) => {
const response = await axios.delete(endpoint, {
headers: getAuthHeaders(),
data: payload,
});
await fetchData();
return response.data.data;
};
return { return {
data, data,
dataLoading, dataLoading,
dataError, dataError,
getData, clearError,
postData, getData: (url, params, refresh = true) => requestWrapper("get", url, params, refresh),
postDataValidated, postData: (url, body, refresh = true) => requestWrapper("post", url, body, refresh),
putData, putData: (url, body, refresh = true) => requestWrapper("put", url, body, refresh),
deleteData, deleteData: (url, refresh = true) => requestWrapper("delete", url, null, refresh),
deleteDataWithBody, deleteDataWithBody: (url, body, refresh = true) => requestWrapper("delete", url, body, refresh)
}; };
}; };

View File

@@ -5,7 +5,6 @@ import { createRoot } from 'react-dom/client'
import App from '@/App.jsx' import App from '@/App.jsx'
import { BrowserRouter } from 'react-router-dom' import { BrowserRouter } from 'react-router-dom'
import { ThemeProvider } from '@/context/ThemeContext' import { ThemeProvider } from '@/context/ThemeContext'
import { AuthProvider } from '@/context/AuthContext'
import { ConfigProvider } from '@/context/ConfigContext.jsx' import { ConfigProvider } from '@/context/ConfigContext.jsx'
/* CSS */ /* CSS */
@@ -15,18 +14,19 @@ import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css"; import "slick-carousel/slick/slick-theme.css";
import '@/css/index.css' import '@/css/index.css'
import { SearchProvider } from './context/SearchContext' import { SearchProvider } from './context/SearchContext'
import { ErrorProvider } from './context/ErrorContext'
createRoot(document.getElementById('root')).render( createRoot(document.getElementById('root')).render(
<StrictMode> <StrictMode>
<ConfigProvider> <ConfigProvider>
<ThemeProvider> <ThemeProvider>
<AuthProvider> <ErrorProvider>
<BrowserRouter> <BrowserRouter>
<SearchProvider> <SearchProvider>
<App /> <App />
</SearchProvider> </SearchProvider>
</BrowserRouter> </BrowserRouter>
</AuthProvider> </ErrorProvider>
</ThemeProvider> </ThemeProvider>
</ConfigProvider> </ConfigProvider>
</StrictMode> </StrictMode>

View File

@@ -7,22 +7,21 @@ import { useState } from 'react';
import { DataProvider } from '@/context/DataContext'; import { DataProvider } from '@/context/DataContext';
import NotificationModal from '@/components/NotificationModal'; import NotificationModal from '@/components/NotificationModal';
import { useSearch } from "@/context/SearchContext"; import { useSearch } from "@/context/SearchContext";
import { useError } from '@/context/ErrorContext';
const Home = () => { const Home = () => {
const { config, configLoading } = useConfig(); const { config, configLoading } = useConfig();
const { showError } = useError();
if (configLoading) return <p><LoadingIcon /></p>; if (configLoading) return <p><LoadingIcon /></p>;
const reqConfig = { const reqConfig = {
baseUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.pastes.all}`, baseUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.pastes.all}`,
params: { params: {}
_sort: 'created_at',
_order: 'desc',
},
}; };
return ( return (
<DataProvider config={reqConfig}> <DataProvider config={reqConfig} onError={showError}>
<HomeContent reqConfig={reqConfig} /> <HomeContent reqConfig={reqConfig} />
</DataProvider> </DataProvider>
); );
@@ -45,8 +44,8 @@ const HomeContent = ({ reqConfig }) => {
const handleSubmit = async (paste) => { const handleSubmit = async (paste) => {
try { try {
const createdPaste = await postData(reqConfig.baseUrl, paste); const createdPaste = await postData(reqConfig.baseUrl, paste);
if (createdPaste && createdPaste.is_private) { if (createdPaste && createdPaste.isPrivate) {
setKey(createdPaste.paste_key); setKey(createdPaste.pasteKey);
} }
} catch (error) { } catch (error) {
setError(error); setError(error);