import React, { useEffect, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { Accordion, AccordionSummary, AccordionDetails, Typography, FormControl, InputLabel, Select, MenuItem, FormControlLabel, Checkbox, FormGroup, TextField, Button, InputAdornment, IconButton, OutlinedInput, } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import DeleteForeverIcon from '@mui/icons-material/DeleteForever'; import CachedIcon from '@mui/icons-material/Cached'; import CloseIcon from '@mui/icons-material/Close'; import { useDispatch, useSelector } from 'react-redux'; import dayjs from 'dayjs'; import EditItemView from './components/EditItemView'; import EditAttributesAccordion from './components/EditAttributesAccordion'; import { useTranslation } from '../common/components/LocalizationProvider'; import useUserAttributes from '../common/attributes/useUserAttributes'; import { sessionActions } from '../store'; import SelectField from '../common/components/SelectField'; import SettingsMenu from './components/SettingsMenu'; import useCommonUserAttributes from '../common/attributes/useCommonUserAttributes'; import { useAdministrator, useRestriction, useManager } from '../common/util/permissions'; import useQuery from '../common/util/useQuery'; import { useCatch } from '../reactHelper'; import useMapStyles from '../map/core/useMapStyles'; import { map } from '../map/core/MapView'; const useStyles = makeStyles((theme) => ({ details: { display: 'flex', flexDirection: 'column', gap: theme.spacing(2), paddingBottom: theme.spacing(3), }, })); const UserPage = () => { const classes = useStyles(); const navigate = useNavigate(); const dispatch = useDispatch(); const t = useTranslation(); const admin = useAdministrator(); const manager = useManager(); const fixedEmail = useRestriction('fixedEmail'); const currentUser = useSelector((state) => state.session.user); const registrationEnabled = useSelector((state) => state.session.server.registration); const openIdForced = useSelector((state) => state.session.server.openIdForce); const totpEnable = useSelector((state) => state.session.server.attributes.totpEnable); const totpForce = useSelector((state) => state.session.server.attributes.totpForce); const mapStyles = useMapStyles(); const commonUserAttributes = useCommonUserAttributes(t); const userAttributes = useUserAttributes(t); const { id } = useParams(); const [item, setItem] = useState(id === currentUser.id.toString() ? currentUser : null); const [deleteEmail, setDeleteEmail] = useState(); const [deleteFailed, setDeleteFailed] = useState(false); const handleDelete = useCatch(async () => { if (deleteEmail === currentUser.email) { setDeleteFailed(false); const response = await fetch(`/api/users/${currentUser.id}`, { method: 'DELETE' }); if (response.ok) { navigate('/login'); dispatch(sessionActions.updateUser(null)); } else { throw Error(await response.text()); } } else { setDeleteFailed(true); } }); const handleGenerateTotp = useCatch(async () => { const response = await fetch('/api/users/totp', { method: 'POST' }); if (response.ok) { setItem({ ...item, totpKey: await response.text() }) } else { throw Error(await response.text()); } }); const query = useQuery(); const [queryHandled, setQueryHandled] = useState(false); const attribute = query.get('attribute'); useEffect(() => { if (!queryHandled && item && attribute) { if (!item.attributes.hasOwnProperty('attribute')) { const updatedAttributes = { ...item.attributes }; updatedAttributes[attribute] = ''; setItem({ ...item, attributes: updatedAttributes }); } setQueryHandled(true); } }, [item, queryHandled, setQueryHandled, attribute]); const onItemSaved = (result) => { if (result.id === currentUser.id) { dispatch(sessionActions.updateUser(result)); } }; const validate = () => item && item.name && item.email && (item.id || item.password) && (admin || !totpForce || item.totpKey); return ( } breadcrumbs={['settingsTitle', 'settingsUser']} > {item && ( <> }> {t('sharedRequired')} setItem({ ...item, name: e.target.value })} label={t('sharedName')} /> setItem({ ...item, email: e.target.value })} label={t('userEmail')} disabled={fixedEmail} /> {!openIdForced && ( setItem({ ...item, password: e.target.value })} label={t('userPassword')} /> )} {totpEnable && ( {t('loginTotpKey')} setItem({ ...item, totpKey: null })}> )} /> )} }> {t('sharedPreferences')} setItem({ ...item, phone: e.target.value })} label={t('sharedPhone')} /> {t('mapDefault')} {t('settingsCoordinateFormat')} {t('settingsSpeedUnit')} {t('settingsDistanceUnit')} {t('settingsAltitudeUnit')} {t('settingsVolumeUnit')} setItem({ ...item, attributes: { ...item.attributes, timezone: e.target.value } })} endpoint="/api/server/timezones" keyGetter={(it) => it} titleGetter={(it) => it} label={t('sharedTimezone')} /> setItem({ ...item, poiLayer: e.target.value })} label={t('mapPoiLayer')} /> setItem({ ...item, twelveHourFormat: e.target.checked })} />} label={t('settingsTwelveHourFormat')} /> }> {t('sharedLocation')} setItem({ ...item, latitude: Number(e.target.value) })} label={t('positionLatitude')} /> setItem({ ...item, longitude: Number(e.target.value) })} label={t('positionLongitude')} /> setItem({ ...item, zoom: Number(e.target.value) })} label={t('serverZoom')} /> }> {t('sharedPermissions')} setItem({ ...item, expirationTime: dayjs(e.target.value, 'YYYY-MM-DD').locale('en').format() })} disabled={!manager} /> setItem({ ...item, deviceLimit: Number(e.target.value) })} label={t('userDeviceLimit')} disabled={!admin} /> setItem({ ...item, userLimit: Number(e.target.value) })} label={t('userUserLimit')} disabled={!admin} /> setItem({ ...item, disabled: e.target.checked })} />} label={t('sharedDisabled')} disabled={!manager} /> setItem({ ...item, administrator: e.target.checked })} />} label={t('userAdmin')} disabled={!admin} /> setItem({ ...item, readonly: e.target.checked })} />} label={t('serverReadonly')} disabled={!manager} /> setItem({ ...item, deviceReadonly: e.target.checked })} />} label={t('userDeviceReadonly')} disabled={!manager} /> setItem({ ...item, limitCommands: e.target.checked })} />} label={t('userLimitCommands')} disabled={!manager} /> setItem({ ...item, disableReports: e.target.checked })} />} label={t('userDisableReports')} disabled={!manager} /> setItem({ ...item, fixedEmail: e.target.checked })} />} label={t('userFixedEmail')} disabled={!manager} /> setItem({ ...item, attributes })} definitions={{ ...commonUserAttributes, ...userAttributes }} focusAttribute={attribute} /> {registrationEnabled && item.id === currentUser.id && !manager && ( }> {t('userDeleteAccount')} setDeleteEmail(e.target.value)} label={t('userEmail')} error={deleteFailed} /> )} )} ); }; export default UserPage;