From 139cfb74988bad0ea06ce16bf0ea3a125e77976e Mon Sep 17 00:00:00 2001 From: Collins Ikechukwu Date: Sat, 30 Nov 2024 23:32:15 +0100 Subject: [PATCH 1/9] feat(types): add optional id field to TemplateTypes interface --- types/interface.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/types/interface.ts b/types/interface.ts index f568c26..db7e680 100644 --- a/types/interface.ts +++ b/types/interface.ts @@ -35,6 +35,7 @@ export interface ContactTypes { } export interface TemplateTypes { + id?: any; name: string; content: string; } \ No newline at end of file From 3897a46ffb8433b1e8bc7e6326e3f44fbea068f1 Mon Sep 17 00:00:00 2001 From: Collins Ikechukwu Date: Sat, 30 Nov 2024 23:32:55 +0100 Subject: [PATCH 2/9] chore: add dompurify dependency to package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index a1afe06..dd78771 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "axios": "^1.7.7", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "dompurify": "^3.2.2", "framer-motion": "^11.11.9", "input-otp": "^1.4.1", "js-cookie": "^3.0.5", From 18ded48f22c39e5dda70755d91f9a1872def20cf Mon Sep 17 00:00:00 2001 From: Collins Ikechukwu Date: Sat, 30 Nov 2024 23:33:05 +0100 Subject: [PATCH 3/9] chore: update yarn.lock with new package versions --- yarn.lock | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/yarn.lock b/yarn.lock index df339f7..5df21c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -743,6 +743,11 @@ "@types/prop-types" "*" csstype "^3.0.2" +"@types/trusted-types@^2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11" + integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw== + "@typescript-eslint/eslint-plugin@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0": version "8.10.0" resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.10.0.tgz" @@ -1402,6 +1407,13 @@ dom-helpers@^5.0.1: "@babel/runtime" "^7.8.7" csstype "^3.0.2" +dompurify@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.2.2.tgz#6c0518745e81686c74a684f5af1e5613e7cc0246" + integrity sha512-YMM+erhdZ2nkZ4fTNRTSI94mb7VG7uVF5vj5Zde7tImgnhZE3R6YW/IACGIHb2ux+QkEXMhe591N+5jWOmL4Zw== + optionalDependencies: + "@types/trusted-types" "^2.0.7" + eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz" From 0c88be8f8ef358595b148a22c66f7cab47d375d2 Mon Sep 17 00:00:00 2001 From: Collins Ikechukwu Date: Sat, 30 Nov 2024 23:33:24 +0100 Subject: [PATCH 4/9] feat(templates): add template listing and search functionality to TemplatesPage --- app/dashboard/templates/all/page.tsx | 182 ++++++++++++++++++++++++++- 1 file changed, 176 insertions(+), 6 deletions(-) diff --git a/app/dashboard/templates/all/page.tsx b/app/dashboard/templates/all/page.tsx index 03bea3f..dac3d87 100644 --- a/app/dashboard/templates/all/page.tsx +++ b/app/dashboard/templates/all/page.tsx @@ -1,9 +1,179 @@ -import React from 'react' +"use client" + +import { useEffect, useState } from "react" +import { motion } from "framer-motion" +import { Plus, Search, Bell } from "lucide-react" +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardFooter, CardHeader } from "@/components/ui/card" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { Input } from "@/components/ui/input" +import { useAuthStore } from "@/store/authStore" +import { useTemplateStore } from "@/store/templateStore" + +export default function TemplatesPage() { + const { user } = useAuthStore(); + const { templates, listAllTemplates, loading, error } = useTemplateStore() + + useEffect(() => { + async function fetchTemplates() { + try { + await listAllTemplates(); + } catch (error) { + console.error("Error fetching templates:", error); + } + } + fetchTemplates(); + }, []) -const page = () => { return ( -
set
- ) -} +
+
+

Templates

