188 lines
5.7 KiB
TypeScript
188 lines
5.7 KiB
TypeScript
import { Button, Stack, TextField, Typography, Box } from '@mui/material';
|
|
import { useRef, useState, type Dispatch } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { isNumeric } from '@/utils/regexes/isNumeric';
|
|
import type { AuthFactory, AuthType } from '../../types/authTypes';
|
|
import { isEmail } from '@/utils/regexes/isEmail';
|
|
import { AuthenticationCard } from '../AuthenticationCard';
|
|
import { CountryCodeSelector } from '../CountryCodeSelector';
|
|
import type { LoginResult, UserStatus } from '../../types/userTypes';
|
|
import { getUserStatusByPhoneNumberOrEmail } from '../../api/authorizationAPI';
|
|
import type { CountryCode } from '@/types/commonTypes';
|
|
import { isPhoneNumber } from '@/utils/regexes/isValidPhoneNumber';
|
|
import { useToast } from '@rkheftan/harmony-ui';
|
|
import { useApi } from '@/hooks/useApi';
|
|
import type { GenerateTokenResponse } from '../../api/identityAPI';
|
|
import { GoogleAuthenticationV2 } from './GoogleAuthenticationV2';
|
|
import { replacePersianWithRealNumbers } from '@/utils/replacePersianWithRealNumbers';
|
|
|
|
export interface LoginRegisterFormProps {
|
|
loginRegisterValue: string;
|
|
setLoginRegisterValue: Dispatch<string>;
|
|
countryCode: CountryCode;
|
|
setCountryCode: Dispatch<CountryCode>;
|
|
authType: AuthType;
|
|
setAuthType: Dispatch<AuthType>;
|
|
onLoginRegisterSubmit: (value: string, userStatus: UserStatus) => void;
|
|
onGoogleAuthenticated: (
|
|
loginResult: LoginResult,
|
|
tokenResponse: GenerateTokenResponse,
|
|
) => void;
|
|
authFactory: AuthFactory;
|
|
}
|
|
|
|
export function LoginRegisterForm({
|
|
loginRegisterValue,
|
|
setLoginRegisterValue,
|
|
countryCode,
|
|
setCountryCode,
|
|
authType,
|
|
setAuthType,
|
|
onLoginRegisterSubmit,
|
|
onGoogleAuthenticated,
|
|
authFactory,
|
|
}: LoginRegisterFormProps) {
|
|
const { t } = useTranslation('authentication');
|
|
const textFieldRef = useRef<HTMLDivElement>(null);
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
const [error, setError] = useState<string>();
|
|
const [touched, setTouched] = useState<boolean>(false);
|
|
const inputError: boolean = touched && !!error;
|
|
const toast = useToast();
|
|
const { loading: userStatusLoading, execute: execUserStatus } = useApi(
|
|
getUserStatusByPhoneNumberOrEmail,
|
|
);
|
|
|
|
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
let newValue = event.target.value;
|
|
newValue = replacePersianWithRealNumbers(newValue);
|
|
if (newValue.startsWith('09')) {
|
|
newValue = newValue.substring(1);
|
|
}
|
|
|
|
setLoginRegisterValue(newValue);
|
|
|
|
// If the new value contains only digits (or is empty), it's a phone number
|
|
if (isNumeric(newValue)) {
|
|
setAuthType('phone');
|
|
} else {
|
|
setAuthType('email');
|
|
}
|
|
};
|
|
|
|
const handleBlur = () => {
|
|
setTouched(true);
|
|
validateInput(loginRegisterValue, authType);
|
|
};
|
|
|
|
const validateInput = (
|
|
value: string,
|
|
authType: AuthType,
|
|
setErrors: boolean = true,
|
|
): boolean => {
|
|
if (!value) {
|
|
if (setErrors) setError(t('loginForm.thisFieldIsRequired'));
|
|
return false;
|
|
}
|
|
|
|
if (authType === 'email' && !isEmail(value)) {
|
|
if (setErrors) setError(t('loginForm.emailIsInvalid'));
|
|
return false;
|
|
}
|
|
|
|
if (authType === 'phone' && !isPhoneNumber(countryCode, value)) {
|
|
if (setErrors) setError(t('loginForm.phoneNumberIsInvalid'));
|
|
return false;
|
|
}
|
|
|
|
if (setErrors) setError(undefined);
|
|
return true;
|
|
};
|
|
|
|
const handleSubmit = async () => {
|
|
if (validateInput(loginRegisterValue, authType, false)) {
|
|
const res = await execUserStatus({
|
|
phoneNumber:
|
|
authType === 'phone' ? countryCode + loginRegisterValue : undefined,
|
|
email: authType === 'email' ? loginRegisterValue : undefined,
|
|
});
|
|
|
|
if (!res) {
|
|
return;
|
|
}
|
|
|
|
if (res.success) {
|
|
onLoginRegisterSubmit(loginRegisterValue, res.userStatus);
|
|
} else {
|
|
toast({ message: res.message, severity: 'error' });
|
|
}
|
|
} else {
|
|
inputRef.current?.focus();
|
|
validateInput(loginRegisterValue, authType);
|
|
}
|
|
};
|
|
|
|
const showAdornment = authType === 'phone' && loginRegisterValue.length > 0;
|
|
|
|
return (
|
|
<AuthenticationCard>
|
|
<Box
|
|
component="form"
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
if (!userStatusLoading) {
|
|
void handleSubmit();
|
|
}
|
|
}}
|
|
>
|
|
<Stack spacing={1}>
|
|
<Typography variant="h5">{t('loginForm.title')}</Typography>
|
|
<Typography variant="body2" color="text.secondary">
|
|
{t('loginForm.description')}
|
|
</Typography>
|
|
</Stack>
|
|
|
|
<TextField
|
|
ref={textFieldRef}
|
|
inputRef={inputRef}
|
|
label={t('loginForm.emailOrPhoneLabel')}
|
|
value={loginRegisterValue}
|
|
onChange={handleInputChange}
|
|
onBlur={handleBlur}
|
|
error={inputError}
|
|
helperText={inputError ? error : ''}
|
|
autoFocus
|
|
slotProps={{
|
|
htmlInput: { dir: 'auto', sx: { lineHeight: 1.5 } },
|
|
input: {
|
|
endAdornment: (
|
|
<CountryCodeSelector
|
|
value={countryCode}
|
|
onChange={setCountryCode}
|
|
show={showAdornment}
|
|
menuAnchor={textFieldRef.current}
|
|
onCloseFocusRef={inputRef}
|
|
/>
|
|
),
|
|
},
|
|
}}
|
|
sx={{ my: 4 }}
|
|
/>
|
|
|
|
<Stack spacing={2}>
|
|
<Button loading={userStatusLoading} type="submit">
|
|
{t('loginForm.submitButton')}
|
|
</Button>
|
|
|
|
<GoogleAuthenticationV2
|
|
authFactory={authFactory}
|
|
onGoogleAuthenticated={onGoogleAuthenticated}
|
|
disabled={userStatusLoading}
|
|
/>
|
|
</Stack>
|
|
</Box>
|
|
</AuthenticationCard>
|
|
);
|
|
}
|