fix: merge style bugs

This commit is contained in:
Koosha Lahouti
2025-08-17 14:31:18 +03:30
parent dd6e2fdd0d
commit 7e5ed6e2fc
14 changed files with 254 additions and 138 deletions

View File

@@ -2,31 +2,19 @@ import {
type SendEmailOtpPayload, type SendEmailOtpPayload,
type ConfirmEmailOtpPayload, type ConfirmEmailOtpPayload,
type CompleteUserInfoPayload, type CompleteUserInfoPayload,
type CompleteUserInfoResponse,
type GenericApiResponse,
} from '../types/completionFormApiTypes'; } from '../types/completionFormApiTypes';
import apiClient from '@/lib/apiClient'; import apiClient from '@/lib/apiClient';
export const sendEmailOtpApi = async ( export const sendEmailOtpApi = async (payload: SendEmailOtpPayload) => {
payload: SendEmailOtpPayload, return apiClient.post('/User/SendEmailOtp', payload);
): Promise<GenericApiResponse & { codeSentAnyway?: boolean }> => {
const { data } = await apiClient.post('/User/SendEmailOtp', payload);
return data;
}; };
export const confirmEmailOtpApi = async ( export const confirmEmailOtpApi = async (payload: ConfirmEmailOtpPayload) => {
payload: ConfirmEmailOtpPayload, return apiClient.post('/User/ConfirmEmailOtp', payload);
): Promise<GenericApiResponse> => {
const { data } = await apiClient.post('/User/ConfirmEmailOtp', payload);
return data;
}; };
export const completeUserInformationApi = async ( export const completeUserInformationApi = async (
payload: CompleteUserInfoPayload, payload: CompleteUserInfoPayload,
): Promise<CompleteUserInfoResponse> => { ) => {
const { data } = await apiClient.post( return apiClient.post('/User/CompleteUserInformation', payload);
'/User/CompleteUserInformation',
payload,
);
return data;
}; };

View File

