mirror of
https://github.com/navidrome/navidrome.git
synced 2025-06-08 19:32:16 +03:00
Add a login page (not been used yet)
This commit is contained in:
parent
11f6acbb63
commit
c661ac8833
@ -2,11 +2,12 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Admin, Resource } from 'react-admin'
|
import { Admin, Resource } from 'react-admin'
|
||||||
import jsonServerProvider from 'ra-data-json-server'
|
import jsonServerProvider from 'ra-data-json-server'
|
||||||
|
import { Login } from './layout'
|
||||||
import user from './user'
|
import user from './user'
|
||||||
|
|
||||||
const dataProvider = jsonServerProvider('/app/api')
|
const dataProvider = jsonServerProvider('/app/api')
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<Admin dataProvider={dataProvider}>
|
<Admin dataProvider={dataProvider} loginPage={Login}>
|
||||||
<Resource name="user" {...user} />
|
<Resource name="user" {...user} />
|
||||||
</Admin>
|
</Admin>
|
||||||
)
|
)
|
||||||
|
179
ui/src/layout/Login.js
Normal file
179
ui/src/layout/Login.js
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { Field, Form } from 'react-final-form'
|
||||||
|
|
||||||
|
import Avatar from '@material-ui/core/Avatar'
|
||||||
|
import Button from '@material-ui/core/Button'
|
||||||
|
import Card from '@material-ui/core/Card'
|
||||||
|
import CardActions from '@material-ui/core/CardActions'
|
||||||
|
import CircularProgress from '@material-ui/core/CircularProgress'
|
||||||
|
import TextField from '@material-ui/core/TextField'
|
||||||
|
import { createMuiTheme, makeStyles } from '@material-ui/core/styles'
|
||||||
|
import { ThemeProvider } from '@material-ui/styles'
|
||||||
|
import LockIcon from '@material-ui/icons/Lock'
|
||||||
|
|
||||||
|
import { Notification, useLogin, useNotify, useTranslate } from 'react-admin'
|
||||||
|
|
||||||
|
import { lightTheme } from './themes'
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
main: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
minHeight: '100vh',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
background: 'url(https://source.unsplash.com/random/1600x900?music)',
|
||||||
|
backgroundRepeat: 'no-repeat',
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'center'
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
minWidth: 300,
|
||||||
|
marginTop: '6em'
|
||||||
|
},
|
||||||
|
avatar: {
|
||||||
|
margin: '1em',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
backgroundColor: theme.palette.secondary.main
|
||||||
|
},
|
||||||
|
systemName: {
|
||||||
|
marginTop: '1em',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
color: 'blue' //theme.palette.grey[500]
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
padding: '0 1em 1em 1em'
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
marginTop: '1em'
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
padding: '0 1em 1em 1em'
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
const renderInput = ({
|
||||||
|
meta: { touched, error } = {},
|
||||||
|
input: { ...inputProps },
|
||||||
|
...props
|
||||||
|
}) => (
|
||||||
|
<TextField
|
||||||
|
error={!!(touched && error)}
|
||||||
|
helperText={touched && error}
|
||||||
|
{...inputProps}
|
||||||
|
{...props}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
const Login = ({ location }) => {
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const translate = useTranslate()
|
||||||
|
const classes = useStyles()
|
||||||
|
const notify = useNotify()
|
||||||
|
const login = useLogin()
|
||||||
|
|
||||||
|
const handleSubmit = (auth) => {
|
||||||
|
setLoading(true)
|
||||||
|
login(auth, location.state ? location.state.nextPathname : '/').catch(
|
||||||
|
(error) => {
|
||||||
|
setLoading(false)
|
||||||
|
notify(
|
||||||
|
typeof error === 'string'
|
||||||
|
? error
|
||||||
|
: typeof error === 'undefined' || !error.message
|
||||||
|
? 'ra.auth.sign_in_error'
|
||||||
|
: error.message,
|
||||||
|
'warning'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const validate = (values) => {
|
||||||
|
const errors = {}
|
||||||
|
if (!values.username) {
|
||||||
|
errors.username = translate('ra.validation.required')
|
||||||
|
}
|
||||||
|
if (!values.password) {
|
||||||
|
errors.password = translate('ra.validation.required')
|
||||||
|
}
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
validate={validate}
|
||||||
|
render={({ handleSubmit }) => (
|
||||||
|
<form onSubmit={handleSubmit} noValidate>
|
||||||
|
<div className={classes.main}>
|
||||||
|
<Card className={classes.card}>
|
||||||
|
<div className={classes.avatar}>
|
||||||
|
<Avatar className={classes.icon}>
|
||||||
|
<LockIcon />
|
||||||
|
</Avatar>
|
||||||
|
</div>
|
||||||
|
<div className={classes.systemName}>CloudSonic</div>
|
||||||
|
<div className={classes.form}>
|
||||||
|
<div className={classes.input}>
|
||||||
|
<Field
|
||||||
|
autoFocus
|
||||||
|
name="username"
|
||||||
|
component={renderInput}
|
||||||
|
label={translate('ra.auth.username')}
|
||||||
|
disabled={loading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={classes.input}>
|
||||||
|
<Field
|
||||||
|
name="password"
|
||||||
|
component={renderInput}
|
||||||
|
label={translate('ra.auth.password')}
|
||||||
|
type="password"
|
||||||
|
disabled={loading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<CardActions className={classes.actions}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
type="submit"
|
||||||
|
color="primary"
|
||||||
|
disabled={loading}
|
||||||
|
className={classes.button}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
{loading && <CircularProgress size={25} thickness={2} />}
|
||||||
|
{translate('ra.auth.sign_in')}
|
||||||
|
</Button>
|
||||||
|
</CardActions>
|
||||||
|
</Card>
|
||||||
|
<Notification />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Login.propTypes = {
|
||||||
|
authProvider: PropTypes.func,
|
||||||
|
previousRoute: PropTypes.string
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to put the ThemeProvider decoration in another component
|
||||||
|
// Because otherwise the useStyles() hook used in Login won't get
|
||||||
|
// the right theme
|
||||||
|
const LoginWithTheme = (props) => (
|
||||||
|
<ThemeProvider theme={createMuiTheme(lightTheme)}>
|
||||||
|
<Login {...props} />
|
||||||
|
</ThemeProvider>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default LoginWithTheme
|
3
ui/src/layout/index.js
Normal file
3
ui/src/layout/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import Login from './Login'
|
||||||
|
|
||||||
|
export { Login }
|
26
ui/src/layout/themes.js
Normal file
26
ui/src/layout/themes.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
export const darkTheme = {
|
||||||
|
palette: {
|
||||||
|
type: 'dark' // Switching the dark mode on is a single property value change.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const lightTheme = {
|
||||||
|
palette: {
|
||||||
|
secondary: {
|
||||||
|
light: '#5f5fc4',
|
||||||
|
main: '#283593',
|
||||||
|
dark: '#001064',
|
||||||
|
contrastText: '#fff'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
overrides: {
|
||||||
|
MuiFilledInput: {
|
||||||
|
root: {
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.04)',
|
||||||
|
'&$disabled': {
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.04)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user