Skip to content

Commit 824f286

Browse files
authored
Merge pull request #290 from authorizerdev/feat/plain-html-editor
feat: add plain html editor for email templates
2 parents e525877 + ecefe12 commit 824f286

File tree

8 files changed

+565
-1500
lines changed

8 files changed

+565
-1500
lines changed

dashboard/package-lock.json

Lines changed: 7 additions & 1072 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dashboard/src/components/UpdateEmailTemplateModal.tsx

Lines changed: 174 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ import {
2929
Tbody,
3030
Td,
3131
Code,
32+
Radio,
33+
RadioGroup,
34+
Stack,
35+
Textarea,
3236
} from '@chakra-ui/react';
3337
import { FaPlus, FaAngleDown, FaAngleUp } from 'react-icons/fa';
3438
import { useClient } from 'urql';
@@ -38,6 +42,7 @@ import {
3842
EmailTemplateInputDataFields,
3943
emailTemplateEventNames,
4044
emailTemplateVariables,
45+
EmailTemplateEditors,
4146
} from '../constants';
4247
import { capitalizeFirstLetter } from '../utils';
4348
import { AddEmailTemplate, EditEmailTemplate } from '../graphql/mutation';
@@ -66,6 +71,8 @@ interface templateVariableDataTypes {
6671
interface emailTemplateDataType {
6772
[EmailTemplateInputDataFields.EVENT_NAME]: string;
6873
[EmailTemplateInputDataFields.SUBJECT]: string;
74+
[EmailTemplateInputDataFields.TEMPLATE]: string;
75+
[EmailTemplateInputDataFields.DESIGN]: string;
6976
}
7077

7178
interface validatorDataType {
@@ -75,6 +82,8 @@ interface validatorDataType {
7582
const initTemplateData: emailTemplateDataType = {
7683
[EmailTemplateInputDataFields.EVENT_NAME]: emailTemplateEventNames.Signup,
7784
[EmailTemplateInputDataFields.SUBJECT]: '',
85+
[EmailTemplateInputDataFields.TEMPLATE]: '',
86+
[EmailTemplateInputDataFields.DESIGN]: '',
7887
};
7988

8089
const initTemplateValidatorData: validatorDataType = {
@@ -91,6 +100,9 @@ const UpdateEmailTemplate = ({
91100
const emailEditorRef = useRef(null);
92101
const { isOpen, onOpen, onClose } = useDisclosure();
93102
const [loading, setLoading] = useState<boolean>(false);
103+
const [editor, setEditor] = useState<string>(
104+
EmailTemplateEditors.PLAIN_HTML_EDITOR,
105+
);
94106
const [templateVariables, setTemplateVariables] = useState<
95107
templateVariableDataTypes[]
96108
>([]);
@@ -107,9 +119,11 @@ const UpdateEmailTemplate = ({
107119
if (selectedTemplate) {
108120
const { design } = selectedTemplate;
109121
try {
110-
const designData = JSON.parse(design);
111-
// @ts-ignore
112-
emailEditorRef.current.editor.loadDesign(designData);
122+
if (design) {
123+
const designData = JSON.parse(design);
124+
// @ts-ignore
125+
emailEditorRef.current.editor.loadDesign(designData);
126+
}
113127
} catch (error) {
114128
console.error(error);
115129
onClose();
@@ -136,88 +150,107 @@ const UpdateEmailTemplate = ({
136150
);
137151
};
138152

153+
const updateTemplate = async (params: emailTemplateDataType) => {
154+
let res: any = {};
155+
if (
156+
view === UpdateModalViews.Edit &&
157+
selectedTemplate?.[EmailTemplateInputDataFields.ID]
158+
) {
159+
res = await client
160+
.mutation(EditEmailTemplate, {
161+
params: {
162+
...params,
163+
id: selectedTemplate[EmailTemplateInputDataFields.ID],
164+
},
165+
})
166+
.toPromise();
167+
} else {
168+
res = await client.mutation(AddEmailTemplate, { params }).toPromise();
169+
}
170+
setLoading(false);
171+
if (res.error) {
172+
toast({
173+
title: capitalizeFirstLetter(res.error.message),
174+
isClosable: true,
175+
status: 'error',
176+
position: 'bottom-right',
177+
});
178+
} else if (
179+
res.data?._add_email_template ||
180+
res.data?._update_email_template
181+
) {
182+
toast({
183+
title: capitalizeFirstLetter(
184+
res.data?._add_email_template?.message ||
185+
res.data?._update_email_template?.message,
186+
),
187+
isClosable: true,
188+
status: 'success',
189+
position: 'bottom-right',
190+
});
191+
setTemplateData({
192+
...initTemplateData,
193+
});
194+
setValidator({ ...initTemplateValidatorData });
195+
fetchEmailTemplatesData();
196+
}
197+
};
198+
139199
const saveData = async () => {
140200
if (!validateData()) return;
141201
setLoading(true);
142-
// @ts-ignore
143-
return await emailEditorRef.current.editor.exportHtml(async (data) => {
144-
const { design, html } = data;
145-
if (!html || !design) {
146-
setLoading(false);
147-
return;
148-
}
149-
const params = {
150-
[EmailTemplateInputDataFields.EVENT_NAME]:
151-
templateData[EmailTemplateInputDataFields.EVENT_NAME],
152-
[EmailTemplateInputDataFields.SUBJECT]:
153-
templateData[EmailTemplateInputDataFields.SUBJECT],
154-
[EmailTemplateInputDataFields.TEMPLATE]: html.trim(),
155-
[EmailTemplateInputDataFields.DESIGN]: JSON.stringify(design),
156-
};
157-
let res: any = {};
158-
if (
159-
view === UpdateModalViews.Edit &&
160-
selectedTemplate?.[EmailTemplateInputDataFields.ID]
161-
) {
162-
res = await client
163-
.mutation(EditEmailTemplate, {
164-
params: {
165-
...params,
166-
id: selectedTemplate[EmailTemplateInputDataFields.ID],
167-
},
168-
})
169-
.toPromise();
170-
} else {
171-
res = await client.mutation(AddEmailTemplate, { params }).toPromise();
172-
}
173-
setLoading(false);
174-
if (res.error) {
175-
toast({
176-
title: capitalizeFirstLetter(res.error.message),
177-
isClosable: true,
178-
status: 'error',
179-
position: 'bottom-right',
180-
});
181-
} else if (
182-
res.data?._add_email_template ||
183-
res.data?._update_email_template
184-
) {
185-
toast({
186-
title: capitalizeFirstLetter(
187-
res.data?._add_email_template?.message ||
188-
res.data?._update_email_template?.message,
189-
),
190-
isClosable: true,
191-
status: 'success',
192-
position: 'bottom-right',
193-
});
194-
setTemplateData({
195-
...initTemplateData,
196-
});
197-
setValidator({ ...initTemplateValidatorData });
198-
fetchEmailTemplatesData();
199-
}
200-
view === UpdateModalViews.ADD && onClose();
201-
});
202+
let params: emailTemplateDataType = {
203+
[EmailTemplateInputDataFields.EVENT_NAME]:
204+
templateData[EmailTemplateInputDataFields.EVENT_NAME],
205+
[EmailTemplateInputDataFields.SUBJECT]:
206+
templateData[EmailTemplateInputDataFields.SUBJECT],
207+
[EmailTemplateInputDataFields.TEMPLATE]:
208+
templateData[EmailTemplateInputDataFields.TEMPLATE],
209+
[EmailTemplateInputDataFields.DESIGN]: '',
210+
};
211+
if (editor === EmailTemplateEditors.UNLAYER_EDITOR) {
212+
// @ts-ignore
213+
await emailEditorRef.current.editor.exportHtml(async (data) => {
214+
const { design, html } = data;
215+
if (!html || !design) {
216+
setLoading(false);
217+
return;
218+
}
219+
params = {
220+
...params,
221+
[EmailTemplateInputDataFields.TEMPLATE]: html.trim(),
222+
[EmailTemplateInputDataFields.DESIGN]: JSON.stringify(design),
223+
};
224+
await updateTemplate(params);
225+
});
226+
} else {
227+
await updateTemplate(params);
228+
}
229+
view === UpdateModalViews.ADD && onClose();
202230
};
231+
203232
const resetData = () => {
204233
if (selectedTemplate) {
205234
setTemplateData(selectedTemplate);
206235
} else {
207236
setTemplateData({ ...initTemplateData });
208237
}
209238
};
239+
240+
// set template data if edit modal is open
210241
useEffect(() => {
211242
if (
212243
isOpen &&
213244
view === UpdateModalViews.Edit &&
214245
selectedTemplate &&
215246
Object.keys(selectedTemplate || {}).length
216247
) {
217-
const { id, created_at, template, design, ...rest } = selectedTemplate;
248+
const { id, created_at, ...rest } = selectedTemplate;
218249
setTemplateData(rest);
219250
}
220251
}, [isOpen]);
252+
253+
// set template variables
221254
useEffect(() => {
222255
const updatedTemplateVariables = Object.entries(
223256
emailTemplateVariables,
@@ -244,6 +277,51 @@ const UpdateEmailTemplate = ({
244277
setTemplateVariables(updatedTemplateVariables);
245278
}, [templateData[EmailTemplateInputDataFields.EVENT_NAME]]);
246279

280+
// change editor
281+
useEffect(() => {
282+
if (isOpen && selectedTemplate) {
283+
const { design } = selectedTemplate;
284+
if (design) {
285+
setEditor(EmailTemplateEditors.UNLAYER_EDITOR);
286+
} else {
287+
setEditor(EmailTemplateEditors.PLAIN_HTML_EDITOR);
288+
}
289+
}
290+
}, [isOpen, selectedTemplate]);
291+
292+
// reset fields when editor is changed
293+
useEffect(() => {
294+
if (selectedTemplate?.design) {
295+
if (editor === EmailTemplateEditors.UNLAYER_EDITOR) {
296+
setTemplateData({
297+
...templateData,
298+
[EmailTemplateInputDataFields.TEMPLATE]: selectedTemplate.template,
299+
[EmailTemplateInputDataFields.DESIGN]: selectedTemplate.design,
300+
});
301+
} else {
302+
setTemplateData({
303+
...templateData,
304+
[EmailTemplateInputDataFields.TEMPLATE]: '',
305+
[EmailTemplateInputDataFields.DESIGN]: '',
306+
});
307+
}
308+
} else if (selectedTemplate?.template) {
309+
if (editor === EmailTemplateEditors.UNLAYER_EDITOR) {
310+
setTemplateData({
311+
...templateData,
312+
[EmailTemplateInputDataFields.TEMPLATE]: '',
313+
[EmailTemplateInputDataFields.DESIGN]: '',
314+
});
315+
} else {
316+
setTemplateData({
317+
...templateData,
318+
[EmailTemplateInputDataFields.TEMPLATE]: selectedTemplate?.template,
319+
[EmailTemplateInputDataFields.DESIGN]: '',
320+
});
321+
}
322+
}
323+
}, [editor]);
324+
247325
return (
248326
<>
249327
{view === UpdateModalViews.ADD ? (
@@ -414,7 +492,22 @@ const UpdateEmailTemplate = ({
414492
alignItems="center"
415493
marginBottom="2%"
416494
>
417-
Template Body
495+
<Flex flex="1">Template Body</Flex>
496+
<Flex flex="3">
497+
<RadioGroup
498+
onChange={(value) => setEditor(value)}
499+
value={editor}
500+
>
501+
<Stack direction="row" spacing="50px">
502+
<Radio value={EmailTemplateEditors.PLAIN_HTML_EDITOR}>
503+
Plain HTML
504+
</Radio>
505+
<Radio value={EmailTemplateEditors.UNLAYER_EDITOR}>
506+
Unlayer Editor
507+
</Radio>
508+
</Stack>
509+
</RadioGroup>
510+
</Flex>
418511
</Flex>
419512
<Flex
420513
width="100%"
@@ -423,7 +516,22 @@ const UpdateEmailTemplate = ({
423516
border="1px solid"
424517
borderColor="gray.200"
425518
>
426-
<EmailEditor ref={emailEditorRef} onReady={onReady} />
519+
{editor === EmailTemplateEditors.UNLAYER_EDITOR ? (
520+
<EmailEditor ref={emailEditorRef} onReady={onReady} />
521+
) : (
522+
<Textarea
523+
value={templateData.template}
524+
onChange={(e) => {
525+
setTemplateData({
526+
...templateData,
527+
[EmailTemplateInputDataFields.TEMPLATE]: e.target.value,
528+
});
529+
}}
530+
placeholder="Template HTML"
531+
border="0"
532+
height="500px"
533+
/>
534+
)}
427535
</Flex>
428536
</Flex>
429537
</ModalBody>

dashboard/src/constants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,3 +337,8 @@ export const webhookPayloadExample: string = `{
337337
},
338338
"auth_recipe":"google"
339339
}`;
340+
341+
export enum EmailTemplateEditors {
342+
UNLAYER_EDITOR = 'unlayer_editor',
343+
PLAIN_HTML_EDITOR = 'plain_html_editor',
344+
}

0 commit comments

Comments
 (0)