Skip to content

Commit

Permalink
chore(dashboard): suggestions from the pr #6718
Browse files Browse the repository at this point in the history
  • Loading branch information
LetItRock committed Oct 21, 2024
1 parent 761f99d commit 2b1ec25
Show file tree
Hide file tree
Showing 11 changed files with 863 additions and 707 deletions.
4 changes: 3 additions & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,9 @@
"zulip",
"zwnj",
"motionone",
"xyflow"
"xyflow",
"Sonner",
"sonner",
],
"flagWords": [],
"patterns": [
Expand Down
3 changes: 2 additions & 1 deletion apps/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"debounce": "^2.2.0",
"lodash.debounce": "^4.0.8",
"lucide-react": "^0.439.0",
"mixpanel-browser": "^2.52.0",
"next-themes": "^0.3.0",
Expand All @@ -63,6 +63,7 @@
"@clerk/types": "^4.6.1",
"@eslint/js": "^9.9.0",
"@playwright/test": "^1.44.0",
"@types/lodash.debounce": "^4.0.9",
"@types/mixpanel-browser": "^2.49.0",
"@types/node": "^22.7.0",
"@types/react": "^18.3.3",
Expand Down
11 changes: 11 additions & 0 deletions apps/dashboard/src/components/primitives/sonner-helpers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ReactNode } from 'react';
import { ExternalToast, toast } from 'sonner';
import { SmallToast } from './sonner';

