diff options
-rw-r--r-- | modern/public/logo.svg | 4 | ||||
-rw-r--r-- | modern/src/LoginPage.js | 118 | ||||
-rw-r--r-- | modern/src/components/LoginForm.js | 125 | ||||
-rw-r--r-- | modern/src/components/RegisterForm.js | 122 | ||||
-rw-r--r-- | modern/src/components/ResetPasswordForm.js | 12 | ||||
-rw-r--r-- | modern/src/theme/palette.js | 2 |
6 files changed, 267 insertions, 116 deletions
diff --git a/modern/public/logo.svg b/modern/public/logo.svg index 6fbe7b0..008b46d 100644 --- a/modern/public/logo.svg +++ b/modern/public/logo.svg @@ -12,8 +12,8 @@ </metadata> <g id="layer1"> <rect id="rect3778" height="64" width="236.1" y="0" x="0" fill="none"/> - <ellipse id="path3038" rx="28.995" ry="28.995" transform="rotate(-30)" cy="43.713" cx="11.713" stroke-width="10.699" fill="none"/> - <g fill="#fff"> + <ellipse id="path3038" rx="28.995" ry="28.995" transform="rotate(-30)" cy="43.713" cx="11.713" stroke-width="10.699" fill="#fff"/> + <g fill="#336"> <circle id="path2993" stroke-width="1.3262" transform="rotate(-30)" cy="43.713" cx="9.4364" r="2.2765"/> <path id="path3004" d="m37.012 24.177-2.8428 3.6128c0.66345 0.52205 1.3255 1.1576 1.7734 1.9333 0.4479 0.77578 0.66726 1.6669 0.78764 2.5025l4.5502-0.65558c-0.193-1.42-0.633-2.804-1.394-4.123s-1.74-2.391-2.874-3.27z" stroke-width="1.0095"/> <path id="path3014" d="m42.504 16.9-2.8428 3.6128c1.607 1.2355 3.0914 2.7935 4.1679 4.6581s1.6835 3.9291 1.95 5.9386l4.5502-0.65558c-0.33967-2.5954-1.1669-5.1513-2.5573-7.5594-1.3903-2.4081-3.1901-4.4025-5.268-5.9944z" stroke-width="1.0095"/> diff --git a/modern/src/LoginPage.js b/modern/src/LoginPage.js index 30275e5..3d4b17f 100644 --- a/modern/src/LoginPage.js +++ b/modern/src/LoginPage.js @@ -1,14 +1,8 @@ import React, { useState } from 'react'; -import { useDispatch } from 'react-redux'; -import { useHistory } from 'react-router-dom'; -import { sessionActions } from './store'; -import { Grid, useMediaQuery, makeStyles, InputLabel, Select, MenuItem, FormControl, Button, TextField, Paper, Link } from '@material-ui/core'; +import { useMediaQuery, makeStyles, Paper } from '@material-ui/core'; import { useTheme } from '@material-ui/core/styles'; -import RegisterDialog from './RegisterDialog'; -import { useSelector } from 'react-redux'; -import t from './common/localization'; -import Logo from './Logo'; +import LoginForm from './components/LoginForm'; const useStyles = makeStyles(theme => ({ root: { @@ -45,123 +39,23 @@ const useStyles = makeStyles(theme => ({ padding: theme.spacing(5), width: "100%", }, - logo: { - textAlign: 'center', - }, })); const LoginPage = () => { const classes = useStyles(); const theme = useTheme(); - const dispatch = useDispatch(); - const history = useHistory(); - - const [failed, setFailed] = useState(false); - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - const [registerDialogShown, setRegisterDialogShown] = useState(false); - const registrationEnabled = useSelector(state => state.session.server ? state.session.server['registration'] : false); + const [CurrentForm, setCurrentForm] = useState(() => LoginForm); const matches = useMediaQuery(theme.breakpoints.down('md')); - const handleEmailChange = (event) => { - setEmail(event.target.value); - } - - const handlePasswordChange = (event) => { - setPassword(event.target.value); - } - - const handleRegister = () => { - setRegisterDialogShown(true); - } - - const handleRegisterResult = () => { - setRegisterDialogShown(false); - } - - const handleLogin = async (event) => { - event.preventDefault(); - const response = await fetch('/api/session', { method: 'POST', body: new URLSearchParams(`email=${email}&password=${password}`) }); - if (response.ok) { - const user = await response.json(); - dispatch(sessionActions.updateUser(user)); - history.push('/'); - } else { - setFailed(true); - setPassword(''); - } - } - return ( <main className={classes.root}> <div className={classes.sidebar}> - {!matches && <Logo fill='#fff'/> } + {!matches && <img src='/logo.svg' alt='Traccar' /> } </div> <Paper className={classes.paper}> - <form className={classes.form} onSubmit={handleLogin}> - <Grid container direction='column' spacing={3}> - <Grid item className={classes.logo}> - {matches && <Logo fill='#333366'/>} - </Grid> - <Grid item> - <TextField - required - fullWidth - error={failed} - label={t('userEmail')} - name='email' - value={email} - autoComplete='email' - autoFocus - onChange={handleEmailChange} - helperText={failed && 'Invalid username or password'} - variant='filled' /> - </Grid> - <Grid item> - <TextField - required - fullWidth - error={failed} - label={t('userPassword')} - name='password' - value={password} - type='password' - autoComplete='current-password' - onChange={handlePasswordChange} - variant='filled' /> - </Grid> - <Grid item> - <Button - type='submit' - variant='contained' - color='secondary' - disabled={!email || !password} - fullWidth> - {t('loginLogin')} - </Button> - </Grid> - <Grid item container> - <Grid item> - <Button onClick={handleRegister} disabled={!registrationEnabled} color="secondary"> - {t('loginRegister')} - </Button> - </Grid> - <Grid item xs> - <FormControl variant="filled" fullWidth> - <InputLabel>{t('loginLanguage')}</InputLabel> - <Select> - <MenuItem value="en">English</MenuItem> - </Select> - </FormControl> - </Grid> - </Grid> - <Grid item container justify="flex-end"> - <Grid item> - <Link underline="none">{t('loginReset')}</Link> - </Grid> - </Grid> - </Grid> + <form className={classes.form}> + <CurrentForm setCurrentForm={setCurrentForm} /> </form> </Paper> </main> diff --git a/modern/src/components/LoginForm.js b/modern/src/components/LoginForm.js new file mode 100644 index 0000000..d52a51d --- /dev/null +++ b/modern/src/components/LoginForm.js @@ -0,0 +1,125 @@ +import React, { useState } from 'react'; +import { Grid, useMediaQuery, makeStyles, InputLabel, Select, MenuItem, FormControl, Button, TextField, Link } from '@material-ui/core'; +import { useTheme } from '@material-ui/core/styles'; +import { useDispatch, useSelector } from 'react-redux'; +import { useHistory } from 'react-router-dom'; +import { sessionActions } from './../store'; +import t from './../common/localization'; +import RegisterForm from './RegisterForm'; +import ResetPasswordForm from './ResetPasswordForm'; + +const useStyles = makeStyles(theme => ({ + logoContainer: { + textAlign: 'center', + }, + resetPassword: { + cursor: 'pointer', + } +})); + +const forms = { + register: () => RegisterForm, + resetPassword: () => ResetPasswordForm, +}; + +const LoginForm = ({ setCurrentForm }) => { + + const classes = useStyles(); + const dispatch = useDispatch(); + const history = useHistory(); + const theme = useTheme(); + const matches = useMediaQuery(theme.breakpoints.down('md')); + + const [failed, setFailed] = useState(false); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const registrationEnabled = useSelector(state => state.session.server ? state.session.server['registration'] : false); + + const handleEmailChange = (event) => { + setEmail(event.target.value); + } + + const handlePasswordChange = (event) => { + setPassword(event.target.value); + } + + const handleLogin = async (event) => { + event.preventDefault(); + const response = await fetch('/api/session', { method: 'POST', body: new URLSearchParams(`email=${email}&password=${password}`) }); + if (response.ok) { + const user = await response.json(); + dispatch(sessionActions.updateUser(user)); + history.push('/'); + } else { + setFailed(true); + setPassword(''); + } + } + + return ( + <Grid container direction='column' spacing={3}> + <Grid item className={classes.logoContainer}> + {matches && <img src='/logo.svg' alt='Traccar' />} + </Grid> + <Grid item> + <TextField + required + fullWidth + error={failed} + label={t('userEmail')} + name='email' + value={email} + autoComplete='email' + autoFocus + onChange={handleEmailChange} + helperText={failed && 'Invalid username or password'} + variant='filled' /> + </Grid> + <Grid item> + <TextField + required + fullWidth + error={failed} + label={t('userPassword')} + name='password' + value={password} + type='password' + autoComplete='current-password' + onChange={handlePasswordChange} + variant='filled' /> + </Grid> + <Grid item> + <Button + onClick={handleLogin} + variant='contained' + color='secondary' + disabled={!email || !password} + fullWidth> + {t('loginLogin')} + </Button> + </Grid> + <Grid item container> + <Grid item> + <Button onClick={() => setCurrentForm(forms.register)} disabled={!registrationEnabled} color="secondary"> + {t('loginRegister')} + </Button> + </Grid> + <Grid item xs> + <FormControl variant="filled" fullWidth> + <InputLabel>{t('loginLanguage')}</InputLabel> + <Select> + <MenuItem value="en">English</MenuItem> + </Select> + </FormControl> + </Grid> + </Grid> + <Grid item container justify="flex-end"> + <Grid item> + <Link onClick={() => setCurrentForm(forms.resetPassword)} className={classes.resetPassword} underline="none">{t('loginReset')}</Link> + </Grid> + </Grid> + </Grid> + ) +} + +export default LoginForm; diff --git a/modern/src/components/RegisterForm.js b/modern/src/components/RegisterForm.js new file mode 100644 index 0000000..6d013f7 --- /dev/null +++ b/modern/src/components/RegisterForm.js @@ -0,0 +1,122 @@ +import React, { useState } from 'react'; +import { Grid, Button, TextField, Typography, Link, makeStyles, Snackbar } from '@material-ui/core'; +import ArrowBackIcon from '@material-ui/icons/ArrowBack'; +import LoginForm from './LoginForm'; +import t from './../common/localization'; + +const useStyles = makeStyles(theme => ({ + register: { + fontSize: theme.spacing(3), + fontWeight: 500 + }, + link: { + fontSize: theme.spacing(3), + fontWeight: 500, + marginTop: theme.spacing(0.5), + cursor: 'pointer' + } +})); + +const forms = { + login: () => LoginForm, +}; + +const RegisterForm = ({ setCurrentForm }) => { + + const classes = useStyles(); + const [name, setName] = useState(''); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [snackbarOpen, setSnackbarOpen] = useState(false); + + const submitDisabled = () => { + return !name || !/(.+)@(.+)\.(.{2,})/.test(email) || !password; + } + + const handleRegister = async () => { + const response = await fetch('/api/users', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({name, email, password}) + }); + + if (response.ok) { + setSnackbarOpen(true); + } + } + + return ( + <> + <Snackbar + anchorOrigin={{ vertical: 'top', horizontal: 'center' }} + open={snackbarOpen} + onClose={() => setCurrentForm(forms.login)} + autoHideDuration={6000} + message={t('loginCreated')} /> + <Grid container direction='column' spacing={3}> + <Grid container item> + <Grid item> + <Typography className={classes.link} color='primary'> + <Link onClick={() => setCurrentForm(forms.login)}> + <ArrowBackIcon /> + </Link> + </Typography> + </Grid> + <Grid item xs> + <Typography className={classes.register} color='primary'> + {t('loginRegister')} + </Typography> + </Grid> + </Grid> + <Grid item> + <TextField + required + fullWidth + label={t('sharedName')} + name='name' + value={name || ''} + autoComplete='name' + autoFocus + onChange={event => setName(event.target.value)} + variant='filled' /> + </Grid> + <Grid item> + <TextField + required + fullWidth + type='email' + label={t('userEmail')} + name='email' + value={email || ''} + autoComplete='email' + onChange={event => setEmail(event.target.value)} + variant='filled' /> + </Grid> + <Grid item> + <TextField + required + fullWidth + label={t('userPassword')} + name='password' + value={password || ''} + type='password' + autoComplete='current-password' + onChange={event => setPassword(event.target.value)} + variant='filled' /> + </Grid> + <Grid item> + <Button + variant='contained' + color="secondary" + onClick={handleRegister} + disabled={submitDisabled()} + fullWidth> + {t('loginRegister')} + </Button> + </Grid> + </Grid> + </> + ) +} + +export default RegisterForm; diff --git a/modern/src/components/ResetPasswordForm.js b/modern/src/components/ResetPasswordForm.js new file mode 100644 index 0000000..c268f80 --- /dev/null +++ b/modern/src/components/ResetPasswordForm.js @@ -0,0 +1,12 @@ +import React from 'react'; + +const ResetPasswordForm = () => { + + return ( + <> + <div>Reset Password Comming Soon!!!</div> + </> + ) +} + +export default ResetPasswordForm; diff --git a/modern/src/theme/palette.js b/modern/src/theme/palette.js index 5a54b77..5c93cfd 100644 --- a/modern/src/theme/palette.js +++ b/modern/src/theme/palette.js @@ -1,5 +1,3 @@ -import { deepPurple, green } from '@material-ui/core/colors'; - const traccarPurple = '#333366'; const traccarGreen = '#4CAF50'; const traccarWhite = '#FFF'; |