diff --git a/apps/dashboard/src/components/primitives/form/avatar-picker.tsx b/apps/dashboard/src/components/primitives/form/avatar-picker.tsx index b1566c93ff0..1dd0eedd2fc 100644 --- a/apps/dashboard/src/components/primitives/form/avatar-picker.tsx +++ b/apps/dashboard/src/components/primitives/form/avatar-picker.tsx @@ -54,7 +54,7 @@ export const AvatarPicker = forwardRef(({ n - )} - {data.result.preview.secondaryAction && ( - - )} - - )} - - )} ); }; -type MarkdownProps = Omit, 'children'> & { children: string }; +type InAppPreviewAvatarProps = HTMLAttributes & { + src?: string; + isPending?: boolean; +}; +export const InAppPreviewAvatar = (props: InAppPreviewAvatarProps) => { + const { className, isPending, src, ...rest } = props; + + if (isPending) { + return ; + } + + if (!src) { + return null; + } + + return avatar; +}; + +type InAppPreviewNotificationProps = HTMLAttributes; +export const InAppPreviewNotification = (props: InAppPreviewNotificationProps) => { + const { className, ...rest } = props; + + return
; +}; + +type InAppPreviewNotificationContentProps = HTMLAttributes; +export const InAppPreviewNotificationContent = (props: InAppPreviewNotificationContentProps) => { + const { className, ...rest } = props; + + return
; +}; + +type InAppPreviewSubjectProps = MarkdownProps & { isPending?: boolean }; +export const InAppPreviewSubject = (props: InAppPreviewSubjectProps) => { + const { className, isPending, ...rest } = props; + + if (isPending) { + return ; + } + + return ; +}; + +type InAppPreviewBodyProps = MarkdownProps & { isPending?: boolean }; +export const InAppPreviewBody = (props: InAppPreviewBodyProps) => { + const { className, isPending, ...rest } = props; + + if (isPending) { + return ( + <> + + + + ); + } + + return ; +}; + +type InAppPreviewActionsProps = HTMLAttributes; +export const InAppPreviewActions = (props: InAppPreviewActionsProps) => { + const { className, ...rest } = props; + return
; +}; + +type InAppPreviewPrimaryActionProps = ButtonProps & { isPending?: boolean }; +export const InAppPreviewPrimaryAction = (props: InAppPreviewPrimaryActionProps) => { + const { className, isPending, children, ...rest } = props; + + if (isPending) { + return ; + } + + return ( + + ); +}; + +type InAppPreviewSecondaryActionProps = ButtonProps & { isPending?: boolean }; +export const InAppPreviewSecondaryAction = (props: InAppPreviewSecondaryActionProps) => { + const { className, isPending, children, ...rest } = props; + + if (isPending) { + return ; + } + + return ( + + ); +}; + +type MarkdownProps = Omit, 'children'> & { children?: string }; const Markdown = (props: MarkdownProps) => { const { children, ...rest } = props; - const tokens = useMemo(() => parseMarkdownIntoTokens(children), [children]); + const tokens = useMemo(() => parseMarkdownIntoTokens(children || ''), [children]); return (

@@ -121,11 +178,3 @@ const Markdown = (props: MarkdownProps) => {

); }; - -const Subject = ({ text, className }: { text: string; className?: string }) => { - return {text}; -}; - -const Body = ({ text, className }: { text: string; className?: string }) => { - return {text}; -}; diff --git a/apps/dashboard/src/components/workflow-editor/steps/configure-in-app-preview.tsx b/apps/dashboard/src/components/workflow-editor/steps/configure-in-app-preview.tsx index 987b0bd5963..95e559f0269 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/configure-in-app-preview.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/configure-in-app-preview.tsx @@ -1,8 +1,17 @@ import { useEffect } from 'react'; import { useParams } from 'react-router-dom'; import { usePreviewStep } from '@/hooks'; -import { InAppPreview } from '@/components/workflow-editor/in-app-preview'; +import { + InAppPreview, + InAppPreviewAvatar, + InAppPreviewBody, + InAppPreviewHeader, + InAppPreviewNotification, + InAppPreviewNotificationContent, + InAppPreviewSubject, +} from '@/components/workflow-editor/in-app-preview'; import { useStepEditorContext } from '@/components/workflow-editor/steps/hooks'; +import { InAppRenderOutput } from '@novu/shared'; export function ConfigureInAppPreview() { const { previewStep, data, isPending: isPreviewPending } = usePreviewStep(); @@ -23,5 +32,26 @@ export function ConfigureInAppPreview() { }); }, [workflowSlug, stepSlug, previewStep, step, isPendingStep]); - return ; + if (!isPreviewPending && !data?.result) { + return null; + } + + const preview = data?.result?.preview as InAppRenderOutput | undefined; + + return ( + + + + + + + + {preview?.subject} + + {preview?.body} + + + + + ); } diff --git a/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-action.tsx b/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-action.tsx index 1a72e6fab53..405181fc017 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-action.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-action.tsx @@ -172,13 +172,12 @@ const ConfigureActionPopover = (props: ComponentProps & { Button text
- + diff --git a/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-editor-preview.tsx b/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-editor-preview.tsx index eb3621983fe..c5fd52c50cb 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-editor-preview.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-editor-preview.tsx @@ -1,11 +1,23 @@ import { CSSProperties, useEffect, useRef, useState } from 'react'; -import { GeneratePreviewResponseDto } from '@novu/shared'; +import { InAppRenderOutput } from '@novu/shared'; import { Notification5Fill } from '@/components/icons'; import { Code2 } from '@/components/icons/code-2'; import { Button } from '@/components/primitives/button'; import { Editor } from '@/components/primitives/editor'; -import { InAppPreview } from '@/components/workflow-editor/in-app-preview'; +import { + InAppPreview, + InAppPreviewActions, + InAppPreviewAvatar, + InAppPreviewBell, + InAppPreviewBody, + InAppPreviewHeader, + InAppPreviewNotification, + InAppPreviewNotificationContent, + InAppPreviewPrimaryAction, + InAppPreviewSecondaryAction, + InAppPreviewSubject, +} from '@/components/workflow-editor/in-app-preview'; import { loadLanguage } from '@uiw/codemirror-extensions-langs'; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/primitives/accordion'; @@ -20,12 +32,12 @@ const getInitialAccordionValue = (value: string) => { type InAppEditorPreviewProps = { value: string; onChange: (value: string) => void; - previewData?: GeneratePreviewResponseDto; + preview?: InAppRenderOutput; applyPreview: () => void; - isPreviewLoading?: boolean; + isPreviewPending?: boolean; }; export const InAppEditorPreview = (props: InAppEditorPreviewProps) => { - const { value, onChange, previewData, applyPreview, isPreviewLoading } = props; + const { value, onChange, preview, applyPreview, isPreviewPending } = props; const [accordionValue, setAccordionValue] = useState(getInitialAccordionValue(value)); const [payloadError, setPayloadError] = useState(''); const [height, setHeight] = useState(0); @@ -47,13 +59,43 @@ export const InAppEditorPreview = (props: InAppEditorPreviewProps) => { }, [value]); return ( -
+
In-app template editor
- +
+
+ + + + + + + + + + {preview?.subject} + + {preview?.body} + + + + + {preview?.primaryAction?.label} + + + + {preview?.secondaryAction?.label} + + + + + +
+
+
diff --git a/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-tabs.tsx b/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-tabs.tsx index 1091e89bb90..df44076655e 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-tabs.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-tabs.tsx @@ -1,5 +1,5 @@ import { zodResolver } from '@hookform/resolvers/zod'; -import { type StepDataDto, type StepUpdateDto, type WorkflowResponseDto } from '@novu/shared'; +import { ChannelTypeEnum, type StepDataDto, type StepUpdateDto, type WorkflowResponseDto } from '@novu/shared'; import { Cross2Icon } from '@radix-ui/react-icons'; import { useEffect, useMemo, useState } from 'react'; import { FieldValues, useForm, useWatch } from 'react-hook-form'; @@ -222,19 +222,25 @@ export const InAppTabs = ({ workflow, step }: { workflow: WorkflowResponseDto; s - { - previewStep({ - stepSlug, - workflowSlug, - data: { controlValues: form.getValues() as FieldValues, previewPayload: JSON.parse(editorValue) }, - }); - }} - /> + {previewData === undefined || + (previewData.result?.type === ChannelTypeEnum.IN_APP && ( + { + previewStep({ + stepSlug, + workflowSlug, + data: { + controlValues: form.getValues() as FieldValues, + previewPayload: JSON.parse(editorValue), + }, + }); + }} + /> + ))}