Files
Account/src/features/profile/routes/SettingPage.tsx
2025-09-14 13:41:26 +03:30

339 lines
11 KiB
TypeScript

import { useState, useEffect } from 'react';
import {
Box,
Typography,
Button,
useColorScheme,
Autocomplete,
TextField,
CircularProgress,
} from '@mui/material';
import { CardContainer } from '@/components/CardContainer';
import { useTranslation } from 'react-i18next';
import { ThemeToggleButton } from '@/components/ThemToggle';
import { PageWrapper } from '../components/PageWrapper';
import { Icon, useToast } from '@rkheftan/harmony-ui';
import { Sun1, Moon, Calendar1 } from 'iconsax-react';
import { useApi } from '@/hooks/useApi';
import { saveSettings } from '../api/settingsApi';
import { useProfile } from '../hooks/useProfile';
type ThemeMode = 'light' | 'dark';
type CalendarType = 'christian' | 'solar' | 'lunar';
interface SettingsState {
language: string;
calendar: CalendarType;
theme: ThemeMode;
}
const languageOptions = [
{ code: 'fa', label: 'فارسی', apiValue: 1 },
{ code: 'en', label: 'English', apiValue: 2 },
];
const calendarOptions: { key: CalendarType; apiValue: number }[] = [
{ key: 'christian', apiValue: 1 },
{ key: 'solar', apiValue: 2 },
{ key: 'lunar', apiValue: 3 },
];
const themeApiMap: Record<ThemeMode, number> = { light: 1, dark: 2 };
export function SettingPage() {
const { t, i18n } = useTranslation(['setting']);
const { mode, setMode } = useColorScheme();
const [savedSettings, setSavedSettings] = useState<SettingsState>({
language: i18n.language,
calendar: 'solar',
theme: mode === 'light' || mode === 'dark' ? mode : 'light',
});
const [draftSettings, setDraftSettings] =
useState<SettingsState>(savedSettings);
const [isEditing, setIsEditing] = useState(false);
const showToast = useToast();
const { isLoadingProfile, refetchProfile } = useProfile();
const { loading: isSaving, execute: executeSaveSettings } =
useApi(saveSettings);
useEffect(() => {
const loadProfile = async () => {
const profileData = await refetchProfile();
if (!profileData) return;
if (profileData.success) {
if (!profileData.userSettings) return;
const { theme, calendarType, language } = profileData.userSettings;
const themeReverseMap: { [key: number]: ThemeMode | undefined } = {
1: 'light',
2: 'dark',
};
const newSettings: SettingsState = {
theme: themeReverseMap[theme] || 'light',
calendar:
calendarOptions.find((c) => c.apiValue === calendarType)?.key ||
'solar',
language:
languageOptions.find((l) => l.apiValue === language)?.code || 'en',
};
setSavedSettings(newSettings);
setDraftSettings(newSettings);
setMode(newSettings.theme);
i18n.changeLanguage(newSettings.language);
} else {
showToast({
message: profileData.message || t('message.serverError'),
severity: 'error',
});
}
};
loadProfile();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
if (isEditing) {
const resolvedMode = mode === 'light' || mode === 'dark' ? mode : 'light';
setDraftSettings({ ...savedSettings, theme: resolvedMode });
}
}, [isEditing, savedSettings, mode]);
const handleCancel = () => {
setIsEditing(false);
setDraftSettings(savedSettings);
setMode(savedSettings.theme);
};
const handleEditToggle = async () => {
if (isEditing) {
const languageObj = languageOptions.find(
(o) => o.code === draftSettings.language,
);
const calendarObj = calendarOptions.find(
(c) => c.key === draftSettings.calendar,
);
if (languageObj && calendarObj) {
const result = await executeSaveSettings({
theme: themeApiMap[draftSettings.theme],
calendarType: calendarObj.apiValue,
language: languageObj.apiValue,
});
if (!result) return;
if (result?.success) {
setMode(draftSettings.theme);
setSavedSettings(draftSettings);
i18n.changeLanguage(draftSettings.language);
setIsEditing(false);
refetchProfile({ force: true });
} else {
// Handle save failure
showToast({
message: result.message || t('message.serverError'),
severity: 'error',
});
}
}
} else {
setIsEditing(true);
}
};
return (
<PageWrapper>
<CardContainer
title={t('settings.title')}
subtitle={t('settings.description')}
highlighted={isEditing}
action={
<Box sx={{ display: 'flex', gap: 1 }}>
{isEditing && (
<Button
variant="text"
onClick={handleCancel}
size="large"
sx={{
color: 'primary.main',
textTransform: 'none',
fontSize: { xs: '0.85rem', sm: '1rem' },
}}
disabled={isSaving || isLoadingProfile}
>
{t('settings.rejectButton')}
</Button>
)}
<Button
onClick={handleEditToggle}
size="large"
variant={isEditing ? 'contained' : 'outlined'}
disabled={isSaving || isLoadingProfile}
>
{isSaving ? (
<CircularProgress size={24} />
) : isEditing ? (
t('settings.saveButton')
) : (
t('settings.editButton')
)}
</Button>
</Box>
}
>
{isLoadingProfile ? (
<Box
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
p: 4,
minHeight: '200px',
}}
>
<CircularProgress />
</Box>
) : (
<Box
sx={{
px: { xs: 2, sm: 3, md: 4 },
py: 2,
display: 'flex',
flexDirection: 'column',
gap: 2,
}}
>
<Box
sx={{
display: 'flex',
flexDirection: { xs: 'column', sm: 'row' },
gap: 4,
}}
>
<Box sx={{ flex: 1 }}>
{isEditing ? (
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: 2,
}}
>
<Typography variant="body1" color="text.primary">
{t('settings.theme')}
</Typography>
<ThemeToggleButton
value={draftSettings.theme}
onChange={(newTheme) => {
if (newTheme) {
setMode(newTheme);
setDraftSettings((prev) => ({
...prev,
theme: newTheme,
}));
}
}}
/>
</Box>
) : (
<>
<Typography variant="caption" color="text.secondary">
{t('settings.theme')}
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Icon
Component={
savedSettings.theme === 'light' ? Sun1 : Moon
}
size="medium"
variant="Bold"
/>
<Typography variant="body1">
{t(`settings.${savedSettings.theme}`)}
</Typography>
</Box>
</>
)}
</Box>
<Box sx={{ flex: 1 }}>
{isEditing ? (
<Autocomplete
options={languageOptions}
getOptionLabel={(o) => o.label}
value={languageOptions.find(
(o) => o.code === draftSettings.language,
)}
onChange={(_, v) =>
v &&
setDraftSettings((prev) => ({
...prev,
language: v.code,
}))
}
renderInput={(p) => (
<TextField {...p} label={t('settings.language')} />
)}
size="medium"
fullWidth
disableClearable
/>
) : (
<>
<Typography variant="caption" color="text.secondary">
{t('settings.language')}
</Typography>
<Typography variant="body1">
{
languageOptions.find(
(o) => o.code === savedSettings.language,
)?.label
}
</Typography>
</>
)}
</Box>
</Box>
<Box sx={{ mt: 2, width: { xs: '100%', sm: 'calc(50% - 16px)' } }}>
{isEditing ? (
<Autocomplete
options={calendarOptions.map((c) => c.key)}
getOptionLabel={(key) => t(`settings.${key}`)}
value={draftSettings.calendar}
onChange={(_, v) =>
v && setDraftSettings((prev) => ({ ...prev, calendar: v }))
}
renderInput={(params) => (
<TextField {...params} label={t('settings.calendar')} />
)}
size="medium"
fullWidth
disableClearable
/>
) : (
<>
<Typography variant="caption" color="text.secondary">
{t('settings.calendar')}
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Icon Component={Calendar1} size="medium" variant="Bold" />
<Typography variant="body1">
{t(`settings.${savedSettings.calendar}`)}
</Typography>
</Box>
</>
)}
</Box>
</Box>
)}
</CardContainer>
</PageWrapper>
);
}