From 19bbf55904722ec709eda6b1d86c9c8f32e05ef8 Mon Sep 17 00:00:00 2001 From: Koosha Lahouti Date: Sun, 14 Sep 2025 00:02:08 +0330 Subject: [PATCH] feat: add using enter instead pushing the button --- .env | 4 +- .../AuthenticationSteps/CompleteSignUp.tsx | 89 ++++--- .../AuthenticationSteps/EnterPasswordForm.tsx | 1 - .../GoogleAuthentication.tsx | 1 - .../AuthenticationSteps/LoginRegiserForm.tsx | 97 +++---- .../AuthenticationSteps/OtpVerifyForm.tsx | 76 +++--- .../AuthenticationSteps/VerifyPhoneNumber.tsx | 75 +++--- .../ForgetPassword/ForgetPasswordOtp.tsx | 92 ++++--- .../UserCompletionForm/SubmitSection.tsx | 5 +- .../routes/UserCompletionPage.tsx | 152 +++++------ .../authentication/types/settingForm.ts | 1 - .../components/security/PasswordDialog.tsx | 240 ++++++++++-------- .../userInformation/PersonalInformation.tsx | 87 ++++--- .../userInformation/PhoneNumber.tsx | 106 ++++---- .../phoneNumber/PhoneActionButtons.tsx | 57 +++-- .../socialMedia/SocialMediaDialog.tsx | 145 ++++++----- src/features/profile/types/settingsType.ts | 6 +- 17 files changed, 675 insertions(+), 559 deletions(-) diff --git a/.env b/.env index 28a64fc..c7fa122 100644 --- a/.env +++ b/.env @@ -1,8 +1,8 @@ VITE_GOOGLE_CLIENT_ID=https://272098283932-bft2gvlgjn8edopg0lnqjq1i9ekdmipt.apps.googleusercontent.com VITE_DEFUALT_AUTH_RETURN_URL=/setting/profile VITE_APP_URL=https://accounts.business-harmony.com -VITE_API_URL=https://accounts.business-harmony.com/apiapp/api -VITE_IDENTITY_URL=https://accounts.business-harmony.com/apiapp/connect/token +VITE_API_URL=https://accounts.business-harmony.com/api +VITE_IDENTITY_URL=https://accounts.business-harmony.com/connect/token VITE_IDENTITY_CLIENT_ID=harmony_identity VITE_IDENTITY_SCOPE=openid profile offline_access IMAGE_BASE_URL=https://accounts.business-harmony.com/uploads/ diff --git a/src/features/authentication/components/AuthenticationSteps/CompleteSignUp.tsx b/src/features/authentication/components/AuthenticationSteps/CompleteSignUp.tsx index 6b63783..6ec07f3 100644 --- a/src/features/authentication/components/AuthenticationSteps/CompleteSignUp.tsx +++ b/src/features/authentication/components/AuthenticationSteps/CompleteSignUp.tsx @@ -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 ( - - {t('completeSignUp.completeSignUp')} - - - - {t( - 'completeSignUp.emailHasBeenSuccessfullyVerifiedPleaseEnterYourContactNumberToContinue', - { email }, - )} - - - setValue(e.target.value)} - onBlur={handleBlur} - error={inputError} - helperText={inputError ? error : ''} - autoFocus - slotProps={{ - htmlInput: { dir: 'auto', sx: { lineHeight: 1.5, paddingX: 0 } }, - input: { - endAdornment: ( - - ), - }, + { + e.preventDefault(); + e.stopPropagation(); + if (!sendSmsLoading) void handleCompleteSignUp(); }} - sx={{ my: 4 }} - /> + > + + {t('completeSignUp.completeSignUp')} + - + + {t( + 'completeSignUp.emailHasBeenSuccessfullyVerifiedPleaseEnterYourContactNumberToContinue', + { email }, + )} + + + setValue(e.target.value)} + onBlur={handleBlur} + error={inputError} + helperText={inputError ? error : ''} + autoFocus + slotProps={{ + htmlInput: { dir: 'auto', sx: { lineHeight: 1.5, paddingX: 0 } }, + input: { + endAdornment: ( + + ), + }, + }} + sx={{ my: 4 }} + /> + + + ); }; diff --git a/src/features/authentication/components/AuthenticationSteps/EnterPasswordForm.tsx b/src/features/authentication/components/AuthenticationSteps/EnterPasswordForm.tsx index 8cc4a59..6dc9f56 100644 --- a/src/features/authentication/components/AuthenticationSteps/EnterPasswordForm.tsx +++ b/src/features/authentication/components/AuthenticationSteps/EnterPasswordForm.tsx @@ -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, diff --git a/src/features/authentication/components/AuthenticationSteps/GoogleAuthentication.tsx b/src/features/authentication/components/AuthenticationSteps/GoogleAuthentication.tsx index 7fedd8d..275a430 100644 --- a/src/features/authentication/components/AuthenticationSteps/GoogleAuthentication.tsx +++ b/src/features/authentication/components/AuthenticationSteps/GoogleAuthentication.tsx @@ -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 { diff --git a/src/features/authentication/components/AuthenticationSteps/LoginRegiserForm.tsx b/src/features/authentication/components/AuthenticationSteps/LoginRegiserForm.tsx index dceb8b5..74be9d9 100644 --- a/src/features/authentication/components/AuthenticationSteps/LoginRegiserForm.tsx +++ b/src/features/authentication/components/AuthenticationSteps/LoginRegiserForm.tsx @@ -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 ( - - {t('loginForm.title')} - - {t('loginForm.description')} - - - - - ), - }, + { + e.preventDefault(); + e.stopPropagation(); + if (!userStatusLoading) { + void handleSubmit(); + } }} - sx={{ my: 4 }} - /> + > + + {t('loginForm.title')} + + {t('loginForm.description')} + + - - - - + ), + }, + }} + sx={{ my: 4 }} /> - + + + + + + + ); } diff --git a/src/features/authentication/components/AuthenticationSteps/OtpVerifyForm.tsx b/src/features/authentication/components/AuthenticationSteps/OtpVerifyForm.tsx index 319e90f..db87c48 100644 --- a/src/features/authentication/components/AuthenticationSteps/OtpVerifyForm.tsx +++ b/src/features/authentication/components/AuthenticationSteps/OtpVerifyForm.tsx @@ -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({ { + e.preventDefault(); + e.stopPropagation(); + if (!loginSignUpLoading) { + void handleVerifyOTP(); + } }} > - {t('verify.verify')} - - + + + + {otpMessage} + + + setOtpCode(value)} + /> + + - - - {otpMessage} - - - setOtpCode(value)} - /> - - { + e.preventDefault(); + e.stopPropagation(); + if (!confirmSmsOtpLoading) { + void handleVerifyOTP(); + } }} > - {t('verify.verify')} - - + + + + {t( + 'verify.a4DigitVerificationCodeHasBeenSentToYourBobileNumberPleaseEnterIt', + )} + + + setOtpCode(value)} + /> + + - - - {t( - 'verify.a4DigitVerificationCodeHasBeenSentToYourBobileNumberPleaseEnterIt', - )} - - - setOtpCode(value)} - /> - - { + e.preventDefault(); + e.stopPropagation(); + if (!confirmForgetPassCodeLoading) { + void handleVerifyOTP(); + } }} > - - {t('forgetPassword.forgetPassword')} + + + {t('forgetPassword.forgetPassword')} + + + + + + + {infoType === 'email' + ? t( + 'forgetPassword.anEmailContainingARecoveryCodeHasBeenSentToThisEmailAddress', + ) + : t( + 'forgetPassword.anCodeContainingARecoveryCodeHasBeenSentToThisPhoneNumber', + )} - - - - {infoType === 'email' - ? t( - 'forgetPassword.anEmailContainingARecoveryCodeHasBeenSentToThisEmailAddress', - ) - : t( - 'forgetPassword.anCodeContainingARecoveryCodeHasBeenSentToThisPhoneNumber', - )} - - - setOtpCode(value)} - /> - - { + e.preventDefault(); + e.stopPropagation(); + void handleSubmit(); }} > - - - {t('completion.title')} - - - {t('completion.description')} - + + + + {t('completion.title')} + + + {t('completion.description')} + + + + + + + + + + - - - - - - - - diff --git a/src/features/authentication/types/settingForm.ts b/src/features/authentication/types/settingForm.ts index 68d7030..bcc4387 100644 --- a/src/features/authentication/types/settingForm.ts +++ b/src/features/authentication/types/settingForm.ts @@ -72,6 +72,5 @@ export interface PersonalInfoFieldsProps extends ValidationProps { } export interface SubmitProps { - onSubmit: () => void; loading: boolean; } diff --git a/src/features/profile/components/security/PasswordDialog.tsx b/src/features/profile/components/security/PasswordDialog.tsx index 5920786..f83ca8c 100644 --- a/src/features/profile/components/security/PasswordDialog.tsx +++ b/src/features/profile/components/security/PasswordDialog.tsx @@ -46,6 +46,12 @@ export function PasswordDialog({ const [showConfirm, setShowConfirm] = useState(false); const navigate = useNavigate(); + const onFormSubmit: React.FormEventHandler = (e) => { + e.preventDefault(); + e.stopPropagation(); + if (!loading) handleSubmit(); + }; + return ( - - {changePassword && ( - <> - setCurrentPassword(e.target.value)} - sx={{ '& .MuiOutlinedInput-root': { borderRadius: 2 }, mt: 2 }} - InputProps={{ - endAdornment: ( - - setShowCurrent(!showCurrent)}> - - - - ), - }} - /> - navigate('/forget-password')} - > - {t('securityForm.forgetPassword')} - - - )} - - setPassword(e.target.value)} - sx={{ '& .MuiOutlinedInput-root': { borderRadius: 2 }, mt: 2 }} - InputProps={{ - endAdornment: ( - - setShowNew(!showNew)}> - - - - ), + + - - {showValidation && ( - - - - - - - )} - - setConfirmPassword(e.target.value)} - error={confirmPassword.length > 0 && !matchPassword} - helperText={ - confirmPassword.length > 0 && !matchPassword - ? t('securityForm.notCompatibility') - : ' ' - } - sx={{ '& .MuiOutlinedInput-root': { borderRadius: 2 } }} - InputProps={{ - endAdornment: ( - - setShowConfirm(!showConfirm)}> - - - - ), - }} - /> - - - - - + + setPassword(e.target.value)} + sx={{ '& .MuiOutlinedInput-root': { borderRadius: 2 }, mt: 2 }} + InputProps={{ + endAdornment: ( + + setShowNew(!showNew)} + type="button" + > + + + + ), + }} + /> + + {showValidation && ( + + + + + + + )} + + setConfirmPassword(e.target.value)} + error={confirmPassword.length > 0 && !matchPassword} + helperText={ + confirmPassword.length > 0 && !matchPassword + ? t('securityForm.notCompatibility') + : ' ' + } + sx={{ '& .MuiOutlinedInput-root': { borderRadius: 2 } }} + InputProps={{ + endAdornment: ( + + setShowConfirm(!showConfirm)} + type="button" + > + + + + ), + }} + /> + + + + + + ); } diff --git a/src/features/profile/components/userInformation/PersonalInformation.tsx b/src/features/profile/components/userInformation/PersonalInformation.tsx index d5de493..c89212c 100644 --- a/src/features/profile/components/userInformation/PersonalInformation.tsx +++ b/src/features/profile/components/userInformation/PersonalInformation.tsx @@ -14,7 +14,6 @@ import { useProfile } from '../../hooks/useProfile'; export function PersonalInformation() { const imageBaseUrl = import.meta.env.IMAGE_BASE_URL; - const { t } = useTranslation('setting'); const [isEditing, setIsEditing] = useState(false); const [uploadedImageUrl, setUploadedImageUrl] = useState(null); @@ -29,18 +28,14 @@ export function PersonalInformation() { const [originalData, setOriginalData] = useState(null); const showToast = useToast(); const infoRowEditRef = useRef<{ validateFields: () => boolean }>(null); - const { isLoadingProfile, refetchProfile } = useProfile(); - const { loading: isSavingProfile, execute: executeSaveProfile } = useApi(saveProfile); useEffect(() => { const loadProfile = async () => { const profileData = await refetchProfile(); - if (!profileData) return; - if (profileData.success) { const fetchedData = { firstName: profileData.firstName ?? '', @@ -66,7 +61,6 @@ export function PersonalInformation() { }); } }; - loadProfile(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -85,14 +79,11 @@ export function PersonalInformation() { if (!data) return; const isValid = infoRowEditRef.current?.validateFields?.(); if (!isValid) return; - const saveData = await executeSaveProfile({ data, imageUrl: uploadedImageFile, }); - if (!saveData) return; - if (saveData?.success) { showToast({ message: t('settingForm.successSaveProfile'), @@ -101,7 +92,6 @@ export function PersonalInformation() { setIsEditing(false); setOriginalData(data); setUploadedImageFile(null); - // Force a refetch to update the cache with the new data refetchProfile({ force: true }); } else { showToast({ @@ -122,6 +112,7 @@ export function PersonalInformation() { {isEditing ? ( <> + + + ) : ( )} - ); } diff --git a/src/features/profile/components/userInformation/socialMedia/SocialMediaDialog.tsx b/src/features/profile/components/userInformation/socialMedia/SocialMediaDialog.tsx index 7715624..c768683 100644 --- a/src/features/profile/components/userInformation/socialMedia/SocialMediaDialog.tsx +++ b/src/features/profile/components/userInformation/socialMedia/SocialMediaDialog.tsx @@ -54,6 +54,21 @@ export default function SocialMediaDialog({ return true; }; + const handleSubmit: React.FormEventHandler = (e) => { + e.preventDefault(); + e.stopPropagation(); + + if (isLoading) return; + + if (dialogStep === 'enterEmail') { + setTouched(true); + if (!validateEmail(emailInput)) return; + onSendCode(); + } else { + onConfirmEmail(); + } + }; + return ( - - - {t('settingForm.newEmail')} - - {t('settingForm.dialogHeader')} - - - - { - setEmailInput(e.target.value); - if (touched) validateEmail(e.target.value); + + { - setTouched(true); - validateEmail(emailInput); - }} - label={t('settingForm.email')} - placeholder="abc@email.com" - autoComplete="email" - inputMode="email" - sx={{ '& .MuiOutlinedInput-root': { borderRadius: 2 }, mt: 2 }} - autoFocus - disabled={isLoading || dialogStep === 'enterCode'} - error={touched && !!emailError} - helperText={touched && emailError ? emailError : ' '} - /> + > + + + {t('settingForm.newEmail')} + + + {t('settingForm.dialogHeader')} + + - {dialogStep === 'enterCode' && ( setVerificationCode(e.target.value)} - label={t('settingForm.verificationCode')} - autoComplete="one-time-code" - inputMode="numeric" - sx={{ '& .MuiOutlinedInput-root': { borderRadius: 2 } }} - autoFocus - disabled={isLoading} + type="email" + value={emailInput} + onChange={(e) => { + setEmailInput(e.target.value); + if (touched) validateEmail(e.target.value); + }} + onBlur={() => { + setTouched(true); + 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 : ' '} /> - )} - - - + + + + + ); diff --git a/src/features/profile/types/settingsType.ts b/src/features/profile/types/settingsType.ts index 473caa9..1cccfc1 100644 --- a/src/features/profile/types/settingsType.ts +++ b/src/features/profile/types/settingsType.ts @@ -1,3 +1,5 @@ +import type { TFunction } from 'i18next'; + export enum Gender { Male = 2, Female = 1, @@ -93,7 +95,9 @@ export interface EmailAccount { export interface PhoneActionButtonsProps { isEditing: boolean; toggleEdit: () => void; - t: (key: string) => string; + t: TFunction<'setting'>; + formId?: string; + isSubmitting?: boolean; } export interface PhoneDisplayProps {