From 4dc9c7a275088a4ce579e5ccfeaeefbe5d4de080 Mon Sep 17 00:00:00 2001 From: Dima Grossman Date: Sat, 28 Dec 2024 21:56:51 +0200 Subject: [PATCH 01/54] feat: wip --- apps/dashboard/public/images/dots.svg | 386 ++++++++++++++++++ .../template-store/workflow-card.tsx | 89 ++++ .../template-store/workflow-sidebar.tsx | 95 +++++ .../workflow-template-modal.tsx | 161 ++++++++ apps/dashboard/src/pages/workflows.tsx | 6 +- 5 files changed, 734 insertions(+), 3 deletions(-) create mode 100644 apps/dashboard/public/images/dots.svg create mode 100644 apps/dashboard/src/components/template-store/workflow-card.tsx create mode 100644 apps/dashboard/src/components/template-store/workflow-sidebar.tsx create mode 100644 apps/dashboard/src/components/template-store/workflow-template-modal.tsx diff --git a/apps/dashboard/public/images/dots.svg b/apps/dashboard/public/images/dots.svg new file mode 100644 index 00000000000..3d26324aecb --- /dev/null +++ b/apps/dashboard/public/images/dots.svg @@ -0,0 +1,386 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/dashboard/src/components/template-store/workflow-card.tsx b/apps/dashboard/src/components/template-store/workflow-card.tsx new file mode 100644 index 00000000000..33f94333210 --- /dev/null +++ b/apps/dashboard/src/components/template-store/workflow-card.tsx @@ -0,0 +1,89 @@ +import React from 'react'; +import { Card, CardContent } from '../primitives/card'; +import { Bell, MessageSquare, MessageCircle, BellRing } from 'lucide-react'; +import { LucideIcon } from 'lucide-react'; + +export type StepType = 'In-App' | 'Email' | 'SMS' | 'Push'; + +interface Step { + icon: LucideIcon; + bgColor: string; + borderColor: string; +} + +interface WorkflowCardProps { + name: string; + description: string; + steps?: StepType[]; + onClick?: () => void; +} + +const STEP_ICON_MAP: Record = { + 'In-App': Bell, + Email: MessageSquare, + SMS: MessageCircle, + Push: BellRing, +}; + +const STEP_COLORS: Record = { + 'In-App': { + bg: 'bg-[#FFE5D3]', + border: 'border-[#FF8D4E]', + }, + Email: { + bg: 'bg-[#E7F6F3]', + border: 'border-[#4EC2AB]', + }, + SMS: { + bg: 'bg-[#FFE9F3]', + border: 'border-[#FF4E9E]', + }, + Push: { + bg: 'bg-[#E7EEFF]', + border: 'border-[#4E77FF]', + }, +}; + +export function WorkflowCard({ + name, + description, + steps = ['In-App', 'Email', 'SMS', 'Push'], + onClick, +}: WorkflowCardProps) { + const mappedSteps = steps.map((step) => ({ + icon: STEP_ICON_MAP[step], + bgColor: STEP_COLORS[step].bg, + borderColor: STEP_COLORS[step].border, + })); + + return ( + + +
+
+
+ {mappedSteps.map((step, index) => ( + +
+ +
+ {index < mappedSteps.length - 1 &&
} + + ))} +
+
+
+ +
+

{name}

+

{description}

+
+ + + ); +} diff --git a/apps/dashboard/src/components/template-store/workflow-sidebar.tsx b/apps/dashboard/src/components/template-store/workflow-sidebar.tsx new file mode 100644 index 00000000000..499abc387dc --- /dev/null +++ b/apps/dashboard/src/components/template-store/workflow-sidebar.tsx @@ -0,0 +1,95 @@ +import { Calendar, Code2, ExternalLink, FileCode2, FileText, KeyRound, LayoutGrid, Users } from 'lucide-react'; + +const useCases = [ + { + icon: , + label: 'Popular', + isHighlighted: true, + bgColor: 'bg-blue-50', + }, + { + icon: , + label: 'Events', + bgColor: 'bg-blue-50', + }, + { + icon: , + label: 'Authentication', + bgColor: 'bg-green-50', + }, + { + icon: , + label: 'Social', + bgColor: 'bg-purple-50', + }, +]; + +const createOptions = [ + { + icon: , + label: 'Code-based workflow', + hasExternalLink: true, + bgColor: 'bg-blue-50', + }, + { + icon: , + label: 'Blank workflow', + bgColor: 'bg-green-50', + }, +]; + +export function WorkflowSidebar() { + return ( +
+
+
+ USE CASES +
+ +
+ {useCases.map((item, index) => ( +
+
{item.icon}
+ {item.label} +
+ ))} +
+
+ +
+
+ OR CREATE +
+
+ {createOptions.map((item, index) => ( +
+
+
{item.icon}
+ {item.label} +
+ {item.hasExternalLink && } +
+ ))} +
+
+ +
+
+
+
+ +
+ Documentation +
+ +

Find out more about how to best setup workflows

+
+
+
+ ); +} diff --git a/apps/dashboard/src/components/template-store/workflow-template-modal.tsx b/apps/dashboard/src/components/template-store/workflow-template-modal.tsx new file mode 100644 index 00000000000..5996fd5e254 --- /dev/null +++ b/apps/dashboard/src/components/template-store/workflow-template-modal.tsx @@ -0,0 +1,161 @@ +import { ComponentProps } from 'react'; +import { RiArrowRightSLine, RiSearchLine } from 'react-icons/ri'; +import { BiCodeAlt } from 'react-icons/bi'; +import { BsLightning } from 'react-icons/bs'; +import { FiLock } from 'react-icons/fi'; +import { HiOutlineUsers } from 'react-icons/hi'; + +import { Dialog, DialogContent, DialogHeader, DialogTrigger } from '@/components/primitives/dialog'; +import { Input, InputField } from '@/components/primitives/input'; +import { Button } from '@/components/primitives/button'; +import { CreateWorkflowButton } from '../create-workflow-button'; +import { WorkflowCard, StepType } from './workflow-card'; +import { WorkflowSidebar } from './workflow-sidebar'; +import { RouteFill } from '../icons'; +import { Form } from '../primitives/form/form'; +import { useForm } from 'react-hook-form'; + +const CATEGORIES = [ + { + id: 'popular', + name: 'Popular', + icon: BsLightning, + }, + { + id: 'events', + name: 'Events', + icon: HiOutlineUsers, + }, + { + id: 'authentication', + name: 'Authentication', + icon: FiLock, + }, + { + id: 'social', + name: 'Social', + icon: HiOutlineUsers, + }, +] as const; + +const CREATE_OPTIONS = [ + { + id: 'code-based', + name: 'Code-based workflow', + icon: BiCodeAlt, + }, + { + id: 'blank', + name: 'Blank workflow', + icon: BsLightning, + }, +] as const; + +interface WorkflowTemplate { + id: string; + name: string; + description: string; + steps: StepType[]; +} + +const WORKFLOW_TEMPLATES: WorkflowTemplate[] = [ + { + id: 'mention-comment-1', + name: 'Mention in a comment', + description: 'Triggered when an actor mentions someone', + steps: ['In-App', 'Email', 'SMS', 'Push'], + }, + { + id: 'mention-comment-2', + name: 'Mention in a comment', + description: 'Triggered when an actor mentions someone', + steps: ['In-App', 'Email', 'SMS', 'Push'], + }, + { + id: 'mention-comment-3', + name: 'Mention in a comment', + description: 'Triggered when an actor mentions someone', + steps: ['In-App', 'Email', 'SMS', 'Push'], + }, + { + id: 'mention-comment-4', + name: 'Mention in a comment', + description: 'Triggered when an actor mentions someone', + steps: ['In-App', 'Email', 'SMS', 'Push'], + }, + { + id: 'mention-comment-5', + name: 'Mention in a comment', + description: 'Triggered when an actor mentions someone', + steps: ['In-App', 'Email', 'SMS', 'Push'], + }, + { + id: 'mention-comment-6', + name: 'Mention in a comment', + description: 'Triggered when an actor mentions someone', + steps: ['In-App', 'Email', 'SMS', 'Push'], + }, +]; + +type WorkflowTemplateModalProps = ComponentProps; + +export function WorkflowTemplateModal(props: WorkflowTemplateModalProps) { + const form = useForm(); + + return ( + + + + +
+ + + +
Create a workflow
+
+
+ {/* Sidebar */} +
+ +
+ + {/* Main Content */} +
+
+

Popular workflows

+
+ + + + +
+
+ +
+ {WORKFLOW_TEMPLATES.map((template) => ( + + + + ))} +
+
+
+ + {/* Footer */} +
+ + + + +
+
+ +
+
+ ); +} diff --git a/apps/dashboard/src/pages/workflows.tsx b/apps/dashboard/src/pages/workflows.tsx index 9be66f68930..6de0ab653fc 100644 --- a/apps/dashboard/src/pages/workflows.tsx +++ b/apps/dashboard/src/pages/workflows.tsx @@ -3,12 +3,12 @@ import { RiRouteFill } from 'react-icons/ri'; import { WorkflowList } from '@/components/workflow-list'; import { DashboardLayout } from '@/components/dashboard-layout'; import { Button } from '@/components/primitives/button'; -import { CreateWorkflowButton } from '@/components/create-workflow-button'; import { OptInModal } from '@/components/opt-in-modal'; import { PageMeta } from '@/components/page-meta'; import { useTelemetry } from '@/hooks/use-telemetry'; import { TelemetryEvent } from '@/utils/telemetry'; import { Badge } from '@/components/primitives/badge'; +import { WorkflowTemplateModal } from '@/components/template-store/workflow-template-modal'; export const WorkflowsPage = () => { const track = useTelemetry(); @@ -34,12 +34,12 @@ export const WorkflowsPage = () => {
- + - +
From 265de59b96dfbedf909cedab19d83ce5aacc7e6e Mon Sep 17 00:00:00 2001 From: Dima Grossman Date: Sat, 28 Dec 2024 23:34:27 +0200 Subject: [PATCH 02/54] fix: items --- .../template-store/workflow-card.tsx | 46 +- .../template-store/workflow-sidebar.tsx | 32 +- .../workflow-template-modal.tsx | 570 ++++++++++++++++-- 3 files changed, 586 insertions(+), 62 deletions(-) diff --git a/apps/dashboard/src/components/template-store/workflow-card.tsx b/apps/dashboard/src/components/template-store/workflow-card.tsx index 33f94333210..0fe3fd13ab3 100644 --- a/apps/dashboard/src/components/template-store/workflow-card.tsx +++ b/apps/dashboard/src/components/template-store/workflow-card.tsx @@ -2,8 +2,9 @@ import React from 'react'; import { Card, CardContent } from '../primitives/card'; import { Bell, MessageSquare, MessageCircle, BellRing } from 'lucide-react'; import { LucideIcon } from 'lucide-react'; +import { StepTypeEnum } from '@novu/shared'; -export type StepType = 'In-App' | 'Email' | 'SMS' | 'Push'; +export type StepType = StepTypeEnum; interface Step { icon: LucideIcon; @@ -19,35 +20,60 @@ interface WorkflowCardProps { } const STEP_ICON_MAP: Record = { - 'In-App': Bell, - Email: MessageSquare, - SMS: MessageCircle, - Push: BellRing, + [StepTypeEnum.IN_APP]: Bell, + [StepTypeEnum.EMAIL]: MessageSquare, + [StepTypeEnum.SMS]: MessageCircle, + [StepTypeEnum.PUSH]: BellRing, + [StepTypeEnum.CHAT]: MessageSquare, + [StepTypeEnum.DIGEST]: Bell, + [StepTypeEnum.TRIGGER]: Bell, + [StepTypeEnum.DELAY]: Bell, + [StepTypeEnum.CUSTOM]: Bell, }; const STEP_COLORS: Record = { - 'In-App': { + [StepTypeEnum.IN_APP]: { bg: 'bg-[#FFE5D3]', border: 'border-[#FF8D4E]', }, - Email: { + [StepTypeEnum.EMAIL]: { bg: 'bg-[#E7F6F3]', border: 'border-[#4EC2AB]', }, - SMS: { + [StepTypeEnum.SMS]: { bg: 'bg-[#FFE9F3]', border: 'border-[#FF4E9E]', }, - Push: { + [StepTypeEnum.PUSH]: { bg: 'bg-[#E7EEFF]', border: 'border-[#4E77FF]', }, + [StepTypeEnum.CHAT]: { + bg: 'bg-[#E7F6F3]', + border: 'border-[#4EC2AB]', + }, + [StepTypeEnum.DIGEST]: { + bg: 'bg-[#FFE5D3]', + border: 'border-[#FF8D4E]', + }, + [StepTypeEnum.TRIGGER]: { + bg: 'bg-[#FFE5D3]', + border: 'border-[#FF8D4E]', + }, + [StepTypeEnum.DELAY]: { + bg: 'bg-[#FFE5D3]', + border: 'border-[#FF8D4E]', + }, + [StepTypeEnum.CUSTOM]: { + bg: 'bg-[#FFE5D3]', + border: 'border-[#FF8D4E]', + }, }; export function WorkflowCard({ name, description, - steps = ['In-App', 'Email', 'SMS', 'Push'], + steps = [StepTypeEnum.IN_APP, StepTypeEnum.EMAIL, StepTypeEnum.SMS, StepTypeEnum.PUSH], onClick, }: WorkflowCardProps) { const mappedSteps = steps.map((step) => ({ diff --git a/apps/dashboard/src/components/template-store/workflow-sidebar.tsx b/apps/dashboard/src/components/template-store/workflow-sidebar.tsx index 499abc387dc..e130a9a2d94 100644 --- a/apps/dashboard/src/components/template-store/workflow-sidebar.tsx +++ b/apps/dashboard/src/components/template-store/workflow-sidebar.tsx @@ -1,28 +1,36 @@ import { Calendar, Code2, ExternalLink, FileCode2, FileText, KeyRound, LayoutGrid, Users } from 'lucide-react'; +interface WorkflowSidebarProps { + selectedCategory: string; + onCategorySelect: (category: string) => void; +} + const useCases = [ { + id: 'popular', icon: , label: 'Popular', - isHighlighted: true, bgColor: 'bg-blue-50', }, { + id: 'events', icon: , label: 'Events', bgColor: 'bg-blue-50', }, { + id: 'authentication', icon: , label: 'Authentication', bgColor: 'bg-green-50', }, { + id: 'social', icon: , label: 'Social', bgColor: 'bg-purple-50', }, -]; +] as const; const createOptions = [ { @@ -38,7 +46,7 @@ const createOptions = [ }, ]; -export function WorkflowSidebar() { +export function WorkflowSidebar({ selectedCategory, onCategorySelect }: WorkflowSidebarProps) { return (
@@ -47,11 +55,12 @@ export function WorkflowSidebar() {
- {useCases.map((item, index) => ( + {useCases.map((item) => (
onCategorySelect(item.id)} + className={`flex items-center gap-2 rounded-xl p-1.5 transition-colors hover:cursor-pointer hover:bg-gray-100 ${ + selectedCategory === item.id ? 'border border-[#EEEFF1] bg-white' : '' }`} >
{item.icon}
@@ -67,19 +76,22 @@ export function WorkflowSidebar() {
{createOptions.map((item, index) => ( -
+
{item.icon}
{item.label}
- {item.hasExternalLink && } + {item.hasExternalLink && }
))}
-
+
diff --git a/apps/dashboard/src/components/template-store/workflow-template-modal.tsx b/apps/dashboard/src/components/template-store/workflow-template-modal.tsx index 5996fd5e254..2d51f38d1ac 100644 --- a/apps/dashboard/src/components/template-store/workflow-template-modal.tsx +++ b/apps/dashboard/src/components/template-store/workflow-template-modal.tsx @@ -1,12 +1,11 @@ -import { ComponentProps } from 'react'; -import { RiArrowRightSLine, RiSearchLine } from 'react-icons/ri'; +import { ComponentProps, useState } from 'react'; +import { RiArrowRightSLine } from 'react-icons/ri'; import { BiCodeAlt } from 'react-icons/bi'; import { BsLightning } from 'react-icons/bs'; import { FiLock } from 'react-icons/fi'; import { HiOutlineUsers } from 'react-icons/hi'; import { Dialog, DialogContent, DialogHeader, DialogTrigger } from '@/components/primitives/dialog'; -import { Input, InputField } from '@/components/primitives/input'; import { Button } from '@/components/primitives/button'; import { CreateWorkflowButton } from '../create-workflow-button'; import { WorkflowCard, StepType } from './workflow-card'; @@ -14,6 +13,7 @@ import { WorkflowSidebar } from './workflow-sidebar'; import { RouteFill } from '../icons'; import { Form } from '../primitives/form/form'; import { useForm } from 'react-hook-form'; +import { StepTypeEnum, WorkflowCreationSourceEnum } from '@novu/shared'; const CATEGORIES = [ { @@ -55,52 +55,538 @@ interface WorkflowTemplate { id: string; name: string; description: string; - steps: StepType[]; + steps: { + name: string; + type: StepTypeEnum; + controlValues?: Record | null; + }[]; + category: 'popular' | 'events' | 'authentication' | 'social'; + tags: string[]; + active?: boolean; + workflowId: string; + __source: WorkflowCreationSourceEnum; } +type WorkflowTemplateModalProps = ComponentProps; + const WORKFLOW_TEMPLATES: WorkflowTemplate[] = [ { - id: 'mention-comment-1', + id: 'mention-notification', name: 'Mention in a comment', description: 'Triggered when an actor mentions someone', - steps: ['In-App', 'Email', 'SMS', 'Push'], + workflowId: 'mention-notification', + steps: [ + { + name: 'In-App Notification', + type: StepTypeEnum.IN_APP, + controlValues: { + content: 'You were mentioned in a comment by {{actorName}}', + title: 'New Mention', + ctaLabel: 'View Comment', + }, + }, + { + name: 'Email Notification', + type: StepTypeEnum.EMAIL, + controlValues: { + subject: 'You were mentioned in a comment', + content: { + type: 'doc', + content: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Hi ', + }, + { + type: 'variable', + attrs: { value: 'subscriberFirstName' }, + }, + { + type: 'text', + text: ',', + }, + ], + }, + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'You were mentioned in a comment by ', + }, + { + type: 'variable', + attrs: { value: 'actorName' }, + }, + { + type: 'text', + text: '.', + }, + ], + }, + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Click the button below to view the comment:', + }, + ], + }, + { + type: 'button', + attrs: { + url: '{{commentUrl}}', + text: 'View Comment', + }, + }, + ], + }, + }, + }, + { + name: 'SMS Alert', + type: StepTypeEnum.SMS, + controlValues: { + content: 'You were mentioned in a comment by {{actorName}}. Click {{commentUrl}} to view.', + }, + }, + { + name: 'Push Notification', + type: StepTypeEnum.PUSH, + controlValues: { + title: 'New Mention', + content: 'You were mentioned in a comment by {{actorName}}', + data: { + url: '{{commentUrl}}', + }, + }, + }, + ], + category: 'popular', + tags: ['mention', 'comment', 'notification'], + active: true, + __source: WorkflowCreationSourceEnum.TEMPLATE_STORE, }, { - id: 'mention-comment-2', - name: 'Mention in a comment', - description: 'Triggered when an actor mentions someone', - steps: ['In-App', 'Email', 'SMS', 'Push'], + id: 'event-notification', + name: 'New Event Notification', + description: 'Notify users about new events', + workflowId: 'event-notification', + steps: [ + { + name: 'In-App Notification', + type: StepTypeEnum.IN_APP, + controlValues: { + content: 'New event: {{eventName}} on {{eventDate}}', + title: 'New Event', + ctaLabel: 'View Event', + }, + }, + { + name: 'Email Notification', + type: StepTypeEnum.EMAIL, + controlValues: { + subject: 'New Event: {{eventName}}', + content: { + type: 'doc', + content: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Hello ', + }, + { + type: 'variable', + attrs: { value: 'subscriberFirstName' }, + }, + { + type: 'text', + text: ',', + }, + ], + }, + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'A new event has been scheduled: ', + }, + { + type: 'variable', + attrs: { value: 'eventName' }, + }, + ], + }, + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Date: ', + }, + { + type: 'variable', + attrs: { value: 'eventDate' }, + }, + ], + }, + { + type: 'button', + attrs: { + url: '{{eventUrl}}', + text: 'View Event Details', + }, + }, + ], + }, + }, + }, + ], + category: 'events', + tags: ['event', 'notification'], + active: true, + __source: WorkflowCreationSourceEnum.TEMPLATE_STORE, }, { - id: 'mention-comment-3', - name: 'Mention in a comment', - description: 'Triggered when an actor mentions someone', - steps: ['In-App', 'Email', 'SMS', 'Push'], + id: 'password-reset', + name: 'Password Reset', + description: 'Send password reset instructions', + workflowId: 'password-reset', + steps: [ + { + name: 'Email Instructions', + type: StepTypeEnum.EMAIL, + controlValues: { + subject: 'Reset Your Password', + content: { + type: 'doc', + content: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Hi ', + }, + { + type: 'variable', + attrs: { value: 'subscriberFirstName' }, + }, + { + type: 'text', + text: ',', + }, + ], + }, + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'We received a request to reset your password. Click the button below to reset it:', + }, + ], + }, + { + type: 'button', + attrs: { + url: '{{resetUrl}}', + text: 'Reset Password', + }, + }, + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'If you did not request this change, please ignore this email.', + }, + ], + }, + ], + }, + }, + }, + { + name: 'SMS Code', + type: StepTypeEnum.SMS, + controlValues: { + content: 'Your password reset code is: {{resetCode}}. This code will expire in 10 minutes.', + }, + }, + ], + category: 'authentication', + tags: ['auth', 'password', 'security'], + active: true, + __source: WorkflowCreationSourceEnum.TEMPLATE_STORE, }, { - id: 'mention-comment-4', - name: 'Mention in a comment', - description: 'Triggered when an actor mentions someone', - steps: ['In-App', 'Email', 'SMS', 'Push'], + id: 'new-follower', + name: 'New Follower', + description: 'Notify user when they get a new follower', + workflowId: 'new-follower', + steps: [ + { + name: 'In-App Alert', + type: StepTypeEnum.IN_APP, + controlValues: { + content: '{{followerName}} started following you', + title: 'New Follower', + ctaLabel: 'View Profile', + }, + }, + { + name: 'Push Notification', + type: StepTypeEnum.PUSH, + controlValues: { + title: 'New Follower', + content: '{{followerName}} started following you', + data: { + url: '{{followerProfileUrl}}', + }, + }, + }, + ], + category: 'social', + tags: ['social', 'follower', 'notification'], + active: true, + __source: WorkflowCreationSourceEnum.TEMPLATE_STORE, }, { - id: 'mention-comment-5', - name: 'Mention in a comment', - description: 'Triggered when an actor mentions someone', - steps: ['In-App', 'Email', 'SMS', 'Push'], + id: 'welcome-message', + name: 'Welcome Message', + description: 'Send welcome message to new users', + workflowId: 'welcome-message', + steps: [ + { + name: 'Welcome Email', + type: StepTypeEnum.EMAIL, + controlValues: { + subject: 'Welcome to Our Platform!', + content: { + type: 'doc', + content: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Welcome ', + }, + { + type: 'variable', + attrs: { value: 'subscriberFirstName' }, + }, + { + type: 'text', + text: '!', + }, + ], + }, + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'We are excited to have you on board. Here are some resources to help you get started:', + }, + ], + }, + { + type: 'bulletList', + content: [ + { + type: 'listItem', + content: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Quick Start Guide', + }, + ], + }, + ], + }, + { + type: 'listItem', + content: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Documentation', + }, + ], + }, + ], + }, + ], + }, + { + type: 'button', + attrs: { + url: '{{dashboardUrl}}', + text: 'Go to Dashboard', + }, + }, + ], + }, + }, + }, + { + name: 'Welcome Notification', + type: StepTypeEnum.IN_APP, + controlValues: { + content: 'Welcome to our platform! Click here to get started.', + title: 'Welcome!', + ctaLabel: 'Get Started', + }, + }, + ], + category: 'popular', + tags: ['onboarding', 'welcome'], + active: true, + __source: WorkflowCreationSourceEnum.TEMPLATE_STORE, }, { - id: 'mention-comment-6', - name: 'Mention in a comment', - description: 'Triggered when an actor mentions someone', - steps: ['In-App', 'Email', 'SMS', 'Push'], + id: 'event-reminder', + name: 'Event Reminder', + description: 'Send event reminders to participants', + workflowId: 'event-reminder', + steps: [ + { + name: 'Email Reminder', + type: StepTypeEnum.EMAIL, + controlValues: { + subject: 'Reminder: {{eventName}} starts in 24 hours', + content: { + type: 'doc', + content: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Hi ', + }, + { + type: 'variable', + attrs: { value: 'subscriberFirstName' }, + }, + { + type: 'text', + text: ',', + }, + ], + }, + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'This is a reminder that ', + }, + { + type: 'variable', + attrs: { value: 'eventName' }, + }, + { + type: 'text', + text: ' starts in 24 hours.', + }, + ], + }, + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Event details:', + }, + ], + }, + { + type: 'bulletList', + content: [ + { + type: 'listItem', + content: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Date: ', + }, + { + type: 'variable', + attrs: { value: 'eventDate' }, + }, + ], + }, + ], + }, + { + type: 'listItem', + content: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Location: ', + }, + { + type: 'variable', + attrs: { value: 'eventLocation' }, + }, + ], + }, + ], + }, + ], + }, + { + type: 'button', + attrs: { + url: '{{eventUrl}}', + text: 'View Event Details', + }, + }, + ], + }, + }, + }, + { + name: 'SMS Reminder', + type: StepTypeEnum.SMS, + controlValues: { + content: 'Reminder: {{eventName}} starts in 24 hours at {{eventLocation}}. Details: {{eventUrl}}', + }, + }, + ], + category: 'events', + tags: ['event', 'reminder'], + active: true, + __source: WorkflowCreationSourceEnum.TEMPLATE_STORE, }, ]; -type WorkflowTemplateModalProps = ComponentProps; - export function WorkflowTemplateModal(props: WorkflowTemplateModalProps) { const form = useForm(); + const [selectedCategory, setSelectedCategory] = useState('popular'); + + const filteredTemplates = WORKFLOW_TEMPLATES.filter((template) => template.category === selectedCategory); return ( @@ -116,25 +602,25 @@ export function WorkflowTemplateModal(props: WorkflowTemplateModalProps) {
{/* Sidebar */}
- +
{/* Main Content */} -
-
-

Popular workflows

-
- - - - -
+
+
+

+ {selectedCategory.charAt(0).toUpperCase() + selectedCategory.slice(1)} workflows +

-
- {WORKFLOW_TEMPLATES.map((template) => ( +
+ {filteredTemplates.map((template) => ( - + step.type as StepType)} + /> ))}
@@ -148,7 +634,7 @@ export function WorkflowTemplateModal(props: WorkflowTemplateModalProps) { Create blank workflow - From f57831ce1edeff4cc87c8a9506f9cca2526e2971 Mon Sep 17 00:00:00 2001 From: Dima Grossman Date: Sat, 28 Dec 2024 23:44:06 +0200 Subject: [PATCH 03/54] fix: items --- .../src/components/create-workflow-button.tsx | 38 +- .../workflow-template-modal.tsx | 741 +++++++++--------- 2 files changed, 402 insertions(+), 377 deletions(-) diff --git a/apps/dashboard/src/components/create-workflow-button.tsx b/apps/dashboard/src/components/create-workflow-button.tsx index d999cb1f37d..56bb460d05b 100644 --- a/apps/dashboard/src/components/create-workflow-button.tsx +++ b/apps/dashboard/src/components/create-workflow-button.tsx @@ -6,7 +6,7 @@ import { RiArrowRightSLine } from 'react-icons/ri'; import { ExternalLink } from '@/components/shared/external-link'; import { useNavigate } from 'react-router-dom'; import { z } from 'zod'; -import { type CreateWorkflowDto, WorkflowCreationSourceEnum, slugify } from '@novu/shared'; +import { type CreateWorkflowDto, WorkflowCreationSourceEnum, slugify, StepTypeEnum } from '@novu/shared'; import { createWorkflow } from '@/api/workflows'; import { Button } from '@/components/primitives/button'; import { FormField, FormItem, FormLabel, FormControl, FormMessage, Form } from '@/components/primitives/form/form'; @@ -31,13 +31,26 @@ import { buildRoute, ROUTES } from '@/utils/routes'; import { AUTOCOMPLETE_PASSWORD_MANAGERS_OFF } from '@/utils/constants'; import { MAX_DESCRIPTION_LENGTH, MAX_TAG_ELEMENTS, workflowSchema } from './workflow-editor/schema'; -type CreateWorkflowButtonProps = ComponentProps; -export const CreateWorkflowButton = (props: CreateWorkflowButtonProps) => { +interface CreateWorkflowButtonProps extends ComponentProps { + template?: { + name: string; + description?: string; + workflowId: string; + tags: string[]; + steps: { + name: string; + type: StepTypeEnum; + controlValues?: Record | null; + }[]; + __source: WorkflowCreationSourceEnum; + }; +} + +export const CreateWorkflowButton = ({ template, ...props }: CreateWorkflowButtonProps) => { const queryClient = useQueryClient(); const navigate = useNavigate(); const { currentEnvironment } = useEnvironment(); const [isOpen, setIsOpen] = useState(false); - // TODO: Move to a use-create-workflow.ts hook const { mutateAsync, isPending } = useMutation({ mutationFn: async (workflow: CreateWorkflowDto) => createWorkflow({ environment: currentEnvironment!, workflow }), onSuccess: async (result) => { @@ -62,7 +75,12 @@ export const CreateWorkflowButton = (props: CreateWorkflowButtonProps) => { const form = useForm>({ resolver: zodResolver(workflowSchema), - defaultValues: { description: '', workflowId: '', name: '', tags: [] }, + defaultValues: { + description: template?.description ?? '', + workflowId: template?.workflowId ?? '', + name: template?.name ?? '', + tags: template?.tags ?? [], + }, }); return ( @@ -88,8 +106,8 @@ export const CreateWorkflowButton = (props: CreateWorkflowButtonProps) => { onSubmit={form.handleSubmit((values) => { mutateAsync({ name: values.name, - steps: [], - __source: WorkflowCreationSourceEnum.DASHBOARD, + steps: template?.steps ?? [], + __source: template?.__source ?? WorkflowCreationSourceEnum.DASHBOARD, workflowId: values.workflowId, description: values.description || undefined, tags: values.tags, @@ -111,7 +129,9 @@ export const CreateWorkflowButton = (props: CreateWorkflowButtonProps) => { {...AUTOCOMPLETE_PASSWORD_MANAGERS_OFF} onChange={(e) => { field.onChange(e); - form.setValue('workflowId', slugify(e.target.value)); + if (!template) { + form.setValue('workflowId', slugify(e.target.value)); + } }} /> @@ -129,7 +149,7 @@ export const CreateWorkflowButton = (props: CreateWorkflowButtonProps) => { Identifier - + diff --git a/apps/dashboard/src/components/template-store/workflow-template-modal.tsx b/apps/dashboard/src/components/template-store/workflow-template-modal.tsx index 2d51f38d1ac..17012d90960 100644 --- a/apps/dashboard/src/components/template-store/workflow-template-modal.tsx +++ b/apps/dashboard/src/components/template-store/workflow-template-modal.tsx @@ -8,12 +8,12 @@ import { HiOutlineUsers } from 'react-icons/hi'; import { Dialog, DialogContent, DialogHeader, DialogTrigger } from '@/components/primitives/dialog'; import { Button } from '@/components/primitives/button'; import { CreateWorkflowButton } from '../create-workflow-button'; -import { WorkflowCard, StepType } from './workflow-card'; +import { WorkflowCard } from './workflow-card'; import { WorkflowSidebar } from './workflow-sidebar'; import { RouteFill } from '../icons'; import { Form } from '../primitives/form/form'; import { useForm } from 'react-hook-form'; -import { StepTypeEnum, WorkflowCreationSourceEnum } from '@novu/shared'; +import { StepTypeEnum, WorkflowCreationSourceEnum, UiComponentEnum } from '@novu/shared'; const CATEGORIES = [ { @@ -69,6 +69,26 @@ interface WorkflowTemplate { type WorkflowTemplateModalProps = ComponentProps; +// For email steps, we need to update the controlValues to match the email editor structure +const EMAIL_TEMPLATE = { + subject: { + component: UiComponentEnum.TEXT_FULL_LINE, + }, + body: { + component: UiComponentEnum.BLOCK_EDITOR, + }, +}; + +// For push steps, we need to update the controlValues to match the push editor structure +const PUSH_TEMPLATE = { + subject: { + component: UiComponentEnum.PUSH_SUBJECT, + }, + body: { + component: UiComponentEnum.PUSH_BODY, + }, +}; + const WORKFLOW_TEMPLATES: WorkflowTemplate[] = [ { id: 'mention-notification', @@ -80,7 +100,7 @@ const WORKFLOW_TEMPLATES: WorkflowTemplate[] = [ name: 'In-App Notification', type: StepTypeEnum.IN_APP, controlValues: { - content: 'You were mentioned in a comment by {{actorName}}', + content: 'You were mentioned in a comment by {{payload.actorName}}', title: 'New Mention', ctaLabel: 'View Comment', }, @@ -89,61 +109,67 @@ const WORKFLOW_TEMPLATES: WorkflowTemplate[] = [ name: 'Email Notification', type: StepTypeEnum.EMAIL, controlValues: { - subject: 'You were mentioned in a comment', - content: { - type: 'doc', - content: [ - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'Hi ', - }, - { - type: 'variable', - attrs: { value: 'subscriberFirstName' }, - }, - { - type: 'text', - text: ',', - }, - ], - }, - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'You were mentioned in a comment by ', - }, - { - type: 'variable', - attrs: { value: 'actorName' }, - }, - { - type: 'text', - text: '.', - }, - ], - }, - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'Click the button below to view the comment:', + subject: { + component: UiComponentEnum.TEXT_FULL_LINE, + value: 'You were mentioned in a comment', + }, + body: { + component: UiComponentEnum.BLOCK_EDITOR, + value: { + type: 'doc', + content: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Hi ', + }, + { + type: 'variable', + attrs: { value: 'subscriber.firstName' }, + }, + { + type: 'text', + text: ',', + }, + ], + }, + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'You were mentioned in a comment by ', + }, + { + type: 'variable', + attrs: { value: 'payload.actorName' }, + }, + { + type: 'text', + text: '.', + }, + ], + }, + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Click the button below to view the comment:', + }, + ], + }, + { + type: 'button', + attrs: { + url: '{{payload.commentUrl}}', + text: 'View Comment', }, - ], - }, - { - type: 'button', - attrs: { - url: '{{commentUrl}}', - text: 'View Comment', }, - }, - ], + ], + }, }, }, }, @@ -151,17 +177,23 @@ const WORKFLOW_TEMPLATES: WorkflowTemplate[] = [ name: 'SMS Alert', type: StepTypeEnum.SMS, controlValues: { - content: 'You were mentioned in a comment by {{actorName}}. Click {{commentUrl}} to view.', + content: 'You were mentioned in a comment by {{payload.actorName}}. Click {{payload.commentUrl}} to view.', }, }, { name: 'Push Notification', type: StepTypeEnum.PUSH, controlValues: { - title: 'New Mention', - content: 'You were mentioned in a comment by {{actorName}}', + subject: { + component: UiComponentEnum.PUSH_SUBJECT, + value: 'New Mention', + }, + body: { + component: UiComponentEnum.PUSH_BODY, + value: 'You were mentioned in a comment by {{payload.actorName}}', + }, data: { - url: '{{commentUrl}}', + url: '{{payload.commentUrl}}', }, }, }, @@ -173,78 +205,92 @@ const WORKFLOW_TEMPLATES: WorkflowTemplate[] = [ }, { id: 'event-notification', - name: 'New Event Notification', - description: 'Notify users about new events', + name: 'Event Notification', + description: 'Notify users about upcoming events', workflowId: 'event-notification', steps: [ - { - name: 'In-App Notification', - type: StepTypeEnum.IN_APP, - controlValues: { - content: 'New event: {{eventName}} on {{eventDate}}', - title: 'New Event', - ctaLabel: 'View Event', - }, - }, { name: 'Email Notification', type: StepTypeEnum.EMAIL, controlValues: { - subject: 'New Event: {{eventName}}', - content: { - type: 'doc', - content: [ - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'Hello ', - }, - { - type: 'variable', - attrs: { value: 'subscriberFirstName' }, - }, - { - type: 'text', - text: ',', - }, - ], - }, - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'A new event has been scheduled: ', - }, - { - type: 'variable', - attrs: { value: 'eventName' }, - }, - ], - }, - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'Date: ', - }, - { - type: 'variable', - attrs: { value: 'eventDate' }, + subject: { + component: UiComponentEnum.TEXT_FULL_LINE, + value: 'New Event: {{payload.eventName}}', + }, + body: { + component: UiComponentEnum.BLOCK_EDITOR, + value: { + type: 'doc', + content: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Hi ', + }, + { + type: 'variable', + attrs: { value: 'subscriber.firstName' }, + }, + { + type: 'text', + text: ',', + }, + ], + }, + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'A new event has been scheduled: ', + }, + { + type: 'variable', + attrs: { value: 'payload.eventName' }, + }, + ], + }, + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Date: ', + }, + { + type: 'variable', + attrs: { value: 'payload.eventDate' }, + }, + ], + }, + { + type: 'button', + attrs: { + url: '{{payload.eventUrl}}', + text: 'View Event Details', }, - ], - }, - { - type: 'button', - attrs: { - url: '{{eventUrl}}', - text: 'View Event Details', }, - }, - ], + ], + }, + }, + }, + }, + { + name: 'Push Notification', + type: StepTypeEnum.PUSH, + controlValues: { + subject: { + component: UiComponentEnum.PUSH_SUBJECT, + value: 'New Event: {{payload.eventName}}', + }, + body: { + component: UiComponentEnum.PUSH_BODY, + value: 'Event scheduled for {{payload.eventDate}}', + }, + data: { + url: '{{payload.eventUrl}}', }, }, }, @@ -261,83 +307,82 @@ const WORKFLOW_TEMPLATES: WorkflowTemplate[] = [ workflowId: 'password-reset', steps: [ { - name: 'Email Instructions', + name: 'Email Notification', type: StepTypeEnum.EMAIL, controlValues: { - subject: 'Reset Your Password', - content: { - type: 'doc', - content: [ - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'Hi ', - }, - { - type: 'variable', - attrs: { value: 'subscriberFirstName' }, - }, - { - type: 'text', - text: ',', - }, - ], - }, - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'We received a request to reset your password. Click the button below to reset it:', - }, - ], - }, - { - type: 'button', - attrs: { - url: '{{resetUrl}}', - text: 'Reset Password', + subject: { + component: UiComponentEnum.TEXT_FULL_LINE, + value: 'Reset Your Password', + }, + body: { + component: UiComponentEnum.BLOCK_EDITOR, + value: { + type: 'doc', + content: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Hi ', + }, + { + type: 'variable', + attrs: { value: 'subscriber.firstName' }, + }, + { + type: 'text', + text: ',', + }, + ], + }, + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'We received a request to reset your password. Click the button below to reset it:', + }, + ], }, - }, - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'If you did not request this change, please ignore this email.', + { + type: 'button', + attrs: { + url: '{{payload.resetUrl}}', + text: 'Reset Password', }, - ], - }, - ], + }, + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'If you did not request a password reset, please ignore this email.', + }, + ], + }, + ], + }, }, }, }, - { - name: 'SMS Code', - type: StepTypeEnum.SMS, - controlValues: { - content: 'Your password reset code is: {{resetCode}}. This code will expire in 10 minutes.', - }, - }, ], category: 'authentication', - tags: ['auth', 'password', 'security'], + tags: ['password', 'security', 'authentication'], active: true, __source: WorkflowCreationSourceEnum.TEMPLATE_STORE, }, { id: 'new-follower', name: 'New Follower', - description: 'Notify user when they get a new follower', + description: 'Notify users when they gain a new follower', workflowId: 'new-follower', steps: [ { - name: 'In-App Alert', + name: 'In-App Notification', type: StepTypeEnum.IN_APP, controlValues: { - content: '{{followerName}} started following you', + content: '{{payload.followerName}} started following you', title: 'New Follower', ctaLabel: 'View Profile', }, @@ -346,232 +391,181 @@ const WORKFLOW_TEMPLATES: WorkflowTemplate[] = [ name: 'Push Notification', type: StepTypeEnum.PUSH, controlValues: { - title: 'New Follower', - content: '{{followerName}} started following you', + subject: { + component: UiComponentEnum.PUSH_SUBJECT, + value: 'New Follower', + }, + body: { + component: UiComponentEnum.PUSH_BODY, + value: '{{payload.followerName}} started following you', + }, data: { - url: '{{followerProfileUrl}}', + url: '{{payload.followerProfileUrl}}', }, }, }, ], category: 'social', - tags: ['social', 'follower', 'notification'], + tags: ['follower', 'social', 'notification'], active: true, __source: WorkflowCreationSourceEnum.TEMPLATE_STORE, }, { id: 'welcome-message', name: 'Welcome Message', - description: 'Send welcome message to new users', + description: 'Send a welcome message to new users', workflowId: 'welcome-message', steps: [ { - name: 'Welcome Email', + name: 'Email Notification', type: StepTypeEnum.EMAIL, controlValues: { - subject: 'Welcome to Our Platform!', - content: { - type: 'doc', - content: [ - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'Welcome ', - }, - { - type: 'variable', - attrs: { value: 'subscriberFirstName' }, - }, - { - type: 'text', - text: '!', - }, - ], - }, - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'We are excited to have you on board. Here are some resources to help you get started:', - }, - ], - }, - { - type: 'bulletList', - content: [ - { - type: 'listItem', - content: [ - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'Quick Start Guide', - }, - ], - }, - ], - }, - { - type: 'listItem', - content: [ - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'Documentation', - }, - ], - }, - ], + subject: { + component: UiComponentEnum.TEXT_FULL_LINE, + value: 'Welcome to Our Platform!', + }, + body: { + component: UiComponentEnum.BLOCK_EDITOR, + value: { + type: 'doc', + content: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Hi ', + }, + { + type: 'variable', + attrs: { value: 'subscriber.firstName' }, + }, + { + type: 'text', + text: ',', + }, + ], + }, + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'We are excited to have you on board. Here are some resources to help you get started:', + }, + ], + }, + { + type: 'button', + attrs: { + url: '{{payload.gettingStartedUrl}}', + text: 'Getting Started Guide', }, - ], - }, - { - type: 'button', - attrs: { - url: '{{dashboardUrl}}', - text: 'Go to Dashboard', }, - }, - ], + ], + }, }, }, }, - { - name: 'Welcome Notification', - type: StepTypeEnum.IN_APP, - controlValues: { - content: 'Welcome to our platform! Click here to get started.', - title: 'Welcome!', - ctaLabel: 'Get Started', - }, - }, ], - category: 'popular', - tags: ['onboarding', 'welcome'], + category: 'authentication', + tags: ['welcome', 'onboarding'], active: true, __source: WorkflowCreationSourceEnum.TEMPLATE_STORE, }, { id: 'event-reminder', name: 'Event Reminder', - description: 'Send event reminders to participants', + description: 'Send reminders for upcoming events', workflowId: 'event-reminder', steps: [ { - name: 'Email Reminder', + name: 'Email Notification', type: StepTypeEnum.EMAIL, controlValues: { - subject: 'Reminder: {{eventName}} starts in 24 hours', - content: { - type: 'doc', - content: [ - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'Hi ', - }, - { - type: 'variable', - attrs: { value: 'subscriberFirstName' }, - }, - { - type: 'text', - text: ',', - }, - ], - }, - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'This is a reminder that ', - }, - { - type: 'variable', - attrs: { value: 'eventName' }, - }, - { - type: 'text', - text: ' starts in 24 hours.', - }, - ], - }, - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'Event details:', - }, - ], - }, - { - type: 'bulletList', - content: [ - { - type: 'listItem', - content: [ - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'Date: ', - }, - { - type: 'variable', - attrs: { value: 'eventDate' }, - }, - ], - }, - ], - }, - { - type: 'listItem', - content: [ - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'Location: ', - }, - { - type: 'variable', - attrs: { value: 'eventLocation' }, - }, - ], - }, - ], + subject: { + component: UiComponentEnum.TEXT_FULL_LINE, + value: 'Reminder: {{payload.eventName}} starts in 24 hours', + }, + body: { + component: UiComponentEnum.BLOCK_EDITOR, + value: { + type: 'doc', + content: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Hi ', + }, + { + type: 'variable', + attrs: { value: 'subscriber.firstName' }, + }, + { + type: 'text', + text: ',', + }, + ], + }, + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'This is a reminder that ', + }, + { + type: 'variable', + attrs: { value: 'payload.eventName' }, + }, + { + type: 'text', + text: ' starts in 24 hours.', + }, + ], + }, + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Date: ', + }, + { + type: 'variable', + attrs: { value: 'payload.eventDate' }, + }, + ], + }, + { + type: 'button', + attrs: { + url: '{{payload.eventUrl}}', + text: 'View Event Details', }, - ], - }, - { - type: 'button', - attrs: { - url: '{{eventUrl}}', - text: 'View Event Details', }, - }, - ], + ], + }, }, }, }, { - name: 'SMS Reminder', - type: StepTypeEnum.SMS, + name: 'Push Notification', + type: StepTypeEnum.PUSH, controlValues: { - content: 'Reminder: {{eventName}} starts in 24 hours at {{eventLocation}}. Details: {{eventUrl}}', + subject: { + component: UiComponentEnum.PUSH_SUBJECT, + value: 'Event Reminder: {{payload.eventName}}', + }, + body: { + component: UiComponentEnum.PUSH_BODY, + value: 'Your event starts in 24 hours', + }, + data: { + url: '{{payload.eventUrl}}', + }, }, }, ], @@ -615,11 +609,22 @@ export function WorkflowTemplateModal(props: WorkflowTemplateModalProps) {
{filteredTemplates.map((template) => ( - + step.type as StepType)} + steps={template.steps.map((step) => step.type)} /> ))} From c3139f0377b078062abcba04802c5746f4570fe9 Mon Sep 17 00:00:00 2001 From: Dima Grossman Date: Sat, 28 Dec 2024 23:44:41 +0200 Subject: [PATCH 04/54] fix: --- apps/dashboard/src/components/workflow-editor/add-step-menu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dashboard/src/components/workflow-editor/add-step-menu.tsx b/apps/dashboard/src/components/workflow-editor/add-step-menu.tsx index 732e836ae72..3efa6fc3f08 100644 --- a/apps/dashboard/src/components/workflow-editor/add-step-menu.tsx +++ b/apps/dashboard/src/components/workflow-editor/add-step-menu.tsx @@ -30,7 +30,7 @@ const MenuItemsGroup = ({ children }: { children: ReactNode }) => { const MenuItem = ({ children, stepType, - disabled = true, + disabled = false, onClick, }: { children: ReactNode; From 93870d9eb57aa0f1236fdd9d0592405fc7a409f0 Mon Sep 17 00:00:00 2001 From: Dima Grossman Date: Sun, 29 Dec 2024 09:34:01 +0200 Subject: [PATCH 05/54] fix: structure --- .../workflow-template-modal.tsx | 631 +++--------------- 1 file changed, 101 insertions(+), 530 deletions(-) diff --git a/apps/dashboard/src/components/template-store/workflow-template-modal.tsx b/apps/dashboard/src/components/template-store/workflow-template-modal.tsx index 17012d90960..5cc75380d7a 100644 --- a/apps/dashboard/src/components/template-store/workflow-template-modal.tsx +++ b/apps/dashboard/src/components/template-store/workflow-template-modal.tsx @@ -1,9 +1,5 @@ import { ComponentProps, useState } from 'react'; import { RiArrowRightSLine } from 'react-icons/ri'; -import { BiCodeAlt } from 'react-icons/bi'; -import { BsLightning } from 'react-icons/bs'; -import { FiLock } from 'react-icons/fi'; -import { HiOutlineUsers } from 'react-icons/hi'; import { Dialog, DialogContent, DialogHeader, DialogTrigger } from '@/components/primitives/dialog'; import { Button } from '@/components/primitives/button'; @@ -13,83 +9,52 @@ import { WorkflowSidebar } from './workflow-sidebar'; import { RouteFill } from '../icons'; import { Form } from '../primitives/form/form'; import { useForm } from 'react-hook-form'; -import { StepTypeEnum, WorkflowCreationSourceEnum, UiComponentEnum } from '@novu/shared'; +import { + StepTypeEnum, + WorkflowCreationSourceEnum, + CreateWorkflowDto, + StepCreateDto, + WorkflowOriginEnum, +} from '@novu/shared'; -const CATEGORIES = [ - { - id: 'popular', - name: 'Popular', - icon: BsLightning, - }, - { - id: 'events', - name: 'Events', - icon: HiOutlineUsers, - }, - { - id: 'authentication', - name: 'Authentication', - icon: FiLock, - }, - { - id: 'social', - name: 'Social', - icon: HiOutlineUsers, - }, -] as const; +interface TipTapContent { + type: string; + content?: Array<{ + type: string; + content?: Array<{ + type: string; + text?: string; + attrs?: { + value?: string; + url?: string; + text?: string; + }; + }>; + attrs?: { + url?: string; + text?: string; + }; + }>; +} -const CREATE_OPTIONS = [ - { - id: 'code-based', - name: 'Code-based workflow', - icon: BiCodeAlt, - }, - { - id: 'blank', - name: 'Blank workflow', - icon: BsLightning, - }, -] as const; +type WorkflowTemplateModalProps = ComponentProps; -interface WorkflowTemplate { +type WorkflowTemplateWithExtras = Omit & { id: string; - name: string; - description: string; - steps: { - name: string; - type: StepTypeEnum; - controlValues?: Record | null; - }[]; category: 'popular' | 'events' | 'authentication' | 'social'; - tags: string[]; active?: boolean; - workflowId: string; - __source: WorkflowCreationSourceEnum; -} - -type WorkflowTemplateModalProps = ComponentProps; - -// For email steps, we need to update the controlValues to match the email editor structure -const EMAIL_TEMPLATE = { - subject: { - component: UiComponentEnum.TEXT_FULL_LINE, - }, - body: { - component: UiComponentEnum.BLOCK_EDITOR, - }, -}; - -// For push steps, we need to update the controlValues to match the push editor structure -const PUSH_TEMPLATE = { - subject: { - component: UiComponentEnum.PUSH_SUBJECT, - }, - body: { - component: UiComponentEnum.PUSH_BODY, - }, + tags: string[]; + description: string; + steps: Array< + StepCreateDto & { + stepId: string; + slug: string; + origin: WorkflowOriginEnum; + } + >; }; -const WORKFLOW_TEMPLATES: WorkflowTemplate[] = [ +const WORKFLOW_TEMPLATES: WorkflowTemplateWithExtras[] = [ { id: 'mention-notification', name: 'Mention in a comment', @@ -99,478 +64,84 @@ const WORKFLOW_TEMPLATES: WorkflowTemplate[] = [ { name: 'In-App Notification', type: StepTypeEnum.IN_APP, + stepId: 'in-app-notification', + slug: 'in-app-notification', + origin: WorkflowOriginEnum.NOVU_CLOUD, controlValues: { - content: 'You were mentioned in a comment by {{payload.actorName}}', - title: 'New Mention', - ctaLabel: 'View Comment', - }, - }, - { - name: 'Email Notification', - type: StepTypeEnum.EMAIL, - controlValues: { - subject: { - component: UiComponentEnum.TEXT_FULL_LINE, - value: 'You were mentioned in a comment', - }, - body: { - component: UiComponentEnum.BLOCK_EDITOR, - value: { - type: 'doc', - content: [ - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'Hi ', - }, - { - type: 'variable', - attrs: { value: 'subscriber.firstName' }, - }, - { - type: 'text', - text: ',', - }, - ], - }, - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'You were mentioned in a comment by ', - }, - { - type: 'variable', - attrs: { value: 'payload.actorName' }, - }, - { - type: 'text', - text: '.', - }, - ], - }, - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'Click the button below to view the comment:', - }, - ], - }, - { - type: 'button', - attrs: { - url: '{{payload.commentUrl}}', - text: 'View Comment', - }, - }, - ], - }, - }, - }, - }, - { - name: 'SMS Alert', - type: StepTypeEnum.SMS, - controlValues: { - content: 'You were mentioned in a comment by {{payload.actorName}}. Click {{payload.commentUrl}} to view.', - }, - }, - { - name: 'Push Notification', - type: StepTypeEnum.PUSH, - controlValues: { - subject: { - component: UiComponentEnum.PUSH_SUBJECT, - value: 'New Mention', - }, - body: { - component: UiComponentEnum.PUSH_BODY, - value: 'You were mentioned in a comment by {{payload.actorName}}', - }, - data: { - url: '{{payload.commentUrl}}', - }, - }, - }, - ], - category: 'popular', - tags: ['mention', 'comment', 'notification'], - active: true, - __source: WorkflowCreationSourceEnum.TEMPLATE_STORE, - }, - { - id: 'event-notification', - name: 'Event Notification', - description: 'Notify users about upcoming events', - workflowId: 'event-notification', - steps: [ - { - name: 'Email Notification', - type: StepTypeEnum.EMAIL, - controlValues: { - subject: { - component: UiComponentEnum.TEXT_FULL_LINE, - value: 'New Event: {{payload.eventName}}', - }, - body: { - component: UiComponentEnum.BLOCK_EDITOR, - value: { - type: 'doc', - content: [ - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'Hi ', - }, - { - type: 'variable', - attrs: { value: 'subscriber.firstName' }, - }, - { - type: 'text', - text: ',', - }, - ], - }, - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'A new event has been scheduled: ', - }, - { - type: 'variable', - attrs: { value: 'payload.eventName' }, - }, - ], - }, - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'Date: ', - }, - { - type: 'variable', - attrs: { value: 'payload.eventDate' }, - }, - ], - }, - { - type: 'button', - attrs: { - url: '{{payload.eventUrl}}', - text: 'View Event Details', - }, - }, - ], - }, - }, - }, - }, - { - name: 'Push Notification', - type: StepTypeEnum.PUSH, - controlValues: { - subject: { - component: UiComponentEnum.PUSH_SUBJECT, - value: 'New Event: {{payload.eventName}}', - }, - body: { - component: UiComponentEnum.PUSH_BODY, - value: 'Event scheduled for {{payload.eventDate}}', - }, - data: { - url: '{{payload.eventUrl}}', - }, - }, - }, - ], - category: 'events', - tags: ['event', 'notification'], - active: true, - __source: WorkflowCreationSourceEnum.TEMPLATE_STORE, - }, - { - id: 'password-reset', - name: 'Password Reset', - description: 'Send password reset instructions', - workflowId: 'password-reset', - steps: [ - { - name: 'Email Notification', - type: StepTypeEnum.EMAIL, - controlValues: { - subject: { - component: UiComponentEnum.TEXT_FULL_LINE, - value: 'Reset Your Password', - }, - body: { - component: UiComponentEnum.BLOCK_EDITOR, - value: { - type: 'doc', - content: [ - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'Hi ', - }, - { - type: 'variable', - attrs: { value: 'subscriber.firstName' }, - }, - { - type: 'text', - text: ',', - }, - ], - }, - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'We received a request to reset your password. Click the button below to reset it:', - }, - ], - }, - { - type: 'button', - attrs: { - url: '{{payload.resetUrl}}', - text: 'Reset Password', - }, - }, - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'If you did not request a password reset, please ignore this email.', - }, - ], - }, - ], + body: 'You were mentioned in a comment by {{payload.actorName}}', + avatar: '', + subject: 'New Mention', + primaryAction: { + label: 'View Comment', + redirect: { + url: '{{payload.commentUrl}}', + target: '_blank', }, }, - }, - }, - ], - category: 'authentication', - tags: ['password', 'security', 'authentication'], - active: true, - __source: WorkflowCreationSourceEnum.TEMPLATE_STORE, - }, - { - id: 'new-follower', - name: 'New Follower', - description: 'Notify users when they gain a new follower', - workflowId: 'new-follower', - steps: [ - { - name: 'In-App Notification', - type: StepTypeEnum.IN_APP, - controlValues: { - content: '{{payload.followerName}} started following you', - title: 'New Follower', - ctaLabel: 'View Profile', - }, - }, - { - name: 'Push Notification', - type: StepTypeEnum.PUSH, - controlValues: { - subject: { - component: UiComponentEnum.PUSH_SUBJECT, - value: 'New Follower', - }, - body: { - component: UiComponentEnum.PUSH_BODY, - value: '{{payload.followerName}} started following you', - }, - data: { - url: '{{payload.followerProfileUrl}}', + secondaryAction: null, + redirect: { + url: '', + target: '_self', }, }, }, - ], - category: 'social', - tags: ['follower', 'social', 'notification'], - active: true, - __source: WorkflowCreationSourceEnum.TEMPLATE_STORE, - }, - { - id: 'welcome-message', - name: 'Welcome Message', - description: 'Send a welcome message to new users', - workflowId: 'welcome-message', - steps: [ { name: 'Email Notification', type: StepTypeEnum.EMAIL, + stepId: 'email-notification', + slug: 'email-notification', + origin: WorkflowOriginEnum.NOVU_CLOUD, controlValues: { - subject: { - component: UiComponentEnum.TEXT_FULL_LINE, - value: 'Welcome to Our Platform!', - }, - body: { - component: UiComponentEnum.BLOCK_EDITOR, - value: { - type: 'doc', - content: [ - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'Hi ', - }, - { - type: 'variable', - attrs: { value: 'subscriber.firstName' }, - }, - { - type: 'text', - text: ',', - }, - ], - }, - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'We are excited to have you on board. Here are some resources to help you get started:', - }, - ], - }, - { - type: 'button', - attrs: { - url: '{{payload.gettingStartedUrl}}', - text: 'Getting Started Guide', + subject: 'You were mentioned in a comment', + body: JSON.stringify({ + type: 'doc', + content: [ + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { type: 'text', text: 'Hi ' }, + { + type: 'variable', + attrs: { id: 'subscriber.firstName', label: null, fallback: null, required: false }, }, - }, - ], - }, - }, - }, - }, - ], - category: 'authentication', - tags: ['welcome', 'onboarding'], - active: true, - __source: WorkflowCreationSourceEnum.TEMPLATE_STORE, - }, - { - id: 'event-reminder', - name: 'Event Reminder', - description: 'Send reminders for upcoming events', - workflowId: 'event-reminder', - steps: [ - { - name: 'Email Notification', - type: StepTypeEnum.EMAIL, - controlValues: { - subject: { - component: UiComponentEnum.TEXT_FULL_LINE, - value: 'Reminder: {{payload.eventName}} starts in 24 hours', - }, - body: { - component: UiComponentEnum.BLOCK_EDITOR, - value: { - type: 'doc', - content: [ - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'Hi ', - }, - { - type: 'variable', - attrs: { value: 'subscriber.firstName' }, - }, - { - type: 'text', - text: ',', - }, - ], - }, - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'This is a reminder that ', - }, - { - type: 'variable', - attrs: { value: 'payload.eventName' }, - }, - { - type: 'text', - text: ' starts in 24 hours.', - }, - ], - }, - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'Date: ', - }, - { - type: 'variable', - attrs: { value: 'payload.eventDate' }, - }, - ], - }, - { - type: 'button', - attrs: { - url: '{{payload.eventUrl}}', - text: 'View Event Details', + { type: 'text', text: ',' }, + ], + }, + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { type: 'text', text: 'You were mentioned in a comment by ' }, + { + type: 'variable', + attrs: { id: 'payload.actorName', label: null, fallback: null, required: false }, }, - }, - ], - }, - }, - }, - }, - { - name: 'Push Notification', - type: StepTypeEnum.PUSH, - controlValues: { - subject: { - component: UiComponentEnum.PUSH_SUBJECT, - value: 'Event Reminder: {{payload.eventName}}', - }, - body: { - component: UiComponentEnum.PUSH_BODY, - value: 'Your event starts in 24 hours', - }, - data: { - url: '{{payload.eventUrl}}', - }, + { type: 'text', text: '.' }, + ], + }, + { + type: 'button', + attrs: { + text: 'View Comment', + isTextVariable: false, + url: '', + isUrlVariable: false, + alignment: 'left', + variant: 'filled', + borderRadius: 'smooth', + buttonColor: '#000000', + textColor: '#ffffff', + showIfKey: null, + }, + }, + ], + }), }, }, ], - category: 'events', - tags: ['event', 'reminder'], + category: 'popular', + tags: ['mention', 'comment', 'notification'], active: true, __source: WorkflowCreationSourceEnum.TEMPLATE_STORE, }, From c8f72ac356b5939db564112e91d75a346d2422d2 Mon Sep 17 00:00:00 2001 From: Dima Grossman Date: Sun, 29 Dec 2024 09:40:17 +0200 Subject: [PATCH 06/54] fix: wip --- .../src/components/create-workflow-button.tsx | 17 +- .../workflow-template-modal.tsx | 216 +++++++----------- 2 files changed, 88 insertions(+), 145 deletions(-) diff --git a/apps/dashboard/src/components/create-workflow-button.tsx b/apps/dashboard/src/components/create-workflow-button.tsx index 56bb460d05b..c37227d06a4 100644 --- a/apps/dashboard/src/components/create-workflow-button.tsx +++ b/apps/dashboard/src/components/create-workflow-button.tsx @@ -6,7 +6,7 @@ import { RiArrowRightSLine } from 'react-icons/ri'; import { ExternalLink } from '@/components/shared/external-link'; import { useNavigate } from 'react-router-dom'; import { z } from 'zod'; -import { type CreateWorkflowDto, WorkflowCreationSourceEnum, slugify, StepTypeEnum } from '@novu/shared'; +import { type CreateWorkflowDto, WorkflowCreationSourceEnum, slugify } from '@novu/shared'; import { createWorkflow } from '@/api/workflows'; import { Button } from '@/components/primitives/button'; import { FormField, FormItem, FormLabel, FormControl, FormMessage, Form } from '@/components/primitives/form/form'; @@ -32,18 +32,7 @@ import { AUTOCOMPLETE_PASSWORD_MANAGERS_OFF } from '@/utils/constants'; import { MAX_DESCRIPTION_LENGTH, MAX_TAG_ELEMENTS, workflowSchema } from './workflow-editor/schema'; interface CreateWorkflowButtonProps extends ComponentProps { - template?: { - name: string; - description?: string; - workflowId: string; - tags: string[]; - steps: { - name: string; - type: StepTypeEnum; - controlValues?: Record | null; - }[]; - __source: WorkflowCreationSourceEnum; - }; + template?: CreateWorkflowDto; } export const CreateWorkflowButton = ({ template, ...props }: CreateWorkflowButtonProps) => { @@ -110,7 +99,7 @@ export const CreateWorkflowButton = ({ template, ...props }: CreateWorkflowButto __source: template?.__source ?? WorkflowCreationSourceEnum.DASHBOARD, workflowId: values.workflowId, description: values.description || undefined, - tags: values.tags, + tags: values.tags || [], }); })} className="flex flex-col gap-4" diff --git a/apps/dashboard/src/components/template-store/workflow-template-modal.tsx b/apps/dashboard/src/components/template-store/workflow-template-modal.tsx index 5cc75380d7a..99cce6fc3b8 100644 --- a/apps/dashboard/src/components/template-store/workflow-template-modal.tsx +++ b/apps/dashboard/src/components/template-store/workflow-template-modal.tsx @@ -9,141 +9,106 @@ import { WorkflowSidebar } from './workflow-sidebar'; import { RouteFill } from '../icons'; import { Form } from '../primitives/form/form'; import { useForm } from 'react-hook-form'; -import { - StepTypeEnum, - WorkflowCreationSourceEnum, - CreateWorkflowDto, - StepCreateDto, - WorkflowOriginEnum, -} from '@novu/shared'; - -interface TipTapContent { - type: string; - content?: Array<{ - type: string; - content?: Array<{ - type: string; - text?: string; - attrs?: { - value?: string; - url?: string; - text?: string; - }; - }>; - attrs?: { - url?: string; - text?: string; - }; - }>; -} +import { StepTypeEnum, WorkflowCreationSourceEnum, CreateWorkflowDto } from '@novu/shared'; type WorkflowTemplateModalProps = ComponentProps; -type WorkflowTemplateWithExtras = Omit & { +interface WorkflowTemplate { id: string; - category: 'popular' | 'events' | 'authentication' | 'social'; - active?: boolean; - tags: string[]; + name: string; description: string; - steps: Array< - StepCreateDto & { - stepId: string; - slug: string; - origin: WorkflowOriginEnum; - } - >; -}; + category: 'popular' | 'events' | 'authentication' | 'social'; + workflowDefinition: CreateWorkflowDto; +} -const WORKFLOW_TEMPLATES: WorkflowTemplateWithExtras[] = [ +const WORKFLOW_TEMPLATES: WorkflowTemplate[] = [ { id: 'mention-notification', name: 'Mention in a comment', description: 'Triggered when an actor mentions someone', - workflowId: 'mention-notification', - steps: [ - { - name: 'In-App Notification', - type: StepTypeEnum.IN_APP, - stepId: 'in-app-notification', - slug: 'in-app-notification', - origin: WorkflowOriginEnum.NOVU_CLOUD, - controlValues: { - body: 'You were mentioned in a comment by {{payload.actorName}}', - avatar: '', - subject: 'New Mention', - primaryAction: { - label: 'View Comment', + category: 'popular', + workflowDefinition: { + name: 'Mention in a comment', + description: 'Triggered when an actor mentions someone', + workflowId: 'mention-notification', + steps: [ + { + name: 'In-App Notification', + type: StepTypeEnum.IN_APP, + controlValues: { + body: 'You were mentioned in a comment by {{payload.actorName}}', + avatar: '', + subject: 'New Mention', + primaryAction: { + label: 'View Comment', + redirect: { + url: '{{payload.commentUrl}}', + target: '_blank', + }, + }, + secondaryAction: null, redirect: { - url: '{{payload.commentUrl}}', - target: '_blank', + url: '', + target: '_self', }, }, - secondaryAction: null, - redirect: { - url: '', - target: '_self', - }, }, - }, - { - name: 'Email Notification', - type: StepTypeEnum.EMAIL, - stepId: 'email-notification', - slug: 'email-notification', - origin: WorkflowOriginEnum.NOVU_CLOUD, - controlValues: { - subject: 'You were mentioned in a comment', - body: JSON.stringify({ - type: 'doc', - content: [ - { - type: 'paragraph', - attrs: { textAlign: 'left' }, - content: [ - { type: 'text', text: 'Hi ' }, - { - type: 'variable', - attrs: { id: 'subscriber.firstName', label: null, fallback: null, required: false }, - }, - { type: 'text', text: ',' }, - ], - }, - { - type: 'paragraph', - attrs: { textAlign: 'left' }, - content: [ - { type: 'text', text: 'You were mentioned in a comment by ' }, - { - type: 'variable', - attrs: { id: 'payload.actorName', label: null, fallback: null, required: false }, + { + name: 'Email Notification', + type: StepTypeEnum.EMAIL, + controlValues: { + subject: 'You were mentioned in a comment', + body: JSON.stringify({ + type: 'doc', + content: [ + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { type: 'text', text: 'Hi ' }, + { + type: 'variable', + attrs: { id: 'subscriber.firstName', label: null, fallback: null, required: false }, + }, + { type: 'text', text: ',' }, + ], + }, + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { type: 'text', text: 'You were mentioned in a comment by ' }, + { + type: 'variable', + attrs: { id: 'payload.actorName', label: null, fallback: null, required: false }, + }, + { type: 'text', text: '.' }, + ], + }, + { + type: 'button', + attrs: { + text: 'View Comment', + isTextVariable: false, + url: '', + isUrlVariable: false, + alignment: 'left', + variant: 'filled', + borderRadius: 'smooth', + buttonColor: '#000000', + textColor: '#ffffff', + showIfKey: null, }, - { type: 'text', text: '.' }, - ], - }, - { - type: 'button', - attrs: { - text: 'View Comment', - isTextVariable: false, - url: '', - isUrlVariable: false, - alignment: 'left', - variant: 'filled', - borderRadius: 'smooth', - buttonColor: '#000000', - textColor: '#ffffff', - showIfKey: null, }, - }, - ], - }), + ], + }), + }, }, - }, - ], - category: 'popular', - tags: ['mention', 'comment', 'notification'], - active: true, - __source: WorkflowCreationSourceEnum.TEMPLATE_STORE, + ], + tags: ['mention', 'comment', 'notification'], + active: true, + __source: WorkflowCreationSourceEnum.TEMPLATE_STORE, + }, }, ]; @@ -180,22 +145,11 @@ export function WorkflowTemplateModal(props: WorkflowTemplateModalProps) {
{filteredTemplates.map((template) => ( - + step.type)} + steps={template.workflowDefinition.steps.map((step) => step.type)} /> ))} From f90257bfb287804fb3010df329b539c28a825afc Mon Sep 17 00:00:00 2001 From: Dima Grossman Date: Sun, 29 Dec 2024 09:49:42 +0200 Subject: [PATCH 07/54] feat: add new templates --- .../template-store/templates/index.ts | 20 +++ .../template-store/templates/login-alert.ts | 125 +++++++++++++ .../templates/mention-notification.ts | 91 ++++++++++ .../templates/payment-failed.ts | 133 ++++++++++++++ .../templates/team-invitation.ts | 120 +++++++++++++ .../templates/trial-expiration.ts | 147 ++++++++++++++++ .../template-store/templates/types.ts | 9 + .../template-store/templates/usage-limit.ts | 166 ++++++++++++++++++ .../workflow-template-modal.tsx | 107 +---------- .../src/components/workflow-editor/schema.ts | 1 - 10 files changed, 813 insertions(+), 106 deletions(-) create mode 100644 apps/dashboard/src/components/template-store/templates/index.ts create mode 100644 apps/dashboard/src/components/template-store/templates/login-alert.ts create mode 100644 apps/dashboard/src/components/template-store/templates/mention-notification.ts create mode 100644 apps/dashboard/src/components/template-store/templates/payment-failed.ts create mode 100644 apps/dashboard/src/components/template-store/templates/team-invitation.ts create mode 100644 apps/dashboard/src/components/template-store/templates/trial-expiration.ts create mode 100644 apps/dashboard/src/components/template-store/templates/types.ts create mode 100644 apps/dashboard/src/components/template-store/templates/usage-limit.ts diff --git a/apps/dashboard/src/components/template-store/templates/index.ts b/apps/dashboard/src/components/template-store/templates/index.ts new file mode 100644 index 00000000000..e0b342687cb --- /dev/null +++ b/apps/dashboard/src/components/template-store/templates/index.ts @@ -0,0 +1,20 @@ +import { loginAlertTemplate } from './login-alert'; +import { mentionNotificationTemplate } from './mention-notification'; +import { paymentFailedTemplate } from './payment-failed'; +import { teamInvitationTemplate } from './team-invitation'; +import { trialExpirationTemplate } from './trial-expiration'; +import { usageLimitTemplate } from './usage-limit'; +import { WorkflowTemplate } from './types'; + +export function getTemplates(): WorkflowTemplate[] { + return [ + mentionNotificationTemplate, + loginAlertTemplate, + trialExpirationTemplate, + paymentFailedTemplate, + teamInvitationTemplate, + usageLimitTemplate, + ]; +} + +export * from './types'; diff --git a/apps/dashboard/src/components/template-store/templates/login-alert.ts b/apps/dashboard/src/components/template-store/templates/login-alert.ts new file mode 100644 index 00000000000..9bf9553d8a6 --- /dev/null +++ b/apps/dashboard/src/components/template-store/templates/login-alert.ts @@ -0,0 +1,125 @@ +import { StepTypeEnum, WorkflowCreationSourceEnum } from '@novu/shared'; +import { WorkflowTemplate } from './types'; + +export const loginAlertTemplate: WorkflowTemplate = { + id: 'login-alert', + name: 'New device login', + description: 'Alert users when a new device signs into their account', + category: 'popular', + workflowDefinition: { + name: 'New device login', + description: 'Alert users when a new device signs into their account', + workflowId: 'login-alert', + steps: [ + { + name: 'Email Alert', + type: StepTypeEnum.EMAIL, + controlValues: { + subject: 'New login detected on your account', + body: JSON.stringify({ + type: 'doc', + content: [ + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { type: 'text', text: 'Hi ' }, + { + type: 'variable', + attrs: { id: 'subscriber.firstName', label: null, fallback: null, required: false }, + }, + { type: 'text', text: ',' }, + ], + }, + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { type: 'text', text: 'We detected a new login to your account from a ' }, + { + type: 'variable', + attrs: { id: 'payload.deviceType', label: null, fallback: 'new device', required: false }, + }, + { type: 'text', text: ' in ' }, + { + type: 'variable', + attrs: { id: 'payload.location', label: null, fallback: 'an unknown location', required: false }, + }, + { type: 'text', text: '.' }, + ], + }, + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { type: 'text', text: 'Time: ' }, + { + type: 'variable', + attrs: { id: 'payload.time', label: null, fallback: null, required: true }, + }, + ], + }, + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { type: 'text', text: 'IP Address: ' }, + { + type: 'variable', + attrs: { id: 'payload.ipAddress', label: null, fallback: 'Unknown', required: false }, + }, + ], + }, + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { + type: 'text', + text: "If this wasn't you, please secure your account immediately by resetting your password.", + }, + ], + }, + { + type: 'button', + attrs: { + text: 'Reset Password', + isTextVariable: false, + url: '{{payload.resetPasswordUrl}}', + isUrlVariable: true, + alignment: 'left', + variant: 'filled', + borderRadius: 'smooth', + buttonColor: '#E53E3E', + textColor: '#ffffff', + showIfKey: null, + }, + }, + ], + }), + }, + }, + { + name: 'In-App Alert', + type: StepTypeEnum.IN_APP, + controlValues: { + subject: 'New login from {{payload.location}}', + body: 'New login detected from {{payload.deviceType}} in {{payload.location}}. Time: {{payload.time}}', + action: { + status: 'warning', + buttons: [ + { + content: 'Review Activity', + buttonColor: '#E53E3E', + textColor: '#ffffff', + }, + ], + }, + }, + }, + ], + tags: ['security', 'login', 'authentication'], + active: true, + __source: WorkflowCreationSourceEnum.TEMPLATE_STORE, + }, +}; diff --git a/apps/dashboard/src/components/template-store/templates/mention-notification.ts b/apps/dashboard/src/components/template-store/templates/mention-notification.ts new file mode 100644 index 00000000000..e75e8b9a01d --- /dev/null +++ b/apps/dashboard/src/components/template-store/templates/mention-notification.ts @@ -0,0 +1,91 @@ +import { StepTypeEnum, WorkflowCreationSourceEnum } from '@novu/shared'; +import { WorkflowTemplate } from './types'; + +export const mentionNotificationTemplate: WorkflowTemplate = { + id: 'mention-notification', + name: 'Mention in a comment', + description: 'Triggered when an actor mentions someone', + category: 'popular', + workflowDefinition: { + name: 'Mention in a comment', + description: 'Triggered when an actor mentions someone', + workflowId: 'mention-notification', + steps: [ + { + name: 'In-App Notification', + type: StepTypeEnum.IN_APP, + controlValues: { + body: 'You were mentioned in a comment by {{payload.actorName}}', + avatar: '', + subject: 'New Mention', + primaryAction: { + label: 'View Comment', + redirect: { + url: '{{payload.commentUrl}}', + target: '_blank', + }, + }, + secondaryAction: null, + redirect: { + url: '', + target: '_self', + }, + }, + }, + { + name: 'Email Notification', + type: StepTypeEnum.EMAIL, + controlValues: { + subject: 'You were mentioned in a comment', + body: JSON.stringify({ + type: 'doc', + content: [ + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { type: 'text', text: 'Hi ' }, + { + type: 'variable', + attrs: { id: 'subscriber.firstName', label: null, fallback: null, required: false }, + }, + { type: 'text', text: ',' }, + ], + }, + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { type: 'text', text: 'You were mentioned in a comment by ' }, + { + type: 'variable', + attrs: { id: 'payload.actorName', label: null, fallback: null, required: false }, + }, + { type: 'text', text: '.' }, + ], + }, + { + type: 'button', + attrs: { + text: 'View Comment', + isTextVariable: false, + url: '', + isUrlVariable: false, + alignment: 'left', + variant: 'filled', + borderRadius: 'smooth', + buttonColor: '#000000', + textColor: '#ffffff', + showIfKey: null, + }, + }, + ], + }), + }, + }, + ], + tags: ['mention', 'comment', 'notification'], + active: true, + __source: WorkflowCreationSourceEnum.TEMPLATE_STORE, + }, +}; diff --git a/apps/dashboard/src/components/template-store/templates/payment-failed.ts b/apps/dashboard/src/components/template-store/templates/payment-failed.ts new file mode 100644 index 00000000000..75cb0fd88a0 --- /dev/null +++ b/apps/dashboard/src/components/template-store/templates/payment-failed.ts @@ -0,0 +1,133 @@ +import { StepTypeEnum, WorkflowCreationSourceEnum } from '@novu/shared'; +import { WorkflowTemplate } from './types'; + +export const paymentFailedTemplate: WorkflowTemplate = { + id: 'payment-failed', + name: 'Payment failed alert', + description: 'Notify users when their payment method fails to process', + category: 'popular', + workflowDefinition: { + name: 'Payment failed alert', + description: 'Notify users when their payment method fails to process', + workflowId: 'payment-failed', + steps: [ + { + name: 'Email Alert', + type: StepTypeEnum.EMAIL, + controlValues: { + subject: 'Action Required: Payment Failed', + body: JSON.stringify({ + type: 'doc', + content: [ + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { type: 'text', text: 'Hi ' }, + { + type: 'variable', + attrs: { id: 'subscriber.firstName', label: null, fallback: null, required: false }, + }, + { type: 'text', text: ',' }, + ], + }, + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { type: 'text', text: 'We were unable to process your payment of ' }, + { + type: 'variable', + attrs: { id: 'payload.amount', label: null, fallback: null, required: true }, + }, + { type: 'text', text: ' for your ' }, + { + type: 'variable', + attrs: { id: 'payload.planName', label: null, fallback: 'subscription', required: false }, + }, + { type: 'text', text: '.' }, + ], + }, + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { type: 'text', text: 'Error: ' }, + { + type: 'variable', + attrs: { + id: 'payload.errorMessage', + label: null, + fallback: 'Payment processing failed', + required: false, + }, + }, + ], + }, + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { type: 'text', text: 'To ensure uninterrupted service, please update your payment information by ' }, + { + type: 'variable', + attrs: { + id: 'payload.retryDeadline', + label: null, + fallback: 'as soon as possible', + required: false, + }, + }, + { type: 'text', text: '.' }, + ], + }, + { + type: 'button', + attrs: { + text: 'Update Payment Method', + isTextVariable: false, + url: '{{payload.updatePaymentUrl}}', + isUrlVariable: true, + alignment: 'left', + variant: 'filled', + borderRadius: 'smooth', + buttonColor: '#E53E3E', + textColor: '#ffffff', + showIfKey: null, + }, + }, + ], + }), + }, + }, + { + name: 'In-App Alert', + type: StepTypeEnum.IN_APP, + controlValues: { + subject: 'Payment Failed', + body: 'We were unable to process your payment of {{payload.amount}}. Please update your payment method.', + action: { + status: 'error', + buttons: [ + { + content: 'Update Payment', + buttonColor: '#E53E3E', + textColor: '#ffffff', + }, + ], + }, + }, + }, + { + name: 'SMS Alert', + type: StepTypeEnum.SMS, + controlValues: { + body: 'Payment failed for {{payload.planName}}. Amount: {{payload.amount}}. Please update your payment method to avoid service interruption.', + }, + }, + ], + tags: ['billing', 'payment', 'error'], + active: true, + __source: WorkflowCreationSourceEnum.TEMPLATE_STORE, + }, +}; diff --git a/apps/dashboard/src/components/template-store/templates/team-invitation.ts b/apps/dashboard/src/components/template-store/templates/team-invitation.ts new file mode 100644 index 00000000000..c9a8fd524bd --- /dev/null +++ b/apps/dashboard/src/components/template-store/templates/team-invitation.ts @@ -0,0 +1,120 @@ +import { StepTypeEnum, WorkflowCreationSourceEnum } from '@novu/shared'; +import { WorkflowTemplate } from './types'; + +export const teamInvitationTemplate: WorkflowTemplate = { + id: 'team-invitation', + name: 'Team member invitation', + description: 'Send invitations to new team members', + category: 'popular', + workflowDefinition: { + name: 'Team member invitation', + description: 'Send invitations to new team members', + workflowId: 'team-invitation', + steps: [ + { + name: 'Email Invitation', + type: StepTypeEnum.EMAIL, + controlValues: { + subject: '{{payload.inviterName}} invited you to join {{payload.organizationName}}', + body: JSON.stringify({ + type: 'doc', + content: [ + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { type: 'text', text: 'Hi ' }, + { + type: 'variable', + attrs: { id: 'subscriber.firstName', label: null, fallback: 'there', required: false }, + }, + { type: 'text', text: ',' }, + ], + }, + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { + type: 'variable', + attrs: { id: 'payload.inviterName', label: null, fallback: 'Someone', required: false }, + }, + { type: 'text', text: ' has invited you to join ' }, + { + type: 'variable', + attrs: { id: 'payload.organizationName', label: null, fallback: 'their team', required: false }, + }, + { type: 'text', text: ' as a ' }, + { + type: 'variable', + attrs: { id: 'payload.role', label: null, fallback: 'team member', required: false }, + }, + { type: 'text', text: '.' }, + ], + }, + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { type: 'text', text: 'Message from ' }, + { + type: 'variable', + attrs: { id: 'payload.inviterName', label: null, fallback: 'the inviter', required: false }, + }, + { type: 'text', text: ':' }, + ], + }, + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { + type: 'variable', + attrs: { id: 'payload.message', label: null, fallback: 'Join our team!', required: false }, + }, + ], + }, + { + type: 'button', + attrs: { + text: 'Accept Invitation', + isTextVariable: false, + url: '{{payload.invitationUrl}}', + isUrlVariable: true, + alignment: 'left', + variant: 'filled', + borderRadius: 'smooth', + buttonColor: '#0047FF', + textColor: '#ffffff', + showIfKey: null, + }, + }, + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { type: 'text', text: 'This invitation will expire in ' }, + { + type: 'variable', + attrs: { id: 'payload.expirationTime', label: null, fallback: '24 hours', required: false }, + }, + { type: 'text', text: '.' }, + ], + }, + ], + }), + }, + }, + { + name: 'SMS Notification', + type: StepTypeEnum.SMS, + controlValues: { + body: '{{payload.inviterName}} invited you to join {{payload.organizationName}}. Click {{payload.invitationUrl}} to accept.', + }, + }, + ], + tags: ['team', 'invitation', 'onboarding'], + active: true, + __source: WorkflowCreationSourceEnum.TEMPLATE_STORE, + }, +}; diff --git a/apps/dashboard/src/components/template-store/templates/trial-expiration.ts b/apps/dashboard/src/components/template-store/templates/trial-expiration.ts new file mode 100644 index 00000000000..246a0eadc00 --- /dev/null +++ b/apps/dashboard/src/components/template-store/templates/trial-expiration.ts @@ -0,0 +1,147 @@ +import { StepTypeEnum, WorkflowCreationSourceEnum } from '@novu/shared'; +import { WorkflowTemplate } from './types'; + +export const trialExpirationTemplate: WorkflowTemplate = { + id: 'trial-expiration', + name: 'Trial expiration reminder', + description: 'Notify users when their trial period is about to end', + category: 'popular', + workflowDefinition: { + name: 'Trial expiration reminder', + description: 'Notify users when their trial period is about to end', + workflowId: 'trial-expiration', + steps: [ + { + name: 'Email Notification', + type: StepTypeEnum.EMAIL, + controlValues: { + subject: 'Your trial period ends in {{payload.daysLeft}} days', + body: JSON.stringify({ + type: 'doc', + content: [ + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { type: 'text', text: 'Hi ' }, + { + type: 'variable', + attrs: { id: 'subscriber.firstName', label: null, fallback: null, required: false }, + }, + { type: 'text', text: ',' }, + ], + }, + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { type: 'text', text: 'Your trial period for ' }, + { + type: 'variable', + attrs: { id: 'payload.productName', label: null, fallback: 'our product', required: false }, + }, + { type: 'text', text: ' will end in ' }, + { + type: 'variable', + attrs: { id: 'payload.daysLeft', label: null, fallback: null, required: true }, + }, + { type: 'text', text: ' days.' }, + ], + }, + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [{ type: 'text', text: 'During your trial, you have:' }], + }, + { + type: 'bulletList', + content: [ + { + type: 'listItem', + content: [ + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { + type: 'variable', + attrs: { + id: 'payload.usage.metric1', + label: null, + fallback: 'Used our features', + required: false, + }, + }, + ], + }, + ], + }, + { + type: 'listItem', + content: [ + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { + type: 'variable', + attrs: { + id: 'payload.usage.metric2', + label: null, + fallback: 'Explored our platform', + required: false, + }, + }, + ], + }, + ], + }, + ], + }, + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [{ type: 'text', text: 'Upgrade now to keep access to all features and your data.' }], + }, + { + type: 'button', + attrs: { + text: 'Upgrade Now', + isTextVariable: false, + url: '{{payload.upgradeUrl}}', + isUrlVariable: true, + alignment: 'left', + variant: 'filled', + borderRadius: 'smooth', + buttonColor: '#0047FF', + textColor: '#ffffff', + showIfKey: null, + }, + }, + ], + }), + }, + }, + { + name: 'In-App Notification', + type: StepTypeEnum.IN_APP, + controlValues: { + subject: 'Trial ends in {{payload.daysLeft}} days', + body: 'Your trial period will end soon. Upgrade now to keep access to all features.', + action: { + buttons: [ + { + content: 'Upgrade', + buttonColor: '#0047FF', + textColor: '#ffffff', + }, + ], + }, + }, + }, + ], + tags: ['trial', 'subscription', 'billing'], + active: true, + __source: WorkflowCreationSourceEnum.TEMPLATE_STORE, + }, +}; diff --git a/apps/dashboard/src/components/template-store/templates/types.ts b/apps/dashboard/src/components/template-store/templates/types.ts new file mode 100644 index 00000000000..dd0f844810a --- /dev/null +++ b/apps/dashboard/src/components/template-store/templates/types.ts @@ -0,0 +1,9 @@ +import { CreateWorkflowDto } from '@novu/shared'; + +export interface WorkflowTemplate { + id: string; + name: string; + description: string; + category: 'popular' | 'events' | 'authentication' | 'social'; + workflowDefinition: CreateWorkflowDto; +} diff --git a/apps/dashboard/src/components/template-store/templates/usage-limit.ts b/apps/dashboard/src/components/template-store/templates/usage-limit.ts new file mode 100644 index 00000000000..b81de930f89 --- /dev/null +++ b/apps/dashboard/src/components/template-store/templates/usage-limit.ts @@ -0,0 +1,166 @@ +import { StepTypeEnum, WorkflowCreationSourceEnum } from '@novu/shared'; +import { WorkflowTemplate } from './types'; + +export const usageLimitTemplate: WorkflowTemplate = { + id: 'usage-limit', + name: 'Usage limit alert', + description: 'Alert users when they approach their usage limits', + category: 'popular', + workflowDefinition: { + name: 'Usage limit alert', + description: 'Alert users when they approach their usage limits', + workflowId: 'usage-limit', + steps: [ + { + name: 'Email Alert', + type: StepTypeEnum.EMAIL, + controlValues: { + subject: 'Approaching usage limit for {{payload.resourceName}}', + body: JSON.stringify({ + type: 'doc', + content: [ + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { type: 'text', text: 'Hi ' }, + { + type: 'variable', + attrs: { id: 'subscriber.firstName', label: null, fallback: null, required: false }, + }, + { type: 'text', text: ',' }, + ], + }, + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { type: 'text', text: 'Your ' }, + { + type: 'variable', + attrs: { id: 'payload.resourceName', label: null, fallback: 'resource', required: false }, + }, + { type: 'text', text: ' usage has reached ' }, + { + type: 'variable', + attrs: { id: 'payload.currentUsage', label: null, fallback: null, required: true }, + }, + { type: 'text', text: ' of your ' }, + { + type: 'variable', + attrs: { id: 'payload.limit', label: null, fallback: null, required: true }, + }, + { type: 'text', text: ' limit.' }, + ], + }, + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [{ type: 'text', text: 'Current Usage Details:' }], + }, + { + type: 'bulletList', + content: [ + { + type: 'listItem', + content: [ + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { type: 'text', text: 'Used: ' }, + { + type: 'variable', + attrs: { id: 'payload.currentUsage', label: null, fallback: null, required: true }, + }, + ], + }, + ], + }, + { + type: 'listItem', + content: [ + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { type: 'text', text: 'Remaining: ' }, + { + type: 'variable', + attrs: { id: 'payload.remaining', label: null, fallback: null, required: true }, + }, + ], + }, + ], + }, + { + type: 'listItem', + content: [ + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [ + { type: 'text', text: 'Reset Date: ' }, + { + type: 'variable', + attrs: { + id: 'payload.resetDate', + label: null, + fallback: 'End of billing period', + required: false, + }, + }, + ], + }, + ], + }, + ], + }, + { + type: 'paragraph', + attrs: { textAlign: 'left' }, + content: [{ type: 'text', text: 'To avoid any service interruptions, consider upgrading your plan.' }], + }, + { + type: 'button', + attrs: { + text: 'Upgrade Plan', + isTextVariable: false, + url: '{{payload.upgradeUrl}}', + isUrlVariable: true, + alignment: 'left', + variant: 'filled', + borderRadius: 'smooth', + buttonColor: '#0047FF', + textColor: '#ffffff', + showIfKey: null, + }, + }, + ], + }), + }, + }, + { + name: 'In-App Alert', + type: StepTypeEnum.IN_APP, + controlValues: { + subject: 'Approaching {{payload.resourceName}} limit', + body: 'You have used {{payload.currentUsage}} of {{payload.limit}} {{payload.resourceName}}. Consider upgrading your plan.', + action: { + status: 'warning', + buttons: [ + { + content: 'View Usage', + buttonColor: '#0047FF', + textColor: '#ffffff', + }, + ], + }, + }, + }, + ], + tags: ['usage', 'limits', 'billing'], + active: true, + __source: WorkflowCreationSourceEnum.TEMPLATE_STORE, + }, +}; diff --git a/apps/dashboard/src/components/template-store/workflow-template-modal.tsx b/apps/dashboard/src/components/template-store/workflow-template-modal.tsx index 99cce6fc3b8..a3a032b42af 100644 --- a/apps/dashboard/src/components/template-store/workflow-template-modal.tsx +++ b/apps/dashboard/src/components/template-store/workflow-template-modal.tsx @@ -1,5 +1,4 @@ import { ComponentProps, useState } from 'react'; -import { RiArrowRightSLine } from 'react-icons/ri'; import { Dialog, DialogContent, DialogHeader, DialogTrigger } from '@/components/primitives/dialog'; import { Button } from '@/components/primitives/button'; @@ -9,108 +8,11 @@ import { WorkflowSidebar } from './workflow-sidebar'; import { RouteFill } from '../icons'; import { Form } from '../primitives/form/form'; import { useForm } from 'react-hook-form'; -import { StepTypeEnum, WorkflowCreationSourceEnum, CreateWorkflowDto } from '@novu/shared'; +import { getTemplates } from './templates'; type WorkflowTemplateModalProps = ComponentProps; -interface WorkflowTemplate { - id: string; - name: string; - description: string; - category: 'popular' | 'events' | 'authentication' | 'social'; - workflowDefinition: CreateWorkflowDto; -} - -const WORKFLOW_TEMPLATES: WorkflowTemplate[] = [ - { - id: 'mention-notification', - name: 'Mention in a comment', - description: 'Triggered when an actor mentions someone', - category: 'popular', - workflowDefinition: { - name: 'Mention in a comment', - description: 'Triggered when an actor mentions someone', - workflowId: 'mention-notification', - steps: [ - { - name: 'In-App Notification', - type: StepTypeEnum.IN_APP, - controlValues: { - body: 'You were mentioned in a comment by {{payload.actorName}}', - avatar: '', - subject: 'New Mention', - primaryAction: { - label: 'View Comment', - redirect: { - url: '{{payload.commentUrl}}', - target: '_blank', - }, - }, - secondaryAction: null, - redirect: { - url: '', - target: '_self', - }, - }, - }, - { - name: 'Email Notification', - type: StepTypeEnum.EMAIL, - controlValues: { - subject: 'You were mentioned in a comment', - body: JSON.stringify({ - type: 'doc', - content: [ - { - type: 'paragraph', - attrs: { textAlign: 'left' }, - content: [ - { type: 'text', text: 'Hi ' }, - { - type: 'variable', - attrs: { id: 'subscriber.firstName', label: null, fallback: null, required: false }, - }, - { type: 'text', text: ',' }, - ], - }, - { - type: 'paragraph', - attrs: { textAlign: 'left' }, - content: [ - { type: 'text', text: 'You were mentioned in a comment by ' }, - { - type: 'variable', - attrs: { id: 'payload.actorName', label: null, fallback: null, required: false }, - }, - { type: 'text', text: '.' }, - ], - }, - { - type: 'button', - attrs: { - text: 'View Comment', - isTextVariable: false, - url: '', - isUrlVariable: false, - alignment: 'left', - variant: 'filled', - borderRadius: 'smooth', - buttonColor: '#000000', - textColor: '#ffffff', - showIfKey: null, - }, - }, - ], - }), - }, - }, - ], - tags: ['mention', 'comment', 'notification'], - active: true, - __source: WorkflowCreationSourceEnum.TEMPLATE_STORE, - }, - }, -]; +const WORKFLOW_TEMPLATES = getTemplates(); export function WorkflowTemplateModal(props: WorkflowTemplateModalProps) { const form = useForm(); @@ -157,17 +59,12 @@ export function WorkflowTemplateModal(props: WorkflowTemplateModalProps) {
- {/* Footer */}
-
diff --git a/apps/dashboard/src/components/workflow-editor/schema.ts b/apps/dashboard/src/components/workflow-editor/schema.ts index 6d5c59447ef..5aa1be2fc1c 100644 --- a/apps/dashboard/src/components/workflow-editor/schema.ts +++ b/apps/dashboard/src/components/workflow-editor/schema.ts @@ -14,7 +14,6 @@ export const workflowSchema = z.object({ tags: z .array(z.string().min(0).max(MAX_TAG_LENGTH)) .max(MAX_TAG_ELEMENTS) - .optional() .refine((tags) => tags?.every((tag) => tag.length <= MAX_TAG_LENGTH), { message: `Tags must be less than ${MAX_TAG_LENGTH} characters`, }) From 8fff01c87235db9f87d6dea4a4b8990bf941f51d Mon Sep 17 00:00:00 2001 From: Dima Grossman Date: Sun, 29 Dec 2024 10:03:14 +0200 Subject: [PATCH 08/54] fix: email preview --- .../template-store/workflow-card.tsx | 150 ++++++++++++++++-- .../workflow-template-modal.tsx | 1 + 2 files changed, 138 insertions(+), 13 deletions(-) diff --git a/apps/dashboard/src/components/template-store/workflow-card.tsx b/apps/dashboard/src/components/template-store/workflow-card.tsx index 0fe3fd13ab3..b016c41206e 100644 --- a/apps/dashboard/src/components/template-store/workflow-card.tsx +++ b/apps/dashboard/src/components/template-store/workflow-card.tsx @@ -3,6 +3,38 @@ import { Card, CardContent } from '../primitives/card'; import { Bell, MessageSquare, MessageCircle, BellRing } from 'lucide-react'; import { LucideIcon } from 'lucide-react'; import { StepTypeEnum } from '@novu/shared'; +import { HoverCard, HoverCardContent, HoverCardTrigger } from '../primitives/hover-card'; +import { + InAppPreview, + InAppPreviewBell, + InAppPreviewHeader, + InAppPreviewNotification, + InAppPreviewNotificationContent, + InAppPreviewSubject, + InAppPreviewBody, + InAppPreviewActions, + InAppPreviewPrimaryAction, +} from '../workflow-editor/in-app-preview'; +import { EmailPreviewHeader, EmailPreviewSubject } from '../workflow-editor/steps/email/email-preview'; +import { Editor } from '@maily-to/core'; +import { + blockquote, + bulletList, + button, + columns, + divider, + hardBreak, + heading1, + heading2, + heading3, + image, + orderedList, + section, + spacer, + text, +} from '@maily-to/core/blocks'; +import { CreateWorkflowDto } from '@novu/shared'; +import { WorkflowStep } from '../workflow-step'; export type StepType = StepTypeEnum; @@ -17,6 +49,7 @@ interface WorkflowCardProps { description: string; steps?: StepType[]; onClick?: () => void; + template?: CreateWorkflowDto; } const STEP_ICON_MAP: Record = { @@ -70,18 +103,103 @@ const STEP_COLORS: Record = { }, }; +function StepPreview({ type, stepContent }: { type: StepType; stepContent?: any }) { + if (!stepContent) { + return ( +
+
Preview coming soon
+
+ ); + } + + if (type === StepTypeEnum.IN_APP) { + const { subject, body, action } = stepContent.controlValues; + + return ( + + + + + + {subject} + {body} + {action?.buttons?.length > 0 && ( + + {action.buttons.map((button: any, index: number) => ( + {button.content} + ))} + + )} + + + + ); + } + + if (type === StepTypeEnum.EMAIL) { + const { subject, body } = stepContent.controlValues; + let parsedBody; + try { + parsedBody = JSON.parse(body); + } catch (e) { + console.error('Failed to parse email body:', e); + return ( +
+
Error parsing email content
+
+ ); + } + + return ( +
+ + +
+ div]:basis-full [&_.tiptap]:h-full', + }} + blocks={[ + text, + heading1, + heading2, + heading3, + bulletList, + orderedList, + image, + section, + columns, + divider, + spacer, + button, + hardBreak, + blockquote, + ]} + contentJson={parsedBody} + /> +
+
+ ); + } + + return ( +
+
Preview coming soon
+
+ ); +} + export function WorkflowCard({ name, description, steps = [StepTypeEnum.IN_APP, StepTypeEnum.EMAIL, StepTypeEnum.SMS, StepTypeEnum.PUSH], onClick, + template, }: WorkflowCardProps) { - const mappedSteps = steps.map((step) => ({ - icon: STEP_ICON_MAP[step], - bgColor: STEP_COLORS[step].bg, - borderColor: STEP_COLORS[step].border, - })); - return (
- {mappedSteps.map((step, index) => ( + {steps.map((step, index) => ( -
- -
- {index < mappedSteps.length - 1 &&
} + + + + + + s.type === steps[index])} + /> + + + {index < steps.length - 1 &&
} ))}
diff --git a/apps/dashboard/src/components/template-store/workflow-template-modal.tsx b/apps/dashboard/src/components/template-store/workflow-template-modal.tsx index a3a032b42af..998113ccd3e 100644 --- a/apps/dashboard/src/components/template-store/workflow-template-modal.tsx +++ b/apps/dashboard/src/components/template-store/workflow-template-modal.tsx @@ -50,6 +50,7 @@ export function WorkflowTemplateModal(props: WorkflowTemplateModalProps) { step.type)} /> From 026490fcbb987c3991f584fd05e2835a475781ad Mon Sep 17 00:00:00 2001 From: Dima Grossman Date: Sun, 29 Dec 2024 10:22:59 +0200 Subject: [PATCH 09/54] Add push and chat previews --- .../template-store/workflow-card.tsx | 64 ++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/apps/dashboard/src/components/template-store/workflow-card.tsx b/apps/dashboard/src/components/template-store/workflow-card.tsx index b016c41206e..3663d1a227e 100644 --- a/apps/dashboard/src/components/template-store/workflow-card.tsx +++ b/apps/dashboard/src/components/template-store/workflow-card.tsx @@ -2,7 +2,13 @@ import React from 'react'; import { Card, CardContent } from '../primitives/card'; import { Bell, MessageSquare, MessageCircle, BellRing } from 'lucide-react'; import { LucideIcon } from 'lucide-react'; -import { StepTypeEnum } from '@novu/shared'; +import { + StepTypeEnum, + ChannelTypeEnum, + ChatRenderOutput, + PushRenderOutput, + GeneratePreviewResponseDto, +} from '@novu/shared'; import { HoverCard, HoverCardContent, HoverCardTrigger } from '../primitives/hover-card'; import { InAppPreview, @@ -35,6 +41,9 @@ import { } from '@maily-to/core/blocks'; import { CreateWorkflowDto } from '@novu/shared'; import { WorkflowStep } from '../workflow-step'; +import { SmsPhone } from '../workflow-editor/steps/sms/sms-phone'; +import { ChatPreview } from '../workflow-editor/steps/chat/chat-preview'; +import { PushPreview } from '../workflow-editor/steps/push/push-preview'; export type StepType = StepTypeEnum; @@ -151,7 +160,7 @@ function StepPreview({ type, stepContent }: { type: StepType; stepContent?: any } return ( -
+
@@ -186,6 +195,57 @@ function StepPreview({ type, stepContent }: { type: StepType; stepContent?: any ); } + if (type === StepTypeEnum.SMS) { + const { body } = stepContent.controlValues; + return ( +
+ +
+ ); + } + + if (type === StepTypeEnum.CHAT) { + const { body } = stepContent.controlValues; + const mockPreviewData: GeneratePreviewResponseDto = { + result: { + type: ChannelTypeEnum.CHAT as const, + preview: { + body, + content: body, + } as ChatRenderOutput, + }, + previewPayloadExample: {}, + }; + + return ( +
+ +
+ ); + } + + if (type === StepTypeEnum.PUSH) { + const { subject, body } = stepContent.controlValues; + const mockPreviewData: GeneratePreviewResponseDto = { + result: { + type: ChannelTypeEnum.PUSH as const, + preview: { + subject, + body, + title: subject, + content: body, + } as PushRenderOutput, + }, + previewPayloadExample: {}, + }; + + return ( +
+ +
+ ); + } + return (
Preview coming soon
From c7e4ce4f3cf0daa206f78ff5682149e7b1cede6b Mon Sep 17 00:00:00 2001 From: Dima Grossman Date: Sun, 29 Dec 2024 10:23:43 +0200 Subject: [PATCH 10/54] fix: remove unused --- .../template-store/workflow-card.tsx | 59 ------------------- 1 file changed, 59 deletions(-) diff --git a/apps/dashboard/src/components/template-store/workflow-card.tsx b/apps/dashboard/src/components/template-store/workflow-card.tsx index 3663d1a227e..933bf63c2f2 100644 --- a/apps/dashboard/src/components/template-store/workflow-card.tsx +++ b/apps/dashboard/src/components/template-store/workflow-card.tsx @@ -1,7 +1,5 @@ import React from 'react'; import { Card, CardContent } from '../primitives/card'; -import { Bell, MessageSquare, MessageCircle, BellRing } from 'lucide-react'; -import { LucideIcon } from 'lucide-react'; import { StepTypeEnum, ChannelTypeEnum, @@ -47,12 +45,6 @@ import { PushPreview } from '../workflow-editor/steps/push/push-preview'; export type StepType = StepTypeEnum; -interface Step { - icon: LucideIcon; - bgColor: string; - borderColor: string; -} - interface WorkflowCardProps { name: string; description: string; @@ -61,57 +53,6 @@ interface WorkflowCardProps { template?: CreateWorkflowDto; } -const STEP_ICON_MAP: Record = { - [StepTypeEnum.IN_APP]: Bell, - [StepTypeEnum.EMAIL]: MessageSquare, - [StepTypeEnum.SMS]: MessageCircle, - [StepTypeEnum.PUSH]: BellRing, - [StepTypeEnum.CHAT]: MessageSquare, - [StepTypeEnum.DIGEST]: Bell, - [StepTypeEnum.TRIGGER]: Bell, - [StepTypeEnum.DELAY]: Bell, - [StepTypeEnum.CUSTOM]: Bell, -}; - -const STEP_COLORS: Record = { - [StepTypeEnum.IN_APP]: { - bg: 'bg-[#FFE5D3]', - border: 'border-[#FF8D4E]', - }, - [StepTypeEnum.EMAIL]: { - bg: 'bg-[#E7F6F3]', - border: 'border-[#4EC2AB]', - }, - [StepTypeEnum.SMS]: { - bg: 'bg-[#FFE9F3]', - border: 'border-[#FF4E9E]', - }, - [StepTypeEnum.PUSH]: { - bg: 'bg-[#E7EEFF]', - border: 'border-[#4E77FF]', - }, - [StepTypeEnum.CHAT]: { - bg: 'bg-[#E7F6F3]', - border: 'border-[#4EC2AB]', - }, - [StepTypeEnum.DIGEST]: { - bg: 'bg-[#FFE5D3]', - border: 'border-[#FF8D4E]', - }, - [StepTypeEnum.TRIGGER]: { - bg: 'bg-[#FFE5D3]', - border: 'border-[#FF8D4E]', - }, - [StepTypeEnum.DELAY]: { - bg: 'bg-[#FFE5D3]', - border: 'border-[#FF8D4E]', - }, - [StepTypeEnum.CUSTOM]: { - bg: 'bg-[#FFE5D3]', - border: 'border-[#FF8D4E]', - }, -}; - function StepPreview({ type, stepContent }: { type: StepType; stepContent?: any }) { if (!stepContent) { return ( From 26e8c3d7e34625a95c0f0340df56cad4c4781600 Mon Sep 17 00:00:00 2001 From: Dima Grossman Date: Sun, 29 Dec 2024 11:29:36 +0200 Subject: [PATCH 11/54] feat: ai flow --- apps/api/package.json | 2 + .../dtos/workflow-suggestion.interface.ts | 9 + .../generate-suggestions.command.ts | 18 + .../generate-suggestions.usecase.ts | 81 + .../usecases/generate-suggestions/index.ts | 2 + .../app/workflows-v2/workflow.controller.ts | 29 +- .../src/app/workflows-v2/workflow.module.ts | 2 + .../template-store/workflow-sidebar.tsx | 132 +- apps/dashboard/src/pages/index.ts | 3 +- pnpm-lock.yaml | 1565 +++++++++-------- 10 files changed, 1094 insertions(+), 749 deletions(-) create mode 100644 apps/api/src/app/workflows-v2/dtos/workflow-suggestion.interface.ts create mode 100644 apps/api/src/app/workflows-v2/usecases/generate-suggestions/generate-suggestions.command.ts create mode 100644 apps/api/src/app/workflows-v2/usecases/generate-suggestions/generate-suggestions.usecase.ts create mode 100644 apps/api/src/app/workflows-v2/usecases/generate-suggestions/index.ts diff --git a/apps/api/package.json b/apps/api/package.json index 591799cd2e7..0729b0dc83e 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -37,6 +37,8 @@ "@nestjs/axios": "3.0.3", "@nestjs/common": "10.4.1", "@nestjs/core": "10.4.1", + "@ai-sdk/openai": "^1.0.11", + "ai": "4.0.22", "@nestjs/jwt": "10.2.0", "@nestjs/passport": "10.0.3", "@nestjs/platform-express": "10.4.1", diff --git a/apps/api/src/app/workflows-v2/dtos/workflow-suggestion.interface.ts b/apps/api/src/app/workflows-v2/dtos/workflow-suggestion.interface.ts new file mode 100644 index 00000000000..b3616e51889 --- /dev/null +++ b/apps/api/src/app/workflows-v2/dtos/workflow-suggestion.interface.ts @@ -0,0 +1,9 @@ +import { CreateWorkflowDto } from '@novu/shared'; + +export interface IWorkflowSuggestion { + id: string; + name: string; + description: string; + category: 'popular' | 'events' | 'authentication' | 'social'; + workflowDefinition: CreateWorkflowDto; +} diff --git a/apps/api/src/app/workflows-v2/usecases/generate-suggestions/generate-suggestions.command.ts b/apps/api/src/app/workflows-v2/usecases/generate-suggestions/generate-suggestions.command.ts new file mode 100644 index 00000000000..32566f50037 --- /dev/null +++ b/apps/api/src/app/workflows-v2/usecases/generate-suggestions/generate-suggestions.command.ts @@ -0,0 +1,18 @@ +import { UserSessionData } from '@novu/shared'; +import { IsString } from 'class-validator'; + +export class GenerateSuggestionsCommand { + @IsString() + prompt: string; + + user: UserSessionData; + + static create(data: { prompt: string; user: UserSessionData }) { + const command = new GenerateSuggestionsCommand(); + + command.prompt = data.prompt; + command.user = data.user; + + return command; + } +} diff --git a/apps/api/src/app/workflows-v2/usecases/generate-suggestions/generate-suggestions.usecase.ts b/apps/api/src/app/workflows-v2/usecases/generate-suggestions/generate-suggestions.usecase.ts new file mode 100644 index 00000000000..8c63b811044 --- /dev/null +++ b/apps/api/src/app/workflows-v2/usecases/generate-suggestions/generate-suggestions.usecase.ts @@ -0,0 +1,81 @@ +import { Injectable } from '@nestjs/common'; +import { openai } from '@ai-sdk/openai'; +import { generateObject } from 'ai'; +import { z } from 'zod'; +import { InstrumentUsecase } from '@novu/application-generic'; +import { StepTypeEnum } from '@novu/shared'; +import { IWorkflowSuggestion } from '../../dtos/workflow-suggestion.interface'; +import { GenerateSuggestionsCommand } from './generate-suggestions.command'; + +const stepSchema = z.object({ + name: z.string(), + type: z.enum([StepTypeEnum.EMAIL, StepTypeEnum.IN_APP, StepTypeEnum.SMS, StepTypeEnum.PUSH, StepTypeEnum.CHAT]), + subject: z.string(), + body: z.string(), +}); + +const workflowSchema = z.object({ + id: z.string(), + name: z.string(), + description: z.string(), + category: z.enum(['popular', 'events', 'authentication', 'social']), + steps: z.array(stepSchema), +}); + +@Injectable() +export class GenerateSuggestionsUsecase { + @InstrumentUsecase() + async execute(command: GenerateSuggestionsCommand): Promise<{ suggestions: IWorkflowSuggestion[] }> { + const result = await generateObject({ + model: openai('gpt-4o', { + structuredOutputs: true, + }), + systemPrompt: `You are an expert in generating workflow suggestions for a notification system. Based on the user's product description, generate 5 relevant workflow suggestions that would be useful for their product. Each workflow should be practical, specific, and follow best practices for user engagement and notification design. + + Consider common use cases like: + - User onboarding and authentication + - Activity updates and engagement + - System alerts and status changes + - Social interactions and collaboration + - Transactional notifications + + For each workflow: + - Create a unique ID + - Give it a clear, descriptive name + - Write a concise description of its purpose + - Assign it to an appropriate category + - Define practical steps with appropriate channels (email, SMS, push, in-app, chat) + - Include relevant subject lines and message content + - Add action buttons where appropriate`, + schemaName: 'workflow-suggestions', + schemaDescription: "A list of workflow suggestions based on the user's product description.", + schema: z.object({ + suggestions: z.array(workflowSchema), + }), + prompt: command.prompt, + }); + + // Transform the flattened result back into the expected structure + return { + suggestions: result.object.suggestions.map((suggestion) => ({ + id: suggestion.id, + name: suggestion.name, + description: suggestion.description, + category: suggestion.category, + workflowDefinition: { + name: suggestion.name, + description: suggestion.description, + workflowId: suggestion.id, + steps: suggestion.steps.map((step) => ({ + name: step.name, + type: step.type, + controlValues: { + subject: step.subject, + body: step.body, + }, + })), + }, + })) as any[], + }; + } +} diff --git a/apps/api/src/app/workflows-v2/usecases/generate-suggestions/index.ts b/apps/api/src/app/workflows-v2/usecases/generate-suggestions/index.ts new file mode 100644 index 00000000000..99190f5a3f3 --- /dev/null +++ b/apps/api/src/app/workflows-v2/usecases/generate-suggestions/index.ts @@ -0,0 +1,2 @@ +export * from './generate-suggestions.command'; +export * from './generate-suggestions.usecase'; diff --git a/apps/api/src/app/workflows-v2/workflow.controller.ts b/apps/api/src/app/workflows-v2/workflow.controller.ts index 03d22ffd860..b5501f7e471 100644 --- a/apps/api/src/app/workflows-v2/workflow.controller.ts +++ b/apps/api/src/app/workflows-v2/workflow.controller.ts @@ -31,6 +31,7 @@ import { WorkflowTestDataResponseDto, } from '@novu/shared'; import { DeleteWorkflowCommand, DeleteWorkflowUseCase, UserAuthGuard, UserSession } from '@novu/application-generic'; +import { IsString } from 'class-validator'; import { ApiCommonResponses } from '../shared/framework/response.decorator'; import { UserAuthentication } from '../shared/framework/swagger/api.key.security'; import { GetWorkflowCommand } from './usecases/get-workflow/get-workflow.command'; @@ -54,6 +55,19 @@ import { GeneratePreviewCommand } from './usecases/generate-preview/generate-pre import { PatchStepCommand } from './usecases/patch-step-data'; import { PatchWorkflowCommand, PatchWorkflowUsecase } from './usecases/patch-workflow'; import { PatchStepUsecase } from './usecases/patch-step-data/patch-step.usecase'; +import { GenerateSuggestionsUsecase } from './usecases/generate-suggestions'; +import { IWorkflowSuggestion } from './dtos/workflow-suggestion.interface'; + +// DTO for the suggestions request +class GenerateWorkflowSuggestionsDto { + @IsString() + prompt: string; +} + +// Response type for workflow suggestions +class WorkflowSuggestionsResponseDto { + suggestions: IWorkflowSuggestion[]; +} @ApiCommonResponses() @Controller({ path: `/workflows`, version: '2' }) @@ -71,9 +85,22 @@ export class WorkflowController { private buildWorkflowTestDataUseCase: BuildWorkflowTestDataUseCase, private buildStepDataUsecase: BuildStepDataUsecase, private patchStepDataUsecase: PatchStepUsecase, - private patchWorkflowUsecase: PatchWorkflowUsecase + private patchWorkflowUsecase: PatchWorkflowUsecase, + private generateSuggestionsUsecase: GenerateSuggestionsUsecase ) {} + @Post('/suggestions') + @UseGuards(UserAuthGuard) + async generateSuggestions( + @UserSession() user: UserSessionData, + @Body() body: GenerateWorkflowSuggestionsDto + ): Promise { + return this.generateSuggestionsUsecase.execute({ + prompt: body.prompt, + user, + }); + } + @Post('') @UseGuards(UserAuthGuard) async create( diff --git a/apps/api/src/app/workflows-v2/workflow.module.ts b/apps/api/src/app/workflows-v2/workflow.module.ts index 9b9b95530a0..fdff6f2d2f6 100644 --- a/apps/api/src/app/workflows-v2/workflow.module.ts +++ b/apps/api/src/app/workflows-v2/workflow.module.ts @@ -41,6 +41,7 @@ import { OverloadContentDataOnWorkflowUseCase } from './usecases/overload-conten import { PatchWorkflowUsecase } from './usecases/patch-workflow'; import { PatchStepUsecase } from './usecases/patch-step-data/patch-step.usecase'; import { BuildPayloadSchema } from './usecases/build-payload-schema/build-payload-schema.usecase'; +import { GenerateSuggestionsUsecase } from './usecases/generate-suggestions'; const DAL_REPOSITORIES = [CommunityOrganizationRepository]; @@ -79,6 +80,7 @@ const DAL_REPOSITORIES = [CommunityOrganizationRepository]; TierRestrictionsValidateUsecase, BuildPayloadSchema, DeleteControlValuesUseCase, + GenerateSuggestionsUsecase, ], }) export class WorkflowModule implements NestModule { diff --git a/apps/dashboard/src/components/template-store/workflow-sidebar.tsx b/apps/dashboard/src/components/template-store/workflow-sidebar.tsx index e130a9a2d94..e2ae9e482a8 100644 --- a/apps/dashboard/src/components/template-store/workflow-sidebar.tsx +++ b/apps/dashboard/src/components/template-store/workflow-sidebar.tsx @@ -1,4 +1,24 @@ -import { Calendar, Code2, ExternalLink, FileCode2, FileText, KeyRound, LayoutGrid, Users } from 'lucide-react'; +import { + Calendar, + Code2, + ExternalLink, + FileCode2, + FileText, + KeyRound, + LayoutGrid, + Users, + Sparkles, +} from 'lucide-react'; +import { useState } from 'react'; +import { Dialog, DialogContent, DialogHeader, DialogTrigger } from '@/components/primitives/dialog'; +import { Button } from '@/components/primitives/button'; +import { Textarea } from '@/components/primitives/textarea'; +import { useEnvironment } from '@/context/environment/hooks'; +import { showToast } from '@/components/primitives/sonner-helpers'; +import { ToastIcon } from '@/components/primitives/sonner'; +import { Badge } from '@/components/primitives/badge'; +import { CreateWorkflowButton } from '@/components/create-workflow-button'; +import { postV2 } from '@/api/api.client'; interface WorkflowSidebarProps { selectedCategory: string; @@ -32,6 +52,115 @@ const useCases = [ }, ] as const; +function AISuggestionsDialog() { + const [prompt, setPrompt] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const [suggestions, setSuggestions] = useState([]); + const { currentEnvironment } = useEnvironment(); + + const handleSubmit = async () => { + if (!prompt) return; + + setIsLoading(true); + try { + const { data } = await postV2<{ data: { suggestions: any[] } }>('/workflows/suggestions', { + environment: currentEnvironment!, + body: { prompt }, + }); + setSuggestions(data.suggestions); + } catch (error) { + showToast({ + children: () => ( + <> + + + Failed to generate suggestions:{' '} + {error instanceof Error ? error.message : 'There was an error generating workflow suggestions.'} + + + ), + options: { + position: 'bottom-right', + }, + }); + } finally { + setIsLoading(false); + } + }; + + return ( + + +
+
+
+ +
+ AI Suggestions +
+
+
+ + +
+
+ +
+
+

Tell us about your product

+

+ Share details about your product or company, and we'll help you create the perfect notification + workflows. +

+
+
+
+
+