106 lines
3.1 KiB
TypeScript
106 lines
3.1 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import {
|
|
Box,
|
|
Typography,
|
|
CircularProgress,
|
|
useTheme,
|
|
useMediaQuery,
|
|
} from '@mui/material';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { CardContainer } from '@/components/CardContainer';
|
|
import { PageWrapper } from '../PageWrapper';
|
|
import { fetchProfile } from '../../api/settingsApi';
|
|
import type { LoginLog } from '../../types/settingsApiType';
|
|
import { useManualApi } from '@/hooks/useApi';
|
|
import { formatDate } from '@/utils/formatSessionDate';
|
|
|
|
export function RecentLogins() {
|
|
const { t, i18n } = useTranslation('setting');
|
|
const [logs, setLogs] = useState<LoginLog[]>([]);
|
|
const theme = useTheme();
|
|
const isXsup = useMediaQuery(theme.breakpoints.up('xs'));
|
|
|
|
const {
|
|
data: profileData,
|
|
loading: isLoading,
|
|
error: fetchError,
|
|
execute: executeFetchProfile,
|
|
} = useManualApi(fetchProfile);
|
|
|
|
useEffect(() => {
|
|
executeFetchProfile();
|
|
}, [i18n.language]);
|
|
|
|
useEffect(() => {
|
|
if (profileData?.success && Array.isArray(profileData.loginLogs)) {
|
|
setLogs(profileData.loginLogs);
|
|
}
|
|
}, [profileData]);
|
|
|
|
const getErrorMessage = (error: unknown): string | null => {
|
|
if (!error) return null;
|
|
if (error instanceof Error) return error.message;
|
|
return String(error);
|
|
};
|
|
|
|
return (
|
|
<PageWrapper>
|
|
<CardContainer
|
|
title={t('securityForm.recentLogins')}
|
|
subtitle={t('securityForm.description')}
|
|
>
|
|
{isLoading ? (
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
p: 4,
|
|
minHeight: '200px',
|
|
}}
|
|
>
|
|
<CircularProgress />
|
|
</Box>
|
|
) : fetchError ? (
|
|
<Box sx={{ textAlign: 'center', p: 4 }}>
|
|
<Typography color="error.main">
|
|
{getErrorMessage(fetchError) || t('active.errorFetch')}
|
|
</Typography>
|
|
</Box>
|
|
) : (
|
|
<Box sx={{ px: 4 }}>
|
|
{logs.map((log, index) => (
|
|
<React.Fragment key={index}>
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
minHeight: 50,
|
|
py: 1.5,
|
|
flexWrap: 'wrap',
|
|
gap: 2,
|
|
}}
|
|
>
|
|
<Typography sx={{ flex: 1, minWidth: 160 }}>
|
|
{formatDate(log.loginDateTime, i18n.language, t)}
|
|
</Typography>
|
|
<Typography sx={{ flex: 1, minWidth: 160 }}>
|
|
{`${log.deviceOs} ${log.deviceName}`}
|
|
</Typography>
|
|
<Typography sx={{ flex: 1, minWidth: 120 }}>
|
|
{log.ipAddress}
|
|
</Typography>
|
|
</Box>
|
|
{isXsup && index < logs.length - 1 && (
|
|
<Box sx={{ borderBottom: 1, borderColor: 'divider' }} />
|
|
)}
|
|
</React.Fragment>
|
|
))}
|
|
</Box>
|
|
)}
|
|
</CardContainer>
|
|
</PageWrapper>
|
|
);
|
|
}
|