192 lines
5.3 KiB
TypeScript
192 lines
5.3 KiB
TypeScript
import { useState, useEffect, useMemo } from 'react';
|
|
import {
|
|
Box,
|
|
Typography,
|
|
Button,
|
|
CircularProgress,
|
|
useTheme,
|
|
useMediaQuery,
|
|
} from '@mui/material';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { CardContainer } from '@/components/CardContainer';
|
|
import { PageWrapper } from '../PageWrapper';
|
|
import { PasswordDialog } from './PasswordDialog';
|
|
import { regex } from '@/utils/regex';
|
|
import { useManualApi } from '@/hooks/useApi';
|
|
import {
|
|
addPassword,
|
|
changePassword,
|
|
fetchProfile,
|
|
} from '../../api/settingsApi';
|
|
|
|
export function PasswordSecurity() {
|
|
const theme = useTheme();
|
|
const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
|
const { t } = useTranslation('setting');
|
|
|
|
const [open, setOpen] = useState(false);
|
|
const [password, setPassword] = useState('');
|
|
const [confirmPassword, setConfirmPassword] = useState('');
|
|
const [currentPassword, setCurrentPassword] = useState('');
|
|
const [showValidation, setShowValidation] = useState(false);
|
|
const [showPasswordAlert, setShowPasswordAlert] = useState(false);
|
|
const [changePasswordUI, setChangePasswordUI] = useState(false);
|
|
|
|
const { validPassword } = regex(password);
|
|
const matchPassword = password === confirmPassword;
|
|
|
|
const {
|
|
data: profileData,
|
|
loading: isFetching,
|
|
error: fetchError,
|
|
execute: executeFetchProfile,
|
|
} = useManualApi(fetchProfile);
|
|
|
|
const {
|
|
data: addData,
|
|
loading: isAdding,
|
|
error: addError,
|
|
execute: executeAddPassword,
|
|
} = useManualApi(addPassword);
|
|
|
|
const {
|
|
data: changeData,
|
|
loading: isChanging,
|
|
error: changeError,
|
|
execute: executeChangePassword,
|
|
} = useManualApi(changePassword);
|
|
|
|
useEffect(() => {
|
|
executeFetchProfile();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (profileData?.success) {
|
|
setChangePasswordUI(profileData.hasPassword || false);
|
|
}
|
|
}, [profileData]);
|
|
|
|
useEffect(() => {
|
|
if (password) {
|
|
setShowValidation(!validPassword);
|
|
} else {
|
|
setShowValidation(false);
|
|
}
|
|
}, [password, validPassword]);
|
|
|
|
useEffect(() => {
|
|
if (addData?.success || changeData?.success) {
|
|
setShowPasswordAlert(true);
|
|
if (!changePasswordUI) {
|
|
setChangePasswordUI(true);
|
|
}
|
|
setOpen(false);
|
|
setPassword('');
|
|
setConfirmPassword('');
|
|
setCurrentPassword('');
|
|
}
|
|
}, [addData, changeData, changePasswordUI]);
|
|
|
|
const handleOpen = () => setOpen(true);
|
|
const handleClose = () => setOpen(false);
|
|
|
|
const handlePasswordSubmit = () => {
|
|
if (changePasswordUI) {
|
|
executeChangePassword({
|
|
oldPassword: currentPassword,
|
|
newPassword: password,
|
|
});
|
|
} else {
|
|
executeAddPassword({ password });
|
|
}
|
|
};
|
|
|
|
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
|
|
title={t('securityForm.password')}
|
|
subtitle={t('securityForm.determinePassword')}
|
|
action={
|
|
<Button variant="contained" onClick={handleOpen}>
|
|
{changePasswordUI
|
|
? t('securityForm.changePassword')
|
|
: t('securityForm.addPassword')}
|
|
</Button>
|
|
}
|
|
>
|
|
{isFetching ? (
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
p: 4,
|
|
minHeight: '120px',
|
|
}}
|
|
>
|
|
<CircularProgress />
|
|
</Box>
|
|
) : fetchError ? (
|
|
<Box sx={{ textAlign: 'center', p: 4 }}>
|
|
<Typography color="error.main">
|
|
{getErrorMessage(fetchError)}
|
|
</Typography>
|
|
</Box>
|
|
) : (
|
|
<Box sx={{ px: 4, py: 2 }}>
|
|
{changePasswordUI ? (
|
|
<Box>
|
|
<Typography variant="h6">
|
|
{t('securityForm.activePassword')}
|
|
</Typography>
|
|
<Typography variant="caption" color="text.secondary">
|
|
{t('securityForm.lastChange')}
|
|
</Typography>
|
|
</Box>
|
|
) : (
|
|
<Typography
|
|
variant="body1"
|
|
color="text.secondary"
|
|
sx={{ textAlign: 'center', py: 4 }}
|
|
>
|
|
{t('securityForm.notDeterminedPassword')}
|
|
</Typography>
|
|
)}
|
|
|
|
<PasswordDialog
|
|
open={open}
|
|
fullScreen={fullScreen}
|
|
handleClose={handleClose}
|
|
password={password}
|
|
setPassword={setPassword}
|
|
confirmPassword={confirmPassword}
|
|
setConfirmPassword={setConfirmPassword}
|
|
currentPassword={currentPassword}
|
|
setCurrentPassword={setCurrentPassword}
|
|
showValidation={showValidation}
|
|
validPassword={validPassword}
|
|
matchPassword={matchPassword}
|
|
loading={isAdding || isChanging}
|
|
handleSubmit={handlePasswordSubmit}
|
|
changePassword={changePasswordUI}
|
|
apiError={getErrorMessage(apiError)}
|
|
t={t}
|
|
/>
|
|
</Box>
|
|
)}
|
|
</CardContainer>
|
|
</PageWrapper>
|
|
);
|
|
}
|