1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-03-27 10:50:10 +03:00

F #3951: Refactor login component (#366)

This commit is contained in:
Sergio Betanzos 2020-10-27 13:29:13 +01:00 committed by GitHub
parent 440941d712
commit 2bba7a7bab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 338 additions and 402 deletions

View File

@ -52,7 +52,7 @@ const App = ({ location, context, store, app }) => {
{location && context ? (
// server build
<StaticRouter location={location} context={context}>
{/* <Router app={appName} /> */}
<Router app={appName} />
</StaticRouter>
) : (
// browser build

View File

@ -5,7 +5,6 @@ import {
makeStyles,
Box,
Fade,
Badge,
Button,
Card,
CardHeader,

View File

@ -13,7 +13,7 @@ import ErrorHelper from 'client/components/FormControl/ErrorHelper'
import { Tr } from 'client/components/HOC/Translate'
const CheckboxController = memo(
({ control, cy, name, label, tooltip, error }) => (
({ control, cy, name, label, tooltip, error, fieldProps }) => (
<Controller
render={({ onChange, value }) => (
<Tooltip title={Tr(tooltip) ?? ''}>
@ -26,6 +26,7 @@ const CheckboxController = memo(
checked={value}
color="primary"
inputProps={{ 'data-cy': cy }}
{...fieldProps}
/>
}
label={Tr(label)}
@ -51,7 +52,8 @@ CheckboxController.propTypes = {
error: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.objectOf(PropTypes.any)
])
]),
fieldProps: PropTypes.object
}
CheckboxController.defaultProps = {
@ -61,7 +63,8 @@ CheckboxController.defaultProps = {
label: '',
tooltip: undefined,
values: [],
error: false
error: false,
fieldProps: undefined
}
CheckboxController.displayName = 'CheckboxController'

View File

@ -1,79 +0,0 @@
/* Copyright 2002-2019, OpenNebula Project, OpenNebula Systems */
/* */
/* Licensed under the Apache License, Version 2.0 (the "License"); you may */
/* not use this file except in compliance with the License. You may obtain */
/* a copy of the License at */
/* */
/* http://www.apache.org/licenses/LICENSE-2.0 */
/* */
/* Unless required by applicable law or agreed to in writing, software */
/* distributed under the License is distributed on an "AS IS" BASIS, */
/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */
/* See the License for the specific language governing permissions and */
/* limitations under the License. */
/* -------------------------------------------------------------------------- */
import React, { useMemo } from 'react'
import { MenuItem, TextField } from '@material-ui/core'
import { FilterVintage } from '@material-ui/icons'
import useAuth from 'client/hooks/useAuth'
import useOpennebula from 'client/hooks/useOpennebula'
import { Tr } from 'client/components/HOC'
import { FILTER_POOL } from 'client/constants'
const GroupSelect = props => {
const { filterPool, authUser } = useAuth()
const { groups } = useOpennebula()
const defaultValue = useMemo(
() =>
filterPool === FILTER_POOL.ALL_RESOURCES
? FILTER_POOL.ALL_RESOURCES
: authUser?.GID,
[filterPool]
)
const orderGroups = useMemo(
() =>
groups
?.sort((a, b) => a.ID - b.ID)
?.map(({ ID, NAME }) => (
<MenuItem key={`selector-group-${ID}`} value={String(ID)}>
{`${ID} - ${String(NAME)}`}
{authUser?.GID === ID && (
<FilterVintage
style={{
fontSize: '1rem',
marginLeft: 16
}}
/>
)}
</MenuItem>
)),
[groups]
)
return (
<TextField
select
fullWidth
defaultValue={defaultValue}
variant="outlined"
inputProps={{ 'data-cy': 'select-group' }}
label={Tr('Select a group')}
FormHelperTextProps={{ 'data-cy': 'select-group-error' }}
{...props}
>
<MenuItem value={FILTER_POOL.ALL_RESOURCES}>{Tr('Show all')}</MenuItem>
{orderGroups}
</TextField>
)
}
GroupSelect.propTypes = {}
GroupSelect.defaultProps = {}
export default GroupSelect

View File

@ -8,18 +8,19 @@ import ErrorHelper from 'client/components/FormControl/ErrorHelper'
import { Tr } from 'client/components/HOC/Translate'
const SelectController = memo(
({ control, cy, name, label, values, error }) => (
({ control, cy, name, label, values, error, fieldProps }) => (
<Controller
as={
<TextField
select
SelectProps={{ displayEmpty: true }}
fullWidth
SelectProps={{ displayEmpty: true }}
label={Tr(label)}
inputProps={{ 'data-cy': cy }}
error={Boolean(error)}
helperText={Boolean(error) && <ErrorHelper label={error?.message} />}
FormHelperTextProps={{ 'data-cy': `${cy}-error` }}
{...fieldProps}
>
{Array.isArray(values) &&
values?.map(({ text, value }) => (
@ -31,6 +32,7 @@ const SelectController = memo(
}
name={name}
control={control}
defaultValue={values[0]?.value}
/>
),
(prevProps, nextProps) => prevProps.error === nextProps.error
@ -45,7 +47,8 @@ SelectController.propTypes = {
error: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.objectOf(PropTypes.any)
])
]),
fieldProps: PropTypes.object
}
SelectController.defaultProps = {
@ -54,7 +57,8 @@ SelectController.defaultProps = {
name: '',
label: '',
values: [],
error: false
error: false,
fieldProps: undefined
}
SelectController.displayName = 'SelectController'

View File

@ -8,7 +8,7 @@ import { Tr } from 'client/components/HOC'
import ErrorHelper from 'client/components/FormControl/ErrorHelper'
const TextController = memo(
({ control, cy, type, name, label, error }) => (
({ control, cy, type, name, label, error, fieldProps }) => (
<Controller
render={({ value, ...props }) =>
<TextField
@ -21,6 +21,7 @@ const TextController = memo(
helperText={Boolean(error) && <ErrorHelper label={error?.message} />}
FormHelperTextProps={{ 'data-cy': `${cy}-error` }}
{...props}
{...fieldProps}
/>
}
name={name}
@ -40,7 +41,8 @@ TextController.propTypes = {
error: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.objectOf(PropTypes.any)
])
]),
fieldProps: PropTypes.object
}
TextController.defaultProps = {
@ -49,7 +51,8 @@ TextController.defaultProps = {
type: 'text',
name: '',
label: '',
error: false
error: false,
fieldProps: undefined
}
TextController.displayName = 'TextController'

View File

@ -26,7 +26,7 @@ const FormWithSchema = ({ id, cy, fields }) => {
return (
<Grid container spacing={1}>
{fields?.map(
({ name, type, htmlType, label, values, dependOf, tooltip, grid }) => {
({ name, type, htmlType, label, values, dependOf, tooltip, grid, fieldProps }) => {
const dataCy = `${cy}-${name}`
const inputName = id ? `${id}.${name}` : name
@ -36,7 +36,7 @@ const FormWithSchema = ({ id, cy, fields }) => {
? useWatch({ control, name: id ? `${id}.${dependOf}` : dependOf })
: null
const htmlTypeValue = typeof htmlType === 'function' && dependOf
const htmlTypeValue = typeof htmlType === 'function'
? htmlType(dependValue)
: htmlType
@ -53,10 +53,11 @@ const FormWithSchema = ({ id, cy, fields }) => {
name: inputName,
label,
tooltip,
values: typeof values === 'function' && dependOf
values: typeof values === 'function'
? values(dependValue)
: values,
error: inputError
error: inputError,
fieldProps
})}
</Grid>
</HiddenInput>

View File

@ -1,12 +1,15 @@
module.exports = {
SignIn: 'Sign In',
Back: 'Back',
Next: 'Next',
Username: 'Username',
Password: 'Password',
Token2FA: '2FA Token',
SelectGroup: 'Select a group',
ShowAll: 'Show all',
NotFound: 'Not found',
Language: 'Language',
KeepLoggedIn: 'Keep me logged in',
Token2FA: '2FA Token',
SignOut: 'Sign Out',
Submit: 'Submit',
Response: 'Response',

View File

@ -20,12 +20,12 @@ const CustomDialog = ({ title, handleClose, children }) => {
fullScreen={isMobile}
open
onClose={handleClose}
maxWidth="lg"
maxWidth="xl"
scroll="paper"
PaperProps={{
style: {
height: isMobile ? '100%' : '80%',
width: isMobile ? '100%' : '80%'
height: isMobile ? '100%' : '90%',
width: isMobile ? '100%' : '90%'
}
}}
>

View File

@ -21,7 +21,12 @@ const DialogInfo = ({ info, handleClose }) => {
const renderTabs = useMemo(() => (
<AppBar position="static">
<Tabs value={tabSelected} onChange={(_, tab) => setTab(tab)}>
<Tabs
value={tabSelected}
variant="scrollable"
scrollButtons="auto"
onChange={(_, tab) => setTab(tab)}
>
{TABS.map(({ name, icon: Icon }, idx) =>
<Tab
key={`tab-${name}`}

View File

@ -0,0 +1,91 @@
import React, { useEffect } from 'react'
import PropTypes from 'prop-types'
import clsx from 'clsx'
import { Box, Button, Slide } from '@material-ui/core'
import { useForm, FormProvider } from 'react-hook-form'
import { yupResolver } from '@hookform/resolvers'
import { SignIn, Back, Next } from 'client/constants/translates'
import loginStyles from 'client/containers/Login/styles'
import { Tr } from 'client/components/HOC'
import ButtonSubmit from 'client/components/FormControl/SubmitButton'
import FormWithSchema from 'client/components/Forms/FormWithSchema'
const Form = ({ onBack, onSubmit, resolver, fields, error, isLoading, transitionProps }) => {
const defaultValues = resolver.default()
const classes = loginStyles()
console.log(defaultValues)
const { handleSubmit, setError, ...methods } = useForm({
reValidateMode: 'onSubmit',
defaultValues,
resolver: yupResolver(resolver)
})
useEffect(() => {
error && setError(fields[0].name, { type: 'manual', message: error })
}, [error])
return (
<Slide
timeout={{ enter: 400 }}
mountOnEnter
unmountOnExit
{...transitionProps}
>
<Box
component="form"
onSubmit={handleSubmit(onSubmit)}
className={clsx(classes.form, { [classes.loading]: isLoading })}
>
<FormProvider {...methods}>
<FormWithSchema cy="login" fields={fields} />
</FormProvider>
<Box>
{onBack && (
<Button onClick={onBack} color="primary" disabled={isLoading}>
{Tr(Back)}
</Button>
)}
<ButtonSubmit
data-cy="login-button"
isSubmitting={isLoading}
label={onBack ? Tr(Next) : Tr(SignIn)}
className={classes.submit}
/>
</Box>
</Box>
</Slide>
)
}
Form.propTypes = {
onBack: PropTypes.func,
resolver: PropTypes.object,
fields: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string
})
),
onSubmit: PropTypes.func.isRequired,
error: PropTypes.string,
isLoading: PropTypes.bool,
transitionProps: PropTypes.shape({
name: PropTypes.string
})
}
Form.defaultProps = {
onBack: undefined,
onSubmit: () => undefined,
resolver: {},
fields: [],
error: undefined,
isLoading: false,
transitionProps: undefined
}
export default Form

View File

@ -1,78 +0,0 @@
import React from 'react'
import { func, string } from 'prop-types'
import { Box, Button, TextField } from '@material-ui/core'
import { useForm } from 'react-hook-form'
import { yupResolver } from '@hookform/resolvers'
import * as yup from 'yup'
import { Token2FA, Next } from 'client/constants/translates'
import loginStyles from 'client/containers/Login/styles'
import { Tr } from 'client/components/HOC'
import ButtonSubmit from 'client/components/FormControl/SubmitButton'
import ErrorHelper from 'client/components/FormControl/ErrorHelper'
const Form2fa = ({ onBack, onSubmit, error }) => {
const classes = loginStyles()
const { register, handleSubmit, errors } = useForm({
reValidateMode: 'onSubmit',
resolver: yupResolver(
yup.object().shape({
token2fa: yup.string().required('Authenticator is a required field')
})
)
})
const tokenError = Boolean(errors.token || error)
return (
<Box
component="form"
className={classes.form}
onSubmit={handleSubmit(onSubmit)}
>
<TextField
autoFocus
fullWidth
required
name="token2fa"
label={Tr(Token2FA)}
variant="outlined"
inputRef={register}
inputProps={{ 'data-cy': 'login-token' }}
error={tokenError}
helperText={
tokenError && <ErrorHelper label={errors.token?.message || error} />
}
FormHelperTextProps={{ 'data-cy': 'login-username-error' }}
/>
<Box>
<Button onClick={onBack} color="primary">
Back
</Button>
<ButtonSubmit
data-cy="login-2fa-button"
isSubmitting={false}
label={Tr(Next)}
className={classes.submit}
/>
</Box>
</Box>
)
}
Form2fa.propTypes = {
onBack: func.isRequired,
onSubmit: func.isRequired,
error: string
}
Form2fa.defaultProps = {
onBack: () => undefined,
onSubmit: () => undefined,
error: null
}
export default Form2fa

View File

@ -1,49 +0,0 @@
import React from 'react'
import { func } from 'prop-types'
import { Box, Button } from '@material-ui/core'
import { useForm, Controller } from 'react-hook-form'
import GroupSelect from 'client/components/FormControl/GroupSelect'
import ButtonSubmit from 'client/components/FormControl/SubmitButton'
import { Tr } from 'client/components/HOC'
import loginStyles from 'client/containers/Login/styles'
import { Next } from 'client/constants/translates'
function FormGroup ({ onBack, onSubmit }) {
const classes = loginStyles()
const { control, handleSubmit } = useForm()
return (
<Box
component="form"
className={classes.form}
onSubmit={handleSubmit(onSubmit)}
>
<Controller as={GroupSelect} name="group" control={control} />
<Button onClick={onBack}>Logout</Button>
<ButtonSubmit
data-cy="login-group-button"
isSubmitting={false}
label={Tr(Next)}
className={classes.submit}
/>
</Box>
)
}
FormGroup.propTypes = {
onBack: func.isRequired,
onSubmit: func.isRequired
}
FormGroup.defaultProps = {
onBack: () => undefined,
onSubmit: () => undefined
}
FormGroup.propTypes = {}
FormGroup.defaultProps = {}
export default FormGroup

View File

@ -1,105 +0,0 @@
import React from 'react'
import { func, string } from 'prop-types'
import { Box, Checkbox, TextField, FormControlLabel } from '@material-ui/core'
import { useForm } from 'react-hook-form'
import { yupResolver } from '@hookform/resolvers'
import * as yup from 'yup'
import {
SignIn,
Username,
Password,
KeepLoggedIn
} from 'client/constants/translates'
import { Tr } from 'client/components/HOC'
import ButtonSubmit from 'client/components/FormControl/SubmitButton'
import ErrorHelper from 'client/components/FormControl/ErrorHelper'
import loginStyles from 'client/containers/Login/styles'
function FormUser ({ onSubmit, error }) {
const classes = loginStyles()
const { register, handleSubmit, errors } = useForm({
reValidateMode: 'onSubmit',
resolver: yupResolver(
yup.object().shape({
user: yup.string().required('Username is a required field'),
token: yup.string().required('Password is a required field'),
remember: yup.boolean()
})
)
})
const userError = Boolean(errors.user || error)
const passError = Boolean(errors.pass)
return (
<Box
component="form"
className={classes.form}
onSubmit={handleSubmit(onSubmit)}
>
<TextField
autoFocus
fullWidth
required
name="user"
autoComplete="username"
label={Tr(Username)}
variant="outlined"
inputRef={register}
inputProps={{ 'data-cy': 'login-username' }}
error={userError}
helperText={
userError && <ErrorHelper label={errors.user?.message || error} />
}
FormHelperTextProps={{ 'data-cy': 'login-username-error' }}
/>
<TextField
fullWidth
required
name="token"
type="password"
autoComplete="current-password"
label={Tr(Password)}
variant="outlined"
inputRef={register}
inputProps={{ 'data-cy': 'login-password' }}
error={passError}
helperText={passError && <ErrorHelper label={errors.pass?.message} />}
FormHelperTextProps={{ 'data-cy': 'login-password-error' }}
/>
<FormControlLabel
control={
<Checkbox
name="remember"
defaultValue={false}
color="primary"
inputRef={register}
inputProps={{ 'data-cy': 'login-remember' }}
/>
}
label={Tr(KeepLoggedIn)}
labelPlacement="end"
/>
<ButtonSubmit
data-cy="login-button"
isSubmitting={false}
label={Tr(SignIn)}
className={classes.submit}
/>
</Box>
)
}
FormUser.propTypes = {
onSubmit: func.isRequired,
error: string
}
FormUser.defaultProps = {
onSubmit: () => undefined,
error: null
}
export default FormUser

View File

@ -1,23 +1,23 @@
import React, { useState } from 'react'
import {
Paper,
Box,
Container,
Slide,
LinearProgress,
useMediaQuery
} from '@material-ui/core'
import React, { useMemo, useState } from 'react'
import { Paper, Box, Container, LinearProgress, useMediaQuery } from '@material-ui/core'
import useAuth from 'client/hooks/useAuth'
import FormUser from 'client/containers/Login/Forms/FormUser'
import Form2fa from 'client/containers/Login/Forms/Form2fa'
import FormGroup from 'client/containers/Login/Forms/FormGroup'
import loginStyles from 'client/containers/Login/styles'
import Logo from 'client/icons/logo'
import { ONEADMIN_ID } from 'client/constants'
const STEP = {
import {
FORM_USER_FIELDS,
FORM_USER_SCHEMA,
FORM_2FA_FIELDS,
FORM_2FA_SCHEMA,
FORM_GROUP_FIELDS,
FORM_GROUP_SCHEMA
} from 'client/containers/Login/schema'
import Form from 'client/containers/Login/Form'
import loginStyles from 'client/containers/Login/styles'
const STEPS = {
USER_FORM: 0,
FA2_FORM: 1,
GROUP_FORM: 2
@ -27,7 +27,7 @@ function Login () {
const classes = loginStyles()
const isMobile = useMediaQuery(theme => theme.breakpoints.only('xs'))
const [user, setUser] = useState(undefined)
const [step, setStep] = useState(STEP.USER_FORM)
const [step, setStep] = useState(STEPS.USER_FORM)
const {
isLoading,
error,
@ -41,10 +41,10 @@ function Login () {
login({ ...user, ...dataForm }).then(data => {
if (data?.token) {
getAuthInfo().then(() => {
data?.id !== ONEADMIN_ID && setStep(STEP.GROUP_FORM)
data?.id !== ONEADMIN_ID && setStep(STEPS.GROUP_FORM)
})
} else {
setStep(data ? STEP.FA2_FORM : step)
setStep(data ? STEPS.FA2_FORM : step)
setUser(data ? dataForm : user)
}
})
@ -55,7 +55,7 @@ function Login () {
const handleBack = () => {
logout()
setUser(undefined)
setStep(STEP.USER_FORM)
setStep(STEPS.USER_FORM)
}
return (
@ -67,50 +67,46 @@ function Login () {
>
{isLoading && <LinearProgress className={classes.loading} />}
<Paper variant="outlined" className={classes.paper}>
<Logo width="100%" height={100} withText viewBox="140 140 380 360" />
<Box className={classes.wrapper}>
<Slide
direction="right"
timeout={{ enter: 400 }}
in={step === STEP.USER_FORM}
appear={false}
mountOnEnter
unmountOnExit
>
<Box style={{ opacity: isLoading ? 0.7 : 1 }}>
<FormUser onSubmit={handleSubmitUser} error={error} />
</Box>
</Slide>
</Box>
<Box>
<Slide
direction="left"
timeout={{ enter: 400 }}
in={step === STEP.FA2_FORM}
mountOnEnter
unmountOnExit
>
<Box style={{ opacity: isLoading ? 0.7 : 1 }}>
<Form2fa
onBack={handleBack}
onSubmit={handleSubmitUser}
error={error}
/>
</Box>
</Slide>
</Box>
<Box className={classes.wrapper}>
<Slide
direction="left"
timeout={{ enter: 400 }}
in={step === STEP.GROUP_FORM}
mountOnEnter
unmountOnExit
>
<Box style={{ opacity: isLoading ? 0.7 : 1 }}>
<FormGroup onBack={handleBack} onSubmit={handleSubmitGroup} />
</Box>
</Slide>
{useMemo(() => (
<Logo width="100%" height={100} withText viewBox="140 140 380 360" />
), [])}
<Box className={classes.wrapperForm}>
{step === STEPS.USER_FORM && <Form
transitionProps={{
direction: 'right',
in: step === STEPS.USER_FORM,
appear: false
}}
onSubmit={handleSubmitUser}
resolver={FORM_USER_SCHEMA}
fields={FORM_USER_FIELDS}
error={error}
isLoading={isLoading}
/>}
{step === STEPS.FA2_FORM && <Form
transitionProps={{
direction: 'left',
in: step === STEPS.FA2_FORM
}}
onBack={handleBack}
onSubmit={handleSubmitUser}
resolver={FORM_2FA_SCHEMA}
fields={FORM_2FA_FIELDS}
error={error}
isLoading={isLoading}
/>}
{step === STEPS.GROUP_FORM && <Form
transitionProps={{
direction: 'left',
in: step === STEPS.GROUP_FORM
}}
onBack={handleBack}
onSubmit={handleSubmitGroup}
resolver={FORM_GROUP_SCHEMA}
fields={FORM_GROUP_FIELDS}
error={error}
isLoading={isLoading}
/>}
</Box>
</Paper>
</Container>

View File

@ -0,0 +1,123 @@
import React from 'react'
import SelectedIcon from '@material-ui/icons/FilterVintage'
import * as yup from 'yup'
import useAuth from 'client/hooks/useAuth'
import useOpennebula from 'client/hooks/useOpennebula'
import { INPUT_TYPES, FILTER_POOL } from 'client/constants'
import { getValidationFromFields } from 'client/utils/helpers'
import {
Username,
Password,
KeepLoggedIn,
Token2FA,
SelectGroup,
ShowAll
} from 'client/constants/translates'
export const USERNAME = {
name: 'user',
label: Username,
type: INPUT_TYPES.TEXT,
validation: yup
.string()
.trim()
.required('Username is a required field')
.default(null),
grid: { md: 12 },
fieldProps: {
autoFocus: true,
required: true,
autoComplete: 'username',
variant: 'outlined'
}
}
export const PASSWORD = {
name: 'token',
label: Password,
type: INPUT_TYPES.TEXT,
htmlType: 'password',
validation: yup
.string()
.trim()
.required('Password is a required field')
.default(null),
grid: { md: 12 },
fieldProps: {
required: true,
autoComplete: 'current-password',
variant: 'outlined'
}
}
export const REMEMBER = {
name: 'remember',
label: KeepLoggedIn,
type: INPUT_TYPES.CHECKBOX,
validation: yup
.boolean()
.default(false),
grid: { md: 12 }
}
export const TOKEN = {
name: 'token2fa',
label: Token2FA,
type: INPUT_TYPES.TEXT,
validation: yup
.string()
.trim()
.required('Authenticator is a required field')
.default(null),
grid: { md: 12 },
fieldProps: {
autoFocus: true,
required: true,
variant: 'outlined'
}
}
export const GROUP = {
name: 'group',
label: SelectGroup,
type: INPUT_TYPES.SELECT,
values: () => {
const { authUser } = useAuth()
const { groups } = useOpennebula()
return [{ text: ShowAll, value: FILTER_POOL.ALL_RESOURCES }]
.concat(groups
.sort((a, b) => a.ID - b.ID)
.map(({ ID, NAME }) => ({
text: (
<>
{`${ID} - ${String(NAME)}`}
{authUser?.GID === ID && (
<SelectedIcon style={{ fontSize: '1rem', marginLeft: 16 }} />
)}
</>
),
value: String(ID)
}))
)
},
validation: yup
.string()
.trim()
.nullable()
.default(FILTER_POOL.ALL_RESOURCES),
grid: { md: 12 },
fieldProps: {
variant: 'outlined'
}
}
export const FORM_USER_FIELDS = [USERNAME, PASSWORD, REMEMBER]
export const FORM_2FA_FIELDS = [TOKEN]
export const FORM_GROUP_FIELDS = [GROUP]
export const FORM_USER_SCHEMA = yup.object(getValidationFromFields(FORM_USER_FIELDS))
export const FORM_2FA_SCHEMA = yup.object(getValidationFromFields(FORM_2FA_FIELDS))
export const FORM_GROUP_SCHEMA = yup.object(getValidationFromFields(FORM_GROUP_FIELDS))

View File

@ -13,7 +13,7 @@ export default makeStyles(theme =>
justifyContent: 'center',
height: '100vh'
},
loading: {
progress: {
height: 4,
width: '100%',
[theme.breakpoints.only('xs')]: {
@ -23,18 +23,35 @@ export default makeStyles(theme =>
},
paper: {
overflow: 'hidden',
padding: theme.spacing(3),
minHeight: 400,
padding: theme.spacing(2),
height: 440,
[theme.breakpoints.up('xs')]: {
display: 'flex',
flexDirection: 'column'
},
[theme.breakpoints.only('xs')]: {
border: 'none',
height: 'calc(100vh - 4px)',
backgroundColor: 'transparent'
}
},
wrapperForm: {
padding: theme.spacing(),
flexGrow: 1,
display: 'flex',
overflow: 'hidden'
},
form: {
width: '100%',
flexShrink: 0,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center'
[theme.breakpoints.up('xs')]: {
justifyContent: 'center'
}
},
loading: {
opacity: 0.7
},
helper: {
animation: '1s ease-out 0s 1'

View File

@ -1,7 +1,7 @@
import React from 'react'
import React, { memo } from 'react'
import { number, string, bool, oneOfType } from 'prop-types'
const Logo = ({ width, height, spinner, withText, viewBox, ...props }) => {
const Logo = memo(({ width, height, spinner, withText, viewBox, ...props }) => {
const cloudColor = {
child1: { from: '#0098c3', to: '#ffffff' },
child2: { from: '#0098c3', to: '#ffffff' },
@ -83,7 +83,7 @@ const Logo = ({ width, height, spinner, withText, viewBox, ...props }) => {
)}
</svg>
)
}
})
Logo.propTypes = {
width: oneOfType([number, string]).isRequired,
@ -101,4 +101,6 @@ Logo.defaultProps = {
withText: false
}
Logo.displayName = 'LogoOne'
export default Logo