+ +
+ +
+ + +
+ + {loading ? ( +

Loading templates...

+ ) : error ? ( +

Error: {error}

+ ) : templates && templates.length > 0 ? ( + + {templates.map((template) => ( + + +

{template.name}

+
+ +

{template.content}

+
+
+ ))} +
+ ) : ( +

No templates found.

+ )} +
+ ); + -export default page \ No newline at end of file + // return ( + //
+ //
+ //
+ // + // + // Logo + // + //
+ //
+ // + // + //
+ //
+ // + // + // + // {user?.first_name?.charAt(0)} + // + //
+ //
+ + //
+ //
+ // Create email + // / + // use template + //
+ + //
+ // + + // + // + //
+ + // + // + // + //
+ // + //
+ //

Add new template

+ //
+ //
+ + // {templates.map((template) => ( + // + // + //

{template.title}

+ //
+ // + //
+ //

{template.description}

+ //
+ //
+ // + // + // {template.type && `Type: ${template.type}`} + // + // + // ))} + // + //
+ //
+ // ) +} From 14b48621a17d4a5fa48820003c154e5688086277 Mon Sep 17 00:00:00 2001 From: Collins Ikechukwu Date: Sat, 30 Nov 2024 23:33:50 +0100 Subject: [PATCH 5/9] refactor: format code and add new features to EmailTemplateEditor - format code with proper indentation and semicolons - add useTemplateStore and useRouter hooks - implement template saving with error handling and navigation - add undo and redo functionality with buttons - improve image upload handling and variable addition - update card descriptions and button layouts --- app/dashboard/templates/create/page.tsx | 391 +++++++++++++++--------- 1 file changed, 240 insertions(+), 151 deletions(-) diff --git a/app/dashboard/templates/create/page.tsx b/app/dashboard/templates/create/page.tsx index 85f9efd..5ae6d55 100644 --- a/app/dashboard/templates/create/page.tsx +++ b/app/dashboard/templates/create/page.tsx @@ -1,184 +1,238 @@ -"use client" +"use client"; -import { useState, useRef, useCallback, useEffect } from "react" -import { motion, AnimatePresence } from "framer-motion" -import EmailEditor, { EditorRef, EmailEditorProps } from 'react-email-editor' -import { Button } from "@/components/ui/button" -import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog" -import { Label } from "@/components/ui/label" -import { Input } from "@/components/ui/input" -import { Textarea } from "@/components/ui/textarea" -import { Toast } from "@/components/ui/toast" -import { Loader2, Save, Eye, Upload, Download, Undo, Redo, Image as ImageIcon, Variable } from "lucide-react" -import { useToast } from "@/hooks/use-toast" +import { useState, useRef, useCallback, useEffect } from "react"; +import { motion, AnimatePresence } from "framer-motion"; +import EmailEditor, { EditorRef, EmailEditorProps } from "react-email-editor"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Label } from "@/components/ui/label"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { Toast } from "@/components/ui/toast"; +import { + Loader2, + Save, + Eye, + Upload, + Download, + Undo, + Redo, + Image as ImageIcon, + Variable, +} from "lucide-react"; +import { useToast } from "@/hooks/use-toast"; +import { useTemplateStore } from "@/store/templateStore"; +import { useRouter } from "next/navigation"; export default function EmailTemplateEditor() { - const emailEditorRef = useRef(null) - const [templateName, setTemplateName] = useState("") - const [previewMode, setPreviewMode] = useState(false) - const [jsonData, setJsonData] = useState("") - const [isLoading, setIsLoading] = useState(true) - const [canUndo, setCanUndo] = useState(false) - const [canRedo, setCanRedo] = useState(false) - const [showVariableDialog, setShowVariableDialog] = useState(false) - const [variableName, setVariableName] = useState("") - const [variableDescription, setVariableDescription] = useState("") - const [variables, setVariables] = useState<{ name: string; description: string }[]>([]) - const { toast } = useToast() + const emailEditorRef = useRef(null); + const [templateName, setTemplateName] = useState(""); + const [previewMode, setPreviewMode] = useState(false); + const [jsonData, setJsonData] = useState(""); + const [isLoading, setIsLoading] = useState(true); + const [canUndo, setCanUndo] = useState(false); + const [canRedo, setCanRedo] = useState(false); + const [showVariableDialog, setShowVariableDialog] = useState(false); + const [variableName, setVariableName] = useState(""); + const [variableDescription, setVariableDescription] = useState(""); + const [variables, setVariables] = useState< + { name: string; description: string }[] + >([]); + const router = useRouter() + const { toast } = useToast(); + const { templates, saveTemplate, listAllTemplates, loading } = + useTemplateStore(); - const onReady: EmailEditorProps['onReady'] = useCallback((unlayer: any) => { - setIsLoading(false) - unlayer.addEventListener('design:updated', () => { - setCanUndo(unlayer.isUndoable()) - setCanRedo(unlayer.isRedoable()) - }) + const onReady: EmailEditorProps["onReady"] = useCallback( + (unlayer: any) => { + setIsLoading(false); + unlayer.addEventListener("design:updated", () => { + setCanUndo(unlayer.isUndoable()); + setCanRedo(unlayer.isRedoable()); + }); - // Register custom variable tool - unlayer.registerTool({ - name: 'variable', - label: 'Variable', - icon: 'fa-tag', - supportedDisplayModes: ['web', 'email'], - options: { - default: { - title: null, - }, - text: { - title: 'Text', - position: 1, - options: { - variable: { - label: 'Variable', - defaultValue: '', - widget: 'dropdown', - data: { - options: variables.map(v => ({ value: `{{${v.name}}}`, label: v.name })) - } - } - } - } - }, - values: {}, - renderer: { - Viewer: (props: any) => { - return {props.values.variable} + // Register custom variable tool + unlayer.registerTool({ + name: "variable", + label: "Variable", + icon: "fa-tag", + supportedDisplayModes: ["web", "email"], + options: { + default: { + title: null, + }, + text: { + title: "Text", + position: 1, + options: { + variable: { + label: "Variable", + defaultValue: "", + widget: "dropdown", + data: { + options: variables.map((v) => ({ + value: `{{${v.name}}}`, + label: v.name, + })), + }, + }, + }, + }, }, - Export: (props: any) => { - return {props.values.variable} + values: {}, + renderer: { + Viewer: (props: any) => { + return {props.values.variable}; + }, + Export: (props: any) => { + return {props.values.variable}; + }, }, - } - }) - }, [variables]) + }); + }, + [variables] + ); const saveDesign = useCallback(() => { emailEditorRef.current?.editor?.saveDesign((design: any) => { - console.log('Design JSON:', design) - setJsonData(JSON.stringify(design, null, 2)) - toast({ - title: "Design Saved", - description: "Your email template design has been saved.", + setJsonData(JSON.stringify(design)); + saveTemplate({ + name: templateName, + content: jsonData, }) - // Here you would typically send this design to your server - }) - }, [toast]) + .then(() => { + toast({ + title: "Design Saved", + description: "Your email template design has been saved.", + }); + router.push('/dashboard/templates/saved') + }) + .catch(() => { + toast({ + title: "Error", + description: "Failed to save template. Please try again later.", + variant: "destructive", + }); + }); + }); + }, [toast, saveTemplate, templateName]); const exportHtml = useCallback(() => { emailEditorRef.current?.editor?.exportHtml((data) => { - const { html } = data - console.log('HTML Output:', html) - const blob = new Blob([html], { type: "text/html" }) - const url = URL.createObjectURL(blob) - const a = document.createElement("a") - a.href = url - a.download = `${templateName || "email-template"}.html` - a.click() - URL.revokeObjectURL(url) + const { html } = data; + console.log("HTML Output:", html); + const blob = new Blob([html], { type: "text/html" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `${templateName || "email-template"}.html`; + a.click(); + URL.revokeObjectURL(url); toast({ title: "Template Exported", description: "Your email template has been exported as HTML.", - }) - }) - }, [templateName, toast]) + }); + }); + }, [templateName, toast]); const togglePreview = useCallback(() => { if (previewMode) { - emailEditorRef.current?.editor?.hidePreview() + emailEditorRef.current?.editor?.hidePreview(); } else { - emailEditorRef.current?.editor?.showPreview("desktop") + emailEditorRef.current?.editor?.showPreview("desktop"); } - setPreviewMode((prev) => !prev) - }, [previewMode]) + setPreviewMode((prev) => !prev); + }, [previewMode]); const loadDesign = useCallback(() => { try { - const design = JSON.parse(jsonData) - emailEditorRef.current?.editor?.loadDesign(design) + const design = JSON.parse(jsonData); + emailEditorRef.current?.editor?.loadDesign(design); toast({ title: "Design Loaded", description: "Your email template design has been loaded.", - }) + }); } catch (error) { toast({ title: "Error", description: `Failed to load design. Please check your JSON data. ${error}`, variant: "destructive", - }) + }); } - }, [jsonData, toast]) + }, [jsonData, toast]); const undoAction = useCallback(() => { - emailEditorRef.current?.editor?.undo() - }, []) + emailEditorRef.current?.editor?.undo(); + }, []); const redoAction = useCallback(() => { - emailEditorRef.current?.editor?.redo() - }, []) + emailEditorRef.current?.editor?.redo(); + }, []); - const handleImageUpload = useCallback((file: File, onSuccess: (url: string) => void) => { - // Here you would typically upload the file to your server or a file storage service - // For this example, we'll create a local object URL - const reader = new FileReader() - reader.onload = (e) => { - const dataUrl = e.target?.result as string - onSuccess(dataUrl) - toast({ - title: "Image Uploaded", - description: "Your image has been successfully uploaded.", - }) - } - reader.readAsDataURL(file) - }, [toast]) + const handleImageUpload = useCallback( + (file: File, onSuccess: (url: string) => void) => { + // Here you would typically upload the file to your server or a file storage service + // For this example, we'll create a local object URL + const reader = new FileReader(); + reader.onload = (e) => { + const dataUrl = e.target?.result as string; + onSuccess(dataUrl); + toast({ + title: "Image Uploaded", + description: "Your image has been successfully uploaded.", + }); + }; + reader.readAsDataURL(file); + }, + [toast] + ); const addVariable = useCallback(() => { if (variableName) { - setVariables(prev => [...prev, { name: variableName, description: variableDescription }]) - setVariableName("") - setVariableDescription("") - setShowVariableDialog(false) + setVariables((prev) => [ + ...prev, + { name: variableName, description: variableDescription }, + ]); + setVariableName(""); + setVariableDescription(""); + setShowVariableDialog(false); toast({ title: "Variable Added", description: `Variable {{${variableName}}} has been added to the template.`, - }) + }); } - }, [variableName, variableDescription, toast]) + }, [variableName, variableDescription, toast]); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.ctrlKey || e.metaKey) { - if (e.key === 'z') { - e.preventDefault() - undoAction() - } else if (e.key === 'y') { - e.preventDefault() - redoAction() + if (e.key === "z") { + e.preventDefault(); + undoAction(); + } else if (e.key === "y") { + e.preventDefault(); + redoAction(); } } - } + }; - window.addEventListener('keydown', handleKeyDown) - return () => window.removeEventListener('keydown', handleKeyDown) - }, [undoAction, redoAction]) + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [undoAction, redoAction]); return ( Template Editor - Create your email template using drag and drop + + Create your email template using drag and drop + @@ -210,10 +266,10 @@ export default function EmailTemplateEditor() { ref={emailEditorRef} onReady={onReady} minHeight={500} - style={{ width: '100%' }} + style={{ width: "100%" }} options={{ appearance: { - theme: 'dark', + theme: "dark", }, features: { stockImages: true, @@ -235,13 +291,19 @@ export default function EmailTemplateEditor() { }} projectId={1} // Replace with your actual Unlayer project ID onLoad={() => { - emailEditorRef.current?.editor?.addEventListener('image:added', (file: File, done: (arg0: { progress: number; url: string }) => void) => { - if (file) { - handleImageUpload(file, (url) => { - done({ progress: 100, url }) - }) + emailEditorRef.current?.editor?.addEventListener( + "image:added", + ( + file: File, + done: (arg0: { progress: number; url: string }) => void + ) => { + if (file) { + handleImageUpload(file, (url) => { + done({ progress: 100, url }); + }); + } } - }) + ); }} /> @@ -249,7 +311,9 @@ export default function EmailTemplateEditor() {
- + @@ -275,15 +339,22 @@ export default function EmailTemplateEditor() { + + - -
- + @@ -298,10 +369,17 @@ export default function EmailTemplateEditor() { - - + + - + @@ -340,15 +418,25 @@ export default function EmailTemplateEditor() { Variable Usage - How to use variables in your template + + How to use variables in your template +

To use variables in your template, follow these steps:

  1. Click the "Add Variable" button to define a new variable.
  2. -
  3. In the editor, use the Variable tool to insert variables into your content.
  4. -
  5. Variables will appear in the format {'{{variableName}}'} in your template.
  6. -
  7. When sending emails, replace these variables with actual values.
  8. +
  9. + In the editor, use the Variable tool to insert variables into your + content. +
  10. +
  11. + Variables will appear in the format {"{{variableName}}"} in your + template. +
  12. +
  13. + When sending emails, replace these variables with actual values. +

Available Variables:

@@ -364,5 +452,6 @@ export default function EmailTemplateEditor() { - ) -} \ No newline at end of file + ); +} + From 64c998128382b4c040ebd980d241f1fafc56b0ce Mon Sep 17 00:00:00 2001 From: Collins Ikechukwu Date: Sat, 30 Nov 2024 23:34:07 +0100 Subject: [PATCH 6/9] feat(dashboard): add saved templates page with search and filter options --- app/dashboard/templates/saved/page.tsx | 172 +++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 app/dashboard/templates/saved/page.tsx diff --git a/app/dashboard/templates/saved/page.tsx b/app/dashboard/templates/saved/page.tsx new file mode 100644 index 0000000..4708590 --- /dev/null +++ b/app/dashboard/templates/saved/page.tsx @@ -0,0 +1,172 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; +import { motion } from "framer-motion"; +import { Plus, Search, Bell } from "lucide-react"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { Button } from "@/components/ui/button"; +import DOMPurify from "dompurify"; +import { + Card, + CardContent, + CardFooter, + CardHeader, +} from "@/components/ui/card"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Input } from "@/components/ui/input"; +import { useAuthStore } from "@/store/authStore"; +import { useTemplateStore } from "@/store/templateStore"; +import { TemplateTypes } from "@/types/interface"; +import EmailEditor, { EditorRef } from "react-email-editor"; + +export default function TemplatesPage() { + const { user } = useAuthStore(); + const { templates, savedTemplates, loading, error } = useTemplateStore(); + + + useEffect(() => { + async function fetchTemplates() { + try { + await savedTemplates(); + } catch (err) { + console.error("Error fetching templates:", err); + } + } + fetchTemplates(); + }, [savedTemplates]); + + return ( +
+
+
+ + + Logo + +
+
+ + +
+
+ + + + {user?.first_name?.charAt(0)} + +
+
+ +
+
+ Create email + / + use template +
+ +
+ + + + +
+ + {/* Loading, Error or Templates */} + {loading ? ( +

Loading templates...

+ ) : error ? ( +

Error: {error}

+ ) : templates && templates.length > 0 ? ( + + + +
+ +
+

Add new template

+
+
+ + {templates.map((template) => ( + + +

{template.name}

+
+ +
+

{template.name}

+
+
+ + + {template.name && `Type: ${template.name}`} + + + ))} + + ) : ( +

No templates found.

+ )} +
+
+ ); +} + +interface EmailTemplatePreviewProps { + content: string; // JSON string for the email template +} + + +function EmailTemplatePreview({ content }: EmailTemplatePreviewProps): JSX.Element { + const emailEditorRef = useRef(null); + + useEffect(() => { + if (emailEditorRef.current) { + emailEditorRef.current.editor?.loadDesign(JSON.parse(content)); + } + }, [content]); + + return ( +
+ +
+ ); +} \ No newline at end of file From 0e55d06ed51de7f20e6942badc0bf0301e9af2ba Mon Sep 17 00:00:00 2001 From: Collins Ikechukwu Date: Sat, 30 Nov 2024 23:34:20 +0100 Subject: [PATCH 7/9] fix(shared): re-enable redirection in withAuth HOC --- shared/withAuth.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/shared/withAuth.tsx b/shared/withAuth.tsx index 8be7440..3eda33a 100644 --- a/shared/withAuth.tsx +++ b/shared/withAuth.tsx @@ -13,11 +13,11 @@ export function withAuth

