User profile page
parent
7cd40357ab
commit
b617d59b69
4
.env
4
.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
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 89 KiB |
|
@ -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 (
|
||||
<Switch>
|
||||
<Route exact path="/dashboard" component={DashboardContainer} />
|
||||
<Route exact path="/user-profile" component={LoginContainer} />
|
||||
<Route exact path="/user-profile" component={UserProfileContainer} />
|
||||
<Route exact path="/machines" component={NetworkContainer} />
|
||||
<Route exact path="/settings" component={SettingsContainer} />
|
||||
<Route component={PageNotFound} />
|
||||
|
|
|
@ -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 }) => {
|
|||
</ListItemIcon>
|
||||
<ListItemText primary={t("Menu.Machines")} />
|
||||
</ListItem>
|
||||
<ListItem button key="system" onClick={() => history.push("/system")}>
|
||||
<ListItemIcon>
|
||||
<DeviceHubIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t("Menu.System")} />
|
||||
</ListItem>
|
||||
</List>
|
||||
<Divider />
|
||||
<List>
|
||||
|
|
|
@ -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 (
|
||||
<div className={classes.loggedInContent}>
|
||||
<Card className={classes.loggedInCard}>
|
||||
<CardHeader
|
||||
avatar={
|
||||
<Avatar aria-label="user" className={classes.avatar}>
|
||||
<AccountBox />
|
||||
</Avatar>
|
||||
}
|
||||
title={<strong>{t("Login.Hello", { username: userName })}</strong>}
|
||||
subheader={
|
||||
<Tooltip title={t("Login.AuthenticationDate")}>
|
||||
<Typography variant="caption" display="block">
|
||||
{loginDate}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
|
||||
<CardActions disableSpacing>
|
||||
<Tooltip title={t("Login.ChangeUser")}>
|
||||
<IconButton
|
||||
size="small"
|
||||
className={classes.onRight}
|
||||
onClick={handleExpandLogin}
|
||||
aria-expanded={expanded}
|
||||
aria-label="show login component"
|
||||
>
|
||||
<RotateLeft />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={t("Login.Logout")}>
|
||||
<IconButton size="small" onClick={onLogout}>
|
||||
<ExitToApp />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</CardActions>
|
||||
<Divider />
|
||||
<Collapse in={expanded} timeout="auto" unmountOnExit>
|
||||
<CardContent
|
||||
className={classes.collapseContent}
|
||||
style={{ paddingBottom: "5px" }}
|
||||
>
|
||||
<LoginComponent
|
||||
credentials={credentials}
|
||||
onChange={onChange}
|
||||
onLogin={handleLogin}
|
||||
/>
|
||||
</CardContent>
|
||||
</Collapse>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
LoggedInComponent.propTypes = {
|
||||
credentials: PropTypes.object.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onLogin: PropTypes.func.isRequired,
|
||||
onLogout: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default LoggedInComponent;
|
|
@ -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 ? (
|
||||
<LoggedInComponent
|
||||
credentials={credentials}
|
||||
onChange={handleChange}
|
||||
onLogin={handleLogin}
|
||||
onLogout={handleLogout}
|
||||
/>
|
||||
) : (
|
||||
<LoginCard
|
||||
credentials={credentials}
|
||||
onChange={handleChange}
|
||||
onLogin={handleLogin}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
<LoginCard
|
||||
credentials={credentials}
|
||||
onChange={handleChange}
|
||||
onLogin={handleLogin}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 (
|
||||
<div className={classes.root}>
|
||||
<MachinesContainer />
|
||||
{/* <NotesContainer /> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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 (
|
||||
<>
|
||||
<TextField
|
||||
id="ertet"
|
||||
label="Test"
|
||||
onChange={handleChange("test")}
|
||||
value={state.network.test}
|
||||
/>
|
||||
<br />
|
||||
<br />
|
||||
<Button variant="contained" color="primary">
|
||||
Read machines
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotesContainer;
|
|
@ -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 (
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} sm={4} lg={2}>
|
||||
<UserProfilePicture userData={userData} />
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={8} lg={10}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<List>
|
||||
<ListItem dense>
|
||||
<ListItemIcon>
|
||||
<BusinessCenterIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Tooltip title={t("User.Profile.OpenPortfolio")}>
|
||||
<Link
|
||||
href="https://lab.code-rove.com/tsp/"
|
||||
target="_blank"
|
||||
>
|
||||
{userName}
|
||||
</Link>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem dense>
|
||||
<ListItemIcon>
|
||||
<EmailIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Tooltip title={t("Generic.SendEmail")}>
|
||||
<Link href="#" onClick={handleEmailSending}>
|
||||
{email}
|
||||
</Link>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
{profilePictureUrl && (
|
||||
<ListItem dense>
|
||||
<ListItemIcon>
|
||||
<Tooltip title={t("Generic.Copy")}>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={handleCopyToClipboard(profilePictureUrl)}
|
||||
>
|
||||
<FileCopyOutlined />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Tooltip title={t("Generic.OpenInNewTab")}>
|
||||
<Link href={profilePictureUrl} target="_blank">
|
||||
{profilePictureUrl}
|
||||
</Link>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
)}
|
||||
</List>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
UserProfileCardContent.propTypes = {
|
||||
userData: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default UserProfileCardContent;
|
|
@ -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 (
|
||||
<>
|
||||
<PageTitle
|
||||
text={t("User.Profile.Hello", { userName: userData.firstName })}
|
||||
/>
|
||||
<Card>
|
||||
<CardHeader title={userData.userName} subheader={userDescription} />
|
||||
<CardContent>
|
||||
<UserProfileCardContent userData={userData} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserProfileContainer;
|
|
@ -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 <Avatar src={url} alt="..." className={classes.profilePicture} />;
|
||||
};
|
||||
|
||||
UserProfilePicture.propTypes = {
|
||||
userData: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default UserProfilePicture;
|
|
@ -0,0 +1,12 @@
|
|||
const style = theme => {
|
||||
return {
|
||||
profilePicture: {
|
||||
margin: "auto",
|
||||
display: "block",
|
||||
width: theme.spacing(25),
|
||||
height: theme.spacing(25)
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default style;
|
|
@ -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 };
|
Loading…
Reference in New Issue