mirror of
https://github.com/strapi/strapi.git
synced 2025-12-26 06:35:47 +00:00
Migration/ Email templates (#11000)
* temp commit * email templates form + test
This commit is contained in:
parent
bd5a98903f
commit
5e4bc05751
@ -59,23 +59,22 @@ export default {
|
||||
},
|
||||
permissions: pluginPermissions.readProviders,
|
||||
},
|
||||
{
|
||||
intlLabel: {
|
||||
id: getTrad('HeaderNav.link.emailTemplates'),
|
||||
defaultMessage: 'Email templates',
|
||||
},
|
||||
id: 'email-templates',
|
||||
to: `/settings/${pluginId}/email-templates`,
|
||||
Component: async () => {
|
||||
const component = await import(
|
||||
/* webpackChunkName: "users-email-settings-page" */ './pages/EmailTemplates'
|
||||
);
|
||||
|
||||
// {
|
||||
// intlLabel: {
|
||||
// id: getTrad('HeaderNav.link.emailTemplates'),
|
||||
// defaultMessage: 'Email templates',
|
||||
// },
|
||||
// id: 'email-templates',
|
||||
// to: `/settings/${pluginId}/email-templates`,
|
||||
// Component: async () => {
|
||||
// const component = await import(
|
||||
// /* webpackChunkName: "users-email-settings-page" */ './pages/EmailTemplates'
|
||||
// );
|
||||
|
||||
// return component;
|
||||
// },
|
||||
// permissions: pluginPermissions.readEmailTemplates,
|
||||
// },
|
||||
return component;
|
||||
},
|
||||
permissions: pluginPermissions.readEmailTemplates,
|
||||
},
|
||||
{
|
||||
intlLabel: {
|
||||
id: getTrad('HeaderNav.link.advancedSettings'),
|
||||
|
||||
@ -1,105 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* The input is made so we can handle a custom descritption
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Error, Label, InputText } from '@buffetjs/core';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { Description, ErrorMessage } from '@buffetjs/styles';
|
||||
import { getTrad } from '../../utils';
|
||||
import Wrapper from './Wrapper';
|
||||
|
||||
const CustomTextInput = ({
|
||||
description,
|
||||
error: inputError,
|
||||
label,
|
||||
name,
|
||||
onChange,
|
||||
validations,
|
||||
value,
|
||||
...rest
|
||||
}) => {
|
||||
const inputId = name;
|
||||
const descriptionId = `description-${inputId}`;
|
||||
const errorId = `error-${inputId}`;
|
||||
|
||||
const link = (
|
||||
<a
|
||||
href="https://strapi.io/documentation/developer-docs/latest/development/plugins/users-permissions.html#templating-emails"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<FormattedMessage id="users-permissions.PopUpForm.Email.link.documentation" />
|
||||
</a>
|
||||
);
|
||||
|
||||
const descriptionCompo = (
|
||||
<Description id={descriptionId}>
|
||||
<FormattedMessage
|
||||
id={getTrad('PopUpForm.Email.email_templates.inputDescription')}
|
||||
values={{ link }}
|
||||
/>
|
||||
</Description>
|
||||
);
|
||||
|
||||
return (
|
||||
<Error inputError={inputError} name={name} type="text" validations={validations}>
|
||||
{({ canCheck, onBlur, error, dispatch }) => (
|
||||
<Wrapper error={error}>
|
||||
<Label htmlFor={inputId}>{label}</Label>
|
||||
<InputText
|
||||
{...rest}
|
||||
name={name}
|
||||
id={inputId}
|
||||
aria-invalid={error ? 'true' : 'false'}
|
||||
onBlur={onBlur}
|
||||
onChange={e => {
|
||||
if (!canCheck) {
|
||||
dispatch({
|
||||
type: 'SET_CHECK',
|
||||
});
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: 'SET_ERROR',
|
||||
error: null,
|
||||
});
|
||||
onChange(e);
|
||||
}}
|
||||
value={value}
|
||||
/>
|
||||
{!error ? descriptionCompo : <ErrorMessage id={errorId}>{error}</ErrorMessage>}
|
||||
</Wrapper>
|
||||
)}
|
||||
</Error>
|
||||
);
|
||||
};
|
||||
|
||||
CustomTextInput.defaultProps = {
|
||||
description: null,
|
||||
id: null,
|
||||
error: null,
|
||||
label: null,
|
||||
onBlur: null,
|
||||
onChange: () => {},
|
||||
validations: {},
|
||||
value: '',
|
||||
};
|
||||
|
||||
CustomTextInput.propTypes = {
|
||||
description: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
error: PropTypes.string,
|
||||
id: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
name: PropTypes.string.isRequired,
|
||||
onBlur: PropTypes.func,
|
||||
onChange: () => {},
|
||||
|
||||
validations: PropTypes.object,
|
||||
value: PropTypes.any,
|
||||
};
|
||||
|
||||
export default CustomTextInput;
|
||||
@ -1,36 +0,0 @@
|
||||
import styled, { css } from 'styled-components';
|
||||
import { colors, sizes } from '@buffetjs/styles';
|
||||
|
||||
/* eslint-disable indent */
|
||||
const Wrapper = styled.div`
|
||||
position: relative;
|
||||
padding-bottom: ${sizes.margin * 2.7}px;
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
> p {
|
||||
width: 100%;
|
||||
padding-top: 10px;
|
||||
font-size: 13px;
|
||||
line-height: normal;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-bottom: -8px;
|
||||
}
|
||||
input[type='checkbox'] {
|
||||
margin-bottom: 13px;
|
||||
}
|
||||
${({ error }) =>
|
||||
!!error &&
|
||||
css`
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
border-color: ${colors.darkOrange};
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
@ -0,0 +1,168 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { Form, GenericInput } from '@strapi/helper-plugin';
|
||||
import { Formik } from 'formik';
|
||||
import { ModalLayout, ModalHeader, ModalFooter, ModalBody } from '@strapi/parts/ModalLayout';
|
||||
import { Grid, GridItem } from '@strapi/parts/Grid';
|
||||
import { Button } from '@strapi/parts/Button';
|
||||
import { Breadcrumbs, Crumb } from '@strapi/parts/Breadcrumbs';
|
||||
import { Textarea } from '@strapi/parts/Textarea';
|
||||
import { getTrad } from '../../../utils';
|
||||
import schema from '../utils/schema';
|
||||
|
||||
const EmailForm = ({ template, onToggle, onSubmit }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<ModalLayout
|
||||
onClose={onToggle}
|
||||
labelledBy={`${formatMessage({
|
||||
id: getTrad('PopUpForm.header.edit.email-templates'),
|
||||
defaultMessage: 'Edit email template',
|
||||
})}, ${formatMessage({ id: getTrad(template.display), defaultMessage: template.display })}`}
|
||||
>
|
||||
<ModalHeader>
|
||||
<Breadcrumbs
|
||||
label={`${formatMessage({
|
||||
id: getTrad('PopUpForm.header.edit.email-templates'),
|
||||
defaultMessage: 'Edit email template',
|
||||
})}, ${formatMessage({
|
||||
id: getTrad(template.display),
|
||||
defaultMessage: template.display,
|
||||
})}`}
|
||||
>
|
||||
<Crumb>
|
||||
{formatMessage({
|
||||
id: getTrad('PopUpForm.header.edit.email-templates'),
|
||||
defaultMessage: 'Edit email template',
|
||||
})}
|
||||
</Crumb>
|
||||
<Crumb>
|
||||
{formatMessage({ id: getTrad(template.display), defaultMessage: template.display })}
|
||||
</Crumb>
|
||||
</Breadcrumbs>
|
||||
</ModalHeader>
|
||||
<Formik
|
||||
onSubmit={onSubmit}
|
||||
initialValues={template}
|
||||
validateOnChange={false}
|
||||
validationSchema={schema}
|
||||
enableReinitialize
|
||||
>
|
||||
{({ errors, values, handleChange, isSubmitting }) => {
|
||||
return (
|
||||
<Form>
|
||||
<ModalBody>
|
||||
<Grid gap={5}>
|
||||
<GridItem col={6} s={12}>
|
||||
<GenericInput
|
||||
intlLabel={{
|
||||
id: getTrad('PopUpForm.Email.options.from.name.label'),
|
||||
defaultMessage: 'Shipper name',
|
||||
}}
|
||||
name="options.from.name"
|
||||
onChange={handleChange}
|
||||
value={values.options.from.name}
|
||||
error={errors?.options?.from?.name}
|
||||
type="text"
|
||||
/>
|
||||
</GridItem>
|
||||
<GridItem col={6} s={12}>
|
||||
<GenericInput
|
||||
intlLabel={{
|
||||
id: getTrad('PopUpForm.Email.options.from.email.label'),
|
||||
defaultMessage: 'Shipper email',
|
||||
}}
|
||||
name="options.from.email"
|
||||
onChange={handleChange}
|
||||
value={values.options.from.email}
|
||||
error={errors?.options?.from?.email}
|
||||
type="text"
|
||||
/>
|
||||
</GridItem>
|
||||
<GridItem col={6} s={12}>
|
||||
<GenericInput
|
||||
intlLabel={{
|
||||
id: getTrad('PopUpForm.Email.options.response_email.label'),
|
||||
defaultMessage: 'Response email',
|
||||
}}
|
||||
name="options.response_email"
|
||||
onChange={handleChange}
|
||||
value={values.options.response_email}
|
||||
error={errors?.options?.response_email}
|
||||
type="text"
|
||||
/>
|
||||
</GridItem>
|
||||
<GridItem col={6} s={12}>
|
||||
<GenericInput
|
||||
intlLabel={{
|
||||
id: getTrad('PopUpForm.Email.options.object.label'),
|
||||
defaultMessage: 'Subject',
|
||||
}}
|
||||
name="options.object"
|
||||
onChange={handleChange}
|
||||
value={values.options.object}
|
||||
error={errors?.options?.object}
|
||||
type="text"
|
||||
/>
|
||||
</GridItem>
|
||||
<GridItem col={12} s={12}>
|
||||
<Textarea
|
||||
label={formatMessage({
|
||||
id: getTrad('PopUpForm.Email.options.message.label'),
|
||||
defaultMessage: 'Message',
|
||||
})}
|
||||
name="options.message"
|
||||
onChange={handleChange}
|
||||
value={values.options.message}
|
||||
error={
|
||||
errors?.options?.message &&
|
||||
formatMessage({
|
||||
id: errors.options.message,
|
||||
defaultMessage: errors.options.message,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
</ModalBody>
|
||||
<ModalFooter
|
||||
startActions={
|
||||
<Button onClick={onToggle} variant="tertiary">
|
||||
Cancel
|
||||
</Button>
|
||||
}
|
||||
endActions={
|
||||
<Button loading={isSubmitting} type="submit">
|
||||
Finish
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</Form>
|
||||
);
|
||||
}}
|
||||
</Formik>
|
||||
</ModalLayout>
|
||||
);
|
||||
};
|
||||
|
||||
EmailForm.propTypes = {
|
||||
template: PropTypes.shape({
|
||||
display: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
options: PropTypes.shape({
|
||||
from: PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
email: PropTypes.string,
|
||||
}),
|
||||
message: PropTypes.string,
|
||||
object: PropTypes.string,
|
||||
response_email: PropTypes.string,
|
||||
}),
|
||||
}).isRequired,
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
onToggle: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default EmailForm;
|
||||
@ -0,0 +1,115 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { Table, Thead, Tbody, Tr, Td, Th } from '@strapi/parts/Table';
|
||||
import { VisuallyHidden } from '@strapi/parts/VisuallyHidden';
|
||||
import { Text, TableLabel } from '@strapi/parts/Text';
|
||||
import { IconButton } from '@strapi/parts/IconButton';
|
||||
import EditIcon from '@strapi/icons/EditIcon';
|
||||
import Reload from '@strapi/icons/Reload';
|
||||
import CheckIcon from '@strapi/icons/CheckIcon';
|
||||
import { getTrad } from '../../../utils';
|
||||
|
||||
const EmailTable = ({ canUpdate, onEditClick }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<Table colCount={3} rowCount={3}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th width="1%">
|
||||
<VisuallyHidden>
|
||||
{formatMessage({
|
||||
id: getTrad('Email.template.table.icon.label'),
|
||||
defaultMessage: 'icon',
|
||||
})}
|
||||
</VisuallyHidden>
|
||||
</Th>
|
||||
<Th>
|
||||
<TableLabel>
|
||||
{formatMessage({
|
||||
id: getTrad('Email.template.table.name.label'),
|
||||
defaultMessage: 'name',
|
||||
})}
|
||||
</TableLabel>
|
||||
</Th>
|
||||
<Th width="1%">
|
||||
<VisuallyHidden>
|
||||
{formatMessage({
|
||||
id: getTrad('Email.template.table.action.label'),
|
||||
defaultMessage: 'action',
|
||||
})}
|
||||
</VisuallyHidden>
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
<Tr>
|
||||
<Td>
|
||||
<Reload
|
||||
aria-label={formatMessage({
|
||||
id: getTrad('Email.template.reset_password'),
|
||||
defaultMessage: 'Reset password',
|
||||
})}
|
||||
/>
|
||||
</Td>
|
||||
<Td>
|
||||
<Text>
|
||||
{formatMessage({
|
||||
id: getTrad('Email.template.reset_password'),
|
||||
defaultMessage: 'Reset password',
|
||||
})}
|
||||
</Text>
|
||||
</Td>
|
||||
<Td>
|
||||
<IconButton
|
||||
onClick={() => onEditClick('reset_password')}
|
||||
label={formatMessage({
|
||||
id: getTrad('Email.template.form.edit.label'),
|
||||
defaultMessage: 'Edit a template',
|
||||
})}
|
||||
noBorder
|
||||
icon={canUpdate && <EditIcon />}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<CheckIcon
|
||||
aria-label={formatMessage({
|
||||
id: getTrad('Email.template.email_confirmation'),
|
||||
defaultMessage: 'Email address confirmation',
|
||||
})}
|
||||
/>
|
||||
</Td>
|
||||
<Td>
|
||||
<Text>
|
||||
{formatMessage({
|
||||
id: getTrad('Email.template.email_confirmation'),
|
||||
defaultMessage: 'Email address confirmation',
|
||||
})}
|
||||
</Text>
|
||||
</Td>
|
||||
<Td>
|
||||
<IconButton
|
||||
onClick={() => onEditClick('email_confirmation')}
|
||||
label={formatMessage({
|
||||
id: getTrad('Email.template.form.edit.label'),
|
||||
defaultMessage: 'Edit a template',
|
||||
})}
|
||||
noBorder
|
||||
icon={canUpdate && <EditIcon />}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
</Tbody>
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
EmailTable.propTypes = {
|
||||
canUpdate: PropTypes.bool.isRequired,
|
||||
onEditClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default EmailTable;
|
||||
@ -1,27 +1,24 @@
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import React, { useMemo, useRef, useState } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from 'react-query';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { Header, List } from '@buffetjs/custom';
|
||||
import { Pencil } from '@buffetjs/icons';
|
||||
import { get } from 'lodash';
|
||||
import {
|
||||
SettingsPageTitle,
|
||||
SizedInput,
|
||||
useTracking,
|
||||
request,
|
||||
getYupInnerErrors,
|
||||
useNotification,
|
||||
useOverlayBlocker,
|
||||
CheckPagePermissions,
|
||||
useRBAC,
|
||||
useFocusWhenNavigate,
|
||||
LoadingIndicatorPage,
|
||||
} from '@strapi/helper-plugin';
|
||||
import { Row } from 'reactstrap';
|
||||
import { useNotifyAT } from '@strapi/parts/LiveRegions';
|
||||
import { Main } from '@strapi/parts/Main';
|
||||
import { ContentLayout, HeaderLayout } from '@strapi/parts/Layout';
|
||||
import pluginPermissions from '../../permissions';
|
||||
import { useForm } from '../../hooks';
|
||||
import ListBaselineAlignment from '../../components/ListBaselineAlignment';
|
||||
import ListRow from '../../components/ListRow';
|
||||
import ModalForm from '../../components/ModalForm';
|
||||
import { getRequestURL, getTrad } from '../../utils';
|
||||
import forms from './utils/forms';
|
||||
import schema from './utils/schema';
|
||||
import { getTrad } from '../../utils';
|
||||
import { fetchData, putEmailTemplate } from './utils/api';
|
||||
import EmailTable from './components/EmailTable';
|
||||
import EmailForm from './components/EmailForm';
|
||||
|
||||
const ProtectedEmailTemplatesPage = () => (
|
||||
<CheckPagePermissions permissions={pluginPermissions.readEmailTemplates}>
|
||||
@ -32,210 +29,133 @@ const ProtectedEmailTemplatesPage = () => (
|
||||
const EmailTemplatesPage = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { trackUsage } = useTracking();
|
||||
const { notifyStatus } = useNotifyAT();
|
||||
const toggleNotification = useNotification();
|
||||
const { lockApp, unlockApp } = useOverlayBlocker();
|
||||
const trackUsageRef = useRef(trackUsage);
|
||||
const buttonSubmitRef = useRef(null);
|
||||
const pageTitle = formatMessage({ id: getTrad('HeaderNav.link.emailTemplates') });
|
||||
const queryClient = useQueryClient();
|
||||
useFocusWhenNavigate();
|
||||
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [templateToEdit, setTemplateToEdit] = useState(null);
|
||||
|
||||
const updatePermissions = useMemo(() => {
|
||||
return { update: pluginPermissions.updateEmailTemplates };
|
||||
}, []);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isSubmiting, setIsSubmiting] = useState(false);
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [templateToEdit, setTemplateToEdit] = useState(null);
|
||||
|
||||
const {
|
||||
isLoading: isLoadingForPermissions,
|
||||
allowedActions: { canUpdate },
|
||||
dispatchResetForm,
|
||||
dispatchSetFormErrors,
|
||||
dispatchSubmitSucceeded,
|
||||
formErrors,
|
||||
handleChange,
|
||||
isLoading,
|
||||
isLoadingForPermissions,
|
||||
modifiedData,
|
||||
} = useForm('email-templates', updatePermissions);
|
||||
} = useRBAC(updatePermissions);
|
||||
|
||||
const emailTemplates = useMemo(() => {
|
||||
return Object.keys(modifiedData).reduce((acc, current) => {
|
||||
const { display, icon } = modifiedData[current];
|
||||
const { status: isLoadingData, data } = useQuery('email-templates', () => fetchData(), {
|
||||
onSuccess: () => {
|
||||
notifyStatus(
|
||||
formatMessage({
|
||||
id: getTrad('Email.template.data.loaded'),
|
||||
defaultMessage: 'Email templates has been loaded',
|
||||
})
|
||||
);
|
||||
},
|
||||
onError: () => {
|
||||
toggleNotification({
|
||||
type: 'warning',
|
||||
message: { id: 'notification.error', defaultMessage: 'An error occured' },
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
acc.push({
|
||||
id: current,
|
||||
name: formatMessage({ id: getTrad(display) }),
|
||||
icon: ['fas', icon],
|
||||
const isLoading = isLoadingForPermissions || isLoadingData !== 'success';
|
||||
|
||||
const handleToggle = () => {
|
||||
setIsModalOpen(prev => !prev);
|
||||
};
|
||||
|
||||
const handleEditClick = template => {
|
||||
setTemplateToEdit(template);
|
||||
handleToggle();
|
||||
};
|
||||
|
||||
const submitMutation = useMutation(body => putEmailTemplate({ 'email-templates': body }), {
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries('email-templates');
|
||||
|
||||
toggleNotification({
|
||||
type: 'success',
|
||||
message: { id: 'notification.success.saved', defaultMessage: 'Saved' },
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
}, [modifiedData, formatMessage]);
|
||||
trackUsageRef.current('didEditEmailTemplates');
|
||||
|
||||
const listTitle = useMemo(() => {
|
||||
const count = emailTemplates.length;
|
||||
|
||||
return formatMessage(
|
||||
{
|
||||
id: getTrad(`List.title.emailTemplates.${count > 1 ? 'plural' : 'singular'}`),
|
||||
},
|
||||
{ number: count }
|
||||
);
|
||||
}, [emailTemplates.length, formatMessage]);
|
||||
|
||||
const handleClosed = useCallback(() => {
|
||||
setTemplateToEdit(null);
|
||||
setShowForm(false);
|
||||
dispatchResetForm();
|
||||
}, [dispatchResetForm]);
|
||||
|
||||
const handleToggle = useCallback(() => {
|
||||
setIsOpen(prev => !prev);
|
||||
}, []);
|
||||
|
||||
const handleClickEdit = useCallback(
|
||||
template => {
|
||||
setTemplateToEdit(template);
|
||||
unlockApp();
|
||||
handleToggle();
|
||||
},
|
||||
[handleToggle]
|
||||
);
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
async e => {
|
||||
e.preventDefault();
|
||||
|
||||
let errors = {};
|
||||
|
||||
try {
|
||||
setIsSubmiting(true);
|
||||
await schema.validate(modifiedData[templateToEdit.id], { abortEarly: false });
|
||||
|
||||
lockApp();
|
||||
|
||||
try {
|
||||
trackUsageRef.current('willEditEmailTemplates');
|
||||
|
||||
await request(getRequestURL('email-templates'), {
|
||||
method: 'PUT',
|
||||
body: { 'email-templates': modifiedData },
|
||||
});
|
||||
|
||||
trackUsageRef.current('didEditEmailTemplates');
|
||||
|
||||
toggleNotification({
|
||||
type: 'success',
|
||||
message: { id: getTrad('notification.success.submit') },
|
||||
});
|
||||
|
||||
dispatchSubmitSucceeded();
|
||||
|
||||
handleToggle();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
toggleNotification({
|
||||
type: 'warning',
|
||||
message: { id: 'notification.error' },
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
errors = getYupInnerErrors(err);
|
||||
} finally {
|
||||
setIsSubmiting(false);
|
||||
unlockApp();
|
||||
}
|
||||
|
||||
dispatchSetFormErrors(errors);
|
||||
onError: () => {
|
||||
toggleNotification({
|
||||
type: 'warning',
|
||||
message: { id: 'notification.error', defaultMessage: 'An error occured' },
|
||||
});
|
||||
unlockApp();
|
||||
},
|
||||
[
|
||||
dispatchSetFormErrors,
|
||||
dispatchSubmitSucceeded,
|
||||
modifiedData,
|
||||
templateToEdit,
|
||||
handleToggle,
|
||||
toggleNotification,
|
||||
lockApp,
|
||||
unlockApp,
|
||||
]
|
||||
);
|
||||
refetchActive: true,
|
||||
});
|
||||
const { isLoading: isSubmittingForm } = submitMutation;
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
buttonSubmitRef.current.click();
|
||||
}, []);
|
||||
const handleSubmit = body => {
|
||||
lockApp();
|
||||
trackUsageRef.current('willEditEmailTemplates');
|
||||
|
||||
const handleOpened = useCallback(() => {
|
||||
setShowForm(true);
|
||||
}, []);
|
||||
const editedTemplates = { ...data, [templateToEdit]: body };
|
||||
submitMutation.mutate(editedTemplates);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Main aria-busy="true">
|
||||
<SettingsPageTitle
|
||||
name={formatMessage({
|
||||
id: getTrad('HeaderNav.link.emailTemplates'),
|
||||
defaultMessage: 'Email templates',
|
||||
})}
|
||||
/>
|
||||
<HeaderLayout
|
||||
title={formatMessage({
|
||||
id: getTrad('HeaderNav.link.emailTemplates'),
|
||||
defaultMessage: 'Email templates',
|
||||
})}
|
||||
/>
|
||||
<ContentLayout>
|
||||
<LoadingIndicatorPage />
|
||||
</ContentLayout>
|
||||
</Main>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsPageTitle name={pageTitle} />
|
||||
<div>
|
||||
<Header title={{ label: pageTitle }} isLoading={isLoadingForPermissions || isLoading} />
|
||||
<ListBaselineAlignment />
|
||||
<List
|
||||
title={listTitle}
|
||||
items={emailTemplates}
|
||||
isLoading={isLoadingForPermissions || isLoading}
|
||||
customRowComponent={template => (
|
||||
<ListRow
|
||||
{...template}
|
||||
onClick={() => {
|
||||
if (canUpdate) {
|
||||
handleClickEdit(template);
|
||||
}
|
||||
}}
|
||||
links={[
|
||||
{
|
||||
icon: canUpdate ? <Pencil fill="#0e1622" /> : null,
|
||||
onClick: e => {
|
||||
e.stopPropagation();
|
||||
handleClickEdit(template);
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<ModalForm
|
||||
isOpen={isOpen}
|
||||
onOpened={handleOpened}
|
||||
onToggle={handleToggle}
|
||||
onClosed={handleClosed}
|
||||
headerBreadcrumbs={[
|
||||
getTrad('PopUpForm.header.edit.email-templates'),
|
||||
get(templateToEdit, 'name', ''),
|
||||
]}
|
||||
onClick={handleClick}
|
||||
onCancel={handleToggle}
|
||||
isLoading={isSubmiting}
|
||||
>
|
||||
{showForm && (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Row>
|
||||
{forms.map(input => {
|
||||
const id = get(templateToEdit, 'id');
|
||||
|
||||
return (
|
||||
<SizedInput
|
||||
key={input.name}
|
||||
{...input}
|
||||
error={formErrors[input.name]}
|
||||
name={`${id}.${input.name}`}
|
||||
onChange={handleChange}
|
||||
value={get(modifiedData, [id, ...input.name.split('.')], '')}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
<button type="submit" style={{ display: 'none' }} ref={buttonSubmitRef}>
|
||||
hidden button to use the native form event
|
||||
</button>
|
||||
</form>
|
||||
<Main aria-busy={isSubmittingForm}>
|
||||
<SettingsPageTitle
|
||||
name={formatMessage({
|
||||
id: getTrad('HeaderNav.link.emailTemplates'),
|
||||
defaultMessage: 'Email templates',
|
||||
})}
|
||||
/>
|
||||
<HeaderLayout
|
||||
title={formatMessage({
|
||||
id: getTrad('HeaderNav.link.emailTemplates'),
|
||||
defaultMessage: 'Email templates',
|
||||
})}
|
||||
/>
|
||||
<ContentLayout>
|
||||
<EmailTable onEditClick={handleEditClick} canUpdate={canUpdate} />
|
||||
{isModalOpen && (
|
||||
<EmailForm
|
||||
template={data[templateToEdit]}
|
||||
onToggle={handleToggle}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
)}
|
||||
</ModalForm>
|
||||
</>
|
||||
</ContentLayout>
|
||||
</Main>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,58 +0,0 @@
|
||||
import produce from 'immer';
|
||||
import { set } from 'lodash';
|
||||
|
||||
const initialState = {
|
||||
formErrors: {},
|
||||
isLoading: true,
|
||||
initialData: {},
|
||||
modifiedData: {},
|
||||
};
|
||||
|
||||
const reducer = (state, action) =>
|
||||
// eslint-disable-next-line consistent-return
|
||||
produce(state, draftState => {
|
||||
switch (action.type) {
|
||||
case 'GET_DATA': {
|
||||
draftState.isLoading = true;
|
||||
draftState.initialData = {};
|
||||
draftState.modifiedData = {};
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'GET_DATA_SUCCEEDED': {
|
||||
draftState.isLoading = false;
|
||||
draftState.initialData = action.data;
|
||||
draftState.modifiedData = action.data;
|
||||
|
||||
break;
|
||||
}
|
||||
case 'GET_DATA_ERROR': {
|
||||
draftState.isLoading = true;
|
||||
break;
|
||||
}
|
||||
case 'ON_CHANGE': {
|
||||
set(draftState, ['modifiedData', ...action.keys.split('.')], action.value);
|
||||
break;
|
||||
}
|
||||
case 'ON_SUBMIT_SUCCEEDED': {
|
||||
draftState.initialData = state.modifiedData;
|
||||
break;
|
||||
}
|
||||
case 'RESET_FORM': {
|
||||
draftState.modifiedData = state.initialData;
|
||||
draftState.formErrors = {};
|
||||
break;
|
||||
}
|
||||
case 'SET_ERRORS': {
|
||||
draftState.formErrors = action.errors;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
return draftState;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default reducer;
|
||||
export { initialState };
|
||||
@ -0,0 +1,576 @@
|
||||
import React from 'react';
|
||||
import { render, waitFor, screen } from '@testing-library/react';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { ThemeProvider, lightTheme } from '@strapi/parts';
|
||||
import { useRBAC } from '@strapi/helper-plugin';
|
||||
import ProtectedEmailTemplatesPage from '../index';
|
||||
import server from './utils/server';
|
||||
|
||||
jest.mock('@strapi/helper-plugin', () => ({
|
||||
...jest.requireActual('@strapi/helper-plugin'),
|
||||
useNotification: jest.fn(),
|
||||
useFocusWhenNavigate: jest.fn(),
|
||||
useOverlayBlocker: jest.fn(() => ({ lockApp: jest.fn, unlockApp: jest.fn() })),
|
||||
useRBAC: jest.fn(),
|
||||
CheckPagePermissions: ({ children }) => children,
|
||||
// useTracking: jest.fn(() => ({
|
||||
// trackUsage: jest.fn()
|
||||
// }))
|
||||
}));
|
||||
|
||||
const client = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const App = (
|
||||
<QueryClientProvider client={client}>
|
||||
<IntlProvider messages={{ en: {} }} textComponent="span" locale="en">
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<ProtectedEmailTemplatesPage />
|
||||
</ThemeProvider>
|
||||
</IntlProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
describe('ADMIN | Pages | Settings | Advanced Settings', () => {
|
||||
beforeAll(() => server.listen());
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
server.resetHandlers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.resetAllMocks();
|
||||
server.close();
|
||||
});
|
||||
|
||||
it('renders and matches the snapshot', async () => {
|
||||
useRBAC.mockImplementation(() => ({
|
||||
isLoading: false,
|
||||
allowedActions: { canUpdate: true },
|
||||
}));
|
||||
|
||||
const { container } = render(App);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Reset password')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(container.firstChild).toMatchInlineSnapshot(`
|
||||
.c24 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #dcdce4;
|
||||
}
|
||||
|
||||
.c24 svg {
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.c24 svg > g,
|
||||
.c24 svg path {
|
||||
fill: #ffffff;
|
||||
}
|
||||
|
||||
.c24[aria-disabled='true'] {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.c25 {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.c25 svg > g,
|
||||
.c25 svg path {
|
||||
fill: #8e8ea9;
|
||||
}
|
||||
|
||||
.c25:hover svg > g,
|
||||
.c25:hover svg path {
|
||||
fill: #666687;
|
||||
}
|
||||
|
||||
.c25:active svg > g,
|
||||
.c25:active svg path {
|
||||
fill: #a5a5ba;
|
||||
}
|
||||
|
||||
.c25[aria-disabled='true'] {
|
||||
background-color: #eaeaef;
|
||||
}
|
||||
|
||||
.c25[aria-disabled='true'] svg path {
|
||||
fill: #666687;
|
||||
}
|
||||
|
||||
.c8 {
|
||||
box-shadow: 0px 1px 4px rgba(33,33,52,0.1);
|
||||
}
|
||||
|
||||
.c9 {
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.c11 {
|
||||
padding-right: 24px;
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
.c13 {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.c10 {
|
||||
position: relative;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
.c10:before {
|
||||
background: linear-gradient(90deg,#000000 0%,rgba(0,0,0,0) 100%);
|
||||
opacity: 0.2;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
box-shadow: 0px 1px 4px rgba(33,33,52,0.1);
|
||||
width: 8px;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.c10:after {
|
||||
background: linear-gradient(270deg,#000000 0%,rgba(0,0,0,0) 100%);
|
||||
opacity: 0.2;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
box-shadow: 0px 1px 4px rgba(33,33,52,0.1);
|
||||
width: 8px;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.c12 {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.c23 tr:last-of-type {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.c14 {
|
||||
border-bottom: 1px solid #eaeaef;
|
||||
}
|
||||
|
||||
.c15 {
|
||||
border-bottom: 1px solid #eaeaef;
|
||||
}
|
||||
|
||||
.c15 td,
|
||||
.c15 th {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.c15 td:first-of-type,
|
||||
.c15 th:first-of-type {
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.c17 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c16 {
|
||||
vertical-align: middle;
|
||||
text-align: left;
|
||||
color: #666687;
|
||||
outline-offset: -4px;
|
||||
}
|
||||
|
||||
.c16 input {
|
||||
vertical-align: sub;
|
||||
}
|
||||
|
||||
.c19 svg {
|
||||
height: 0.25rem;
|
||||
}
|
||||
|
||||
.c20 {
|
||||
font-weight: 400;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.43;
|
||||
color: #32324d;
|
||||
}
|
||||
|
||||
.c21 {
|
||||
font-weight: 600;
|
||||
line-height: 1.14;
|
||||
}
|
||||
|
||||
.c22 {
|
||||
font-weight: 600;
|
||||
font-size: 0.6875rem;
|
||||
line-height: 1.45;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
background: #f6f6f9;
|
||||
padding-top: 56px;
|
||||
padding-right: 56px;
|
||||
padding-bottom: 56px;
|
||||
padding-left: 56px;
|
||||
}
|
||||
|
||||
.c7 {
|
||||
padding-right: 56px;
|
||||
padding-left: 56px;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-webkit-box-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c3 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
font-weight: 600;
|
||||
font-size: 2rem;
|
||||
line-height: 1.25;
|
||||
color: #32324d;
|
||||
}
|
||||
|
||||
.c5 {
|
||||
font-weight: 400;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.43;
|
||||
color: #666687;
|
||||
}
|
||||
|
||||
.c6 {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.c18 {
|
||||
border: 0;
|
||||
-webkit-clip: rect(0 0 0 0);
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
<main
|
||||
aria-busy="false"
|
||||
aria-labelledby="main-content-title"
|
||||
class="c0"
|
||||
id="main-content"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
style="height: 0px;"
|
||||
>
|
||||
<div
|
||||
class="c1"
|
||||
data-strapi-header="true"
|
||||
>
|
||||
<div
|
||||
class="c2"
|
||||
>
|
||||
<div
|
||||
class="c3"
|
||||
>
|
||||
<h1
|
||||
class="c4"
|
||||
id="main-content-title"
|
||||
>
|
||||
Email templates
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
class="c5 c6"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<div
|
||||
class="c8"
|
||||
>
|
||||
<div
|
||||
class="c9 c10"
|
||||
>
|
||||
<div
|
||||
class="c11 c12"
|
||||
>
|
||||
<table
|
||||
aria-colcount="3"
|
||||
aria-rowcount="3"
|
||||
class="c13"
|
||||
>
|
||||
<thead
|
||||
class="c14"
|
||||
>
|
||||
<tr
|
||||
aria-rowindex="1"
|
||||
class="c15"
|
||||
>
|
||||
<th
|
||||
aria-colindex="1"
|
||||
class="c16"
|
||||
tabindex="0"
|
||||
width="1%"
|
||||
>
|
||||
<div
|
||||
class="c17"
|
||||
>
|
||||
<div
|
||||
class="c18"
|
||||
>
|
||||
icon
|
||||
</div>
|
||||
<span
|
||||
class="c19"
|
||||
/>
|
||||
</div>
|
||||
</th>
|
||||
<th
|
||||
aria-colindex="2"
|
||||
class="c16"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="c17"
|
||||
>
|
||||
<span
|
||||
class="c20 c21 c22"
|
||||
>
|
||||
name
|
||||
</span>
|
||||
<span
|
||||
class="c19"
|
||||
/>
|
||||
</div>
|
||||
</th>
|
||||
<th
|
||||
aria-colindex="3"
|
||||
class="c16"
|
||||
tabindex="-1"
|
||||
width="1%"
|
||||
>
|
||||
<div
|
||||
class="c17"
|
||||
>
|
||||
<div
|
||||
class="c18"
|
||||
>
|
||||
action
|
||||
</div>
|
||||
<span
|
||||
class="c19"
|
||||
/>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody
|
||||
class="c23"
|
||||
>
|
||||
<tr
|
||||
aria-rowindex="2"
|
||||
class="c15"
|
||||
>
|
||||
<td
|
||||
aria-colindex="1"
|
||||
class="c16"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-label="Reset password"
|
||||
fill="none"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M15.681 2.804A9.64 9.64 0 0011.818 2C6.398 2 2 6.48 2 12c0 5.521 4.397 10 9.818 10 2.03 0 4.011-.641 5.67-1.835a9.987 9.987 0 003.589-4.831 1.117 1.117 0 00-.664-1.418 1.086 1.086 0 00-1.393.676 7.769 7.769 0 01-2.792 3.758 7.546 7.546 0 01-4.41 1.428V4.222h.002a7.492 7.492 0 013.003.625 7.61 7.61 0 012.5 1.762l.464.551-2.986 3.042a.186.186 0 00.129.316H22V3.317a.188.188 0 00-.112-.172.179.179 0 00-.199.04l-2.355 2.4-.394-.468-.02-.02a9.791 9.791 0 00-3.239-2.293zm-3.863 1.418V2v2.222zm0 0v15.556c-4.216 0-7.636-3.484-7.636-7.778s3.42-7.777 7.636-7.778z"
|
||||
fill="#212134"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</td>
|
||||
<td
|
||||
aria-colindex="2"
|
||||
class="c16"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span
|
||||
class="c20"
|
||||
>
|
||||
Reset password
|
||||
</span>
|
||||
</td>
|
||||
<td
|
||||
aria-colindex="3"
|
||||
class="c16"
|
||||
>
|
||||
<span>
|
||||
<button
|
||||
aria-disabled="false"
|
||||
aria-labelledby="tooltip-1"
|
||||
class="c24 c25"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M23.604 3.514c.528.528.528 1.36 0 1.887l-2.622 2.607-4.99-4.99L18.6.396a1.322 1.322 0 011.887 0l3.118 3.118zM0 24v-4.99l14.2-14.2 4.99 4.99L4.99 24H0z"
|
||||
fill="#212134"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
aria-rowindex="3"
|
||||
class="c15"
|
||||
>
|
||||
<td
|
||||
aria-colindex="1"
|
||||
class="c16"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-label="Email address confirmation"
|
||||
fill="none"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M20.727 2.97a.2.2 0 01.286 0l2.85 2.89a.2.2 0 010 .28L9.554 20.854a.2.2 0 01-.285 0l-9.13-9.243a.2.2 0 010-.281l2.85-2.892a.2.2 0 01.284 0l6.14 6.209L20.726 2.97z"
|
||||
fill="#212134"
|
||||
/>
|
||||
</svg>
|
||||
</td>
|
||||
<td
|
||||
aria-colindex="2"
|
||||
class="c16"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span
|
||||
class="c20"
|
||||
>
|
||||
Email address confirmation
|
||||
</span>
|
||||
</td>
|
||||
<td
|
||||
aria-colindex="3"
|
||||
class="c16"
|
||||
>
|
||||
<span>
|
||||
<button
|
||||
aria-disabled="false"
|
||||
aria-labelledby="tooltip-3"
|
||||
class="c24 c25"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M23.604 3.514c.528.528.528 1.36 0 1.887l-2.622 2.607-4.99-4.99L18.6.396a1.322 1.322 0 011.887 0l3.118 3.118zM0 24v-4.99l14.2-14.2 4.99 4.99L4.99 24H0z"
|
||||
fill="#212134"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
`);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,41 @@
|
||||
import { setupServer } from 'msw/node';
|
||||
import { rest } from 'msw';
|
||||
|
||||
const handlers = [
|
||||
rest.get('*/email-templates', (req, res, ctx) => {
|
||||
return res(
|
||||
ctx.delay(100),
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
email_confirmation: {
|
||||
display: 'Email.template.email_confirmation',
|
||||
options: {
|
||||
from: {
|
||||
email: 'mochoko@strapi.io',
|
||||
name: 'Administration Panel',
|
||||
},
|
||||
message: 'Thank you for registering. Please click on the link below.',
|
||||
object: 'Account confirmation',
|
||||
response_email: '',
|
||||
},
|
||||
},
|
||||
reset_password: {
|
||||
display: 'Email.template.reset_password',
|
||||
options: {
|
||||
from: {
|
||||
email: 'mochoko@strapi.io',
|
||||
name: 'Administration Panel',
|
||||
},
|
||||
message: 'We heard that you lost your password. Sorry about that!',
|
||||
object: 'Reset password',
|
||||
response_email: '',
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
}),
|
||||
];
|
||||
|
||||
const server = setupServer(...handlers);
|
||||
|
||||
export default server;
|
||||
@ -0,0 +1,13 @@
|
||||
import { axiosInstance, getRequestURL } from '../../../utils';
|
||||
|
||||
const fetchData = async () => {
|
||||
const { data } = await axiosInstance.get(getRequestURL('email-templates'));
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const putEmailTemplate = body => {
|
||||
return axiosInstance.put(getRequestURL('email-templates'), body);
|
||||
};
|
||||
|
||||
export { fetchData, putEmailTemplate };
|
||||
@ -1,81 +0,0 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { getTrad } from '../../../utils';
|
||||
import CustomTextInput from '../CustomTextInput';
|
||||
|
||||
const forms = [
|
||||
{
|
||||
autoFocus: true,
|
||||
label: getTrad('PopUpForm.Email.options.from.name.label'),
|
||||
name: 'options.from.name',
|
||||
type: 'text',
|
||||
placeholder: getTrad('PopUpForm.Email.options.from.name.placeholder'),
|
||||
size: { xs: 6 },
|
||||
validations: {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
autoFocus: false,
|
||||
label: getTrad('PopUpForm.Email.options.from.email.label'),
|
||||
name: 'options.from.email',
|
||||
type: 'email',
|
||||
placeholder: getTrad('PopUpForm.Email.options.from.email.placeholder'),
|
||||
size: { xs: 6 },
|
||||
validations: {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
autoFocus: false,
|
||||
label: getTrad('PopUpForm.Email.options.response_email.label'),
|
||||
name: 'options.response_email',
|
||||
type: 'email',
|
||||
placeholder: getTrad('PopUpForm.Email.options.response_email.placeholder'),
|
||||
size: { xs: 6 },
|
||||
validations: {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
autoFocus: false,
|
||||
label: getTrad('PopUpForm.Email.options.object.label'),
|
||||
name: 'options.object',
|
||||
type: 'customText',
|
||||
placeholder: getTrad('PopUpForm.Email.options.object.placeholder'),
|
||||
customInputs: { customText: CustomTextInput },
|
||||
descriptione: () => (
|
||||
<FormattedMessage
|
||||
id={getTrad('PopUpForm.Email.email_templates.inputDescription')}
|
||||
values={{
|
||||
link: (
|
||||
<a
|
||||
href="https://strapi.io/documentation/developer-docs/latest/development/plugins/users-permissions.html#templating-emails"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<FormattedMessage id={getTrad('PopUpForm.Email.link.documentation')} />
|
||||
</a>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
),
|
||||
size: { xs: 6 },
|
||||
validations: {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
autoFocus: false,
|
||||
label: getTrad('PopUpForm.Email.options.message.label'),
|
||||
name: 'options.message',
|
||||
type: 'textarea',
|
||||
style: { height: '15.6rem' },
|
||||
size: { xs: 12 },
|
||||
validations: {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export default forms;
|
||||
@ -18,8 +18,13 @@
|
||||
"EditPage.form.roles": "Role details",
|
||||
"Email.template.email_confirmation": "Email address confirmation",
|
||||
"Email.template.reset_password": "Reset password",
|
||||
"Email.template.table.name.label": "name",
|
||||
"Email.template.table.icon.label": "icon",
|
||||
"Email.template.table.action.label": "action",
|
||||
"Email.template.data.loaded": "Email templates has been loaded",
|
||||
"Email.template.form.edit.label": "Edit a template",
|
||||
"HeaderNav.link.advancedSettings": "Advanced settings",
|
||||
"HeaderNav.link.emailTemplates": "Email Templates",
|
||||
"HeaderNav.link.emailTemplates": "Email templates",
|
||||
"HeaderNav.link.providers": "Providers",
|
||||
"HeaderNav.link.roles": "Roles",
|
||||
"Form.title.advancedSettings": "Settings",
|
||||
@ -52,7 +57,7 @@
|
||||
"PopUpForm.Providers.secret.placeholder": "TEXT",
|
||||
"PopUpForm.Providers.subdomain.label": "Host URI (Subdomain)",
|
||||
"PopUpForm.Providers.subdomain.placeholder": "my.subdomain.com",
|
||||
"PopUpForm.header.edit.email-templates": "Edit Email Templates",
|
||||
"PopUpForm.header.edit.email-templates": "Edit email template",
|
||||
"PopUpForm.header.edit.providers": "Edit Provider",
|
||||
"Settings.roles.deleted": "Role deleted",
|
||||
"Settings.roles.edited": "Role edited",
|
||||
@ -76,5 +81,15 @@
|
||||
"popUpWarning.button.cancel": "Cancel",
|
||||
"popUpWarning.button.confirm": "Confirm",
|
||||
"popUpWarning.title": "Please confirm",
|
||||
"popUpWarning.warning.cancel": "Are you sure you want to cancel your modifications?"
|
||||
"popUpWarning.warning.cancel": "Are you sure you want to cancel your modifications?",
|
||||
"components.Input.error.validation.email": "This is an invalid email",
|
||||
"components.Input.error.validation.json": "This doesn't match the JSON format",
|
||||
"components.Input.error.validation.max": "The value is too high.",
|
||||
"components.Input.error.validation.maxLength": "The value is too long.",
|
||||
"components.Input.error.validation.min": "The value is too low.",
|
||||
"components.Input.error.validation.minLength": "The value is too short.",
|
||||
"components.Input.error.validation.minSupMax": "Can't be superior",
|
||||
"components.Input.error.validation.regex": "The value does not match the regex.",
|
||||
"components.Input.error.validation.required": "This value is required.",
|
||||
"components.Input.error.validation.unique": "This value is already used."
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user