( const router = useRouter() // If you decide to re-enable redirection, uncomment the following code: - // useEffect(() => { - // if (!loading && !user) { - // router.push('/auth/login') - // } - // }, [user, loading]) + useEffect(() => { + if (!loading && !user) { + router.push('/auth/login') + } + }, [user, loading]) // Improved Loading State if (loading) { From a40436fd3a896ffdd99ba6415d820d0c53575a43 Mon Sep 17 00:00:00 2001 From: Collins Ikechukwu Date: Sat, 30 Nov 2024 23:34:38 +0100 Subject: [PATCH 8/9] feat(templateStore): add saved templates retrieval and enhance error handling - Add `savedTemplates` method to fetch saved email templates. - Improve error handling and messages. - Change `deleteTemplate` to use template ID instead of the entire object. - Ensure consistent formatting and code style. --- store/templateStore.ts | 115 ++++++++++++++++++++++++----------------- 1 file changed, 67 insertions(+), 48 deletions(-) diff --git a/store/templateStore.ts b/store/templateStore.ts index e3cc635..5f26fbc 100644 --- a/store/templateStore.ts +++ b/store/templateStore.ts @@ -1,89 +1,108 @@ -import apiService from '@/lib/api-service'; -import { TemplateTypes } from '@/types/interface'; -import { create } from 'zustand'; +import apiService from "@/lib/api-service"; +import { TemplateTypes } from "@/types/interface"; +import { create } from "zustand"; interface TemplateState { templates: TemplateTypes[] | null; loading: boolean; error: string | null; listAllTemplates: () => Promise; + savedTemplates: () => Promise; saveTemplate: (data: TemplateTypes) => Promise; updateTemplate: (data: TemplateTypes) => Promise; - deleteTemplate: (data: TemplateTypes) => Promise; + deleteTemplate: (id: string) => Promise; } export const useTemplateStore = create((set, get) => ({ templates: null, - loading: false, + loading: false, error: null, listAllTemplates: async () => { + set({ loading: true, error: null }); try { - set({ loading: true, error: null }); - const response = await apiService.listAllEmailTemplate({}); // Assuming this API fetches templates - set({ templates: response.data.templates, loading: false }); // Store templates in state + const response = await apiService.listAllEmailTemplate(); + + if (!response.data || !Array.isArray(response.data.templates)) { + throw new Error("Invalid response format from the server."); + } + + set({ templates: response.data.templates, loading: false }); } catch (error) { - console.error('Failed to load templates:', error); - set({ loading: false, error: 'Failed to load templates' }); // Set error state if failed + console.error("Error fetching templates:", error); + set({ loading: false, error: "Unable to fetch templates. Please try again later." }); } - }, + }, + savedTemplates: async () => { + set({ loading: true, error: null }); + try { + const response = await apiService.savedEmailTemplates(); + + if (!response.data || !Array.isArray(response.data.templates)) { + throw new Error("Invalid response format from the server."); + } + + set({ templates: response.data.templates, loading: false }); + } catch (error) { + console.error("Error fetching templates:", error); + set({ loading: false, error: "Unable to fetch templates. Please try again later." }); + } + }, // Save a new email template saveTemplate: async (data: TemplateTypes) => { + set({ loading: true, error: null }); try { - set({ loading: true, error: null }); // Set loading state to true - const response = await apiService.saveEmailTemplate(data); // Save the template - const currentTemplates = get().templates; - - // Add the new template to the existing templates array - set({ - templates: currentTemplates ? [...currentTemplates, response.data.template] : [response.data.template], - loading: false, - }); + const response = await apiService.saveEmailTemplate(data); + const newTemplate = response.data.template; + + if (!newTemplate) { + throw new Error("Invalid response from the server."); + } + + const currentTemplates = get().templates || []; + set({ templates: [...currentTemplates, newTemplate], loading: false }); } catch (error) { - console.error('Failed to save template:', error); - set({ loading: false, error: 'Failed to save template' }); // Set error state if failed + console.error("Error saving template:", error); + set({ loading: false, error: "Unable to save template. Please try again later." }); } }, + // Update an existing email template updateTemplate: async (data: TemplateTypes) => { + set({ loading: true, error: null }); try { - set({ loading: true, error: null }); // Set loading state to true - const response = await apiService.updateEmailTemplate(data); // Update the template - const currentTemplates = get().templates; + const response = await apiService.updateEmailTemplate(data); + const updatedTemplate = response.data.template; - // Update the template in the existing templates list - if (currentTemplates) { - const updatedTemplates = currentTemplates.map(template => - template.id === response.data.template.id ? response.data.template : template - ); - set({ templates: updatedTemplates, loading: false }); - } else { - set({ templates: [response.data.template], loading: false }); + if (!updatedTemplate) { + throw new Error("Invalid response from the server."); } + + const currentTemplates = get().templates || []; + const updatedTemplates = currentTemplates.map((template) => + template.id === updatedTemplate.id ? updatedTemplate : template + ); + + set({ templates: updatedTemplates, loading: false }); } catch (error) { - console.error('Failed to update template:', error); - set({ loading: false, error: 'Failed to update template' }); // Set error state if failed + console.error("Error updating template:", error); + set({ loading: false, error: "Unable to update template. Please try again later." }); } }, // Delete an email template - deleteTemplate: async (data: TemplateTypes) => { + deleteTemplate: async (id: string) => { + set({ loading: true, error: null }); try { - set({ loading: true, error: null }); // Set loading state to true - await apiService.deleteEmailTemplate(data); // Delete the template - const currentTemplates = get().templates; + await apiService.deleteEmailTemplate({ id }); + const currentTemplates = get().templates || []; + const updatedTemplates = currentTemplates.filter((template) => template.id !== id); - // Remove the deleted template from the state - if (currentTemplates) { - const updatedTemplates = currentTemplates.filter( - template => template.id !== data.id - ); - set({ templates: updatedTemplates, loading: false }); - } + set({ templates: updatedTemplates, loading: false }); } catch (error) { - console.error('Failed to delete template:', error); - set({ loading: false, error: 'Failed to delete template' }); // Set error state if failed + console.error("Error deleting template:", error); + set({ loading: false, error: "Unable to delete template. Please try again later." }); } }, })); From 938226599c37241fe20a2786f6c7a41c15158072 Mon Sep 17 00:00:00 2001 From: Collins Ikechukwu Date: Sat, 30 Nov 2024 23:35:01 +0100 Subject: [PATCH 9/9] feat(api-service): update email template API methods - Change listAllEmailTemplate to use GET method without data parameter - Add new savedEmailTemplates method for fetching saved email templates --- lib/api-service.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/api-service.ts b/lib/api-service.ts index 8b48cb6..902e1d2 100644 --- a/lib/api-service.ts +++ b/lib/api-service.ts @@ -187,8 +187,11 @@ class ApiService { } //templates - async listAllEmailTemplate(data: TemplateTypes) { - return this.executeApiCall("post", "/user/templates", data); + async listAllEmailTemplate() { + return this.executeApiCall("get", "/user/templates"); + } + async savedEmailTemplates() { + return this.executeApiCall("get", "/user/email-templates"); } async updateEmailTemplate(data: TemplateTypes) { return this.executeApiCall("put", "/auth/campaigns", data);