diff --git a/interface/package.json b/interface/package.json
index ad0d7a710..84ce39639 100644
--- a/interface/package.json
+++ b/interface/package.json
@@ -45,7 +45,7 @@
"@eslint/js": "^10.0.1",
"@preact/preset-vite": "^2.10.5",
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
- "@types/node": "^25.9.3",
+ "@types/node": "^26.0.0",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
"concurrently": "^10.0.3",
diff --git a/interface/pnpm-lock.yaml b/interface/pnpm-lock.yaml
index f7bb2c04a..735c6571a 100644
--- a/interface/pnpm-lock.yaml
+++ b/interface/pnpm-lock.yaml
@@ -65,13 +65,13 @@ importers:
version: 10.0.1(eslint@10.5.0)
'@preact/preset-vite':
specifier: ^2.10.5
- version: 2.10.5(@babel/core@7.29.7)(preact@10.29.2)(vite@8.0.16(@types/node@25.9.3)(terser@5.48.0))
+ version: 2.10.5(@babel/core@7.29.7)(preact@10.29.2)(vite@8.0.16(@types/node@26.0.0)(terser@5.48.0))
'@trivago/prettier-plugin-sort-imports':
specifier: ^6.0.2
version: 6.0.2(prettier@3.8.4)
'@types/node':
- specifier: ^25.9.3
- version: 25.9.3
+ specifier: ^26.0.0
+ version: 26.0.0
'@types/react':
specifier: ^19.2.17
version: 19.2.17
@@ -101,10 +101,10 @@ importers:
version: 8.61.1(eslint@10.5.0)(typescript@6.0.3)
vite:
specifier: ^8.0.16
- version: 8.0.16(@types/node@25.9.3)(terser@5.48.0)
+ version: 8.0.16(@types/node@26.0.0)(terser@5.48.0)
vite-plugin-imagemin:
specifier: ^0.6.1
- version: 0.6.1(vite@8.0.16(@types/node@25.9.3)(terser@5.48.0))
+ version: 0.6.1(vite@8.0.16(@types/node@26.0.0)(terser@5.48.0))
packages:
@@ -682,8 +682,8 @@ packages:
resolution: {integrity: sha512-zmPitbQ8+6zNutpwgcQuLcsEpn/Cj54Kbn7L5pX0Os5kdWplB7xPgEh/g+SWOB/qmows2gpuCaPyduq8ZZRnxA==}
deprecated: This is a stub types definition. minimatch provides its own type definitions, so you do not need this installed.
- '@types/node@25.9.3':
- resolution: {integrity: sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==}
+ '@types/node@26.0.0':
+ resolution: {integrity: sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==}
'@types/parse-json@4.0.2':
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
@@ -1175,8 +1175,8 @@ packages:
duplexer3@0.1.5:
resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==}
- electron-to-chromium@1.5.375:
- resolution: {integrity: sha512-ZWP5eB4BVPW/ZYo9252hQZHZ5XavtsTgpbhcmMmRwymavC5AsLWQWBPaKMeNd2LW0KGby5HPXvj7+sr4ta5j/Q==}
+ electron-to-chromium@1.5.376:
+ resolution: {integrity: sha512-cUVA7/RvbFTEuw/i3obUwDTRIXojaxkResf+ibByPFxjc6XK3VNtcQXV0NSbAlJ0FMjcJGgftVVB4Qo184EXvA==}
emoji-regex@10.6.0:
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
@@ -2136,8 +2136,8 @@ packages:
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
- nanoid@3.3.12:
- resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==}
+ nanoid@3.3.13:
+ resolution: {integrity: sha512-sPdqC6ByMVVGvF1ynvvMo0/o+oD1VX7DaHhijt1bFgjvBkHBib4t49GoNDhf2NDta4oeUNlaGbSt5K7qjZ955Q==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
@@ -2823,8 +2823,8 @@ packages:
unbzip2-stream@1.4.3:
resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==}
- undici-types@7.24.6:
- resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==}
+ undici-types@8.3.0:
+ resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==}
universalify@2.0.1:
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
@@ -3405,19 +3405,19 @@ snapshots:
'@popperjs/core@2.11.8': {}
- '@preact/preset-vite@2.10.5(@babel/core@7.29.7)(preact@10.29.2)(vite@8.0.16(@types/node@25.9.3)(terser@5.48.0))':
+ '@preact/preset-vite@2.10.5(@babel/core@7.29.7)(preact@10.29.2)(vite@8.0.16(@types/node@26.0.0)(terser@5.48.0))':
dependencies:
'@babel/core': 7.29.7
'@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7)
'@babel/plugin-transform-react-jsx-development': 7.29.7(@babel/core@7.29.7)
- '@prefresh/vite': 2.4.12(preact@10.29.2)(vite@8.0.16(@types/node@25.9.3)(terser@5.48.0))
+ '@prefresh/vite': 2.4.12(preact@10.29.2)(vite@8.0.16(@types/node@26.0.0)(terser@5.48.0))
'@rollup/pluginutils': 5.4.0
babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.29.7)
debug: 4.4.3
magic-string: 0.30.21
picocolors: 1.1.1
- vite: 8.0.16(@types/node@25.9.3)(terser@5.48.0)
- vite-prerender-plugin: 0.5.13(vite@8.0.16(@types/node@25.9.3)(terser@5.48.0))
+ vite: 8.0.16(@types/node@26.0.0)(terser@5.48.0)
+ vite-prerender-plugin: 0.5.13(vite@8.0.16(@types/node@26.0.0)(terser@5.48.0))
zimmerframe: 1.1.4
transitivePeerDependencies:
- preact
@@ -3432,7 +3432,7 @@ snapshots:
'@prefresh/utils@1.2.1': {}
- '@prefresh/vite@2.4.12(preact@10.29.2)(vite@8.0.16(@types/node@25.9.3)(terser@5.48.0))':
+ '@prefresh/vite@2.4.12(preact@10.29.2)(vite@8.0.16(@types/node@26.0.0)(terser@5.48.0))':
dependencies:
'@babel/core': 7.29.7
'@prefresh/babel-plugin': 0.5.3
@@ -3440,7 +3440,7 @@ snapshots:
'@prefresh/utils': 1.2.1
'@rollup/pluginutils': 4.2.1
preact: 10.29.2
- vite: 8.0.16(@types/node@25.9.3)(terser@5.48.0)
+ vite: 8.0.16(@types/node@26.0.0)(terser@5.48.0)
transitivePeerDependencies:
- supports-color
@@ -3543,7 +3543,7 @@ snapshots:
'@types/glob@7.2.0':
dependencies:
'@types/minimatch': 6.0.0
- '@types/node': 25.9.3
+ '@types/node': 26.0.0
'@types/imagemin-gifsicle@7.0.4':
dependencies:
@@ -3572,21 +3572,21 @@ snapshots:
'@types/imagemin@7.0.1':
dependencies:
- '@types/node': 25.9.3
+ '@types/node': 26.0.0
'@types/json-schema@7.0.15': {}
'@types/keyv@3.1.4':
dependencies:
- '@types/node': 25.9.3
+ '@types/node': 26.0.0
'@types/minimatch@6.0.0':
dependencies:
minimatch: 10.2.5
- '@types/node@25.9.3':
+ '@types/node@26.0.0':
dependencies:
- undici-types: 7.24.6
+ undici-types: 8.3.0
'@types/parse-json@4.0.2': {}
@@ -3606,11 +3606,11 @@ snapshots:
'@types/responselike@1.0.3':
dependencies:
- '@types/node': 25.9.3
+ '@types/node': 26.0.0
'@types/svgo@2.6.4':
dependencies:
- '@types/node': 25.9.3
+ '@types/node': 26.0.0
'@typescript-eslint/eslint-plugin@8.61.1(@typescript-eslint/parser@8.61.1(eslint@10.5.0)(typescript@6.0.3))(eslint@10.5.0)(typescript@6.0.3)':
dependencies:
@@ -3826,7 +3826,7 @@ snapshots:
dependencies:
baseline-browser-mapping: 2.10.38
caniuse-lite: 1.0.30001799
- electron-to-chromium: 1.5.375
+ electron-to-chromium: 1.5.376
node-releases: 2.0.48
update-browserslist-db: 1.2.3(browserslist@4.28.2)
@@ -4181,7 +4181,7 @@ snapshots:
duplexer3@0.1.5: {}
- electron-to-chromium@1.5.375: {}
+ electron-to-chromium@1.5.376: {}
emoji-regex@10.6.0: {}
@@ -5100,7 +5100,7 @@ snapshots:
ms@2.1.3: {}
- nanoid@3.3.12: {}
+ nanoid@3.3.13: {}
natural-compare@1.4.0: {}
@@ -5299,7 +5299,7 @@ snapshots:
postcss@8.5.15:
dependencies:
- nanoid: 3.3.12
+ nanoid: 3.3.13
picocolors: 1.1.1
source-map-js: 1.2.1
@@ -5741,7 +5741,7 @@ snapshots:
buffer: 5.7.1
through: 2.3.8
- undici-types@7.24.6: {}
+ undici-types@8.3.0: {}
universalify@2.0.1: {}
@@ -5774,7 +5774,7 @@ snapshots:
spdx-correct: 3.2.0
spdx-expression-parse: 3.0.1
- vite-plugin-imagemin@0.6.1(vite@8.0.16(@types/node@25.9.3)(terser@5.48.0)):
+ vite-plugin-imagemin@0.6.1(vite@8.0.16(@types/node@26.0.0)(terser@5.48.0)):
dependencies:
'@types/imagemin': 7.0.1
'@types/imagemin-gifsicle': 7.0.4
@@ -5799,11 +5799,11 @@ snapshots:
imagemin-webp: 6.1.0
jpegtran-bin: 6.0.1
pathe: 0.2.0
- vite: 8.0.16(@types/node@25.9.3)(terser@5.48.0)
+ vite: 8.0.16(@types/node@26.0.0)(terser@5.48.0)
transitivePeerDependencies:
- supports-color
- vite-prerender-plugin@0.5.13(vite@8.0.16(@types/node@25.9.3)(terser@5.48.0)):
+ vite-prerender-plugin@0.5.13(vite@8.0.16(@types/node@26.0.0)(terser@5.48.0)):
dependencies:
kolorist: 1.8.0
magic-string: 0.30.21
@@ -5811,9 +5811,9 @@ snapshots:
simple-code-frame: 1.3.0
source-map: 0.7.6
stack-trace: 1.0.0
- vite: 8.0.16(@types/node@25.9.3)(terser@5.48.0)
+ vite: 8.0.16(@types/node@26.0.0)(terser@5.48.0)
- vite@8.0.16(@types/node@25.9.3)(terser@5.48.0):
+ vite@8.0.16(@types/node@26.0.0)(terser@5.48.0):
dependencies:
lightningcss: 1.32.0
picomatch: 4.0.4
@@ -5821,7 +5821,7 @@ snapshots:
rolldown: 1.0.3
tinyglobby: 0.2.17
optionalDependencies:
- '@types/node': 25.9.3
+ '@types/node': 26.0.0
fsevents: 2.3.3
terser: 5.48.0
diff --git a/interface/src/App.tsx b/interface/src/App.tsx
index cd78d9539..9149eb940 100644
--- a/interface/src/App.tsx
+++ b/interface/src/App.tsx
@@ -1,8 +1,9 @@
import { memo, useEffect, useState } from 'react';
+import { Outlet } from 'react-router';
-import AppRouting from 'AppRouting';
import CustomTheme from 'CustomTheme';
import { Toaster } from 'components/toast';
+import { Authentication } from 'contexts/authentication';
import TypesafeI18n from 'i18n/i18n-react';
import type { Locales } from 'i18n/i18n-types';
import { loadLocaleAsync } from 'i18n/i18n-util.async';
@@ -43,7 +44,9 @@ const App = memo(() => {
return (
-
+
+
+
diff --git a/interface/src/AppRouting.tsx b/interface/src/AppRouting.tsx
deleted file mode 100644
index db7292a27..000000000
--- a/interface/src/AppRouting.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import { type FC, memo, useContext, useEffect, useRef } from 'react';
-import { Navigate, Route, Routes } from 'react-router';
-
-import AuthenticatedRouting from 'AuthenticatedRouting';
-import SignIn from 'SignIn';
-import { RequireAuthenticated, RequireUnauthenticated } from 'components';
-import { toast } from 'components/toast';
-import { Authentication, AuthenticationContext } from 'contexts/authentication';
-import { useI18nContext } from 'i18n/i18n-react';
-
-interface SecurityRedirectProps {
- readonly message: string;
- readonly signOut?: boolean;
-}
-
-const RootRedirect: FC = memo(
- ({ message, signOut = false }) => {
- const { signOut: contextSignOut } = useContext(AuthenticationContext);
- const hasShownToast = useRef(false);
-
- useEffect(() => {
- // Prevent duplicate toasts on strict mode or re-renders
- if (!hasShownToast.current) {
- hasShownToast.current = true;
- if (signOut) {
- contextSignOut(false);
- }
- toast.success(message);
- }
- // Only run once on mount - using ref to track execution
- }, []);
-
- return ;
- }
-);
-
-const AppRouting: FC = memo(() => {
- const { LL } = useI18nContext();
-
- return (
-
-
- }
- />
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
- );
-});
-
-export default AppRouting;
diff --git a/interface/src/AuthenticatedRouting.tsx b/interface/src/AuthenticatedRouting.tsx
deleted file mode 100644
index ae4eeb1b6..000000000
--- a/interface/src/AuthenticatedRouting.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import { memo, useContext } from 'react';
-import { Navigate, Route, Routes } from 'react-router';
-
-import Commands from 'app/main/Commands';
-import CustomEntities from 'app/main/CustomEntities';
-import Customizations from 'app/main/Customizations';
-import Dashboard from 'app/main/Dashboard';
-import Devices from 'app/main/Devices';
-import Help from 'app/main/Help';
-import Modules from 'app/main/Modules';
-import Scheduler from 'app/main/Scheduler';
-import Sensors from 'app/main/Sensors';
-import UserProfile from 'app/main/UserProfile';
-import APSettings from 'app/settings/APSettings';
-import ApplicationSettings from 'app/settings/ApplicationSettings';
-import DownloadUpload from 'app/settings/DownloadUpload';
-import MqttSettings from 'app/settings/MqttSettings';
-import NTPSettings from 'app/settings/NTPSettings';
-import Settings from 'app/settings/Settings';
-import Version from 'app/settings/Version';
-import Network from 'app/settings/network/Network';
-import Security from 'app/settings/security/Security';
-import APStatus from 'app/status/APStatus';
-import Activity from 'app/status/Activity';
-import HardwareStatus from 'app/status/HardwareStatus';
-import MqttStatus from 'app/status/MqttStatus';
-import NTPStatus from 'app/status/NTPStatus';
-import NetworkStatus from 'app/status/NetworkStatus';
-import Status from 'app/status/Status';
-import SystemLog from 'app/status/SystemLog';
-import { Layout } from 'components';
-import { AuthenticatedContext } from 'contexts/authentication';
-
-const AuthenticatedRouting = memo(() => {
- const { me } = useContext(AuthenticatedContext);
- return (
-
-
- } />
- } />
- } />
- } />
- } />
-
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
-
- {me.admin && (
- <>
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
-
- } />
- } />
-
- } />
- } />
- } />
- } />
- >
- )}
-
- } />
-
-
- );
-});
-
-export default AuthenticatedRouting;
diff --git a/interface/src/app/settings/network/Network.tsx b/interface/src/app/settings/network/Network.tsx
index e90e5b835..5f4ef6528 100644
--- a/interface/src/app/settings/network/Network.tsx
+++ b/interface/src/app/settings/network/Network.tsx
@@ -1,12 +1,5 @@
import { memo, useState } from 'react';
-import {
- Navigate,
- Route,
- Routes,
- matchRoutes,
- useLocation,
- useNavigate
-} from 'react-router';
+import { Outlet, useMatch, useNavigate } from 'react-router';
import { Tab } from '@mui/material';
@@ -14,27 +7,13 @@ import { RouterTabs, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import type { WiFiNetwork } from 'types';
-import NetworkSettings from './NetworkSettings';
import { WiFiConnectionContext } from './WiFiConnectionContext';
-import WiFiNetworkScanner from './WiFiNetworkScanner';
const Network = () => {
const { LL } = useI18nContext();
useLayoutTitle(LL.NETWORK(0));
- // this also works!
- // const routerTab = useMatch(`settings/network/:path/*`)?.pathname || false;
- const matchedRoutes = matchRoutes(
- [
- {
- path: '/settings/network/settings',
- element:
- },
- { path: '/settings/network/scan', element: }
- ],
- useLocation()
- );
- const routerTab = matchedRoutes?.[0]?.route.path || false;
+ const routerTab = useMatch('/settings/network/:tab')?.pathname || false;
const navigate = useNavigate();
@@ -64,14 +43,7 @@ const Network = () => {
/>
-
- } />
- } />
- }
- />
-
+
);
};
diff --git a/interface/src/app/settings/security/Security.tsx b/interface/src/app/settings/security/Security.tsx
index 5701a907c..1ddc639fc 100644
--- a/interface/src/app/settings/security/Security.tsx
+++ b/interface/src/app/settings/security/Security.tsx
@@ -1,31 +1,16 @@
import { memo } from 'react';
-import { Navigate, Route, Routes, matchRoutes, useLocation } from 'react-router';
+import { Outlet, useMatch } from 'react-router';
import { Tab } from '@mui/material';
import { RouterTabs, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
-import ManageUsers from './ManageUsers';
-import SecuritySettings from './SecuritySettings';
-
const Security = () => {
const { LL } = useI18nContext();
useLayoutTitle(LL.SECURITY(0));
- const location = useLocation();
-
- const matchedRoutes = matchRoutes(
- [
- {
- path: '/settings/security/settings',
- element:
- },
- { path: '/settings/security/users', element: }
- ],
- location
- );
- const routerTab = matchedRoutes?.[0]?.route.path || false;
+ const routerTab = useMatch('/settings/security/:tab')?.pathname || false;
return (
<>
@@ -36,14 +21,7 @@ const Security = () => {
/>
-
- } />
- } />
- }
- />
-
+
>
);
};
diff --git a/interface/src/components/routing/RequireAdmin.tsx b/interface/src/components/routing/RequireAdmin.tsx
index 20be11eda..c8f6942c1 100644
--- a/interface/src/components/routing/RequireAdmin.tsx
+++ b/interface/src/components/routing/RequireAdmin.tsx
@@ -1,17 +1,14 @@
import { memo, useContext } from 'react';
-import type { FC } from 'react';
-import { Navigate } from 'react-router';
+import { Navigate, Outlet } from 'react-router';
import { AuthenticatedContext } from 'contexts/authentication';
-import type { RequiredChildrenProps } from 'utils';
-const RequireAdmin: FC = ({ children }) => {
- const authenticatedContext = useContext(AuthenticatedContext);
- return authenticatedContext.me.admin ? (
- <>{children}>
- ) : (
-
- );
+// Layout-route guard: renders nested admin routes only for admins, otherwise
+// redirects home. Must be used inside the authenticated route subtree so that
+// AuthenticatedContext (and `me`) is available.
+const RequireAdmin = () => {
+ const { me } = useContext(AuthenticatedContext);
+ return me.admin ? : ;
};
export default memo(RequireAdmin);
diff --git a/interface/src/components/routing/RootRedirect.tsx b/interface/src/components/routing/RootRedirect.tsx
new file mode 100644
index 000000000..03b305ed0
--- /dev/null
+++ b/interface/src/components/routing/RootRedirect.tsx
@@ -0,0 +1,35 @@
+import { memo, useContext, useEffect, useRef } from 'react';
+import { Navigate } from 'react-router';
+
+import { toast } from 'components/toast';
+import { AuthenticationContext } from 'contexts/authentication';
+import { useI18nContext } from 'i18n/i18n-react';
+
+type RootRedirectKind = 'unauthorized' | 'fileUpdated';
+
+// Shows a one-shot toast and bounces back to "/". Used by the /unauthorized and
+// /fileUpdated routes. Resolves its own i18n message so it can be used directly
+// as a static route element.
+const RootRedirect = ({ kind }: { kind: RootRedirectKind }) => {
+ const { LL } = useI18nContext();
+ const { signOut } = useContext(AuthenticationContext);
+ const hasShownToast = useRef(false);
+
+ useEffect(() => {
+ // Guard against StrictMode double-invoke / re-renders.
+ if (hasShownToast.current) return;
+ hasShownToast.current = true;
+
+ if (kind === 'unauthorized') {
+ signOut(false);
+ toast.success(LL.PLEASE_SIGNIN());
+ } else {
+ toast.success(LL.UPLOAD_SUCCESSFUL());
+ }
+ // Run once on mount.
+ }, []);
+
+ return ;
+};
+
+export default memo(RootRedirect);
diff --git a/interface/src/components/routing/index.ts b/interface/src/components/routing/index.ts
index db622f2c3..31d376c1b 100644
--- a/interface/src/components/routing/index.ts
+++ b/interface/src/components/routing/index.ts
@@ -2,3 +2,4 @@ export { default as RouterTabs } from './RouterTabs';
export { default as RequireAdmin } from './RequireAdmin';
export { default as RequireAuthenticated } from './RequireAuthenticated';
export { default as RequireUnauthenticated } from './RequireUnauthenticated';
+export { default as RootRedirect } from './RootRedirect';
diff --git a/interface/src/contexts/authentication/Authentication.tsx b/interface/src/contexts/authentication/Authentication.tsx
index 351caf50a..823a5c9fc 100644
--- a/interface/src/contexts/authentication/Authentication.tsx
+++ b/interface/src/contexts/authentication/Authentication.tsx
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import type { FC } from 'react';
-import { redirect } from 'react-router';
+import { useNavigate } from 'react-router';
import { callAction } from 'api/app';
import { ACCESS_TOKEN } from 'api/endpoints';
@@ -18,6 +18,7 @@ import { AuthenticationContext } from './context';
const Authentication: FC = ({ children }) => {
const { LL } = useI18nContext();
+ const navigate = useNavigate();
const [initialized, setInitialized] = useState(false);
const [me, setMe] = useState();
@@ -60,7 +61,7 @@ const Authentication: FC = ({ children }) => {
setMe(undefined);
setVersions(undefined);
if (doRedirect) {
- redirect('/');
+ void navigate('/', { replace: true });
}
};
diff --git a/interface/src/index.tsx b/interface/src/index.tsx
index f3420463d..c21620263 100644
--- a/interface/src/index.tsx
+++ b/interface/src/index.tsx
@@ -1,6 +1,8 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import {
+ Navigate,
+ Outlet,
Route,
RouterProvider,
createBrowserRouter,
@@ -9,6 +11,45 @@ import {
} from 'react-router';
import App from 'App';
+import SignIn from 'SignIn';
+import Commands from 'app/main/Commands';
+import CustomEntities from 'app/main/CustomEntities';
+import Customizations from 'app/main/Customizations';
+import Dashboard from 'app/main/Dashboard';
+import Devices from 'app/main/Devices';
+import Help from 'app/main/Help';
+import Modules from 'app/main/Modules';
+import Scheduler from 'app/main/Scheduler';
+import Sensors from 'app/main/Sensors';
+import UserProfile from 'app/main/UserProfile';
+import APSettings from 'app/settings/APSettings';
+import ApplicationSettings from 'app/settings/ApplicationSettings';
+import DownloadUpload from 'app/settings/DownloadUpload';
+import MqttSettings from 'app/settings/MqttSettings';
+import NTPSettings from 'app/settings/NTPSettings';
+import Settings from 'app/settings/Settings';
+import Version from 'app/settings/Version';
+import Network from 'app/settings/network/Network';
+import NetworkSettings from 'app/settings/network/NetworkSettings';
+import WiFiNetworkScanner from 'app/settings/network/WiFiNetworkScanner';
+import ManageUsers from 'app/settings/security/ManageUsers';
+import Security from 'app/settings/security/Security';
+import SecuritySettings from 'app/settings/security/SecuritySettings';
+import APStatus from 'app/status/APStatus';
+import Activity from 'app/status/Activity';
+import HardwareStatus from 'app/status/HardwareStatus';
+import MqttStatus from 'app/status/MqttStatus';
+import NTPStatus from 'app/status/NTPStatus';
+import NetworkStatus from 'app/status/NetworkStatus';
+import Status from 'app/status/Status';
+import SystemLog from 'app/status/SystemLog';
+import {
+ Layout,
+ RequireAdmin,
+ RequireAuthenticated,
+ RequireUnauthenticated,
+ RootRedirect
+} from 'components';
const errorPageStyles = {
container: {
@@ -105,7 +146,87 @@ function ErrorPage() {
const router = createBrowserRouter(
createRoutesFromElements(
- } errorElement={} />
+ } errorElement={}>
+
+
+
+ }
+ />
+ } />
+ } />
+
+
+
+
+
+
+ }
+ >
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ }>
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ }>
+ }
+ />
+ } />
+ } />
+ }
+ />
+
+
+ }>
+ }
+ />
+ } />
+ } />
+ }
+ />
+
+
+ } />
+ } />
+ } />
+ } />
+
+
+ } />
+
+
)
);