export const smallToast = ({ children, options }: { children: ReactNode; options: ExternalToast }) => {
return toast(<SmallToast>{children}</SmallToast>, {
duration: 5000,
unstyled: true,
...options,
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
useReactFlow,
ViewportHelperFunctionOptions,
} from '@xyflow/react';
import type { StepDto } from '@novu/shared';
import '@xyflow/react/dist/style.css';
import {
AddNode,
Expand All @@ -27,6 +26,7 @@ import {
import { AddNodeEdgeType, AddNodeEdge } from './edges';
import { NODE_HEIGHT, NODE_WIDTH } from './base-node';
import { StepTypeEnum } from '@/utils/enums';
import { Step } from '@/utils/types';

const nodeTypes = {
trigger: TriggerNode,
Expand All @@ -51,7 +51,7 @@ const panOnDrag = [1, 2];
const Y_DISTANCE = NODE_HEIGHT + 50;

const mapStepToNode = (
step: StepDto,
step: Step,
previousPosition: { x: number; y: number },
addStepIndex: number
): Node<NodeData, keyof typeof nodeTypes> => {
Expand All @@ -72,7 +72,7 @@ const mapStepToNode = (
};
};

const WorkflowCanvasChild = ({ steps }: { steps: StepDto[] }) => {
const WorkflowCanvasChild = ({ steps }: { steps: Step[] }) => {
const reactFlowWrapper = useRef<HTMLDivElement>(null);
const reactFlowInstance = useReactFlow();

Expand Down Expand Up @@ -168,7 +168,7 @@ const WorkflowCanvasChild = ({ steps }: { steps: StepDto[] }) => {
);
};

export const WorkflowCanvas = ({ steps }: { steps: StepDto[] }) => {
export const WorkflowCanvas = ({ steps }: { steps: Step[] }) => {
return (
<ReactFlowProvider>
<WorkflowCanvasChild steps={steps} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { ReactNode, useMemo, useCallback, useRef } from 'react';
import { ReactNode, useMemo, useCallback, useRef, useLayoutEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useForm, useFieldArray } from 'react-hook-form';
// eslint-disable-next-line
// @ts-ignore
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';
import { toast } from 'sonner';
import { RiProgress1Line } from 'react-icons/ri';
import type { StepCreateDto } from '@novu/shared';

import { WorkflowEditorContext } from './workflow-editor-context';
import { StepTypeEnum } from '@/utils/enums';
Expand All @@ -16,7 +14,8 @@ import { buildRoute, ROUTES } from '@/utils/routes';
import { useEnvironment } from '@/context/environment/hooks';
import { formSchema } from './schema';
import { useFetchWorkflow, useUpdateWorkflow, useFormAutoSave } from '@/hooks';
import { SmallToast } from '../primitives/sonner';
import { Step } from '@/utils/types';
import { smallToast } from '../primitives/sonner-helpers';

const STEP_NAME_BY_TYPE: Record<StepTypeEnum, string> = {
email: 'Email Step',
Expand All @@ -30,7 +29,7 @@ const STEP_NAME_BY_TYPE: Record<StepTypeEnum, string> = {
custom: 'Custom Step',
};

const createStep = (type: StepTypeEnum): Pick<StepCreateDto, 'name' | 'type'> => ({
const createStep = (type: StepTypeEnum): Step => ({
name: STEP_NAME_BY_TYPE[type],
type,
});
Expand All @@ -47,40 +46,46 @@ export const WorkflowEditorProvider = ({ children }: { children: ReactNode }) =>
name: 'steps',
});

const { workflow } = useFetchWorkflow({
const { workflow, error } = useFetchWorkflow({
workflowId,
onSuccess: (data) => {
reset({ ...data, steps: data.steps.map((step) => ({ ...step })) });
},
onError: () => {
navigate(buildRoute(ROUTES.WORKFLOWS, { environmentId: currentEnvironment?._id ?? '' }));
},
});

useLayoutEffect(() => {
if (error) {
navigate(buildRoute(ROUTES.WORKFLOWS, { environmentId: currentEnvironment?._id ?? '' }));
}

if (!workflow) {
return;
}

reset({ ...workflow, steps: workflow.steps.map((step) => ({ ...step })) });
}, [workflow, error, navigate, reset, currentEnvironment]);

const { updateWorkflow } = useUpdateWorkflow({
onSuccess: (data) => {
reset({ ...data, steps: data.steps.map((step) => ({ ...step })) });
if (changesSavedToastIdRef.current) {
return;
}

const id = toast(
<SmallToast>
<RiProgress1Line className="size-6" />
<span className="text-sm">Saved</span>
</SmallToast>,
{
duration: 5000,
const id = smallToast({
children: (
<>
<RiProgress1Line className="size-6" />
<span className="text-sm">Saved</span>
</>
),
options: {
position: 'bottom-left',
unstyled: true,
classNames: {
toast: 'ml-10',
},
onAutoClose: () => {
changesSavedToastIdRef.current = undefined;
},
}
);
},
});
changesSavedToastIdRef.current = id;
},
});
Expand Down
8 changes: 8 additions & 0 deletions apps/dashboard/src/hooks/use-data-ref.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { useRef } from 'react';

export const useDataRef = <T>(data: T) => {
const ref = useRef<T>(data);
ref.current = data;

return ref;
};
14 changes: 14 additions & 0 deletions apps/dashboard/src/hooks/use-debounce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useCallback, useEffect } from 'react';
import debounce from 'lodash.debounce';
import { useDataRef } from './use-data-ref';

export const useDebounce = <Arguments = unknown | unknown[]>(callback: (args?: Arguments) => void, ms = 0) => {
const callbackRef = useDataRef(callback);

// eslint-disable-next-line react-hooks/exhaustive-deps
const debouncedCallback = useCallback(debounce(callbackRef.current, ms), [callbackRef, ms]);

useEffect(() => debouncedCallback.cancel, [debouncedCallback.cancel]);

return debouncedCallback;
};
21 changes: 2 additions & 19 deletions apps/dashboard/src/hooks/use-fetch-workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,11 @@ import { QueryKeys } from '@/utils/query-keys';
import { fetchWorkflow } from '@/api/workflows';
import { useEnvironment } from '@/context/environment/hooks';

export const useFetchWorkflow = ({
workflowId,
onSuccess,
onError,
}: {
workflowId?: string;
onSuccess?: (data: WorkflowResponseDto) => void;
onError?: (error: unknown) => void;
}) => {
export const useFetchWorkflow = ({ workflowId }: { workflowId?: string }) => {
const { currentEnvironment } = useEnvironment();
const { data, isPending, error } = useQuery<WorkflowResponseDto>({
queryKey: [QueryKeys.fetchWorkflow, currentEnvironment?._id, workflowId],
queryFn: async () => {
try {
const result = await fetchWorkflow({ workflowId });
onSuccess?.(result);
return result;
} catch (error) {
onError?.(error);
throw error;
}
},
queryFn: () => fetchWorkflow({ workflowId }),
enabled: !!currentEnvironment?._id && !!workflowId,
});

Expand Down
16 changes: 6 additions & 10 deletions apps/dashboard/src/hooks/use-form-autosave.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useCallback, useRef } from 'react';
import { FieldValues, SubmitHandler, UseFormReturn, useWatch } from 'react-hook-form';
import debounce from 'debounce';
import useDeepCompareEffect from 'use-deep-compare-effect';
import { useDebounce } from './use-debounce';
import { useDataRef } from './use-data-ref';

export const useFormAutoSave = <T extends FieldValues>({
onSubmit,
Expand All @@ -10,20 +10,16 @@ export const useFormAutoSave = <T extends FieldValues>({
onSubmit: SubmitHandler<T>;
form: UseFormReturn<T>;
}) => {
const onSubmitRef = useRef<SubmitHandler<T>>(onSubmit);
onSubmitRef.current = onSubmit;
const onSubmitRef = useDataRef(onSubmit);
const { formState, control, handleSubmit } = form;

const watchedData = useWatch<T>({
control,
});

const debouncedSave = useCallback<() => void>(
debounce(() => {
handleSubmit(onSubmitRef.current)();
}, 1000),
[handleSubmit]
);
const debouncedSave = useDebounce(() => {
handleSubmit(onSubmitRef.current)();
}, 500);

useDeepCompareEffect(() => {
if (formState.isDirty) {
Expand Down
5 changes: 5 additions & 0 deletions apps/dashboard/src/utils/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { StepCreateDto } from '@novu/shared';

export enum BaseEnvironmentEnum {
DEVELOPMENT = 'Development',
PRODUCTION = 'Production',
Expand All @@ -16,3 +18,6 @@ export enum ConnectionStatus {
DISCONNECTED = 'disconnected',
LOADING = 'loading',
}

// TODO: update this when the API types are updated
export type Step = Pick<StepCreateDto, 'name' | 'type'>;
Loading

0 comments on commit 2b1ec25

Please sign in to comment.