@flare/tuitio-react-client

master
Tudor Stanciu 2023-02-14 01:36:06 +02:00
parent e1c4b2ca04
commit 7f6056baf4
14 changed files with 409 additions and 506 deletions

8
.env
View File

@ -1,8 +1,8 @@
#REACT_APP_IDENTITY_AUTHENTICATION_URL=http://localhost:5063/identity/authenticate?UserName={username}&Password={password} #REACT_APP_TUITIO_URL=http://localhost:5063/identity/authenticate?UserName={username}&Password={password}
REACT_APP_IDENTITY_AUTHENTICATION_URL=https://lab.code-rove.com/tuitio/identity/authenticate?UserName={username}&Password={password} REACT_APP_TUITIO_URL=https://lab.code-rove.com/tuitio
REACT_APP_NETWORK_RESURRECTOR_API_URL=http://localhost:5064 #REACT_APP_NETWORK_RESURRECTOR_API_URL=http://localhost:5064
#REACT_APP_NETWORK_RESURRECTOR_API_URL=https://lab.code-rove.com/network-resurrector-api REACT_APP_NETWORK_RESURRECTOR_API_URL=https://lab.code-rove.com/network-resurrector-api
#600000 milliseconds = 10 minutes #600000 milliseconds = 10 minutes
REACT_APP_MACHINE_PING_INTERVAL=600000 REACT_APP_MACHINE_PING_INTERVAL=600000

View File

@ -1,5 +1,5 @@
PUBLIC_URL=/network-resurrector/ PUBLIC_URL=/network-resurrector/
REACT_APP_IDENTITY_AUTHENTICATION_URL=https://lab.code-rove.com/tuitio/identity/authenticate?UserName={username}&Password={password} REACT_APP_TUITIO_URL=https://lab.code-rove.com/tuitio
REACT_APP_NETWORK_RESURRECTOR_API_URL=https://lab.code-rove.com/network-resurrector-api REACT_APP_NETWORK_RESURRECTOR_API_URL=https://lab.code-rove.com/network-resurrector-api
#900000 milliseconds = 15 minutes #900000 milliseconds = 15 minutes

692
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,7 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@flare/js-utils": "^1.0.2", "@flare/tuitio-react-client": "^1.0.0",
"@material-ui/core": "^4.11.2", "@material-ui/core": "^4.11.2",
"@material-ui/icons": "^4.11.2", "@material-ui/icons": "^4.11.2",
"@testing-library/jest-dom": "^5.11.6", "@testing-library/jest-dom": "^5.11.6",

View File

