chore: move api to api folder and seperate the types into another file

This commit is contained in:
Koosha Lahouti
2025-08-15 15:21:34 +03:30
parent 5a72b597c8
commit 0b42f4ccba
22 changed files with 845 additions and 1402 deletions

View File

@@ -2,116 +2,46 @@ import React, { useState, useEffect } from 'react';
import {
Box,
Typography,
Button,
CircularProgress,
useTheme,
useMediaQuery,
} from '@mui/material';
import { useTranslation } from 'react-i18next';
import type { TFunction } from 'i18next';
import { CardContainer } from '@/components/CardContainer';
import { PageWrapper } from '../PageWrapper';
import apiClient from '@/lib/apiClient';
function formatLoginDate(isoDate: string, lang: string, t: TFunction): string {
const date = new Date(isoDate);
const now = new Date();
const diffInMinutes = Math.floor(
(now.getTime() - date.getTime()) / (1000 * 60),
);
if (diffInMinutes < 1) return t('active.justNow');
if (diffInMinutes < 60)
return t('active.minutesAgo', { count: diffInMinutes });
let displayLocale: string;
let options: Intl.DateTimeFormatOptions;
if (lang.startsWith('fa')) {
displayLocale = 'fa-IR';
options = {
calendar: 'persian',
numberingSystem: 'arab',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hour12: false,
};
} else {
displayLocale = 'en-US';
options = {
calendar: 'gregory',
numberingSystem: 'latn',
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: false,
};
}
return new Intl.DateTimeFormat(displayLocale, options).format(date);
}
interface LoginLog {
loginDateTime: string;
ipAddress: string;
deviceName: string;
deviceOs: string;
}
interface ProfileApiResponse {
success: boolean;
loginLogs?: LoginLog[];
}
import { fetchProfile } from '../../api/settingsApi';
import type { LoginLog } from '../../types/settingsApiType';
import { useManualApi } from '@/hooks/useApi';
import { formatDate } from '@/utils/formatSessionDate'; // ✅ 1. IMPORT the shared function
export function RecentLogins() {
const { t, i18n } = useTranslation('setting');
const token = localStorage.getItem('authToken');
const [logs, setLogs] = useState<LoginLog[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [fetchError, setFetchError] = useState<string | null>(null);
const theme = useTheme();
const isXsup = useMediaQuery(theme.breakpoints.up('xs'));
const {
data: profileData,
loading: isLoading,
error: fetchError,
execute: executeFetchProfile,
} = useManualApi(fetchProfile);
useEffect(() => {
const fetchLoginLogs = async () => {
setIsLoading(true);
setFetchError(null);
executeFetchProfile();
}, [i18n.language]);
if (!token) {
setIsLoading(false);
setFetchError(t('active.notLoggedIn'));
return;
}
useEffect(() => {
if (profileData?.success && Array.isArray(profileData.loginLogs)) {
setLogs(profileData.loginLogs);
}
}, [profileData]);
try {
const res = await apiClient.post<ProfileApiResponse>(
'/Profile/GetProfile',
{},
{ headers: { Authorization: `Bearer ${token}` } },
);
if (res.data.success && Array.isArray(res.data.loginLogs)) {
setLogs(res.data.loginLogs);
} else {
throw new Error(t('active.failFetchActiveSessions'));
}
} catch (err) {
console.error(t('active.failFetchActiveSessions'), err);
setFetchError(t('active.errorFetch'));
} finally {
setIsLoading(false);
}
};
fetchLoginLogs();
}, [token, i18n.language, t]);
const getErrorMessage = (error: unknown): string | null => {
if (!error) return null;
if (error instanceof Error) return error.message;
return String(error);
};
return (
<PageWrapper>
@@ -133,7 +63,9 @@ export function RecentLogins() {
</Box>
) : fetchError ? (
<Box sx={{ textAlign: 'center', p: 4 }}>
<Typography color="error.main">{fetchError}</Typography>
<Typography color="error.main">
{getErrorMessage(fetchError) || t('active.errorFetch')}
</Typography>
</Box>
) : (
<Box sx={{ px: 4 }}>
@@ -142,45 +74,26 @@ export function RecentLogins() {
<Box
sx={{
display: 'flex',
flexDirection: { xs: 'column', sm: 'row' },
alignItems: { xs: 'flex-start', sm: 'center' },
flexDirection: 'row',
alignItems: 'center',
minHeight: 50,
py: 1.5,
flexWrap: 'wrap',
gap: 2,
}}
>
<Typography
variant="body2"
sx={{
flexBasis: { xs: '100%', sm: 'auto' },
mb: { xs: 1, sm: 0 },
minWidth: { sm: '172.5px' },
}}
>
{formatLoginDate(log.loginDateTime, i18n.language, t)}
<Typography sx={{ flex: 1, minWidth: 160 }}>
{formatDate(log.loginDateTime, i18n.language, t)}
</Typography>
<Typography
variant="body2"
sx={{
flexBasis: { xs: '100%', sm: 'auto' },
mb: { xs: 1, sm: 0 },
minWidth: { sm: '172.5px' },
}}
>
<Typography sx={{ flex: 1, minWidth: 160 }}>
{`${log.deviceOs} ${log.deviceName}`}
</Typography>
<Typography
variant="body2"
sx={{
flexBasis: { xs: '100%', sm: 'auto' },
mb: { xs: 1, sm: 0 },
minWidth: { sm: '172.5px' },
}}
>
<Typography sx={{ flex: 1, minWidth: 120 }}>
{log.ipAddress}
</Typography>
</Box>
{isXsup && (
<Box sx={{ color: 'divider', borderBottom: '1px solid' }} />
{isXsup && index < logs.length - 1 && (
<Box sx={{ borderBottom: 1, borderColor: 'divider' }} />
)}
</React.Fragment>
))}