Skip to content

Commit

Permalink
fix(dashboard): in-app editor preview only call when tab is opened
Browse files Browse the repository at this point in the history
  • Loading branch information
LetItRock committed Dec 2, 2024
1 parent 8713310 commit 629ebff
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 108 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CSSProperties, useEffect, useRef, useState } from 'react';
import { InAppRenderOutput } from '@novu/shared';
import { InAppRenderOutput, StepDataDto, WorkflowResponseDto } from '@novu/shared';

import { Notification5Fill } from '@/components/icons';
import { Code2 } from '@/components/icons/code-2';
Expand All @@ -21,6 +21,7 @@ import {
import { loadLanguage } from '@uiw/codemirror-extensions-langs';
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/primitives/accordion';
import { InAppTabsSection } from '@/components/workflow-editor/steps/in-app/in-app-tabs-section';
import { useEditorPreview } from '../use-editor-preview';

const getInitialAccordionValue = (value: string) => {
try {
Expand All @@ -31,22 +32,28 @@ const getInitialAccordionValue = (value: string) => {
};

type InAppEditorPreviewProps = {
value: string;
onChange: (value: string) => void;
preview?: InAppRenderOutput;
applyPreview: () => void;
isPreviewPending?: boolean;
workflow: WorkflowResponseDto;
step: StepDataDto;
formValues: Record<string, unknown>;
};
export const InAppEditorPreview = (props: InAppEditorPreviewProps) => {
const { value, onChange, preview, applyPreview, isPreviewPending } = props;
const [accordionValue, setAccordionValue] = useState<string | undefined>(getInitialAccordionValue(value));

export const InAppEditorPreview = ({ workflow, step, formValues }: InAppEditorPreviewProps) => {
const workflowSlug = workflow.workflowId;
const stepSlug = step.stepId;
const { editorValue, setEditorValue, previewStep, previewData, isPreviewPending } = useEditorPreview({
workflowSlug,
stepSlug,
stepName: step.name,
controlValues: formValues,
});
const [accordionValue, setAccordionValue] = useState<string | undefined>(getInitialAccordionValue(editorValue));
const [payloadError, setPayloadError] = useState('');
const [height, setHeight] = useState(0);
const contentRef = useRef<HTMLDivElement>(null);

useEffect(() => {
setAccordionValue(getInitialAccordionValue(value));
}, [value]);
setAccordionValue(getInitialAccordionValue(editorValue));
}, [editorValue]);

useEffect(() => {
const timeout = setTimeout(() => {
Expand All @@ -57,7 +64,10 @@ export const InAppEditorPreview = (props: InAppEditorPreviewProps) => {
}, 0);

return () => clearTimeout(timeout);
}, [value]);
}, [editorValue]);

const previewResult = previewData?.result;
const preview = previewResult?.preview as InAppRenderOutput | undefined;

return (
<InAppTabsSection>
Expand Down Expand Up @@ -113,8 +123,8 @@ export const InAppEditorPreview = (props: InAppEditorPreviewProps) => {
style={{ '--radix-collapsible-content-height': `${height}px` } as CSSProperties}
>
<Editor
value={value}
onChange={onChange}
value={editorValue}
onChange={setEditorValue}
lang="json"
extensions={[loadLanguage('json')?.extension ?? []]}
className="border-neutral-alpha-200 bg-background text-foreground-600 mx-0 mt-0 rounded-lg border border-dashed p-3"
Expand All @@ -127,7 +137,7 @@ export const InAppEditorPreview = (props: InAppEditorPreviewProps) => {
className="self-end"
onClick={() => {
try {
applyPreview();
previewStep();
setPayloadError('');
} catch (e) {
setPayloadError(String(e));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { ChannelTypeEnum } from '@novu/shared';
import { Cross2Icon } from '@radix-ui/react-icons';
import { FieldValues, useFormContext } from 'react-hook-form';
import { useFormContext } from 'react-hook-form';
import { RiEdit2Line, RiPencilRuler2Line } from 'react-icons/ri';
import { useNavigate } from 'react-router-dom';

Expand All @@ -12,7 +11,6 @@ import { InAppEditor } from '@/components/workflow-editor/steps/in-app/in-app-ed
import { InAppEditorPreview } from '@/components/workflow-editor/steps/in-app/in-app-editor-preview';
import { CustomStepControls } from '../controls/custom-step-controls';
import { StepEditorProps } from '@/components/workflow-editor/steps/configure-step-template-form';
import { useDebouncedPreview } from '../use-debounced-preview';

const tabsContentClassName = 'h-full w-full overflow-y-auto';

Expand All @@ -21,10 +19,6 @@ export const InAppTabs = (props: StepEditorProps) => {
const { dataSchema, uiSchema } = step.controls;
const form = useFormContext();
const navigate = useNavigate();
const { editorValue, setEditorValue, previewStep, previewData, isPreviewPending } = useDebouncedPreview({
workflow,
step,
});

return (
<Tabs defaultValue="editor" className="flex h-full flex-1 flex-col">
Expand Down Expand Up @@ -64,25 +58,7 @@ export const InAppTabs = (props: StepEditorProps) => {
<CustomStepControls dataSchema={dataSchema} origin={workflow.origin} />
</TabsContent>
<TabsContent value="preview" className={tabsContentClassName}>
{previewData === undefined ||
(previewData.result?.type === ChannelTypeEnum.IN_APP && (
<InAppEditorPreview
value={editorValue}
onChange={setEditorValue}
preview={previewData?.result.preview}
isPreviewPending={isPreviewPending}
applyPreview={() => {
previewStep({
stepSlug: step.stepId,
workflowSlug: workflow.workflowId,
data: {
controlValues: form.getValues() as FieldValues,
previewPayload: JSON.parse(editorValue),
},
});
}}
/>
))}
<InAppEditorPreview workflow={workflow} step={step} formValues={form.getValues()} />
</TabsContent>
<Separator />
</Tabs>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { useCallback, useEffect, useState } from 'react';

import { usePreviewStep } from '@/hooks';
import { NovuApiError } from '@/api/api.client';
import { ToastIcon } from '@/components/primitives/sonner';
import { showToast } from '@/components/primitives/sonner-helpers';
import { useDataRef } from '@/hooks/use-data-ref';

export const useEditorPreview = ({
workflowSlug,
stepSlug,
stepName,
controlValues,
}: {
workflowSlug: string;
stepSlug: string;
stepName: string;
controlValues: Record<string, unknown>;
}) => {
const [editorValue, setEditorValue] = useState('{}');
const {
previewStep,
data: previewData,
isPending: isPreviewPending,
} = usePreviewStep({
onSuccess: (res) => {
setEditorValue(JSON.stringify(res.previewPayloadExample, null, 2));
},
onError: (error) => {
if (error instanceof NovuApiError) {
showToast({
children: () => (
<>
<ToastIcon variant="error" />
<span className="text-sm">
Failed to preview step <span className="font-bold">{stepName}</span> with error: {error.message}
</span>
</>
),
options: {
position: 'bottom-right',
classNames: {
toast: 'ml-10 mb-4',
},
},
});
}
},
});
const dataRef = useDataRef({
workflowSlug,
stepSlug,
controlValues,
editorValue,
});

useEffect(() => {
previewStep({
workflowSlug: dataRef.current.workflowSlug,
stepSlug: dataRef.current.stepSlug,
data: { controlValues: dataRef.current.controlValues, previewPayload: JSON.parse(dataRef.current.editorValue) },
});
}, [dataRef, previewStep]);

const previewStepCallback = useCallback(() => {
return previewStep({
workflowSlug,
stepSlug,
data: { controlValues, previewPayload: JSON.parse(editorValue) },
});
}, [workflowSlug, stepSlug, controlValues, editorValue, previewStep]);

return { editorValue, setEditorValue, previewStep: previewStepCallback, previewData, isPreviewPending };
};
6 changes: 5 additions & 1 deletion apps/dashboard/src/hooks/use-preview-step.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ import type { GeneratePreviewResponseDto } from '@novu/shared';
import { useMutation } from '@tanstack/react-query';
import { previewStep } from '@/api/workflows';

export const usePreviewStep = ({ onSuccess }: { onSuccess?: (data: GeneratePreviewResponseDto) => void } = {}) => {
export const usePreviewStep = ({
onSuccess,
onError,
}: { onSuccess?: (data: GeneratePreviewResponseDto) => void; onError?: (error: Error) => void } = {}) => {
const { mutateAsync, isPending, error, data } = useMutation({
mutationFn: previewStep,
onSuccess,
onError,
});

return {
Expand Down

0 comments on commit 629ebff

Please sign in to comment.