From 2120a4ee029d9362e7ee88111e4ae632a7dad17b Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 13 Aug 2019 08:23:37 +0200 Subject: [PATCH] implemented ui for login information --- scm-ui/public/locales/de/commons.json | 6 +- scm-ui/public/locales/en/commons.json | 7 +- scm-ui/src/components/InfoBox.js | 118 ++++++++++++++++++++++++ scm-ui/src/components/InfoItem.js | 8 ++ scm-ui/src/components/LoginForm.js | 120 ++++++++++++++++++++++++ scm-ui/src/components/LoginInfo.js | 54 +++++++++++ scm-ui/src/containers/Login.js | 127 +++++--------------------- 7 files changed, 332 insertions(+), 108 deletions(-) create mode 100644 scm-ui/src/components/InfoBox.js create mode 100644 scm-ui/src/components/InfoItem.js create mode 100644 scm-ui/src/components/LoginForm.js create mode 100644 scm-ui/src/components/LoginInfo.js diff --git a/scm-ui/public/locales/de/commons.json b/scm-ui/public/locales/de/commons.json index 3964863f46..cdbedd8b6b 100644 --- a/scm-ui/public/locales/de/commons.json +++ b/scm-ui/public/locales/de/commons.json @@ -5,7 +5,11 @@ "logo-alt": "SCM-Manager", "username-placeholder": "Benutzername", "password-placeholder": "Passwort", - "submit": "Anmelden" + "submit": "Anmelden", + "plugin": "Plugin", + "feature": "Feature", + "tip": "Tipp", + "loading": "Lade Daten ..." }, "logout": { "error": { diff --git a/scm-ui/public/locales/en/commons.json b/scm-ui/public/locales/en/commons.json index c57ef700ef..181fdc975c 100644 --- a/scm-ui/public/locales/en/commons.json +++ b/scm-ui/public/locales/en/commons.json @@ -5,7 +5,12 @@ "logo-alt": "SCM-Manager", "username-placeholder": "Your Username", "password-placeholder": "Your Password", - "submit": "Login" + "submit": "Login", + "plugin": "Plugin", + "feature": "Feature", + "tip": "Tip", + "loading": "Loading ...", + "error": "Error" }, "logout": { "error": { diff --git a/scm-ui/src/components/InfoBox.js b/scm-ui/src/components/InfoBox.js new file mode 100644 index 0000000000..95d487a210 --- /dev/null +++ b/scm-ui/src/components/InfoBox.js @@ -0,0 +1,118 @@ +//@flow +import * as React from "react"; +import { ErrorNotification } from "@scm-manager/ui-components"; +import injectSheet from "react-jss"; +import classNames from "classnames"; +import { translate } from "react-i18next"; +import type { InfoItem } from "./InfoItem"; + +const styles = { + image: { + display: "flex", + alignItems: "center", + justifyContent: "center", + flexDirection: "column", + width: 160, + height: 160 + }, + icon: { + color: "#bff1e6" + }, + label: { + marginTop: "0.5em" + }, + content: { + marginLeft: "1.5em" + }, + link: { + width: "60%", + height: "200px", + position: "absolute", + cursor: "pointer", + zIndex: 20 + }, + "@media (max-width: 768px)": { + link: { + width: "100%", + } + } +}; + +type Props = { + type: "plugin" | "feature", + item?: InfoItem, + error?: Error, + + // context props + classes: any, + t: string => string +}; + +class InfoBox extends React.Component { + + renderBody = () => { + const { item, error, t } = this.props; + + const bodyClasses = classNames("media-content", "content", this.props.classes.content); + + if (error) { + return ( +
+

{t("login.error")}

+ +
+ ); + } + + const title = item ? item.title : t("login.loading"); + const summary = item ? item.summary : t("login.loading"); + + + return ( +
+

+ {title} +

+

{summary}

+
+ ); + + }; + + createHref = () => { + const { item } = this.props; + return item ? item._links.self.href : "#"; + }; + + createLink = () => { + const { classes } = this.props; + // eslint-disable-next-line jsx-a11y/anchor-has-content + return ; + }; + + render() { + const { type, classes, t } = this.props; + const icon = type === "plugin" ? "puzzle-piece" : "star"; + return ( + <> + {this.createLink()} +
+
+
+ +
{t("login." + type)}
+
{t("login.tip")}
+
+
+ {this.renderBody()} +
+ + ); + } + +} + +export default injectSheet(styles)(translate("commons")(InfoBox)); + + diff --git a/scm-ui/src/components/InfoItem.js b/scm-ui/src/components/InfoItem.js new file mode 100644 index 0000000000..b947bd3fce --- /dev/null +++ b/scm-ui/src/components/InfoItem.js @@ -0,0 +1,8 @@ +// @flow +import type { Link } from "@scm-manager/ui-types"; + +export type InfoItem = { + title: string, + summary: string, + _links: {[string]: Link} +}; diff --git a/scm-ui/src/components/LoginForm.js b/scm-ui/src/components/LoginForm.js new file mode 100644 index 0000000000..c1ad8c0ea5 --- /dev/null +++ b/scm-ui/src/components/LoginForm.js @@ -0,0 +1,120 @@ +//@flow +import React from "react"; +import { translate } from "react-i18next"; +import { Image, ErrorNotification, InputField, SubmitButton, UnauthorizedError } from "@scm-manager/ui-components"; +import classNames from "classnames"; +import injectSheet from "react-jss"; + +const styles = { + avatar: { + marginTop: "-70px", + paddingBottom: "20px" + }, + avatarImage: { + border: "1px solid lightgray", + padding: "5px", + background: "#fff", + borderRadius: "50%", + width: "128px", + height: "128px" + }, + avatarSpacing: { + marginTop: "5rem" + } +}; + +type Props = { + error?: Error, + loading: boolean, + login: (username: string, password: string) => void, + + // context props + t: string => string, + classes: any +}; + +type State = { + username: string, + password: string +}; + +class LoginForm extends React.Component { + + constructor(props: Props) { + super(props); + this.state = { username: "", password: "" }; + } + + handleSubmit = (event: Event) => { + event.preventDefault(); + if (this.isValid()) { + this.props.login( + this.state.username, + this.state.password + ); + } + }; + + handleUsernameChange = (value: string) => { + this.setState({ username: value }); + }; + + handlePasswordChange = (value: string) => { + this.setState({ password: value }); + }; + + isValid() { + return this.state.username && this.state.password; + } + + areCredentialsInvalid() { + const { t, error } = this.props; + if (error instanceof UnauthorizedError) { + return new Error(t("errorNotification.wrongLoginCredentials")); + } else { + return error; + } + } + + render() { + const { loading, classes, t } = this.props; + return ( +
+

