Files
Account/src/features/profile/components/userInformation/PersonalInformation.tsx
2025-08-21 14:51:56 +03:30

225 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect, useRef } from 'react';
import { Box, Button, CircularProgress } from '@mui/material';
import { useTranslation } from 'react-i18next';
import { CardContainer } from '@/components/CardContainer';
import { ProfileImage } from './personalInformation/ProfileImage';
import { InfoRowDisplay } from './personalInformation/InfoRowDisplay';
import { InfoRowEdit } from './personalInformation/InfoRowEdit';
import { PageWrapper } from '../PageWrapper';
import { useApi } from '@/hooks/useApi';
import { Gender, type InfoRowData } from '../../types/settingsType';
import { saveProfile } from '../../api/settingsApi';
import { useToast } from '@rkheftan/harmony-ui';
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<string | null>(null);
const [uploadedImageFile, setUploadedImageFile] = useState<File | null>(null);
const [data, setData] = useState<InfoRowData>({
firstName: '',
lastName: '',
nationalCode: '',
gender: Gender.None,
country: '',
});
const [originalData, setOriginalData] = useState<InfoRowData | null>(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 ?? '',
lastName: profileData.lastName ?? '',
nationalCode: profileData.nationalCode ?? '',
gender: Object.values(Gender).includes(profileData.gender as Gender)
? (profileData.gender as Gender)
: Gender.None,
country: profileData.countryCode ?? '',
};
setData(fetchedData);
setOriginalData(fetchedData);
setUploadedImageUrl(
profileData.profileImageUrl
? `${imageBaseUrl}${profileData.profileImageUrl}`
: null,
);
setUploadedImageFile(null);
} else {
showToast({
message: profileData.message || t('message.serverError'),
severity: 'error',
});
}
};
loadProfile();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const initials = `${data?.firstName?.trim()[0] || ''}${data?.lastName?.trim()[0] || ''}`;
const handleEditClick = () => setIsEditing(true);
const handleCancelClick = () => {
setIsEditing(false);
if (originalData) setData(originalData);
setUploadedImageFile(null);
};
const handleSaveClick = async () => {
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'),
severity: 'success',
});
setIsEditing(false);
setOriginalData(data);
setUploadedImageFile(null);
// Force a refetch to update the cache with the new data
refetchProfile({ force: true });
} else {
showToast({
message: saveData.message || t('message.serverError'),
severity: 'error',
});
}
};
return (
<PageWrapper>
<CardContainer
title={t('settingForm.titlePersonalInfo')}
subtitle={t('settingForm.descriptionPersonalInfo')}
highlighted={isEditing}
action={
<Box sx={{ display: 'flex', gap: 1 }}>
{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' },
}}
>
{isSavingProfile ? (
<CircularProgress size={24} color="inherit" />
) : (
t('settingForm.saveButton')
)}
</Button>
</>
) : (
<Button
onClick={handleEditClick}
size="large"
variant="outlined"
sx={{ borderRadius: 1 }}
>
{t('settingForm.editButton')}
</Button>
)}
</Box>
}
>
{isLoadingProfile ? (
<Box
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
p: 4,
minHeight: '200px',
}}
>
<CircularProgress />
</Box>
) : (
<Box
sx={{
mx: { xs: 2, sm: 3, md: 4 },
py: 2,
display: 'flex',
flexDirection: 'column',
gap: 2,
bgcolor: 'background.paper',
}}
>
{isEditing && (
<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);
}}
/>
)}
{data &&
(isEditing ? (
<InfoRowEdit
ref={infoRowEditRef}
data={data}
setData={setData}
/>
) : (
<InfoRowDisplay
data={data}
uploadedImageUrl={uploadedImageUrl}
initials={initials}
/>
))}
</Box>
)}
</CardContainer>
</PageWrapper>
);
}