Compare commits

..

No commits in common. "96c3cf8fbc7d0942bd6bf9b03204cab3c072a52e" and "c83bb6aec6b3106bd8969c1cfcf81663f6e01818" have entirely different histories.

24 changed files with 3806 additions and 3382 deletions

5
.env
View File

@ -1,6 +1,5 @@
REACT_APP_TUITIO_URL=https://lab.code-rove.com/tuitio
REACT_APP_IDENTITY_AUTHENTICATION_URL=https://toodle.ddns.net/identity-server-api/identity/authenticate?UserName={username}&Password={password}
REACT_APP_ENABLE_TEMPLATE_CONTENT=True
#REACT_APP_CDN_URL=http://localhost:5050
REACT_APP_CDN_URL=https://lab.code-rove.com/cdn
REACT_APP_CDN_URL=http://localhost:5050
#PUBLIC_URL=/cdn-admin/

View File

@ -1,4 +1,4 @@
PUBLIC_URL=/cdn-admin/
REACT_APP_TUITIO_URL=https://lab.code-rove.com/tuitio
REACT_APP_IDENTITY_AUTHENTICATION_URL=https://toodle.ddns.net/identity-server-api/identity/authenticate?UserName={username}&Password={password}
REACT_APP_ENABLE_TEMPLATE_CONTENT=False
REACT_APP_CDN_URL=https://lab.code-rove.com/cdn
REACT_APP_CDN_URL=https://toodle.ddns.net/cdn

2
.npmrc
View File

@ -1 +1 @@
@flare:registry=https://lab.code-rove.com/public-node-registry
@flare:registry=https://toodle.ddns.net/public-node-registry

View File

