import React, { useState, useCallback, useEffect } from 'react' import PropTypes from 'prop-types' import { Field, Form } from 'react-final-form' import { useDispatch } from 'react-redux' 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 Link from '@material-ui/core/Link' import TextField from '@material-ui/core/TextField' import { ThemeProvider, makeStyles } from '@material-ui/core/styles' import { createMuiTheme, useLogin, useNotify, useRefresh, useSetLocale, useTranslate, useVersion, } from 'react-admin' import Logo from '../icons/android-icon-192x192.png' import Notification from './Notification' import useCurrentTheme from '../themes/useCurrentTheme' import config from '../config' import { clearQueue } from '../actions' import { retrieveTranslation } from '../i18n' import { INSIGHTS_DOC_URL } from '../consts.js' const useStyles = makeStyles( (theme) => ({ main: { display: 'flex', flexDirection: 'column', minHeight: '100vh', alignItems: 'center', justifyContent: 'flex-start', background: `url(${config.loginBackgroundURL})`, backgroundRepeat: 'no-repeat', backgroundSize: 'cover', backgroundPosition: 'center', }, card: { minWidth: 300, marginTop: '6em', overflow: 'visible', }, avatar: { margin: '1em', display: 'flex', justifyContent: 'center', marginTop: '-3em', }, icon: { backgroundColor: 'transparent', width: '6.3em', height: '6.3em', }, systemName: { marginTop: '1em', display: 'flex', justifyContent: 'center', color: '#3f51b5', //theme.palette.grey[500] }, welcome: { marginTop: '1em', padding: '0 1em 1em 1em', display: 'flex', justifyContent: 'center', flexWrap: 'wrap', color: '#3f51b5', //theme.palette.grey[500] }, form: { padding: '0 1em 1em 1em', }, input: { marginTop: '1em', }, actions: { padding: '0 1em 1em 1em', }, button: {}, systemNameLink: { textDecoration: 'none', }, message: { marginTop: '1em', padding: '0 1em 1em 1em', textAlign: 'center', wordBreak: 'break-word', fontSize: '0.875em', }, }), { name: 'NDLogin' }, ) const renderInput = ({ meta: { touched, error } = {}, input: { ...inputProps }, ...props }) => ( ) const FormLogin = ({ loading, handleSubmit, validate }) => { const translate = useTranslate() const classes = useStyles() return (
(
{'logo'}
Navidrome
{config.welcomeMessage && (
)}
)} /> ) } const InsightsNotice = ({ url }) => { const translate = useTranslate() const classes = useStyles() const anchorRegex = /\[(.+?)]/g const originalMsg = translate('ra.auth.insightsCollectionNote') // Split the entire message on newlines const lines = originalMsg.split('\n') const renderedLines = lines.map((line, lineIndex) => { const segments = [] let lastIndex = 0 let match // Find bracketed text in each line while ((match = anchorRegex.exec(line)) !== null) { // match.index is where "[something]" starts // match[1] is the text inside the brackets const bracketText = match[1] // Push the text before the bracket segments.push(line.slice(lastIndex, match.index)) // Push the component segments.push( {bracketText} , ) // Update lastIndex to the character right after the bracketed text lastIndex = match.index + match[0].length } // Push the remaining text after the last bracket segments.push(line.slice(lastIndex)) // Return this line’s parts, plus a
if not the last line return ( {segments} {lineIndex < lines.length - 1 &&
}
) }) return
{renderedLines}
} const FormSignUp = ({ loading, handleSubmit, validate }) => { const translate = useTranslate() const classes = useStyles() return (
(
{'logo'}
{translate('ra.auth.welcome1')}
{translate('ra.auth.welcome2')}
)} /> ) } const Login = ({ location }) => { const [loading, setLoading] = useState(false) const translate = useTranslate() const notify = useNotify() const login = useLogin() const dispatch = useDispatch() const handleSubmit = useCallback( (auth) => { setLoading(true) dispatch(clearQueue()) 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', ) }, ) }, [dispatch, login, notify, setLoading, location], ) const validateLogin = useCallback( (values) => { const errors = {} if (!values.username) { errors.username = translate('ra.validation.required') } if (!values.password) { errors.password = translate('ra.validation.required') } return errors }, [translate], ) const validateSignup = useCallback( (values) => { const errors = validateLogin(values) const regex = /^\w+$/g if (values.username && !values.username.match(regex)) { errors.username = translate('ra.validation.invalidChars') } if (!values.confirmPassword) { errors.confirmPassword = translate('ra.validation.required') } if (values.confirmPassword !== values.password) { errors.confirmPassword = translate('ra.validation.passwordDoesNotMatch') } return errors }, [translate, validateLogin], ) if (config.firstTime) { return ( ) } return ( ) } 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) => { const theme = useCurrentTheme() const setLocale = useSetLocale() const refresh = useRefresh() const version = useVersion() useEffect(() => { if (config.defaultLanguage !== '' && !localStorage.getItem('locale')) { retrieveTranslation(config.defaultLanguage) .then(() => { setLocale(config.defaultLanguage).then(() => { localStorage.setItem('locale', config.defaultLanguage) }) refresh(true) }) .catch((e) => { throw new Error( 'Cannot load language "' + config.defaultLanguage + '": ' + e, ) }) } }, [refresh, setLocale]) return ( ) } export default LoginWithTheme