mirror of
https://github.com/strapi/strapi.git
synced 2025-08-15 04:08:04 +00:00
[Email] Migrate to typescript (#18136)
Co-authored-by: Josh <37798644+joshuaellis@users.noreply.github.com>
This commit is contained in:
parent
0b13b1b50a
commit
a9552a70bf
@ -12,7 +12,10 @@ const LazyCompo = ({ loadComponent }) => {
|
||||
try {
|
||||
const loadedCompo = await loadComponent();
|
||||
|
||||
setCompo(() => loadedCompo.default);
|
||||
// TODO the loaded component provided can currently come from a default or named export
|
||||
// We will move the entire codebase to use named exports only
|
||||
// Until then we support both cases with priority given to the existing default exports
|
||||
setCompo(() => loadedCompo?.default ?? loadedCompo);
|
||||
} catch (err) {
|
||||
// TODO return the error component
|
||||
console.log(err);
|
||||
|
@ -2,6 +2,16 @@ root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = false
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
charset = utf-8
|
||||
|
||||
|
||||
[{package.json,*.yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
@ -1,3 +1,4 @@
|
||||
node_modules/
|
||||
.eslintrc.js
|
||||
index.d.ts
|
||||
index.d.ts
|
||||
dist/
|
||||
|
@ -7,7 +7,7 @@ module.exports = {
|
||||
},
|
||||
{
|
||||
files: ['**/*'],
|
||||
excludedFiles: ['admin/**/*'],
|
||||
excludedFiles: ['admin/**/*', 'server/**/*'],
|
||||
extends: ['custom/back'],
|
||||
},
|
||||
],
|
||||
|
4
packages/core/email/admin/.eslintrc.js
Normal file
4
packages/core/email/admin/.eslintrc.js
Normal file
@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['custom/front/typescript'],
|
||||
};
|
@ -9,8 +9,12 @@ import { prefixPluginTranslations } from '@strapi/helper-plugin';
|
||||
|
||||
import { PERMISSIONS } from './constants';
|
||||
|
||||
export default {
|
||||
register(app) {
|
||||
import type { Plugin } from '@strapi/types';
|
||||
|
||||
const admin: Plugin.Config.AdminInput = {
|
||||
// TODO typing app in strapi/types as every plugin needs it
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
register(app: any) {
|
||||
// Create the email settings section
|
||||
app.createSettingSection(
|
||||
{
|
||||
@ -26,11 +30,11 @@ export default {
|
||||
id: 'settings',
|
||||
to: `/settings/email`,
|
||||
async Component() {
|
||||
const component = await import(
|
||||
const { ProtectedSettingsPage } = await import(
|
||||
/* webpackChunkName: "email-settings-page" */ './pages/Settings'
|
||||
);
|
||||
|
||||
return component;
|
||||
return ProtectedSettingsPage;
|
||||
},
|
||||
permissions: PERMISSIONS.settings,
|
||||
},
|
||||
@ -41,8 +45,9 @@ export default {
|
||||
name: 'email',
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
bootstrap() {},
|
||||
async registerTrads({ locales }) {
|
||||
async registerTrads({ locales }: { locales: string[] }) {
|
||||
const importedTrads = await Promise.all(
|
||||
locales.map((locale) => {
|
||||
return import(
|
||||
@ -66,3 +71,6 @@ export default {
|
||||
return Promise.resolve(importedTrads);
|
||||
},
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default admin;
|
324
packages/core/email/admin/src/pages/Settings.tsx
Normal file
324
packages/core/email/admin/src/pages/Settings.tsx
Normal file
@ -0,0 +1,324 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ContentLayout,
|
||||
Flex,
|
||||
Grid,
|
||||
GridItem,
|
||||
HeaderLayout,
|
||||
Main,
|
||||
Option,
|
||||
Select,
|
||||
TextInput,
|
||||
Typography,
|
||||
} from '@strapi/design-system';
|
||||
import {
|
||||
CheckPagePermissions,
|
||||
getYupInnerErrors,
|
||||
LoadingIndicatorPage,
|
||||
SettingsPageTitle,
|
||||
useFetchClient,
|
||||
useFocusWhenNavigate,
|
||||
useNotification,
|
||||
useOverlayBlocker,
|
||||
} from '@strapi/helper-plugin';
|
||||
import { Envelop } from '@strapi/icons';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useQuery, useMutation } from 'react-query';
|
||||
import styled from 'styled-components';
|
||||
import { ValidationError } from 'yup';
|
||||
|
||||
import { PERMISSIONS } from '../constants';
|
||||
import { schema } from '../utils/schema';
|
||||
|
||||
import type { EmailSettings } from '../../../shared/types';
|
||||
|
||||
const DocumentationLink = styled.a`
|
||||
color: ${({ theme }) => theme.colors.primary600};
|
||||
`;
|
||||
|
||||
interface MutationBody {
|
||||
to: string;
|
||||
}
|
||||
|
||||
export const ProtectedSettingsPage = () => (
|
||||
<CheckPagePermissions permissions={PERMISSIONS.settings}>
|
||||
<SettingsPage />
|
||||
</CheckPagePermissions>
|
||||
);
|
||||
|
||||
const SettingsPage = () => {
|
||||
const toggleNotification = useNotification();
|
||||
const { formatMessage } = useIntl();
|
||||
const { lockApp, unlockApp } = useOverlayBlocker();
|
||||
const { get, post } = useFetchClient();
|
||||
|
||||
const [testAddress, setTestAddress] = React.useState('');
|
||||
const [isTestAddressValid, setIsTestAddressValid] = React.useState(false);
|
||||
|
||||
// TODO: I'm not sure how to type this. I think it should be Record<string, TranslationMessage> but that type is defined in the helper-plugin
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const [formErrors, setFormErrors] = React.useState<Record<string, any>>({});
|
||||
|
||||
const { data, isLoading } = useQuery(['email', 'settings'], async () => {
|
||||
const res = await get<EmailSettings>('/email/settings');
|
||||
const {
|
||||
data: { config },
|
||||
} = res;
|
||||
|
||||
return config;
|
||||
});
|
||||
|
||||
const mutation = useMutation<void, Error, MutationBody>(
|
||||
async (body) => {
|
||||
await post('/email/test', body);
|
||||
},
|
||||
{
|
||||
onError() {
|
||||
toggleNotification!({
|
||||
type: 'warning',
|
||||
message: formatMessage(
|
||||
{
|
||||
id: 'email.Settings.email.plugin.notification.test.error',
|
||||
defaultMessage: 'Failed to send a test mail to {to}',
|
||||
},
|
||||
{ to: testAddress }
|
||||
),
|
||||
});
|
||||
},
|
||||
onSuccess() {
|
||||
toggleNotification!({
|
||||
type: 'success',
|
||||
message: formatMessage(
|
||||
{
|
||||
id: 'email.Settings.email.plugin.notification.test.success',
|
||||
defaultMessage: 'Email test succeeded, check the {to} mailbox',
|
||||
},
|
||||
{ to: testAddress }
|
||||
),
|
||||
});
|
||||
},
|
||||
retry: false,
|
||||
}
|
||||
);
|
||||
|
||||
useFocusWhenNavigate();
|
||||
|
||||
React.useEffect(() => {
|
||||
schema
|
||||
.validate({ email: testAddress }, { abortEarly: false })
|
||||
.then(() => setIsTestAddressValid(true))
|
||||
.catch(() => setIsTestAddressValid(false));
|
||||
}, [testAddress]);
|
||||
|
||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setTestAddress(() => event.target.value);
|
||||
};
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
try {
|
||||
await schema.validate({ email: testAddress }, { abortEarly: false });
|
||||
} catch (error) {
|
||||
if (error instanceof ValidationError) {
|
||||
setFormErrors(getYupInnerErrors(error));
|
||||
}
|
||||
}
|
||||
|
||||
lockApp!();
|
||||
|
||||
mutation.mutate({ to: testAddress });
|
||||
|
||||
unlockApp!();
|
||||
};
|
||||
|
||||
return (
|
||||
<Main labelledBy="title" aria-busy={isLoading || mutation.isLoading}>
|
||||
<SettingsPageTitle
|
||||
name={formatMessage({
|
||||
id: 'email.Settings.email.plugin.title',
|
||||
defaultMessage: 'Configuration',
|
||||
})}
|
||||
/>
|
||||
|
||||
<HeaderLayout
|
||||
id="title"
|
||||
title={formatMessage({
|
||||
id: 'email.Settings.email.plugin.title',
|
||||
defaultMessage: 'Configuration',
|
||||
})}
|
||||
subtitle={formatMessage({
|
||||
id: 'email.Settings.email.plugin.subTitle',
|
||||
defaultMessage: 'Test the settings for the Email plugin',
|
||||
})}
|
||||
/>
|
||||
|
||||
<ContentLayout>
|
||||
{isLoading ? (
|
||||
<LoadingIndicatorPage />
|
||||
) : (
|
||||
data && (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Flex direction="column" alignItems="stretch" gap={7}>
|
||||
<Box
|
||||
background="neutral0"
|
||||
hasRadius
|
||||
shadow="filterShadow"
|
||||
paddingTop={6}
|
||||
paddingBottom={6}
|
||||
paddingLeft={7}
|
||||
paddingRight={7}
|
||||
>
|
||||
<Flex direction="column" alignItems="stretch" gap={4}>
|
||||
<Flex direction="column" alignItems="stretch" gap={1}>
|
||||
<Typography variant="delta" as="h2">
|
||||
{formatMessage({
|
||||
id: 'email.Settings.email.plugin.title.config',
|
||||
defaultMessage: 'Configuration',
|
||||
})}
|
||||
</Typography>
|
||||
<Typography>
|
||||
{formatMessage(
|
||||
{
|
||||
id: 'email.Settings.email.plugin.text.configuration',
|
||||
defaultMessage:
|
||||
'The plugin is configured through the {file} file, checkout this {link} for the documentation.',
|
||||
},
|
||||
{
|
||||
file: './config/plugins.js',
|
||||
link: (
|
||||
<DocumentationLink
|
||||
href="https://docs.strapi.io/developer-docs/latest/plugins/email.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{formatMessage({
|
||||
id: 'email.link',
|
||||
defaultMessage: 'Link',
|
||||
})}
|
||||
</DocumentationLink>
|
||||
),
|
||||
}
|
||||
)}
|
||||
</Typography>
|
||||
</Flex>
|
||||
|
||||
<Grid gap={5}>
|
||||
<GridItem col={6} s={12}>
|
||||
<TextInput
|
||||
name="shipper-email"
|
||||
label={formatMessage({
|
||||
id: 'email.Settings.email.plugin.label.defaultFrom',
|
||||
defaultMessage: 'Default sender email',
|
||||
})}
|
||||
placeholder={formatMessage({
|
||||
id: 'email.Settings.email.plugin.placeholder.defaultFrom',
|
||||
defaultMessage: "ex: Strapi No-Reply '<'no-reply@strapi.io'>'",
|
||||
})}
|
||||
disabled
|
||||
value={data.settings.defaultFrom}
|
||||
/>
|
||||
</GridItem>
|
||||
|
||||
<GridItem col={6} s={12}>
|
||||
<TextInput
|
||||
name="response-email"
|
||||
label={formatMessage({
|
||||
id: 'email.Settings.email.plugin.label.defaultReplyTo',
|
||||
defaultMessage: 'Default response email',
|
||||
})}
|
||||
placeholder={formatMessage({
|
||||
id: 'email.Settings.email.plugin.placeholder.defaultReplyTo',
|
||||
defaultMessage: `ex: Strapi '<'example@strapi.io'>'`,
|
||||
})}
|
||||
disabled
|
||||
value={data.settings.defaultReplyTo}
|
||||
/>
|
||||
</GridItem>
|
||||
|
||||
<GridItem col={6} s={12}>
|
||||
<Select
|
||||
name="email-provider"
|
||||
label={formatMessage({
|
||||
id: 'email.Settings.email.plugin.label.provider',
|
||||
defaultMessage: 'Email provider',
|
||||
})}
|
||||
disabled
|
||||
value={data.provider}
|
||||
>
|
||||
<Option value={data.provider}>{data.provider}</Option>
|
||||
</Select>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
</Flex>
|
||||
</Box>
|
||||
|
||||
<Flex
|
||||
alignItems="stretch"
|
||||
background="neutral0"
|
||||
direction="column"
|
||||
gap={4}
|
||||
hasRadius
|
||||
shadow="filterShadow"
|
||||
paddingTop={6}
|
||||
paddingBottom={6}
|
||||
paddingLeft={7}
|
||||
paddingRight={7}
|
||||
>
|
||||
<Typography variant="delta" as="h2">
|
||||
{formatMessage({
|
||||
id: 'email.Settings.email.plugin.title.test',
|
||||
defaultMessage: 'Test email delivery',
|
||||
})}
|
||||
</Typography>
|
||||
|
||||
<Grid gap={5}>
|
||||
<GridItem col={6} s={12}>
|
||||
<TextInput
|
||||
id="test-address-input"
|
||||
name="test-address"
|
||||
onChange={handleChange}
|
||||
label={formatMessage({
|
||||
id: 'email.Settings.email.plugin.label.testAddress',
|
||||
defaultMessage: 'Recipient email',
|
||||
})}
|
||||
value={testAddress}
|
||||
error={
|
||||
formErrors.email?.id &&
|
||||
formatMessage({
|
||||
id: `email.${formErrors.email?.id}`,
|
||||
defaultMessage: 'This is an invalid email',
|
||||
})
|
||||
}
|
||||
placeholder={formatMessage({
|
||||
id: 'email.Settings.email.plugin.placeholder.testAddress',
|
||||
defaultMessage: 'ex: developer@example.com',
|
||||
})}
|
||||
/>
|
||||
</GridItem>
|
||||
<GridItem col={7} s={12}>
|
||||
<Button
|
||||
loading={mutation.isLoading}
|
||||
disabled={!isTestAddressValid}
|
||||
type="submit"
|
||||
startIcon={<Envelop />}
|
||||
>
|
||||
{formatMessage({
|
||||
id: 'email.Settings.email.plugin.button.test-email',
|
||||
defaultMessage: 'Send test email',
|
||||
})}
|
||||
</Button>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</form>
|
||||
)
|
||||
)}
|
||||
</ContentLayout>
|
||||
</Main>
|
||||
);
|
||||
};
|
@ -1,314 +0,0 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ContentLayout,
|
||||
Flex,
|
||||
Grid,
|
||||
GridItem,
|
||||
HeaderLayout,
|
||||
Main,
|
||||
Option,
|
||||
Select,
|
||||
TextInput,
|
||||
Typography,
|
||||
} from '@strapi/design-system';
|
||||
import {
|
||||
CheckPagePermissions,
|
||||
getYupInnerErrors,
|
||||
LoadingIndicatorPage,
|
||||
SettingsPageTitle,
|
||||
useFetchClient,
|
||||
useFocusWhenNavigate,
|
||||
useNotification,
|
||||
useOverlayBlocker,
|
||||
} from '@strapi/helper-plugin';
|
||||
import { Envelop } from '@strapi/icons';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useQuery, useMutation } from 'react-query';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { PERMISSIONS } from '../../constants';
|
||||
import schema from '../../utils/schema';
|
||||
|
||||
const DocumentationLink = styled.a`
|
||||
color: ${({ theme }) => theme.colors.primary600};
|
||||
`;
|
||||
|
||||
const ProtectedSettingsPage = () => (
|
||||
<CheckPagePermissions permissions={PERMISSIONS.settings}>
|
||||
<SettingsPage />
|
||||
</CheckPagePermissions>
|
||||
);
|
||||
|
||||
const SettingsPage = () => {
|
||||
const toggleNotification = useNotification();
|
||||
const { formatMessage } = useIntl();
|
||||
const { lockApp, unlockApp } = useOverlayBlocker();
|
||||
const { get, post } = useFetchClient();
|
||||
const { data, isLoading } = useQuery(['email', 'settings'], async () => {
|
||||
const {
|
||||
data: { config },
|
||||
} = await get('/email/settings');
|
||||
|
||||
return config;
|
||||
});
|
||||
|
||||
const mutation = useMutation(
|
||||
(body) => post('/email/test', body),
|
||||
{
|
||||
onError() {
|
||||
toggleNotification({
|
||||
type: 'warning',
|
||||
message: formatMessage(
|
||||
{
|
||||
id: 'email.Settings.email.plugin.notification.test.error',
|
||||
defaultMessage: 'Failed to send a test mail to {to}',
|
||||
},
|
||||
{ to: testAddress }
|
||||
),
|
||||
});
|
||||
},
|
||||
|
||||
onSuccess() {
|
||||
toggleNotification({
|
||||
type: 'success',
|
||||
message: formatMessage(
|
||||
{
|
||||
id: 'email.Settings.email.plugin.notification.test.success',
|
||||
defaultMessage: 'Email test succeeded, check the {to} mailbox',
|
||||
},
|
||||
{ to: testAddress }
|
||||
),
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
retry: false,
|
||||
}
|
||||
);
|
||||
|
||||
useFocusWhenNavigate();
|
||||
|
||||
const [formErrors, setFormErrors] = React.useState({});
|
||||
const [testAddress, setTestAddress] = React.useState('');
|
||||
const [isTestAddressValid, setIsTestAddressValid] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
schema
|
||||
.validate({ email: testAddress }, { abortEarly: false })
|
||||
.then(() => setIsTestAddressValid(true))
|
||||
.catch(() => setIsTestAddressValid(false));
|
||||
}, [testAddress]);
|
||||
|
||||
const handleChange = (e) => {
|
||||
setTestAddress(() => e.target.value);
|
||||
};
|
||||
|
||||
const handleSubmit = async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
try {
|
||||
await schema.validate({ email: testAddress }, { abortEarly: false });
|
||||
} catch (error) {
|
||||
setFormErrors(getYupInnerErrors(error));
|
||||
}
|
||||
|
||||
lockApp();
|
||||
|
||||
mutation.mutate({ to: testAddress });
|
||||
|
||||
unlockApp();
|
||||
};
|
||||
|
||||
return (
|
||||
<Main labelledBy="title" aria-busy={isLoading || mutation.isLoading}>
|
||||
<SettingsPageTitle
|
||||
name={formatMessage({
|
||||
id: 'email.Settings.email.plugin.title',
|
||||
defaultMessage: 'Configuration',
|
||||
})}
|
||||
/>
|
||||
|
||||
<HeaderLayout
|
||||
id="title"
|
||||
title={formatMessage({
|
||||
id: 'email.Settings.email.plugin.title',
|
||||
defaultMessage: 'Configuration',
|
||||
})}
|
||||
subtitle={formatMessage({
|
||||
id: 'email.Settings.email.plugin.subTitle',
|
||||
defaultMessage: 'Test the settings for the Email plugin',
|
||||
})}
|
||||
/>
|
||||
|
||||
<ContentLayout>
|
||||
{isLoading ? (
|
||||
<LoadingIndicatorPage />
|
||||
) : (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Flex direction="column" alignItems="stretch" gap={7}>
|
||||
<Box
|
||||
background="neutral0"
|
||||
hasRadius
|
||||
shadow="filterShadow"
|
||||
paddingTop={6}
|
||||
paddingBottom={6}
|
||||
paddingLeft={7}
|
||||
paddingRight={7}
|
||||
>
|
||||
<Flex direction="column" alignItems="stretch" gap={4}>
|
||||
<Flex direction="column" alignItems="stretch" gap={1}>
|
||||
<Typography variant="delta" as="h2">
|
||||
{formatMessage({
|
||||
id: 'email.Settings.email.plugin.title.config',
|
||||
defaultMessage: 'Configuration',
|
||||
})}
|
||||
</Typography>
|
||||
<Typography>
|
||||
{formatMessage(
|
||||
{
|
||||
id: 'email.Settings.email.plugin.text.configuration',
|
||||
defaultMessage:
|
||||
'The plugin is configured through the {file} file, checkout this {link} for the documentation.',
|
||||
},
|
||||
{
|
||||
file: './config/plugins.js',
|
||||
link: (
|
||||
<DocumentationLink
|
||||
href="https://docs.strapi.io/developer-docs/latest/plugins/email.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{formatMessage({
|
||||
id: 'email.link',
|
||||
defaultMessage: 'Link',
|
||||
})}
|
||||
</DocumentationLink>
|
||||
),
|
||||
}
|
||||
)}
|
||||
</Typography>
|
||||
</Flex>
|
||||
|
||||
<Grid gap={5}>
|
||||
<GridItem col={6} s={12}>
|
||||
<TextInput
|
||||
name="shipper-email"
|
||||
label={formatMessage({
|
||||
id: 'email.Settings.email.plugin.label.defaultFrom',
|
||||
defaultMessage: 'Default sender email',
|
||||
})}
|
||||
placeholder={formatMessage({
|
||||
id: 'email.Settings.email.plugin.placeholder.defaultFrom',
|
||||
defaultMessage: "ex: Strapi No-Reply '<'no-reply@strapi.io'>'",
|
||||
})}
|
||||
disabled
|
||||
onChange={() => {}}
|
||||
value={data.settings.defaultFrom}
|
||||
/>
|
||||
</GridItem>
|
||||
|
||||
<GridItem col={6} s={12}>
|
||||
<TextInput
|
||||
name="response-email"
|
||||
label={formatMessage({
|
||||
id: 'email.Settings.email.plugin.label.defaultReplyTo',
|
||||
defaultMessage: 'Default response email',
|
||||
})}
|
||||
placeholder={formatMessage({
|
||||
id: 'email.Settings.email.plugin.placeholder.defaultReplyTo',
|
||||
defaultMessage: `ex: Strapi '<'example@strapi.io'>'`,
|
||||
})}
|
||||
disabled
|
||||
onChange={() => {}}
|
||||
value={data.settings.defaultReplyTo}
|
||||
/>
|
||||
</GridItem>
|
||||
|
||||
<GridItem col={6} s={12}>
|
||||
<Select
|
||||
name="email-provider"
|
||||
label={formatMessage({
|
||||
id: 'email.Settings.email.plugin.label.provider',
|
||||
defaultMessage: 'Email provider',
|
||||
})}
|
||||
disabled
|
||||
onChange={() => {}}
|
||||
value={data.provider}
|
||||
>
|
||||
<Option value={data.provider}>{data.provider}</Option>
|
||||
</Select>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
</Flex>
|
||||
</Box>
|
||||
|
||||
<Flex
|
||||
alignItems="stretch"
|
||||
background="neutral0"
|
||||
direction="column"
|
||||
gap={4}
|
||||
hasRadius
|
||||
shadow="filterShadow"
|
||||
paddingTop={6}
|
||||
paddingBottom={6}
|
||||
paddingLeft={7}
|
||||
paddingRight={7}
|
||||
>
|
||||
<Typography variant="delta" as="h2">
|
||||
{formatMessage({
|
||||
id: 'email.Settings.email.plugin.title.test',
|
||||
defaultMessage: 'Test email delivery',
|
||||
})}
|
||||
</Typography>
|
||||
|
||||
<Grid gap={5} alignItems="end">
|
||||
<GridItem col={6} s={12}>
|
||||
<TextInput
|
||||
id="test-address-input"
|
||||
name="test-address"
|
||||
onChange={handleChange}
|
||||
label={formatMessage({
|
||||
id: 'email.Settings.email.plugin.label.testAddress',
|
||||
defaultMessage: 'Recipient email',
|
||||
})}
|
||||
value={testAddress}
|
||||
error={
|
||||
formErrors.email?.id &&
|
||||
formatMessage({
|
||||
id: `email.${formErrors.email?.id}`,
|
||||
defaultMessage: 'This is an invalid email',
|
||||
})
|
||||
}
|
||||
placeholder={formatMessage({
|
||||
id: 'email.Settings.email.plugin.placeholder.testAddress',
|
||||
defaultMessage: 'ex: developer@example.com',
|
||||
})}
|
||||
/>
|
||||
</GridItem>
|
||||
<GridItem col={7} s={12}>
|
||||
<Button
|
||||
loading={mutation.isLoading}
|
||||
disabled={!isTestAddressValid}
|
||||
type="submit"
|
||||
startIcon={<Envelop />}
|
||||
>
|
||||
{formatMessage({
|
||||
id: 'email.Settings.email.plugin.button.test-email',
|
||||
defaultMessage: 'Send test email',
|
||||
})}
|
||||
</Button>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</form>
|
||||
)}
|
||||
</ContentLayout>
|
||||
</Main>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProtectedSettingsPage;
|
@ -1,9 +1,11 @@
|
||||
const translations = require('../en.json');
|
||||
import translations from '../en.json';
|
||||
|
||||
const typedTranslations: Record<string, string> = translations;
|
||||
|
||||
describe('translations', () => {
|
||||
describe('plural syntax', () => {
|
||||
it('should avoid .plural/.singular syntax', () => {
|
||||
Object.keys(translations).forEach((translationKey) => {
|
||||
Object.keys(typedTranslations).forEach((translationKey) => {
|
||||
const keyParts = translationKey.split('.');
|
||||
const lastKeyPart = keyParts.pop();
|
||||
|
||||
@ -13,7 +15,7 @@ describe('translations', () => {
|
||||
keyParts.push('plural');
|
||||
const pluralKey = keyParts.join('.');
|
||||
|
||||
expect(translations[pluralKey]).toBeUndefined();
|
||||
expect(typedTranslations[pluralKey]).toBeUndefined();
|
||||
}
|
||||
});
|
||||
});
|
@ -1,8 +1,6 @@
|
||||
import { translatedErrors } from '@strapi/helper-plugin';
|
||||
import * as yup from 'yup';
|
||||
|
||||
const schema = yup.object().shape({
|
||||
export const schema = yup.object().shape({
|
||||
email: yup.string().email(translatedErrors.email).required(translatedErrors.required),
|
||||
});
|
||||
|
||||
export default schema;
|
8
packages/core/email/admin/tsconfig.json
Normal file
8
packages/core/email/admin/tsconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "tsconfig/client.json",
|
||||
"include": ["./src", "../shared/types.ts"],
|
||||
"compilerOptions": {
|
||||
"rootDir": "../",
|
||||
}
|
||||
}
|
||||
|
@ -19,15 +19,42 @@
|
||||
"url": "https://strapi.io"
|
||||
}
|
||||
],
|
||||
"exports": {
|
||||
"./strapi-admin": {
|
||||
"types": "./dist/admin/src/index.d.ts",
|
||||
"source": "./admin/src/index.ts",
|
||||
"import": "./dist/admin/index.mjs",
|
||||
"require": "./dist/admin/index.js",
|
||||
"default": "./dist/admin/index.js"
|
||||
},
|
||||
"./strapi-server": {
|
||||
"types": "./dist/server/src/index.d.ts",
|
||||
"source": "./server/src/index.ts",
|
||||
"import": "./dist/server/index.mjs",
|
||||
"require": "./dist/server/index.js",
|
||||
"default": "./dist/server/index.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"files": [
|
||||
"./dist",
|
||||
"strapi-server.js"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "pack-up build",
|
||||
"clean": "run -T rimraf ./dist",
|
||||
"lint": "run -T eslint .",
|
||||
"prepublishOnly": "yarn clean && yarn build",
|
||||
"test:front": "run -T cross-env IS_EE=true jest --config ./jest.config.front.js",
|
||||
"test:front:ce": "run -T cross-env IS_EE=false jest --config ./jest.config.front.js",
|
||||
"test:front:watch": "run -T cross-env IS_EE=true jest --config ./jest.config.front.js --watchAll",
|
||||
"test:front:watch:ce": "run -T cross-env IS_EE=false jest --config ./jest.config.front.js --watchAll"
|
||||
"test:front:watch:ce": "run -T cross-env IS_EE=false jest --config ./jest.config.front.js --watchAll",
|
||||
"test:ts:front": "run -T tsc -p admin/tsconfig.json",
|
||||
"watch": "pack-up watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@strapi/design-system": "1.12.0",
|
||||
"@strapi/helper-plugin": "4.14.4",
|
||||
"@strapi/icons": "1.12.0",
|
||||
"@strapi/provider-email-sendmail": "4.14.4",
|
||||
"@strapi/utils": "4.14.4",
|
||||
@ -38,8 +65,12 @@
|
||||
"yup": "0.32.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@strapi/helper-plugin": "4.14.4",
|
||||
"@strapi/pack-up": "workspace:*",
|
||||
"@strapi/types": "workspace:*",
|
||||
"@testing-library/react": "14.0.0",
|
||||
"@types/koa": "2.13.4",
|
||||
"@types/lodash": "^4.14.191",
|
||||
"koa": "2.13.4",
|
||||
"msw": "1.3.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
@ -47,6 +78,7 @@
|
||||
"styled-components": "5.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"koa": "2.13.4",
|
||||
"react": "^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0",
|
||||
"react-router-dom": "5.3.4",
|
||||
|
27
packages/core/email/packup.config.ts
Normal file
27
packages/core/email/packup.config.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { defineConfig } from '@strapi/pack-up';
|
||||
|
||||
export default defineConfig({
|
||||
bundles: [
|
||||
{
|
||||
source: './admin/src/index.ts',
|
||||
import: './dist/admin/index.mjs',
|
||||
require: './dist/admin/index.js',
|
||||
types: './dist/admin/src/index.d.ts',
|
||||
runtime: 'web',
|
||||
},
|
||||
{
|
||||
source: './server/src/index.ts',
|
||||
import: './dist/server/index.mjs',
|
||||
require: './dist/server/index.js',
|
||||
types: './dist/server/src/index.d.ts',
|
||||
runtime: 'node',
|
||||
},
|
||||
],
|
||||
dist: './dist',
|
||||
/**
|
||||
* Because we're exporting a server & client package
|
||||
* which have different runtimes we want to ignore
|
||||
* what they look like in the package.json
|
||||
*/
|
||||
exports: {},
|
||||
});
|
7
packages/core/email/server/.eslintrc.js
Normal file
7
packages/core/email/server/.eslintrc.js
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['custom/back/typescript'],
|
||||
parserOptions: {
|
||||
project: ['./server/tsconfig.eslint.json'],
|
||||
},
|
||||
};
|
43
packages/core/email/server/bootstrap.js
vendored
43
packages/core/email/server/bootstrap.js
vendored
@ -1,43 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const createProvider = (emailConfig) => {
|
||||
const providerName = emailConfig.provider?.toLowerCase();
|
||||
let provider;
|
||||
|
||||
let modulePath;
|
||||
try {
|
||||
modulePath = require.resolve(`@strapi/provider-email-${providerName}`);
|
||||
} catch (error) {
|
||||
if (error.code === 'MODULE_NOT_FOUND') {
|
||||
modulePath = providerName;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
provider = require(modulePath);
|
||||
} catch (err) {
|
||||
throw new Error(`Could not load email provider "${providerName}".`);
|
||||
}
|
||||
|
||||
return provider.init(emailConfig.providerOptions, emailConfig.settings);
|
||||
};
|
||||
|
||||
module.exports = async ({ strapi }) => {
|
||||
const emailConfig = strapi.config.get('plugin.email');
|
||||
strapi.plugin('email').provider = createProvider(emailConfig);
|
||||
|
||||
// Add permissions
|
||||
const actions = [
|
||||
{
|
||||
section: 'settings',
|
||||
category: 'email',
|
||||
displayName: 'Access the Email Settings page',
|
||||
uid: 'settings.read',
|
||||
pluginName: 'email',
|
||||
},
|
||||
];
|
||||
|
||||
await strapi.admin.services.permission.actionProvider.registerMany(actions);
|
||||
};
|
@ -1,68 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const { pick } = require('lodash/fp');
|
||||
const { ApplicationError } = require('@strapi/utils').errors;
|
||||
|
||||
/**
|
||||
* Email.js controller
|
||||
*
|
||||
* @description: A set of functions called "actions" of the `email` plugin.
|
||||
*/
|
||||
module.exports = {
|
||||
async send(ctx) {
|
||||
const options = ctx.request.body;
|
||||
|
||||
try {
|
||||
await strapi.plugin('email').service('email').send(options);
|
||||
} catch (e) {
|
||||
if (e.statusCode === 400) {
|
||||
throw new ApplicationError(e.message);
|
||||
} else {
|
||||
throw new Error(`Couldn't send email: ${e.message}.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Send 200 `ok`
|
||||
ctx.send({});
|
||||
},
|
||||
|
||||
async test(ctx) {
|
||||
const { to } = ctx.request.body;
|
||||
|
||||
if (!to) {
|
||||
throw new ApplicationError('No recipient(s) are given');
|
||||
}
|
||||
|
||||
const email = {
|
||||
to,
|
||||
subject: `Strapi test mail to: ${to}`,
|
||||
text: `Great! You have correctly configured the Strapi email plugin with the ${strapi.config.get(
|
||||
'plugin.email.provider'
|
||||
)} provider. \r\nFor documentation on how to use the email plugin checkout: https://docs.strapi.io/developer-docs/latest/plugins/email.html`,
|
||||
};
|
||||
|
||||
try {
|
||||
await strapi.plugin('email').service('email').send(email);
|
||||
} catch (e) {
|
||||
if (e.statusCode === 400) {
|
||||
throw new ApplicationError(e.message);
|
||||
} else {
|
||||
throw new Error(`Couldn't send test email: ${e.message}.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Send 200 `ok`
|
||||
ctx.send({});
|
||||
},
|
||||
|
||||
async getSettings(ctx) {
|
||||
const config = strapi.plugin('email').service('email').getProviderSettings();
|
||||
|
||||
ctx.send({
|
||||
config: pick(
|
||||
['provider', 'settings.defaultFrom', 'settings.defaultReplyTo', 'settings.testAddress'],
|
||||
config
|
||||
),
|
||||
});
|
||||
},
|
||||
};
|
@ -1,7 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const email = require('./email');
|
||||
|
||||
module.exports = {
|
||||
email,
|
||||
};
|
@ -1,6 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
admin: require('./admin'),
|
||||
'content-api': require('./content-api'),
|
||||
};
|
@ -1,7 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const email = require('./email');
|
||||
|
||||
module.exports = {
|
||||
email,
|
||||
};
|
57
packages/core/email/server/src/bootstrap.ts
Normal file
57
packages/core/email/server/src/bootstrap.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import type { Strapi } from '@strapi/types';
|
||||
import type { EmailConfig, SendOptions } from './types';
|
||||
|
||||
interface EmailProvider {
|
||||
send: (options: SendOptions) => Promise<any>;
|
||||
}
|
||||
|
||||
interface EmailProviderModule {
|
||||
init: (
|
||||
options: EmailConfig['providerOptions'],
|
||||
settings: EmailConfig['settings']
|
||||
) => EmailProvider;
|
||||
name?: string;
|
||||
provider?: string;
|
||||
}
|
||||
|
||||
const createProvider = (emailConfig: EmailConfig) => {
|
||||
const providerName = emailConfig.provider.toLowerCase();
|
||||
let provider: EmailProviderModule;
|
||||
|
||||
let modulePath: string;
|
||||
try {
|
||||
modulePath = require.resolve(`@strapi/provider-email-${providerName}`);
|
||||
} catch (error) {
|
||||
if (error instanceof Error && 'code' in error && error.code === 'MODULE_NOT_FOUND') {
|
||||
modulePath = providerName;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
provider = require(modulePath);
|
||||
} catch (err) {
|
||||
throw new Error(`Could not load email provider "${providerName}".`);
|
||||
}
|
||||
|
||||
return provider.init(emailConfig.providerOptions, emailConfig.settings);
|
||||
};
|
||||
|
||||
export const bootstrap = async ({ strapi }: { strapi: Strapi }) => {
|
||||
const emailConfig: EmailConfig = strapi.config.get('plugin.email');
|
||||
strapi.plugin('email').provider = createProvider(emailConfig);
|
||||
|
||||
// Add permissions
|
||||
const actions = [
|
||||
{
|
||||
section: 'settings',
|
||||
category: 'email',
|
||||
displayName: 'Access the Email Settings page',
|
||||
uid: 'settings.read',
|
||||
pluginName: 'email',
|
||||
},
|
||||
];
|
||||
|
||||
await strapi.admin!.services.permission.actionProvider.registerMany(actions);
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
import type { StrapiConfig } from './types';
|
||||
|
||||
module.exports = {
|
||||
export const config: StrapiConfig = {
|
||||
default: {
|
||||
provider: 'sendmail',
|
||||
providerOptions: {},
|
77
packages/core/email/server/src/controllers/email.ts
Normal file
77
packages/core/email/server/src/controllers/email.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import { pick } from 'lodash/fp';
|
||||
import { errors } from '@strapi/utils';
|
||||
|
||||
import type Koa from 'koa';
|
||||
import type { EmailConfig, SendOptions } from '../types';
|
||||
|
||||
const { ApplicationError } = errors;
|
||||
|
||||
/**
|
||||
* Email.js controller
|
||||
*
|
||||
* @description: A set of functions called "actions" of the `email` plugin.
|
||||
*/
|
||||
const emailController = {
|
||||
async send(ctx: Koa.Context) {
|
||||
const options: SendOptions = ctx.request.body;
|
||||
|
||||
try {
|
||||
await strapi.plugin('email').service('email').send(options);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
if ('statusCode' in error && error.statusCode === 400) {
|
||||
throw new ApplicationError(error.message);
|
||||
} else {
|
||||
throw new Error(`Couldn't send email: ${error.message}.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send 200 `ok`
|
||||
ctx.send({});
|
||||
},
|
||||
|
||||
async test(ctx: Koa.Context) {
|
||||
const { to } = ctx.request.body;
|
||||
|
||||
if (!to) {
|
||||
throw new ApplicationError('No recipient(s) are given');
|
||||
}
|
||||
|
||||
const email: SendOptions = {
|
||||
to,
|
||||
subject: `Strapi test mail to: ${to}`,
|
||||
text: `Great! You have correctly configured the Strapi email plugin with the ${strapi.config.get(
|
||||
'plugin.email.provider'
|
||||
)} provider. \r\nFor documentation on how to use the email plugin checkout: https://docs.strapi.io/developer-docs/latest/plugins/email.html`,
|
||||
};
|
||||
|
||||
try {
|
||||
await strapi.plugin('email').service('email').send(email);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
if ('statusCode' in error && error.statusCode === 400) {
|
||||
throw new ApplicationError(error.message);
|
||||
} else {
|
||||
throw new Error(`Couldn't send test email: ${error.message}.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send 200 `ok`
|
||||
ctx.send({});
|
||||
},
|
||||
|
||||
async getSettings(ctx: Koa.Context) {
|
||||
const config: EmailConfig = strapi.plugin('email').service('email').getProviderSettings();
|
||||
|
||||
ctx.send({
|
||||
config: pick(
|
||||
['provider', 'settings.defaultFrom', 'settings.defaultReplyTo', 'settings.testAddress'],
|
||||
config
|
||||
),
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default emailController;
|
3
packages/core/email/server/src/controllers/index.ts
Normal file
3
packages/core/email/server/src/controllers/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import email from './email';
|
||||
|
||||
export const controllers = { email };
|
13
packages/core/email/server/src/index.ts
Normal file
13
packages/core/email/server/src/index.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { bootstrap } from './bootstrap';
|
||||
import { services } from './services';
|
||||
import { routes } from './routes';
|
||||
import { controllers } from './controllers';
|
||||
import { config } from './config';
|
||||
|
||||
export default {
|
||||
bootstrap,
|
||||
services,
|
||||
routes,
|
||||
controllers,
|
||||
config,
|
||||
};
|
@ -1,6 +1,4 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
type: 'admin',
|
||||
routes: [
|
||||
{
|
@ -1,6 +1,4 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
type: 'content-api',
|
||||
routes: [
|
||||
{
|
7
packages/core/email/server/src/routes/index.ts
Normal file
7
packages/core/email/server/src/routes/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import admin from './admin';
|
||||
import contentApi from './content-api';
|
||||
|
||||
export const routes = {
|
||||
admin,
|
||||
'content-api': contentApi,
|
||||
};
|
@ -1,18 +1,19 @@
|
||||
'use strict';
|
||||
import * as _ from 'lodash';
|
||||
import { keysDeep, template } from '@strapi/utils';
|
||||
|
||||
const _ = require('lodash');
|
||||
const {
|
||||
template: { createStrictInterpolationRegExp },
|
||||
keysDeep,
|
||||
} = require('@strapi/utils');
|
||||
import type {
|
||||
EmailConfig,
|
||||
EmailOptions,
|
||||
EmailTemplate,
|
||||
EmailTemplateData,
|
||||
SendOptions,
|
||||
} from '../types';
|
||||
|
||||
const getProviderSettings = () => {
|
||||
return strapi.config.get('plugin.email');
|
||||
};
|
||||
const { createStrictInterpolationRegExp } = template;
|
||||
|
||||
const send = async (options) => {
|
||||
return strapi.plugin('email').provider.send(options);
|
||||
};
|
||||
const getProviderSettings = (): EmailConfig => strapi.config.get('plugin.email');
|
||||
|
||||
const send = async (options: SendOptions) => strapi.plugin('email').provider.send(options);
|
||||
|
||||
/**
|
||||
* fill subject, text and html using lodash template
|
||||
@ -21,9 +22,14 @@ const send = async (options) => {
|
||||
* @param {object} data - data used to fill the template
|
||||
* @returns {{ subject, text, subject }}
|
||||
*/
|
||||
const sendTemplatedEmail = (emailOptions = {}, emailTemplate = {}, data = {}) => {
|
||||
const sendTemplatedEmail = (
|
||||
emailOptions: EmailOptions,
|
||||
emailTemplate: EmailTemplate,
|
||||
data: EmailTemplateData
|
||||
) => {
|
||||
const attributes = ['subject', 'text', 'html'];
|
||||
const missingAttributes = _.difference(attributes, Object.keys(emailTemplate));
|
||||
|
||||
if (missingAttributes.length > 0) {
|
||||
throw new Error(
|
||||
`Following attributes are missing from your email template : ${missingAttributes.join(', ')}`
|
||||
@ -39,8 +45,6 @@ const sendTemplatedEmail = (emailOptions = {}, emailTemplate = {}, data = {}) =>
|
||||
? Object.assign(compiled, {
|
||||
[attribute]: _.template(emailTemplate[attribute], {
|
||||
interpolate,
|
||||
evaluate: false,
|
||||
escape: false,
|
||||
})(data),
|
||||
})
|
||||
: compiled,
|
||||
@ -50,8 +54,10 @@ const sendTemplatedEmail = (emailOptions = {}, emailTemplate = {}, data = {}) =>
|
||||
return strapi.plugin('email').provider.send({ ...emailOptions, ...templatedAttributes });
|
||||
};
|
||||
|
||||
module.exports = () => ({
|
||||
const emailService = () => ({
|
||||
getProviderSettings,
|
||||
send,
|
||||
sendTemplatedEmail,
|
||||
});
|
||||
|
||||
export default emailService;
|
3
packages/core/email/server/src/services/index.ts
Normal file
3
packages/core/email/server/src/services/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import email from './email';
|
||||
|
||||
export const services = { email };
|
43
packages/core/email/server/src/types.ts
Normal file
43
packages/core/email/server/src/types.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import type { Plugin } from '@strapi/types';
|
||||
|
||||
export interface EmailConfig extends Record<string, unknown> {
|
||||
provider: string;
|
||||
providerOptions?: object;
|
||||
settings?: {
|
||||
defaultFrom?: string;
|
||||
};
|
||||
}
|
||||
|
||||
type LoadedPluginConfig = Plugin.LoadedPlugin['config'];
|
||||
|
||||
export interface StrapiConfig extends LoadedPluginConfig {
|
||||
default: EmailConfig;
|
||||
}
|
||||
|
||||
export interface EmailTemplateData {
|
||||
url?: string;
|
||||
user?: {
|
||||
email: string;
|
||||
firstname: string;
|
||||
lastname: string;
|
||||
username: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface EmailOptions {
|
||||
from?: string;
|
||||
to: string;
|
||||
cc?: string;
|
||||
bcc?: string;
|
||||
replyTo?: string;
|
||||
[key: string]: string | undefined; // to allow additional template attributes if needed
|
||||
}
|
||||
|
||||
export interface EmailTemplate {
|
||||
subject: string;
|
||||
text: string;
|
||||
html?: string;
|
||||
[key: string]: string | undefined; // to allow additional template attributes if needed
|
||||
}
|
||||
|
||||
export type SendOptions = EmailOptions & EmailTemplate;
|
9
packages/core/email/server/tsconfig.build.json
Normal file
9
packages/core/email/server/tsconfig.build.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": ["./src"],
|
||||
"exclude": ["./src/**/*.test.ts"],
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist/server"
|
||||
}
|
||||
}
|
@ -1,5 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
8
packages/core/email/server/tsconfig.json
Normal file
8
packages/core/email/server/tsconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "tsconfig/base.json",
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"],
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": true
|
||||
},
|
||||
}
|
11
packages/core/email/shared/types.ts
Normal file
11
packages/core/email/shared/types.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export interface EmailSettings {
|
||||
config: ConfigSettings;
|
||||
}
|
||||
|
||||
export interface ConfigSettings {
|
||||
provider: string;
|
||||
settings: {
|
||||
defaultFrom: string;
|
||||
defaultReplyTo: string;
|
||||
};
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = require('./admin/src').default;
|
@ -1,17 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
const bootstrap = require('./server/bootstrap');
|
||||
const services = require('./server/services');
|
||||
const routes = require('./server/routes');
|
||||
const controllers = require('./server/controllers');
|
||||
const config = require('./server/config');
|
||||
|
||||
module.exports = () => {
|
||||
return {
|
||||
bootstrap,
|
||||
config,
|
||||
routes,
|
||||
controllers,
|
||||
services,
|
||||
};
|
||||
};
|
||||
module.exports = require('./dist/server');
|
||||
|
8
packages/core/email/tsconfig.build.json
Normal file
8
packages/core/email/tsconfig.build.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "tsconfig/client.json",
|
||||
"include": ["./admin", "./shared", "./server"],
|
||||
"compilerOptions": {
|
||||
"declarationDir": "./dist",
|
||||
"outDir": "./dist"
|
||||
}
|
||||
}
|
@ -12,12 +12,15 @@ import type { domain } from '@strapi/permissions';
|
||||
|
||||
type Permission = domain.permission.Permission;
|
||||
|
||||
export interface CheckPagePermissions {
|
||||
export interface CheckPagePermissionsProps {
|
||||
children: React.ReactNode;
|
||||
permissions?: Permission[];
|
||||
}
|
||||
|
||||
const CheckPagePermissions = ({ permissions = [], children }: CheckPagePermissions) => {
|
||||
const CheckPagePermissions = ({
|
||||
permissions = [],
|
||||
children,
|
||||
}: CheckPagePermissionsProps): React.JSX.Element => {
|
||||
const abortController = new AbortController();
|
||||
const { signal } = abortController;
|
||||
const { allPermissions } = useRBACProvider();
|
||||
@ -73,7 +76,7 @@ const CheckPagePermissions = ({ permissions = [], children }: CheckPagePermissio
|
||||
return <Redirect to="/" />;
|
||||
}
|
||||
|
||||
return children;
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export { CheckPagePermissions };
|
||||
|
@ -11,12 +11,12 @@ type Permission = domain.permission.Permission;
|
||||
// NOTE: this component is very similar to the CheckPagePermissions
|
||||
// except that it does not handle redirections nor loading state
|
||||
|
||||
export interface CheckPagePermissions {
|
||||
export interface CheckPermissionsProps {
|
||||
children: React.ReactNode;
|
||||
permissions?: Permission[];
|
||||
}
|
||||
|
||||
const CheckPermissions = ({ permissions = [], children }: CheckPagePermissions) => {
|
||||
const CheckPermissions = ({ permissions = [], children }: CheckPermissionsProps) => {
|
||||
const { allPermissions } = useRBACProvider();
|
||||
const toggleNotification = useNotification();
|
||||
const [state, setState] = React.useState({ isLoading: true, canAccess: false });
|
||||
@ -69,7 +69,7 @@ const CheckPermissions = ({ permissions = [], children }: CheckPagePermissions)
|
||||
return null;
|
||||
}
|
||||
|
||||
return children;
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export { CheckPermissions };
|
||||
|
@ -2,30 +2,14 @@ import { join } from 'path';
|
||||
import fse from 'fs-extra';
|
||||
import { defaultsDeep, defaults, getOr, get } from 'lodash/fp';
|
||||
import { env } from '@strapi/utils';
|
||||
import type { Strapi, Common, Schema } from '@strapi/types';
|
||||
import type { Strapi, Plugin } from '@strapi/types';
|
||||
import { loadFile } from '../../app-configuration/load-config-file';
|
||||
import loadFiles from '../../../load/load-files';
|
||||
import { getEnabledPlugins } from './get-enabled-plugins';
|
||||
import { getUserPluginsConfig } from './get-user-plugins-config';
|
||||
|
||||
type LoadedPlugin = {
|
||||
config: {
|
||||
default: Record<string, unknown> | ((opts: { env: typeof env }) => Record<string, unknown>);
|
||||
validator: (config: Record<string, unknown>) => void;
|
||||
};
|
||||
bootstrap: ({ strapi }: { strapi: Strapi }) => void | Promise<void>;
|
||||
destroy: ({ strapi }: { strapi: Strapi }) => void | Promise<void>;
|
||||
register: ({ strapi }: { strapi: Strapi }) => void | Promise<void>;
|
||||
routes: Record<string, Common.Router>;
|
||||
controllers: Record<string, Common.Controller>;
|
||||
services: Record<string, Common.Service>;
|
||||
policies: Record<string, Common.Policy>;
|
||||
middlewares: Record<string, Common.Middleware>;
|
||||
contentTypes: Record<string, { schema: Schema.ContentType }>;
|
||||
};
|
||||
|
||||
interface Plugins {
|
||||
[key: string]: LoadedPlugin;
|
||||
[key: string]: Plugin.LoadedPlugin;
|
||||
}
|
||||
|
||||
const defaultPlugin = {
|
||||
|
@ -3,5 +3,5 @@
|
||||
export interface AdminInput {
|
||||
register: unknown;
|
||||
bootstrap: unknown;
|
||||
registerTrads: unknown;
|
||||
registerTrads: ({ locales }: { locales: string[] }) => Promise<unknown>;
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { env } from '@strapi/utils';
|
||||
|
||||
export interface Config {
|
||||
validator: () => unknown;
|
||||
default: object | (() => object);
|
||||
validator: (config: Record<string, unknown>) => void;
|
||||
default: Record<string, unknown> | ((opts: { env: typeof env }) => Record<string, unknown>);
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
import type { Common, Shared, Utils } from '../..';
|
||||
import { env } from '@strapi/utils';
|
||||
|
||||
import type { Common, Shared, Utils, Schema } from '../..';
|
||||
import type { Strapi } from '../../..';
|
||||
|
||||
export type IsEnabled<
|
||||
TName extends keyof any,
|
||||
@ -15,4 +18,20 @@ export type IsEnabled<
|
||||
: false
|
||||
: false;
|
||||
|
||||
export type LoadedPlugin = {
|
||||
config: {
|
||||
default: Record<string, unknown> | ((opts: { env: typeof env }) => Record<string, unknown>);
|
||||
validator: (config: Record<string, unknown>) => void;
|
||||
};
|
||||
bootstrap: ({ strapi }: { strapi: Strapi }) => void | Promise<void>;
|
||||
destroy: ({ strapi }: { strapi: Strapi }) => void | Promise<void>;
|
||||
register: ({ strapi }: { strapi: Strapi }) => void | Promise<void>;
|
||||
routes: Record<string, Common.Router>;
|
||||
controllers: Record<string, Common.Controller>;
|
||||
services: Record<string, Common.Service>;
|
||||
policies: Record<string, Common.Policy>;
|
||||
middlewares: Record<string, Common.Middleware>;
|
||||
contentTypes: Record<string, { schema: Schema.ContentType }>;
|
||||
};
|
||||
|
||||
export * as Config from './config';
|
||||
|
@ -7934,9 +7934,14 @@ __metadata:
|
||||
"@strapi/design-system": 1.12.0
|
||||
"@strapi/helper-plugin": 4.14.4
|
||||
"@strapi/icons": 1.12.0
|
||||
"@strapi/pack-up": "workspace:*"
|
||||
"@strapi/provider-email-sendmail": 4.14.4
|
||||
"@strapi/types": "workspace:*"
|
||||
"@strapi/utils": 4.14.4
|
||||
"@testing-library/react": 14.0.0
|
||||
"@types/koa": 2.13.4
|
||||
"@types/lodash": ^4.14.191
|
||||
koa: 2.13.4
|
||||
lodash: 4.17.21
|
||||
msw: 1.3.0
|
||||
prop-types: ^15.8.1
|
||||
@ -7948,6 +7953,7 @@ __metadata:
|
||||
styled-components: 5.3.3
|
||||
yup: 0.32.9
|
||||
peerDependencies:
|
||||
koa: 2.13.4
|
||||
react: ^17.0.0 || ^18.0.0
|
||||
react-dom: ^17.0.0 || ^18.0.0
|
||||
react-router-dom: 5.3.4
|
||||
|
Loading…
x
Reference in New Issue
Block a user