Merged PR 93: Migrate from Create React App to Vite
- Update dependencies and refactor import for localStorage utility - Refactor component prop types from JSX.Element to React.ReactElement for consistency - refactor: migrate from Create React App to Vitemaster
parent
1adc2f6de1
commit
5ab5d0777f
|
@ -233,4 +233,12 @@
|
|||
◾ Upgrade packages to the latest versions
|
||||
</Content>
|
||||
</Note>
|
||||
<Note>
|
||||
<Version>1.4.1</Version>
|
||||
<Date>2025-04-27 01:50</Date>
|
||||
<Content>
|
||||
NetworkResurrector UI: Migrtion from Create React App to Vite
|
||||
◾ The frontend of the application has been migrated from Create React App to Vite, a modern build tool that significantly improves the development experience and build performance.
|
||||
</Content>
|
||||
</Note>
|
||||
</ReleaseNotes>
|
|
@ -1,8 +1,8 @@
|
|||
REACT_APP_TUITIO_URL=http://#######
|
||||
REACT_APP_NETWORK_RESURRECTOR_API_URL=http://#######
|
||||
VITE_APP_TUITIO_URL=http://#######
|
||||
VITE_APP_NETWORK_RESURRECTOR_API_URL=http://#######
|
||||
|
||||
#600000 milliseconds = 10 minutes
|
||||
REACT_APP_MACHINE_PING_INTERVAL=600000
|
||||
VITE_APP_MACHINE_PING_INTERVAL=600000
|
||||
|
||||
#300000 milliseconds = 5 minutes
|
||||
REACT_APP_MACHINE_STARTING_TIME=300000
|
||||
VITE_APP_MACHINE_STARTING_TIME=300000
|
|
@ -1,9 +1,9 @@
|
|||
PUBLIC_URL=
|
||||
REACT_APP_TUITIO_URL=https://#######
|
||||
REACT_APP_NETWORK_RESURRECTOR_API_URL=https://#######
|
||||
VITE_APP_TUITIO_URL=https://#######
|
||||
VITE_APP_NETWORK_RESURRECTOR_API_URL=https://#######
|
||||
|
||||
#900000 milliseconds = 15 minutes
|
||||
REACT_APP_MACHINE_PING_INTERVAL=900000
|
||||
VITE_APP_MACHINE_PING_INTERVAL=900000
|
||||
|
||||
#300000 milliseconds = 5 minutes
|
||||
REACT_APP_MACHINE_STARTING_TIME=300000
|
||||
VITE_APP_MACHINE_STARTING_TIME=300000
|
|
@ -1,34 +0,0 @@
|
|||
{
|
||||
"root": true,
|
||||
"extends": [
|
||||
"prettier",
|
||||
"plugin:prettier/recommended",
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": ["@typescript-eslint", "prettier", "react", "react-hooks"],
|
||||
"rules": {
|
||||
"react-hooks/rules-of-hooks": "error",
|
||||
"react-hooks/exhaustive-deps": "warn",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"no-debugger": "warn"
|
||||
},
|
||||
"ignorePatterns": ["**/public"],
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true
|
||||
},
|
||||
"globals": {
|
||||
"JSX": true
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
const getCacheIdentifier = require("react-dev-utils/getCacheIdentifier");
|
||||
|
||||
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== "false";
|
||||
|
||||
module.exports = function override(config, webpackEnv) {
|
||||
console.log("overriding webpack config...");
|
||||
|
||||
const isEnvDevelopment = webpackEnv === "development";
|
||||
const isEnvProduction = webpackEnv === "production";
|
||||
const loaders = config.module.rules[1].oneOf;
|
||||
|
||||
loaders.splice(loaders.length - 1, 0, {
|
||||
test: /\.(js|mjs|cjs)$/,
|
||||
exclude: /@babel(?:\/|\\{1,2})runtime/,
|
||||
loader: require.resolve("babel-loader"),
|
||||
options: {
|
||||
babelrc: false,
|
||||
configFile: false,
|
||||
compact: false,
|
||||
presets: [[require.resolve("babel-preset-react-app/dependencies"), { helpers: true }]],
|
||||
cacheDirectory: true,
|
||||
// See #6846 for context on why cacheCompression is disabled
|
||||
cacheCompression: false,
|
||||
// @remove-on-eject-begin
|
||||
cacheIdentifier: getCacheIdentifier(isEnvProduction ? "production" : isEnvDevelopment && "development", [
|
||||
"babel-plugin-named-asset-import",
|
||||
"babel-preset-react-app",
|
||||
"react-dev-utils",
|
||||
"react-scripts"
|
||||
]),
|
||||
// @remove-on-eject-end
|
||||
// Babel sourcemaps are needed for debugging into node_modules
|
||||
// code. Without the options below, debuggers like VSCode
|
||||
// show incorrect code and set breakpoints on the wrong lines.
|
||||
sourceMaps: shouldUseSourceMap,
|
||||
inputSourceMap: shouldUseSourceMap
|
||||
}
|
||||
});
|
||||
|
||||
return config;
|
||||
};
|
|
@ -1,5 +1,5 @@
|
|||
# BUILD ENVIRONMENT
|
||||
FROM node:16-slim AS builder
|
||||
FROM node:20-slim AS builder
|
||||
WORKDIR /app
|
||||
|
||||
ARG APP_SUBFOLDER=""
|
||||
|
@ -15,7 +15,7 @@ COPY . ./
|
|||
RUN if [ -z "$APP_SUBFOLDER" ]; then npm run build; else PUBLIC_URL=/${APP_SUBFOLDER}/ npm run build; fi
|
||||
|
||||
# PRODUCTION ENVIRONMENT
|
||||
FROM node:16-slim
|
||||
FROM node:20-slim
|
||||
|
||||
ARG APP_SUBFOLDER=""
|
||||
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
import js from "@eslint/js";
|
||||
import globals from "globals";
|
||||
import react from "eslint-plugin-react";
|
||||
import reactHooks from "eslint-plugin-react-hooks";
|
||||
import reactRefresh from "eslint-plugin-react-refresh";
|
||||
import tseslint from "typescript-eslint";
|
||||
import prettier from "eslint-plugin-prettier";
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ["node_modules", "dist", "build", "**/public", "setenv.js"] },
|
||||
{
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||
files: ["**/*.{js,jsx,ts,tsx}"],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: "detect"
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
react,
|
||||
"react-hooks": reactHooks,
|
||||
"react-refresh": reactRefresh,
|
||||
prettier
|
||||
},
|
||||
rules: {
|
||||
...js.configs.recommended.rules,
|
||||
...react.configs.recommended.rules,
|
||||
...react.configs["jsx-runtime"].rules,
|
||||
...reactHooks.configs.recommended.rules,
|
||||
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_",
|
||||
ignoreRestSiblings: true
|
||||
}
|
||||
],
|
||||
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"no-debugger": "warn",
|
||||
"react-hooks/rules-of-hooks": "error",
|
||||
"react-hooks/exhaustive-deps": "warn",
|
||||
"prefer-const": "warn",
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"react/prop-types": "off",
|
||||
"@typescript-eslint/no-unused-expressions": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ["**/*.{ts,tsx}"],
|
||||
rules: {
|
||||
"no-undef": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ["**/*.{js,jsx}"],
|
||||
rules: {
|
||||
"no-undef": "warn",
|
||||
"no-unused-expressions": "off"
|
||||
}
|
||||
}
|
||||
);
|
|
@ -0,0 +1,35 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="/favicon.jpg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="description" content="Web site created using create-react-app" />
|
||||
<link rel="apple-touch-icon" href="/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
|
||||
<script src="/env.js"></script>
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
<title>Network resurrector</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
File diff suppressed because it is too large
Load Diff
|
@ -2,6 +2,7 @@
|
|||
"name": "network-resurrector-frontend",
|
||||
"version": "1.3.1",
|
||||
"description": "Frontend component of Network resurrector system",
|
||||
"type": "module",
|
||||
"author": {
|
||||
"name": "Tudor Stanciu",
|
||||
"email": "tudor.stanciu94@gmail.com",
|
||||
|
@ -13,54 +14,57 @@
|
|||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@flare/js-utils": "^1.1.0",
|
||||
"@flare/tuitio-client-react": "^1.2.10",
|
||||
"@mui/icons-material": "^5.14.16",
|
||||
"@mui/lab": "^5.0.0-alpha.169",
|
||||
"@mui/material": "^5.14.16",
|
||||
"axios": "^1.6.8",
|
||||
"i18next": "^22.4.15",
|
||||
"i18next-browser-languagedetector": "^7.0.1",
|
||||
"i18next-http-backend": "^2.2.0",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@flare/tuitio-client-react": "^1.3.0",
|
||||
"@flare/utiliyo": "^1.2.1",
|
||||
"@mui/icons-material": "^7.0.2",
|
||||
"@mui/lab": "^7.0.0-beta.11",
|
||||
"@mui/material": "^7.0.2",
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
"axios": "^1.9.0",
|
||||
"i18next": "^25.0.1",
|
||||
"i18next-browser-languagedetector": "^8.0.5",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-i18next": "^12.2.2",
|
||||
"moment": "^2.30.1",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-i18next": "^15.5.1",
|
||||
"react-lazylog": "^4.5.3",
|
||||
"react-router-dom": "^6.10.0",
|
||||
"react-toastify": "^9.1.3",
|
||||
"react-router-dom": "^7.5.2",
|
||||
"react-toastify": "^11.0.5",
|
||||
"react-world-flags": "^1.6.0",
|
||||
"swr": "^2.2.5"
|
||||
"swr": "^2.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@types/lodash": "^4.17.7",
|
||||
"@types/react": "^18.2.33",
|
||||
"@types/react-dom": "^18.2.14",
|
||||
"@types/react-world-flags": "^1.4.5",
|
||||
"eslint": "^8.34.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"prettier": "^2.8.4",
|
||||
"react-app-rewired": "^2.2.1",
|
||||
"typescript": "^4.9.5"
|
||||
"@eslint/js": "^9.25.1",
|
||||
"@types/lodash": "^4.17.16",
|
||||
"@types/react": "^19.1.2",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"@types/react-world-flags": "^1.6.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint-plugin-prettier": "^5.2.6",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.20",
|
||||
"globals": "^16.0.0",
|
||||
"prettier": "^3.5.3",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.31.0",
|
||||
"vite": "^6.3.3",
|
||||
"vite-plugin-checker": "^0.9.1",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-app-rewired start",
|
||||
"build": "react-app-rewired build",
|
||||
"test": "react-app-rewired test",
|
||||
"eject": "react-app-rewired eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
"start": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview",
|
||||
"test": "jest"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.jpg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/icon?family=Material+Icons"
|
||||
/>
|
||||
<script src="%PUBLIC_URL%/env.js"></script>
|
||||
<title>Network resurrector</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
|
@ -2,13 +2,13 @@
|
|||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const prefix = "REACT_APP_";
|
||||
const publicUrl = process.env.PUBLIC_URL || "";
|
||||
const prefix = "VITE_APP_";
|
||||
const publicUrl = import.meta.env.PUBLIC_URL || "";
|
||||
const scriptPath = path.join("./application", publicUrl, "env.js");
|
||||
|
||||
function generateScriptContent() {
|
||||
const prefixRegex = new RegExp(`^${prefix}`);
|
||||
const env = process.env;
|
||||
const env = import.meta.env;
|
||||
const config = Object.keys(env)
|
||||
.filter(key => prefixRegex.test(key))
|
||||
.reduce((c, key) => Object.assign({}, c, { [key]: env[key] }), {});
|
||||
|
@ -24,6 +24,4 @@ function saveScriptContent(scriptContents) {
|
|||
console.log("Setting environment variables...");
|
||||
const scriptContent = generateScriptContent();
|
||||
saveScriptContent(scriptContent);
|
||||
console.log(
|
||||
`Updated ${scriptPath} with ${prefix}* environment variables: ${scriptContent}.`
|
||||
);
|
||||
console.log(`Updated ${scriptPath} with ${prefix}* environment variables: ${scriptContent}.`);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React from "react";
|
||||
import { UserPermissionsProvider, SensitiveInfoProvider } from "../providers";
|
||||
import { SensitiveInfoProvider } from "../providers";
|
||||
import { UserPermissionsProvider } from "../units/permissions";
|
||||
import AppLayout from "./layout/AppLayout";
|
||||
|
||||
const App = () => {
|
|
@ -4,7 +4,7 @@ import { BrowserRouter, Navigate, Route, Routes, useLocation } from "react-route
|
|||
import { useTuitioToken } from "@flare/tuitio-client-react";
|
||||
import LoginContainer from "../features/login/components/LoginContainer";
|
||||
|
||||
const PrivateRoute = ({ children }: { children: JSX.Element }): JSX.Element => {
|
||||
const PrivateRoute = ({ children }: { children: React.ReactElement }): React.ReactElement => {
|
||||
const { valid } = useTuitioToken();
|
||||
const location = useLocation();
|
||||
return valid ? (
|
||||
|
@ -20,7 +20,7 @@ const PrivateRoute = ({ children }: { children: JSX.Element }): JSX.Element => {
|
|||
);
|
||||
};
|
||||
|
||||
const PublicRoute = ({ children }: { children: JSX.Element }): JSX.Element => {
|
||||
const PublicRoute = ({ children }: { children: React.ReactElement }): React.ReactElement => {
|
||||
const location = useLocation();
|
||||
const { valid } = useTuitioToken();
|
||||
const to = useMemo(() => {
|
||||
|
@ -43,7 +43,7 @@ const PublicRoute = ({ children }: { children: JSX.Element }): JSX.Element => {
|
|||
|
||||
const AppRouter: React.FC = () => {
|
||||
return (
|
||||
<BrowserRouter basename={process.env.PUBLIC_URL || ""}>
|
||||
<BrowserRouter basename={import.meta.env.PUBLIC_URL || ""}>
|
||||
<Routes>
|
||||
<Route path="/" element={<Navigate to="/dashboard" />} />
|
||||
<Route
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Typography, Box } from "@mui/material";
|
||||
|
||||
const styles = {
|
||||
|
@ -20,12 +19,18 @@ const styles = {
|
|||
}
|
||||
};
|
||||
|
||||
const PageTitle = ({ text, toolBar, navigation }) => {
|
||||
type PageTitleProps = {
|
||||
text: string;
|
||||
toolBar?: React.ReactNode;
|
||||
navigation?: React.ReactNode;
|
||||
};
|
||||
|
||||
const PageTitle: React.FC<PageTitleProps> = ({ text, toolBar, navigation }) => {
|
||||
return (
|
||||
<Box sx={styles.box}>
|
||||
{navigation && navigation}
|
||||
<Box sx={styles.title}>
|
||||
<Typography sx={styles.titleText} variant="h3" size="sm">
|
||||
<Typography sx={styles.titleText} variant="h3">
|
||||
{text}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
@ -34,10 +39,4 @@ const PageTitle = ({ text, toolBar, navigation }) => {
|
|||
);
|
||||
};
|
||||
|
||||
PageTitle.propTypes = {
|
||||
text: PropTypes.string.isRequired,
|
||||
toolBar: PropTypes.node,
|
||||
navigation: PropTypes.node
|
||||
};
|
||||
|
||||
export default PageTitle;
|
|
@ -4,7 +4,7 @@ import { Icon as MuiIcon, IconProps } from "@mui/material";
|
|||
|
||||
interface Props extends IconProps {
|
||||
code?: string | null;
|
||||
fallback?: JSX.Element;
|
||||
fallback?: React.ReactElement;
|
||||
}
|
||||
|
||||
const DynamicIcon: React.FC<Props> = ({ code, fallback, ...res }) => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from "react";
|
||||
import { IconButton } from "@mui/material";
|
||||
import { Brightness2 as MoonIcon, WbSunny as SunIcon } from "@mui/icons-material";
|
||||
import { useApplicationTheme } from "../../providers/ThemeProvider";
|
||||
import { useApplicationTheme } from "../../hooks";
|
||||
|
||||
const LightDarkToggle = () => {
|
||||
const { isDark, onDarkModeChanged } = useApplicationTheme();
|
|
@ -5,7 +5,7 @@ type MenuItem = {
|
|||
code: string;
|
||||
name: string;
|
||||
route: string;
|
||||
icon: JSX.Element;
|
||||
icon: React.ReactElement;
|
||||
order: number;
|
||||
subMenus?: MenuItem[];
|
||||
};
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
const COLOR_SCHEME = {
|
||||
LIGHT: "light",
|
||||
DARK: "dark"
|
||||
};
|
||||
|
||||
export { COLOR_SCHEME };
|
|
@ -7,13 +7,13 @@ const ReleaseNoteSummary = ({ releaseNote, collapsed }) => {
|
|||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Grid container>
|
||||
<Grid item xs={6} sm={2} md={2}>
|
||||
<Grid container flexGrow={1}>
|
||||
<Grid size={{ xs: 12, sm: 2 }}>
|
||||
<Typography variant={collapsed ? "subtitle2" : "h6"}>
|
||||
{`${t("About.ReleaseNotes.Version")}: ${releaseNote.version}`}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={6} sm={2} md={collapsed ? 2 : 4}>
|
||||
<Grid size={{ xs: 6, sm: 2, md: collapsed ? 2 : 4 }}>
|
||||
<Typography variant={collapsed ? "subtitle2" : "h6"}>
|
||||
{`${t("About.ReleaseNotes.Date")}: ${t("DATE_FORMAT", {
|
||||
date: { value: releaseNote.date, format: "DD-MM-YYYY HH:mm" }
|
||||
|
@ -21,7 +21,7 @@ const ReleaseNoteSummary = ({ releaseNote, collapsed }) => {
|
|||
</Typography>
|
||||
</Grid>
|
||||
{collapsed && (
|
||||
<Grid item xs={12} sm={8} md={8}>
|
||||
<Grid size={{ xs: 12, sm: 8, md: 6 }}>
|
||||
<Typography variant="body2">{releaseNote.notes[0]}</Typography>
|
||||
</Grid>
|
||||
)}
|
|
@ -43,7 +43,7 @@ const SystemVersionComponent: React.FC<Props> = ({ data }) => {
|
|||
|
||||
const frontend = t("DATE_FORMAT", {
|
||||
date: {
|
||||
value: process.env.APP_DATE ?? new Date(),
|
||||
value: import.meta.env.APP_DATE ?? new Date(),
|
||||
format
|
||||
}
|
||||
});
|
||||
|
@ -109,7 +109,7 @@ const SystemVersionComponent: React.FC<Props> = ({ data }) => {
|
|||
primary={
|
||||
<VersionLabel>
|
||||
{t("About.System.Version.Frontend", {
|
||||
version: process.env.APP_VERSION ?? packageData.version
|
||||
version: import.meta.env.APP_VERSION ?? packageData.version
|
||||
})}
|
||||
</VersionLabel>
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ type GridCellProps = {
|
|||
const GridCell: React.FC<GridCellProps> = ({ label, value }) => {
|
||||
const { mask } = useSensitiveInfo();
|
||||
return (
|
||||
<Grid item xs={12} md={6} lg={3}>
|
||||
<Grid size={{ xs: 12, md: 6, lg: 3 }}>
|
||||
<DataLabel label={label} data={mask(value)} />
|
||||
</Grid>
|
||||
);
|
||||
|
@ -78,12 +78,13 @@ const MachineAccordion: React.FC<Props> = ({ machine, actions, logs, addLog }) =
|
|||
<AccordionSummary aria-controls={`machine-${machine.machineId}-summary`} id={`machine-${machine.machineId}`}>
|
||||
<Grid
|
||||
container
|
||||
flex={1}
|
||||
sx={{
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Grid item xs={11}>
|
||||
<Grid size={11}>
|
||||
<Grid container>
|
||||
<GridCell label={t("Machine.FullName")} value={machine.fullMachineName} />
|
||||
<GridCell label={t("Machine.Name")} value={machine.machineName} />
|
||||
|
@ -91,7 +92,7 @@ const MachineAccordion: React.FC<Props> = ({ machine, actions, logs, addLog }) =
|
|||
<GridCell label={t("Machine.MAC")} value={machine.macAddress} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={1} style={{ textAlign: "right" }}>
|
||||
<Grid size={1} style={{ textAlign: "right" }}>
|
||||
<ActionsGroup machine={machine} actions={actions} addLog={addLog} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useState, useCallback } from "react";
|
||||
import MachineTableRow from "./MachineTableRow";
|
||||
import MachineAccordion from "./MachineAccordion";
|
||||
import { ViewModes } from "./ViewModeSelection";
|
||||
import { ViewModes } from "../constants";
|
||||
import { blip } from "../../../utils";
|
||||
import { LastPage, RotateLeft, Launch, Stop } from "@mui/icons-material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
|
|
@ -3,7 +3,8 @@ import { NetworkStateContext, NetworkDispatchContext } from "../../network/state
|
|||
import MachinesListComponent from "./MachinesListComponent";
|
||||
import PageTitle from "../../../components/common/PageTitle";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ViewModeSelection, { ViewModes } from "./ViewModeSelection";
|
||||
import ViewModeSelection from "./ViewModeSelection";
|
||||
import { ViewModes } from "../constants";
|
||||
import { endpoints } from "../../../utils/api";
|
||||
import { useSWR, fetcher } from "units/swr";
|
||||
import { blip } from "utils";
|
||||
|
|
|
@ -4,7 +4,7 @@ import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from
|
|||
import Paper from "@mui/material/Paper";
|
||||
import MachineContainer from "./MachineContainer";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ViewModes } from "./ViewModeSelection";
|
||||
import { ViewModes } from "../constants";
|
||||
|
||||
const MachinesList = ({ machines, viewMode }) => {
|
||||
return (
|
|
@ -5,11 +5,7 @@ import ViewListIcon from "@mui/icons-material/ViewList";
|
|||
import { ToggleButtonGroup, ToggleButton } from "@mui/material";
|
||||
import { Tooltip } from "@mui/material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const ViewModes = {
|
||||
TABLE: "table",
|
||||
ACCORDION: "accordion"
|
||||
};
|
||||
import { ViewModes } from "../constants";
|
||||
|
||||
const ViewModeSelection = ({ initialMode, callback }) => {
|
||||
const [state, setState] = useState({
|
|
@ -2,7 +2,7 @@ import React from "react";
|
|||
import PropTypes from "prop-types";
|
||||
import { IconButton, Tooltip } from "@mui/material";
|
||||
|
||||
const ActionButton = React.forwardRef(props => {
|
||||
const ActionButton = React.forwardRef((props, ref) => {
|
||||
const { action, machine, callback, disabled } = props;
|
||||
const id = `machine-item-${machine.machineId}-${action.code}`;
|
||||
const handleActionClick = event => {
|
||||
|
@ -21,6 +21,7 @@ const ActionButton = React.forwardRef(props => {
|
|||
onFocus={event => event.stopPropagation()}
|
||||
onClick={handleActionClick}
|
||||
disabled={disabled}
|
||||
ref={ref}
|
||||
>
|
||||
<action.icon />
|
||||
</IconButton>
|
||||
|
@ -36,7 +37,8 @@ ActionButton.propTypes = {
|
|||
action: PropTypes.shape({
|
||||
code: PropTypes.string.isRequired,
|
||||
tooltip: PropTypes.string.isRequired,
|
||||
effect: PropTypes.func.isRequired
|
||||
effect: PropTypes.func.isRequired,
|
||||
icon: PropTypes.elementType.isRequired
|
||||
}).isRequired,
|
||||
callback: PropTypes.func,
|
||||
disabled: PropTypes.bool
|
|
@ -13,8 +13,8 @@ const initialState = { on: false };
|
|||
const defaultPingInterval = 1200000; //20 minutes
|
||||
const defaultStartingTime = 300000; //5 minutes
|
||||
|
||||
const pingInterval = parseInt(process.env.REACT_APP_MACHINE_PING_INTERVAL ?? "") || defaultPingInterval;
|
||||
const startingTime = parseInt(process.env.REACT_APP_MACHINE_STARTING_TIME ?? "") || defaultStartingTime;
|
||||
const pingInterval = parseInt(import.meta.env.VITE_APP_MACHINE_PING_INTERVAL ?? "") || defaultPingInterval;
|
||||
const startingTime = parseInt(import.meta.env.VITE_APP_MACHINE_STARTING_TIME ?? "") || defaultStartingTime;
|
||||
|
||||
type Props = {
|
||||
machine: Machine;
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
export const ViewModes = {
|
||||
TABLE: "table",
|
||||
ACCORDION: "accordion"
|
||||
};
|
|
@ -4,7 +4,7 @@ import NetworkStateProvider from "../state/NetworkStateProvider";
|
|||
import { usePermissions } from "../../../hooks";
|
||||
import NotAllowed from "../../../components/common/NotAllowed";
|
||||
|
||||
const NetworkContainer = (): JSX.Element | null => {
|
||||
const NetworkContainer = (): React.ReactElement | null => {
|
||||
const { loading, viewMachines } = usePermissions();
|
||||
|
||||
if (loading) return null;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from "react";
|
||||
import { useApplicationTheme } from "../../../providers/ThemeProvider";
|
||||
import { useApplicationTheme } from "hooks";
|
||||
import { Grid, Paper, FormControlLabel, Switch, Box } from "@mui/material";
|
||||
import LanguageContainer from "./language/LanguageContainer";
|
||||
import { PaperTitle } from "components/common";
|
||||
|
@ -24,7 +24,7 @@ const AppearanceComponent = () => {
|
|||
<Paper variant="outlined">
|
||||
<PaperTitle text={t("Settings.Appearance.Title")} />
|
||||
<Grid container spacing={0}>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3}>
|
||||
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }}>
|
||||
<FormControlLabel
|
||||
value="start"
|
||||
control={<Switch checked={isDark} onChange={handleChange} color="secondary" name="dark-mode-switch" />}
|
||||
|
@ -32,7 +32,7 @@ const AppearanceComponent = () => {
|
|||
labelPlacement="start"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3}>
|
||||
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }}>
|
||||
<FormControlLabel
|
||||
value="start"
|
||||
control={
|
|
@ -156,9 +156,9 @@ const ContactOptions = ({ contactOptions, userName }) => {
|
|||
const chunks = useMemo(() => sliceContactOptions(sorted), [sorted]);
|
||||
|
||||
return (
|
||||
<Grid container>
|
||||
<Grid container flex={1}>
|
||||
{chunks.map((chunk, index) => (
|
||||
<Grid item xs={12} md={6} key={index}>
|
||||
<Grid size={{ xs: 12, md: 6 }} key={index}>
|
||||
<ContactOptionList options={chunk} />
|
||||
</Grid>
|
||||
))}
|
|
@ -12,7 +12,7 @@ const SecurityComponent = ({ userGroups, userRoles }) => {
|
|||
return (
|
||||
<Paper>
|
||||
<Grid container>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<div style={styles.paper}>
|
||||
<Typography gutterBottom variant="body1">
|
||||
{t("User.Profile.Security.UserGroups")}
|
||||
|
@ -24,7 +24,7 @@ const SecurityComponent = ({ userGroups, userRoles }) => {
|
|||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<div style={styles.paper}>
|
||||
<Typography gutterBottom variant="body1">
|
||||
{t("User.Profile.Security.UserRoles")}
|
|
@ -1,5 +1,6 @@
|
|||
import { useSensitiveInfo } from "../providers/SensitiveInfoProvider";
|
||||
import { usePermissions } from "../providers/UserPermissionsProvider";
|
||||
import useSensitiveInfo from "./useSensitiveInfo";
|
||||
import { usePermissions } from "../units/permissions";
|
||||
import { useClipboard } from "./useClipboard";
|
||||
import useApplicationTheme from "./useApplicationTheme";
|
||||
|
||||
export { useSensitiveInfo, usePermissions, useClipboard };
|
||||
export { useSensitiveInfo, usePermissions, useClipboard, useApplicationTheme };
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import { useContext } from "react";
|
||||
import { ApplicationThemeContext } from "../providers/contexts";
|
||||
import { COLOR_SCHEME } from "../constants";
|
||||
|
||||
const useApplicationTheme = () => {
|
||||
const { state, actions } = useContext(ApplicationThemeContext);
|
||||
const { onColorSchemeChanged } = actions;
|
||||
|
||||
const { scheme } = state;
|
||||
const onDarkModeChanged = active => onColorSchemeChanged(active ? COLOR_SCHEME.DARK : COLOR_SCHEME.LIGHT);
|
||||
|
||||
return { isDark: scheme === COLOR_SCHEME.DARK, onDarkModeChanged };
|
||||
};
|
||||
|
||||
export default useApplicationTheme;
|
|
@ -0,0 +1,24 @@
|
|||
import { useContext } from "react";
|
||||
import { obfuscate } from "../utils/obfuscateStrings";
|
||||
import { SensitiveInfoContext } from "../providers/contexts";
|
||||
|
||||
const useSensitiveInfo = () => {
|
||||
const { state, actions } = useContext(SensitiveInfoContext);
|
||||
const { enabled } = state;
|
||||
const { onSensitiveInfoEnabled } = actions;
|
||||
|
||||
const mask = text => {
|
||||
if (!enabled) return text;
|
||||
return obfuscate(text, "◾");
|
||||
};
|
||||
|
||||
const maskElements = list => {
|
||||
if (!enabled) return list;
|
||||
const maskedList = list.map(z => obfuscate(z, "◻️"));
|
||||
return maskedList;
|
||||
};
|
||||
|
||||
return { enabled, onSensitiveInfoEnabled, mask, maskElements };
|
||||
};
|
||||
|
||||
export default useSensitiveInfo;
|
|
@ -1,5 +1,5 @@
|
|||
import React, { Suspense } from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import ThemeProvider from "./providers/ThemeProvider";
|
||||
import { CssBaseline } from "@mui/material";
|
||||
import AppRouter from "./components/AppRouter";
|
||||
|
@ -8,10 +8,22 @@ import { ToastProvider } from "./providers";
|
|||
import env from "./utils/env";
|
||||
import "./utils/i18n";
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
|
||||
const domNode = document.getElementById("root");
|
||||
if (!domNode) {
|
||||
throw new Error("Could not find root element");
|
||||
}
|
||||
|
||||
const root = createRoot(domNode, {
|
||||
onUncaughtError: (error, errorInfo) => {
|
||||
console.error(error, errorInfo);
|
||||
},
|
||||
onCaughtError: (error, errorInfo) => {
|
||||
console.error(error, errorInfo);
|
||||
}
|
||||
});
|
||||
|
||||
root.render(
|
||||
<TuitioProvider tuitioUrl={env.REACT_APP_TUITIO_URL}>
|
||||
<TuitioProvider tuitioUrl={env.VITE_APP_TUITIO_URL}>
|
||||
<ThemeProvider>
|
||||
<CssBaseline />
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import React, { useReducer, useMemo, useContext } from "react";
|
||||
import React, { useReducer, useMemo } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { obfuscate } from "../utils/obfuscateStrings";
|
||||
|
||||
const SensitiveInfoContext = React.createContext();
|
||||
import { SensitiveInfoContext } from "./contexts";
|
||||
|
||||
const initialState = {
|
||||
enabled: false
|
||||
|
@ -26,25 +24,6 @@ const dispatchActions = dispatch => ({
|
|||
onSensitiveInfoEnabled: enabled => dispatch({ type: "onSensitiveInfoEnabled", payload: { enabled } })
|
||||
});
|
||||
|
||||
const useSensitiveInfo = () => {
|
||||
const { state, actions } = useContext(SensitiveInfoContext);
|
||||
const { enabled } = state;
|
||||
const { onSensitiveInfoEnabled } = actions;
|
||||
|
||||
const mask = text => {
|
||||
if (!enabled) return text;
|
||||
return obfuscate(text, "◾");
|
||||
};
|
||||
|
||||
const maskElements = list => {
|
||||
if (!enabled) return list;
|
||||
const maskedList = list.map(z => obfuscate(z, "◻️"));
|
||||
return maskedList;
|
||||
};
|
||||
|
||||
return { enabled, onSensitiveInfoEnabled, mask, maskElements };
|
||||
};
|
||||
|
||||
const SensitiveInfoProvider = ({ children }) => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const actions = useMemo(() => dispatchActions(dispatch), [dispatch]);
|
||||
|
@ -65,5 +44,5 @@ SensitiveInfoProvider.propTypes = {
|
|||
children: PropTypes.node.isRequired
|
||||
};
|
||||
|
||||
export { SensitiveInfoProvider, useSensitiveInfo };
|
||||
export { SensitiveInfoProvider };
|
||||
export default SensitiveInfoProvider;
|
|
@ -1,17 +1,13 @@
|
|||
import React, { useReducer, useMemo, useContext } from "react";
|
||||
import React, { useReducer, useMemo } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { ThemeProvider as MuiThemeProvider } from "@mui/material/styles";
|
||||
import { localStorage } from "@flare/js-utils";
|
||||
import { localStorage } from "@flare/utiliyo";
|
||||
import { getThemes } from "../units/themes";
|
||||
import { ApplicationThemeContext } from "./contexts";
|
||||
import { COLOR_SCHEME } from "../constants";
|
||||
|
||||
const ApplicationThemeContext = React.createContext();
|
||||
const LOCAL_STORAGE_COLOR_SCHEME_KEY = "network-resurrector-color-scheme";
|
||||
|
||||
const COLOR_SCHEME = {
|
||||
LIGHT: "light",
|
||||
DARK: "dark"
|
||||
};
|
||||
|
||||
const colorScheme = localStorage.getItem(LOCAL_STORAGE_COLOR_SCHEME_KEY);
|
||||
const prefersDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
|
||||
|
@ -40,16 +36,6 @@ const dispatchActions = dispatch => ({
|
|||
}
|
||||
});
|
||||
|
||||
const useApplicationTheme = () => {
|
||||
const { state, actions } = useContext(ApplicationThemeContext);
|
||||
const { onColorSchemeChanged } = actions;
|
||||
|
||||
const { scheme } = state;
|
||||
const onDarkModeChanged = active => onColorSchemeChanged(active ? COLOR_SCHEME.DARK : COLOR_SCHEME.LIGHT);
|
||||
|
||||
return { isDark: scheme === COLOR_SCHEME.DARK, onDarkModeChanged };
|
||||
};
|
||||
|
||||
const ThemeProvider = ({ children }) => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const actions = useMemo(() => dispatchActions(dispatch), [dispatch]);
|
||||
|
@ -71,5 +57,5 @@ ThemeProvider.propTypes = {
|
|||
children: PropTypes.node.isRequired
|
||||
};
|
||||
|
||||
export { ThemeProvider, ApplicationThemeContext, useApplicationTheme };
|
||||
export { ThemeProvider };
|
||||
export default ThemeProvider;
|
|
@ -0,0 +1,4 @@
|
|||
import React from "react";
|
||||
|
||||
export const SensitiveInfoContext = React.createContext();
|
||||
export const ApplicationThemeContext = React.createContext();
|
|
@ -1,6 +1,5 @@
|
|||
import ThemeProvider from "./ThemeProvider";
|
||||
import ToastProvider from "./ToastProvider";
|
||||
import SensitiveInfoProvider from "./SensitiveInfoProvider";
|
||||
import UserPermissionsProvider from "./UserPermissionsProvider";
|
||||
|
||||
export { ThemeProvider, ToastProvider, SensitiveInfoProvider, UserPermissionsProvider };
|
||||
export { ThemeProvider, ToastProvider, SensitiveInfoProvider };
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import React, { useState, useMemo, ReactNode } from "react";
|
||||
import { endpoints } from "../../utils/api";
|
||||
import { PermissionsDto } from "types/dtos";
|
||||
import { useSWR, fetcher } from "units/swr";
|
||||
import { blip } from "utils";
|
||||
|
||||
type UserPermissionsContextPayload = {
|
||||
permissions: string[];
|
||||
loading: boolean;
|
||||
};
|
||||
|
||||
const initialState: UserPermissionsContextPayload = {
|
||||
permissions: [],
|
||||
loading: true
|
||||
};
|
||||
|
||||
const UserPermissionsContext = React.createContext<UserPermissionsContextPayload>(initialState);
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const UserPermissionsProvider: React.FC<Props> = ({ children }) => {
|
||||
const [state, setState] = useState<UserPermissionsContextPayload>(initialState);
|
||||
|
||||
const url = useMemo(() => (state.permissions?.length ? null : endpoints.security.permissions), [state.permissions]);
|
||||
useSWR<PermissionsDto, Error>(url, fetcher, {
|
||||
revalidateOnFocus: false,
|
||||
onError: err => blip.error(err.message),
|
||||
onSuccess: data => setState({ ...data, loading: false })
|
||||
});
|
||||
|
||||
return <UserPermissionsContext.Provider value={state}>{children}</UserPermissionsContext.Provider>;
|
||||
};
|
||||
|
||||
export { UserPermissionsProvider, UserPermissionsContext };
|
||||
export default UserPermissionsProvider;
|
|
@ -1,8 +1,5 @@
|
|||
import React, { useState, useContext, useMemo, ReactNode } from "react";
|
||||
import { endpoints } from "../utils/api";
|
||||
import { PermissionsDto } from "types/dtos";
|
||||
import { useSWR, fetcher } from "units/swr";
|
||||
import { blip } from "utils";
|
||||
import { useContext, useMemo } from "react";
|
||||
import { UserPermissionsContext } from "./UserPermissionsProvider";
|
||||
|
||||
const permissionCodes = {
|
||||
VIEW_DASHBOARD: "VIEW_DASHBOARD",
|
||||
|
@ -15,16 +12,6 @@ const permissionCodes = {
|
|||
SYSTEM_ADMINISTRATION: "SYSTEM_ADMINISTRATION"
|
||||
};
|
||||
|
||||
type UserPermissionsContextPayload = {
|
||||
permissions: string[];
|
||||
loading: boolean;
|
||||
};
|
||||
|
||||
const initialState: UserPermissionsContextPayload = {
|
||||
permissions: [],
|
||||
loading: true
|
||||
};
|
||||
|
||||
const getPermissionFlags = (permissions: string[]) => {
|
||||
const viewDashboard = permissions.includes(permissionCodes.VIEW_DASHBOARD) ?? false;
|
||||
const manageUsers = permissions.includes(permissionCodes.MANAGE_USERS) ?? false;
|
||||
|
@ -56,30 +43,10 @@ const getPermissionFlags = (permissions: string[]) => {
|
|||
};
|
||||
};
|
||||
|
||||
const UserPermissionsContext = React.createContext<UserPermissionsContextPayload>(initialState);
|
||||
|
||||
const usePermissions = () => {
|
||||
const { permissions, loading } = useContext(UserPermissionsContext);
|
||||
const flags = useMemo(() => getPermissionFlags(permissions), [permissions]);
|
||||
return { loading, ...flags };
|
||||
};
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const UserPermissionsProvider: React.FC<Props> = ({ children }) => {
|
||||
const [state, setState] = useState<UserPermissionsContextPayload>(initialState);
|
||||
|
||||
const url = useMemo(() => (state.permissions?.length ? null : endpoints.security.permissions), [state.permissions]);
|
||||
useSWR<PermissionsDto, Error>(url, fetcher, {
|
||||
revalidateOnFocus: false,
|
||||
onError: err => blip.error(err.message),
|
||||
onSuccess: data => setState({ ...data, loading: false })
|
||||
});
|
||||
|
||||
return <UserPermissionsContext.Provider value={state}>{children}</UserPermissionsContext.Provider>;
|
||||
};
|
||||
|
||||
export { UserPermissionsProvider, usePermissions };
|
||||
export default UserPermissionsProvider;
|
||||
export { usePermissions };
|
|
@ -0,0 +1,4 @@
|
|||
import UserPermissionsProvider from "./UserPermissionsProvider";
|
||||
|
||||
export * from "./hooks";
|
||||
export { UserPermissionsProvider };
|
|
@ -1,6 +1,6 @@
|
|||
import env from "../utils/env";
|
||||
|
||||
const apiHost = env.REACT_APP_NETWORK_RESURRECTOR_API_URL;
|
||||
const apiHost = env.VITE_APP_NETWORK_RESURRECTOR_API_URL;
|
||||
|
||||
const networkRoute = `${apiHost}/network`;
|
||||
const systemRoute = `${apiHost}/system`;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const runtimeEnv = window.env;
|
||||
const compileEnv = process.env;
|
||||
const compileEnv = import.meta.env;
|
||||
const env = { ...compileEnv, ...runtimeEnv };
|
||||
export default env;
|
||||
|
|
|
@ -37,8 +37,8 @@ i18n
|
|||
|
||||
if (format === "intlTimeFromX") {
|
||||
if (value && moment(value.start).isValid()) {
|
||||
let startDate = moment(value.start);
|
||||
let endDate = moment(value.end);
|
||||
const startDate = moment(value.start);
|
||||
const endDate = moment(value.end);
|
||||
return moment(endDate).from(startDate, true);
|
||||
}
|
||||
return "";
|
||||
|
@ -46,9 +46,9 @@ i18n
|
|||
|
||||
if (format === "intlHoursFromX") {
|
||||
if (value && moment(value.start).isValid()) {
|
||||
let startDate = moment(value.start);
|
||||
let endDate = moment(value.end);
|
||||
let span = moment.duration(endDate - startDate);
|
||||
const startDate = moment(value.start);
|
||||
const endDate = moment(value.end);
|
||||
const span = moment.duration(endDate - startDate);
|
||||
return `${parseInt(span.asHours(), 10)}h ${parseInt(span.asMinutes() % 60, 10)}m`;
|
||||
}
|
||||
return "";
|
||||
|
@ -77,7 +77,7 @@ i18n
|
|||
}
|
||||
},
|
||||
backend: {
|
||||
loadPath: `${process.env.PUBLIC_URL || ""}/locales/{{lng}}/{{ns}}.json`
|
||||
loadPath: `${import.meta.env.PUBLIC_URL || ""}/locales/{{lng}}/{{ns}}.json`
|
||||
}
|
||||
},
|
||||
() => {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
/// <reference types="vite/client" />
|
|
@ -18,7 +18,9 @@
|
|||
"jsx": "react-jsx",
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": true,
|
||||
"allowUnreachableCode": false
|
||||
"allowUnreachableCode": false,
|
||||
"types": ["vite/client"]
|
||||
},
|
||||
"include": ["src"]
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import viteTsconfigPaths from "vite-tsconfig-paths";
|
||||
import eslintPlugin from "vite-plugin-eslint";
|
||||
import checker from "vite-plugin-checker";
|
||||
|
||||
export default defineConfig({
|
||||
// depending on your application, base can also be "/"
|
||||
base: "/",
|
||||
plugins: [
|
||||
react({
|
||||
jsxImportSource: "@emotion/react",
|
||||
babel: {
|
||||
plugins: ["@emotion/babel-plugin"]
|
||||
}
|
||||
}),
|
||||
viteTsconfigPaths(),
|
||||
eslintPlugin({
|
||||
cache: false
|
||||
}),
|
||||
checker({ typescript: true })
|
||||
],
|
||||
server: {
|
||||
open: true,
|
||||
port: 3000
|
||||
},
|
||||
build: {
|
||||
outDir: "build"
|
||||
}
|
||||
});
|
Loading…
Reference in New Issue