diff --git a/gui/__mocks__/fileMock.js b/gui/__mocks__/fileMock.js
new file mode 100644
index 00000000000..0e56c5b5f76
--- /dev/null
+++ b/gui/__mocks__/fileMock.js
@@ -0,0 +1 @@
+module.exports = 'test-file-stub'
diff --git a/gui/__tests__/containers/SignIn.test.js b/gui/__tests__/containers/SignIn.test.js
index 13ef34c020f..8fe696dc21d 100644
--- a/gui/__tests__/containers/SignIn.test.js
+++ b/gui/__tests__/containers/SignIn.test.js
@@ -65,12 +65,6 @@ describe('containers/SignIn', () => {
currentUrl: '/signin'
})
expect(newState.authentication.allowed).toEqual(false)
- expect(newState.authentication.errors).toEqual([])
- })
-
- it('cannot submit an empty form', async () => {
- const store = createStore()
- const wrapper = mountSignIn(store)
- expect(wrapper.find('form button').getDOMNode().disabled).toEqual(true)
+ expect(wrapper.text()).toContain('Your email or password is incorrect. Please try again')
})
})
diff --git a/gui/src/App.js b/gui/src/App.js
index bdb43811df8..2abbc873980 100644
--- a/gui/src/App.js
+++ b/gui/src/App.js
@@ -1,8 +1,9 @@
import React, { PureComponent } from 'react'
-import Layout from 'Layout'
-import createStore from 'connectors/redux'
import { Provider } from 'react-redux'
import { hot } from 'react-hot-loader'
+import createStore from 'connectors/redux'
+import Layout from 'Layout'
+import './index.css'
class App extends PureComponent {
// Remove the server-side injected CSS.
diff --git a/gui/src/Layout.js b/gui/src/Layout.js
index e44c43d2902..18a3d62d301 100644
--- a/gui/src/Layout.js
+++ b/gui/src/Layout.js
@@ -1,115 +1,49 @@
-import React from 'react'
-import Routes from 'react-static-routes'
+import React, { PureComponent } from 'react'
import CssBaseline from '@material-ui/core/CssBaseline'
-import Grid from '@material-ui/core/Grid'
-import PrivateRoute from './PrivateRoute'
-import Header from 'containers/Header'
-import Loading from 'components/Loading'
-import Notifications from 'containers/Notifications'
-import universal from 'react-universal-component'
-import { Redirect } from 'react-router'
+import { bindActionCreators } from 'redux'
+import { connect } from 'react-redux'
import { Router, Route, Switch } from 'react-static'
+import { Redirect } from 'react-router'
+import Routes from 'react-static-routes'
import { hot } from 'react-hot-loader'
-import { withStyles } from '@material-ui/core/styles'
-import { connect } from 'react-redux'
-import { bindActionCreators } from 'redux'
-import { useHooks, useState } from 'use-react-hooks'
+import universal from 'react-universal-component'
+import Loading from 'components/Loading'
+import Private from './Private'
+import PrivateRoute from './PrivateRoute'
-// Asynchronously load routes that are chunked via code-splitting
-// 'import' as a function must take a string. It can't take a variable.
const uniOpts = { loading: Loading }
-const DashboardsIndex = universal(import('./containers/Dashboards/Index'), uniOpts)
-const JobsIndex = universal(import('./containers/Jobs/Index'), uniOpts)
-const JobsShow = universal(import('./containers/Jobs/Show'), uniOpts)
-const JobsDefinition = universal(import('./containers/Jobs/Definition'), uniOpts)
-const JobsNew = universal(import('./containers/Jobs/New'), uniOpts)
-const BridgesIndex = universal(import('./containers/Bridges/Index'), uniOpts)
-const BridgesNew = universal(import('./containers/Bridges/New'), uniOpts)
-const BridgesShow = universal(import('./containers/Bridges/Show'), uniOpts)
-const BridgesEdit = universal(import('./containers/Bridges/Edit'), uniOpts)
-const JobRunsIndex = universal(import('./containers/JobRuns/Index'), uniOpts)
-const JobRunsShow = universal(import('./containers/JobRuns/Show'), uniOpts)
-const JobRunsShowJson = universal(import('./containers/JobRuns/ShowJson'), uniOpts)
-const Configuration = universal(import('./containers/Configuration'), uniOpts)
const SignIn = universal(import('./containers/SignIn'), uniOpts)
const SignOut = universal(import('./containers/SignOut'), uniOpts)
-const styles = theme => {
- return {
- content: {
- marginTop: 0,
- marginBottom: theme.spacing.unit * 5
+class Layout extends PureComponent {
+ // Remove the server-side injected CSS.
+ componentDidMount () {
+ const jssStyles = document.getElementById('jss-server-side')
+ if (jssStyles && jssStyles.parentNode) {
+ jssStyles.parentNode.removeChild(jssStyles)
}
}
-}
-
-const Layout = useHooks(props => {
- const [headerHeight, resizeHeaderHeight] = useState(0)
- const onHeaderResize = (_width, height) => {
- resizeHeaderHeight(height)
+ render () {
+ const { redirectTo } = this.props
+
+ return (
+
+
+
+
+
+
+
+ {redirectTo && }
+
+
+
+
+
+ )
}
-
- const { classes, redirectTo } = props
-
- return (
-
-
-
-
-
- { props.drawerContainer = ref }}
- style={{ paddingTop: headerHeight }}
- >
-
-
-
-
-
-
- {redirectTo && }
- (
-
- )}
- />
-
-
-
- }
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
-})
+}
const mapStateToProps = state => ({
redirectTo: state.redirect.to
@@ -125,4 +59,4 @@ export const ConnectedLayout = connect(
mapDispatchToProps
)(Layout)
-export default hot(module)(withStyles(styles)(ConnectedLayout))
+export default hot(module)(ConnectedLayout)
diff --git a/gui/src/Private.js b/gui/src/Private.js
new file mode 100644
index 00000000000..40d3160ad7f
--- /dev/null
+++ b/gui/src/Private.js
@@ -0,0 +1,104 @@
+import React from 'react'
+import Routes from 'react-static-routes'
+import Grid from '@material-ui/core/Grid'
+import universal from 'react-universal-component'
+import { Switch } from 'react-static'
+import { hot } from 'react-hot-loader'
+import { withStyles } from '@material-ui/core/styles'
+import { useHooks, useState } from 'use-react-hooks'
+import Header from 'containers/Header'
+import Loading from 'components/Loading'
+import Notifications from 'containers/Notifications'
+import PrivateRoute from './PrivateRoute'
+
+// Asynchronously load routes that are chunked via code-splitting
+// 'import' as a function must take a string. It can't take a variable.
+const uniOpts = { loading: Loading }
+const DashboardsIndex = universal(import('./containers/Dashboards/Index'), uniOpts)
+const JobsIndex = universal(import('./containers/Jobs/Index'), uniOpts)
+const JobsShow = universal(import('./containers/Jobs/Show'), uniOpts)
+const JobsDefinition = universal(import('./containers/Jobs/Definition'), uniOpts)
+const JobsNew = universal(import('./containers/Jobs/New'), uniOpts)
+const BridgesIndex = universal(import('./containers/Bridges/Index'), uniOpts)
+const BridgesNew = universal(import('./containers/Bridges/New'), uniOpts)
+const BridgesShow = universal(import('./containers/Bridges/Show'), uniOpts)
+const BridgesEdit = universal(import('./containers/Bridges/Edit'), uniOpts)
+const JobRunsIndex = universal(import('./containers/JobRuns/Index'), uniOpts)
+const JobRunsShow = universal(import('./containers/JobRuns/Show'), uniOpts)
+const JobRunsShowJson = universal(import('./containers/JobRuns/ShowJson'), uniOpts)
+const Configuration = universal(import('./containers/Configuration'), uniOpts)
+
+const styles = theme => {
+ return {
+ content: {
+ marginTop: 0,
+ marginBottom: theme.spacing.unit * 5
+ }
+ }
+}
+
+const Private = useHooks(props => {
+ const [headerHeight, resizeHeaderHeight] = useState(0)
+
+ const onHeaderResize = (_width, height) => {
+ resizeHeaderHeight(height)
+ }
+
+ const { classes } = props
+
+ return (
+
+
+
+
+ { props.drawerContainer = ref }}
+ style={{ paddingTop: headerHeight }}
+ >
+
+
+
+
+ (
+
+ )}
+ />
+
+
+
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+})
+
+export default hot(module)(withStyles(styles)(Private))
diff --git a/gui/src/components/Logo.js b/gui/src/components/Logo.js
index 013b3945045..c32740d6ad2 100644
--- a/gui/src/components/Logo.js
+++ b/gui/src/components/Logo.js
@@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
+import pick from 'lodash/pick'
import Image from './Image'
-import logo from '../images/chainlink-operator-logo.svg'
import { withStyles } from '@material-ui/core/styles'
const styles = theme => {
@@ -16,21 +16,16 @@ const styles = theme => {
}
}
-const Logo = ({ width, height }) => {
- const size = { width, height }
-
- return (
-
- )
+const Logo = props => {
+ const imageProps = pick(props, ['src', 'width', 'height', 'alt'])
+ return
}
Logo.propTypes = {
+ src: PropTypes.string.isRequired,
width: PropTypes.number,
- height: PropTypes.number
+ height: PropTypes.number,
+ alt: PropTypes.string
}
export default withStyles(styles)(Logo)
diff --git a/gui/src/components/Logos/Hexagon.js b/gui/src/components/Logos/Hexagon.js
new file mode 100644
index 00000000000..d7307afe8e9
--- /dev/null
+++ b/gui/src/components/Logos/Hexagon.js
@@ -0,0 +1,21 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import Logo from '../Logo'
+import src from '../../images/icon-logo-blue.svg'
+
+const Hexagon = props => {
+ return (
+
+ )
+}
+
+Hexagon.propTypes = {
+ width: PropTypes.number,
+ height: PropTypes.number
+}
+
+export default Hexagon
diff --git a/gui/src/components/Logos/Main.js b/gui/src/components/Logos/Main.js
new file mode 100644
index 00000000000..eb494a5bc7a
--- /dev/null
+++ b/gui/src/components/Logos/Main.js
@@ -0,0 +1,21 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import Logo from '../Logo'
+import src from '../../images/chainlink-operator-logo.svg'
+
+const Main = props => {
+ return (
+
+ )
+}
+
+Main.propTypes = {
+ width: PropTypes.number,
+ height: PropTypes.number
+}
+
+export default Main
diff --git a/gui/src/containers/Header.js b/gui/src/containers/Header.js
index 87e8ee11c81..748855e9feb 100644
--- a/gui/src/containers/Header.js
+++ b/gui/src/containers/Header.js
@@ -19,7 +19,7 @@ import IconButton from '@material-ui/core/IconButton'
import MenuIcon from '@material-ui/icons/Menu'
import Portal from '@material-ui/core/Portal'
import LoadingBar from 'components/LoadingBar'
-import Logo from 'components/Logo'
+import MainLogo from 'components/Logos/Main'
import AvatarMenu from 'components/AvatarMenu'
import { submitSignOut } from 'actions'
import fetchCountSelector from 'selectors/fetchCount'
@@ -140,7 +140,7 @@ const Header = useHooks(props => {
-
+
diff --git a/gui/src/containers/SignIn.js b/gui/src/containers/SignIn.js
index 2fa25866f6e..c740e5ffca6 100644
--- a/gui/src/containers/SignIn.js
+++ b/gui/src/containers/SignIn.js
@@ -3,20 +3,38 @@ import { connect } from 'react-redux'
import { Redirect } from 'react-router'
import { withStyles } from '@material-ui/core/styles'
import Button from '@material-ui/core/Button'
+import Card from '@material-ui/core/Card'
+import CardContent from '@material-ui/core/CardContent'
import Typography from '@material-ui/core/Typography'
import TextField from '@material-ui/core/TextField'
import { Grid } from '@material-ui/core'
+import { useHooks, useState } from 'use-react-hooks'
+import { hot } from 'react-hot-loader'
import { submitSignIn } from 'actions'
-import Title from 'components/Title'
+import HexagonLogo from 'components/Logos/Hexagon'
import matchRouteAndMapDispatchToProps from 'utils/matchRouteAndMapDispatchToProps'
-import { useHooks, useState } from 'use-react-hooks'
const styles = theme => ({
- button: {
- margin: theme.spacing.unit * 5
+ container: {
+ height: '100%'
+ },
+ cardContent: {
+ paddingTop: theme.spacing.unit * 6,
+ paddingLeft: theme.spacing.unit * 4,
+ paddingRight: theme.spacing.unit * 4,
+ '&:last-child': {
+ paddingBottom: theme.spacing.unit * 6
+ }
+ },
+ headerRow: {
+ textAlign: 'center'
},
- title: {
- marginTop: theme.spacing.unit * 5
+ error: {
+ backgroundColor: theme.palette.error.light,
+ marginTop: theme.spacing.unit * 2
+ },
+ errorText: {
+ color: theme.palette.error.main
}
})
@@ -31,55 +49,96 @@ export const SignIn = useHooks((props) => {
e.preventDefault()
props.submitSignIn({ email, password })
}
- const { classes, fetching, authenticated } = props
- const enabled = email.length > 0 && password.length > 0
+ const { classes, fetching, authenticated, errors } = props
if (authenticated) return
return (
-
+
)
-}
-)
+})
const mapStateToProps = state => ({
fetching: state.authentication.fetching,
- authenticated: state.authentication.allowed
+ authenticated: state.authentication.allowed,
+ errors: state.notifications.errors
})
export const ConnectedSignIn = connect(
@@ -87,4 +146,4 @@ export const ConnectedSignIn = connect(
matchRouteAndMapDispatchToProps({ submitSignIn })
)(SignIn)
-export default withStyles(styles)(ConnectedSignIn)
+export default hot(module)(withStyles(styles)(ConnectedSignIn))
diff --git a/gui/src/images/icon-logo-blue.svg b/gui/src/images/icon-logo-blue.svg
new file mode 100644
index 00000000000..e35f1df284c
--- /dev/null
+++ b/gui/src/images/icon-logo-blue.svg
@@ -0,0 +1 @@
+
diff --git a/gui/src/index.css b/gui/src/index.css
new file mode 100644
index 00000000000..29d0d4c55ee
--- /dev/null
+++ b/gui/src/index.css
@@ -0,0 +1,9 @@
+body {
+ height: 100vh;
+ min-height: 100vh;
+}
+
+#root {
+ height: 100%;
+ min-height: 100%;
+}
diff --git a/jest.config.js b/jest.config.js
index 1dac83b3067..95c0f589817 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -11,6 +11,7 @@ module.exports = {
'/node_modules/'
],
moduleNameMapper: {
+ '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '/gui/__mocks__/fileMock.js',
'\\.(css|less|sass|scss)$': '/gui/__mocks__/styleMock.js'
}
}