339 lines
11 KiB
TypeScript
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>
|
|
);
|
|
}
|