@@ -16,12 +16,6 @@ import {
confirmEmailOtpApi, confirmEmailOtpApi,
completeUserInformationApi, completeUserInformationApi,
} from '../api/userCompletion'; } from '../api/userCompletion';
import {
type SendEmailOtpPayload,
type ConfirmEmailOtpPayload,
type CompleteUserInfoPayload,
} from '../types/completionFormApiTypes';
import { type ApiResponse } from '@/types/apiResponse';
import { containsSymbol } from '@/utils/regexes/containsSymbol'; import { containsSymbol } from '@/utils/regexes/containsSymbol';
import { least8Chars } from '@/utils/regexes/least8Chars'; import { least8Chars } from '@/utils/regexes/least8Chars';
import { containsNumber } from '@/utils/regexes/containsNumber'; import { containsNumber } from '@/utils/regexes/containsNumber';
@@ -63,46 +57,20 @@ export function UserCompletionPage() {
const matchPassword = password === confirmPassword; const matchPassword = password === confirmPassword;
const correctEmail = isEmail(email); const correctEmail = isEmail(email);
const { execute: sendCode, data: sendCodeData } = useApi( const { execute: sendCode, data: sendCodeData } = useApi(sendEmailOtpApi);
async (payload: SendEmailOtpPayload) => {
const result = await sendEmailOtpApi(payload);
const conformingResult: ApiResponse = {
...result,
errorCode: result.errorCode || 0,
validations: [],
};
return { data: conformingResult };
},
);
const { const {
execute: verifyCode, execute: verifyCode,
loading: isVerifyingCode,
data: verifyCodeData, data: verifyCodeData,
} = useApi(async (payload: ConfirmEmailOtpPayload) => { loading: isVerifyingCode,
const result = await confirmEmailOtpApi(payload); } = useApi(confirmEmailOtpApi);
const conformingResult: ApiResponse = {
...result,
errorCode: result.errorCode || 0,
validations: [],
};
return { data: conformingResult };
});
const { const {
execute: submitForm, execute: submitForm,
data: submitData,
loading: isSubmitting, loading: isSubmitting,
error: submitError, error: submitError,
data: submitData, } = useApi(completeUserInformationApi);
} = useApi(async (payload: CompleteUserInfoPayload) => {
const result = await completeUserInformationApi(payload);
const conformingResult: ApiResponse = {
...result,
errorCode: result.errorCode || 0,
validations: [],
};
return { data: conformingResult };
});
const getErrorMessage = (error: unknown): string | null => { const getErrorMessage = (error: unknown): string | null => {
if (!error) return null; if (!error) return null;
@@ -112,9 +80,10 @@ export function UserCompletionPage() {
useEffect(() => { useEffect(() => {
if (sendCodeData) { if (sendCodeData) {
if (sendCodeData.success) { if (sendCodeData.data.success) {
showToast({ showToast({
message: sendCodeData.message || t('completion.successfullCodeSent'), message:
sendCodeData.data.message || t('completion.successfullCodeSent'),
severity: 'success', severity: 'success',
}); });
setCodeSent(true); setCodeSent(true);
@@ -122,7 +91,7 @@ export function UserCompletionPage() {
setCountdown(120); setCountdown(120);
} else { } else {
showToast({ showToast({
message: sendCodeData.message || t('completion.problem'), message: sendCodeData.data.message || t('completion.problem'),
severity: 'error', severity: 'error',
}); });
} }
@@ -131,15 +100,15 @@ export function UserCompletionPage() {
useEffect(() => { useEffect(() => {
if (verifyCodeData) { if (verifyCodeData) {
if (verifyCodeData.success) { if (verifyCodeData.data.success) {
setEmailVerified(true); setEmailVerified(true);
showToast({ showToast({
message: verifyCodeData.message || t('completion.codeVerified'), message: verifyCodeData.data.message || t('completion.codeVerified'),
severity: 'success', severity: 'success',
}); });
} else { } else {
showToast({ showToast({
message: verifyCodeData.message || t('completion.invalidCode'), message: verifyCodeData.data.message || t('completion.invalidCode'),
severity: 'error', severity: 'error',
}); });
setEmailVerified(false); setEmailVerified(false);
@@ -151,13 +120,13 @@ export function UserCompletionPage() {
if (submitData) { if (submitData) {
showToast({ showToast({
message: message:
submitData.message || submitData.data.message ||
t( t(
submitData.success submitData.data.success
? 'completion.submitSuccess' ? 'completion.submitSuccess'
: 'completion.submitError', : 'completion.submitError',
), ),
severity: submitData.success ? 'success' : 'error', severity: submitData.data.success ? 'success' : 'error',
}); });
} else if (submitError) { } else if (submitError) {
showToast({ showToast({
@@ -205,7 +174,6 @@ export function UserCompletionPage() {
const handleSubmit = () => { const handleSubmit = () => {
submitForm({ submitForm({
userId: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
firstName, firstName,
lastName, lastName,
gender: sex, gender: sex,
@@ -325,7 +293,7 @@ export function UserCompletionPage() {
onSubmit={handleSubmit} onSubmit={handleSubmit}
loading={isSubmitting} loading={isSubmitting}
error={getErrorMessage(submitError)} error={getErrorMessage(submitError)}
success={!!submitData?.success} success={!!submitData?.data.success}
/> />
</Box> </Box>
</Box> </Box>

View File

@@ -14,7 +14,6 @@ export interface ConfirmEmailOtpPayload {
} }
export interface CompleteUserInfoPayload { export interface CompleteUserInfoPayload {
userId: string;
firstName: string; firstName: string;
lastName: string; lastName: string;
gender: 0 | 1 | 2; gender: 0 | 1 | 2;

View File

@@ -15,7 +15,7 @@ import {
import { type InfoRowData } from '../types/settingsType'; import { type InfoRowData } from '../types/settingsType';
export async function fetchProfile() { export async function fetchProfile() {
return apiClient.post<GetProfileApiResponse>('/Profile/GetProfile'); return apiClient.post<GetProfileApiResponse>('/Profile/GetProfile', {});
} }
export async function saveProfile(payload: { export async function saveProfile(payload: {

View File

@@ -65,7 +65,6 @@ export function PasswordDialog({
px: { xs: 2, sm: 3 }, px: { xs: 2, sm: 3 },
}} }}
> >
{/* ✅ 4. API ERROR DISPLAY ADDED */}
{apiError && ( {apiError && (
<Typography color="error" variant="body2" textAlign="center"> <Typography color="error" variant="body2" textAlign="center">
{apiError} {apiError}

View File

@@ -21,6 +21,7 @@ import { containsNumber } from '@/utils/regexes/containsNumber';
import { least8Chars } from '@/utils/regexes/least8Chars'; import { least8Chars } from '@/utils/regexes/least8Chars';
import { hasUpperAndLowerLetter } from '@/utils/regexes/hasUpperAndLowerLetter'; import { hasUpperAndLowerLetter } from '@/utils/regexes/hasUpperAndLowerLetter';
import { containsSymbol } from '@/utils/regexes/containsSymbol'; import { containsSymbol } from '@/utils/regexes/containsSymbol';
import { useToast } from '@rkheftan/harmony-ui';
export function PasswordSecurity() { export function PasswordSecurity() {
const theme = useTheme(); const theme = useTheme();
@@ -42,6 +43,7 @@ export function PasswordSecurity() {
hasNumber && hasMinLength && hasUpperAndLower && hasSpecialChar; hasNumber && hasMinLength && hasUpperAndLower && hasSpecialChar;
const matchPassword = password === confirmPassword; const matchPassword = password === confirmPassword;
const showToast = useToast();
const { const {
data: profileData, data: profileData,
@@ -79,6 +81,12 @@ export function PasswordSecurity() {
useEffect(() => { useEffect(() => {
if (addData?.success || changeData?.success) { if (addData?.success || changeData?.success) {
showToast({
message: changePasswordUI
? t('securityForm.passwordChanged')
: t('securityForm.passwordAdded'),
severity: 'success',
});
if (!changePasswordUI) { if (!changePasswordUI) {
setChangePasswordUI(true); setChangePasswordUI(true);
} }
@@ -87,7 +95,16 @@ export function PasswordSecurity() {
setConfirmPassword(''); setConfirmPassword('');
setCurrentPassword(''); setCurrentPassword('');
} }
}, [addData, changeData, changePasswordUI]); }, [addData, changeData, changePasswordUI, showToast, t]);
useEffect(() => {
if (addError || changeError) {
showToast({
message: getErrorMessage(addError || changeError) || t('error.general'),
severity: 'error',
});
}
}, [addError, changeError, t, showToast]);
const handleOpen = () => setOpen(true); const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false); const handleClose = () => setOpen(false);
@@ -120,7 +137,12 @@ export function PasswordSecurity() {
title={t('securityForm.password')} title={t('securityForm.password')}
subtitle={t('securityForm.determinePassword')} subtitle={t('securityForm.determinePassword')}
action={ action={
<Button variant="contained" onClick={handleOpen}> <Button
variant="contained"
onClick={handleOpen}
size="medium"
sx={{ flexShrink: 0, flexGrow: 0, width: 'auto' }}
>
{changePasswordUI {changePasswordUI
? t('securityForm.changePassword') ? t('securityForm.changePassword')
: t('securityForm.addPassword')} : t('securityForm.addPassword')}

View File

@@ -13,12 +13,14 @@ import { fetchProfile } from '../../api/settingsApi';
import type { LoginLog } from '../../types/settingsApiType'; import type { LoginLog } from '../../types/settingsApiType';
import { useApi } from '@/hooks/useApi'; import { useApi } from '@/hooks/useApi';
import { formatDate } from '@/utils/formatSessionDate'; import { formatDate } from '@/utils/formatSessionDate';
import { useToast } from '@rkheftan/harmony-ui';
export function RecentLogins() { export function RecentLogins() {
const { t, i18n } = useTranslation('setting'); const { t, i18n } = useTranslation('setting');
const [logs, setLogs] = useState<LoginLog[]>([]); const [logs, setLogs] = useState<LoginLog[]>([]);
const theme = useTheme(); const theme = useTheme();
const isXsup = useMediaQuery(theme.breakpoints.up('xs')); const isXsup = useMediaQuery(theme.breakpoints.up('xs'));
const showToast = useToast();
const { const {
data: profileData, data: profileData,
@@ -32,6 +34,14 @@ export function RecentLogins() {
} }
}, [profileData]); }, [profileData]);
useEffect(() => {
if (fetchError) {
showToast({
message: getErrorMessage(fetchError) || t('active.errorFetch'),
severity: 'error',
});
}
}, [fetchError, t, showToast]);
const getErrorMessage = (error: unknown): string | null => { const getErrorMessage = (error: unknown): string | null => {
if (!error) return null; if (!error) return null;
if (error instanceof Error) return error.message; if (error instanceof Error) return error.message;

View File

@@ -9,6 +9,7 @@ import { PageWrapper } from '../PageWrapper';
import { useApi } from '@/hooks/useApi'; import { useApi } from '@/hooks/useApi';
import { Gender, type InfoRowData } from '../../types/settingsType'; import { Gender, type InfoRowData } from '../../types/settingsType';
import { fetchProfile, saveProfile } from '../../api/settingsApi'; import { fetchProfile, saveProfile } from '../../api/settingsApi';
import { useToast } from '@rkheftan/harmony-ui';
export function PersonalInformation() { export function PersonalInformation() {
const { t } = useTranslation('setting'); const { t } = useTranslation('setting');
@@ -22,6 +23,7 @@ export function PersonalInformation() {
country: '', country: '',
}); });
const [originalData, setOriginalData] = useState<InfoRowData | null>(null); const [originalData, setOriginalData] = useState<InfoRowData | null>(null);
const showToast = useToast();
const { const {
data: profileData, data: profileData,
@@ -55,10 +57,14 @@ export function PersonalInformation() {
useEffect(() => { useEffect(() => {
if (saveData?.success) { if (saveData?.success) {
showToast({
message: t('settingForm.successSaveProfile'),
severity: 'success',
});
setIsEditing(false); setIsEditing(false);
setOriginalData(data); setOriginalData(data);
} }
}, [saveData, data]); }, [saveData, data, showToast, t]);
const initials = `${data?.firstName?.trim()[0] || ''}${data?.lastName?.trim()[0] || ''}`; const initials = `${data?.firstName?.trim()[0] || ''}${data?.lastName?.trim()[0] || ''}`;
@@ -84,6 +90,25 @@ export function PersonalInformation() {
if (error instanceof Error) return error.message; if (error instanceof Error) return error.message;
return String(error); return String(error);
}; };
useEffect(() => {
if (saveProfileError) {
showToast({
message:
getErrorMessage(saveProfileError) || t('settingForm.errorSave'),
severity: 'error',
});
}
}, [saveProfileError, showToast, t]);
useEffect(() => {
if (fetchProfileError) {
showToast({
message:
getErrorMessage(fetchProfileError) || t('settingForm.errorFetch'),
severity: 'error',
});
}
}, [fetchProfileError, showToast, t]);
return ( return (
<PageWrapper> <PageWrapper>
@@ -95,62 +120,51 @@ export function PersonalInformation() {
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
flexDirection: 'column', gap: 1,
alignItems: 'flex-end',
width: '100%',
}} }}
> >
<Box {isEditing ? (
sx={{ <>
display: 'flex',
gap: 1,
flexWrap: 'wrap',
justifyContent: 'flex-end',
}}
>
{isEditing ? (
<>
<Button
variant="text"
onClick={handleCancelClick}
size="large"
sx={{
color: 'primary.main',
textTransform: 'none',
width: { xs: '100%', sm: 'auto' },
}}
>
{t('settingForm.rejectButton')}
</Button>
<Button
onClick={handleSaveClick}
size="large"
variant="contained"
sx={{
textTransform: 'none',
width: { xs: '100%', sm: 'auto' },
}}
disabled={isSavingProfile}
>
{isSavingProfile ? (
<CircularProgress size={24} color="inherit" />
) : (
t('settingForm.saveButton')
)}
</Button>
</>
) : (
<Button <Button
onClick={handleEditClick} variant="text"
onClick={handleCancelClick}
size="large" size="large"
variant="outlined" sx={{
sx={{ borderRadius: 1 }} color: 'primary.main',
disabled={isLoadingProfile} textTransform: 'none',
width: { xs: '100%', sm: 'auto' },
}}
> >
{t('settingForm.editButton')} {t('settingForm.rejectButton')}
</Button> </Button>
)} <Button
</Box> onClick={handleSaveClick}
size="large"
variant="contained"
sx={{
textTransform: 'none',
width: { xs: '100%', sm: 'auto' },
}}
disabled={isSavingProfile}
>
{isSavingProfile ? (
<CircularProgress size={24} color="inherit" />
) : (
t('settingForm.saveButton')
)}
</Button>
</>
) : (
<Button
onClick={handleEditClick}
size="large"
variant="outlined"
sx={{ borderRadius: 1 }}
disabled={isLoadingProfile}
>
{t('settingForm.editButton')}
</Button>
)}
{getErrorMessage(saveProfileError) && ( {getErrorMessage(saveProfileError) && (
<Typography <Typography
color="error" color="error"

View File

@@ -16,9 +16,11 @@ import {
changePhoneNumber, changePhoneNumber,
} from '../../api/settingsApi'; } from '../../api/settingsApi';
import { type Phone } from '../../types/settingsType'; import { type Phone } from '../../types/settingsType';
import { useToast } from '@rkheftan/harmony-ui';
export function PhoneNumber() { export function PhoneNumber() {
const { t, i18n } = useTranslation('setting'); const { t, i18n } = useTranslation('setting');
const toast = useToast();
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
const [phoneNumber, setPhoneNumber] = useState(''); const [phoneNumber, setPhoneNumber] = useState('');
const [verificationCode, setVerificationCode] = useState(''); const [verificationCode, setVerificationCode] = useState('');
@@ -74,12 +76,36 @@ export function PhoneNumber() {
} }
}, [profileData]); }, [profileData]);
useEffect(() => {
if (fetchError) {
toast({
message:
getErrorMessage(fetchError) || t('settingForm.errorFetchPhoneNumber'),
severity: 'error',
});
}
}, [fetchError, toast, t]);
useEffect(() => { useEffect(() => {
if (sendCodeData?.success) { if (sendCodeData?.success) {
setButtonState('counting'); setButtonState('counting');
setIsVerified(false); setIsVerified(false);
toast({
message: t('settingForm.codeSentSuccessfully'),
severity: 'success',
});
} }
}, [sendCodeData]); }, [sendCodeData, t, toast]);
useEffect(() => {
if (sendCodeError) {
toast({
message:
getErrorMessage(sendCodeError) || t('settingForm.errorSendCode'),
severity: 'error',
});
}
}, [sendCodeError, toast, t]);
useEffect(() => { useEffect(() => {
if (confirmData?.success && confirmData.confirm) { if (confirmData?.success && confirmData.confirm) {
@@ -87,8 +113,22 @@ export function PhoneNumber() {
setShowToast(true); setShowToast(true);
const fullPhoneNumber = countryCode + phoneNumber.replace(/^0/, ''); const fullPhoneNumber = countryCode + phoneNumber.replace(/^0/, '');
executeChangePhone({ phoneNumber: fullPhoneNumber }); executeChangePhone({ phoneNumber: fullPhoneNumber });
toast({
message: t('settingForm.phoneVerified'),
severity: 'success',
});
} }
}, [confirmData, countryCode, phoneNumber, executeChangePhone]); }, [confirmData, countryCode, phoneNumber, executeChangePhone, t, toast]);
useEffect(() => {
if (confirmError) {
toast({
message:
getErrorMessage(confirmError) || t('settingForm.errorConfirmCode'),
severity: 'error',
});
}
}, [confirmError, toast, t]);
useEffect(() => { useEffect(() => {
if (changePhoneData?.success) { if (changePhoneData?.success) {
@@ -101,8 +141,23 @@ export function PhoneNumber() {
}, },
]); ]);
setIsEditing(false); setIsEditing(false);
toast({
message: t('settingForm.phoneChangedSuccessfully'),
severity: 'success',
});
} }
}, [changePhoneData, countryCode, phoneNumber, t]); }, [changePhoneData, countryCode, phoneNumber, t, toast]);
useEffect(() => {
if (changePhoneError) {
toast({
message:
getErrorMessage(changePhoneError) ||
t('settingForm.errorChangePhone'),
severity: 'error',
});
}
}, [changePhoneError, toast, t]);
const apiError = useMemo( const apiError = useMemo(
() => sendCodeError || confirmError || changePhoneError, () => sendCodeError || confirmError || changePhoneError,
@@ -151,7 +206,6 @@ export function PhoneNumber() {
const handleSendCode = () => { const handleSendCode = () => {
handleBlur(); handleBlur();
if (formError || !isPhoneValid(countryCode, phoneNumber)) return; if (formError || !isPhoneValid(countryCode, phoneNumber)) return;
executeSendCode({ executeSendCode({
phoneNumber: countryCode + phoneNumber.replace(/^0/, ''), phoneNumber: countryCode + phoneNumber.replace(/^0/, ''),
}); });

View File

@@ -16,6 +16,7 @@ import {
changeEmail, changeEmail,
} from '../../api/settingsApi'; } from '../../api/settingsApi';
import { type EmailAccount } from '../../types/settingsType'; import { type EmailAccount } from '../../types/settingsType';
import { useToast } from '@rkheftan/harmony-ui';
export function SocialMedia() { export function SocialMedia() {
const { t } = useTranslation('setting'); const { t } = useTranslation('setting');
@@ -28,6 +29,7 @@ export function SocialMedia() {
); );
const [emailList, setEmailList] = useState<EmailAccount[]>([]); const [emailList, setEmailList] = useState<EmailAccount[]>([]);
const [formError, setFormError] = useState<string | null>(null); const [formError, setFormError] = useState<string | null>(null);
const showToast = useToast();
const { const {
data: profileData, data: profileData,
@@ -82,15 +84,23 @@ export function SocialMedia() {
useEffect(() => { useEffect(() => {
if (sendCodeData?.success) { if (sendCodeData?.success) {
showToast({
message: t('settingForm.verificationCodeSent'),
severity: 'success',
});
setDialogStep('enterCode'); setDialogStep('enterCode');
} }
}, [sendCodeData]); }, [sendCodeData, t, showToast]);
useEffect(() => { useEffect(() => {
if (confirmData?.success && confirmData.confirm) { if (confirmData?.success && confirmData.confirm) {
showToast({
message: t('settingForm.verificationSuccessful'),
severity: 'success',
});
executeChangeEmail({ email: emailInput }); executeChangeEmail({ email: emailInput });
} }
}, [confirmData, emailInput, executeChangeEmail]); }, [confirmData, emailInput, executeChangeEmail, t, showToast]);
useEffect(() => { useEffect(() => {
if (changeEmailData?.success) { if (changeEmailData?.success) {
@@ -102,9 +112,34 @@ export function SocialMedia() {
time: t('settingForm.justNow'), time: t('settingForm.justNow'),
}, },
]); ]);
showToast({
message: t('settingForm.emailChangedSuccessfully'),
severity: 'success',
});
resetDialog(); resetDialog();
} }
}, [changeEmailData, emailInput, t]); }, [changeEmailData, emailInput, t, showToast]);
useEffect(() => {
if (sendCodeError) {
showToast({
message: getErrorMessage(sendCodeError) || t('settingForm.error'),
severity: 'error',
});
}
if (confirmError) {
showToast({
message: getErrorMessage(confirmError) || t('settingForm.error'),
severity: 'error',
});
}
if (changeEmailError) {
showToast({
message: getErrorMessage(changeEmailError) || t('settingForm.error'),
severity: 'error',
});
}
}, [sendCodeError, confirmError, changeEmailError, t, showToast]);
const resetDialog = () => { const resetDialog = () => {
setOpenDialog(false); setOpenDialog(false);
@@ -115,7 +150,7 @@ export function SocialMedia() {
}; };
const handleSendCode = () => { const handleSendCode = () => {
if (!/^\S+@\S+\.\S+$/.test(emailInput)) { if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(emailInput)) {
setFormError(t('settingForm.emailIsInvalid')); setFormError(t('settingForm.emailIsInvalid'));
return; return;
} }

View File

@@ -68,6 +68,9 @@ export function ProfileImage({
sx={{ sx={{
borderRadius: 2, borderRadius: 2,
textTransform: 'none', textTransform: 'none',
flexShrink: 0,
flexGrow: 0,
width: 'auto',
}} }}
startIcon={ startIcon={
<Icon <Icon

View File

@@ -12,6 +12,7 @@ import { CountDownTimer } from '@/components/CountDownTimer';
import { CountryCodeSelector } from '../../CountryCodeSelector'; import { CountryCodeSelector } from '../../CountryCodeSelector';
import { Icon } from '@rkheftan/harmony-ui'; import { Icon } from '@rkheftan/harmony-ui';
import { type PhoneEditFormProps } from '@/features/profile/types/settingsType'; import { type PhoneEditFormProps } from '@/features/profile/types/settingsType';
import { useState } from 'react';
export default function PhoneEditForm({ export default function PhoneEditForm({
phoneNumber, phoneNumber,
@@ -39,6 +40,8 @@ export default function PhoneEditForm({
return digitsOnly.length >= 8 && digitsOnly.length <= 15; return digitsOnly.length >= 8 && digitsOnly.length <= 15;
}; };
const [isSending, setIsSending] = useState(false);
return ( return (
<> <>
<Box sx={{ width: '100%' }}> <Box sx={{ width: '100%' }}>
@@ -73,7 +76,7 @@ export default function PhoneEditForm({
error={inputError} error={inputError}
helperText={inputError ? error : ''} helperText={inputError ? error : ''}
onChange={(e) => setPhoneNumber(e.target.value)} onChange={(e) => setPhoneNumber(e.target.value)}
sx={{ flex: '1 1 220px', minWidth: 0 }} sx={{ flex: '1 1 220px' }}
placeholder="09123456789" placeholder="09123456789"
InputProps={{ InputProps={{
endAdornment: endAdornment:
@@ -122,12 +125,18 @@ export default function PhoneEditForm({
) : ( ) : (
<Button <Button
variant="text" variant="text"
onClick={() => { onClick={async () => {
if (isValidPhoneNumber(phoneNumber)) { if (isValidPhoneNumber(phoneNumber)) {
handleSendCode(); setIsSending(true);
try {
await handleSendCode();
} finally {
setIsSending(false);
}
} }
}} }}
disabled={ disabled={
isSending ||
buttonState === 'counting' || buttonState === 'counting' ||
phoneNumber.length === 0 || phoneNumber.length === 0 ||
!isValidPhoneNumber(phoneNumber) !isValidPhoneNumber(phoneNumber)
@@ -138,7 +147,9 @@ export default function PhoneEditForm({
textTransform: 'none', textTransform: 'none',
}} }}
> >
{buttonState === 'counting' ? ( {isSending ? (
<CircularProgress size={20} />
) : buttonState === 'counting' ? (
<CountDownTimer <CountDownTimer
initialSeconds={60} initialSeconds={60}
onComplete={() => setButtonState('default')} onComplete={() => setButtonState('default')}

View File

@@ -17,6 +17,7 @@ import { type ApiSession } from '../types/settingsApiType';
import { useApi } from '@/hooks/useApi'; import { useApi } from '@/hooks/useApi';
import { formatDate } from '@/utils/formatSessionDate'; import { formatDate } from '@/utils/formatSessionDate';
import { type Device } from '../types/settingsType'; import { type Device } from '../types/settingsType';
import { useToast } from '@rkheftan/harmony-ui';
export function ActiveDevicesPage() { export function ActiveDevicesPage() {
const { t, i18n } = useTranslation('setting'); const { t, i18n } = useTranslation('setting');
@@ -24,6 +25,7 @@ export function ActiveDevicesPage() {
const [loadingDeleteIds, setLoadingDeleteIds] = useState<string[]>([]); const [loadingDeleteIds, setLoadingDeleteIds] = useState<string[]>([]);
const theme = useTheme(); const theme = useTheme();
const isXsup = useMediaQuery(theme.breakpoints.up('xs')); const isXsup = useMediaQuery(theme.breakpoints.up('xs'));
const showToast = useToast();
const { const {
data: profileData, data: profileData,
@@ -65,10 +67,17 @@ export function ActiveDevicesPage() {
if (data.success) { if (data.success) {
setDevices((prevDevices) => prevDevices.filter((d) => d.id !== id)); setDevices((prevDevices) => prevDevices.filter((d) => d.id !== id));
} else { } else {
console.error('Delete failed:', data.message); showToast({
message: data.message || t('active.deleteFailed'),
severity: 'error',
});
} }
} catch (error: unknown) { } catch (error: unknown) {
console.error('Delete error:', error); // console.error('Delete error:', error);
showToast({
message: t('active.deleteFailed'),
severity: 'error',
});
} finally { } finally {
setLoadingDeleteIds((prev) => setLoadingDeleteIds((prev) =>
prev.filter((loadingId) => loadingId !== id), prev.filter((loadingId) => loadingId !== id),
@@ -104,6 +113,9 @@ export function ActiveDevicesPage() {
borderColor: 'error.main', borderColor: 'error.main',
color: 'error.main', color: 'error.main',
textTransform: 'none', textTransform: 'none',
flexShrink: 0,
flexGrow: 0,
width: 'auto',
}} }}
disabled={isLoading || isTerminating} disabled={isLoading || isTerminating}
> >
@@ -215,6 +227,7 @@ export function ActiveDevicesPage() {
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
color: 'success.main', color: 'success.main',
textTransform: 'none', textTransform: 'none',
maxWidth: '125px',
'&.Mui-disabled': { '&.Mui-disabled': {
color: 'success.main', color: 'success.main',
borderColor: 'success.main', borderColor: 'success.main',

View File

@@ -56,7 +56,7 @@ export function SettingPage() {
data: profileData, data: profileData,
loading: isFetching, loading: isFetching,
error: fetchError, error: fetchError,
} = useApi(fetchProfile, { immediate: true }); } = useApi(fetchProfile);
const { const {
data: saveData, data: saveData,
@@ -142,7 +142,7 @@ export function SettingPage() {
subtitle={t('settings.description')} subtitle={t('settings.description')}
highlighted={isEditing} highlighted={isEditing}
action={ action={
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}> <Box sx={{ display: 'flex', gap: 1 }}>
{isEditing && ( {isEditing && (
<Button <Button
variant="text" variant="text"