From 03cddfd2da64dd7a9524c60bd271c467da0e8348 Mon Sep 17 00:00:00 2001 From: gabriellsh <40830821+gabriellsh@users.noreply.github.com> Date: Wed, 15 Apr 2020 21:04:19 -0300 Subject: [PATCH] [IMPROVE] Administration -> Mailer Rewrite to React. (#17191) --- app/mail-messages/client/router.js | 12 --- app/mail-messages/client/views/index.js | 2 - app/mail-messages/client/views/mailer.html | 62 ------------ app/mail-messages/client/views/mailer.js | 47 --------- .../client/components/mailer/Mailer.js | 99 +++++++++++++++++++ .../components/mailer/Mailer.stories.js | 11 +++ .../client/components/mailer/MailerRoute.js | 37 +++++++ app/ui-admin/client/routes.js | 5 + app/utils/lib/isJSON.js | 7 ++ packages/rocketchat-i18n/i18n/en.i18n.json | 1 + 10 files changed, 160 insertions(+), 123 deletions(-) delete mode 100644 app/mail-messages/client/views/mailer.html delete mode 100644 app/mail-messages/client/views/mailer.js create mode 100644 app/ui-admin/client/components/mailer/Mailer.js create mode 100644 app/ui-admin/client/components/mailer/Mailer.stories.js create mode 100644 app/ui-admin/client/components/mailer/MailerRoute.js create mode 100644 app/utils/lib/isJSON.js diff --git a/app/mail-messages/client/router.js b/app/mail-messages/client/router.js index 93e982b4ea5..73bb53fac23 100644 --- a/app/mail-messages/client/router.js +++ b/app/mail-messages/client/router.js @@ -2,18 +2,6 @@ import { Meteor } from 'meteor/meteor'; import { FlowRouter } from 'meteor/kadira:flow-router'; import { BlazeLayout } from 'meteor/kadira:blaze-layout'; -import { registerAdminRoute } from '../../ui-admin/client'; - -registerAdminRoute('/mailer', { - name: 'admin-mailer', - async action() { - await import('./views'); - return BlazeLayout.render('main', { - center: 'mailer', - }); - }, -}); - FlowRouter.route('/mailer/unsubscribe/:_id/:createdAt', { name: 'mailer-unsubscribe', async action(params) { diff --git a/app/mail-messages/client/views/index.js b/app/mail-messages/client/views/index.js index 55bb6a5d7a7..81a1668b492 100644 --- a/app/mail-messages/client/views/index.js +++ b/app/mail-messages/client/views/index.js @@ -1,4 +1,2 @@ -import './mailer.html'; -import './mailer'; import './mailerUnsubscribe.html'; import './mailerUnsubscribe'; diff --git a/app/mail-messages/client/views/mailer.html b/app/mail-messages/client/views/mailer.html deleted file mode 100644 index 905edccd9d2..00000000000 --- a/app/mail-messages/client/views/mailer.html +++ /dev/null @@ -1,62 +0,0 @@ - diff --git a/app/mail-messages/client/views/mailer.js b/app/mail-messages/client/views/mailer.js deleted file mode 100644 index 673681f422d..00000000000 --- a/app/mail-messages/client/views/mailer.js +++ /dev/null @@ -1,47 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { Template } from 'meteor/templating'; -import { Tracker } from 'meteor/tracker'; -import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; -import toastr from 'toastr'; - -import { settings } from '../../../settings'; -import { handleError } from '../../../utils'; -import { SideNav } from '../../../ui-utils/client'; - -Template.mailer.helpers({ - fromEmail() { - return settings.get('From_Email'); - }, -}); - -Template.mailer.events({ - 'click .send'(e, t) { - e.preventDefault(); - const from = $(t.find('[name=from]')).val(); - const subject = $(t.find('[name=subject]')).val(); - const body = $(t.find('[name=body]')).val(); - const dryrun = $(t.find('[name=dryrun]:checked')).val(); - const query = $(t.find('[name=query]')).val(); - if (!from) { - toastr.error(TAPi18n.__('error-invalid-from-address')); - return; - } - if (body.indexOf('[unsubscribe]') === -1) { - toastr.error(TAPi18n.__('error-missing-unsubscribe-link')); - return; - } - return Meteor.call('Mailer.sendMail', from, subject, body, dryrun, query, function(err) { - if (err) { - return handleError(err); - } - return toastr.success(TAPi18n.__('The_emails_are_being_sent')); - }); - }, -}); - -Template.mailer.onRendered(() => { - Tracker.afterFlush(() => { - SideNav.setFlex('adminFlex'); - SideNav.openFlex(); - }); -}); diff --git a/app/ui-admin/client/components/mailer/Mailer.js b/app/ui-admin/client/components/mailer/Mailer.js new file mode 100644 index 00000000000..bb0907a74ff --- /dev/null +++ b/app/ui-admin/client/components/mailer/Mailer.js @@ -0,0 +1,99 @@ +import React, { useState } from 'react'; +import { TextInput, TextAreaInput, Field, FieldGroup, CheckBox, Button, Icon, ButtonGroup } from '@rocket.chat/fuselage'; + +import { useTranslation } from '../../../../../client/contexts/TranslationContext'; +import { Page } from '../../../../../client/components/basic/Page'; +import { isEmail } from '../../../../utils/lib/isEmail.js'; +import { isJSON } from '../../../../utils/lib/isJSON.js'; + +export function Mailer({ sendMail = () => {}, ...props }) { + const t = useTranslation(); + + const [fromEmail, setFromEmail] = useState({ value: '', error: false }); + const [dryRun, setDryRun] = useState(false); + const [query, setQuery] = useState({ value: '', error: false }); + const [subject, setSubject] = useState(''); + const [emailBody, setEmailBody] = useState(''); + + return + + + + + {t('From')} + + { + setFromEmail({ + value: e.currentTarget.value, + error: !isEmail(e.currentTarget.value), + }); + }} + /> + + + + + setDryRun(!dryRun)} + /> + + {t('Dry_run')} + + + {t('Dry_run_description')} + + + {t('Query')} + + { + setQuery({ + value: e.currentTarget.value, + error: e.currentTarget.value && !isJSON(e.currentTarget.value), + }); + }} + /> + + {t('Query_description')} + + + {t('Subject')} + + { + setSubject(e.currentTarget.value); + }} + /> + + + + {t('Email_body')} + + setEmailBody(e.currentTarget.value)} + /> + + + + + + + + + ; +} diff --git a/app/ui-admin/client/components/mailer/Mailer.stories.js b/app/ui-admin/client/components/mailer/Mailer.stories.js new file mode 100644 index 00000000000..b86f8ca5d59 --- /dev/null +++ b/app/ui-admin/client/components/mailer/Mailer.stories.js @@ -0,0 +1,11 @@ +import React from 'react'; + +import { Mailer } from './Mailer'; + +export default { + title: 'admin/pages/mailer', + component: Mailer, +}; + +export const _default = () => + ; diff --git a/app/ui-admin/client/components/mailer/MailerRoute.js b/app/ui-admin/client/components/mailer/MailerRoute.js new file mode 100644 index 00000000000..366ed51aa98 --- /dev/null +++ b/app/ui-admin/client/components/mailer/MailerRoute.js @@ -0,0 +1,37 @@ +import React from 'react'; +import toastr from 'toastr'; + +import { usePermission } from '../../../../../client/contexts/AuthorizationContext'; +import { useMethod } from '../../../../../client/contexts/ServerContext'; +import { useTranslation } from '../../../../../client/contexts/TranslationContext'; +import { Mailer } from './Mailer'; +import { NotAuthorizedPage } from '../settings/NotAuthorizedPage'; + + +const useSendMail = () => { + const meteorSendMail = useMethod('Mailer.sendMail'); + const t = useTranslation(); + return ({ fromEmail, subject, emailBody, dryRun, query }) => { + if (query.error) { + toastr.error(t('Query_is_not_valid_JSON')); + return; + } + if (fromEmail.error || fromEmail.length < 1) { + toastr.error(t('error-invalid-from-address')); + return; + } + if (emailBody.indexOf('[unsubscribe]') === -1) { + toastr.error(t('error-missing-unsubscribe-link')); + return; + } + meteorSendMail(fromEmail.value, subject, emailBody, dryRun, query.value); + toastr.success(t('The_emails_are_being_sent')); + }; +}; + +export default function MailerRoute(props) { + const canAccessMailer = usePermission('access-mailer'); + const sendMail = useSendMail(); + + return canAccessMailer ? : ; +} diff --git a/app/ui-admin/client/routes.js b/app/ui-admin/client/routes.js index 910e3386300..b30d9cb3a40 100644 --- a/app/ui-admin/client/routes.js +++ b/app/ui-admin/client/routes.js @@ -38,6 +38,11 @@ registerAdminRoute('/info', { lazyRouteComponent: () => import('./components/info/InformationRoute'), }); +registerAdminRoute('/mailer', { + name: 'admin-mailer', + lazyRouteComponent: () => import('./components/mailer/MailerRoute'), +}); + registerAdminRoute('/users', { name: 'admin-users', action: async () => { diff --git a/app/utils/lib/isJSON.js b/app/utils/lib/isJSON.js new file mode 100644 index 00000000000..ce5547a312b --- /dev/null +++ b/app/utils/lib/isJSON.js @@ -0,0 +1,7 @@ +export const isJSON = (value) => { + try { + return !!JSON.parse(value); + } catch { + return false; + } +}; diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index ad60198b88d..d0854c8776a 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -2743,6 +2743,7 @@ "Purchase_for_price": "Purchase for $%s", "Query": "Query", "Query_description": "Additional conditions for determining which users to send the email to. Unsubscribed users are automatically removed from the query. It must be a valid JSON. Example: \"{\"createdAt\":{\"$gt\":{\"$date\": \"2015-01-01T00:00:00.000Z\"}}}\"", + "Query_is_not_valid_JSON": "Query is not valid JSON", "Queue": "Queue", "quote": "quote", "Quote": "Quote",