|  |  |  | 
|---|
|  |  |  | import * as React from 'react'; | 
|---|
|  |  |  | import { useState } from 'react'; | 
|---|
|  |  |  | import React, { useState, useRef, useEffect, useMemo } from "react"; | 
|---|
|  |  |  | import { useLocation } from 'react-router-dom'; | 
|---|
|  |  |  | import { | 
|---|
|  |  |  | Avatar, | 
|---|
|  |  |  | Box, | 
|---|
|  |  |  | Button, | 
|---|
|  |  |  | Card, | 
|---|
|  |  |  | CardActions, | 
|---|
|  |  |  | CircularProgress, | 
|---|
|  |  |  | Typography, | 
|---|
|  |  |  | Button, | 
|---|
|  |  |  | TextField, | 
|---|
|  |  |  | Stack, | 
|---|
|  |  |  | Autocomplete, | 
|---|
|  |  |  | InputAdornment, | 
|---|
|  |  |  | IconButton, | 
|---|
|  |  |  | } from '@mui/material'; | 
|---|
|  |  |  | import LockIcon from '@mui/icons-material/Lock'; | 
|---|
|  |  |  | import { | 
|---|
|  |  |  | Form, | 
|---|
|  |  |  | required, | 
|---|
|  |  |  | TextInput, | 
|---|
|  |  |  | useTranslate, | 
|---|
|  |  |  | useLogin, | 
|---|
|  |  |  | useNotify, | 
|---|
|  |  |  | } from 'react-admin'; | 
|---|
|  |  |  | import { LOGIN_BACKGROUND } from '@/config/setting'; | 
|---|
|  |  |  | import { useForm, Controller, useWatch, FormProvider, useFormContext } from "react-hook-form"; | 
|---|
|  |  |  | import ProviderChoices from "./ProviderChoices"; | 
|---|
|  |  |  | import Visibility from '@mui/icons-material/Visibility'; | 
|---|
|  |  |  | import VisibilityOff from '@mui/icons-material/VisibilityOff'; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | const Login = () => { | 
|---|
|  |  |  | const [loading, setLoading] = useState(false); | 
|---|
|  |  |  | const Login = (props) => { | 
|---|
|  |  |  | const translate = useTranslate(); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | const notify = useNotify(); | 
|---|
|  |  |  | const login = useLogin(); | 
|---|
|  |  |  | const location = useLocation(); | 
|---|
|  |  |  | const { systemInfo: { mode }, tenantList } = props; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | const handleSubmit = (auth) => { | 
|---|
|  |  |  | const { control, handleSubmit, watch, setValue, getValues, setError, clearErrors } = useForm(); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | const [loading, setLoading] = useState(false); | 
|---|
|  |  |  | const [showPassword, setShowPassword] = useState(false); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | const username = watch('username'); | 
|---|
|  |  |  | const password = watch('password'); | 
|---|
|  |  |  | const tenantId = watch('tenantId'); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | useEffect(() => { | 
|---|
|  |  |  | if (tenantList.length > 0 && !tenantId) { | 
|---|
|  |  |  | const rememberTenantId = localStorage.getItem('remember_tenantId'); | 
|---|
|  |  |  | if (rememberTenantId && tenantList.some(t => t.id === Number(rememberTenantId))) { | 
|---|
|  |  |  | setValue('tenantId', Number(rememberTenantId)); | 
|---|
|  |  |  | } else { | 
|---|
|  |  |  | setValue('tenantId', tenantList[0].id); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  | }, [tenantList, setValue]); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | const onSubmit = (data) => { | 
|---|
|  |  |  | setLoading(true); | 
|---|
|  |  |  | // js native confirm && root | 
|---|
|  |  |  | login( | 
|---|
|  |  |  | auth, | 
|---|
|  |  |  | data, | 
|---|
|  |  |  | location.state ? (location.state).nextPathname : '/' | 
|---|
|  |  |  | ).catch((error) => { | 
|---|
|  |  |  | ).catch((res) => { | 
|---|
|  |  |  | setLoading(false); | 
|---|
|  |  |  | notify( | 
|---|
|  |  |  | typeof error === 'string' | 
|---|
|  |  |  | ? error | 
|---|
|  |  |  | : typeof error === 'undefined' || !error.message | 
|---|
|  |  |  | ? 'ra.auth.sign_in_error' | 
|---|
|  |  |  | : error.message, | 
|---|
|  |  |  | { | 
|---|
|  |  |  | type: 'error', | 
|---|
|  |  |  | messageArgs: { | 
|---|
|  |  |  | _: | 
|---|
|  |  |  | typeof error === 'string' | 
|---|
|  |  |  | ? error | 
|---|
|  |  |  | : error && error.message | 
|---|
|  |  |  | ? error.message | 
|---|
|  |  |  | : undefined, | 
|---|
|  |  |  | }, | 
|---|
|  |  |  | } | 
|---|
|  |  |  | ); | 
|---|
|  |  |  | const { code, msg, data } = res; | 
|---|
|  |  |  | if (code === 10003) { | 
|---|
|  |  |  | setError('username', { | 
|---|
|  |  |  | message: msg | 
|---|
|  |  |  | }) | 
|---|
|  |  |  | } else if (code === 10004) { | 
|---|
|  |  |  | setError('username', { | 
|---|
|  |  |  | message: msg | 
|---|
|  |  |  | }) | 
|---|
|  |  |  | } else if (code === 10001) { | 
|---|
|  |  |  | setError('password', { | 
|---|
|  |  |  | message: msg | 
|---|
|  |  |  | }) | 
|---|
|  |  |  | } else { | 
|---|
|  |  |  | notify(msg, { type: 'error', messageArgs: { _: msg } }); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | }); | 
|---|
|  |  |  | }; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | return ( | 
|---|
|  |  |  | <Form onSubmit={handleSubmit} noValidate> | 
|---|
|  |  |  | {/* https://unsplash.com/ */} | 
|---|
|  |  |  | <> | 
|---|
|  |  |  | <Box | 
|---|
|  |  |  | sx={{ | 
|---|
|  |  |  | display: 'flex', | 
|---|
|  |  |  | flexDirection: 'column', | 
|---|
|  |  |  | minHeight: '100vh', | 
|---|
|  |  |  | alignItems: 'center', | 
|---|
|  |  |  | justifyContent: 'flex-start', | 
|---|
|  |  |  | // justifyContent: 'center', | 
|---|
|  |  |  | background: `url(/login_bg.jpg)`, | 
|---|
|  |  |  | backgroundRepeat: 'no-repeat', | 
|---|
|  |  |  | backgroundSize: 'cover', | 
|---|
|  |  |  | }} | 
|---|
|  |  |  | p={2} | 
|---|
|  |  |  | display="flex" | 
|---|
|  |  |  | flexDirection='column' | 
|---|
|  |  |  | component="form" onSubmit={handleSubmit(onSubmit)} noValidate | 
|---|
|  |  |  | > | 
|---|
|  |  |  | <video | 
|---|
|  |  |  | autoPlay | 
|---|
|  |  |  | loop | 
|---|
|  |  |  | muted | 
|---|
|  |  |  | style={{ | 
|---|
|  |  |  | position: 'fixed', | 
|---|
|  |  |  | top: 0, | 
|---|
|  |  |  | left: 0, | 
|---|
|  |  |  | width: '100%', | 
|---|
|  |  |  | height: '100%', | 
|---|
|  |  |  | // objectFit: 'cover', | 
|---|
|  |  |  | // objectFit: 'contain', | 
|---|
|  |  |  | objectFit: 'fill', | 
|---|
|  |  |  | // objectFit: 'scale-down', | 
|---|
|  |  |  | zIndex: 0, | 
|---|
|  |  |  | }} | 
|---|
|  |  |  | > | 
|---|
|  |  |  | {LOGIN_BACKGROUND === 'media' && ( | 
|---|
|  |  |  | <source src="/login_bg.mp4" type="video/mp4" /> | 
|---|
|  |  |  | <Stack spacing={2}> | 
|---|
|  |  |  | {mode === 'OFFLINE' && ( | 
|---|
|  |  |  | <Controller | 
|---|
|  |  |  | name="tenantId" | 
|---|
|  |  |  | control={control} | 
|---|
|  |  |  | rules={{ required: true }} | 
|---|
|  |  |  | defaultValue={tenantList.length > 0 ? tenantList[0].id : ''} | 
|---|
|  |  |  | render={({ field: { onChange, value, ref } }) => { | 
|---|
|  |  |  | const selectedTenant = tenantList.find(tenant => tenant.id === value) || null; | 
|---|
|  |  |  | return ( | 
|---|
|  |  |  | <Autocomplete | 
|---|
|  |  |  | options={tenantList} | 
|---|
|  |  |  | getOptionLabel={(option) => option.name} | 
|---|
|  |  |  | value={selectedTenant} | 
|---|
|  |  |  | onChange={(_, newValue) => { | 
|---|
|  |  |  | const newTenantId = newValue ? newValue.id : ''; | 
|---|
|  |  |  | onChange(newTenantId); | 
|---|
|  |  |  | localStorage.setItem('remember_tenantId', newTenantId); | 
|---|
|  |  |  | }} | 
|---|
|  |  |  | renderInput={(params) => ( | 
|---|
|  |  |  | <TextField | 
|---|
|  |  |  | {...params} | 
|---|
|  |  |  | label={translate("page.login.tenant")} | 
|---|
|  |  |  | variant="standard" | 
|---|
|  |  |  | inputRef={ref} | 
|---|
|  |  |  | /> | 
|---|
|  |  |  | )} | 
|---|
|  |  |  | /> | 
|---|
|  |  |  | ); | 
|---|
|  |  |  | }} | 
|---|
|  |  |  | /> | 
|---|
|  |  |  | )} | 
|---|
|  |  |  | </video> | 
|---|
|  |  |  |  | 
|---|
|  |  |  | <Card sx={{ | 
|---|
|  |  |  | minWidth: 300, | 
|---|
|  |  |  | marginTop: '6em', | 
|---|
|  |  |  | zIndex: 1 | 
|---|
|  |  |  | }}> | 
|---|
|  |  |  | <Box | 
|---|
|  |  |  | sx={{ | 
|---|
|  |  |  | margin: '1em', | 
|---|
|  |  |  | display: 'flex', | 
|---|
|  |  |  | justifyContent: 'center', | 
|---|
|  |  |  | }} | 
|---|
|  |  |  | > | 
|---|
|  |  |  | <Avatar sx={{ bgcolor: 'secondary.main' }}> | 
|---|
|  |  |  | <LockIcon /> | 
|---|
|  |  |  | </Avatar> | 
|---|
|  |  |  | </Box> | 
|---|
|  |  |  | <Box | 
|---|
|  |  |  | sx={{ | 
|---|
|  |  |  | marginTop: '1em', | 
|---|
|  |  |  | display: 'flex', | 
|---|
|  |  |  | justifyContent: 'center', | 
|---|
|  |  |  | color: theme => theme.palette.grey[500], | 
|---|
|  |  |  | }} | 
|---|
|  |  |  | > | 
|---|
|  |  |  | Hint: root / 123456 | 
|---|
|  |  |  | </Box> | 
|---|
|  |  |  | <Box sx={{ padding: '0 1em 1em 1em' }}> | 
|---|
|  |  |  | <Box sx={{ marginTop: '1em' }}> | 
|---|
|  |  |  | <TextInput | 
|---|
|  |  |  | <Controller | 
|---|
|  |  |  | name="username" | 
|---|
|  |  |  | control={control} | 
|---|
|  |  |  | defaultValue="" | 
|---|
|  |  |  | rules={{ required: true }} | 
|---|
|  |  |  | render={({ field, fieldState: { error } }) => ( | 
|---|
|  |  |  | <TextField | 
|---|
|  |  |  | {...field} | 
|---|
|  |  |  | label={translate("page.login.username")} | 
|---|
|  |  |  | variant="standard" | 
|---|
|  |  |  | disabled={loading} | 
|---|
|  |  |  | autoFocus | 
|---|
|  |  |  | source="username" | 
|---|
|  |  |  | label={translate('ra.auth.username')} | 
|---|
|  |  |  | disabled={loading} | 
|---|
|  |  |  | validate={required()} | 
|---|
|  |  |  | autoComplete="off" | 
|---|
|  |  |  | error={!!error} | 
|---|
|  |  |  | helperText={error?.message || ""} | 
|---|
|  |  |  | /> | 
|---|
|  |  |  | </Box> | 
|---|
|  |  |  | <Box sx={{ marginTop: '1em' }}> | 
|---|
|  |  |  | <TextInput | 
|---|
|  |  |  | source="password" | 
|---|
|  |  |  | label={translate('ra.auth.password')} | 
|---|
|  |  |  | type="password" | 
|---|
|  |  |  | disabled={loading} | 
|---|
|  |  |  | validate={required()} | 
|---|
|  |  |  | /> | 
|---|
|  |  |  | </Box> | 
|---|
|  |  |  | </Box> | 
|---|
|  |  |  | <CardActions sx={{ padding: '0 1em 1em 1em' }}> | 
|---|
|  |  |  | <Button | 
|---|
|  |  |  | variant="contained" | 
|---|
|  |  |  | type="submit" | 
|---|
|  |  |  | color="primary" | 
|---|
|  |  |  | disabled={loading} | 
|---|
|  |  |  | fullWidth | 
|---|
|  |  |  | > | 
|---|
|  |  |  | {loading && ( | 
|---|
|  |  |  | <CircularProgress size={25} thickness={2} /> | 
|---|
|  |  |  | )} | 
|---|
|  |  |  | {translate('ra.auth.sign_in')} | 
|---|
|  |  |  | </Button> | 
|---|
|  |  |  | </CardActions> | 
|---|
|  |  |  | </Card> | 
|---|
|  |  |  | </Box> | 
|---|
|  |  |  | </Form> | 
|---|
|  |  |  | ); | 
|---|
|  |  |  | }; | 
|---|
|  |  |  | )} | 
|---|
|  |  |  | /> | 
|---|
|  |  |  |  | 
|---|
|  |  |  | export default Login; | 
|---|
|  |  |  | <Controller | 
|---|
|  |  |  | name="password" | 
|---|
|  |  |  | control={control} | 
|---|
|  |  |  | defaultValue="" | 
|---|
|  |  |  | rules={{ required: true }} | 
|---|
|  |  |  | render={({ field, fieldState: { error } }) => ( | 
|---|
|  |  |  | <TextField | 
|---|
|  |  |  | {...field} | 
|---|
|  |  |  | label={translate("page.login.password")} | 
|---|
|  |  |  | type={showPassword ? 'text' : 'password'} | 
|---|
|  |  |  | variant="standard" | 
|---|
|  |  |  | disabled={loading} | 
|---|
|  |  |  | autoComplete="off" | 
|---|
|  |  |  | error={!!error} | 
|---|
|  |  |  | helperText={error?.message || ""} | 
|---|
|  |  |  | InputProps={{ | 
|---|
|  |  |  | endAdornment: ( | 
|---|
|  |  |  | <InputAdornment position="end"> | 
|---|
|  |  |  | <IconButton | 
|---|
|  |  |  | aria-label="toggle password visibility" | 
|---|
|  |  |  | onClick={() => setShowPassword((show) => !show)} | 
|---|
|  |  |  | onMouseDown={(event) => { event.preventDefault() }} | 
|---|
|  |  |  | edge="end" | 
|---|
|  |  |  | > | 
|---|
|  |  |  | {showPassword ? <VisibilityOff /> : <Visibility />} | 
|---|
|  |  |  | </IconButton> | 
|---|
|  |  |  | </InputAdornment> | 
|---|
|  |  |  | ), | 
|---|
|  |  |  | }} | 
|---|
|  |  |  | /> | 
|---|
|  |  |  | )} | 
|---|
|  |  |  | /> | 
|---|
|  |  |  |  | 
|---|
|  |  |  | <Box /> | 
|---|
|  |  |  |  | 
|---|
|  |  |  | <Button | 
|---|
|  |  |  | type="submit" | 
|---|
|  |  |  | variant="contained" | 
|---|
|  |  |  | disabled={loading || !((mode === 'OFFLINE' ? tenantId : true) && username && password)} | 
|---|
|  |  |  | > | 
|---|
|  |  |  | {loading && <CircularProgress size={25} thickness={2} />} | 
|---|
|  |  |  | {translate('page.login.button.login')} | 
|---|
|  |  |  | </Button> | 
|---|
|  |  |  |  | 
|---|
|  |  |  | </Stack> | 
|---|
|  |  |  | {/* <Box mt={1} mb={1} sx={{ textAlign: 'center' }}>or</Box> */} | 
|---|
|  |  |  |  | 
|---|
|  |  |  | {/* <ProviderChoices type="LOG IN" /> */} | 
|---|
|  |  |  | </Box > | 
|---|
|  |  |  | </> | 
|---|
|  |  |  | ) | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | export default Login; | 
|---|