Updated template with better features
This commit is contained in:
@@ -8,8 +8,12 @@ export const AuthProvider = ({ children }) => {
|
|||||||
const axios = createAxiosInstance();
|
const axios = createAxiosInstance();
|
||||||
const { config } = useConfig();
|
const { config } = useConfig();
|
||||||
|
|
||||||
const [user, setUser] = useState(() => JSON.parse(localStorage.getItem("user")) || null);
|
|
||||||
const [token, setToken] = useState(() => localStorage.getItem("token"));
|
const [token, setToken] = useState(() => localStorage.getItem("token"));
|
||||||
|
const [identity, setIdentity] = useState(() => {
|
||||||
|
const stored = localStorage.getItem("identity");
|
||||||
|
return stored ? JSON.parse(stored) : null;
|
||||||
|
});
|
||||||
|
|
||||||
const [authStatus, setAuthStatus] = useState("checking");
|
const [authStatus, setAuthStatus] = useState("checking");
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
@@ -29,6 +33,7 @@ export const AuthProvider = ({ children }) => {
|
|||||||
const res = await axios.get(VALIDATE_URL, {
|
const res = await axios.get(VALIDATE_URL, {
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
setAuthStatus("authenticated");
|
setAuthStatus("authenticated");
|
||||||
} else {
|
} else {
|
||||||
@@ -45,53 +50,121 @@ export const AuthProvider = ({ children }) => {
|
|||||||
|
|
||||||
const login = async (formData) => {
|
const login = async (formData) => {
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
const BASE_URL = config.apiConfig.baseUrl;
|
const BASE_URL = config.apiConfig.baseUrl;
|
||||||
const LOGIN_URL = `${BASE_URL}${config.apiConfig.endpoints.auth.login}`;
|
const LOGIN_URL = `${BASE_URL}${config.apiConfig.endpoints.auth.login}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await axios.post(LOGIN_URL, formData);
|
const res = await axios.post(LOGIN_URL, formData);
|
||||||
const { token, member, tokenTime } = res.data.data;
|
|
||||||
|
const { token, user, account, metadata } = res.data;
|
||||||
|
|
||||||
|
const identity = {
|
||||||
|
user,
|
||||||
|
account,
|
||||||
|
metadata,
|
||||||
|
};
|
||||||
|
|
||||||
localStorage.setItem("token", token);
|
localStorage.setItem("token", token);
|
||||||
localStorage.setItem("user", JSON.stringify(member));
|
localStorage.setItem("identity", JSON.stringify(identity));
|
||||||
localStorage.setItem("tokenTime", tokenTime);
|
|
||||||
|
|
||||||
setToken(token);
|
setToken(token);
|
||||||
setUser(member);
|
setIdentity(identity);
|
||||||
setAuthStatus("authenticated");
|
setAuthStatus("authenticated");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error al iniciar sesión:", err);
|
console.error("Error al iniciar sesión:", err);
|
||||||
|
|
||||||
let message = "Ha ocurrido un error inesperado.";
|
let message = "Ha ocurrido un error inesperado.";
|
||||||
|
|
||||||
if (err.response) {
|
if (err.response) {
|
||||||
const { status, data } = err.response;
|
const { status, data } = err.response;
|
||||||
|
|
||||||
if (status === 400) {
|
if (status === 400) {
|
||||||
message = "Usuario o contraseña incorrectos.";
|
message = "Usuario o contraseña incorrectos.";
|
||||||
} else if (status === 403) {
|
} else if (status === 403) {
|
||||||
message = "Tu cuenta está inactiva o ha sido suspendida.";
|
message = "Tu cuenta está inactiva o suspendida.";
|
||||||
} else if (status === 404) {
|
} else if (status === 404) {
|
||||||
message = "Usuario no encontrado.";
|
message = "Usuario no encontrado.";
|
||||||
} else if (data?.message) {
|
} else if (data?.message) {
|
||||||
message = data.message;
|
message = data.message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setError(message);
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const register = async (formData) => {
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
const BASE_URL = config.apiConfig.baseUrl;
|
||||||
|
const REGISTER_URL = `${BASE_URL}${config.apiConfig.endpoints.auth.register}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await axios.post(REGISTER_URL, formData);
|
||||||
|
|
||||||
|
const { token, user, account, metadata } = res.data;
|
||||||
|
|
||||||
|
const identity = {
|
||||||
|
user,
|
||||||
|
account,
|
||||||
|
metadata,
|
||||||
|
};
|
||||||
|
|
||||||
|
localStorage.setItem("token", token);
|
||||||
|
localStorage.setItem("identity", JSON.stringify(identity));
|
||||||
|
|
||||||
|
setToken(token);
|
||||||
|
setIdentity(identity);
|
||||||
|
setAuthStatus("authenticated");
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error al registrarse:", 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 suspendida.";
|
||||||
|
} else if (status === 404) {
|
||||||
|
message = "Usuario no encontrado.";
|
||||||
|
} else if (data?.message) {
|
||||||
|
message = data.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setError(message);
|
setError(message);
|
||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
localStorage.clear();
|
localStorage.removeItem("token");
|
||||||
setUser(null);
|
localStorage.removeItem("identity");
|
||||||
|
setIdentity(null);
|
||||||
setToken(null);
|
setToken(null);
|
||||||
setAuthStatus("unauthenticated");
|
setAuthStatus("unauthenticated");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const clearError = () => setError(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthContext.Provider value={{ user, token, authStatus, login, logout, error }}>
|
<AuthContext.Provider
|
||||||
|
value={{
|
||||||
|
identity, // { user, account, metadata }
|
||||||
|
token,
|
||||||
|
authStatus,
|
||||||
|
login,
|
||||||
|
register,
|
||||||
|
logout,
|
||||||
|
error,
|
||||||
|
clearError
|
||||||
|
}}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</AuthContext.Provider>
|
</AuthContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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}>
|
||||||
|
|||||||
40
src/context/ErrorContext.jsx
Normal file
40
src/context/ErrorContext.jsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { createContext, useState, useContext } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
ErrorProvider.propTypes = {
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useError = () => useContext(ErrorContext);
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { AuthContext } from "@/context/AuthContext";
|
import { AuthContext } from "../context/AuthContext";
|
||||||
|
|
||||||
export const useAuth = () => useContext(AuthContext);
|
export const useAuth = () => useContext(AuthContext);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { ConfigContext } from "@/context/ConfigContext.jsx";
|
import { ConfigContext } from "../context/ConfigContext.jsx";
|
||||||
|
|
||||||
export const useConfig = () => useContext(ConfigContext);
|
export const useConfig = () => useContext(ConfigContext);
|
||||||
|
|||||||
@@ -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,101 +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 = {}) => {
|
const requestWrapper = async (method, endpoint, payload = null, refresh = false) => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(url, {
|
const isFormData = payload instanceof FormData;
|
||||||
headers: getAuthHeaders(),
|
const headers = getAuthHeaders(isFormData);
|
||||||
params,
|
const cfg = { headers };
|
||||||
});
|
let response;
|
||||||
return { data: response.data.data, error: null };
|
|
||||||
} catch (err) {
|
|
||||||
return {
|
|
||||||
data: null,
|
|
||||||
error: 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 response = await axios.put(endpoint, payload, {
|
|
||||||
headers: getAuthHeaders(),
|
|
||||||
});
|
|
||||||
await fetchData();
|
|
||||||
return response.data.data;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteData = async (endpoint) => {
|
const clearError = () => setError(null);
|
||||||
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)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { DataContext } from "@/context/DataContext";
|
import { DataContext } from "../context/DataContext";
|
||||||
|
|
||||||
export const useDataContext = () => useContext(DataContext);
|
export const useDataContext = () => useContext(DataContext);
|
||||||
|
|||||||
35
src/hooks/useRequestCount.js
Normal file
35
src/hooks/useRequestCount.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { useConfig } from './useConfig';
|
||||||
|
|
||||||
|
const useRequestCount = () => {
|
||||||
|
const { config } = useConfig();
|
||||||
|
const [count, setCount] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!config) return;
|
||||||
|
|
||||||
|
const fetchCount = async () => {
|
||||||
|
try {
|
||||||
|
const res = await axios.get(
|
||||||
|
config.apiConfig.baseUrl + config.apiConfig.endpoints.requests.count,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
setCount(res.data.count);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('❌ Error al obtener el número de solicitudes:', err.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchCount();
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
|
return count;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useRequestCount;
|
||||||
@@ -32,7 +32,7 @@ const useSessionRenewal = () => {
|
|||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
logout();
|
logout();
|
||||||
}
|
}
|
||||||
}, 10000); // revisa cada 10 segundos
|
}, 10000);
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [alreadyWarned, logout]);
|
}, [alreadyWarned, logout]);
|
||||||
@@ -50,7 +50,7 @@ const useSessionRenewal = () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(
|
const response = await axios.get(
|
||||||
`${config.apiConfig.baseUrl}${config.apiConfig.endpoints.auth.refreshToken}`,
|
`${config.apiConfig.coreUrl}${config.apiConfig.endpoints.auth.refreshToken}`,
|
||||||
null,
|
null,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
@@ -59,7 +59,7 @@ const useSessionRenewal = () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const newToken = response.data.data.token;
|
const newToken = response.data.token;
|
||||||
localStorage.setItem("token", newToken);
|
localStorage.setItem("token", newToken);
|
||||||
setShowModal(false);
|
setShowModal(false);
|
||||||
setAlreadyWarned(false);
|
setAlreadyWarned(false);
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
export const renderErrorAlert = (error, options = {}) => {
|
|
||||||
const { className = 'alert alert-danger py-1 px-2 small', role = 'alert' } = options;
|
|
||||||
|
|
||||||
if (!error) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={className} role={role}>
|
|
||||||
{typeof error === 'string' ? error : 'An unexpected error occurred.'}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const resetErrorIfEditEnds = (editMode, setError) => {
|
|
||||||
if (!editMode) setError(null);
|
|
||||||
};
|
|
||||||
5
src/util/array.js
Normal file
5
src/util/array.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
const random = (arr) => {
|
||||||
|
return arr[Math.floor(Math.random() * arr.length)]
|
||||||
|
}
|
||||||
|
|
||||||
|
export { random }
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
export const DateParser = {
|
|
||||||
sqlToString: (sqlDate) => {
|
|
||||||
const [datePart] = sqlDate.split('T');
|
|
||||||
const [year, month, day] = datePart.split('-');
|
|
||||||
return `${day}/${month}/${year}`;
|
|
||||||
},
|
|
||||||
|
|
||||||
timestampToString: (timestamp) => {
|
|
||||||
const [datePart] = timestamp.split('T');
|
|
||||||
const [year, month, day] = datePart.split('-');
|
|
||||||
return `${day}/${month}/${year}`;
|
|
||||||
},
|
|
||||||
|
|
||||||
isoToStringWithTime: (isoString) => {
|
|
||||||
if (!isoString) return '—';
|
|
||||||
|
|
||||||
const date = new Date(isoString);
|
|
||||||
if (isNaN(date)) return '—'; // Para proteger aún más por si llega basura
|
|
||||||
|
|
||||||
return new Intl.DateTimeFormat('es-ES', {
|
|
||||||
day: '2-digit',
|
|
||||||
month: '2-digit',
|
|
||||||
year: 'numeric',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
hour12: false,
|
|
||||||
timeZone: 'Europe/Madrid'
|
|
||||||
}).format(date);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
export const errorParser = (err) => {
|
|
||||||
const message = err.response?.data?.message;
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(message);
|
|
||||||
return Object.values(parsed)[0];
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
} catch (e) {
|
|
||||||
return message || err.message || "Unknown error";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
export const generateSecurePassword = (length = 12) => {
|
|
||||||
const upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
||||||
const lower = 'abcdefghijklmnopqrstuvwxyz';
|
|
||||||
const digits = '0123456789';
|
|
||||||
const symbols = '!@#$%^&*'; // <- compatibles con bcrypt
|
|
||||||
const all = upper + lower + digits + symbols;
|
|
||||||
|
|
||||||
if (length < 8) length = 8;
|
|
||||||
|
|
||||||
const getRand = (chars) => chars[Math.floor(Math.random() * chars.length)];
|
|
||||||
|
|
||||||
let password = [
|
|
||||||
getRand(upper),
|
|
||||||
getRand(lower),
|
|
||||||
getRand(digits),
|
|
||||||
getRand(symbols),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (let i = password.length; i < length; i++) {
|
|
||||||
password.push(getRand(all));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = password.length - 1; i > 0; i--) {
|
|
||||||
const j = Math.floor(Math.random() * (i + 1));
|
|
||||||
[password[i], password[j]] = [password[j], password[i]];
|
|
||||||
}
|
|
||||||
|
|
||||||
return password.join('');
|
|
||||||
};
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
export const parseJwt = (token) => {
|
|
||||||
try {
|
|
||||||
return JSON.parse(atob(token.split('.')[1]));
|
|
||||||
} catch (e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user