|  |  |  | 
|---|
|  |  |  | useTranslate, | 
|---|
|  |  |  | useLogin, | 
|---|
|  |  |  | useNotify, | 
|---|
|  |  |  | email as validEmail, | 
|---|
|  |  |  | } from 'react-admin'; | 
|---|
|  |  |  | import { useForm, Controller } from 'react-hook-form'; | 
|---|
|  |  |  | import ProviderChoices from "./ProviderChoices"; | 
|---|
|  |  |  | import Visibility from '@mui/icons-material/Visibility'; | 
|---|
|  |  |  | import VisibilityOff from '@mui/icons-material/VisibilityOff'; | 
|---|
|  |  |  | import { sendEmailCode, register } from '@/api/auth'; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | const Register = (props) => { | 
|---|
|  |  |  | const translate = useTranslate(); | 
|---|
|  |  |  | const notify = useNotify(); | 
|---|
|  |  |  | const login = useLogin(); | 
|---|
|  |  |  | const location = useLocation(); | 
|---|
|  |  |  | const { systemInfo, tenantList } = props; | 
|---|
|  |  |  | const { systemInfo } = props; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | const { control, watch, handleSubmit, setValue } = useForm(); | 
|---|
|  |  |  | const { control, watch, handleSubmit, setValue, setError, clearErrors } = useForm(); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | const [loading, setLoading] = useState(false); | 
|---|
|  |  |  | const [showPassword, setShowPassword] = useState(true); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | const email = watch('email'); | 
|---|
|  |  |  | const username = watch('username'); | 
|---|
|  |  |  | const password = watch('password'); | 
|---|
|  |  |  | const confirmPassword = watch('confirmPassword'); | 
|---|
|  |  |  | const tenantId = watch('tenantId'); | 
|---|
|  |  |  | const code = watch('code'); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | useEffect(() => { | 
|---|
|  |  |  | if (tenantList.length > 0 && !tenantId) { | 
|---|
|  |  |  | setValue('tenantId', tenantList[0].id); | 
|---|
|  |  |  | const [loading, setLoading] = useState(false); | 
|---|
|  |  |  | const [codeLoading, setCodeLoading] = useState(false); | 
|---|
|  |  |  | const [showPassword, setShowPassword] = useState(true); | 
|---|
|  |  |  | const [isCounting, setIsCounting] = useState(false); | 
|---|
|  |  |  | const [countdown, setCountdown] = useState(60); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // send code | 
|---|
|  |  |  | const handleSendCode = async () => { | 
|---|
|  |  |  | if (!email) { | 
|---|
|  |  |  | setError("email", { | 
|---|
|  |  |  | message: translate('ra.validation.required') | 
|---|
|  |  |  | }) | 
|---|
|  |  |  | return; | 
|---|
|  |  |  | } | 
|---|
|  |  |  | }, [tenantList, setValue]); | 
|---|
|  |  |  | const emailError = validEmail()(email); | 
|---|
|  |  |  | if (emailError) { | 
|---|
|  |  |  | setError("email", { | 
|---|
|  |  |  | message: translate("ra.validation.email") | 
|---|
|  |  |  | }) | 
|---|
|  |  |  | return; | 
|---|
|  |  |  | } | 
|---|
|  |  |  | clearErrors("email"); | 
|---|
|  |  |  | setCodeLoading(true); | 
|---|
|  |  |  | sendEmailCode({ email }).then(res => { | 
|---|
|  |  |  | setCodeLoading(false); | 
|---|
|  |  |  | const { code, msg, data } = res; | 
|---|
|  |  |  | if (code === 200) { | 
|---|
|  |  |  | notify(msg, { type: 'success', messageArgs: { _: msg } }); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | const onSubmit = (data) => { | 
|---|
|  |  |  | notify("Registration is not open yet"); | 
|---|
|  |  |  | return; | 
|---|
|  |  |  | const timestamp = Math.floor(Date.now() / 1000); | 
|---|
|  |  |  | const expirationTime = timestamp + 60; | 
|---|
|  |  |  | localStorage.setItem('codeExpirationTime', expirationTime); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | setIsCounting(true); | 
|---|
|  |  |  | setCountdown(60); | 
|---|
|  |  |  | } else if (code === 10005 || code === 10006) { | 
|---|
|  |  |  | setError('email', { | 
|---|
|  |  |  | message: msg | 
|---|
|  |  |  | }) | 
|---|
|  |  |  | } else { | 
|---|
|  |  |  | notify(msg, { type: 'error', messageArgs: { _: msg } }); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | }).catch((error) => { | 
|---|
|  |  |  | setCodeLoading(false); | 
|---|
|  |  |  | notify(error.message, { type: 'error', messageArgs: { _: error.message } }); | 
|---|
|  |  |  | console.error(error); | 
|---|
|  |  |  | }) | 
|---|
|  |  |  | }; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // countdown | 
|---|
|  |  |  | useEffect(() => { | 
|---|
|  |  |  | const codeExpirationTime = localStorage.getItem('codeExpirationTime'); | 
|---|
|  |  |  | if (codeExpirationTime) { | 
|---|
|  |  |  | const currentTimestamp = Math.floor(Date.now() / 1000); | 
|---|
|  |  |  | const remainingTime = codeExpirationTime - currentTimestamp; | 
|---|
|  |  |  | if (remainingTime > 0) { | 
|---|
|  |  |  | setCountdown(remainingTime); | 
|---|
|  |  |  | setIsCounting(true); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | const interval = setInterval(() => { | 
|---|
|  |  |  | if (isCounting && countdown > 0) { | 
|---|
|  |  |  | setCountdown(prev => prev - 1); | 
|---|
|  |  |  | } else if (countdown <= 0) { | 
|---|
|  |  |  | clearInterval(interval); | 
|---|
|  |  |  | setIsCounting(false); | 
|---|
|  |  |  | localStorage.removeItem('codeExpirationTime'); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | }, 1000); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | return () => clearInterval(interval); | 
|---|
|  |  |  | }, [countdown, isCounting]); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // register | 
|---|
|  |  |  | const onSubmit = (params) => { | 
|---|
|  |  |  | setLoading(true); | 
|---|
|  |  |  | // js native confirm && root | 
|---|
|  |  |  | login( | 
|---|
|  |  |  | data, | 
|---|
|  |  |  | location.state ? (location.state).nextPathname : '/' | 
|---|
|  |  |  | ).catch((error) => { | 
|---|
|  |  |  | register(params).then(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 === 200) { | 
|---|
|  |  |  | notify(msg, { type: 'success', messageArgs: { _: msg } }); | 
|---|
|  |  |  | // to login | 
|---|
|  |  |  | login( | 
|---|
|  |  |  | params, | 
|---|
|  |  |  | location.state ? (location.state).nextPathname : '/' | 
|---|
|  |  |  | ).catch(({ code, msg }) => { | 
|---|
|  |  |  | setLoading(false); | 
|---|
|  |  |  | notify(msg, { type: 'error', messageArgs: { _: msg } }); | 
|---|
|  |  |  | }); | 
|---|
|  |  |  | } else if (code === 10002) { | 
|---|
|  |  |  | setError("username", { | 
|---|
|  |  |  | message: msg | 
|---|
|  |  |  | }) | 
|---|
|  |  |  | } else { | 
|---|
|  |  |  | notify(msg, { type: 'error', messageArgs: { _: msg } }); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | }).catch((error) => { | 
|---|
|  |  |  | setLoading(false); | 
|---|
|  |  |  | notify(error.message, { type: 'error', messageArgs: { _: error.message } }); | 
|---|
|  |  |  | console.error(error); | 
|---|
|  |  |  | }) | 
|---|
|  |  |  | }; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | return ( | 
|---|
|  |  |  | 
|---|
|  |  |  | > | 
|---|
|  |  |  | <Stack spacing={2}> | 
|---|
|  |  |  | <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) => { | 
|---|
|  |  |  | onChange(newValue ? newValue.id : ''); | 
|---|
|  |  |  | }} | 
|---|
|  |  |  | renderInput={(params) => ( | 
|---|
|  |  |  | <TextField | 
|---|
|  |  |  | {...params} | 
|---|
|  |  |  | label={translate("page.login.tenant")} | 
|---|
|  |  |  | variant="standard" | 
|---|
|  |  |  | inputRef={ref} | 
|---|
|  |  |  | /> | 
|---|
|  |  |  | )} | 
|---|
|  |  |  | /> | 
|---|
|  |  |  | ); | 
|---|
|  |  |  | }} | 
|---|
|  |  |  | /> | 
|---|
|  |  |  |  | 
|---|
|  |  |  | <Controller | 
|---|
|  |  |  | name="username" | 
|---|
|  |  |  | control={control} | 
|---|
|  |  |  | defaultValue="" | 
|---|
|  |  |  | rules={{ required: true }} | 
|---|
|  |  |  | render={({ field }) => ( | 
|---|
|  |  |  | render={({ field, fieldState: { error } }) => ( | 
|---|
|  |  |  | <TextField | 
|---|
|  |  |  | {...field} | 
|---|
|  |  |  | label={translate('ra.auth.username')} | 
|---|
|  |  |  | label={translate("page.login.username")} | 
|---|
|  |  |  | variant="standard" | 
|---|
|  |  |  | disabled={loading} | 
|---|
|  |  |  | autoFocus | 
|---|
|  |  |  | autoComplete="off" | 
|---|
|  |  |  | error={!!error} | 
|---|
|  |  |  | helperText={error?.message || ""} | 
|---|
|  |  |  | /> | 
|---|
|  |  |  | )} | 
|---|
|  |  |  | /> | 
|---|
|  |  |  | 
|---|
|  |  |  | name="password" | 
|---|
|  |  |  | control={control} | 
|---|
|  |  |  | defaultValue="" | 
|---|
|  |  |  | rules={{ required: true }} | 
|---|
|  |  |  | render={({ field }) => ( | 
|---|
|  |  |  | rules={{ | 
|---|
|  |  |  | required: translate('ra.validation.required'), | 
|---|
|  |  |  | pattern: { | 
|---|
|  |  |  | value: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d\.]{6,13}$/, | 
|---|
|  |  |  | message: translate('page.settings.resetPwd.tip.pwdInputLimit'), | 
|---|
|  |  |  | }, | 
|---|
|  |  |  | }} | 
|---|
|  |  |  | render={({ field, fieldState: { error } }) => ( | 
|---|
|  |  |  | <TextField | 
|---|
|  |  |  | {...field} | 
|---|
|  |  |  | label={translate('ra.auth.password')} | 
|---|
|  |  |  | 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"> | 
|---|
|  |  |  | 
|---|
|  |  |  | name="confirmPassword" | 
|---|
|  |  |  | control={control} | 
|---|
|  |  |  | defaultValue="" | 
|---|
|  |  |  | rules={{ required: true }} | 
|---|
|  |  |  | render={({ field }) => ( | 
|---|
|  |  |  | rules={{ | 
|---|
|  |  |  | required: translate('ra.validation.required'), | 
|---|
|  |  |  | validate: value => | 
|---|
|  |  |  | value === password || translate('page.settings.resetPwd.tip.pwdNotMatch'), | 
|---|
|  |  |  | }} | 
|---|
|  |  |  | render={({ field, fieldState: { error } }) => ( | 
|---|
|  |  |  | <TextField | 
|---|
|  |  |  | {...field} | 
|---|
|  |  |  | label={translate('page.login.confirmPwd')} | 
|---|
|  |  |  | 
|---|
|  |  |  | variant="standard" | 
|---|
|  |  |  | disabled={loading} | 
|---|
|  |  |  | autoComplete="off" | 
|---|
|  |  |  | error={!!error} | 
|---|
|  |  |  | helperText={error?.message || ""} | 
|---|
|  |  |  | InputProps={{ | 
|---|
|  |  |  | endAdornment: ( | 
|---|
|  |  |  | <InputAdornment position="end"> | 
|---|
|  |  |  | 
|---|
|  |  |  | )} | 
|---|
|  |  |  | /> | 
|---|
|  |  |  |  | 
|---|
|  |  |  | <Controller | 
|---|
|  |  |  | name="email" | 
|---|
|  |  |  | control={control} | 
|---|
|  |  |  | defaultValue="" | 
|---|
|  |  |  | rules={{ | 
|---|
|  |  |  | required: translate('ra.validation.required'), | 
|---|
|  |  |  | pattern: { | 
|---|
|  |  |  | value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, | 
|---|
|  |  |  | message: translate("ra.validation.email"), | 
|---|
|  |  |  | }, | 
|---|
|  |  |  | }} | 
|---|
|  |  |  | render={({ field, fieldState: { error } }) => ( | 
|---|
|  |  |  | <TextField | 
|---|
|  |  |  | {...field} | 
|---|
|  |  |  | label={translate('page.login.email')} | 
|---|
|  |  |  | variant="standard" | 
|---|
|  |  |  | disabled={loading} | 
|---|
|  |  |  | // autoComplete="off" | 
|---|
|  |  |  | error={!!error} | 
|---|
|  |  |  | helperText={error ? error.message : ""} | 
|---|
|  |  |  | /> | 
|---|
|  |  |  | )} | 
|---|
|  |  |  | /> | 
|---|
|  |  |  |  | 
|---|
|  |  |  | <Box display="flex" alignItems="center" justifyContent='center' width="100%"> | 
|---|
|  |  |  | <Controller | 
|---|
|  |  |  | name="code" | 
|---|
|  |  |  | control={control} | 
|---|
|  |  |  | defaultValue="" | 
|---|
|  |  |  | rules={{ | 
|---|
|  |  |  | required: translate('ra.validation.required'), | 
|---|
|  |  |  | }} | 
|---|
|  |  |  | render={({ field, fieldState: { error } }) => ( | 
|---|
|  |  |  | <TextField | 
|---|
|  |  |  | {...field} | 
|---|
|  |  |  | label={translate('page.login.code')} | 
|---|
|  |  |  | variant="standard" | 
|---|
|  |  |  | disabled={loading} | 
|---|
|  |  |  | autoComplete="off" | 
|---|
|  |  |  | error={!!error} | 
|---|
|  |  |  | helperText={error ? error.message : ""} | 
|---|
|  |  |  | sx={{ | 
|---|
|  |  |  | width: '65%', | 
|---|
|  |  |  | mr: 2, | 
|---|
|  |  |  | }} | 
|---|
|  |  |  | /> | 
|---|
|  |  |  | )} | 
|---|
|  |  |  | /> | 
|---|
|  |  |  |  | 
|---|
|  |  |  | <Button | 
|---|
|  |  |  | variant="outlined" | 
|---|
|  |  |  | onClick={handleSendCode} | 
|---|
|  |  |  | disabled={codeLoading || isCounting} | 
|---|
|  |  |  | sx={{ | 
|---|
|  |  |  | width: '35%', | 
|---|
|  |  |  | mt: 1, | 
|---|
|  |  |  | whiteSpace: 'nowrap', | 
|---|
|  |  |  | }} | 
|---|
|  |  |  | > | 
|---|
|  |  |  | {codeLoading ? ( | 
|---|
|  |  |  | <CircularProgress size={20} color="primary" sx={{ marginRight: 1 }} /> | 
|---|
|  |  |  | ) : | 
|---|
|  |  |  | isCounting ? ( | 
|---|
|  |  |  | `${countdown}s` | 
|---|
|  |  |  | ) : ( | 
|---|
|  |  |  | translate('page.login.button.code') | 
|---|
|  |  |  | ) | 
|---|
|  |  |  | } | 
|---|
|  |  |  | </Button> | 
|---|
|  |  |  | </Box> | 
|---|
|  |  |  |  | 
|---|
|  |  |  | <Box /> | 
|---|
|  |  |  |  | 
|---|
|  |  |  | <Button | 
|---|
|  |  |  | type="submit" | 
|---|
|  |  |  | variant="contained" | 
|---|
|  |  |  | disabled={loading || !(tenantId && username && password && confirmPassword)} | 
|---|
|  |  |  | disabled={loading || !(email && username && password && confirmPassword && code)} | 
|---|
|  |  |  | sx={{ | 
|---|
|  |  |  | backgroundColor: "#3D4BA7" | 
|---|
|  |  |  | }} | 
|---|