Merge branch 'develop' into feat/auth

This commit is contained in:
SajadMRjl
2025-08-16 02:45:21 +03:30
committed by GitHub
57 changed files with 7196 additions and 394 deletions

View File

@@ -0,0 +1,63 @@
import React from 'react';
import { Box, Typography } from '@mui/material';
interface CardContainerProps {
title: string;
subtitle: string;
action?: React.ReactNode;
children?: React.ReactNode;
highlighted?: boolean;
}
export function CardContainer({
title,
subtitle,
action,
children,
highlighted,
}: CardContainerProps) {
return (
<Box sx={{ width: '100%', bgcolor: 'background.paper' }}>
<Box
sx={{
marginInline: 'auto',
width: '100%',
display: 'flex',
flexDirection: 'column',
gap: 2,
}}
>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: { xs: 'flex-start', sm: 'center' },
flexDirection: { xs: 'column', sm: 'row' },
bgcolor: highlighted ? 'primary.light' : 'background.default',
p: 2,
borderRadius: 1,
gap: { xs: 1, sm: 0 },
}}
>
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
<Typography
variant="h6"
color={highlighted ? 'primary.main' : 'text.primary'}
>
{title}
</Typography>
<Typography
color={highlighted ? 'primary.main' : 'text.secondary'}
variant="body2"
>
{subtitle}
</Typography>
</Box>
{action}
</Box>
{children}
</Box>
</Box>
);
}

View File

@@ -0,0 +1,41 @@
import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { toLocaleDigits } from '@/utils/persianDigit';
interface CountdownTimerProps {
initialSeconds: number;
onComplete?: () => void;
}
export function CountDownTimer({
initialSeconds,
onComplete,
}: CountdownTimerProps) {
const { i18n } = useTranslation();
const [secondsLeft, setSecondsLeft] = useState(initialSeconds);
useEffect(() => {
setSecondsLeft(initialSeconds);
const timer = setInterval(() => {
setSecondsLeft((prev) => {
if (prev <= 1) {
clearInterval(timer);
onComplete?.();
return 0;
}
return prev - 1;
});
}, 1000);
return () => clearInterval(timer);
}, [initialSeconds, onComplete]);
const formatTime = (totalSeconds: number) => {
const minutes = String(Math.floor(totalSeconds / 60)).padStart(2, '0');
const seconds = String(totalSeconds % 60).padStart(2, '0');
return toLocaleDigits(`${minutes}:${seconds}`, i18n.language);
};
return <span>{formatTime(secondsLeft)}</span>;
}

View File

@@ -0,0 +1,40 @@
import { Box, Typography } from '@mui/material';
import { useTranslation } from 'react-i18next';
// TODO: move countries outside of feature directory
import { countries } from '@/features/authentication/data/Countries';
interface CountryFlagProps {
code: string;
}
export function CountryFlag({ code }: CountryFlagProps) {
const { t } = useTranslation();
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 (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Box
component="img"
loading="lazy"
src={flagUrl}
alt={displayName}
sx={{
width: 24,
height: 16,
borderRadius: 0.5,
border: '1px solid',
borderColor: 'divider',
}}
/>
<Typography variant="body2">{displayName}</Typography>
</Box>
);
}

View File

@@ -0,0 +1,85 @@
import { ToggleButtonGroup, ToggleButton, Box } from '@mui/material';
import { Sun1, Moon } from 'iconsax-react';
import { useTranslation } from 'react-i18next';
import { Icon } from '@rkheftan/harmony-ui';
import { type MouseEvent } from 'react';
enum ThemeMode {
Light = 'light',
Dark = 'dark',
}
interface ThemeToggleButtonProps {
value: 'light' | 'dark';
onChange: (newMode: 'light' | 'dark') => void;
}
export const ThemeToggleButton = ({
value,
onChange,
}: ThemeToggleButtonProps) => {
const { t } = useTranslation('setting');
const handleChange = (
_event: MouseEvent<HTMLElement>,
newMode: 'light' | 'dark' | null,
) => {
if (newMode !== null) {
onChange(newMode);
}
};
return (
<Box>
<ToggleButtonGroup
value={value}
exclusive
onChange={handleChange}
sx={{
borderRadius: 1.5,
border: '1px solid',
borderColor: 'divider',
overflow: 'hidden',
}}
>
<ToggleButton
value={ThemeMode.Light}
aria-label="light theme"
sx={{
textTransform: 'none',
display: 'flex',
gap: 1,
px: 2,
py: 1,
'&.Mui-selected': {
bgcolor: 'primary.light',
color: 'primary.main',
},
}}
>
<Icon Component={Sun1} color="primary.main" variant="Bold" />
{t('settings.light')}
</ToggleButton>
<ToggleButton
value={ThemeMode.Dark}
aria-label="dark theme"
sx={{
textTransform: 'none',
display: 'flex',
gap: 1,
px: 2,
py: 1,
'&.Mui-selected': {
bgcolor: 'primary.light',
color: 'primary.main',
},
}}
>
<Icon Component={Moon} size="medium" color="primary.light" />
{t('settings.dark')}
</ToggleButton>
</ToggleButtonGroup>
</Box>
);
};