chore: change the styles and add countries to the user profile

This commit is contained in:
Koosha Lahouti
2025-08-10 17:51:46 -07:00
parent 8e6c09225d
commit ed57858c2e
6 changed files with 146 additions and 128 deletions

View File

@@ -1,40 +0,0 @@
// components/Countries.ts
export interface Country {
name: string;
fa: string;
flag: string;
}
export const countries: Country[] = [
{ name: 'Afghanistan', fa: 'افغانستان', flag: 'af' },
{ name: 'Albania', fa: 'آلبانی', flag: 'al' },
{ name: 'Algeria', fa: 'الجزایر', flag: 'dz' },
{ name: 'Argentina', fa: 'آرژانتین', flag: 'ar' },
{ name: 'Armenia', fa: 'ارمنستان', flag: 'am' },
{ name: 'Australia', fa: 'استرالیا', flag: 'au' },
{ name: 'Austria', fa: 'اتریش', flag: 'at' },
{ name: 'Bahrain', fa: 'بحرین', flag: 'bh' },
{ name: 'Canada', fa: 'کانادا', flag: 'ca' },
{ name: 'China', fa: 'چین', flag: 'cn' },
{ name: 'France', fa: 'فرانسه', flag: 'fr' },
{ name: 'Germany', fa: 'آلمان', flag: 'de' },
{ name: 'India', fa: 'هند', flag: 'in' },
{ name: 'Iran', fa: 'ایران', flag: 'ir' },
{ name: 'Iraq', fa: 'عراق', flag: 'iq' },
{ name: 'Italy', fa: 'ایتالیا', flag: 'it' },
{ name: 'Japan', fa: 'ژاپن', flag: 'jp' },
{ name: 'Netherlands', fa: 'هلند', flag: 'nl' },
{ name: 'Pakistan', fa: 'پاکستان', flag: 'pk' },
{ name: 'Qatar', fa: 'قطر', flag: 'qa' },
{ name: 'Russia', fa: 'روسیه', flag: 'ru' },
{ name: 'Saudi Arabia', fa: 'عربستان سعودی', flag: 'sa' },
{ name: 'Spain', fa: 'اسپانیا', flag: 'es' },
{ name: 'Sweden', fa: 'سوئد', flag: 'se' },
{ name: 'Switzerland', fa: 'سوئیس', flag: 'ch' },
{ name: 'Turkey', fa: 'ترکیه', flag: 'tr' },
{ name: 'United Arab Emirates', fa: 'امارات متحده عربی', flag: 'ae' },
{ name: 'United Kingdom', fa: 'بریتانیا', flag: 'gb' },
{ name: 'United States', fa: 'ایالات متحده آمریکا', flag: 'us' },
{ name: 'Yemen', fa: 'یمن', flag: 'ye' },
];

View File

@@ -1,46 +1,34 @@
import { Box, Typography } from '@mui/material'; import { Box, Typography } from '@mui/material';
import { useTranslation } from 'react-i18next';
import { countries } from '@/features/profile/data/countries';
export const countryCodeMap: { [key: string]: string } = { interface CountryFlagProps {
ایران: 'ir', code: string;
قطر: 'qa', }
آلمان: 'de',
آمریکا: 'us',
فرانسه: 'fr',
ایتالیا: 'it',
اسپانیا: 'es',
انگلیس: 'gb',
کانادا: 'ca',
استرالیا: 'au',
چین: 'cn',
ژاپن: 'jp',
هند: 'in',
روسیه: 'ru',
برزیل: 'br',
آرژانتین: 'ar',
ترکیه: 'tr',
سوئیس: 'ch',
سوئد: 'se',
نروژ: 'no',
عربستان: 'sa',
امارات: 'ae',
عراق: 'iq',
پاکستان: 'pk',
};
export function CountryFlag({ country }: { country: string }) { export function CountryFlag({ code }: CountryFlagProps) {
const countryCode = countryCodeMap[country] || 'un'; const { t } = useTranslation();
const flagUrl = `https://flagcdn.com/w40/${countryCode}.png`;
const countryObj = code ? countries.find((c) => c.code === code) : null;
if (!countryObj) {
return null;
}
const displayName = t(countryObj.label);
const flagUrl = `https://flagcdn.com/w40/${countryObj.code.toLowerCase()}.png`;
return ( return (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<img <img
loading="lazy"
src={flagUrl} src={flagUrl}
alt={country} alt={displayName}
width="24" width="24"
height="16" height="16"
style={{ borderRadius: '2px', border: '1px solid #ccc' }} style={{ borderRadius: '2px', border: '1px solid #ccc' }}
/> />
<Typography variant="body2">{country}</Typography> <Typography variant="body2">{displayName}</Typography>
</Box> </Box>
); );
} }

