fix: dashboard api calls, multi profile fetch
This commit is contained in:
@@ -9,7 +9,6 @@ import {
|
||||
Button,
|
||||
Link,
|
||||
CircularProgress,
|
||||
Typography,
|
||||
InputAdornment,
|
||||
} from '@mui/material';
|
||||
import { CloseCircle, Eye, EyeSlash } from 'iconsax-react';
|
||||
@@ -18,6 +17,7 @@ import { type PasswordDialogProps } from '../../types/settingsType';
|
||||
import { PasswordValidationItem } from './PasswordValidation';
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export function PasswordDialog({
|
||||
open,
|
||||
@@ -34,13 +34,13 @@ export function PasswordDialog({
|
||||
loading,
|
||||
handleSubmit,
|
||||
changePassword,
|
||||
apiError,
|
||||
t,
|
||||
hasMinLength,
|
||||
hasNumber,
|
||||
hasUpperAndLower,
|
||||
hasSpecialChar,
|
||||
}: PasswordDialogProps) {
|
||||
const { t } = useTranslation('setting');
|
||||
|
||||
const [showCurrent, setShowCurrent] = useState(false);
|
||||
const [showNew, setShowNew] = useState(false);
|
||||
const [showConfirm, setShowConfirm] = useState(false);
|
||||
@@ -77,12 +77,6 @@ export function PasswordDialog({
|
||||
px: { xs: 2, sm: 3 },
|
||||
}}
|
||||
>
|
||||
{apiError && (
|
||||
<Typography color="error" variant="body2" textAlign="center">
|
||||
{apiError}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{changePassword && (
|
||||
<>
|
||||
<TextField
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
@@ -12,16 +12,13 @@ import { CardContainer } from '@/components/CardContainer';
|
||||
import { PageWrapper } from '../PageWrapper';
|
||||
import { PasswordDialog } from './PasswordDialog';
|
||||
import { useApi } from '@/hooks/useApi';
|
||||
import {
|
||||
addPassword,
|
||||
changePassword,
|
||||
fetchProfile,
|
||||
} from '../../api/settingsApi';
|
||||
import { addPassword, changePassword } from '../../api/settingsApi';
|
||||
import { containsNumber } from '@/utils/regexes/containsNumber';
|
||||
import { least8Chars } from '@/utils/regexes/least8Chars';
|
||||
import { hasUpperAndLowerLetter } from '@/utils/regexes/hasUpperAndLowerLetter';
|
||||
import { containsSymbol } from '@/utils/regexes/containsSymbol';
|
||||
import { useToast } from '@rkheftan/harmony-ui';
|
||||
import { useProfile } from '../../hooks/useProfile';
|
||||
|
||||
export function PasswordSecurity() {
|
||||
const theme = useTheme();
|
||||
@@ -45,31 +42,33 @@ export function PasswordSecurity() {
|
||||
const matchPassword = password === confirmPassword;
|
||||
const showToast = useToast();
|
||||
|
||||
const {
|
||||
data: profileData,
|
||||
loading: isFetching,
|
||||
error: fetchError,
|
||||
} = useApi(fetchProfile, { immediate: true });
|
||||
const { isLoadingProfile, refetchProfile } = useProfile();
|
||||
|
||||
const {
|
||||
data: addData,
|
||||
loading: isAdding,
|
||||
error: addError,
|
||||
execute: executeAddPassword,
|
||||
} = useApi(addPassword);
|
||||
const { loading: isAdding, execute: executeAddPassword } =
|
||||
useApi(addPassword);
|
||||
|
||||
const {
|
||||
data: changeData,
|
||||
loading: isChanging,
|
||||
error: changeError,
|
||||
execute: executeChangePassword,
|
||||
} = useApi(changePassword);
|
||||
const { loading: isChanging, execute: executeChangePassword } =
|
||||
useApi(changePassword);
|
||||
|
||||
useEffect(() => {
|
||||
if (profileData?.success) {
|
||||
setChangePasswordUI(profileData.hasPassword || false);
|
||||
}
|
||||
}, [profileData]);
|
||||
const loadProfile = async () => {
|
||||
const profileData = await refetchProfile();
|
||||
|
||||
if (!profileData) return;
|
||||
|
||||
if (profileData.success) {
|
||||
setChangePasswordUI(profileData.hasPassword || false);
|
||||
} else {
|
||||
showToast({
|
||||
message: profileData.message || t('message.serverError'),
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
loadProfile();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (password) {
|
||||
@@ -79,30 +78,6 @@ export function PasswordSecurity() {
|
||||
}
|
||||
}, [password, validPassword]);
|
||||
|
||||
useEffect(() => {
|
||||
if (addData?.success || changeData?.success) {
|
||||
showToast({
|
||||
message: changePasswordUI
|
||||
? t('securityForm.passwordChanged')
|
||||
: t('securityForm.passwordAdded'),
|
||||
severity: 'success',
|
||||
});
|
||||
if (!changePasswordUI) {
|
||||
setChangePasswordUI(true);
|
||||
}
|
||||
setOpen(false);
|
||||
setPassword('');
|
||||
setConfirmPassword('');
|
||||
setCurrentPassword('');
|
||||
} else if (addData || changeData) {
|
||||
showToast({
|
||||
message:
|
||||
addData?.message || changeData?.message || t('securityForm.error'),
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
}, [addData, changeData, changePasswordUI, showToast, t]);
|
||||
|
||||
const handleOpen = () => setOpen(true);
|
||||
const handleClose = () => setOpen(false);
|
||||
|
||||
@@ -115,26 +90,48 @@ export function PasswordSecurity() {
|
||||
return;
|
||||
}
|
||||
if (changePasswordUI) {
|
||||
await executeChangePassword({
|
||||
const changeData = await executeChangePassword({
|
||||
oldPassword: currentPassword,
|
||||
newPassword: password,
|
||||
});
|
||||
|
||||
if (!changeData) return;
|
||||
|
||||
if (!changeData.success) {
|
||||
showToast({
|
||||
message: changeData?.message || t('securityForm.error'),
|
||||
severity: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
await executeAddPassword({ newPassword: password });
|
||||
const addData = await executeAddPassword({ newPassword: password });
|
||||
|
||||
if (!addData) return;
|
||||
|
||||
if (!addData.success) {
|
||||
showToast({
|
||||
message: addData?.message || t('securityForm.error'),
|
||||
severity: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
showToast({
|
||||
message: changePasswordUI
|
||||
? t('securityForm.passwordChanged')
|
||||
: t('securityForm.passwordAdded'),
|
||||
severity: 'success',
|
||||
});
|
||||
if (!changePasswordUI) {
|
||||
setChangePasswordUI(true);
|
||||
}
|
||||
setOpen(false);
|
||||
setPassword('');
|
||||
setConfirmPassword('');
|
||||
setCurrentPassword('');
|
||||
};
|
||||
|
||||
const getErrorMessage = (error: unknown): string | null => {
|
||||
if (!error) return null;
|
||||
if (error instanceof Error) return error.message;
|
||||
return String(error);
|
||||
};
|
||||
|
||||
const apiError = useMemo(
|
||||
() => addError || changeError,
|
||||
[addError, changeError],
|
||||
);
|
||||
|
||||
return (
|
||||
<PageWrapper>
|
||||
<CardContainer
|
||||
@@ -153,7 +150,7 @@ export function PasswordSecurity() {
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
{isFetching ? (
|
||||
{isLoadingProfile ? (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
@@ -165,12 +162,6 @@ export function PasswordSecurity() {
|
||||
>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
) : fetchError ? (
|
||||
<Box sx={{ textAlign: 'center', p: 4 }}>
|
||||
<Typography color="error.main">
|
||||
{getErrorMessage(fetchError)}
|
||||
</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
<Box sx={{ px: 4, py: 2 }}>
|
||||
{changePasswordUI ? (
|
||||
@@ -208,8 +199,6 @@ export function PasswordSecurity() {
|
||||
loading={isAdding || isChanging}
|
||||
handleSubmit={handlePasswordSubmit}
|
||||
changePassword={changePasswordUI}
|
||||
apiError={getErrorMessage(apiError)}
|
||||
t={t}
|
||||
hasMinLength={hasMinLength}
|
||||
hasNumber={hasNumber}
|
||||
hasUpperAndLower={hasUpperAndLower}
|
||||
|
||||
@@ -9,11 +9,10 @@ import {
|
||||
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 { useApi } from '@/hooks/useApi';
|
||||
import { formatDate } from '@/utils/formatSessionDate';
|
||||
import { useToast } from '@rkheftan/harmony-ui';
|
||||
import { useProfile } from '../../hooks/useProfile';
|
||||
|
||||
export function RecentLogins() {
|
||||
const { t, i18n } = useTranslation('setting');
|
||||
@@ -22,31 +21,43 @@ export function RecentLogins() {
|
||||
const isXsup = useMediaQuery(theme.breakpoints.up('xs'));
|
||||
const showToast = useToast();
|
||||
|
||||
const {
|
||||
data: profileData,
|
||||
loading: isLoading,
|
||||
error: fetchError,
|
||||
} = useApi(fetchProfile, { immediate: true });
|
||||
const { isLoadingProfile, refetchProfile } = useProfile();
|
||||
|
||||
// useEffect(() => {
|
||||
// if (profileData?.success && Array.isArray(profileData.loginLogs)) {
|
||||
// setLogs(profileData.loginLogs);
|
||||
// }
|
||||
// }, [profileData]);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (fetchError) {
|
||||
// showToast({
|
||||
// message: getErrorMessage(fetchError) || t('active.errorFetch'),
|
||||
// severity: 'error',
|
||||
// });
|
||||
// }
|
||||
// }, [fetchError, t, showToast]);
|
||||
|
||||
useEffect(() => {
|
||||
if (profileData?.success && Array.isArray(profileData.loginLogs)) {
|
||||
setLogs(profileData.loginLogs);
|
||||
}
|
||||
}, [profileData]);
|
||||
const loadProfile = async () => {
|
||||
const profileData = await refetchProfile();
|
||||
|
||||
useEffect(() => {
|
||||
if (fetchError) {
|
||||
showToast({
|
||||
message: getErrorMessage(fetchError) || t('active.errorFetch'),
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
}, [fetchError, t, showToast]);
|
||||
const getErrorMessage = (error: unknown): string | null => {
|
||||
if (!error) return null;
|
||||
if (error instanceof Error) return error.message;
|
||||
return String(error);
|
||||
};
|
||||
if (!profileData) return;
|
||||
|
||||
if (profileData.success) {
|
||||
if (!Array.isArray(profileData.loginLogs)) return;
|
||||
setLogs(profileData.loginLogs);
|
||||
} else {
|
||||
showToast({
|
||||
message: profileData.message || t('active.errorFetch'),
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
loadProfile();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<PageWrapper>
|
||||
@@ -54,7 +65,7 @@ export function RecentLogins() {
|
||||
title={t('securityForm.recentLogins')}
|
||||
subtitle={t('securityForm.description')}
|
||||
>
|
||||
{isLoading ? (
|
||||
{isLoadingProfile ? (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
@@ -66,12 +77,6 @@ export function RecentLogins() {
|
||||
>
|
||||
<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) => (
|
||||
|
||||
Reference in New Issue
Block a user