@craftercms/studio-ui
Version:
Services, components, models & utils to build CrafterCMS authoring extensions.
268 lines (266 loc) • 9.08 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 React, { useEffect, useRef, useState } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { encrypt as encryptService } from '../../services/security';
import Snackbar from '@mui/material/Snackbar';
import SnackbarContent from '@mui/material/SnackbarContent';
import IconButton from '@mui/material/IconButton';
import CloseIcon from '@mui/icons-material/Close';
import { makeStyles } from 'tss-react/mui';
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import ErrorIcon from '@mui/icons-material/Error';
import { green, red } from '@mui/material/colors';
import { setRequestForgeryToken } from '../../utils/auth';
import TextField from '@mui/material/TextField';
import Button from '@mui/material/Button';
import GlobalAppToolbar from '../GlobalAppToolbar';
import Box from '@mui/material/Box';
import { useSpreadState } from '../../hooks/useSpreadState';
import Paper from '@mui/material/Paper';
import useUpdateRefs from '../../hooks/useUpdateRefs';
import { copyToClipboard } from '../../utils/system';
const messages = defineMessages({
inputLabel: {
id: 'encryptTool.inputLabel',
defaultMessage: 'Raw Text'
},
buttonText: {
id: 'encryptTool.buttonText',
defaultMessage: 'Encrypt Text'
},
successMessage: {
id: 'encryptTool.successMessage',
defaultMessage: 'Encrypted text copied to clipboard.'
},
errorMessage: {
id: 'encryptTool.errorMessage',
defaultMessage: 'Text encryption failed. Please try again momentarily.'
},
clearResultButtonText: {
id: 'encryptTool.clearResultButtonText',
defaultMessage: 'Clear'
}
});
const useStyles = makeStyles()((theme) => ({
form: {
padding: '20px'
},
title: {
color: '#555555'
},
success: {
backgroundColor: green[600]
},
error: {
backgroundColor: red[600]
},
icon: {
fontSize: 20
},
iconVariant: {
opacity: 0.9,
marginRight: theme.spacing(1)
},
message: {
display: 'flex',
alignItems: 'center'
}
}));
const notificationInitialState = {
open: false,
variant: 'success'
};
export const EncryptTool = (props) => {
const { site, embedded = false, showAppsButton, onSubmittingAndOrPendingChange } = props;
const { classes } = useStyles();
const inputRef = useRef();
const [text, setText] = useState('');
const [result, setResult] = useState(null);
const [fetching, setFetching] = useState(null);
const [notificationSettings, setNotificationSettings] = useSpreadState(notificationInitialState);
const { formatMessage } = useIntl();
const fnRefs = useUpdateRefs({ onSubmittingAndOrPendingChange });
const hasText = Boolean(text);
const focus = () => {
const toolRawTextInput = document.querySelector('#encryptionToolRawText');
toolRawTextInput.focus();
};
const encrypt = (e) => {
e.preventDefault();
if (text) {
setRequestForgeryToken();
setFetching(true);
setResult(null);
encryptService(text, site).subscribe({
next(encryptedText) {
const resultingText = `\${enc:${encryptedText}}`;
setFetching(false);
setText('');
setResult(resultingText);
copyToClipboard(resultingText)
.then(() => {
setNotificationSettings({ open: true, variant: 'success' });
})
.catch(() => {
inputRef.current.focus();
inputRef.current.select();
});
},
error() {
setNotificationSettings({ open: true, variant: 'error' });
}
});
} else {
focus();
}
};
const clear = () => {
setText('');
setResult(null);
focus();
};
useEffect(() => {
fnRefs.current.onSubmittingAndOrPendingChange?.({ hasPendingChanges: hasText });
}, [hasText, fnRefs]);
return React.createElement(
Paper,
{ elevation: 0 },
!embedded &&
React.createElement(GlobalAppToolbar, {
title: React.createElement(FormattedMessage, {
id: 'encryptTool.pageTitle',
defaultMessage: 'Encryption Tool'
}),
showAppsButton: showAppsButton
}),
React.createElement(
Box,
{ p: '20px' },
React.createElement(
'form',
{ onSubmit: encrypt },
React.createElement(TextField, {
sx: { mb: 2 },
label: formatMessage(messages.inputLabel),
value: text,
onChange: (e) => setText(e.target.value),
onKeyUp: (e) => {
if (e.key === 'Enter' && e.ctrlKey) {
encrypt(e);
}
},
fullWidth: true,
id: 'encryptionToolRawText',
name: 'encryptionToolRawText',
autoFocus: true,
disabled: fetching,
multiline: true
}),
result &&
React.createElement(TextField, {
fullWidth: true,
type: 'text',
color: 'success',
sx: { mb: 2 },
ref: inputRef,
label: React.createElement(FormattedMessage, { id: 'words.result', defaultMessage: 'Result' }),
InputProps: { readOnly: true },
value: result,
onClick: (e) => {
const input = e.target;
const value = input.value;
input.select();
copyToClipboard(value).then(() => {
setNotificationSettings({ open: true, variant: 'success' });
});
}
}),
React.createElement(
'div',
null,
React.createElement(
Button,
{ type: 'button', onClick: clear, disabled: fetching, variant: 'outlined', sx: { mr: 1 } },
formatMessage(messages.clearResultButtonText)
),
React.createElement(
Button,
{ type: 'submit', onClick: encrypt, disabled: fetching, color: 'primary', variant: 'contained' },
formatMessage(messages.buttonText)
)
),
React.createElement(
Snackbar,
{
anchorOrigin: {
vertical: 'top',
horizontal: 'right'
},
open: notificationSettings.open,
autoHideDuration: 5000,
onClose: () => {
setNotificationSettings({ open: false });
}
},
React.createElement(SnackbarContent, {
className: `${notificationSettings.variant === 'success' ? classes.success : classes.error} ${classes.iconVariant}`,
'aria-describedby': 'encryptToolSnackbar',
message: React.createElement(
'span',
{ id: 'encryptToolSnackbar', className: classes.message },
notificationSettings.variant === 'success'
? React.createElement(CheckCircleIcon, { className: `${classes.icon} ${classes.iconVariant}` })
: React.createElement(ErrorIcon, { className: `${classes.icon} ${classes.iconVariant}` }),
formatMessage(
notificationSettings.variant === 'success' ? messages.successMessage : messages.errorMessage
)
),
action: [
React.createElement(
IconButton,
{
key: 'close',
'aria-label': 'close',
color: 'inherit',
onClick: () => setNotificationSettings({ open: false }),
size: 'large'
},
React.createElement(CloseIcon, { className: classes.icon })
)
]
})
)
)
)
);
};
export default EncryptTool;