View File

@@ -2,12 +2,17 @@ import { ToggleButtonGroup, ToggleButton, Box } from '@mui/material';
import { useColorScheme } from '@mui/material/styles'; import { useColorScheme } from '@mui/material/styles';
import { Sun1, Moon } from 'iconsax-react'; import { Sun1, Moon } from 'iconsax-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Icon } from '@rkheftan/harmony-ui';
import { type MouseEvent } from 'react';
export const ThemeToggleButton = () => { export const ThemeToggleButton = () => {
const { mode, setMode } = useColorScheme(); const { mode, setMode } = useColorScheme();
const { t } = useTranslation('setting'); const { t } = useTranslation('setting');
const handleChange = (_: any, newMode: 'light' | 'dark' | null) => { const handleChange = (
_event: MouseEvent<HTMLElement>,
newMode: 'light' | 'dark' | null,
) => {
if (newMode !== null) { if (newMode !== null) {
setMode(newMode); setMode(newMode);
localStorage.setItem('theme', newMode); localStorage.setItem('theme', newMode);
@@ -41,7 +46,7 @@ export const ThemeToggleButton = () => {
}, },
}} }}
> >
<Sun1 size={18} color="#2979FF" variant="Bold" /> <Icon Component={Sun1} color="primary.main" variant="Bold" />
{t('settings.light')} {t('settings.light')}
</ToggleButton> </ToggleButton>
@@ -59,7 +64,7 @@ export const ThemeToggleButton = () => {
}, },
}} }}
> >
<Moon size={18} color="#82B1FF" /> <Icon Component={Moon} size="medium" color="primary.light" />
{t('settings.dark')} {t('settings.dark')}
</ToggleButton> </ToggleButton>
</ToggleButtonGroup> </ToggleButtonGroup>

View File

