@craftercms/studio-ui
Version:
Services, components, models & utils to build CrafterCMS authoring extensions.
387 lines (385 loc) • 14.7 kB
JavaScript
/*
* 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;