feat: add using enter instead pushing the button

This commit is contained in:
Koosha Lahouti
2025-09-14 00:02:08 +03:30
parent 9ec1415f70
commit 19bbf55904
17 changed files with 675 additions and 559 deletions

View File

@@ -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 { useRef, useState, type Dispatch } from 'react';
import { useTranslation } from 'react-i18next';
@@ -69,47 +69,56 @@ export const CompleteSignUp = ({
return (
<AuthenticationCard>
<Typography variant="h5" sx={{ mb: 0.5 }}>
{t('completeSignUp.completeSignUp')}
</Typography>
<Typography variant="body2" color="textSecondary" sx={{ mt: 1, mb: 2 }}>
{t(
'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}
/>
),
},
<Box
component="form"
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
if (!sendSmsLoading) void handleCompleteSignUp();
}}
sx={{ my: 4 }}
/>
>
<Typography variant="h5" sx={{ mb: 0.5 }}>
{t('completeSignUp.completeSignUp')}
</Typography>
<Button loading={sendSmsLoading} onClick={handleCompleteSignUp}>
{t('verify.confirmAndContinue')}
</Button>
<Typography variant="body2" color="textSecondary" sx={{ mt: 1, mb: 2 }}>
{t(
'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>
);
};

View File

@@ -21,7 +21,6 @@ import {
import type { LoginResult, PasswordLoginRequest } from '../../types/userTypes';
import { Icon, useToast } from '@rkheftan/harmony-ui';
import { useApi } from '@/hooks/useApi';
import { useAuth } from '@/hooks/useAuth';
import {
generateTokenWithPassword,
type GenerateTokenResponse,

View File

@@ -14,7 +14,6 @@ import {
generateTokenWithGoogle,
type GenerateTokenResponse,
} from '../../api/identityAPI';
import { useAuth } from '@/hooks/useAuth';
import type { AuthFactory } from '../../types/authTypes';
export interface GoogleAuthenticationProps {

View File

@@ -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 { useTranslation } from 'react-i18next';
import { isNumeric } from '@/utils/regexes/isNumeric';
@@ -120,51 +120,62 @@ export function LoginRegisterForm({
return (
<AuthenticationCard>
<Stack spacing={1}>
<Typography variant="h5">{t('loginForm.title')}</Typography>
<Typography variant="body2" color="text.secondary">
{t('loginForm.description')}
</Typography>
</Stack>
<TextField
ref={textFieldRef}
inputRef={inputRef}
label={t('loginForm.emailOrPhoneLabel')}
value={loginRegisterValue}
onChange={handleInputChange}
onBlur={handleBlur}
error={inputError}
helperText={inputError ? error : ''}
autoFocus
slotProps={{
htmlInput: { dir: 'auto', sx: { lineHeight: 1.5, paddingX: 0 } },
input: {
endAdornment: (
<CountryCodeSelector
value={countryCode}
onChange={setCountryCode}
show={showAdornment}
menuAnchor={textFieldRef.current}
onCloseFocusRef={inputRef}
/>
),
},
<Box
component="form"
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
if (!userStatusLoading) {
void handleSubmit();
}
}}
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}>
<Button loading={userStatusLoading} onClick={handleSubmit}>
{t('loginForm.submitButton')}
</Button>
<GoogleAuthentication
authFactory={authFactory}
onGoogleAuthenticated={onGoogleAuthenticated}
disabled={userStatusLoading}
<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>
<Stack spacing={2}>
<Button loading={userStatusLoading} type="submit">
{t('loginForm.submitButton')}
</Button>
<GoogleAuthentication
authFactory={authFactory}
onGoogleAuthenticated={onGoogleAuthenticated}
disabled={userStatusLoading}
/>
</Stack>
</Box>
</AuthenticationCard>
);
}

View File

@@ -18,7 +18,6 @@ import {
generateTokenWithOtp,
type GenerateTokenResponse,
} from '../../api/identityAPI';
import { useAuth } from '@/hooks/useAuth';
interface OtpVerifyFormProps {
value: string;
@@ -166,43 +165,54 @@ export function OtpVerifyForm({
<Stack alignItems="center">
<AuthenticationCard>
<Box
sx={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: 4,
mb: 0.5,
component="form"
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
if (!loginSignUpLoading) {
void handleVerifyOTP();
}
}}
>
<Typography variant="h5">{t('verify.verify')}</Typography>
<Button
variant="outlined"
size="large"
sx={{ textTransform: 'lowercase', width: 'auto' }}
endIcon={<Icon Component={Edit2} />}
onClick={onEditValue}
<Box
sx={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: 4,
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>
</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>
<Stack

View File

@@ -96,43 +96,54 @@ export function VerifyPhoneNumber({
<Stack alignItems="center">
<AuthenticationCard>
<Box
sx={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: 4,
mb: 0.5,
component="form"
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
if (!confirmSmsOtpLoading) {
void handleVerifyOTP();
}
}}
>
<Typography variant="h5">{t('verify.verify')}</Typography>
<Button
variant="outlined"
size="large"
sx={{ textTransform: 'lowercase', width: 'auto' }}
endIcon={<Icon Component={Edit2} />}
onClick={onEditValue}
<Box
sx={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: 4,
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>
</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>
<Stack

View File

@@ -116,54 +116,62 @@ export function ForgetPasswordOtp({
<Stack alignItems="center">
<AuthenticationCard>
<Box
sx={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: 4,
mb: 0.5,
component="form"
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
if (!confirmForgetPassCodeLoading) {
void handleVerifyOTP();
}
}}
>
<Typography variant="h5">
{t('forgetPassword.forgetPassword')}
<Box
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>
<Button
variant="outlined"
size="large"
sx={{ textTransform: 'lowercase', width: 'auto' }}
endIcon={<Icon Component={Edit2} />}
onClick={onEditInfo}
>
{infoType === 'phone'
? countryCode + forgettedPasswordInfo
: forgettedPasswordInfo}
<DigitInput
error={otpDigitInvalid || isStatusSuccess === false}
success={isStatusSuccess === true}
onChange={(value) => setOtpCode(value)}
/>
<Button type="submit" loading={confirmForgetPassCodeLoading}>
{t('forgetPassword.confirm')}
</Button>
</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>
<Stack

View File

@@ -11,7 +11,7 @@ import {
import { useTranslation } from 'react-i18next';
import { type SubmitProps } from '../../types/settingForm';
export function SubmitSection({ onSubmit, loading }: SubmitProps) {
export function SubmitSection({ loading }: SubmitProps) {
const { t, i18n } = useTranslation('completionForm');
const [openDialog, setOpenDialog] = useState(false);
@@ -50,8 +50,9 @@ export function SubmitSection({ onSubmit, loading }: SubmitProps) {
<Button
variant="contained"
onClick={onSubmit}
type="submit"
disabled={loading}
form="completionForm"
sx={{
width: { xs: '100%', sm: '247px' },
alignSelf: { xs: 'stretch', sm: 'center' },

View File

@@ -390,80 +390,90 @@ export function UserCompletionPage() {
maxWidth="730px"
>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
gap: 2,
component="form"
id="completionForm"
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
void handleSubmit();
}}
>
<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
sx={{
display: 'flex',
flexDirection: 'column',
gap: 2,
}}
>
<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>
<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>
</AuthenticationCard>
</FlexBox>

View File

@@ -72,6 +72,5 @@ export interface PersonalInfoFieldsProps extends ValidationProps {
}
export interface SubmitProps {
onSubmit: () => void;
loading: boolean;
}