@@ -11,6 +11,8 @@ import { CardContainer } from '@/components/CardContainer';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ThemeToggleButton } from '@/components/ThemToggle'; import { ThemeToggleButton } from '@/components/ThemToggle';
import { PageWrapper } from '../PageWrapper'; import { PageWrapper } from '../PageWrapper';
import { Icon } from '@rkheftan/harmony-ui';
import { Sun1, Moon, Calendar1 } from 'iconsax-react';
export function Setting() { export function Setting() {
const { t, i18n } = useTranslation(['setting']); const { t, i18n } = useTranslation(['setting']);
@@ -21,18 +23,18 @@ export function Setting() {
); );
const [draftLanguage, setDraftLanguage] = useState<string>(savedLanguage); const [draftLanguage, setDraftLanguage] = useState<string>(savedLanguage);
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
const [selectedCalendar, setSelectedCalendar] = useState<string>( const [selectedCalendar, setSelectedCalendar] = useState<
t('settings.solar'), 'christian' | 'solar' | 'lunar'
); >('solar');
const languageOptions = [ const languageOptions = [
{ code: 'en', label: 'English' }, { code: 'en', label: 'English' },
{ code: 'fa', label: 'فارسی' }, { code: 'fa', label: 'فارسی' },
]; ];
const calendarOptions = [ const calendarOptions: ('christian' | 'solar' | 'lunar')[] = [
t('settings.christian'), 'christian',
t('settings.solar'), 'solar',
t('settings.lunar'), 'lunar',
]; ];
const handleDraftLanguageChange = ( const handleDraftLanguageChange = (
@@ -56,6 +58,10 @@ export function Setting() {
const handleEditToggle = () => const handleEditToggle = () =>
isEditing ? handleSave() : setIsEditing(true); isEditing ? handleSave() : setIsEditing(true);
// useEffect(() => {
// setSelectedCalendar(t('settings.solar'));
// }, [i18n.language, t]);
return ( return (
<PageWrapper> <PageWrapper>
<Box <Box
@@ -141,11 +147,26 @@ export function Setting() {
<Typography variant="caption" color="text.secondary"> <Typography variant="caption" color="text.secondary">
{t('settings.theme')} {t('settings.theme')}
</Typography> </Typography>
<Typography variant="body1">
{mode === 'light' <Box
? t('settings.light') sx={{
: t('settings.dark')} display: 'flex',
</Typography> alignItems: 'center',
gap: 1,
}}
>
<Icon
Component={mode === 'light' ? Sun1 : Moon}
size="medium"
variant="Bold"
color={mode === 'light' ? 'black' : 'primary.main'}
/>
<Typography variant="body1">
{mode === 'light'
? t('settings.light')
: t('settings.dark')}
</Typography>
</Box>
</Box> </Box>
)} )}
</Box> </Box>
@@ -184,10 +205,11 @@ export function Setting() {
{isEditing ? ( {isEditing ? (
<Autocomplete <Autocomplete
options={calendarOptions} options={calendarOptions}
getOptionLabel={(key) => t(`settings.${key}`)}
value={selectedCalendar} value={selectedCalendar}
onChange={(_, v) => v && setSelectedCalendar(v)} onChange={(_, v) => v && setSelectedCalendar(v)}
renderInput={(p) => ( renderInput={(params) => (
<TextField {...p} label={t('settings.calendar')} /> <TextField {...params} label={t('settings.calendar')} />
)} )}
size="medium" size="medium"
fullWidth fullWidth
@@ -197,7 +219,17 @@ export function Setting() {
<Typography variant="caption" color="text.secondary"> <Typography variant="caption" color="text.secondary">
{t('settings.calendar')} {t('settings.calendar')}
</Typography> </Typography>
<Typography variant="body1">{selectedCalendar}</Typography> <Box sx={{ display: 'flex', gap: 1 }}>
<Icon
Component={Calendar1}
size="medium"
color={mode === 'light' ? 'black' : 'primary.main'}
variant="Bold"
/>
<Typography variant="body1">
{t(`settings.${selectedCalendar}`)}
</Typography>
</Box>
</Box> </Box>
)} )}
</Box> </Box>

View File

@@ -1,7 +1,7 @@
import { Box, Typography, Avatar } from '@mui/material'; import { Box, Typography, Avatar } from '@mui/material';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { CountryFlag } from '@/components/CountryFlag';
import { DisplayField } from './DisplayField'; import { DisplayField } from './DisplayField';
import { CountryFlag } from '@/components/CountryFlag';
import { Gender } from '@/features/profile/types'; import { Gender } from '@/features/profile/types';
interface InfoRowData { interface InfoRowData {
@@ -26,6 +26,7 @@ export function InfoRowDisplay({
const { t } = useTranslation('profileSetting'); const { t } = useTranslation('profileSetting');
const displayValue = (value: string) => const displayValue = (value: string) =>
value?.trim() || t('settingForm.notDetermined'); value?.trim() || t('settingForm.notDetermined');
const getGenderLabel = (gender: Gender | '') => { const getGenderLabel = (gender: Gender | '') => {
switch (gender) { switch (gender) {
case Gender.Male: case Gender.Male:
@@ -36,6 +37,7 @@ export function InfoRowDisplay({
return t('settingForm.notDetermined'); return t('settingForm.notDetermined');
} }
}; };
return ( return (
<Box sx={{ mb: 2 }}> <Box sx={{ mb: 2 }}>
<Box sx={{ width: '100%' }}> <Box sx={{ width: '100%' }}>
@@ -70,7 +72,9 @@ export function InfoRowDisplay({
{initials} {initials}
</Avatar> </Avatar>
<Typography variant="body1" color="text.primary"> <Typography variant="body1" color="text.primary">
{`${displayValue(data.firstName)} ${displayValue(data.lastName)}`} {`${displayValue(data.firstName)} ${displayValue(
data.lastName,
)}`}
</Typography> </Typography>
</Box> </Box>
</Box> </Box>
@@ -79,7 +83,15 @@ export function InfoRowDisplay({
<Typography variant="caption" color="text.secondary"> <Typography variant="caption" color="text.secondary">
{t('settingForm.country')} {t('settingForm.country')}
</Typography> </Typography>
<CountryFlag country={data.country} /> <Box sx={{ mt: 0.5 }}>
{data.country ? (
<CountryFlag code={data.country} />
) : (
<Typography variant="body1" color="text.primary">
{t('settingForm.notDetermined')}
</Typography>
)}
</Box>
</Box> </Box>
</Box> </Box>
</Box> </Box>

View File

@@ -7,7 +7,8 @@ import {
Autocomplete, Autocomplete,
} from '@mui/material'; } from '@mui/material';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { CountryFlag, countryCodeMap } from '@/components/CountryFlag'; import { countries } from '@/features/profile/data/countries';
import { CountryFlag } from '@/components/CountryFlag';
import { Gender } from '@/features/profile/types'; import { Gender } from '@/features/profile/types';
import { type InfoRowData } from '@/features/profile/types'; import { type InfoRowData } from '@/features/profile/types';
@@ -24,44 +25,50 @@ export function InfoRowEdit({
gender, gender,
setGender, setGender,
}: InfoRowEditProps) { }: InfoRowEditProps) {
const { t } = useTranslation('profileSetting'); const { t } = useTranslation(['countries', 'profileSetting']);
const labels = [ const countryOptions = countries.map((c) => ({
{ code: c.code,
name: 'firstName' as keyof InfoRowData, label: t(c.label, { ns: 'countries' }),
label: t('settingForm.name'), }));
value: data.firstName,
}, const currentCountry =
{ countryOptions.find((c) => c.code === data.country) || null;
name: 'lastName' as keyof InfoRowData,
label: t('settingForm.familyName'),
value: data.lastName,
},
{
name: 'nationalCode' as keyof InfoRowData,
label: t('settingForm.nationalCode'),
value: data.nationalCode,
},
];
return ( return (
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap' }}> <Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap' }}>
{labels.map((field, idx) => ( {[
{
name: 'firstName' as keyof InfoRowData,
label: t('settingForm.name', { ns: 'profileSetting' }),
value: data.firstName,
},
{
name: 'lastName' as keyof InfoRowData,
label: t('settingForm.familyName', { ns: 'profileSetting' }),
value: data.lastName,
},
{
name: 'nationalCode' as keyof InfoRowData,
label: t('settingForm.nationalCode', { ns: 'profileSetting' }),
value: data.nationalCode,
},
].map(({ name, label, value }) => (
<Box <Box
key={idx} key={name}
sx={{ width: { xs: '100%', sm: '48%', md: 'calc(50% - 8px)' } }} sx={{ width: { xs: '100%', sm: '48%', md: 'calc(50% - 8px)' } }}
> >
<TextField <TextField
fullWidth fullWidth
name={field.name} name={name}
value={field.value} value={value}
onChange={(e) => onChange={(e) =>
setData((prev) => ({ setData((prev) => ({
...prev, ...prev,
[field.name]: e.target.value, [name]: e.target.value,
})) }))
} }
label={field.label} label={label}
/> />
</Box> </Box>
))} ))}
@@ -75,36 +82,50 @@ export function InfoRowEdit({
renderValue={(selected) => renderValue={(selected) =>
selected ? ( selected ? (
selected === Gender.Male ? ( selected === Gender.Male ? (
t('settingForm.man') t('settingForm.man', { ns: 'profileSetting' })
) : ( ) : (
t('settingForm.woman') t('settingForm.woman', { ns: 'profileSetting' })
) )
) : ( ) : (
<span>{t('settingForm.genderPlaceholder')}</span> <span>
{t('settingForm.genderPlaceholder', { ns: 'profileSetting' })}
</span>
) )
} }
> >
<MenuItem value={Gender.Male}>{t('settingForm.man')}</MenuItem> <MenuItem value={Gender.Male}>
<MenuItem value={Gender.Female}>{t('settingForm.woman')}</MenuItem> {t('settingForm.man', { ns: 'profileSetting' })}
</MenuItem>
<MenuItem value={Gender.Female}>
{t('settingForm.woman', { ns: 'profileSetting' })}
</MenuItem>
</Select> </Select>
</FormControl> </FormControl>
</Box> </Box>
<Autocomplete <Autocomplete
sx={{ width: { xs: '100%', sm: '48%', md: 'calc(50% - 8px)' } }} sx={{ width: { xs: '100%', sm: '48%', md: 'calc(50% - 8px)' } }}
options={Object.keys(countryCodeMap)} options={countryOptions}
value={data.country} getOptionLabel={(option) => option.label}
value={currentCountry}
onChange={(_, newValue) => onChange={(_, newValue) =>
setData((prev) => ({ ...prev, country: newValue || '' })) setData((prev) => ({
...prev,
country: newValue?.code || '',
}))
} }
renderOption={(props, option) => ( renderOption={(props, option) => (
<Box component="li" {...props}> <Box component="li" {...props} key={option.code}>
<CountryFlag country={option} /> <CountryFlag code={option.code} />
</Box> </Box>
)} )}
renderInput={(params) => ( renderInput={(params) => (
<TextField {...params} label={t('settingForm.country')} /> <TextField
{...params}
label={t('settingForm.country', { ns: 'profileSetting' })}
/>
)} )}
clearOnEscape
/> />
</Box> </Box>
); );