Merge pull request #32 from rkheftan/fix/enter
feat: add using enter instead pushing the button
This commit is contained in:
2
.env
2
.env
@@ -1,6 +1,6 @@
|
|||||||
VITE_GOOGLE_CLIENT_ID=https://272098283932-bft2gvlgjn8edopg0lnqjq1i9ekdmipt.apps.googleusercontent.com
|
VITE_GOOGLE_CLIENT_ID=https://272098283932-bft2gvlgjn8edopg0lnqjq1i9ekdmipt.apps.googleusercontent.com
|
||||||
VITE_DEFUALT_AUTH_RETURN_URL=/setting/profile
|
VITE_DEFUALT_AUTH_RETURN_URL=/setting/profile
|
||||||
VITE_APP_URL=https://account.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
|
||||||
VITE_IDENTITY_CLIENT_ID=harmony_identity
|
VITE_IDENTITY_CLIENT_ID=harmony_identity
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Button, TextField, Typography } from '@mui/material';
|
import { Box, Button, TextField, Typography } from '@mui/material';
|
||||||
import parsePhoneNumberFromString from 'libphonenumber-js';
|
import parsePhoneNumberFromString from 'libphonenumber-js';
|
||||||
import { useRef, useState, type Dispatch } from 'react';
|
import { useRef, useState, type Dispatch } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -78,47 +78,56 @@ export const CompleteSignUp = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthenticationCard>
|
<AuthenticationCard>
|
||||||
<Typography variant="h5" sx={{ mb: 0.5 }}>
|
<Box
|
||||||
{t('completeSignUp.completeSignUp')}
|
component="form"
|
||||||
</Typography>
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
<Typography variant="body2" color="textSecondary" sx={{ mt: 1, mb: 2 }}>
|
e.stopPropagation();
|
||||||
{t(
|
if (!sendSmsLoading) void handleCompleteSignUp();
|
||||||
'completeSignUp.emailHasBeenSuccessfullyVerifiedPleaseEnterYourContactNumberToContinue',
|
|
||||||
{ email },
|
|
||||||
)}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<TextField
|
|
||||||
ref={textFieldRef}
|
|
||||||
inputRef={inputRef}
|
|
||||||
label={t('completeSignUp.phoneNumber')}
|
|
||||||
value={value}
|
|
||||||
onChange={handleChange}
|
|
||||||
onBlur={handleBlur}
|
|
||||||
error={inputError}
|
|
||||||
helperText={inputError ? error : ''}
|
|
||||||
autoFocus
|
|
||||||
slotProps={{
|
|
||||||
htmlInput: { dir: 'auto', sx: { lineHeight: 1.5, paddingX: 0 } },
|
|
||||||
input: {
|
|
||||||
endAdornment: (
|
|
||||||
<CountryCodeSelector
|
|
||||||
value={countryCode}
|
|
||||||
onChange={setCountryCode}
|
|
||||||
show={true}
|
|
||||||
menuAnchor={textFieldRef.current}
|
|
||||||
onCloseFocusRef={inputRef}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
sx={{ my: 4 }}
|
>
|
||||||
/>
|
<Typography variant="h5" sx={{ mb: 0.5 }}>
|
||||||
|
{t('completeSignUp.completeSignUp')}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
<Button loading={sendSmsLoading} onClick={handleCompleteSignUp}>
|
<Typography variant="body2" color="textSecondary" sx={{ mt: 1, mb: 2 }}>
|
||||||
{t('verify.confirmAndContinue')}
|
{t(
|
||||||
</Button>
|
'completeSignUp.emailHasBeenSuccessfullyVerifiedPleaseEnterYourContactNumberToContinue',
|
||||||
|
{ email },
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
ref={textFieldRef}
|
||||||
|
inputRef={inputRef}
|
||||||
|
label={t('completeSignUp.phoneNumber')}
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => setValue(e.target.value)}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
error={inputError}
|
||||||
|
helperText={inputError ? error : ''}
|
||||||
|
autoFocus
|
||||||
|
slotProps={{
|
||||||
|
htmlInput: { dir: 'auto', sx: { lineHeight: 1.5, paddingX: 0 } },
|
||||||
|
input: {
|
||||||
|
endAdornment: (
|
||||||
|
<CountryCodeSelector
|
||||||
|
value={countryCode}
|
||||||
|
onChange={setCountryCode}
|
||||||
|
show={true}
|
||||||
|
menuAnchor={textFieldRef.current}
|
||||||
|
onCloseFocusRef={inputRef}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
sx={{ my: 4 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button loading={sendSmsLoading} type="submit">
|
||||||
|
{t('verify.confirmAndContinue')}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
</AuthenticationCard>
|
</AuthenticationCard>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import {
|
|||||||
import type { LoginResult, PasswordLoginRequest } from '../../types/userTypes';
|
import type { LoginResult, PasswordLoginRequest } from '../../types/userTypes';
|
||||||
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 { useAuth } from '@/hooks/useAuth';
|
|
||||||
import {
|
import {
|
||||||
generateTokenWithPassword,
|
generateTokenWithPassword,
|
||||||
type GenerateTokenResponse,
|
type GenerateTokenResponse,
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import {
|
|||||||
generateTokenWithGoogle,
|
generateTokenWithGoogle,
|
||||||
type GenerateTokenResponse,
|
type GenerateTokenResponse,
|
||||||
} from '../../api/identityAPI';
|
} from '../../api/identityAPI';
|
||||||
import { useAuth } from '@/hooks/useAuth';
|
|
||||||
import type { AuthFactory } from '../../types/authTypes';
|
import type { AuthFactory } from '../../types/authTypes';
|
||||||
|
|
||||||
export interface GoogleAuthenticationProps {
|
export interface GoogleAuthenticationProps {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Button, Stack, TextField, Typography } from '@mui/material';
|
import { Button, Stack, TextField, Typography, Box } from '@mui/material';
|
||||||
import { useRef, useState, type Dispatch } from 'react';
|
import { useRef, useState, type Dispatch } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { isNumeric } from '@/utils/regexes/isNumeric';
|
import { isNumeric } from '@/utils/regexes/isNumeric';
|
||||||
@@ -124,51 +124,62 @@ export function LoginRegisterForm({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthenticationCard>
|
<AuthenticationCard>
|
||||||
<Stack spacing={1}>
|
<Box
|
||||||
<Typography variant="h5">{t('loginForm.title')}</Typography>
|
component="form"
|
||||||
<Typography variant="body2" color="text.secondary">
|
onSubmit={(e) => {
|
||||||
{t('loginForm.description')}
|
e.preventDefault();
|
||||||
</Typography>
|
e.stopPropagation();
|
||||||
</Stack>
|
if (!userStatusLoading) {
|
||||||
|
void handleSubmit();
|
||||||
<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, paddingX: 0 } },
|
|
||||||
input: {
|
|
||||||
endAdornment: (
|
|
||||||
<CountryCodeSelector
|
|
||||||
value={countryCode}
|
|
||||||
onChange={setCountryCode}
|
|
||||||
show={showAdornment}
|
|
||||||
menuAnchor={textFieldRef.current}
|
|
||||||
onCloseFocusRef={inputRef}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
sx={{ my: 4 }}
|
>
|
||||||
/>
|
<Stack spacing={1}>
|
||||||
|
<Typography variant="h5">{t('loginForm.title')}</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
{t('loginForm.description')}
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
<Stack spacing={2}>
|
<TextField
|
||||||
<Button loading={userStatusLoading} onClick={handleSubmit}>
|
ref={textFieldRef}
|
||||||
{t('loginForm.submitButton')}
|
inputRef={inputRef}
|
||||||
</Button>
|
label={t('loginForm.emailOrPhoneLabel')}
|
||||||
|
value={loginRegisterValue}
|
||||||
<GoogleAuthentication
|
onChange={handleInputChange}
|
||||||
authFactory={authFactory}
|
onBlur={handleBlur}
|
||||||
onGoogleAuthenticated={onGoogleAuthenticated}
|
error={inputError}
|
||||||
disabled={userStatusLoading}
|
helperText={inputError ? error : ''}
|
||||||
|
autoFocus
|
||||||
|
slotProps={{
|
||||||
|
htmlInput: { dir: 'auto', sx: { lineHeight: 1.5, paddingX: 0 } },
|
||||||
|
input: {
|
||||||
|
endAdornment: (
|
||||||
|
<CountryCodeSelector
|
||||||
|
value={countryCode}
|
||||||
|
onChange={setCountryCode}
|
||||||
|
show={showAdornment}
|
||||||
|
menuAnchor={textFieldRef.current}
|
||||||
|
onCloseFocusRef={inputRef}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
sx={{ my: 4 }}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
|
||||||
|
<Stack spacing={2}>
|
||||||
|
<Button loading={userStatusLoading} type="submit">
|
||||||
|
{t('loginForm.submitButton')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<GoogleAuthentication
|
||||||
|
authFactory={authFactory}
|
||||||
|
onGoogleAuthenticated={onGoogleAuthenticated}
|
||||||
|
disabled={userStatusLoading}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
</AuthenticationCard>
|
</AuthenticationCard>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import {
|
|||||||
generateTokenWithOtp,
|
generateTokenWithOtp,
|
||||||
type GenerateTokenResponse,
|
type GenerateTokenResponse,
|
||||||
} from '../../api/identityAPI';
|
} from '../../api/identityAPI';
|
||||||
import { useAuth } from '@/hooks/useAuth';
|
|
||||||
|
|
||||||
interface OtpVerifyFormProps {
|
interface OtpVerifyFormProps {
|
||||||
value: string;
|
value: string;
|
||||||
@@ -166,43 +165,54 @@ export function OtpVerifyForm({
|
|||||||
<Stack alignItems="center">
|
<Stack alignItems="center">
|
||||||
<AuthenticationCard>
|
<AuthenticationCard>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
component="form"
|
||||||
display: 'flex',
|
onSubmit={(e) => {
|
||||||
flexDirection: 'row',
|
e.preventDefault();
|
||||||
alignItems: 'center',
|
e.stopPropagation();
|
||||||
justifyContent: 'space-between',
|
if (!loginSignUpLoading) {
|
||||||
gap: 4,
|
void handleVerifyOTP();
|
||||||
mb: 0.5,
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="h5">{t('verify.verify')}</Typography>
|
<Box
|
||||||
|
sx={{
|
||||||
<Button
|
display: 'flex',
|
||||||
variant="outlined"
|
flexDirection: 'row',
|
||||||
size="large"
|
alignItems: 'center',
|
||||||
sx={{ textTransform: 'lowercase', width: 'auto' }}
|
justifyContent: 'space-between',
|
||||||
endIcon={<Icon Component={Edit2} />}
|
gap: 4,
|
||||||
onClick={onEditValue}
|
mb: 0.5,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{authType === 'phone' ? countryCode + value : value}
|
<Typography variant="h5">{t('verify.verify')}</Typography>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
size="large"
|
||||||
|
sx={{ textTransform: 'lowercase', width: 'auto' }}
|
||||||
|
endIcon={<Icon Component={Edit2} />}
|
||||||
|
onClick={onEditValue}
|
||||||
|
>
|
||||||
|
{authType === 'phone' ? countryCode + value : value}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Typography variant="body2" color="textSecondary" sx={{ mt: 1 }}>
|
||||||
|
{otpMessage}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<DigitInput
|
||||||
|
error={otpDigitInvalid || isStatusSuccess === false}
|
||||||
|
success={isStatusSuccess === true}
|
||||||
|
onChange={(value) => setOtpCode(value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button type="submit" loading={loginSignUpLoading}>
|
||||||
|
{authMode === 'register'
|
||||||
|
? t('verify.confirmAndContinue')
|
||||||
|
: t('verify.confirmAndLogin')}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Typography variant="body2" color="textSecondary" sx={{ mt: 1 }}>
|
|
||||||
{otpMessage}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<DigitInput
|
|
||||||
error={otpDigitInvalid || isStatusSuccess === false}
|
|
||||||
success={isStatusSuccess === true}
|
|
||||||
onChange={(value) => setOtpCode(value)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button onClick={handleVerifyOTP} loading={loginSignUpLoading}>
|
|
||||||
{authMode === 'register'
|
|
||||||
? t('verify.confirmAndContinue')
|
|
||||||
: t('verify.confirmAndLogin')}
|
|
||||||
</Button>
|
|
||||||
</AuthenticationCard>
|
</AuthenticationCard>
|
||||||
|
|
||||||
<Stack
|
<Stack
|
||||||
|
|||||||
@@ -96,43 +96,54 @@ export function VerifyPhoneNumber({
|
|||||||
<Stack alignItems="center">
|
<Stack alignItems="center">
|
||||||
<AuthenticationCard>
|
<AuthenticationCard>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
component="form"
|
||||||
display: 'flex',
|
onSubmit={(e) => {
|
||||||
flexDirection: 'row',
|
e.preventDefault();
|
||||||
alignItems: 'center',
|
e.stopPropagation();
|
||||||
justifyContent: 'space-between',
|
if (!confirmSmsOtpLoading) {
|
||||||
gap: 4,
|
void handleVerifyOTP();
|
||||||
mb: 0.5,
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="h5">{t('verify.verify')}</Typography>
|
<Box
|
||||||
|
sx={{
|
||||||
<Button
|
display: 'flex',
|
||||||
variant="outlined"
|
flexDirection: 'row',
|
||||||
size="large"
|
alignItems: 'center',
|
||||||
sx={{ textTransform: 'lowercase', width: 'auto' }}
|
justifyContent: 'space-between',
|
||||||
endIcon={<Icon Component={Edit2} />}
|
gap: 4,
|
||||||
onClick={onEditValue}
|
mb: 0.5,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{countryCode + value}
|
<Typography variant="h5">{t('verify.verify')}</Typography>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
size="large"
|
||||||
|
sx={{ textTransform: 'lowercase', width: 'auto' }}
|
||||||
|
endIcon={<Icon Component={Edit2} />}
|
||||||
|
onClick={onEditValue}
|
||||||
|
>
|
||||||
|
{countryCode + value}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Typography variant="body2" color="textSecondary" sx={{ mt: 1 }}>
|
||||||
|
{t(
|
||||||
|
'verify.a4DigitVerificationCodeHasBeenSentToYourBobileNumberPleaseEnterIt',
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<DigitInput
|
||||||
|
error={otpDigitInvalid || isStatusSuccess === false}
|
||||||
|
success={isStatusSuccess === true}
|
||||||
|
onChange={(value) => setOtpCode(value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button type="submit" loading={confirmSmsOtpLoading}>
|
||||||
|
{t('verify.confirmAndLogin')}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Typography variant="body2" color="textSecondary" sx={{ mt: 1 }}>
|
|
||||||
{t(
|
|
||||||
'verify.a4DigitVerificationCodeHasBeenSentToYourBobileNumberPleaseEnterIt',
|
|
||||||
)}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<DigitInput
|
|
||||||
error={otpDigitInvalid || isStatusSuccess === false}
|
|
||||||
success={isStatusSuccess === true}
|
|
||||||
onChange={(value) => setOtpCode(value)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button onClick={handleVerifyOTP} loading={confirmSmsOtpLoading}>
|
|
||||||
{t('verify.confirmAndLogin')}
|
|
||||||
</Button>
|
|
||||||
</AuthenticationCard>
|
</AuthenticationCard>
|
||||||
|
|
||||||
<Stack
|
<Stack
|
||||||
|
|||||||
@@ -116,54 +116,62 @@ export function ForgetPasswordOtp({
|
|||||||
<Stack alignItems="center">
|
<Stack alignItems="center">
|
||||||
<AuthenticationCard>
|
<AuthenticationCard>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
component="form"
|
||||||
display: 'flex',
|
onSubmit={(e) => {
|
||||||
flexDirection: 'row',
|
e.preventDefault();
|
||||||
alignItems: 'center',
|
e.stopPropagation();
|
||||||
justifyContent: 'space-between',
|
if (!confirmForgetPassCodeLoading) {
|
||||||
gap: 4,
|
void handleVerifyOTP();
|
||||||
mb: 0.5,
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="h5">
|
<Box
|
||||||
{t('forgetPassword.forgetPassword')}
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
gap: 4,
|
||||||
|
mb: 0.5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="h5">
|
||||||
|
{t('forgetPassword.forgetPassword')}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
size="large"
|
||||||
|
sx={{ textTransform: 'lowercase', width: 'auto' }}
|
||||||
|
endIcon={<Icon Component={Edit2} />}
|
||||||
|
onClick={onEditInfo}
|
||||||
|
>
|
||||||
|
{infoType === 'phone'
|
||||||
|
? countryCode + forgettedPasswordInfo
|
||||||
|
: forgettedPasswordInfo}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Typography variant="body2" color="textSecondary" sx={{ mt: 1 }}>
|
||||||
|
{infoType === 'email'
|
||||||
|
? t(
|
||||||
|
'forgetPassword.anEmailContainingARecoveryCodeHasBeenSentToThisEmailAddress',
|
||||||
|
)
|
||||||
|
: t(
|
||||||
|
'forgetPassword.anCodeContainingARecoveryCodeHasBeenSentToThisPhoneNumber',
|
||||||
|
)}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Button
|
<DigitInput
|
||||||
variant="outlined"
|
error={otpDigitInvalid || isStatusSuccess === false}
|
||||||
size="large"
|
success={isStatusSuccess === true}
|
||||||
sx={{ textTransform: 'lowercase', width: 'auto' }}
|
onChange={(value) => setOtpCode(value)}
|
||||||
endIcon={<Icon Component={Edit2} />}
|
/>
|
||||||
onClick={onEditInfo}
|
|
||||||
>
|
<Button type="submit" loading={confirmForgetPassCodeLoading}>
|
||||||
{infoType === 'phone'
|
{t('forgetPassword.confirm')}
|
||||||
? countryCode + forgettedPasswordInfo
|
|
||||||
: forgettedPasswordInfo}
|
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Typography variant="body2" color="textSecondary" sx={{ mt: 1 }}>
|
|
||||||
{infoType === 'email'
|
|
||||||
? t(
|
|
||||||
'forgetPassword.anEmailContainingARecoveryCodeHasBeenSentToThisEmailAddress',
|
|
||||||
)
|
|
||||||
: t(
|
|
||||||
'forgetPassword.anCodeContainingARecoveryCodeHasBeenSentToThisPhoneNumber',
|
|
||||||
)}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<DigitInput
|
|
||||||
error={otpDigitInvalid || isStatusSuccess === false}
|
|
||||||
success={isStatusSuccess === true}
|
|
||||||
onChange={(value) => setOtpCode(value)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onClick={handleVerifyOTP}
|
|
||||||
loading={confirmForgetPassCodeLoading}
|
|
||||||
>
|
|
||||||
{t('forgetPassword.confirm')}
|
|
||||||
</Button>
|
|
||||||
</AuthenticationCard>
|
</AuthenticationCard>
|
||||||
|
|
||||||
<Stack
|
<Stack
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { type SubmitProps } from '../../types/settingForm';
|
import { type SubmitProps } from '../../types/settingForm';
|
||||||
|
|
||||||
export function SubmitSection({ onSubmit, loading }: SubmitProps) {
|
export function SubmitSection({ loading }: SubmitProps) {
|
||||||
const { t, i18n } = useTranslation('completionForm');
|
const { t, i18n } = useTranslation('completionForm');
|
||||||
const [openDialog, setOpenDialog] = useState(false);
|
const [openDialog, setOpenDialog] = useState(false);
|
||||||
|
|
||||||
@@ -50,8 +50,9 @@ export function SubmitSection({ onSubmit, loading }: SubmitProps) {
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={onSubmit}
|
type="submit"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
|
form="completionForm"
|
||||||
sx={{
|
sx={{
|
||||||
width: { xs: '100%', sm: '247px' },
|
width: { xs: '100%', sm: '247px' },
|
||||||
alignSelf: { xs: 'stretch', sm: 'center' },
|
alignSelf: { xs: 'stretch', sm: 'center' },
|
||||||
|
|||||||
@@ -390,80 +390,90 @@ export function UserCompletionPage() {
|
|||||||
maxWidth="730px"
|
maxWidth="730px"
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
component="form"
|
||||||
display: 'flex',
|
id="completionForm"
|
||||||
flexDirection: 'column',
|
onSubmit={(e) => {
|
||||||
gap: 2,
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
void handleSubmit();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
<Box
|
||||||
<Typography variant="h5" color="text.primary">
|
sx={{
|
||||||
{t('completion.title')}
|
display: 'flex',
|
||||||
</Typography>
|
flexDirection: 'column',
|
||||||
<Typography variant="body2" color="text.secondary">
|
gap: 2,
|
||||||
{t('completion.description')}
|
}}
|
||||||
</Typography>
|
>
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||||
|
<Typography variant="h5" color="text.primary">
|
||||||
|
{t('completion.title')}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
{t('completion.description')}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<PersonalInfoFields
|
||||||
|
firstName={firstName}
|
||||||
|
setFirstName={setFirstName}
|
||||||
|
lastName={lastName}
|
||||||
|
setLastName={setLastName}
|
||||||
|
nationalId={nationalId}
|
||||||
|
setNationalId={setNationalId}
|
||||||
|
birthDate={birthDate}
|
||||||
|
setBirthDate={setBirthDate}
|
||||||
|
sex={sex}
|
||||||
|
setSex={setSex}
|
||||||
|
country={country}
|
||||||
|
setCountry={setCountry}
|
||||||
|
errors={errors}
|
||||||
|
touched={touched}
|
||||||
|
handleBlur={handleBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PasswordSection
|
||||||
|
showPasswordSection={showPasswordSection}
|
||||||
|
setShowPasswordSection={setShowPasswordSection}
|
||||||
|
password={password}
|
||||||
|
setPassword={setPassword}
|
||||||
|
confirmPassword={confirmPassword}
|
||||||
|
setConfirmPassword={setConfirmPassword}
|
||||||
|
matchPassword={matchPassword}
|
||||||
|
hasNumber={hasNumber}
|
||||||
|
hasMinLength={hasMinLength}
|
||||||
|
hasUpperAndLower={hasUpperAndLower}
|
||||||
|
hasSpecialChar={hasSpecialChar}
|
||||||
|
validPassword={validPassword}
|
||||||
|
errors={errors}
|
||||||
|
touched={touched}
|
||||||
|
handleBlur={handleBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<EmailSection
|
||||||
|
showEmail={showEmail}
|
||||||
|
setShowEmail={setShowEmail}
|
||||||
|
email={email}
|
||||||
|
setEmail={setEmail}
|
||||||
|
correctEmail={correctEmail}
|
||||||
|
codeSent={codeSent}
|
||||||
|
verificationCode={verificationCode}
|
||||||
|
setVerificationCode={setVerificationCode}
|
||||||
|
buttonState={buttonState}
|
||||||
|
countdown={countdown}
|
||||||
|
handleSendCode={handleSendCode}
|
||||||
|
handleVerifyCode={handleVerifyCode}
|
||||||
|
emailVerified={emailVerified}
|
||||||
|
isVerifyingCode={isVerifyingCode}
|
||||||
|
isSendingCode={isSendingCode}
|
||||||
|
handleEditEmail={handleEditEmail}
|
||||||
|
errors={errors}
|
||||||
|
touched={touched}
|
||||||
|
handleBlur={handleBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SubmitSection loading={isSubmitting} />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<PersonalInfoFields
|
|
||||||
firstName={firstName}
|
|
||||||
setFirstName={setFirstName}
|
|
||||||
lastName={lastName}
|
|
||||||
setLastName={setLastName}
|
|
||||||
nationalId={nationalId}
|
|
||||||
setNationalId={setNationalId}
|
|
||||||
birthDate={birthDate}
|
|
||||||
setBirthDate={setBirthDate}
|
|
||||||
sex={sex}
|
|
||||||
setSex={setSex}
|
|
||||||
country={country}
|
|
||||||
setCountry={setCountry}
|
|
||||||
errors={errors}
|
|
||||||
touched={touched}
|
|
||||||
handleBlur={handleBlur}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PasswordSection
|
|
||||||
showPasswordSection={showPasswordSection}
|
|
||||||
setShowPasswordSection={setShowPasswordSection}
|
|
||||||
password={password}
|
|
||||||
setPassword={setPassword}
|
|
||||||
confirmPassword={confirmPassword}
|
|
||||||
setConfirmPassword={setConfirmPassword}
|
|
||||||
matchPassword={matchPassword}
|
|
||||||
hasNumber={hasNumber}
|
|
||||||
hasMinLength={hasMinLength}
|
|
||||||
hasUpperAndLower={hasUpperAndLower}
|
|
||||||
hasSpecialChar={hasSpecialChar}
|
|
||||||
validPassword={validPassword}
|
|
||||||
errors={errors}
|
|
||||||
touched={touched}
|
|
||||||
handleBlur={handleBlur}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<EmailSection
|
|
||||||
showEmail={showEmail}
|
|
||||||
setShowEmail={setShowEmail}
|
|
||||||
email={email}
|
|
||||||
setEmail={setEmail}
|
|
||||||
correctEmail={correctEmail}
|
|
||||||
codeSent={codeSent}
|
|
||||||
verificationCode={verificationCode}
|
|
||||||
setVerificationCode={setVerificationCode}
|
|
||||||
buttonState={buttonState}
|
|
||||||
countdown={countdown}
|
|
||||||
handleSendCode={handleSendCode}
|
|
||||||
handleVerifyCode={handleVerifyCode}
|
|
||||||
emailVerified={emailVerified}
|
|
||||||
isVerifyingCode={isVerifyingCode}
|
|
||||||
isSendingCode={isSendingCode}
|
|
||||||
handleEditEmail={handleEditEmail}
|
|
||||||
errors={errors}
|
|
||||||
touched={touched}
|
|
||||||
handleBlur={handleBlur}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SubmitSection onSubmit={handleSubmit} loading={isSubmitting} />
|
|
||||||
</Box>
|
</Box>
|
||||||
</AuthenticationCard>
|
</AuthenticationCard>
|
||||||
</FlexBox>
|
</FlexBox>
|
||||||
|
|||||||
@@ -72,6 +72,5 @@ export interface PersonalInfoFieldsProps extends ValidationProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SubmitProps {
|
export interface SubmitProps {
|
||||||
onSubmit: () => void;
|
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,12 @@ export function PasswordDialog({
|
|||||||
const [showConfirm, setShowConfirm] = useState(false);
|
const [showConfirm, setShowConfirm] = useState(false);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const onFormSubmit: React.FormEventHandler<HTMLFormElement> = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
if (!loading) handleSubmit();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
open={open}
|
open={open}
|
||||||
@@ -69,120 +75,132 @@ export function PasswordDialog({
|
|||||||
</Box>
|
</Box>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
|
|
||||||
<DialogContent
|
<Box component="form" onSubmit={onFormSubmit}>
|
||||||
sx={{
|
<DialogContent
|
||||||
display: 'flex',
|
sx={{
|
||||||
flexDirection: 'column',
|
display: 'flex',
|
||||||
gap: 2,
|
flexDirection: 'column',
|
||||||
px: { xs: 2, sm: 3 },
|
gap: 2,
|
||||||
}}
|
px: { xs: 2, sm: 3 },
|
||||||
>
|
|
||||||
{changePassword && (
|
|
||||||
<>
|
|
||||||
<TextField
|
|
||||||
label={t('securityForm.currentPassword')}
|
|
||||||
type={showCurrent ? 'text' : 'password'}
|
|
||||||
fullWidth
|
|
||||||
value={currentPassword}
|
|
||||||
onChange={(e) => setCurrentPassword(e.target.value)}
|
|
||||||
sx={{ '& .MuiOutlinedInput-root': { borderRadius: 2 }, mt: 2 }}
|
|
||||||
InputProps={{
|
|
||||||
endAdornment: (
|
|
||||||
<InputAdornment position="end">
|
|
||||||
<IconButton onClick={() => setShowCurrent(!showCurrent)}>
|
|
||||||
<Icon Component={showCurrent ? Eye : EyeSlash} />
|
|
||||||
</IconButton>
|
|
||||||
</InputAdornment>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Link
|
|
||||||
sx={{ fontSize: '15px', cursor: 'pointer' }}
|
|
||||||
onClick={() => navigate('/forget-password')}
|
|
||||||
>
|
|
||||||
{t('securityForm.forgetPassword')}
|
|
||||||
</Link>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<TextField
|
|
||||||
label={t('securityForm.newPassword')}
|
|
||||||
type={showNew ? 'text' : 'password'}
|
|
||||||
fullWidth
|
|
||||||
value={password}
|
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
|
||||||
sx={{ '& .MuiOutlinedInput-root': { borderRadius: 2 }, mt: 2 }}
|
|
||||||
InputProps={{
|
|
||||||
endAdornment: (
|
|
||||||
<InputAdornment position="end">
|
|
||||||
<IconButton onClick={() => setShowNew(!showNew)}>
|
|
||||||
<Icon Component={showNew ? Eye : EyeSlash} />
|
|
||||||
</IconButton>
|
|
||||||
</InputAdornment>
|
|
||||||
),
|
|
||||||
}}
|
}}
|
||||||
/>
|
|
||||||
|
|
||||||
{showValidation && (
|
|
||||||
<Box sx={{ mt: 1 }}>
|
|
||||||
<PasswordValidationItem
|
|
||||||
isValid={hasNumber}
|
|
||||||
label={t('securityForm.hasNumber')}
|
|
||||||
/>
|
|
||||||
<PasswordValidationItem
|
|
||||||
isValid={hasMinLength}
|
|
||||||
label={t('securityForm.hasMinLength')}
|
|
||||||
/>
|
|
||||||
<PasswordValidationItem
|
|
||||||
isValid={hasUpperAndLower}
|
|
||||||
label={t('securityForm.hasUpperAndLower')}
|
|
||||||
/>
|
|
||||||
<PasswordValidationItem
|
|
||||||
isValid={hasSpecialChar}
|
|
||||||
label={t('securityForm.hasSpecialChar')}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<TextField
|
|
||||||
label={t('securityForm.confirmPassword')}
|
|
||||||
type={showConfirm ? 'text' : 'password'}
|
|
||||||
fullWidth
|
|
||||||
value={confirmPassword}
|
|
||||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
||||||
error={confirmPassword.length > 0 && !matchPassword}
|
|
||||||
helperText={
|
|
||||||
confirmPassword.length > 0 && !matchPassword
|
|
||||||
? t('securityForm.notCompatibility')
|
|
||||||
: ' '
|
|
||||||
}
|
|
||||||
sx={{ '& .MuiOutlinedInput-root': { borderRadius: 2 } }}
|
|
||||||
InputProps={{
|
|
||||||
endAdornment: (
|
|
||||||
<InputAdornment position="end">
|
|
||||||
<IconButton onClick={() => setShowConfirm(!showConfirm)}>
|
|
||||||
<Icon Component={showConfirm ? Eye : EyeSlash} />
|
|
||||||
</IconButton>
|
|
||||||
</InputAdornment>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</DialogContent>
|
|
||||||
|
|
||||||
<DialogActions sx={{ px: 3, pb: 2, justifyContent: 'center' }}>
|
|
||||||
<Button
|
|
||||||
fullWidth
|
|
||||||
sx={{ height: 48, textTransform: 'none' }}
|
|
||||||
variant="contained"
|
|
||||||
onClick={handleSubmit}
|
|
||||||
>
|
>
|
||||||
{loading ? (
|
{changePassword && (
|
||||||
<CircularProgress size={24} color="inherit" />
|
<>
|
||||||
) : (
|
<TextField
|
||||||
t('securityForm.confirm')
|
label={t('securityForm.currentPassword')}
|
||||||
|
type={showCurrent ? 'text' : 'password'}
|
||||||
|
fullWidth
|
||||||
|
value={currentPassword}
|
||||||
|
onChange={(e) => setCurrentPassword(e.target.value)}
|
||||||
|
sx={{ '& .MuiOutlinedInput-root': { borderRadius: 2 }, mt: 2 }}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton
|
||||||
|
onClick={() => setShowCurrent(!showCurrent)}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<Icon Component={showCurrent ? Eye : EyeSlash} />
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Link
|
||||||
|
sx={{ fontSize: '15px', cursor: 'pointer' }}
|
||||||
|
onClick={() => navigate('/forget-password')}
|
||||||
|
>
|
||||||
|
{t('securityForm.forgetPassword')}
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
<TextField
|
||||||
|
label={t('securityForm.newPassword')}
|
||||||
|
type={showNew ? 'text' : 'password'}
|
||||||
|
fullWidth
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
sx={{ '& .MuiOutlinedInput-root': { borderRadius: 2 }, mt: 2 }}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton
|
||||||
|
onClick={() => setShowNew(!showNew)}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<Icon Component={showNew ? Eye : EyeSlash} />
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{showValidation && (
|
||||||
|
<Box sx={{ mt: 1 }}>
|
||||||
|
<PasswordValidationItem
|
||||||
|
isValid={hasNumber}
|
||||||
|
label={t('securityForm.hasNumber')}
|
||||||
|
/>
|
||||||
|
<PasswordValidationItem
|
||||||
|
isValid={hasMinLength}
|
||||||
|
label={t('securityForm.hasMinLength')}
|
||||||
|
/>
|
||||||
|
<PasswordValidationItem
|
||||||
|
isValid={hasUpperAndLower}
|
||||||
|
label={t('securityForm.hasUpperAndLower')}
|
||||||
|
/>
|
||||||
|
<PasswordValidationItem
|
||||||
|
isValid={hasSpecialChar}
|
||||||
|
label={t('securityForm.hasSpecialChar')}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
label={t('securityForm.confirmPassword')}
|
||||||
|
type={showConfirm ? 'text' : 'password'}
|
||||||
|
fullWidth
|
||||||
|
value={confirmPassword}
|
||||||
|
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||||
|
error={confirmPassword.length > 0 && !matchPassword}
|
||||||
|
helperText={
|
||||||
|
confirmPassword.length > 0 && !matchPassword
|
||||||
|
? t('securityForm.notCompatibility')
|
||||||
|
: ' '
|
||||||
|
}
|
||||||
|
sx={{ '& .MuiOutlinedInput-root': { borderRadius: 2 } }}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton
|
||||||
|
onClick={() => setShowConfirm(!showConfirm)}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<Icon Component={showConfirm ? Eye : EyeSlash} />
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
|
||||||
|
<DialogActions sx={{ px: 3, pb: 2, justifyContent: 'center' }}>
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
sx={{ height: 48, textTransform: 'none' }}
|
||||||
|
variant="contained"
|
||||||
|
type="submit"
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<CircularProgress size={24} color="inherit" />
|
||||||
|
) : (
|
||||||
|
t('securityForm.confirm')
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Box>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import { useProfile } from '../../hooks/useProfile';
|
|||||||
|
|
||||||
export function PersonalInformation() {
|
export function PersonalInformation() {
|
||||||
const imageBaseUrl = import.meta.env.IMAGE_BASE_URL;
|
const imageBaseUrl = import.meta.env.IMAGE_BASE_URL;
|
||||||
|
|
||||||
const { t } = useTranslation('setting');
|
const { t } = useTranslation('setting');
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
const [uploadedImageUrl, setUploadedImageUrl] = useState<string | null>(null);
|
const [uploadedImageUrl, setUploadedImageUrl] = useState<string | null>(null);
|
||||||
@@ -29,18 +28,14 @@ export function PersonalInformation() {
|
|||||||
const [originalData, setOriginalData] = useState<InfoRowData | null>(null);
|
const [originalData, setOriginalData] = useState<InfoRowData | null>(null);
|
||||||
const showToast = useToast();
|
const showToast = useToast();
|
||||||
const infoRowEditRef = useRef<{ validateFields: () => boolean }>(null);
|
const infoRowEditRef = useRef<{ validateFields: () => boolean }>(null);
|
||||||
|
|
||||||
const { isLoadingProfile, refetchProfile } = useProfile();
|
const { isLoadingProfile, refetchProfile } = useProfile();
|
||||||
|
|
||||||
const { loading: isSavingProfile, execute: executeSaveProfile } =
|
const { loading: isSavingProfile, execute: executeSaveProfile } =
|
||||||
useApi(saveProfile);
|
useApi(saveProfile);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadProfile = async () => {
|
const loadProfile = async () => {
|
||||||
const profileData = await refetchProfile();
|
const profileData = await refetchProfile();
|
||||||
|
|
||||||
if (!profileData) return;
|
if (!profileData) return;
|
||||||
|
|
||||||
if (profileData.success) {
|
if (profileData.success) {
|
||||||
const fetchedData = {
|
const fetchedData = {
|
||||||
firstName: profileData.firstName ?? '',
|
firstName: profileData.firstName ?? '',
|
||||||
@@ -66,7 +61,6 @@ export function PersonalInformation() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadProfile();
|
loadProfile();
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
@@ -85,14 +79,11 @@ export function PersonalInformation() {
|
|||||||
if (!data) return;
|
if (!data) return;
|
||||||
const isValid = infoRowEditRef.current?.validateFields?.();
|
const isValid = infoRowEditRef.current?.validateFields?.();
|
||||||
if (!isValid) return;
|
if (!isValid) return;
|
||||||
|
|
||||||
const saveData = await executeSaveProfile({
|
const saveData = await executeSaveProfile({
|
||||||
data,
|
data,
|
||||||
imageUrl: uploadedImageFile,
|
imageUrl: uploadedImageFile,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!saveData) return;
|
if (!saveData) return;
|
||||||
|
|
||||||
if (saveData?.success) {
|
if (saveData?.success) {
|
||||||
showToast({
|
showToast({
|
||||||
message: t('settingForm.successSaveProfile'),
|
message: t('settingForm.successSaveProfile'),
|
||||||
@@ -101,7 +92,6 @@ export function PersonalInformation() {
|
|||||||
setIsEditing(false);
|
setIsEditing(false);
|
||||||
setOriginalData(data);
|
setOriginalData(data);
|
||||||
setUploadedImageFile(null);
|
setUploadedImageFile(null);
|
||||||
// Force a refetch to update the cache with the new data
|
|
||||||
refetchProfile({ force: true });
|
refetchProfile({ force: true });
|
||||||
} else {
|
} else {
|
||||||
showToast({
|
showToast({
|
||||||
@@ -122,6 +112,7 @@ export function PersonalInformation() {
|
|||||||
{isEditing ? (
|
{isEditing ? (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
|
type="button"
|
||||||
variant="text"
|
variant="text"
|
||||||
onClick={handleCancelClick}
|
onClick={handleCancelClick}
|
||||||
size="large"
|
size="large"
|
||||||
@@ -134,9 +125,11 @@ export function PersonalInformation() {
|
|||||||
{t('settingForm.rejectButton')}
|
{t('settingForm.rejectButton')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleSaveClick}
|
type="submit"
|
||||||
|
form="profileForm"
|
||||||
size="large"
|
size="large"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
|
disabled={isSavingProfile}
|
||||||
sx={{
|
sx={{
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
width: { xs: '100%', sm: 'auto' },
|
width: { xs: '100%', sm: 'auto' },
|
||||||
@@ -174,6 +167,42 @@ export function PersonalInformation() {
|
|||||||
>
|
>
|
||||||
<CircularProgress />
|
<CircularProgress />
|
||||||
</Box>
|
</Box>
|
||||||
|
) : isEditing ? (
|
||||||
|
<Box
|
||||||
|
component="form"
|
||||||
|
id="profileForm"
|
||||||
|
noValidate
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
if (!isSavingProfile) void handleSaveClick();
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
mx: { xs: 2, sm: 3, md: 4 },
|
||||||
|
py: 2,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 2,
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ProfileImage
|
||||||
|
initials={initials}
|
||||||
|
uploadedImageUrl={uploadedImageUrl}
|
||||||
|
onImageChange={(file) => {
|
||||||
|
setUploadedImageFile(file);
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () =>
|
||||||
|
setUploadedImageUrl(reader.result as string);
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}}
|
||||||
|
onRemoveImage={() => {
|
||||||
|
setUploadedImageFile(null);
|
||||||
|
setUploadedImageUrl(null);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<InfoRowEdit ref={infoRowEditRef} data={data} setData={setData} />
|
||||||
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@@ -185,37 +214,11 @@ export function PersonalInformation() {
|
|||||||
bgcolor: 'background.paper',
|
bgcolor: 'background.paper',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isEditing && (
|
<InfoRowDisplay
|
||||||
<ProfileImage
|
data={data}
|
||||||
initials={initials}
|
uploadedImageUrl={uploadedImageUrl}
|
||||||
uploadedImageUrl={uploadedImageUrl}
|
initials={initials}
|
||||||
onImageChange={(file) => {
|
/>
|
||||||
setUploadedImageFile(file);
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = () =>
|
|
||||||
setUploadedImageUrl(reader.result as string);
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
}}
|
|
||||||
onRemoveImage={() => {
|
|
||||||
setUploadedImageFile(null);
|
|
||||||
setUploadedImageUrl(null);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{data &&
|
|
||||||
(isEditing ? (
|
|
||||||
<InfoRowEdit
|
|
||||||
ref={infoRowEditRef}
|
|
||||||
data={data}
|
|
||||||
setData={setData}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<InfoRowDisplay
|
|
||||||
data={data}
|
|
||||||
uploadedImageUrl={uploadedImageUrl}
|
|
||||||
initials={initials}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</CardContainer>
|
</CardContainer>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import parsePhoneNumberFromString from 'libphonenumber-js';
|
import parsePhoneNumberFromString from 'libphonenumber-js';
|
||||||
import { PageWrapper } from '../PageWrapper';
|
import { PageWrapper } from '../PageWrapper';
|
||||||
@@ -40,20 +40,21 @@ export function PhoneNumber() {
|
|||||||
|
|
||||||
const { loading: isSendingCode, execute: executeSendCode } =
|
const { loading: isSendingCode, execute: executeSendCode } =
|
||||||
useApi(sendVerificationCode);
|
useApi(sendVerificationCode);
|
||||||
|
|
||||||
const { loading: isVerifying, execute: executeConfirmCode } = useApi(
|
const { loading: isVerifying, execute: executeConfirmCode } = useApi(
|
||||||
confirmPhoneNumberCode,
|
confirmPhoneNumberCode,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { loading: isChangingPhone, execute: executeChangePhone } =
|
const { loading: isChangingPhone, execute: executeChangePhone } =
|
||||||
useApi(changePhoneNumber);
|
useApi(changePhoneNumber);
|
||||||
|
|
||||||
|
const isBusy = useMemo(
|
||||||
|
() => isVerifying || isSendingCode || isChangingPhone,
|
||||||
|
[isVerifying, isSendingCode, isChangingPhone],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadProfile = async () => {
|
const loadProfile = async () => {
|
||||||
const profileData = await refetchProfile();
|
const profileData = await refetchProfile();
|
||||||
|
|
||||||
if (!profileData) return;
|
if (!profileData) return;
|
||||||
|
|
||||||
if (profileData?.success) {
|
if (profileData?.success) {
|
||||||
setPhones([
|
setPhones([
|
||||||
{
|
{
|
||||||
@@ -69,36 +70,32 @@ export function PhoneNumber() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
loadProfile();
|
void loadProfile();
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [refetchProfile, toast]);
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!phoneNumberTouched) return;
|
||||||
if (!phoneNumber) {
|
if (!phoneNumber) {
|
||||||
setPhoneNumberError(t('settingForm.thisFieldIsRequired'));
|
setPhoneNumberError(t('settingForm.thisFieldIsRequired'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!isPhoneValid(countryCode, phoneNumber)) {
|
const parsed = parsePhoneNumberFromString(countryCode + phoneNumber);
|
||||||
|
const valid = Boolean(parsed?.isValid());
|
||||||
|
if (!valid) {
|
||||||
setPhoneNumberError(t('settingForm.phoneNumberIsInvalid'));
|
setPhoneNumberError(t('settingForm.phoneNumberIsInvalid'));
|
||||||
} else {
|
} else {
|
||||||
setPhoneNumberError(undefined);
|
setPhoneNumberError(undefined);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [countryCode, phoneNumber, phoneNumberTouched, t]);
|
||||||
}, [countryCode, phoneNumber, phoneNumberTouched]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!verificationCodeTouched) return;
|
||||||
if (!verificationCode) {
|
if (!verificationCode) {
|
||||||
setVerificationCodeError(t('settingForm.verificationCodeRequired'));
|
setVerificationCodeError(t('settingForm.verificationCodeRequired'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setVerificationCodeError(undefined);
|
setVerificationCodeError(undefined);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [verificationCode, verificationCodeTouched, t]);
|
||||||
}, [verificationCode, verificationCodeTouched]);
|
|
||||||
|
|
||||||
const isPhoneValid = (code: string, phone: string) => {
|
|
||||||
const phoneNum = parsePhoneNumberFromString(code + phone);
|
|
||||||
return phoneNum?.isValid();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBlur = (key: string) => {
|
const handleBlur = (key: string) => {
|
||||||
if (key === 'phoneNumber') {
|
if (key === 'phoneNumber') {
|
||||||
@@ -126,14 +123,11 @@ export function PhoneNumber() {
|
|||||||
|
|
||||||
const handleSendCode = async () => {
|
const handleSendCode = async () => {
|
||||||
setPhoneNumberTouched(true);
|
setPhoneNumberTouched(true);
|
||||||
if (phoneNumberError) return;
|
if (!phoneNumber || phoneNumberError) return;
|
||||||
|
|
||||||
const result = await executeSendCode({
|
const result = await executeSendCode({
|
||||||
phoneNumber: countryCode + phoneNumber.replace(/^0/, ''),
|
phoneNumber: countryCode + phoneNumber.replace(/^0/, ''),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
|
|
||||||
if (result?.success) {
|
if (result?.success) {
|
||||||
setButtonState('counting');
|
setButtonState('counting');
|
||||||
setIsVerified(false);
|
setIsVerified(false);
|
||||||
@@ -151,18 +145,13 @@ export function PhoneNumber() {
|
|||||||
|
|
||||||
const handleVerifyCode = async () => {
|
const handleVerifyCode = async () => {
|
||||||
setVerificationCodeTouched(true);
|
setVerificationCodeTouched(true);
|
||||||
|
if (!verificationCode || verificationCodeError) return;
|
||||||
if (verificationCodeError) return;
|
|
||||||
|
|
||||||
const fullPhoneNumber = countryCode + phoneNumber.replace(/^0/, '');
|
const fullPhoneNumber = countryCode + phoneNumber.replace(/^0/, '');
|
||||||
|
|
||||||
const confirmResult = await executeConfirmCode({
|
const confirmResult = await executeConfirmCode({
|
||||||
phoneNumber: fullPhoneNumber,
|
phoneNumber: fullPhoneNumber,
|
||||||
verifyCode: verificationCode,
|
verifyCode: verificationCode,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!confirmResult) return;
|
if (!confirmResult) return;
|
||||||
|
|
||||||
if (!confirmResult.success || !confirmResult.confirm) {
|
if (!confirmResult.success || !confirmResult.confirm) {
|
||||||
toast({
|
toast({
|
||||||
message: confirmResult?.message || t('message.serverError'),
|
message: confirmResult?.message || t('message.serverError'),
|
||||||
@@ -170,19 +159,15 @@ export function PhoneNumber() {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsVerified(true);
|
setIsVerified(true);
|
||||||
toast({
|
toast({
|
||||||
message: t('settingForm.phoneVerified'),
|
message: t('settingForm.phoneVerified'),
|
||||||
severity: 'success',
|
severity: 'success',
|
||||||
});
|
});
|
||||||
|
|
||||||
const changeResult = await executeChangePhone({
|
const changeResult = await executeChangePhone({
|
||||||
phoneNumber: fullPhoneNumber,
|
phoneNumber: fullPhoneNumber,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!changeResult) return;
|
if (!changeResult) return;
|
||||||
|
|
||||||
if (changeResult?.success) {
|
if (changeResult?.success) {
|
||||||
setPhones([
|
setPhones([
|
||||||
{
|
{
|
||||||
@@ -212,6 +197,8 @@ export function PhoneNumber() {
|
|||||||
isEditing={isEditing}
|
isEditing={isEditing}
|
||||||
toggleEdit={toggleEdit}
|
toggleEdit={toggleEdit}
|
||||||
t={t}
|
t={t}
|
||||||
|
formId="phoneForm"
|
||||||
|
isSubmitting={isBusy}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -228,27 +215,40 @@ export function PhoneNumber() {
|
|||||||
<CircularProgress />
|
<CircularProgress />
|
||||||
</Box>
|
</Box>
|
||||||
) : isEditing ? (
|
) : isEditing ? (
|
||||||
<PhoneEditForm
|
<Box
|
||||||
phoneNumber={phoneNumber}
|
component="form"
|
||||||
setPhoneNumber={setPhoneNumber}
|
id="phoneForm"
|
||||||
countryCode={countryCode}
|
noValidate
|
||||||
setCountryCode={setCountryCode}
|
onSubmit={(e) => {
|
||||||
verificationCode={verificationCode}
|
e.preventDefault();
|
||||||
setVerificationCode={setVerificationCode}
|
e.stopPropagation();
|
||||||
isVerified={isVerified}
|
if (!isBusy) void handleVerifyCode();
|
||||||
isVerifying={isVerifying || isSendingCode || isChangingPhone}
|
}}
|
||||||
isSendingCode={isSendingCode}
|
>
|
||||||
buttonState={buttonState}
|
<PhoneEditForm
|
||||||
setButtonState={setButtonState}
|
phoneNumber={phoneNumber}
|
||||||
handleSendCode={handleSendCode}
|
setPhoneNumber={setPhoneNumber}
|
||||||
handleVerifyClick={handleVerifyCode}
|
countryCode={countryCode}
|
||||||
phoneNumberError={phoneNumberTouched ? phoneNumberError : undefined}
|
setCountryCode={setCountryCode}
|
||||||
verificationCodeError={
|
verificationCode={verificationCode}
|
||||||
verificationCodeTouched ? verificationCodeError : undefined
|
setVerificationCode={setVerificationCode}
|
||||||
}
|
isVerified={isVerified}
|
||||||
handleBlur={handleBlur}
|
isVerifying={isBusy}
|
||||||
phones={phones}
|
isSendingCode={isSendingCode}
|
||||||
/>
|
buttonState={buttonState}
|
||||||
|
setButtonState={setButtonState}
|
||||||
|
handleSendCode={handleSendCode}
|
||||||
|
handleVerifyClick={handleVerifyCode}
|
||||||
|
phoneNumberError={
|
||||||
|
phoneNumberTouched ? phoneNumberError : undefined
|
||||||
|
}
|
||||||
|
verificationCodeError={
|
||||||
|
verificationCodeTouched ? verificationCodeError : undefined
|
||||||
|
}
|
||||||
|
handleBlur={handleBlur}
|
||||||
|
phones={phones}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<PhoneDisplay
|
<PhoneDisplay
|
||||||
phones={phones.map((p) => {
|
phones={phones.map((p) => {
|
||||||
|
|||||||
@@ -8,36 +8,51 @@ export default function PhoneActionButtons({
|
|||||||
}: PhoneActionButtonsProps) {
|
}: PhoneActionButtonsProps) {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||||
{isEditing && (
|
{isEditing ? (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="text"
|
||||||
|
onClick={toggleEdit}
|
||||||
|
size="large"
|
||||||
|
sx={{
|
||||||
|
color: 'primary.main',
|
||||||
|
textTransform: 'none',
|
||||||
|
width: { xs: '100%', sm: 'auto' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('settingForm.rejectButton')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
form="phoneForm"
|
||||||
|
size="large"
|
||||||
|
variant="outlined"
|
||||||
|
sx={{
|
||||||
|
borderRadius: 1,
|
||||||
|
bgcolor: 'primary.main',
|
||||||
|
color: 'primary.contrastText',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
textTransform: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('settingForm.saveButton')}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
<Button
|
<Button
|
||||||
variant="text"
|
|
||||||
onClick={toggleEdit}
|
onClick={toggleEdit}
|
||||||
size="large"
|
size="large"
|
||||||
|
variant="outlined"
|
||||||
sx={{
|
sx={{
|
||||||
color: 'primary.main',
|
borderRadius: 1,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
width: { xs: '100%', sm: 'auto' },
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('settingForm.rejectButton')}
|
{t('settingForm.editPhoneNumber')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button
|
|
||||||
onClick={toggleEdit}
|
|
||||||
size="large"
|
|
||||||
variant="outlined"
|
|
||||||
sx={{
|
|
||||||
borderRadius: 1,
|
|
||||||
bgcolor: isEditing ? 'primary.main' : 'background.default',
|
|
||||||
color: isEditing ? 'primary.contrastText' : 'primary.main',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
textTransform: 'none',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isEditing
|
|
||||||
? t('settingForm.saveButton')
|
|
||||||
: t('settingForm.editPhoneNumber')}
|
|
||||||
</Button>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,21 @@ export default function SocialMediaDialog({
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSubmit: React.FormEventHandler<HTMLFormElement> = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
if (isLoading) return;
|
||||||
|
|
||||||
|
if (dialogStep === 'enterEmail') {
|
||||||
|
setTouched(true);
|
||||||
|
if (!validateEmail(emailInput)) return;
|
||||||
|
onSendCode();
|
||||||
|
} else {
|
||||||
|
onConfirmEmail();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
open={open}
|
open={open}
|
||||||
@@ -77,76 +92,80 @@ export default function SocialMediaDialog({
|
|||||||
</Box>
|
</Box>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
|
|
||||||
<DialogContent
|
<Box component="form" onSubmit={handleSubmit}>
|
||||||
sx={{
|
<DialogContent
|
||||||
display: 'flex',
|
sx={{
|
||||||
flexDirection: 'column',
|
display: 'flex',
|
||||||
gap: 2,
|
flexDirection: 'column',
|
||||||
px: { xs: 2, sm: 3 },
|
gap: 2,
|
||||||
}}
|
px: { xs: 2, sm: 3 },
|
||||||
>
|
|
||||||
<Box>
|
|
||||||
<Typography fontWeight="bold">{t('settingForm.newEmail')}</Typography>
|
|
||||||
<Typography variant="body2" color="text.secondary">
|
|
||||||
{t('settingForm.dialogHeader')}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<TextField
|
|
||||||
fullWidth
|
|
||||||
type="email"
|
|
||||||
value={emailInput}
|
|
||||||
onChange={(e) => {
|
|
||||||
setEmailInput(e.target.value);
|
|
||||||
if (touched) validateEmail(e.target.value);
|
|
||||||
}}
|
}}
|
||||||
onBlur={() => {
|
>
|
||||||
setTouched(true);
|
<Box>
|
||||||
validateEmail(emailInput);
|
<Typography fontWeight="bold">
|
||||||
}}
|
{t('settingForm.newEmail')}
|
||||||
label={t('settingForm.email')}
|
</Typography>
|
||||||
placeholder="abc@email.com"
|
<Typography variant="body2" color="text.secondary">
|
||||||
autoComplete="email"
|
{t('settingForm.dialogHeader')}
|
||||||
inputMode="email"
|
</Typography>
|
||||||
sx={{ '& .MuiOutlinedInput-root': { borderRadius: 2 }, mt: 2 }}
|
</Box>
|
||||||
autoFocus
|
|
||||||
disabled={isLoading || dialogStep === 'enterCode'}
|
|
||||||
error={touched && !!emailError}
|
|
||||||
helperText={touched && emailError ? emailError : ' '}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{dialogStep === 'enterCode' && (
|
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
type="text"
|
type="email"
|
||||||
value={verificationCode}
|
value={emailInput}
|
||||||
onChange={(e) => setVerificationCode(e.target.value)}
|
onChange={(e) => {
|
||||||
label={t('settingForm.verificationCode')}
|
setEmailInput(e.target.value);
|
||||||
autoComplete="one-time-code"
|
if (touched) validateEmail(e.target.value);
|
||||||
inputMode="numeric"
|
}}
|
||||||
sx={{ '& .MuiOutlinedInput-root': { borderRadius: 2 } }}
|
onBlur={() => {
|
||||||
autoFocus
|
setTouched(true);
|
||||||
disabled={isLoading}
|
validateEmail(emailInput);
|
||||||
|
}}
|
||||||
|
label={t('settingForm.email')}
|
||||||
|
placeholder="abc@email.com"
|
||||||
|
autoComplete="email"
|
||||||
|
inputMode="email"
|
||||||
|
sx={{ '& .MuiOutlinedInput-root': { borderRadius: 2 }, mt: 2 }}
|
||||||
|
autoFocus={dialogStep === 'enterEmail'}
|
||||||
|
disabled={isLoading || dialogStep === 'enterCode'}
|
||||||
|
error={touched && !!emailError}
|
||||||
|
helperText={touched && emailError ? emailError : ' '}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</DialogContent>
|
|
||||||
|
|
||||||
<Box sx={{ px: 3, pb: 2 }}>
|
{dialogStep === 'enterCode' && (
|
||||||
<Button
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
sx={{ height: 48, textTransform: 'none', borderRadius: 2 }}
|
type="text"
|
||||||
variant="contained"
|
value={verificationCode}
|
||||||
disabled={isLoading || (dialogStep === 'enterEmail' && !emailInput)}
|
onChange={(e) => setVerificationCode(e.target.value)}
|
||||||
onClick={dialogStep === 'enterEmail' ? onSendCode : onConfirmEmail}
|
label={t('settingForm.verificationCode')}
|
||||||
>
|
autoComplete="one-time-code"
|
||||||
{isLoading ? (
|
inputMode="numeric"
|
||||||
<CircularProgress size={24} color="inherit" />
|
sx={{ '& .MuiOutlinedInput-root': { borderRadius: 2 } }}
|
||||||
) : dialogStep === 'enterEmail' ? (
|
autoFocus
|
||||||
t('settingForm.verificationCodeButton')
|
disabled={isLoading}
|
||||||
) : (
|
/>
|
||||||
t('settingForm.confirmAndSave')
|
|
||||||
)}
|
)}
|
||||||
</Button>
|
</DialogContent>
|
||||||
|
|
||||||
|
<Box sx={{ px: 3, pb: 2 }}>
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
sx={{ height: 48, textTransform: 'none', borderRadius: 2 }}
|
||||||
|
variant="contained"
|
||||||
|
type="submit"
|
||||||
|
disabled={isLoading || (dialogStep === 'enterEmail' && !emailInput)}
|
||||||
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<CircularProgress size={24} color="inherit" />
|
||||||
|
) : dialogStep === 'enterEmail' ? (
|
||||||
|
t('settingForm.verificationCodeButton')
|
||||||
|
) : (
|
||||||
|
t('settingForm.confirmAndSave')
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { TFunction } from 'i18next';
|
||||||
|
|
||||||
export enum Gender {
|
export enum Gender {
|
||||||
Male = 2,
|
Male = 2,
|
||||||
Female = 1,
|
Female = 1,
|
||||||
@@ -93,7 +95,9 @@ export interface EmailAccount {
|
|||||||
export interface PhoneActionButtonsProps {
|
export interface PhoneActionButtonsProps {
|
||||||
isEditing: boolean;
|
isEditing: boolean;
|
||||||
toggleEdit: () => void;
|
toggleEdit: () => void;
|
||||||
t: (key: string) => string;
|
t: TFunction<'setting'>;
|
||||||
|
formId?: string;
|
||||||
|
isSubmitting?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PhoneDisplayProps {
|
export interface PhoneDisplayProps {
|
||||||
|
|||||||
Reference in New Issue
Block a user