mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-07 08:19:52 +03:00
Merge remote-tracking branch 'origin/v3.4' into dev
This commit is contained in:
84
interface/src/contexts/authentication/Authentication.tsx
Normal file
84
interface/src/contexts/authentication/Authentication.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import { FC, useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import * as AuthenticationApi from '../../api/authentication';
|
||||
import { ACCESS_TOKEN } from '../../api/endpoints';
|
||||
import { LoadingSpinner } from '../../components';
|
||||
import { Me } from '../../types';
|
||||
import { FeaturesContext } from '../features';
|
||||
import { AuthenticationContext } from './context';
|
||||
|
||||
const Authentication: FC = ({ children }) => {
|
||||
const { features } = useContext(FeaturesContext);
|
||||
const navigate = useNavigate();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const [initialized, setInitialized] = useState<boolean>(false);
|
||||
const [me, setMe] = useState<Me>();
|
||||
|
||||
const signIn = (accessToken: string) => {
|
||||
try {
|
||||
AuthenticationApi.getStorage().setItem(ACCESS_TOKEN, accessToken);
|
||||
const decodedMe = AuthenticationApi.decodeMeJWT(accessToken);
|
||||
setMe(decodedMe);
|
||||
enqueueSnackbar(`Logged in as ${decodedMe.username}`, { variant: 'success' });
|
||||
} catch (error: any) {
|
||||
setMe(undefined);
|
||||
throw new Error('Failed to parse JWT ' + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
const signOut = (redirect: boolean) => {
|
||||
AuthenticationApi.clearAccessToken();
|
||||
setMe(undefined);
|
||||
if (redirect) {
|
||||
navigate('/');
|
||||
}
|
||||
};
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
if (!features.security) {
|
||||
setMe({ admin: true, username: 'admin' });
|
||||
setInitialized(true);
|
||||
return;
|
||||
}
|
||||
const accessToken = AuthenticationApi.getStorage().getItem(ACCESS_TOKEN);
|
||||
if (accessToken) {
|
||||
try {
|
||||
await AuthenticationApi.verifyAuthorization();
|
||||
setMe(AuthenticationApi.decodeMeJWT(accessToken));
|
||||
setInitialized(true);
|
||||
} catch (error: any) {
|
||||
setMe(undefined);
|
||||
setInitialized(true);
|
||||
}
|
||||
} else {
|
||||
setMe(undefined);
|
||||
setInitialized(true);
|
||||
}
|
||||
}, [features]);
|
||||
|
||||
useEffect(() => {
|
||||
refresh();
|
||||
}, [refresh]);
|
||||
|
||||
if (initialized) {
|
||||
return (
|
||||
<AuthenticationContext.Provider
|
||||
value={{
|
||||
signIn,
|
||||
signOut,
|
||||
me,
|
||||
refresh
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AuthenticationContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
return <LoadingSpinner height="100vh" />;
|
||||
};
|
||||
|
||||
export default Authentication;
|
||||
19
interface/src/contexts/authentication/context.ts
Normal file
19
interface/src/contexts/authentication/context.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { createContext } from 'react';
|
||||
import { Me } from '../../types';
|
||||
|
||||
export interface AuthenticationContextValue {
|
||||
refresh: () => Promise<void>;
|
||||
signIn: (accessToken: string) => void;
|
||||
signOut: (redirect: boolean) => void;
|
||||
me?: Me;
|
||||
}
|
||||
|
||||
const AuthenticationContextDefaultValue = {} as AuthenticationContextValue;
|
||||
export const AuthenticationContext = createContext(AuthenticationContextDefaultValue);
|
||||
|
||||
export interface AuthenticatedContextValue extends AuthenticationContextValue {
|
||||
me: Me;
|
||||
}
|
||||
|
||||
const AuthenticatedContextDefaultValue = {} as AuthenticatedContextValue;
|
||||
export const AuthenticatedContext = createContext(AuthenticatedContextDefaultValue);
|
||||
2
interface/src/contexts/authentication/index.ts
Normal file
2
interface/src/contexts/authentication/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './context';
|
||||
export { default as Authentication } from './Authentication';
|
||||
47
interface/src/contexts/features/FeaturesLoader.tsx
Normal file
47
interface/src/contexts/features/FeaturesLoader.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { FC, useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import * as FeaturesApi from '../../api/features';
|
||||
|
||||
import { extractErrorMessage } from '../../utils';
|
||||
import { Features } from '../../types';
|
||||
import { ApplicationError, LoadingSpinner } from '../../components';
|
||||
|
||||
import { FeaturesContext } from '.';
|
||||
|
||||
const FeaturesLoader: FC = (props) => {
|
||||
const [errorMessage, setErrorMessage] = useState<string>();
|
||||
const [features, setFeatures] = useState<Features>();
|
||||
|
||||
const loadFeatures = useCallback(async () => {
|
||||
try {
|
||||
const response = await FeaturesApi.readFeatures();
|
||||
setFeatures(response.data);
|
||||
} catch (error: any) {
|
||||
setErrorMessage(extractErrorMessage(error, 'Failed to fetch application details.'));
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
loadFeatures();
|
||||
}, [loadFeatures]);
|
||||
|
||||
if (features) {
|
||||
return (
|
||||
<FeaturesContext.Provider
|
||||
value={{
|
||||
features
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</FeaturesContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
if (errorMessage) {
|
||||
return <ApplicationError message={errorMessage} />;
|
||||
}
|
||||
|
||||
return <LoadingSpinner height="100vh" />;
|
||||
};
|
||||
|
||||
export default FeaturesLoader;
|
||||
10
interface/src/contexts/features/context.ts
Normal file
10
interface/src/contexts/features/context.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
import { Features } from '../../types';
|
||||
|
||||
export interface FeaturesContextValue {
|
||||
features: Features;
|
||||
}
|
||||
|
||||
const FeaturesContextDefaultValue = {} as FeaturesContextValue;
|
||||
export const FeaturesContext = createContext(FeaturesContextDefaultValue);
|
||||
2
interface/src/contexts/features/index.ts
Normal file
2
interface/src/contexts/features/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './context';
|
||||
export { default as FeaturesLoader } from './FeaturesLoader';
|
||||
Reference in New Issue
Block a user