@ -2,7 +2,7 @@ import React from "react";
import ApplicationStepper from "./stepper/ApplicationStepper"; import ApplicationStepper from "./stepper/ApplicationStepper";
import Switcher from "./Switcher"; import Switcher from "./Switcher";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import { useAuthorizationToken } from "../../hooks"; import { useTuitioToken } from "@flare/tuitio-react-client";
import LoginContainer from "../../features/login/components/LoginContainer"; import LoginContainer from "../../features/login/components/LoginContainer";
const useStyles = makeStyles(() => ({ const useStyles = makeStyles(() => ({
@ -16,7 +16,7 @@ const useStyles = makeStyles(() => ({
const Main = () => { const Main = () => {
const classes = useStyles(); const classes = useStyles();
const { validateToken } = useAuthorizationToken(); const { validate: validateToken } = useTuitioToken();
const tokenIsValid = validateToken(); const tokenIsValid = validateToken();
return ( return (

View File

@ -1,4 +1,4 @@
import React, { useState, useCallback } from "react"; import React, { useState, useMemo } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import { import {
@ -18,35 +18,29 @@ import LoginComponent from "./LoginComponent";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useToast } from "../../../hooks"; import { useToast } from "../../../hooks";
import styles from "../styles"; import styles from "../styles";
import { useTuitioUser } from "@flare/tuitio-react-client";
const useStyles = makeStyles(styles); const useStyles = makeStyles(styles);
const LoggedInComponent = ({ const LoggedInComponent = ({ credentials, onChange, onLogin, onLogout }) => {
credentials,
token,
onChange,
onLogin,
onLogout
}) => {
const classes = useStyles(); const classes = useStyles();
const { t } = useTranslation(); const { t } = useTranslation();
const [expanded, setExpanded] = useState(false); const [expanded, setExpanded] = useState(false);
const { info } = useToast(); const { info } = useToast();
const { lastLoginDate, userName } = useTuitioUser();
const handleExpandLogin = () => { const handleExpandLogin = () => {
setExpanded(!expanded); setExpanded(!expanded);
}; };
const getTokenValidFrom = useCallback(() => { const loginDate = useMemo(() => {
const tokenValidFrom = token?.validFrom; if (lastLoginDate) {
const valueForDisplay = t("LONG_DATE", { date: lastLoginDate });
if (tokenValidFrom) {
const valueForDisplay = t("LONG_DATE", { date: tokenValidFrom });
return valueForDisplay; return valueForDisplay;
} }
return "N/A"; return "N/A";
}, [token, t]); }, [lastLoginDate, t]);
const handleLogin = async () => { const handleLogin = async () => {
const result = await onLogin(); const result = await onLogin();
@ -65,15 +59,11 @@ const LoggedInComponent = ({
<AccountBox /> <AccountBox />
</Avatar> </Avatar>
} }
title={ title={<strong>{t("Login.Hello", { username: userName })}</strong>}
<strong>
{t("Login.Hello", { username: credentials.userName })}
</strong>
}
subheader={ subheader={
<Tooltip title={t("Login.AuthenticationDate")}> <Tooltip title={t("Login.AuthenticationDate")}>
<Typography variant="caption" display="block"> <Typography variant="caption" display="block">
{getTokenValidFrom()} {loginDate}
</Typography> </Typography>
</Tooltip> </Tooltip>
} }
@ -117,7 +107,6 @@ const LoggedInComponent = ({
LoggedInComponent.propTypes = { LoggedInComponent.propTypes = {
credentials: PropTypes.object.isRequired, credentials: PropTypes.object.isRequired,
token: PropTypes.object,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
onLogin: PropTypes.func.isRequired, onLogin: PropTypes.func.isRequired,
onLogout: PropTypes.func.isRequired onLogout: PropTypes.func.isRequired

View File

@ -1,59 +1,49 @@
import React, { useContext } from "react"; import React, { useState } from "react";
import LoginCard from "./LoginCard"; import LoginCard from "./LoginCard";
import { authenticate, invalidate } from "../../../utils/identity"; import { useToast } from "../../../hooks";
import {
ApplicationStateContext,
ApplicationDispatchContext
} from "../../../state/ApplicationContexts";
import { useToast, useAuthorizationToken } from "../../../hooks";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import LoggedInComponent from "./LoggedInComponent"; import LoggedInComponent from "./LoggedInComponent";
import { useTuitioClient, useTuitioToken } from "@flare/tuitio-react-client";
const LoginContainer = () => { const LoginContainer = () => {
const state = useContext(ApplicationStateContext); const [credentials, setCredentials] = useState({
const dispatchActions = useContext(ApplicationDispatchContext); userName: "",
password: ""
});
const { error } = useToast(); const { error } = useToast();
const { t } = useTranslation(); const { t } = useTranslation();
const { tokenIsValid, invalidateToken, getToken } = useAuthorizationToken(); const { login, logout } = useTuitioClient({
onLoginFailed: response => error(t("Login.IncorrectCredentials")),
onLoginError: err => error(err.message)
});
const { valid: tokenIsValid } = useTuitioToken();
const handleChange = prop => event => { const handleChange = prop => event => {
dispatchActions.onCredentialsChange(prop, event.target.value); setCredentials(prev => ({ ...prev, [prop]: event.target.value }));
}; };
const handleLogin = async () => { const handleLogin = () => {
const { userName, password } = state.credentials; const { userName, password } = credentials;
return login(userName, password);
try {
const response = await authenticate(userName, password);
if (response.status === "SUCCESS") {
dispatchActions.onAuthorizationTokenChange(response.token);
return response.token;
} else if (response.status === "BAD_CREDENTIALS") {
error(t("Login.IncorrectCredentials"));
}
} catch (err) {
error(err.message);
}
}; };
const handleLogout = () => { const handleLogout = () => {
invalidate(); logout();
invalidateToken();
}; };
return ( return (
<> <>
{tokenIsValid ? ( {tokenIsValid ? (
<LoggedInComponent <LoggedInComponent
credentials={state.credentials} credentials={credentials}
token={getToken()}
onChange={handleChange} onChange={handleChange}
onLogin={handleLogin} onLogin={handleLogin}
onLogout={handleLogout} onLogout={handleLogout}
/> />
) : ( ) : (
<LoginCard <LoginCard
credentials={state.credentials} credentials={credentials}
onChange={handleChange} onChange={handleChange}
onLogin={handleLogin} onLogin={handleLogin}
/> />

View File

@ -1,2 +1 @@
export { useToast } from "./useToast"; export { useToast } from "./useToast";
export { useAuthorizationToken } from "./useAuthorizationToken";

View File

@ -1,29 +0,0 @@
import { useContext } from "react";
import {
ApplicationStateContext,
ApplicationDispatchContext
} from "../state/ApplicationContexts";
export const useAuthorizationToken = () => {
const state = useContext(ApplicationStateContext);
const dispatchActions = useContext(ApplicationDispatchContext);
const getToken = () => state.security.authorization.token;
const validateToken = () => {
const token = getToken();
if (!token) {
return false;
}
const valid = new Date(token.validUntil) >= new Date();
return valid;
};
const tokenIsValid = validateToken();
const invalidateToken = () => {
dispatchActions.onAuthorizationTokenChange(null);
};
return { getToken, validateToken, tokenIsValid, invalidateToken };
};

View File

@ -4,11 +4,14 @@ import "./index.css";
import "./utils/i18n"; import "./utils/i18n";
import App from "./components/App"; import App from "./components/App";
import { BrowserRouter as Router } from "react-router-dom"; import { BrowserRouter as Router } from "react-router-dom";
import { TuitioProvider } from "@flare/tuitio-react-client";
ReactDOM.render( ReactDOM.render(
<Router basename={process.env.PUBLIC_URL || ""}> <Router basename={process.env.PUBLIC_URL || ""}>
<Suspense fallback={<div>Loading...</div>}> <Suspense fallback={<div>Loading...</div>}>
<TuitioProvider tuitioUrl={process.env.REACT_APP_TUITIO_URL}>
<App /> <App />
</TuitioProvider>
</Suspense> </Suspense>
</Router>, </Router>,
document.getElementById("root") document.getElementById("root")

View File

@ -1,20 +1,4 @@
import { localStorage } from "@flare/js-utils";
import { storageKeys } from "../utils/identity";
const { getItem } = localStorage;
const token = getItem(storageKeys.TOKEN);
const userName = getItem(storageKeys.USER);
export const initialState = { export const initialState = {
credentials: {
userName: userName || "",
password: ""
},
security: {
authorization: {
token
}
},
network: { network: {
machines: Object.assign([], { loaded: false }), machines: Object.assign([], { loaded: false }),
test: "" test: ""

View File

@ -1,15 +1,5 @@
export function reducer(state, action) { export function reducer(state, action) {
switch (action.type) { switch (action.type) {
case "onCredentialsChange": {
const { prop, value } = action.payload;
return {
...state,
credentials: {
...state.credentials,
[prop]: value
}
};
}
case "onNetworkChange": { case "onNetworkChange": {
const { prop, value } = action.payload; const { prop, value } = action.payload;
return { return {
@ -20,19 +10,6 @@ export function reducer(state, action) {
} }
}; };
} }
case "onAuthorizationTokenChange": {
const { token } = action.payload;
return {
...state,
security: {
...state.security,
authorization: {
...state.security.authorization,
token
}
}
};
}
default: { default: {
return state; return state;
} }
@ -40,10 +17,6 @@ export function reducer(state, action) {
} }
export const dispatchActions = dispatch => ({ export const dispatchActions = dispatch => ({
onCredentialsChange: (prop, value) =>
dispatch({ type: "onCredentialsChange", payload: { prop, value } }),
onNetworkChange: (prop, value) => onNetworkChange: (prop, value) =>
dispatch({ type: "onNetworkChange", payload: { prop, value } }), dispatch({ type: "onNetworkChange", payload: { prop, value } })
onAuthorizationTokenChange: token =>
dispatch({ type: "onAuthorizationTokenChange", payload: { token } })
}); });

View File

@ -1,12 +1,9 @@
import axios from "axios"; import axios from "axios";
import i18next from "i18next"; import i18next from "i18next";
import { localStorage } from "@flare/js-utils"; import { fetch as fetchTuitioData } from "@flare/tuitio-client";
import { storageKeys } from "./identity";
const { getItem } = localStorage;
function getHeaders() { function getHeaders() {
const token = getItem(storageKeys.TOKEN); const { token } = fetchTuitioData();
const language = i18next.language; const language = i18next.language;
return { return {

View File

@ -1,37 +0,0 @@
import { request } from "./axios";
import { localStorage } from "@flare/js-utils";
const { setItem, getItem, removeItem } = 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);
}
};
export { storageKeys, authenticate, invalidate };