Run deno fmt

This commit is contained in:
Luis Eduardo
2025-02-04 18:56:15 +00:00
parent c99d234040
commit f0a60bd482
31 changed files with 433 additions and 356 deletions
+5 -5
View File
@@ -2,10 +2,10 @@
This package includes a custom logger for the project.
Functions are exposed for logging messages easily; it is imperative that
these functions are used throughout the project to maintain a standard in
log messages.
Functions are exposed for logging messages easily; it is imperative that these
functions are used throughout the project to maintain a standard in log
messages.
If necessary, the `logWriter` can be edited so that in addition to writing to
stdout, it can also write to a file or send the logs to a server like
Better Stack or New Relic.
stdout, it can also write to a file or send the logs to a server like Better
Stack or New Relic.
+16 -7
View File
@@ -1,19 +1,24 @@
# paginateutil
This package provides a utility for paginating data. It should be used in conjunction with database queries to paginate results.
This package provides a utility for paginating data. It should be used in
conjunction with database queries to paginate results.
Use these utilities to paginate results and, above all, to return a common structure in the different places where pagination is performed, maintaining consistency throughout the project.
Use these utilities to paginate results and, above all, to return a common
structure in the different places where pagination is performed, maintaining
consistency throughout the project.
## Usage
### 1. Define your queries:
- **PaginateCount:** This function should return the total number of records that match the query.
- **PaginateCount:** This function should return the total number of records
that match the query.
- **Paginate:** This function should return the paginated records.
### 2. Create the offset from the request parameters:
Use the `CreateOffsetFromParams` function to create the offset from the request parameters needed to paginate the results.
Use the `CreateOffsetFromParams` function to create the offset from the request
parameters needed to paginate the results.
### 3. Define the signature of your pagination function:
@@ -38,9 +43,13 @@ func PaginateXYZ(
### Example
Refer to `internal/service/backups/paginate_backups.go` for an example of how to use the `paginateutil` package.
Refer to `internal/service/backups/paginate_backups.go` for an example of how to
use the `paginateutil` package.
## Notes
- **Default Values:** Ensure to set reasonable default values for pagination parameters (`page` and `limit`) if they are not provided.
- **Common Response Structure:** Use `CreatePaginateResponse` to generate a common response structure that includes information about pagination, such as the total number of items, the current page, and the number of items per page.
- **Default Values:** Ensure to set reasonable default values for pagination
parameters (`page` and `limit`) if they are not provided.
- **Common Response Structure:** Use `CreatePaginateResponse` to generate a
common response structure that includes information about pagination, such as
the total number of items, the current page, and the number of items per page.
+2 -1
View File
@@ -1,5 +1,6 @@
# Validate functions
All the functions in this directory are used to validate data, all of them _MUST BE PURE FUNCTIONS_.
All the functions in this directory are used to validate data, all of them _MUST
BE PURE FUNCTIONS_.
https://en.wikipedia.org/wiki/Pure_function
+9 -3
View File
@@ -1,9 +1,15 @@
# reqctx
`reqctx` is a utility package designed to manage request-specific context values in this project. It helps to encapsulate authentication status and user information within the Echo request context in a type-safe manner.
`reqctx` is a utility package designed to manage request-specific context values
in this project. It helps to encapsulate authentication status and user
information within the Echo request context in a type-safe manner.
## Purpose
When developing web applications, it is common to pass values such as authentication status and user information through the request lifecycle. Using Echo's built-in context (`echo.Context`) can lead to potential issues such as typographical errors, lack of type safety, and reduced code readability.
When developing web applications, it is common to pass values such as
authentication status and user information through the request lifecycle. Using
Echo's built-in context (`echo.Context`) can lead to potential issues such as
typographical errors, lack of type safety, and reduced code readability.
`reqctx` addresses these issues by providing a structured way to manage these context values.
`reqctx` addresses these issues by providing a structured way to manage these
context values.
+1 -1
View File
@@ -1,3 +1,3 @@
[x-cloak] {
display: none !important;
}
}
@@ -5,4 +5,4 @@ html {
.table tbody tr {
@apply hover:bg-base-200;
}
}
+1 -1
View File
@@ -8,4 +8,4 @@
.htmx-request.htmx-indicator {
display: block;
}
}
@@ -1,3 +1,3 @@
svg[data-nodxgo="lucide"]:not([class*="size-"]) {
@apply size-4;
}
}
+1 -1
View File
@@ -1,3 +1,3 @@
.notyf__toast {
@apply rounded-btn break-all !important;
}
}
@@ -20,4 +20,4 @@
* {
scrollbar-width: thin;
}
}
}
@@ -58,4 +58,4 @@
.ss-value-delete svg path {
@apply stroke-primary-content !important;
}
}
@@ -6,4 +6,4 @@ body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown),
html.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown) {
height: 100% !important;
overflow-y: visible !important;
}
}
@@ -1,4 +1,4 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind variants;
@tailwind variants;
+1 -1
View File
@@ -6,4 +6,4 @@
@import "./partials/slim-select.css";
@import "./partials/notyf.css";
@import "./partials/scrollbar.css";
@import "./partials/sweetalert2.css";
@import "./partials/sweetalert2.css";
+10 -10
View File
@@ -1,11 +1,11 @@
import { initThemeHelper } from './init-theme-helper.js'
import { initSweetAlert2 } from './init-sweetalert2.js'
import { initNotyf } from './init-notyf.js'
import { initHTMX } from './init-htmx.js'
import { initHelpers } from './init-helpers.js'
import { initThemeHelper } from "./init-theme-helper.js";
import { initSweetAlert2 } from "./init-sweetalert2.js";
import { initNotyf } from "./init-notyf.js";
import { initHTMX } from "./init-htmx.js";
import { initHelpers } from "./init-helpers.js";
initThemeHelper()
initSweetAlert2()
initNotyf()
initHTMX()
initHelpers()
initThemeHelper();
initSweetAlert2();
initNotyf();
initHTMX();
initHelpers();
+42 -42
View File
@@ -1,75 +1,75 @@
export function initHelpers () {
function debounce (fn, delayMilliseconds) {
let timeoutInstance
export function initHelpers() {
function debounce(fn, delayMilliseconds) {
let timeoutInstance;
return function (...args) {
clearTimeout(timeoutInstance)
clearTimeout(timeoutInstance);
timeoutInstance = setTimeout(() => {
fn.apply(this, args)
}, delayMilliseconds)
}
fn.apply(this, args);
}, delayMilliseconds);
};
}
function copyToClipboard (textToCopy) {
const successMessage = 'Text copied'
const errorMessage = 'Error copying text'
function copyToClipboard(textToCopy) {
const successMessage = "Text copied";
const errorMessage = "Error copying text";
/* First, try the modern approach */
if (navigator.clipboard && window.isSecureContext) {
return navigator.clipboard
.writeText(textToCopy)
.then(() => {
toaster.success(successMessage)
toaster.success(successMessage);
})
.catch((err) => {
console.error(errorMessage, err)
toaster.error(errorMessage)
})
console.error(errorMessage, err);
toaster.error(errorMessage);
});
}
/* Fallback: use execCommand("copy") method */
const textArea = document.createElement('textarea')
textArea.value = textToCopy
const textArea = document.createElement("textarea");
textArea.value = textToCopy;
textArea.style.position = 'fixed'
textArea.style.left = '-9999px'
textArea.style.top = '-9999px'
document.body.appendChild(textArea)
textArea.focus()
textArea.select()
textArea.style.position = "fixed";
textArea.style.left = "-9999px";
textArea.style.top = "-9999px";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
const successful = document.execCommand('copy')
const successful = document.execCommand("copy");
if (successful) {
toaster.success(successMessage)
toaster.success(successMessage);
} else {
console.error('Fallback', errorMessage)
toaster.error(errorMessage)
console.error("Fallback", errorMessage);
toaster.error(errorMessage);
}
} catch (err) {
console.error('Fallback', errorMessage, err)
toaster.error(errorMessage)
console.error("Fallback", errorMessage, err);
toaster.error(errorMessage);
}
document.body.removeChild(textArea)
document.body.removeChild(textArea);
}
function textareaAutoGrow (element) {
if (!element?.style) return
element.style.height = '1px'
element.style.height = (element.scrollHeight + 2) + 'px'
function textareaAutoGrow(element) {
if (!element?.style) return;
element.style.height = "1px";
element.style.height = (element.scrollHeight + 2) + "px";
}
function formatJson (inJSON) {
if (typeof inJSON !== 'string' || inJSON === '') return inJSON
function formatJson(inJSON) {
if (typeof inJSON !== "string" || inJSON === "") return inJSON;
try {
return JSON.stringify(JSON.parse(inJSON), null, 2)
} catch (e) {
return inJSON
return JSON.stringify(JSON.parse(inJSON), null, 2);
} catch {
return inJSON;
}
}
window.debounce = debounce
window.copyToClipboard = copyToClipboard
window.textareaAutoGrow = textareaAutoGrow
window.formatJson = formatJson
window.debounce = debounce;
window.copyToClipboard = copyToClipboard;
window.textareaAutoGrow = textareaAutoGrow;
window.formatJson = formatJson;
}
+38 -38
View File
@@ -1,72 +1,72 @@
export function initHTMX () {
export function initHTMX() {
const triggers = {
ctm_alert: function (evt) {
const message = decodeURIComponent(evt.detail.value)
window.swalAlert(message)
const message = decodeURIComponent(evt.detail.value);
window.swalAlert(message);
},
ctm_alert_with_refresh: function (evt) {
const message = decodeURIComponent(evt.detail.value)
const message = decodeURIComponent(evt.detail.value);
window.swalAlert(message).then(() => {
location.reload()
})
location.reload();
});
},
ctm_alert_with_redirect: function (evt) {
const payload = decodeURIComponent(evt.detail.value)
const parts = payload.split('-::-::-')
const payload = decodeURIComponent(evt.detail.value);
const parts = payload.split("-::-::-");
if (parts.length !== 2) {
return
return;
}
const message = parts[0]
const url = parts[1]
const message = parts[0];
const url = parts[1];
window.swalAlert(message).then(() => {
location.href = url
})
location.href = url;
});
},
ctm_toast_success: function (evt) {
const message = decodeURIComponent(evt.detail.value)
toaster.success(message)
const message = decodeURIComponent(evt.detail.value);
toaster.success(message);
},
ctm_toast_error: function (evt) {
const message = decodeURIComponent(evt.detail.value)
toaster.error(message)
const message = decodeURIComponent(evt.detail.value);
toaster.error(message);
},
ctm_toast_success_infinite: function (evt) {
const message = decodeURIComponent(evt.detail.value)
toaster.successInfinite(message)
const message = decodeURIComponent(evt.detail.value);
toaster.successInfinite(message);
},
ctm_toast_error_infinite: function (evt) {
const message = decodeURIComponent(evt.detail.value)
toaster.errorInfinite(message)
}
}
const message = decodeURIComponent(evt.detail.value);
toaster.errorInfinite(message);
},
};
for (const key in triggers) {
document.addEventListener(key, triggers[key])
document.addEventListener(key, triggers[key]);
}
// Add trigger to use sweetalert2 for confirms
document.addEventListener('htmx:confirm', function (e) {
if (!e.detail.target.hasAttribute('hx-confirm')) return
document.addEventListener("htmx:confirm", function (e) {
if (!e.detail.target.hasAttribute("hx-confirm")) return;
e.preventDefault()
e.preventDefault();
window.swalConfirm(e.detail.question).then(function (result) {
if (result.isConfirmed) e.detail.issueRequest(true)
})
})
if (result.isConfirmed) e.detail.issueRequest(true);
});
});
// This fixes this issue:
// https://stackoverflow.com/questions/73658449/htmx-request-not-firing-when-hx-attributes-are-added-dynamically-from-javascrip
document.addEventListener('DOMContentLoaded', function () {
document.addEventListener("DOMContentLoaded", function () {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === 1 && !node['htmx-internal-data']) {
htmx.process(node)
if (node.nodeType === 1 && !node["htmx-internal-data"]) {
htmx.process(node);
}
})
})
})
observer.observe(document, { childList: true, subtree: true })
})
});
});
});
observer.observe(document, { childList: true, subtree: true });
});
}
+28 -28
View File
@@ -1,53 +1,53 @@
export function initNotyf () {
let toastQueue = []
export function initNotyf() {
let toastQueue = [];
const toastCfg = {
duration: 5000,
ripple: false,
position: { x: 'right', y: 'bottom' },
dismissible: true
}
position: { x: "right", y: "bottom" },
dismissible: true,
};
const infiniteToastCfg = {
...toastCfg,
duration: 0
}
duration: 0,
};
window.toaster = {
success: (message) => {
toastQueue.push({ type: 'success', message, config: toastCfg })
toastQueue.push({ type: "success", message, config: toastCfg });
},
error: (message) => {
toastQueue.push({ type: 'error', message, config: toastCfg })
toastQueue.push({ type: "error", message, config: toastCfg });
},
successInfinite: (message) => {
toastQueue.push({ type: 'success', message, config: infiniteToastCfg })
toastQueue.push({ type: "success", message, config: infiniteToastCfg });
},
errorInfinite: (message) => {
toastQueue.push({ type: 'error', message, config: infiniteToastCfg })
}
}
toastQueue.push({ type: "error", message, config: infiniteToastCfg });
},
};
document.addEventListener('DOMContentLoaded', function () {
const notyf = new Notyf()
document.addEventListener("DOMContentLoaded", function () {
const notyf = new Notyf();
toastQueue.forEach(item => {
notyf.open({ type: item.type, message: item.message, ...item.config })
})
toastQueue.forEach((item) => {
notyf.open({ type: item.type, message: item.message, ...item.config });
});
toastQueue = []
toastQueue = [];
window.toaster.success = (message) => {
notyf.open({ type: 'success', message, ...toastCfg })
}
notyf.open({ type: "success", message, ...toastCfg });
};
window.toaster.error = (message) => {
notyf.open({ type: 'error', message, ...toastCfg })
}
notyf.open({ type: "error", message, ...toastCfg });
};
window.toaster.successInfinite = (message) => {
notyf.open({ type: 'success', message, ...infiniteToastCfg })
}
notyf.open({ type: "success", message, ...infiniteToastCfg });
};
window.toaster.errorInfinite = (message) => {
notyf.open({ type: 'error', message, ...infiniteToastCfg })
}
})
notyf.open({ type: "error", message, ...infiniteToastCfg });
};
});
}
+25 -25
View File
@@ -1,34 +1,34 @@
export function initSweetAlert2 () {
export function initSweetAlert2() {
// Docs at https://sweetalert2.github.io/#configuration
const defaultConfig = {
icon: 'info',
confirmButtonText: 'Okay',
cancelButtonText: 'Cancel',
icon: "info",
confirmButtonText: "Okay",
cancelButtonText: "Cancel",
customClass: {
popup: 'rounded-box bg-base-100 text-base-content',
confirmButton: 'btn btn-primary',
denyButton: 'btn btn-warning',
cancelButton: 'btn btn-error'
}
}
popup: "rounded-box bg-base-100 text-base-content",
confirmButton: "btn btn-primary",
denyButton: "btn btn-warning",
cancelButton: "btn btn-error",
},
};
async function swalAlert (text) {
return Swal.fire({
async function swalAlert(text) {
return await Swal.fire({
...defaultConfig,
title: text
})
}
async function swalConfirm (text) {
return Swal.fire({
...defaultConfig,
icon: 'question',
title: text,
confirmButtonText: 'Confirm',
showCancelButton: true
})
});
}
window.swalAlert = swalAlert
window.swalConfirm = swalConfirm
async function swalConfirm(text) {
return await Swal.fire({
...defaultConfig,
icon: "question",
title: text,
confirmButtonText: "Confirm",
showCancelButton: true,
});
}
window.swalAlert = swalAlert;
window.swalConfirm = swalConfirm;
}
+11 -11
View File
@@ -1,17 +1,17 @@
export function initThemeHelper () {
function getTheme () {
const theme = localStorage.getItem('theme')
return theme || ''
export function initThemeHelper() {
function getTheme() {
const theme = localStorage.getItem("theme");
return theme || "";
}
function setTheme (theme) {
localStorage.setItem('theme', theme)
document.documentElement.setAttribute('data-theme', theme)
function setTheme(theme) {
localStorage.setItem("theme", theme);
document.documentElement.setAttribute("data-theme", theme);
}
window.getTheme = getTheme
window.setTheme = setTheme
window.getTheme = getTheme;
window.setTheme = setTheme;
const theme = getTheme()
setTheme(theme)
const theme = getTheme();
setTheme(theme);
}
@@ -1,19 +1,19 @@
window.alpineChangeThemeButton = function () {
return {
theme: '',
theme: "",
loadTheme () {
const theme = window.getTheme()
this.theme = theme || 'system'
loadTheme() {
const theme = window.getTheme();
this.theme = theme || "system";
},
setTheme (theme) {
window.setTheme(theme)
this.theme = theme || 'system'
setTheme(theme) {
window.setTheme(theme);
this.theme = theme || "system";
},
init () {
this.loadTheme()
}
}
}
init() {
this.loadTheme();
},
};
};
@@ -5,42 +5,43 @@ window.alpineOptionsDropdown = function () {
contentEl: null,
closeTimeout: null,
init () {
this.buttonEl = this.$refs.button
this.contentEl = this.$refs.content
init() {
this.buttonEl = this.$refs.button;
this.contentEl = this.$refs.content;
},
open () {
this.isOpen = true
this.contentEl.classList.remove('hidden')
this.positionContent()
open() {
this.isOpen = true;
this.contentEl.classList.remove("hidden");
this.positionContent();
if (this.closeTimeout) {
clearTimeout(this.closeTimeout)
this.closeTimeout = null
clearTimeout(this.closeTimeout);
this.closeTimeout = null;
}
},
close () {
close() {
this.closeTimeout = setTimeout(() => {
this.isOpen = false
this.contentEl.classList.add('hidden')
}, 200)
this.isOpen = false;
this.contentEl.classList.add("hidden");
}, 200);
},
positionContent () {
const buttonRect = this.buttonEl.getBoundingClientRect()
const contentHeight = this.contentEl.offsetHeight
const windowHeight = window.innerHeight
const moreSpaceBelow = (windowHeight - buttonRect.bottom) > buttonRect.top
positionContent() {
const buttonRect = this.buttonEl.getBoundingClientRect();
const contentHeight = this.contentEl.offsetHeight;
const windowHeight = window.innerHeight;
const moreSpaceBelow =
(windowHeight - buttonRect.bottom) > buttonRect.top;
this.contentEl.style.left = `${buttonRect.left}px`
this.contentEl.style.left = `${buttonRect.left}px`;
if (moreSpaceBelow) {
this.contentEl.style.top = `${buttonRect.bottom}px`
this.contentEl.style.top = `${buttonRect.bottom}px`;
} else {
this.contentEl.style.top = `${buttonRect.top - contentHeight}px`
this.contentEl.style.top = `${buttonRect.top - contentHeight}px`;
}
}
}
}
},
};
};
@@ -2,41 +2,41 @@ window.alpineStarOnGithub = function () {
return {
stars: null,
async init () {
const stars = await this.getStars()
async init() {
const stars = await this.getStars();
if (stars !== null) {
this.stars = stars
this.stars = stars;
}
},
async getStars () {
const cacheKey = 'pbw-gh-stars'
async getStars() {
const cacheKey = "pbw-gh-stars";
const cachedJSON = localStorage.getItem(cacheKey)
const cachedJSON = localStorage.getItem(cacheKey);
if (cachedJSON) {
const cached = JSON.parse(cachedJSON)
const cached = JSON.parse(cachedJSON);
if (Date.now() - cached.timestamp < 2 * 60 * 1000) {
return cached.value
return cached.value;
}
}
const url = 'https://api.github.com/repos/eduardolat/pgbackweb'
const url = "https://api.github.com/repos/eduardolat/pgbackweb";
try {
const response = await fetch(url)
const response = await fetch(url);
if (!response.ok) {
return null
return null;
}
const data = await response.json()
const value = data.stargazers_count
const data = await response.json();
const value = data.stargazers_count;
const dataToCache = JSON.stringify({
value,
timestamp: Date.now()
})
localStorage.setItem(cacheKey, dataToCache)
return value
timestamp: Date.now(),
});
localStorage.setItem(cacheKey, dataToCache);
return value;
} catch {
return null
return null;
}
}
}
}
},
};
};
@@ -3,20 +3,20 @@ window.alpineSummaryHowToSlider = function () {
slidesQty: 4,
currentSlide: 1,
get hasNextSlide () {
return this.currentSlide < this.slidesQty
get hasNextSlide() {
return this.currentSlide < this.slidesQty;
},
get hasPrevSlide () {
return this.currentSlide > 1
get hasPrevSlide() {
return this.currentSlide > 1;
},
nextSlide () {
if (this.hasNextSlide) this.currentSlide++
nextSlide() {
if (this.hasNextSlide) this.currentSlide++;
},
prevSlide () {
if (this.hasPrevSlide) this.currentSlide--
}
}
}
prevSlide() {
if (this.hasPrevSlide) this.currentSlide--;
},
};
};
+30 -30
View File
@@ -1,53 +1,53 @@
document.addEventListener('DOMContentLoaded', function () {
const el = document.getElementById('dashboard-aside')
const key = 'dashboard-aside-scroll-position'
document.addEventListener("DOMContentLoaded", function () {
const el = document.getElementById("dashboard-aside");
const key = "dashboard-aside-scroll-position";
if (!el) return
if (!el) return;
const saveScrollPosition = window.debounce(
() => {
const scrollPosition = el.scrollTop
localStorage.setItem(key, scrollPosition)
const scrollPosition = el.scrollTop;
localStorage.setItem(key, scrollPosition);
},
200
)
el.addEventListener('scroll', saveScrollPosition)
200,
);
el.addEventListener("scroll", saveScrollPosition);
const scrollPosition = localStorage.getItem(key)
const scrollPosition = localStorage.getItem(key);
if (scrollPosition) {
el.scrollTop = parseInt(scrollPosition, 10)
el.scrollTop = parseInt(scrollPosition, 10);
}
})
});
window.alpineDashboardAsideItem = function (link = '', strict = false) {
window.alpineDashboardAsideItem = function (link = "", strict = false) {
return {
link,
strict,
is_active: false,
checkActive () {
checkActive() {
if (this.strict) {
this.is_active = window.location.pathname === this.link
return
this.is_active = window.location.pathname === this.link;
return;
}
this.is_active = window.location.pathname.startsWith(this.link)
this.is_active = window.location.pathname.startsWith(this.link);
},
init () {
this.checkActive()
init() {
this.checkActive();
const originalPushState = window.history.pushState
const originalPushState = window.history.pushState;
window.history.pushState = (...args) => {
originalPushState.apply(window.history, args)
this.checkActive()
}
originalPushState.apply(window.history, args);
this.checkActive();
};
const originalReplaceState = window.history.replaceState
const originalReplaceState = window.history.replaceState;
window.history.replaceState = (...args) => {
originalReplaceState.apply(window.history, args)
this.checkActive()
}
}
}
}
originalReplaceState.apply(window.history, args);
this.checkActive();
};
},
};
};
@@ -2,41 +2,42 @@ window.alpineDashboardHeaderUpdates = function () {
return {
latestRelease: null,
async init () {
const latestRelease = await this.getLatestRelease()
async init() {
const latestRelease = await this.getLatestRelease();
if (latestRelease !== null) {
this.latestRelease = latestRelease
this.latestRelease = latestRelease;
}
},
async getLatestRelease () {
const cacheKey = 'pbw-gh-last-release'
async getLatestRelease() {
const cacheKey = "pbw-gh-last-release";
const cachedJSON = localStorage.getItem(cacheKey)
const cachedJSON = localStorage.getItem(cacheKey);
if (cachedJSON) {
const cached = JSON.parse(cachedJSON)
const cached = JSON.parse(cachedJSON);
if (Date.now() - cached.timestamp < 2 * 60 * 1000) {
return cached.value
return cached.value;
}
}
const url = 'https://api.github.com/repos/eduardolat/pgbackweb/releases/latest'
const url =
"https://api.github.com/repos/eduardolat/pgbackweb/releases/latest";
try {
const response = await fetch(url)
const response = await fetch(url);
if (!response.ok) {
return null
return null;
}
const data = await response.json()
const value = data.name
const data = await response.json();
const value = data.name;
const dataToCache = JSON.stringify({
value,
timestamp: Date.now()
})
localStorage.setItem(cacheKey, dataToCache)
return value
timestamp: Date.now(),
});
localStorage.setItem(cacheKey, dataToCache);
return value;
} catch {
return null
return null;
}
}
}
}
},
};
};