skyouc
2025-04-15 e80184f5d053bff9d0f7fdaade6c800bee84c7dd
rsf-admin/src/page/login/Register.jsx
@@ -7,64 +7,139 @@
    Button,
    TextField,
    Stack,
    Autocomplete
    Autocomplete,
    InputAdornment,
    IconButton,
} from '@mui/material';
import {
    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 { tab, 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 email = watch('email');
    const username = watch('username');
    const password = watch('password');
    const tenantId = watch('tenantId');
    const confirmPassword = watch('confirmPassword');
    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) => {
        console.log(data);
                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 (
@@ -77,45 +152,20 @@
            >
                <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 || ""}
                            />
                        )}
                    />
@@ -124,25 +174,155 @@
                        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')}
                                type="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">
                                            <IconButton
                                                aria-label="toggle password visibility"
                                                onClick={() => setShowPassword((show) => !show)}
                                                onMouseDown={(event) => { event.preventDefault() }}
                                                edge="end"
                                            >
                                                {showPassword ? <VisibilityOff /> : <Visibility />}
                                            </IconButton>
                                        </InputAdornment>
                                    ),
                                }}
                            />
                        )}
                    />
                    <Box mt={10}></Box>
                    <Controller
                        name="confirmPassword"
                        control={control}
                        defaultValue=""
                        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')}
                                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>
                                    ),
                                }}
                            />
                        )}
                    />
                    <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)}
                        disabled={loading || !(email && username && password && confirmPassword && code)}
                        sx={{
                            backgroundColor: "#3D4BA7"
                        }}
@@ -153,6 +333,8 @@
                </Stack>
                <Box mt={1} mb={1} sx={{ textAlign: 'center' }}>or</Box>
                <ProviderChoices type="REGISTER" />
            </Box >
        </>
    )