@ -4,7 +4,7 @@ Built with [React](https://facebook.github.io/react/), [Material-UI](https://mat
**This version uses React 16.14.0, React Router v5, MaterialUI v4, built with React Hooks and React Context (No Redux)**
[View Demo](https://lab.code-rove.com/cdn-admin/) | [Download](https://lab.code-rove.com/gitea/tudor/cdn-frontend) | [More projects](https://lab.code-rove.com/heimdall) | [Support forum](https://lab.code-rove.com/forum)
[View Demo](https://toodle.ddns.net/cdn-admin/) | [Download](https://toodle.ddns.net/gitea/tudor/cdn-frontend) | [More projects](https://toodle.ddns.net/heimdall) | [Support forum](https://toodle.ddns.net/forum)
[![image](https://user-images.githubusercontent.com/24964748/55800639-df780300-5adc-11e9-84b7-7c2437088516.png)](https://flatlogic.com/admin-dashboards/react-material-admin/demo)

View File

@ -1,5 +1,5 @@
# build environment
FROM node:14-slim as builder
FROM node:12-slim as builder
WORKDIR /app
COPY .npmrc .npmrc
@ -11,7 +11,7 @@ COPY . ./
RUN npm run build
# production environment
FROM node:14-slim
FROM node:12-slim
RUN printf '\n\n- Copy application files\n'
ARG APP_SUBFOLDER=cdn-admin
@ -22,11 +22,6 @@ COPY --from=builder /app/build/index.html ./application/
#install static server
RUN npm install -g serve
# environment variables
ENV AUTHOR="Tudor Stanciu"
ARG APP_VERSION=0.0.0
ENV APP_VERSION=${APP_VERSION}
#set workdir to root
WORKDIR /

6723
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,15 @@
{
"name": "cdn-admin",
"version": "1.1.0",
"version": "1.0.0",
"description": "CDN administration application",
"author": {
"name": "Tudor Stanciu",
"email": "tudor.stanciu94@gmail.com",
"url": "https://lab.code-rove.com/tsp"
"url": "https://toodle.ddns.net/tsp"
},
"repository": {
"type": "git",
"url": "https://lab.code-rove.com/gitea/tudor/cdn-frontend"
"url": "https://toodle.ddns.net/gitea/tudor/cdn-frontend"
},
"private": true,
"homepage": "./",
@ -19,8 +19,7 @@
"node-fetch": "^2.6.1"
},
"dependencies": {
"@flare/js-utils": "^1.0.3",
"@flare/tuitio-client-react": "^1.1.1",
"@flare/js-utils": "^1.0.0",
"@material-ui/core": "^4.11.3",
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.60",
@ -29,7 +28,7 @@
"@mdi/react": "^1.4.0",
"@babel/eslint-parser": "^7.16.5",
"apexcharts": "^3.24.0",
"axios": "^1.3.4",
"axios": "^0.19.2",
"classnames": "^2.2.6",
"font-awesome": "^4.7.0",
"i18next": "^19.4.4",

View File

@ -8,10 +8,21 @@ import Layout from "./Layout/Layout";
import Error from "../pages/error";
import Login from "../pages/login";
import ServerNotAvailable from "../features/server/availability/components/ServerNotAvailable";
import { useTuitioToken } from "@flare/tuitio-client-react";
// context
import { useUserState } from "../contexts/UserContext";
import { useToast } from "../contexts/ToastContext";
import { useTranslation } from "react-i18next";
export default function App() {
const { valid: authenticated } = useTuitioToken();
const { authenticated, messageCode } = useUserState();
const { t } = useTranslation();
const { notify } = useToast();
useEffect(() => {
if (!messageCode) return;
notify(t(messageCode), "error");
}, [messageCode, t, notify]);
return (
<BrowserRouter basename={process.env.PUBLIC_URL || ""}>

View File

@ -33,8 +33,7 @@ import {
useLayoutDispatch,
toggleSidebar
} from "../../contexts/LayoutContext";
import { useTuitioClient } from "@flare/tuitio-client-react";
import { useToast } from "../../hooks";
import { useUserDispatch, signOut } from "../../contexts/UserContext";
const messages = [
{
@ -95,6 +94,7 @@ export default function Header(props) {
// global
var layoutState = useLayoutState();
var layoutDispatch = useLayoutDispatch();
var userDispatch = useUserDispatch();
// local
var [mailMenu, setMailMenu] = useState(null);
@ -104,12 +104,6 @@ export default function Header(props) {
var [profileMenu, setProfileMenu] = useState(null);
var [isSearchOpen, setSearchOpen] = useState(false);
const { error } = useToast();
const { logout } = useTuitioClient({
onLogoutFailed: (errorMessage) => error(errorMessage),
onLogoutError: (err) => error(err.message)
});
return (
<AppBar position="fixed" className={classes.appBar}>
<Toolbar className={classes.toolbar}>
@ -326,7 +320,7 @@ export default function Header(props) {
<Typography
className={classes.profileMenuLink}
color="primary"
onClick={logout}
onClick={() => signOut(userDispatch, props.history)}
>
Sign Out
</Typography>

View File

@ -1,7 +1,7 @@
import { makeStyles } from "@material-ui/styles";
import { alpha } from "@material-ui/core/styles";
import { fade } from "@material-ui/core/styles/colorManipulator";
export default makeStyles((theme) => ({
export default makeStyles(theme => ({
logotype: {
color: "white",
marginLeft: theme.spacing(2.5),
@ -10,45 +10,45 @@ export default makeStyles((theme) => ({
fontSize: 18,
whiteSpace: "nowrap",
[theme.breakpoints.down("xs")]: {
display: "none"
}
display: "none",
},
},
appBar: {
width: "100vw",
zIndex: theme.zIndex.drawer + 1,
transition: theme.transitions.create(["margin"], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen
})
duration: theme.transitions.duration.leavingScreen,
}),
},
toolbar: {
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(2)
paddingRight: theme.spacing(2),
},
hide: {
display: "none"
display: "none",
},
grow: {
flexGrow: 1
flexGrow: 1,
},
search: {
position: "relative",
borderRadius: 25,
paddingLeft: theme.spacing(2.5),
width: 36,
backgroundColor: alpha(theme.palette.common.black, 0),
backgroundColor: fade(theme.palette.common.black, 0),
transition: theme.transitions.create(["background-color", "width"]),
"&:hover": {
cursor: "pointer",
backgroundColor: alpha(theme.palette.common.black, 0.08)
}
backgroundColor: fade(theme.palette.common.black, 0.08),
},
},
searchFocused: {
backgroundColor: alpha(theme.palette.common.black, 0.08),
backgroundColor: fade(theme.palette.common.black, 0.08),
width: "100%",
[theme.breakpoints.up("md")]: {
width: 250
}
width: 250,
},
},
searchIcon: {
width: 36,
@ -60,115 +60,115 @@ export default makeStyles((theme) => ({
justifyContent: "center",
transition: theme.transitions.create("right"),
"&:hover": {
cursor: "pointer"
}
cursor: "pointer",
},
},
searchIconOpened: {
right: theme.spacing(1.25)
right: theme.spacing(1.25),
},
inputRoot: {
color: "inherit",
width: "100%"
width: "100%",
},
inputInput: {
height: 36,
padding: 0,
paddingRight: 36 + theme.spacing(1.25),
width: "100%"
width: "100%",
},
messageContent: {
display: "flex",
flexDirection: "column"
flexDirection: "column",
},
headerMenu: {
marginTop: theme.spacing(7)
marginTop: theme.spacing(7),
},
headerMenuList: {
display: "flex",
flexDirection: "column"
flexDirection: "column",
},
headerMenuItem: {
"&:hover, &:focus": {
backgroundColor: theme.palette.background.light
backgroundColor: theme.palette.background.light,
// color: "white",
}
},
},
headerMenuButton: {
marginLeft: theme.spacing(2),
padding: theme.spacing(0.5)
padding: theme.spacing(0.5),
},
headerMenuButtonSandwich: {
marginLeft: 9,
[theme.breakpoints.down("sm")]: {
marginLeft: 0
},
padding: theme.spacing(0.5)
padding: theme.spacing(0.5),
},
headerMenuButtonCollapse: {
marginRight: theme.spacing(2)
marginRight: theme.spacing(2),
},
headerIcon: {
fontSize: 28,
color: "rgba(255, 255, 255, 0.35)"
color: "rgba(255, 255, 255, 0.35)",
},
headerIconCollapse: {
color: "white"
color: "white",
},
profileMenu: {
minWidth: 265
minWidth: 265,
},
profileMenuUser: {
display: "flex",
flexDirection: "column",
padding: theme.spacing(2)
padding: theme.spacing(2),
},
profileMenuItem: {
color: theme.palette.text.hint
color: theme.palette.text.hint,
},
profileMenuIcon: {
marginRight: theme.spacing(2),
color: theme.palette.text.hint,
"&:hover": {
color: theme.palette.primary.main
'&:hover': {
color: theme.palette.primary.main,
}
},
profileMenuLink: {
fontSize: 16,
textDecoration: "none",
"&:hover": {
cursor: "pointer"
}
cursor: "pointer",
},
},
messageNotification: {
height: "auto",
display: "flex",
alignItems: "center",
"&:hover, &:focus": {
backgroundColor: theme.palette.background.light
}
backgroundColor: theme.palette.background.light,
},
},
messageNotificationSide: {
display: "flex",
flexDirection: "column",
alignItems: "center",
marginRight: theme.spacing(2)
marginRight: theme.spacing(2),
},
messageNotificationBodySide: {
alignItems: "flex-start",
marginRight: 0
marginRight: 0,
},
sendMessageButton: {
margin: theme.spacing(4),
marginTop: theme.spacing(2),
marginBottom: theme.spacing(2),
textTransform: "none"
textTransform: "none",
},
sendButtonIcon: {
marginLeft: theme.spacing(2)
marginLeft: theme.spacing(2),
},
purchaseBtn: {
[theme.breakpoints.down("sm")]: {
display: "none"
[theme.breakpoints.down('sm')]: {
display: 'none'
},
marginRight: theme.spacing(3)
}

View File

@ -26,7 +26,7 @@ const ContentFooter = () => {
<div>
<Link
color={"primary"}
href={"https://lab.code-rove.com/heimdall"}
href={"https://toodle.ddns.net/heimdall"}
target={"_blank"}
className={classes.link}
>
@ -34,7 +34,7 @@ const ContentFooter = () => {
</Link>
<Link
color={"primary"}
href={"https://lab.code-rove.com/heimdall"}
href={"https://toodle.ddns.net/heimdall"}
target={"_blank"}
className={classes.link}
>
@ -42,7 +42,7 @@ const ContentFooter = () => {
</Link>
<Link
color={"primary"}
href={"https://lab.code-rove.com/heimdall"}
href={"https://toodle.ddns.net/heimdall"}
target={"_blank"}
className={classes.link}
>
@ -60,7 +60,7 @@ const ContentFooter = () => {
<Icon path={TwitterIcon} size={1} color="#6E6E6E99" />
</IconButton>
</Link>
<Link href={"https://lab.code-rove.com/gitea/"} target={"_blank"}>
<Link href={"https://toodle.ddns.net/gitea/"} target={"_blank"}>
<IconButton aria-label="github" style={{ marginRight: -12 }}>
<Icon path={GithubIcon} size={1} color="#6E6E6E99" />
</IconButton>

View File

@ -68,19 +68,19 @@ let menu = [
{
id: 9,
label: "Menu.Library",
link: "https://lab.code-rove.com/gitea/tudor/cdn-frontend",
link: "https://toodle.ddns.net/gitea/tudor/cdn-frontend",
icon: <LibraryIcon />
},
{
id: 10,
label: "Menu.Support",
link: "https://lab.code-rove.com/tsp",
link: "https://toodle.ddns.net/tsp",
icon: <SupportIcon />
},
{
id: 11,
label: "Menu.FAQ",
link: "https://lab.code-rove.com/tsp",
link: "https://toodle.ddns.net/tsp",
icon: <FAQIcon />
}
];

View File

@ -61,7 +61,7 @@ export default function Widget({
aria-owns="widget-menu"
aria-haspopup="true"
onClick={() => setMoreMenuOpen(true)}
ref={setMoreButtonRef}
buttonRef={setMoreButtonRef}
>
<MoreIcon />
</IconButton>

View File

@ -37,7 +37,7 @@ const Widget = ({
aria-owns="widget-menu"
aria-haspopup="true"
onClick={() => props.setMoreMenuOpen(true)}
ref={props.setMoreButtonRef}
buttonRef={props.setMoreButtonRef}
>
<MoreIcon />
</IconButton>
@ -77,7 +77,7 @@ const Widget = ({
</div>
);
const styles = (theme) => ({
const styles = theme => ({
widgetWrapper: {
display: "flex",
minHeight: "100%"

100
src/contexts/UserContext.js Normal file
View File

@ -0,0 +1,100 @@
import React from "react";
import PropTypes from "prop-types";
import { authenticate, invalidate, validateToken } from "../utils/identity";
var UserStateContext = React.createContext();
var UserDispatchContext = React.createContext();
function userReducer(state, action) {
switch (action.type) {
case "LOGIN_SUCCESS":
return { ...state, ...action.payload, authenticated: true };
case "SIGN_OUT_SUCCESS":
return { ...state, authenticated: false };
case "LOGIN_FAILURE":
return {
...state,
authenticated: false,
messageCode: action.payload?.messageCode
};
default: {
throw new Error(`Unhandled action type: ${action.type}`);
}
}
}
function UserProvider({ children }) {
const { valid, token } = validateToken();
var [state, dispatch] = React.useReducer(userReducer, {
authenticated: valid === true,
token
});
return (
<UserStateContext.Provider value={state}>
<UserDispatchContext.Provider value={dispatch}>
{children}
</UserDispatchContext.Provider>
</UserStateContext.Provider>
);
}
UserProvider.propTypes = {
children: PropTypes.node
};
function useUserState() {
var context = React.useContext(UserStateContext);
if (context === undefined) {
throw new Error("useUserState must be used within a UserProvider");
}
return context;
}
function useUserDispatch() {
var context = React.useContext(UserDispatchContext);
if (context === undefined) {
throw new Error("useUserDispatch must be used within a UserProvider");
}
return context;
}
function setLoginFailed(dispatch, setError, setIsLoading, messageCode) {
dispatch({ type: "LOGIN_FAILURE", payload: { messageCode } });
setError(true);
setIsLoading(false);
}
function loginUser(dispatch, login, password, history, setIsLoading, setError) {
setError(false);
setIsLoading(true);
if (!!login && !!password) {
authenticate(login, password).then((response) => {
if (response.status === "SUCCESS") {
setError(null);
setIsLoading(false);
dispatch({ type: "LOGIN_SUCCESS", payload: { token: response.token } });
} else if (response.status === "BAD_CREDENTIALS") {
setLoginFailed(
dispatch,
setError,
setIsLoading,
"Login.IncorrectCredentials"
);
}
});
history.push("/dashboard");
} else {
setLoginFailed(dispatch, setError, setIsLoading, "Login.EmptyCredentials");
}
}
function signOut(dispatch, history) {
invalidate();
dispatch({ type: "SIGN_OUT_SUCCESS" });
history.push("/login");
}
export { UserProvider, useUserState, useUserDispatch, loginUser, signOut };

View File

@ -136,7 +136,7 @@ const ResourceComponent = ({
<TextField
fullWidth
multiline
minRows={2}
rows={12}
id="resource-description"
label={t("Resource.Description")}
variant="outlined"

View File

@ -6,6 +6,7 @@ import { LoadingText } from "../../../../components";
import ResourceComponent from "./ResourceComponent";
import { makeStyles } from "@material-ui/core/styles";
import style from "../styles";
import { useToast } from "../../../../hooks";
const useStyles = makeStyles(style);
@ -20,6 +21,7 @@ const ResourceContainer = () => {
const params = useParams();
const { getResource } = useResourcesApi();
const { getMimeTypes, getResourceCategories } = useDictionariesApi();
const { success } = useToast();
const isNew = useMemo(() => params.id === "new", [params.id]);

View File

@ -1,15 +1,15 @@
import { useTuitioToken } from "@flare/tuitio-client-react";
import { useUserState } from "../contexts/UserContext";
import { useCallback } from "react";
const useResourceSecurity = () => {
const { token } = useTuitioToken();
const { token } = useUserState();
const secureUrl = useCallback(
(url) => {
const separator = url.includes("?") ? "&" : "?";
const securedUrl = `${url}${separator}token=${token}`;
const securedUrl = `${url}${separator}token=${token.raw}`;
return securedUrl;
},
[token]
[token.raw]
);
return { secureUrl };

View File

@ -1,18 +1,18 @@
import React, { Suspense } from "react";
import ReactDOM from "react-dom";
import { TuitioProvider } from "@flare/tuitio-client-react";
import { ThemeProvider } from "@material-ui/styles";
import { CssBaseline } from "@material-ui/core";
import Themes from "./themes";
import App from "./components/App";
import * as serviceWorker from "./serviceWorker";
import { LayoutProvider } from "./contexts/LayoutContext";
import { UserProvider } from "./contexts/UserContext";
import { ToastProvider } from "./contexts/ToastContext";
import "./utils/i18n";
ReactDOM.render(
<LayoutProvider>
<TuitioProvider tuitioUrl={process.env.REACT_APP_TUITIO_URL}>
<UserProvider>
<ThemeProvider theme={Themes.default}>
<CssBaseline />
<Suspense fallback={<div>Loading...</div>}>
@ -21,7 +21,7 @@ ReactDOM.render(
</ToastProvider>
</Suspense>
</ThemeProvider>
</TuitioProvider>
</UserProvider>
</LayoutProvider>,
document.getElementById("root")
);

View File

@ -18,32 +18,23 @@ import useStyles from "./styles";
// logo
import logo from "./logo.svg";
import google from "../../images/google.svg";
import { useToast } from "../../hooks";
import { useTuitioClient } from "@flare/tuitio-client-react";
function Login() {
const classes = useStyles();
const [activeTabId, setActiveTabId] = useState(0);
const [nameValue, setNameValue] = useState("");
const [loginValue, setLoginValue] = useState("");
const [passwordValue, setPasswordValue] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [errorEncountered, setErrorEncountered] = useState(false);
// context
import { useUserDispatch, loginUser } from "../../contexts/UserContext";
const { error } = useToast();
const { login } = useTuitioClient({
onLoginSuccess: () => setIsLoading(false),
onLoginFailed: () => error(t("Login.IncorrectCredentials")),
onLoginError: (err) => {
setErrorEncountered(true);
error(err.message);
}
});
function Login(props) {
var classes = useStyles();
const handleLogin = () => {
setIsLoading(true);
login(loginValue, passwordValue);
};
// global
var userDispatch = useUserDispatch();
// local
var [isLoading, setIsLoading] = useState(false);
var [error, setError] = useState(null);
var [activeTabId, setActiveTabId] = useState(0);
var [nameValue, setNameValue] = useState("");
var [loginValue, setLoginValue] = useState("");
var [passwordValue, setPasswordValue] = useState("");
return (
<Grid container className={classes.container}>
@ -77,7 +68,7 @@ function Login() {
<Typography className={classes.formDividerWord}>or</Typography>
<div className={classes.formDivider} />
</div>
<Fade in={errorEncountered}>
<Fade in={error}>
<Typography color="secondary" className={classes.errorMessage}>
Something is wrong with your login or password :(
</Typography>
@ -120,7 +111,16 @@ function Login() {
disabled={
loginValue.length === 0 || passwordValue.length === 0
}
onClick={handleLogin}
onClick={() =>
loginUser(
userDispatch,
loginValue,
passwordValue,
props.history,
setIsLoading,
setError
)
}
variant="contained"
color="primary"
size="large"
@ -201,7 +201,16 @@ function Login() {
<CircularProgress size={26} />
) : (
<Button
onClick={handleLogin}
onClick={() =>
loginUser(
userDispatch,
loginValue,
passwordValue,
props.history,
setIsLoading,
setError
)
}
disabled={
loginValue.length === 0 ||
passwordValue.length === 0 ||

View File

@ -1,31 +1,32 @@
import defaultTheme from "./default";
import { createTheme } from "@material-ui/core/styles";
import { createMuiTheme } from "@material-ui/core";
const overrides = {
typography: {
h1: {
fontSize: "3rem"
fontSize: "3rem",
},
h2: {
fontSize: "2rem"
fontSize: "2rem",
},
h3: {
fontSize: "1.64rem"
fontSize: "1.64rem",
},
h4: {
fontSize: "1.5rem"
fontSize: "1.5rem",
},
h5: {
fontSize: "1.285rem"
fontSize: "1.285rem",
},
h6: {
fontSize: "1.142rem"
}
}
fontSize: "1.142rem",
},
},
};
const themes = {
default: createTheme({ ...defaultTheme, ...overrides })
default: createMuiTheme({ ...defaultTheme, ...overrides }),
};
export default themes;

View File

@ -1,14 +1,15 @@
import axios from "axios";
import i18next from "i18next";
import { fetch as fetchTuitioData } from "@flare/tuitio-client";
import { getItem } from "./localStorage";
import { storageKeys } from "./identity";
function getHeaders() {
const { token } = fetchTuitioData();
const token = getItem(storageKeys.TOKEN);
const language = i18next.language;
return {
"Content-Type": "application/json",
Authorization: `Tuitio ${token}`,
Authorization: `Basic ${token.raw}`,
"Accept-Language": `${language}`
};
}

50
src/utils/identity.js Normal file
View File

@ -0,0 +1,50 @@
import { request } from "./axios";
import { setItem, getItem, removeItem } from "./localStorage";
const storageKeys = {
TOKEN: "AUTHORIZATION_TOKEN",
USER: "USER_NAME"
};
const authenticate = async (userName, password) => {
const urlTemplate = process.env.REACT_APP_IDENTITY_AUTHENTICATION_URL;
const url = urlTemplate
.replace("{username}", userName)
.replace("{password}", password);
const options = {
method: "post"
};
const response = await request(url, options);
if (response.status === "SUCCESS") {
setItem(storageKeys.TOKEN, response.token);
setItem(storageKeys.USER, userName);
}
return response;
};
const invalidate = () => {
const token = getItem(storageKeys.TOKEN);
if (token) {
removeItem(storageKeys.TOKEN);
removeItem(storageKeys.USER);
}
};
const validateToken = () => {
let token = getItem(storageKeys.TOKEN);
if (!token) {
return { valid: false };
}
const valid = new Date(token.validUntil) >= new Date();
if (!valid) {
invalidate();
token = null;
}
return { valid, token };
};
export { storageKeys, authenticate, invalidate, validateToken };

38
src/utils/localStorage.js Normal file
View File

@ -0,0 +1,38 @@
import { typeValidator } from "@flare/js-utils";
const setItem = (key, value) => {
const { isArray, isObject } = typeValidator;
let valueToStore = value;
if (isArray(value) || isObject(value)) {
valueToStore = JSON.stringify(value);
}
window.localStorage.setItem(key, valueToStore);
};
const getItem = (key) => {
var value = window.localStorage.getItem(key);
const { isJson } = typeValidator;
var { data, success } = isJson(value);
if (success) {
return data;
} else {
return value;
}
};
const removeItem = (key) => {
window.localStorage.removeItem(key);
};
const clear = () => {
window.localStorage.clear();
};
const key = (index) => {
var keyName = window.localStorage.key(index);
return keyName;
};
export { setItem, getItem, removeItem, clear, key };