diff --git a/.env b/.env index 9cd8b93..c6fe26f 100644 --- a/.env +++ b/.env @@ -1,6 +1,6 @@ -#REACT_APP_TUITIO_URL=http://localhost:5063/identity/authenticate?UserName={username}&Password={password} - +#REACT_APP_TUITIO_URL=http://localhost:5063 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=https://lab.code-rove.com/network-resurrector-api diff --git a/public/locales/en/translations.json b/public/locales/en/translations.json index 0822ee5..fad2b45 100644 --- a/public/locales/en/translations.json +++ b/public/locales/en/translations.json @@ -12,9 +12,16 @@ "English": "English", "Romanian": "Romanian" }, + "Generic": { + "Copy": "Copy", + "OpenInNewTab": "Open in new tab", + "CopiedToClipboard": "Copied to clipboard", + "SendEmail": "Send email" + }, "Menu": { "Dashboard": "Dashboard", "Machines": "Machines", + "System": "System", "Trash": "Trash", "Settings": "Settings" }, @@ -22,12 +29,15 @@ "Username": "Username", "Password": "Password", "Label": "Login", - "ChangeUser": "Change user", - "UserChanged": "User changed", "Logout": "Logout", - "IncorrectCredentials": "Incorrect credentials.", - "Hello": "Hi, {{username}}", - "AuthenticationDate": "Authentication date" + "IncorrectCredentials": "Incorrect credentials." + }, + "User": { + "Profile": { + "Hello": "Hi, {{userName}}", + "Description": "{{userName}}, authenticated on {{loginDate}}", + "OpenPortfolio": "Open portfolio" + } }, "Machine": { "FullName": "Full machine name", diff --git a/public/locales/ro/translations.json b/public/locales/ro/translations.json index 51c3bd0..e13005e 100644 --- a/public/locales/ro/translations.json +++ b/public/locales/ro/translations.json @@ -3,9 +3,16 @@ "English": "Engleză", "Romanian": "Română" }, + "Generic": { + "Copy": "Copiază", + "OpenInNewTab": "Deschide într-un tab nou", + "CopiedToClipboard": "Copiat în clipboard", + "SendEmail": "Trimite email" + }, "Menu": { "Dashboard": "Bord", "Machines": "Mașini", + "System": "Sistem", "Trash": "Gunoi", "Settings": "Setări" }, @@ -13,12 +20,15 @@ "Username": "Utilizator", "Password": "Parolă", "Label": "Autentificare", - "ChangeUser": "Schimbă utilizatorul", - "UserChanged": "Utilizator schimbat", "Logout": "Deconectare", - "IncorrectCredentials": "Credențiale incorecte.", - "Hello": "Salut, {{username}}", - "AuthenticationDate": "Momentul autentificării" + "IncorrectCredentials": "Credențiale incorecte." + }, + "User": { + "Profile": { + "Hello": "Salut, {{userName}}", + "Description": "{{userName}}, autentificat pe {{loginDate}}", + "OpenPortfolio": "Deschide portofoliu" + } }, "Machine": { "FullName": "Nume intreg masina", diff --git a/src/assets/images/DefaultUserProfilePicture.png b/src/assets/images/DefaultUserProfilePicture.png new file mode 100644 index 0000000..297be82 Binary files /dev/null and b/src/assets/images/DefaultUserProfilePicture.png differ diff --git a/src/components/layout/AppRoutes.js b/src/components/layout/AppRoutes.js index 69e207f..c841ae2 100644 --- a/src/components/layout/AppRoutes.js +++ b/src/components/layout/AppRoutes.js @@ -1,16 +1,16 @@ import React from "react"; import { Route, Switch } from "react-router-dom"; import PageNotFound from "./PageNotFound"; -import LoginContainer from "../../features/login/components/LoginContainer"; import NetworkContainer from "../../features/network/components/NetworkContainer"; import SettingsContainer from "../../features/settings/components/SettingsContainer"; import DashboardContainer from "../../features/dashboard/components/DashboardContainer"; +import UserProfileContainer from "../../features/user/profile/components/UserProfileContainer"; const AppRoutes = () => { return ( - + diff --git a/src/components/layout/Sidebar.js b/src/components/layout/Sidebar.js index 4c2aea1..a79af4e 100644 --- a/src/components/layout/Sidebar.js +++ b/src/components/layout/Sidebar.js @@ -15,6 +15,7 @@ import ChevronRightIcon from "@material-ui/icons/ChevronRight"; import ListItem from "@material-ui/core/ListItem"; import DeleteIcon from "@material-ui/icons/Delete"; import DnsIcon from "@material-ui/icons/Dns"; +import DeviceHubIcon from "@material-ui/icons/DeviceHub"; import SettingsIcon from "@material-ui/icons/Settings"; import DashboardIcon from "@material-ui/icons/Dashboard"; import { useHistory } from "react-router-dom"; @@ -74,6 +75,12 @@ const Sidebar = ({ open, handleDrawerClose }) => { + history.push("/system")}> + + + + + diff --git a/src/features/login/components/LoggedInComponent.js b/src/features/login/components/LoggedInComponent.js deleted file mode 100644 index 9b90404..0000000 --- a/src/features/login/components/LoggedInComponent.js +++ /dev/null @@ -1,111 +0,0 @@ -import React, { useState, useMemo } from "react"; -import PropTypes from "prop-types"; -import { makeStyles } from "@material-ui/core/styles"; -import { - Avatar, - Collapse, - Tooltip, - Divider, - Typography, - Card, - CardContent, - CardActions, - CardHeader, - IconButton -} from "@material-ui/core"; -import { AccountBox, RotateLeft, ExitToApp } from "@material-ui/icons"; -import LoginComponent from "./LoginComponent"; -import { useTranslation } from "react-i18next"; -import { useToast } from "../../../hooks"; -import styles from "../styles"; -import { useTuitioUser } from "@flare/tuitio-client-react"; - -const useStyles = makeStyles(styles); - -const LoggedInComponent = ({ credentials, onChange, onLogin, onLogout }) => { - const classes = useStyles(); - const { t } = useTranslation(); - const [expanded, setExpanded] = useState(false); - const { info } = useToast(); - const { userName } = useTuitioUser(); - - const handleExpandLogin = () => { - setExpanded(!expanded); - }; - - const loginDate = useMemo(() => { - const valueForDisplay = t("LONG_DATE", { date: new Date() }); - return valueForDisplay; - }, [t]); - - const handleLogin = async () => { - const result = await onLogin(); - if (result) { - setExpanded(false); - info(t("Login.UserChanged")); - } - }; - - return ( -
- - - - - } - title={{t("Login.Hello", { username: userName })}} - subheader={ - - - {loginDate} - - - } - /> - - - - - - - - - - - - - - - - - - - - -
- ); -}; - -LoggedInComponent.propTypes = { - credentials: PropTypes.object.isRequired, - onChange: PropTypes.func.isRequired, - onLogin: PropTypes.func.isRequired, - onLogout: PropTypes.func.isRequired -}; - -export default LoggedInComponent; diff --git a/src/features/login/components/LoginContainer.js b/src/features/login/components/LoginContainer.js index d14e5c7..8821de3 100644 --- a/src/features/login/components/LoginContainer.js +++ b/src/features/login/components/LoginContainer.js @@ -2,8 +2,7 @@ import React, { useState } from "react"; import LoginCard from "./LoginCard"; import { useToast } from "../../../hooks"; import { useTranslation } from "react-i18next"; -import LoggedInComponent from "./LoggedInComponent"; -import { useTuitioClient, useTuitioToken } from "@flare/tuitio-client-react"; +import { useTuitioClient } from "@flare/tuitio-client-react"; const LoginContainer = () => { const [credentials, setCredentials] = useState({ @@ -13,11 +12,10 @@ const LoginContainer = () => { const { error } = useToast(); const { t } = useTranslation(); - const { login, logout } = useTuitioClient({ + const { login } = useTuitioClient({ onLoginFailed: () => error(t("Login.IncorrectCredentials")), onLoginError: err => error(err.message) }); - const { valid: tokenIsValid } = useTuitioToken(); const handleChange = prop => event => { setCredentials(prev => ({ ...prev, [prop]: event.target.value })); @@ -28,27 +26,12 @@ const LoginContainer = () => { return login(userName, password); }; - const handleLogout = () => { - logout(); - }; - return ( - <> - {tokenIsValid ? ( - - ) : ( - - )} - + ); }; diff --git a/src/features/login/styles.js b/src/features/login/styles.js index 7b5308d..b295f9e 100644 --- a/src/features/login/styles.js +++ b/src/features/login/styles.js @@ -1,23 +1,7 @@ const styles = theme => ({ - loggedInCard: { - minWidth: 350 - }, onRight: { marginLeft: "auto" }, - avatar: { - backgroundColor: theme.palette.primary.main - }, - collapseContent: { - padding: 0 - }, - loggedInContent: { - minHeight: "80vh", - display: "flex", - alignItems: "center", - justifyContent: "center", - fontSize: "calc(10px + 2vmin)" - }, appLogin: { minHeight: "100vh", display: "flex", diff --git a/src/features/network/components/NetworkContainer.js b/src/features/network/components/NetworkContainer.js index 925925f..9866e45 100644 --- a/src/features/network/components/NetworkContainer.js +++ b/src/features/network/components/NetworkContainer.js @@ -1,6 +1,5 @@ import React from "react"; import MachinesContainer from "../../machines/components/MachinesContainer"; -//import NotesContainer from "../../notes/components/NotesContainer"; import { makeStyles } from "@material-ui/core/styles"; import styles from "../styles"; @@ -12,7 +11,6 @@ const NetworkContainer = () => { return (
- {/* */}
); }; diff --git a/src/features/notes/components/NotesContainer.js b/src/features/notes/components/NotesContainer.js deleted file mode 100644 index 6efc323..0000000 --- a/src/features/notes/components/NotesContainer.js +++ /dev/null @@ -1,33 +0,0 @@ -import React, { useContext } from "react"; -import { TextField, Button } from "@material-ui/core"; -import { - ApplicationStateContext, - ApplicationDispatchContext -} from "../../../state/ApplicationContexts"; - -const NotesContainer = () => { - const state = useContext(ApplicationStateContext); - const dispatchActions = useContext(ApplicationDispatchContext); - - const handleChange = prop => event => { - dispatchActions.onNetworkChange(prop, event.target.value); - }; - - return ( - <> - -
-
- - - ); -}; - -export default NotesContainer; diff --git a/src/features/user/profile/components/UserProfileCardContent.js b/src/features/user/profile/components/UserProfileCardContent.js new file mode 100644 index 0000000..75e5142 --- /dev/null +++ b/src/features/user/profile/components/UserProfileCardContent.js @@ -0,0 +1,112 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { + Grid, + List, + ListItem, + ListItemText, + ListItemIcon, + Link, + IconButton, + Tooltip +} from "@material-ui/core"; +import UserProfilePicture from "./UserProfilePicture"; +import BusinessCenterIcon from "@material-ui/icons/BusinessCenter"; +import { FileCopyOutlined } from "@material-ui/icons"; +import EmailIcon from "@material-ui/icons/Email"; +import { useToast } from "../../../../hooks"; +import { useTranslation } from "react-i18next"; + +const UserProfileCardContent = ({ userData }) => { + const { email, profilePictureUrl } = userData; + const { t } = useTranslation(); + const { info } = useToast(); + + const handleCopyToClipboard = url => () => { + navigator.clipboard.writeText(url); + info(t("Generic.CopiedToClipboard")); + }; + + const handleEmailSending = event => { + window.location.href = `mailto:${email}`; + event.preventDefault(); + }; + + const userName = `${userData.firstName} ${userData.lastName}`; + + return ( + + + + + + + + + + + + + + + {userName} + + + } + /> + + + + + + + + {email} + + + } + /> + + {profilePictureUrl && ( + + + + + + + + + + + {profilePictureUrl} + + + } + /> + + )} + + + + + + ); +}; + +UserProfileCardContent.propTypes = { + userData: PropTypes.object.isRequired +}; + +export default UserProfileCardContent; diff --git a/src/features/user/profile/components/UserProfileContainer.js b/src/features/user/profile/components/UserProfileContainer.js new file mode 100644 index 0000000..7ae1fee --- /dev/null +++ b/src/features/user/profile/components/UserProfileContainer.js @@ -0,0 +1,47 @@ +import React, { useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { Card, CardHeader, CardContent } from "@material-ui/core"; +import { useTuitioToken } from "@flare/tuitio-client-react"; +import { camelizeKeys } from "../../../../utils/camelizeKeys"; +import PageTitle from "../../../../components/common/PageTitle"; +import UserProfileCardContent from "./UserProfileCardContent"; + +const UserProfileContainer = () => { + const { t } = useTranslation(); + const { token } = useTuitioToken(); + + const decodedToken = useMemo(() => atob(token), [token]); + const userData = useMemo( + () => camelizeKeys(JSON.parse(decodedToken)), + [decodedToken] + ); + console.log("userData", userData); + const userLoginDate = useMemo( + () => + t("DATE_FORMAT", { + date: { value: userData.createdAt, format: "DD-MM-YYYY HH:mm:ss" } + }), + [t, userData.createdAt] + ); + + const userDescription = t("User.Profile.Description", { + userName: `${userData.firstName} ${userData.lastName}`, + loginDate: userLoginDate + }); + + return ( + <> + + + + + + + + + ); +}; + +export default UserProfileContainer; diff --git a/src/features/user/profile/components/UserProfilePicture.js b/src/features/user/profile/components/UserProfilePicture.js new file mode 100644 index 0000000..6ca0002 --- /dev/null +++ b/src/features/user/profile/components/UserProfilePicture.js @@ -0,0 +1,21 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { makeStyles } from "@material-ui/core/styles"; +import style from "../styles"; +import Avatar from "@material-ui/core/Avatar"; +import DefaultUserProfilePicture from "../../../../assets/images/DefaultUserProfilePicture.png"; + +const useStyles = makeStyles(style); + +const UserProfilePicture = ({ userData }) => { + const classes = useStyles(); + const { profilePictureUrl } = userData; + const url = profilePictureUrl ?? DefaultUserProfilePicture; + return ; +}; + +UserProfilePicture.propTypes = { + userData: PropTypes.object.isRequired +}; + +export default UserProfilePicture; diff --git a/src/features/user/profile/styles.js b/src/features/user/profile/styles.js new file mode 100644 index 0000000..e140522 --- /dev/null +++ b/src/features/user/profile/styles.js @@ -0,0 +1,12 @@ +const style = theme => { + return { + profilePicture: { + margin: "auto", + display: "block", + width: theme.spacing(25), + height: theme.spacing(25) + } + }; +}; + +export default style; diff --git a/src/utils/camelizeKeys.js b/src/utils/camelizeKeys.js new file mode 100644 index 0000000..5691b1f --- /dev/null +++ b/src/utils/camelizeKeys.js @@ -0,0 +1,31 @@ +function camelizeKeys(o) { + var newO, origKey, newKey, value; + if (o instanceof Array) { + return o.map(function (value) { + if (typeof value === "object") { + value = camelizeKeys(value); + } + return value; + }); + } else { + newO = {}; + for (origKey in o) { + if (Object.prototype.hasOwnProperty.call(o, origKey)) { + newKey = ( + origKey.charAt(0).toLowerCase() + origKey.slice(1) || origKey + ).toString(); + value = o[origKey]; + if ( + value instanceof Array || + (value !== null && value.constructor === Object) + ) { + value = camelizeKeys(value); + } + newO[newKey] = value; + } + } + } + return newO; +} + +export { camelizeKeys };