fix: ui bugs
This commit is contained in:
2
.env
2
.env
@@ -1,5 +1,5 @@
|
|||||||
VITE_GOOGLE_CLIENT_ID=272098283932-bft2gvlgjn8edopg0lnqjq1i9ekdmipt.apps.googleusercontent.com
|
VITE_GOOGLE_CLIENT_ID=272098283932-bft2gvlgjn8edopg0lnqjq1i9ekdmipt.apps.googleusercontent.com
|
||||||
VITE_DEFUALT_AUTH_RETURN_URL=/setting/profile
|
VITE_DEFAULT_AUTH_RETURN_URL=/setting/profile
|
||||||
VITE_APP_URL=https://accounts.business-harmony.com
|
VITE_APP_URL=https://accounts.business-harmony.com
|
||||||
VITE_API_URL=https://accounts.business-harmony.com/api
|
VITE_API_URL=https://accounts.business-harmony.com/api
|
||||||
VITE_IDENTITY_URL=https://accounts.business-harmony.com/connect/token
|
VITE_IDENTITY_URL=https://accounts.business-harmony.com/connect/token
|
||||||
|
|||||||
@@ -58,4 +58,4 @@
|
|||||||
"typescript-eslint": "^8.34.1",
|
"typescript-eslint": "^8.34.1",
|
||||||
"vite": "^7.0.0"
|
"vite": "^7.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,8 +9,8 @@
|
|||||||
"phoneNumberIsInvalid": "Phone number is invalid",
|
"phoneNumberIsInvalid": "Phone number is invalid",
|
||||||
"thisFieldIsRequired": "This field is required",
|
"thisFieldIsRequired": "This field is required",
|
||||||
"googleAuthenticationFailed": "Login with google failed",
|
"googleAuthenticationFailed": "Login with google failed",
|
||||||
"persian": "Persian(Fa)",
|
"persian": "Persian (Fa)",
|
||||||
"english": "English(En)",
|
"english": "English (En)",
|
||||||
"accountInfo": "Harmony Account - 2025"
|
"accountInfo": "Harmony Account - 2025"
|
||||||
},
|
},
|
||||||
"verify": {
|
"verify": {
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
"phoneNumberIsInvalid": "شماره وارد شده نامعتبر میباشد",
|
"phoneNumberIsInvalid": "شماره وارد شده نامعتبر میباشد",
|
||||||
"thisFieldIsRequired": "این فیلد الزامی است",
|
"thisFieldIsRequired": "این فیلد الزامی است",
|
||||||
"googleAuthenticationFailed": "ورود با گوگل با خطا مواجه شد",
|
"googleAuthenticationFailed": "ورود با گوگل با خطا مواجه شد",
|
||||||
"persian": "فارسی(Fa)",
|
"persian": "فارسی (Fa)",
|
||||||
"english": "انگلیسی(En)",
|
"english": "انگلیسی (En)",
|
||||||
"accountInfo": "۱۴۰۴-هارمونی اکانت"
|
"accountInfo": "۱۴۰۴-هارمونی اکانت"
|
||||||
},
|
},
|
||||||
"verify": {
|
"verify": {
|
||||||
|
|||||||
@@ -57,10 +57,20 @@ const DigitInput: React.FC<DigitInputProps> = ({
|
|||||||
event: KeyboardEvent<HTMLDivElement>,
|
event: KeyboardEvent<HTMLDivElement>,
|
||||||
index: number,
|
index: number,
|
||||||
) => {
|
) => {
|
||||||
if (event.key === 'Backspace' && code[index]) {
|
if (event.key === 'Backspace') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
handleChange('', index);
|
|
||||||
if (index > 0) inputRefs.current[index - 1]?.focus();
|
if (code[index]) {
|
||||||
|
// We clear the current value.
|
||||||
|
handleChange('', index);
|
||||||
|
} else if (index > 0) {
|
||||||
|
// We move focus to the previous input and SELECT its content.
|
||||||
|
const prevInput = inputRefs.current[index - 1];
|
||||||
|
if (prevInput) {
|
||||||
|
prevInput.focus();
|
||||||
|
prevInput.select();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import type {
|
|||||||
SendEmailOtpRequest,
|
SendEmailOtpRequest,
|
||||||
SendForgetPassCodeRequest,
|
SendForgetPassCodeRequest,
|
||||||
SendSmsOtpRequest,
|
SendSmsOtpRequest,
|
||||||
|
SendSmsOtpResponse,
|
||||||
} from '../types/userTypes';
|
} from '../types/userTypes';
|
||||||
import apiClient from '@/lib/apiClient';
|
import apiClient from '@/lib/apiClient';
|
||||||
|
|
||||||
@@ -39,15 +40,29 @@ export const loginWithPassword = async (body: PasswordLoginRequest) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const sendSmsOtp = async (body: SendSmsOtpRequest) => {
|
export const sendSmsOtp = async (body: SendSmsOtpRequest) => {
|
||||||
return apiClient.post<ApiResponse>('User/SendSmsOtp', body);
|
return apiClient.post<SendSmsOtpResponse>('User/SendSmsOtp', body);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sendEmailOtp = async (body: SendEmailOtpRequest) => {
|
export const sendEmailOtp = async (body: SendEmailOtpRequest) => {
|
||||||
return apiClient.post<ApiResponse>('User/SendEmailOtp', body);
|
return apiClient.post<ApiResponse>('User/SendEmailOtp', body);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const confirmSmsOtp = async (body: ConfirmSmsOtpRequest) => {
|
export const sendSmsCodeCompleteUserInforamation = async (
|
||||||
return apiClient.post<ConfirmOtpResponse>('User/ConfirmSmsOtp', body);
|
body: SendSmsOtpRequest,
|
||||||
|
) => {
|
||||||
|
return apiClient.post<SendSmsOtpResponse>(
|
||||||
|
'User/SendSmsCodeCompleteUserInforamation',
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const confirmSmsCodeCompleteUserInforamation = async (
|
||||||
|
body: ConfirmSmsOtpRequest,
|
||||||
|
) => {
|
||||||
|
return apiClient.post<ConfirmOtpResponse>(
|
||||||
|
'User/ConfirmSmsCodeCompleteUserInforamation',
|
||||||
|
body,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const confirmEmailOtp = async (body: ConfirmEmailOtpRequest) => {
|
export const confirmEmailOtp = async (body: ConfirmEmailOtpRequest) => {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ export interface AuthenticationCardProps extends PropsWithChildren {
|
|||||||
sx?: SxProps<Theme>;
|
sx?: SxProps<Theme>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Beacuse in the otp verify there is a element outside of the authentication card
|
|
||||||
export const AuthenticationCard = ({
|
export const AuthenticationCard = ({
|
||||||
children,
|
children,
|
||||||
maxWidth,
|
maxWidth,
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export const AuthenticationSteps = (): JSX.Element => {
|
|||||||
useState<string>('');
|
useState<string>('');
|
||||||
const [memoryTokenRes, setMemoryTokenRes] = useState<GenerateTokenResponse>();
|
const [memoryTokenRes, setMemoryTokenRes] = useState<GenerateTokenResponse>();
|
||||||
const [hasPassword, setHasPassword] = useState(false);
|
const [hasPassword, setHasPassword] = useState(false);
|
||||||
|
const [timerValue, setTimerValue] = useState(120);
|
||||||
const { login } = useAuth();
|
const { login } = useAuth();
|
||||||
|
|
||||||
const authFactory: AuthFactory = useMemo(() => {
|
const authFactory: AuthFactory = useMemo(() => {
|
||||||
@@ -66,8 +67,13 @@ export const AuthenticationSteps = (): JSX.Element => {
|
|||||||
return resFactory;
|
return resFactory;
|
||||||
}, [searchParams]);
|
}, [searchParams]);
|
||||||
|
|
||||||
const handleLoginRegister = (value: string, userStatus: UserStatus) => {
|
const handleLoginRegister = (
|
||||||
|
value: string,
|
||||||
|
userStatus: UserStatus,
|
||||||
|
timerValue: number,
|
||||||
|
) => {
|
||||||
setAuthType(isNumeric(value) ? 'phone' : 'email');
|
setAuthType(isNumeric(value) ? 'phone' : 'email');
|
||||||
|
setTimerValue(timerValue);
|
||||||
|
|
||||||
switch (userStatus) {
|
switch (userStatus) {
|
||||||
case UserStatus.NotRegistered:
|
case UserStatus.NotRegistered:
|
||||||
@@ -131,7 +137,7 @@ export const AuthenticationSteps = (): JSX.Element => {
|
|||||||
|
|
||||||
const redirectToReturnUrl = (refreshToken: string) => {
|
const redirectToReturnUrl = (refreshToken: string) => {
|
||||||
if (authFactory.isCurrentApplication()) {
|
if (authFactory.isCurrentApplication()) {
|
||||||
navigate(import.meta.env.VITE_DEFUALT_AUTH_RETURN_URL);
|
navigate(import.meta.env.VITE_DEFAULT_AUTH_RETURN_URL);
|
||||||
} else {
|
} else {
|
||||||
if (authMode === 'register') {
|
if (authMode === 'register') {
|
||||||
navigate(
|
navigate(
|
||||||
@@ -169,6 +175,7 @@ export const AuthenticationSteps = (): JSX.Element => {
|
|||||||
authType={authType}
|
authType={authType}
|
||||||
value={loginRegisterValue}
|
value={loginRegisterValue}
|
||||||
hasPassword={hasPassword}
|
hasPassword={hasPassword}
|
||||||
|
initialTimerValue={timerValue}
|
||||||
onLoginWithPassword={() => setCurrentStep('enterPassword')}
|
onLoginWithPassword={() => setCurrentStep('enterPassword')}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -194,6 +201,7 @@ export const AuthenticationSteps = (): JSX.Element => {
|
|||||||
setValue={setAddedPhoneNumberValue}
|
setValue={setAddedPhoneNumberValue}
|
||||||
email={loginRegisterValue}
|
email={loginRegisterValue}
|
||||||
onCompleteSignUp={() => setCurrentStep('addedPhoneNumberVerify')}
|
onCompleteSignUp={() => setCurrentStep('addedPhoneNumberVerify')}
|
||||||
|
setTimerValue={setTimerValue}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -203,6 +211,7 @@ export const AuthenticationSteps = (): JSX.Element => {
|
|||||||
onEditValue={() => setCurrentStep('addPhoneNumber')}
|
onEditValue={() => setCurrentStep('addPhoneNumber')}
|
||||||
value={addedPhoneNumberValue}
|
value={addedPhoneNumberValue}
|
||||||
onPhoneNumberVerified={handlePhoneNumberVerified}
|
onPhoneNumberVerified={handlePhoneNumberVerified}
|
||||||
|
initialTimerValue={timerValue}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -4,10 +4,14 @@ import { useRef, useState, type ChangeEvent, type Dispatch } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { AuthenticationCard } from '../AuthenticationCard';
|
import { AuthenticationCard } from '../AuthenticationCard';
|
||||||
import { CountryCodeSelector } from '../../../../components/CountryCodeSelector';
|
import { CountryCodeSelector } from '../../../../components/CountryCodeSelector';
|
||||||
import { sendSmsOtp } from '../../api/authorizationAPI';
|
import {
|
||||||
|
sendSmsCodeCompleteUserInforamation,
|
||||||
|
sendSmsOtp,
|
||||||
|
} from '../../api/authorizationAPI';
|
||||||
import type { CountryCode } from '@/types/commonTypes';
|
import type { CountryCode } from '@/types/commonTypes';
|
||||||
import { useApi } from '@/hooks/useApi';
|
import { useApi } from '@/hooks/useApi';
|
||||||
import { replacePersianWithRealNumbers } from '@/utils/replacePersianWithRealNumbers';
|
import { replacePersianWithRealNumbers } from '@/utils/replacePersianWithRealNumbers';
|
||||||
|
import { useToast } from '@rkheftan/harmony-ui';
|
||||||
|
|
||||||
export interface CompleteSignUpProps {
|
export interface CompleteSignUpProps {
|
||||||
email: string;
|
email: string;
|
||||||
@@ -16,6 +20,7 @@ export interface CompleteSignUpProps {
|
|||||||
countryCode: CountryCode;
|
countryCode: CountryCode;
|
||||||
setCountryCode: Dispatch<CountryCode>;
|
setCountryCode: Dispatch<CountryCode>;
|
||||||
onCompleteSignUp: (countryCode: string, value: string) => void;
|
onCompleteSignUp: (countryCode: string, value: string) => void;
|
||||||
|
setTimerValue: Dispatch<number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CompleteSignUp = ({
|
export const CompleteSignUp = ({
|
||||||
@@ -25,6 +30,7 @@ export const CompleteSignUp = ({
|
|||||||
countryCode,
|
countryCode,
|
||||||
setCountryCode,
|
setCountryCode,
|
||||||
onCompleteSignUp,
|
onCompleteSignUp,
|
||||||
|
setTimerValue,
|
||||||
}: CompleteSignUpProps) => {
|
}: CompleteSignUpProps) => {
|
||||||
const { t, i18n } = useTranslation('authentication');
|
const { t, i18n } = useTranslation('authentication');
|
||||||
const [error, setError] = useState<string>();
|
const [error, setError] = useState<string>();
|
||||||
@@ -32,7 +38,10 @@ export const CompleteSignUp = ({
|
|||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const [touched, setTouched] = useState<boolean>(false);
|
const [touched, setTouched] = useState<boolean>(false);
|
||||||
const inputError: boolean = touched && !!error;
|
const inputError: boolean = touched && !!error;
|
||||||
const { loading: sendSmsLoading, execute: sendSmsCall } = useApi(sendSmsOtp);
|
const { loading: sendSmsLoading, execute: sendSmsCall } = useApi(
|
||||||
|
sendSmsCodeCompleteUserInforamation,
|
||||||
|
);
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
const isPhoneValid = (code: string, phone: string) => {
|
const isPhoneValid = (code: string, phone: string) => {
|
||||||
const phoneNumber = parsePhoneNumberFromString(code + phone);
|
const phoneNumber = parsePhoneNumberFromString(code + phone);
|
||||||
@@ -74,8 +83,20 @@ export const CompleteSignUp = ({
|
|||||||
newValue = newValue.substring(1);
|
newValue = newValue.substring(1);
|
||||||
setValue(newValue);
|
setValue(newValue);
|
||||||
}
|
}
|
||||||
await sendSmsCall({ phoneNumber: countryCode + newValue });
|
const res = await sendSmsCall({ phoneNumber: countryCode + newValue });
|
||||||
onCompleteSignUp(countryCode, newValue);
|
|
||||||
|
if (!res) return;
|
||||||
|
|
||||||
|
if (res.success) {
|
||||||
|
onCompleteSignUp(countryCode, newValue);
|
||||||
|
setTimerValue(res.totalSecondForCodeToExpire);
|
||||||
|
} else {
|
||||||
|
toast({
|
||||||
|
message: res.message,
|
||||||
|
severity: 'error',
|
||||||
|
});
|
||||||
|
setError(res.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ export const EnterPasswordForm = ({
|
|||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="large"
|
size="large"
|
||||||
sx={{ width: 'auto' }}
|
sx={{ width: 'auto', textTransform: 'none' }}
|
||||||
endIcon={<Icon Component={Edit2} />}
|
endIcon={<Icon Component={Edit2} />}
|
||||||
onClick={onEditValue}
|
onClick={onEditValue}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,101 +0,0 @@
|
|||||||
import { Button } from '@mui/material';
|
|
||||||
import { useEffect, useRef } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import type {
|
|
||||||
GoogleCodeClientResponse,
|
|
||||||
LoginOrSignUpWithGoogleRequest,
|
|
||||||
LoginResult,
|
|
||||||
} from '../../types/userTypes';
|
|
||||||
import { loginOrSignUpWithGoogle } from '../../api/authorizationAPI';
|
|
||||||
import { Google } from 'iconsax-react';
|
|
||||||
import { Icon, useToast } from '@rkheftan/harmony-ui';
|
|
||||||
import { useApi } from '@/hooks/useApi';
|
|
||||||
import {
|
|
||||||
generateTokenWithGoogle,
|
|
||||||
type GenerateTokenResponse,
|
|
||||||
} from '../../api/identityAPI';
|
|
||||||
import type { AuthFactory } from '../../types/authTypes';
|
|
||||||
|
|
||||||
export interface GoogleAuthenticationProps {
|
|
||||||
disabled: boolean;
|
|
||||||
authFactory: AuthFactory;
|
|
||||||
onGoogleAuthenticated: (
|
|
||||||
loginResult: LoginResult,
|
|
||||||
tokenResponse: GenerateTokenResponse,
|
|
||||||
) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated use V2 instead
|
|
||||||
*/
|
|
||||||
export const GoogleAuthentication = ({
|
|
||||||
disabled,
|
|
||||||
authFactory,
|
|
||||||
onGoogleAuthenticated,
|
|
||||||
}: GoogleAuthenticationProps) => {
|
|
||||||
const { t } = useTranslation('authentication');
|
|
||||||
const { loading: loginWithGoogleLoading, execute: loginWithGoogleCall } =
|
|
||||||
useApi(loginOrSignUpWithGoogle);
|
|
||||||
const toast = useToast();
|
|
||||||
const clientRef = useRef<any>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const script = document.createElement('script');
|
|
||||||
script.src = 'https://accounts.google.com/gsi/client';
|
|
||||||
script.async = true;
|
|
||||||
script.defer = true;
|
|
||||||
document.body.appendChild(script);
|
|
||||||
|
|
||||||
script.onload = () => {
|
|
||||||
clientRef.current = google.accounts.id.initialize({
|
|
||||||
client_id: import.meta.env.VITE_GOOGLE_CLIENT_ID,
|
|
||||||
callback: async (resp: GoogleCodeClientResponse) => {
|
|
||||||
const apiRequest: LoginOrSignUpWithGoogleRequest = {
|
|
||||||
idToken: resp.id_token,
|
|
||||||
returnUrl: authFactory.redirectUrl,
|
|
||||||
};
|
|
||||||
const res = await loginWithGoogleCall(apiRequest);
|
|
||||||
|
|
||||||
if (!res) return;
|
|
||||||
|
|
||||||
if (res.success) {
|
|
||||||
const tokenRes = await generateTokenWithGoogle({
|
|
||||||
...apiRequest,
|
|
||||||
client_id: authFactory.clientId,
|
|
||||||
});
|
|
||||||
|
|
||||||
onGoogleAuthenticated(res, tokenRes.data);
|
|
||||||
} else {
|
|
||||||
toast({
|
|
||||||
message: t('loginForm.googleAuthenticationFailed'),
|
|
||||||
severity: 'error',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.body.removeChild(script);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleGoogleLogin = () => {
|
|
||||||
if (clientRef.current) {
|
|
||||||
clientRef.current.requestCode();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
onClick={handleGoogleLogin}
|
|
||||||
disabled={disabled}
|
|
||||||
loading={loginWithGoogleLoading}
|
|
||||||
variant="outlined"
|
|
||||||
startIcon={<Icon Component={Google} variant="Bold" />}
|
|
||||||
>
|
|
||||||
{t('loginForm.loginWithGoogle')}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import type {
|
import type {
|
||||||
LoginOrSignUpWithGoogleRequest,
|
LoginOrSignUpWithGoogleRequest,
|
||||||
LoginResult,
|
LoginResult,
|
||||||
@@ -12,7 +12,7 @@ import { loginOrSignUpWithGoogle } from '../../api/authorizationAPI';
|
|||||||
import { useApi } from '@/hooks/useApi';
|
import { useApi } from '@/hooks/useApi';
|
||||||
import { useToast } from '@rkheftan/harmony-ui';
|
import { useToast } from '@rkheftan/harmony-ui';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Box } from '@mui/material';
|
import { Box, Button } from '@mui/material';
|
||||||
|
|
||||||
interface GoogleAuthenticationV2Props {
|
interface GoogleAuthenticationV2Props {
|
||||||
authFactory: AuthFactory;
|
authFactory: AuthFactory;
|
||||||
@@ -29,6 +29,7 @@ export const GoogleAuthenticationV2 = ({
|
|||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const { t } = useTranslation('authentication');
|
const { t } = useTranslation('authentication');
|
||||||
const { execute: loginWithGoogleCall } = useApi(loginOrSignUpWithGoogle);
|
const { execute: loginWithGoogleCall } = useApi(loginOrSignUpWithGoogle);
|
||||||
|
const [isGoogleLoaded, setIsGoogleLoaded] = useState(false);
|
||||||
|
|
||||||
const googleBtnRef = useRef<HTMLDivElement>(null);
|
const googleBtnRef = useRef<HTMLDivElement>(null);
|
||||||
const renderedRef = useRef(false);
|
const renderedRef = useRef(false);
|
||||||
@@ -74,6 +75,8 @@ export const GoogleAuthenticationV2 = ({
|
|||||||
const initializeGoogle = () => {
|
const initializeGoogle = () => {
|
||||||
if (!window.google) return;
|
if (!window.google) return;
|
||||||
|
|
||||||
|
setIsGoogleLoaded(true);
|
||||||
|
|
||||||
google.accounts.id.initialize({
|
google.accounts.id.initialize({
|
||||||
client_id: import.meta.env.VITE_GOOGLE_CLIENT_ID,
|
client_id: import.meta.env.VITE_GOOGLE_CLIENT_ID,
|
||||||
callback: handleCredentialResponse,
|
callback: handleCredentialResponse,
|
||||||
@@ -85,8 +88,9 @@ export const GoogleAuthenticationV2 = ({
|
|||||||
type: 'standard', // The standard "Sign in with Google" button
|
type: 'standard', // The standard "Sign in with Google" button
|
||||||
text: 'signin_with', // Text: "Sign in with Google"
|
text: 'signin_with', // Text: "Sign in with Google"
|
||||||
shape: 'rectangular', // Matches your MUI button shape
|
shape: 'rectangular', // Matches your MUI button shape
|
||||||
width: '456', // Set a width (Google caps this at 400px)
|
width: '400', // Set a width (Google caps this at 400px)
|
||||||
logo_alignment: 'left',
|
logo_alignment: 'left',
|
||||||
|
|
||||||
height: '44',
|
height: '44',
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -116,6 +120,7 @@ export const GoogleAuthenticationV2 = ({
|
|||||||
minHeight: 44,
|
minHeight: 44,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{!isGoogleLoaded && <Button variant="outlined" loading />}
|
||||||
<div ref={googleBtnRef} id="google-signin-button"></div>
|
<div ref={googleBtnRef} id="google-signin-button"></div>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Box, Typography, MenuItem, Select, Stack } from '@mui/material';
|
import { Box, Typography, MenuItem, Select, Stack } from '@mui/material';
|
||||||
|
import { Icon } from '@rkheftan/harmony-ui';
|
||||||
import { Global } from 'iconsax-react';
|
import { Global } from 'iconsax-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
@@ -15,32 +16,45 @@ export default function LanguageAccountBar() {
|
|||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
px: 2,
|
px: 2,
|
||||||
py: 1,
|
py: 1.5,
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
direction: i18n.language === 'fa' ? 'rtl' : 'ltr',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack direction="row" alignItems="center" spacing={1}>
|
<Stack direction="row" alignItems="center" spacing={1}>
|
||||||
|
<Typography sx={{ pr: 4, color: 'secondary.light' }} variant="body2">
|
||||||
|
{t('loginForm.accountInfo')}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Icon Component={Global} color="action.active" size="small" />
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
size="small"
|
size="small"
|
||||||
value={i18n.language}
|
value={i18n.language}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
variant="standard"
|
variant="standard"
|
||||||
disableUnderline
|
disableUnderline
|
||||||
|
sx={{
|
||||||
|
'& .MuiSelect-select': {
|
||||||
|
paddingTop: 0,
|
||||||
|
paddingBottom: 0,
|
||||||
|
paddingRight: 2,
|
||||||
|
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
fontWeight: 600,
|
||||||
|
color: 'action.active',
|
||||||
|
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<MenuItem value="fa">{t('loginForm.persian')}</MenuItem>
|
<MenuItem value="fa">{t('loginForm.persian')}</MenuItem>
|
||||||
<MenuItem value="en">{t('loginForm.english')}</MenuItem>
|
<MenuItem value="en">{t('loginForm.english')}</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
<Global size={20} style={{ color: '#666' }} />
|
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Typography sx={{ color: 'text.primary' }} variant="body2">
|
|
||||||
{t('loginForm.accountInfo')}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,11 @@ export interface LoginRegisterFormProps {
|
|||||||
setCountryCode: Dispatch<CountryCode>;
|
setCountryCode: Dispatch<CountryCode>;
|
||||||
authType: AuthType;
|
authType: AuthType;
|
||||||
setAuthType: Dispatch<AuthType>;
|
setAuthType: Dispatch<AuthType>;
|
||||||
onLoginRegisterSubmit: (value: string, userStatus: UserStatus) => void;
|
onLoginRegisterSubmit: (
|
||||||
|
value: string,
|
||||||
|
userStatus: UserStatus,
|
||||||
|
timerValue: number,
|
||||||
|
) => void;
|
||||||
onGoogleAuthenticated: (
|
onGoogleAuthenticated: (
|
||||||
loginResult: LoginResult,
|
loginResult: LoginResult,
|
||||||
tokenResponse: GenerateTokenResponse,
|
tokenResponse: GenerateTokenResponse,
|
||||||
@@ -90,17 +94,17 @@ export function LoginRegisterForm({
|
|||||||
setErrors: boolean = true,
|
setErrors: boolean = true,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
if (setErrors) setError(t('loginForm.thisFieldIsRequired'));
|
if (setErrors) setError('loginForm.thisFieldIsRequired');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authType === 'email' && !isEmail(value)) {
|
if (authType === 'email' && !isEmail(value)) {
|
||||||
if (setErrors) setError(t('loginForm.emailIsInvalid'));
|
if (setErrors) setError('loginForm.emailIsInvalid');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authType === 'phone' && !isPhoneNumber(countryCode, value)) {
|
if (authType === 'phone' && !isPhoneNumber(countryCode, value)) {
|
||||||
if (setErrors) setError(t('loginForm.phoneNumberIsInvalid'));
|
if (setErrors) setError('loginForm.phoneNumberIsInvalid');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +135,11 @@ export function LoginRegisterForm({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
onLoginRegisterSubmit(newValue, res.userStatus);
|
onLoginRegisterSubmit(
|
||||||
|
newValue,
|
||||||
|
res.userStatus,
|
||||||
|
res.totalSecondForOtpToExpire,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
toast({ message: res.message, severity: 'error' });
|
toast({ message: res.message, severity: 'error' });
|
||||||
}
|
}
|
||||||
@@ -171,7 +179,7 @@ export function LoginRegisterForm({
|
|||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
error={inputError}
|
error={inputError}
|
||||||
helperText={inputError ? error : ''}
|
helperText={inputError && t(error || '')}
|
||||||
autoFocus
|
autoFocus
|
||||||
slotProps={{
|
slotProps={{
|
||||||
htmlInput: { dir: 'auto', sx: { lineHeight: 1.5 } },
|
htmlInput: { dir: 'auto', sx: { lineHeight: 1.5 } },
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ interface OtpVerifyFormProps {
|
|||||||
) => void;
|
) => void;
|
||||||
authFactory: AuthFactory;
|
authFactory: AuthFactory;
|
||||||
hasPassword: boolean;
|
hasPassword: boolean;
|
||||||
|
initialTimerValue: number;
|
||||||
onLoginWithPassword: () => void;
|
onLoginWithPassword: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,13 +45,14 @@ export function OtpVerifyForm({
|
|||||||
onOTPVerified,
|
onOTPVerified,
|
||||||
authFactory,
|
authFactory,
|
||||||
hasPassword,
|
hasPassword,
|
||||||
|
initialTimerValue,
|
||||||
onLoginWithPassword,
|
onLoginWithPassword,
|
||||||
}: OtpVerifyFormProps) {
|
}: OtpVerifyFormProps) {
|
||||||
const [otpCode, setOtpCode] = useState<string>('');
|
const [otpCode, setOtpCode] = useState<string>('');
|
||||||
const [otpDigitInvalid, setOtpDigitInvalid] = useState<boolean>(false);
|
const [otpDigitInvalid, setOtpDigitInvalid] = useState<boolean>(false);
|
||||||
const [isStatusSuccess, setIsStatusSuccess] = useState<boolean>();
|
const [isStatusSuccess, setIsStatusSuccess] = useState<boolean>();
|
||||||
const { t, i18n } = useTranslation('authentication');
|
const { t, i18n } = useTranslation('authentication');
|
||||||
const [resendTimer, setResendTimer] = useState<number>(120);
|
const [resendTimer, setResendTimer] = useState<number>(initialTimerValue);
|
||||||
const [canResend, setCanResend] = useState(false);
|
const [canResend, setCanResend] = useState(false);
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const { loading: smsResendLoading, execute: smsResendCall } =
|
const { loading: smsResendLoading, execute: smsResendCall } =
|
||||||
@@ -200,7 +202,7 @@ export function OtpVerifyForm({
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="large"
|
size="large"
|
||||||
sx={{
|
sx={{
|
||||||
textTransform: 'lowercase',
|
textTransform: 'none',
|
||||||
width: 'auto',
|
width: 'auto',
|
||||||
}}
|
}}
|
||||||
endIcon={<Icon Component={Edit2} />}
|
endIcon={<Icon Component={Edit2} />}
|
||||||
|
|||||||
@@ -5,16 +5,21 @@ import DigitInput from '@/components/DigitsInput';
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { AuthenticationCard } from '../AuthenticationCard';
|
import { AuthenticationCard } from '../AuthenticationCard';
|
||||||
import type { ConfirmSmsOtpRequest } from '../../types/userTypes';
|
import type { ConfirmSmsOtpRequest } from '../../types/userTypes';
|
||||||
import { confirmSmsOtp, sendSmsOtp } from '../../api/authorizationAPI';
|
import {
|
||||||
|
confirmSmsCodeCompleteUserInforamation,
|
||||||
|
sendSmsCodeCompleteUserInforamation,
|
||||||
|
} from '../../api/authorizationAPI';
|
||||||
import type { CountryCode } from '@/types/commonTypes';
|
import type { CountryCode } from '@/types/commonTypes';
|
||||||
import { Icon, useToast } from '@rkheftan/harmony-ui';
|
import { Icon, useToast } from '@rkheftan/harmony-ui';
|
||||||
import { useApi } from '@/hooks/useApi';
|
import { useApi } from '@/hooks/useApi';
|
||||||
|
import { LTRTypography } from '@/components/common/LTRTypography';
|
||||||
|
|
||||||
interface VerifyPhoneNumberProps {
|
interface VerifyPhoneNumberProps {
|
||||||
value: string;
|
value: string;
|
||||||
countryCode: CountryCode;
|
countryCode: CountryCode;
|
||||||
onEditValue: () => void;
|
onEditValue: () => void;
|
||||||
onPhoneNumberVerified: () => void;
|
onPhoneNumberVerified: () => void;
|
||||||
|
initialTimerValue: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function VerifyPhoneNumber({
|
export function VerifyPhoneNumber({
|
||||||
@@ -22,18 +27,21 @@ export function VerifyPhoneNumber({
|
|||||||
countryCode,
|
countryCode,
|
||||||
onEditValue,
|
onEditValue,
|
||||||
onPhoneNumberVerified,
|
onPhoneNumberVerified,
|
||||||
|
initialTimerValue,
|
||||||
}: VerifyPhoneNumberProps) {
|
}: VerifyPhoneNumberProps) {
|
||||||
const [otpCode, setOtpCode] = useState<string>('');
|
const [otpCode, setOtpCode] = useState<string>('');
|
||||||
const [otpDigitInvalid, setOtpDigitInvalid] = useState<boolean>(false);
|
const [otpDigitInvalid, setOtpDigitInvalid] = useState<boolean>(false);
|
||||||
const [isStatusSuccess, setIsStatusSuccess] = useState<boolean>();
|
const [isStatusSuccess, setIsStatusSuccess] = useState<boolean>();
|
||||||
const { t } = useTranslation('authentication');
|
const { t } = useTranslation('authentication');
|
||||||
const [resendTimer, setResendTimer] = useState<number>(120);
|
const [resendTimer, setResendTimer] = useState<number>(initialTimerValue);
|
||||||
const [canResend, setCanResend] = useState(false);
|
const [canResend, setCanResend] = useState(false);
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const { loading: smsResendLoading, execute: smsResendCall } =
|
const { loading: smsResendLoading, execute: smsResendCall } = useApi(
|
||||||
useApi(sendSmsOtp);
|
sendSmsCodeCompleteUserInforamation,
|
||||||
const { loading: confirmSmsOtpLoading, execute: confirmSmsOtpCall } =
|
);
|
||||||
useApi(confirmSmsOtp);
|
const { loading: confirmSmsOtpLoading, execute: confirmSmsOtpCall } = useApi(
|
||||||
|
confirmSmsCodeCompleteUserInforamation,
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (otpCode.length === 4) {
|
if (otpCode.length === 4) {
|
||||||
@@ -55,9 +63,10 @@ export function VerifyPhoneNumber({
|
|||||||
}, [resendTimer]);
|
}, [resendTimer]);
|
||||||
|
|
||||||
const handleResendOTPCode = async () => {
|
const handleResendOTPCode = async () => {
|
||||||
await smsResendCall({ phoneNumber: countryCode + value });
|
const res = await smsResendCall({ phoneNumber: countryCode + value });
|
||||||
|
|
||||||
setResendTimer(120);
|
if (!res) return;
|
||||||
|
setResendTimer(res.totalSecondForCodeToExpire);
|
||||||
setCanResend(false);
|
setCanResend(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -130,7 +139,7 @@ export function VerifyPhoneNumber({
|
|||||||
endIcon={<Icon Component={Edit2} />}
|
endIcon={<Icon Component={Edit2} />}
|
||||||
onClick={onEditValue}
|
onClick={onEditValue}
|
||||||
>
|
>
|
||||||
{countryCode + value}
|
<LTRTypography>{countryCode + value}</LTRTypography>
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|||||||
@@ -110,22 +110,14 @@ export function EmailSection(props: EmailSectionProps) {
|
|||||||
}}
|
}}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
input: {
|
input: {
|
||||||
startAdornment: (
|
startAdornment:
|
||||||
<InputAdornment position="start" sx={{ width: ADORN_W }}>
|
!isVerifyingCode && emailVerified ? (
|
||||||
<Box
|
<InputAdornment position="start" sx={{ width: ADORN_W }}>
|
||||||
sx={{
|
|
||||||
width: ADORN_W,
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
alignItems: 'center',
|
width: ADORN_W,
|
||||||
visibility:
|
display: 'flex',
|
||||||
!isVerifyingCode && emailVerified
|
justifyContent: 'center',
|
||||||
? 'visible'
|
|
||||||
: 'hidden',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
@@ -135,10 +127,9 @@ export function EmailSection(props: EmailSectionProps) {
|
|||||||
color="success.main"
|
color="success.main"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</InputAdornment>
|
||||||
</InputAdornment>
|
) : undefined,
|
||||||
),
|
endAdornment: codeSent ? (
|
||||||
endAdornment: (
|
|
||||||
<InputAdornment position="end" sx={{ width: ADORN_W }}>
|
<InputAdornment position="end" sx={{ width: ADORN_W }}>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@@ -147,23 +138,19 @@ export function EmailSection(props: EmailSectionProps) {
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<IconButton
|
||||||
sx={{ visibility: codeSent ? 'visible' : 'hidden' }}
|
onClick={handleEditEmail}
|
||||||
|
disabled={!codeSent}
|
||||||
>
|
>
|
||||||
<IconButton
|
<Icon
|
||||||
onClick={handleEditEmail}
|
Component={Edit2}
|
||||||
disabled={!codeSent}
|
color="primary.main"
|
||||||
>
|
size="medium"
|
||||||
<Icon
|
/>
|
||||||
Component={Edit2}
|
</IconButton>
|
||||||
color="primary.main"
|
|
||||||
size="medium"
|
|
||||||
/>
|
|
||||||
</IconButton>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
),
|
) : undefined,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export function AuthenticationPage() {
|
|||||||
align="center"
|
align="center"
|
||||||
justify="center"
|
justify="center"
|
||||||
sx={{
|
sx={{
|
||||||
minHeight: '100vh',
|
minHeight: '100dvh',
|
||||||
gap: 3,
|
gap: 3,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export interface GetUserStatusByPhoneNumberOrEmailRequest {
|
|||||||
|
|
||||||
export interface GetUserStatusByPhoneNumberOrEmailResponse extends ApiResponse {
|
export interface GetUserStatusByPhoneNumberOrEmailResponse extends ApiResponse {
|
||||||
userStatus: UserStatus;
|
userStatus: UserStatus;
|
||||||
|
totalSecondForOtpToExpire: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum UserStatus {
|
export enum UserStatus {
|
||||||
@@ -50,6 +51,10 @@ export interface SendSmsOtpRequest {
|
|||||||
phoneNumber: string;
|
phoneNumber: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SendSmsOtpResponse extends ApiResponse {
|
||||||
|
totalSecondForCodeToExpire: number;
|
||||||
|
}
|
||||||
|
|
||||||
// SendEmailOtp
|
// SendEmailOtp
|
||||||
|
|
||||||
export interface SendEmailOtpRequest {
|
export interface SendEmailOtpRequest {
|
||||||
|
|||||||
Reference in New Issue
Block a user