UNPKG

@craftercms/studio-ui

Version:

Services, components, models & utils to build CrafterCMS authoring extensions.

387 lines (385 loc) 14.7 kB
/* * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ import Box from '@mui/material/Box'; import React, { useEffect, useState } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import GlobalAppToolbar from '../GlobalAppToolbar'; import { Typography } from '@mui/material'; import Paper from '@mui/material/Paper'; import useStyles from './styles'; import Avatar from '@mui/material/Avatar'; import Container from '@mui/material/Container'; import { dispatchLanguageChange, getCurrentLocale, setStoredLanguage } from '../../utils/i18n'; import { fetchProductLanguages } from '../../services/configuration'; import TextField from '@mui/material/TextField'; import MenuItem from '@mui/material/MenuItem'; import FormHelperText from '@mui/material/FormHelperText'; import Skeleton from '@mui/material/Skeleton'; import PasswordTextField from '../PasswordTextField/PasswordTextField'; import PrimaryButton from '../PrimaryButton'; import { setMyPassword } from '../../services/users'; import { useDispatch } from 'react-redux'; import { showErrorDialog } from '../../state/reducers/dialogs/error'; import { showSystemNotification } from '../../state/actions/system'; import { useActiveUser } from '../../hooks/useActiveUser'; import { PasswordStrengthDisplayPopper } from '../PasswordStrengthDisplayPopper'; import Select from '@mui/material/Select'; import InputLabel from '@mui/material/InputLabel'; import FormControl from '@mui/material/FormControl'; import useSiteLookup from '../../hooks/useSiteLookup'; import Button from '@mui/material/Button'; import TableContainer from '@mui/material/TableContainer'; import Table from '@mui/material/Table'; import TableBody from '@mui/material/TableBody'; import TableRow from '@mui/material/TableRow'; import TableCell from '@mui/material/TableCell'; import { preferencesGroups } from './utils'; const translations = defineMessages({ languageUpdated: { id: 'accountManagement.languageUpdated', defaultMessage: 'Language preference changed' }, passwordChanged: { id: 'accountManagement.passwordChanged', defaultMessage: 'Password changed successfully' } }); export function AccountManagement(props) { const { passwordRequirementsMinComplexity = 4 } = props; const { classes, cx: clsx } = useStyles(); const user = useActiveUser(); const [language, setLanguage] = useState(() => getCurrentLocale()); const [languages, setLanguages] = useState(); const [currentPassword, setCurrentPassword] = useState(''); const [newPassword, setNewPassword] = useState(''); const [verifiedPassword, setVerifiedPassword] = useState(''); const [validPassword, setValidPassword] = useState(false); const dispatch = useDispatch(); const { formatMessage } = useIntl(); const [anchorEl, setAnchorEl] = useState(null); const sitesLookup = useSiteLookup(); const sitesIds = Object.keys(sitesLookup); const [selectedSite, setSelectedSite] = useState('all'); // Retrieve Platform Languages. useEffect(() => { fetchProductLanguages().subscribe(setLanguages); }, []); const onLanguageChanged = (language) => { setLanguage(language); setStoredLanguage(language, user.username); dispatchLanguageChange(language); dispatch( showSystemNotification({ message: formatMessage(translations.languageUpdated) }) ); }; const onSave = () => { setMyPassword(user.username, currentPassword, newPassword).subscribe({ next() { dispatch( showSystemNotification({ message: formatMessage(translations.passwordChanged) }) ); setCurrentPassword(''); setVerifiedPassword(''); setNewPassword(''); }, error({ response: { response } }) { dispatch(showErrorDialog({ error: response })); } }); }; const onClearPreference = (group, showNotification = true) => { if (selectedSite === 'all') { sitesIds.forEach((siteId) => { group.onClear({ siteId, siteUuid: sitesLookup[siteId].uuid, username: user.username }); }); } else { group.onClear({ siteId: selectedSite, siteUuid: sitesLookup[selectedSite].uuid, username: user.username }); } if (showNotification) { dispatch(showSystemNotification({ message: formatMessage({ defaultMessage: 'Preferences cleared' }) })); } }; const onClearEverything = () => { preferencesGroups.forEach((group) => onClearPreference(group, false)); dispatch(showSystemNotification({ message: formatMessage({ defaultMessage: 'Preferences cleared' }) })); }; return React.createElement( Paper, { elevation: 0, sx: { mb: 2 } }, React.createElement(GlobalAppToolbar, { title: React.createElement(FormattedMessage, { id: 'words.account', defaultMessage: 'Account' }) }), React.createElement( Container, { maxWidth: 'md', sx: { mb: 2, pb: 2 } }, React.createElement( Paper, { className: clsx(classes.paper, 'mt20') }, React.createElement( Box, { display: 'flex', alignItems: 'center' }, React.createElement( Avatar, { className: classes.avatar }, user.firstName.charAt(0), user.lastName?.charAt(0) ?? '' ), React.createElement( 'section', null, React.createElement(Typography, null, user.firstName, ' ', user.lastName), React.createElement(Typography, null, user.email) ) ) ), React.createElement( Paper, { className: classes.paper }, React.createElement( Typography, { variant: 'h5' }, React.createElement(FormattedMessage, { id: 'accountManagement.changeLanguage', defaultMessage: 'Change Language' }) ), React.createElement( Box, { marginTop: '16px' }, languages ? React.createElement( TextField, { fullWidth: true, select: true, label: React.createElement(FormattedMessage, { id: 'words.language', defaultMessage: 'Language' }), value: language, onChange: (event) => onLanguageChanged(event.target.value) }, languages?.map((option) => React.createElement(MenuItem, { key: option.id, value: option.id }, option.label) ) ) : React.createElement(Skeleton, { width: '100%', height: '80px' }) ) ), React.createElement( Paper, { className: classes.paper }, React.createElement( Typography, { variant: 'h5' }, React.createElement(FormattedMessage, { id: 'accountManagement.changePassword', defaultMessage: 'Change Password' }) ), React.createElement( FormHelperText, null, React.createElement(FormattedMessage, { id: 'accountManagement.changeHelperText', defaultMessage: "Once your password has been successfully updated, you'll be required to login again." }) ), React.createElement( Box, { display: 'flex', flexDirection: 'column' }, React.createElement(PasswordTextField, { margin: 'normal', label: React.createElement(FormattedMessage, { id: 'accountManagement.currentPassword', defaultMessage: 'Current Password' }), required: true, fullWidth: true, value: currentPassword, onChange: (e) => { setCurrentPassword(e.target.value); } }), React.createElement(PasswordTextField, { margin: 'normal', label: React.createElement(FormattedMessage, { id: 'accountManagement.newPassword', defaultMessage: 'New Password' }), required: true, fullWidth: true, value: newPassword, onChange: (e) => { setNewPassword(e.target.value); }, error: Boolean(newPassword) && !validPassword, helperText: newPassword && !validPassword && React.createElement(FormattedMessage, { id: 'accountManagement.passwordInvalid', defaultMessage: 'Password is invalid.' }), onFocus: (e) => setAnchorEl(e.target), onBlur: () => setAnchorEl(null), inputProps: { autoComplete: 'new-password' } }), React.createElement(PasswordTextField, { margin: 'normal', label: React.createElement(FormattedMessage, { id: 'accountManagement.confirmPassword', defaultMessage: 'Confirm Password' }), required: true, fullWidth: true, value: verifiedPassword, onChange: (e) => { setVerifiedPassword(e.target.value); }, error: newPassword !== verifiedPassword, helperText: newPassword !== verifiedPassword && React.createElement(FormattedMessage, { id: 'accountManagement.passwordMatch', defaultMessage: 'Must match the previous password.' }) }), React.createElement( PrimaryButton, { disabled: !validPassword || newPassword !== verifiedPassword || currentPassword === '', className: classes.save, onClick: () => onSave() }, React.createElement(FormattedMessage, { id: 'words.save', defaultMessage: 'Save' }) ) ) ), React.createElement( Paper, { className: classes.paper }, React.createElement( Typography, { variant: 'h5', mb: 3 }, React.createElement(FormattedMessage, { defaultMessage: 'Stored Preferences' }) ), React.createElement( Typography, { mb: 3, variant: 'body2' }, React.createElement(FormattedMessage, { defaultMessage: 'Clear your user preferences and reset to defaults per project or for all projects.' }) ), React.createElement( Box, { display: 'flex', justifyContent: 'space-between', mb: 3 }, React.createElement( FormControl, { sx: { minWidth: 200 } }, React.createElement(InputLabel, null, React.createElement(FormattedMessage, { defaultMessage: 'Project' })), React.createElement( Select, { value: selectedSite, label: React.createElement(FormattedMessage, { defaultMessage: 'Project' }), onChange: (event) => { setSelectedSite(event.target.value); } }, React.createElement( MenuItem, { value: 'all' }, React.createElement(FormattedMessage, { defaultMessage: 'All Projects' }) ), sitesIds.map((siteId) => React.createElement(MenuItem, { key: siteId, value: siteId }, sitesLookup[siteId].name) ) ) ), React.createElement( Button, { variant: 'outlined', color: 'warning', size: 'large', onClick: onClearEverything }, React.createElement(FormattedMessage, { defaultMessage: 'Clear everything' }), ' ', selectedSite === 'all' && React.createElement(FormattedMessage, { defaultMessage: '(All Projects)' }) ) ), React.createElement( TableContainer, { component: Paper }, React.createElement( Table, { size: 'small' }, React.createElement( TableBody, null, preferencesGroups.map((group, index) => React.createElement( TableRow, { key: index, sx: { '&:last-child td, &:last-child th': { border: 0 } } }, React.createElement(TableCell, { component: 'th', scope: 'row' }, group.label), React.createElement( TableCell, { align: 'right' }, React.createElement( Button, { variant: 'text', onClick: () => onClearPreference(group) }, React.createElement(FormattedMessage, { defaultMessage: 'Clear' }) ) ) ) ) ) ) ) ) ), React.createElement(PasswordStrengthDisplayPopper, { open: Boolean(anchorEl), anchorEl: anchorEl, placement: 'top', value: newPassword, passwordRequirementsMinComplexity: passwordRequirementsMinComplexity, onValidStateChanged: setValidPassword }) ); } export default AccountManagement;