{t("login.title")}

+

{t("login.subtitle")}

+
+
+ {t("login.logo-alt")} +
+ +
+ + + + +
+
+ ); + } + +} + +export default injectSheet(styles)(translate("commons")(LoginForm)); + + diff --git a/scm-ui/src/components/LoginInfo.js b/scm-ui/src/components/LoginInfo.js new file mode 100644 index 0000000000..3bd80eaddc --- /dev/null +++ b/scm-ui/src/components/LoginInfo.js @@ -0,0 +1,54 @@ +//@flow +import React from "react"; +import InfoBox from "./InfoBox"; +import type { InfoItem } from "./InfoItem"; + +type Props = { +}; + +type State = { + plugin?: InfoItem, + feature?: InfoItem, + error?: Error +}; + +class LoginInfo extends React.Component { + + constructor(props: Props) { + super(props); + this.state = { + }; + } + + componentDidMount() { + fetch("https://login-info.scm-manager.org/api/v1/login-info") + .then(response => response.json()) + .then(info => { + this.setState({ + plugin: info.plugin, + feature: info.feature, + error: undefined + }); + }) + .catch(error => { + this.setState({ + error + }); + }); + } + + render() { + const { plugin, feature, error } = this.state; + return ( +
+ + +
+ ); + } + +} + +export default LoginInfo; + + diff --git a/scm-ui/src/containers/Login.js b/scm-ui/src/containers/Login.js index d14d9f5896..5d63698168 100644 --- a/scm-ui/src/containers/Login.js +++ b/scm-ui/src/containers/Login.js @@ -1,8 +1,6 @@ //@flow import React from "react"; import { Redirect, withRouter } from "react-router-dom"; -import injectSheet from "react-jss"; -import { translate } from "react-i18next"; import { login, isAuthenticated, @@ -10,148 +8,65 @@ import { getLoginFailure } from "../modules/auth"; import { connect } from "react-redux"; - -import { - InputField, - SubmitButton, - ErrorNotification, - Image, UnauthorizedError -} from "@scm-manager/ui-components"; -import classNames from "classnames"; import { getLoginLink } from "../modules/indexResource"; +import LoginForm from "../components/LoginForm"; +import LoginInfo from "../components/LoginInfo"; +import classNames from "classnames"; +import injectSheet from "react-jss"; const styles = { - avatar: { - marginTop: "-70px", - paddingBottom: "20px" - }, - avatarImage: { - border: "1px solid lightgray", - padding: "5px", - background: "#fff", - borderRadius: "50%", - width: "128px", - height: "128px" - }, - avatarSpacing: { - marginTop: "5rem" + section: { + paddingTop: "2em" } }; type Props = { authenticated: boolean, loading: boolean, - error: Error, + error?: Error, link: string, // dispatcher props login: (link: string, username: string, password: string) => void, // context props - t: string => string, classes: any, + t: string => string, from: any, location: any }; -type State = { - username: string, - password: string -}; +class Login extends React.Component { -class Login extends React.Component { - constructor(props: Props) { - super(props); - this.state = { username: "", password: "" }; - } - - handleUsernameChange = (value: string) => { - this.setState({ username: value }); + login = (username: string, password: string) => { + const { link, login } = this.props; + login(link, username, password); }; - handlePasswordChange = (value: string) => { - this.setState({ password: value }); - }; - - handleSubmit = (event: Event) => { - event.preventDefault(); - if (this.isValid()) { - this.props.login( - this.props.link, - this.state.username, - this.state.password - ); - } - }; - - isValid() { - return this.state.username && this.state.password; - } - - isInValid() { - return !this.isValid(); - } - - areCredentialsInvalid() { - const { t, error } = this.props; - if (error instanceof UnauthorizedError) { - return new Error(t("errorNotification.wrongLoginCredentials")); - } else { - return error; - } - } - renderRedirect = () => { const { from } = this.props.location.state || { from: { pathname: "/" } }; - return ; + return ; }; render() { - const { authenticated, loading, t, classes } = this.props; + const { authenticated, loading, error, classes } = this.props; if (authenticated) { return this.renderRedirect(); } return ( -
+
-
-
-

{t("login.title")}

-

{t("login.subtitle")}

-
-
- {t("login.logo-alt")} -
- -
- - - - -
+
+
+ +
- ); + ); } } @@ -179,6 +94,6 @@ const StyledLogin = injectSheet(styles)( connect( mapStateToProps, mapDispatchToProps - )(translate("commons")(Login)) + )(Login) ); export default withRouter(StyledLogin);