Skip to content

Commit

Permalink
Merge branch 'next' into nv-4932-redo-the-select-state-for-step-in-th…
Browse files Browse the repository at this point in the history
…e-canvas
  • Loading branch information
BiswaViraj authored Dec 18, 2024
2 parents b7c29cd + 7ac6ced commit 9fee67c
Show file tree
Hide file tree
Showing 19 changed files with 872 additions and 49 deletions.
2 changes: 1 addition & 1 deletion apps/api/src/app/subscribers/subscribers.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ export class SubscribersController {
type: String,
enum: PreferenceLevelEnum,
required: true,
description: 'the preferences level to be retrieved (template / global) ',
description: 'Fetch global or per workflow channel preferences',
})
@ApiQuery({
name: 'includeInactiveChannels',
Expand Down
1 change: 1 addition & 0 deletions apps/dashboard/public/images/phones/iphone-push.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
215 changes: 215 additions & 0 deletions apps/dashboard/public/images/phones/iphone-sms.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 15 additions & 9 deletions apps/dashboard/src/components/workflow-editor/add-step-menu.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { useFeatureFlag } from '@/hooks/use-feature-flag';
import { STEP_TYPE_TO_COLOR } from '@/utils/color';
import { StepTypeEnum } from '@/utils/enums';
import { cn } from '@/utils/ui';
import { FeatureFlagsKeysEnum } from '@novu/shared';
import { PopoverPortal } from '@radix-ui/react-popover';
import React, { ReactNode, useState } from 'react';
import { RiAddLine } from 'react-icons/ri';
import { PopoverPortal } from '@radix-ui/react-popover';
import { Node } from './base-node';
import { Popover, PopoverContent, PopoverTrigger } from '../primitives/popover';
import { STEP_TYPE_TO_ICON } from '../icons/utils';
import { Badge } from '../primitives/badge';
import { cn } from '@/utils/ui';
import { StepTypeEnum } from '@/utils/enums';
import { STEP_TYPE_TO_COLOR } from '@/utils/color';
import { useFeatureFlag } from '@/hooks/use-feature-flag';
import { FeatureFlagsKeysEnum } from '@novu/shared';
import { Popover, PopoverContent, PopoverTrigger } from '../primitives/popover';
import { Node } from './base-node';

const noop = () => {};

Expand Down Expand Up @@ -132,7 +132,13 @@ export const AddStepMenu = ({
Push
</MenuItem>
<MenuItem stepType={StepTypeEnum.CHAT}>Chat</MenuItem>
<MenuItem stepType={StepTypeEnum.SMS}>SMS</MenuItem>
<MenuItem
stepType={StepTypeEnum.SMS}
disabled={!arePushChatSMSEnabled}
onClick={() => handleMenuItemClick(StepTypeEnum.SMS)}
>
SMS
</MenuItem>
</MenuItemsGroup>
</MenuGroup>
<MenuGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { UiComponentEnum } from '@novu/shared';

import { InAppAction } from '@/components/workflow-editor/steps/in-app/in-app-action';
import { InAppSubject } from '@/components/workflow-editor/steps/in-app/in-app-subject';
import { InAppBody } from '@/components/workflow-editor/steps/in-app/in-app-body';
import { InAppAvatar } from '@/components/workflow-editor/steps/in-app/in-app-avatar';
import { InAppRedirect } from '@/components/workflow-editor/steps/in-app/in-app-redirect';
import { DelayAmount } from '@/components/workflow-editor/steps/delay/delay-amount';
import { Maily } from '@/components/workflow-editor/steps/email/maily';
import { EmailSubject } from '@/components/workflow-editor/steps/email/email-subject';
import { DigestKey } from '@/components/workflow-editor/steps/digest/digest-key';
import { DigestWindow } from '@/components/workflow-editor/steps/digest/digest-window';
import { EmailSubject } from '@/components/workflow-editor/steps/email/email-subject';
import { Maily } from '@/components/workflow-editor/steps/email/maily';
import { InAppAction } from '@/components/workflow-editor/steps/in-app/in-app-action';
import { InAppAvatar } from '@/components/workflow-editor/steps/in-app/in-app-avatar';
import { InAppBody } from '@/components/workflow-editor/steps/in-app/in-app-body';
import { InAppRedirect } from '@/components/workflow-editor/steps/in-app/in-app-redirect';
import { InAppSubject } from '@/components/workflow-editor/steps/in-app/in-app-subject';
import { BaseBody } from './base/base-body';
import { BaseSubject } from './base/base-subject';

Expand Down Expand Up @@ -55,7 +55,9 @@ export const getComponentByType = ({ component }: { component?: UiComponentEnum
case UiComponentEnum.PUSH_SUBJECT: {
return <BaseSubject />;
}

case UiComponentEnum.SMS_BODY: {
return <BaseBody />;
}
default: {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import {
WorkflowOriginEnum,
WorkflowResponseDto,
} from '@novu/shared';
import merge from 'lodash.merge';
import { AnimatePresence, motion } from 'motion/react';
import { useEffect, useCallback, useMemo, useState, HTMLAttributes, ReactNode } from 'react';
import { HTMLAttributes, ReactNode, useCallback, useEffect, useMemo, useState } from 'react';
import { useForm } from 'react-hook-form';
import { RiArrowLeftSLine, RiArrowRightSLine, RiCloseFill, RiDeleteBin2Line, RiPencilRuler2Fill } from 'react-icons/ri';
import { Link, useNavigate } from 'react-router-dom';
import merge from 'lodash.merge';

import { ConfirmationModal } from '@/components/confirmation-modal';
import { PageMeta } from '@/components/page-meta';
Expand All @@ -32,24 +32,25 @@ import {
getFirstControlsErrorMessage,
updateStepInWorkflow,
} from '@/components/workflow-editor/step-utils';
import { ConfigureStepTemplateIssueCta } from '@/components/workflow-editor/steps/configure-step-template-issue-cta';
import { DelayControlValues } from '@/components/workflow-editor/steps/delay/delay-control-values';
import { DigestControlValues } from '@/components/workflow-editor/steps/digest/digest-control-values';
import { ConfigureEmailStepPreview } from '@/components/workflow-editor/steps/email/configure-email-step-preview';
import { ConfigureInAppStepPreview } from '@/components/workflow-editor/steps/in-app/configure-in-app-step-preview';
import { SaveFormContext } from '@/components/workflow-editor/steps/save-form-context';
import { SdkBanner } from '@/components/workflow-editor/steps/sdk-banner';
import { buildRoute, ROUTES } from '@/utils/routes';
import { ConfigureSmsStepPreview } from '@/components/workflow-editor/steps/sms/configure-sms-step-preview';
import { useFeatureFlag } from '@/hooks/use-feature-flag';
import { useFormAutosave } from '@/hooks/use-form-autosave';
import {
AUTOCOMPLETE_PASSWORD_MANAGERS_OFF,
INLINE_CONFIGURABLE_STEP_TYPES,
TEMPLATE_CONFIGURABLE_STEP_TYPES,
STEP_TYPE_LABELS,
TEMPLATE_CONFIGURABLE_STEP_TYPES,
} from '@/utils/constants';
import { useFormAutosave } from '@/hooks/use-form-autosave';
import { buildDefaultValuesOfDataSchema, buildDynamicZodSchema } from '@/utils/schema';
import { buildDefaultValues } from '@/utils/schema';
import { DelayControlValues } from '@/components/workflow-editor/steps/delay/delay-control-values';
import { ConfigureStepTemplateIssueCta } from '@/components/workflow-editor/steps/configure-step-template-issue-cta';
import { ConfigureInAppStepPreview } from '@/components/workflow-editor/steps/in-app/configure-in-app-step-preview';
import { ConfigureEmailStepPreview } from '@/components/workflow-editor/steps/email/configure-email-step-preview';
import { useFeatureFlag } from '@/hooks/use-feature-flag';
import { DigestControlValues } from '@/components/workflow-editor/steps/digest/digest-control-values';
import { SaveFormContext } from '@/components/workflow-editor/steps/save-form-context';
import { buildRoute, ROUTES } from '@/utils/routes';
import { buildDefaultValues, buildDefaultValuesOfDataSchema, buildDynamicZodSchema } from '@/utils/schema';
import { ConfigurePushStepPreview } from './push/configure-push-step-preview';

const STEP_TYPE_TO_INLINE_CONTROL_VALUES: Record<StepTypeEnum, () => React.JSX.Element | null> = {
[StepTypeEnum.DELAY]: DelayControlValues,
Expand All @@ -66,9 +67,9 @@ const STEP_TYPE_TO_INLINE_CONTROL_VALUES: Record<StepTypeEnum, () => React.JSX.E
const STEP_TYPE_TO_PREVIEW: Record<StepTypeEnum, ((props: HTMLAttributes<HTMLDivElement>) => ReactNode) | null> = {
[StepTypeEnum.IN_APP]: ConfigureInAppStepPreview,
[StepTypeEnum.EMAIL]: ConfigureEmailStepPreview,
[StepTypeEnum.SMS]: null,
[StepTypeEnum.SMS]: ConfigureSmsStepPreview,
[StepTypeEnum.CHAT]: null,
[StepTypeEnum.PUSH]: null,
[StepTypeEnum.PUSH]: ConfigurePushStepPreview,
[StepTypeEnum.CUSTOM]: null,
[StepTypeEnum.TRIGGER]: null,
[StepTypeEnum.DIGEST]: null,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
import { useCallback, useEffect, useMemo } from 'react';
import merge from 'lodash.merge';
import isEqual from 'lodash.isequal';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import {
type StepDataDto,
Expand All @@ -10,23 +6,28 @@ import {
UpdateWorkflowDto,
type WorkflowResponseDto,
} from '@novu/shared';
import isEqual from 'lodash.isequal';
import merge from 'lodash.merge';
import { useCallback, useEffect, useMemo } from 'react';
import { useForm } from 'react-hook-form';

import { Form } from '@/components/primitives/form/form';
import { flattenIssues, updateStepInWorkflow } from '@/components/workflow-editor/step-utils';
import { EmailTabs } from '@/components/workflow-editor/steps/email/email-tabs';
import { InAppTabs } from '@/components/workflow-editor/steps/in-app/in-app-tabs';
import { buildDefaultValues, buildDefaultValuesOfDataSchema, buildDynamicZodSchema } from '@/utils/schema';
import { OtherStepTabs } from './other-steps-tabs';
import { Form } from '@/components/primitives/form/form';
import { useFormAutosave } from '@/hooks/use-form-autosave';
import { PushTabs } from '@/components/workflow-editor/steps/push/push-tabs';
import { SaveFormContext } from '@/components/workflow-editor/steps/save-form-context';
import { EmailTabs } from '@/components/workflow-editor/steps/email/email-tabs';
import { SmsTabs } from '@/components/workflow-editor/steps/sms/sms-tabs';
import { useFormAutosave } from '@/hooks/use-form-autosave';
import { buildDefaultValues, buildDefaultValuesOfDataSchema, buildDynamicZodSchema } from '@/utils/schema';
import { CommonCustomControlValues } from './common/common-custom-control-values';
import { PushTabs } from '@/components/workflow-editor/steps/push/push-tabs';
import { OtherStepTabs } from './other-steps-tabs';

const STEP_TYPE_TO_TEMPLATE_FORM: Record<StepTypeEnum, (args: StepEditorProps) => React.JSX.Element | null> = {
[StepTypeEnum.EMAIL]: EmailTabs,
[StepTypeEnum.CHAT]: OtherStepTabs,
[StepTypeEnum.IN_APP]: InAppTabs,
[StepTypeEnum.SMS]: OtherStepTabs,
[StepTypeEnum.SMS]: SmsTabs,
[StepTypeEnum.PUSH]: PushTabs,
[StepTypeEnum.DIGEST]: CommonCustomControlValues,
[StepTypeEnum.DELAY]: CommonCustomControlValues,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { usePreviewStep } from '@/hooks/use-preview-step';
import { PushPreview } from './push-preview';
import * as Sentry from '@sentry/react';
import { useWorkflow } from '../../workflow-provider';
import { useParams } from 'react-router-dom';
import { useEffect } from 'react';

export function ConfigurePushStepPreview() {
const {
previewStep,
data: previewData,
isPending: isPreviewPending,
} = usePreviewStep({
onError: (error) => {
Sentry.captureException(error);
},
});

const { step, isPending } = useWorkflow();

const { workflowSlug, stepSlug } = useParams<{
workflowSlug: string;
stepSlug: string;
}>();

useEffect(() => {
if (!workflowSlug || !stepSlug || !step || isPending) return;

previewStep({
workflowSlug,
stepSlug,
previewData: { controlValues: step.controls.values, previewPayload: {} },
});
}, [workflowSlug, stepSlug, previewStep, step, isPending]);

return <PushPreview previewData={previewData} isPreviewPending={isPreviewPending} />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { CSSProperties, useEffect, useRef, useState } from 'react';
import { type StepDataDto, type WorkflowResponseDto } from '@novu/shared';
import { Code2 } from '@/components/icons/code-2';
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/primitives/accordion';
import { Button } from '@/components/primitives/button';
import { Editor } from '@/components/primitives/editor';
import { loadLanguage } from '@uiw/codemirror-extensions-langs';
import { useEditorPreview } from '../use-editor-preview';
import { PushPreview } from './push-preview';
import { RiCellphoneFill } from 'react-icons/ri';
import { PushTabsSection } from './push-tabs-section';

const getInitialAccordionValue = (value: string) => {
try {
return Object.keys(JSON.parse(value)).length > 0 ? 'payload' : undefined;
} catch (e) {
return undefined;
}
};

type PushEditorPreviewProps = {
workflow: WorkflowResponseDto;
step: StepDataDto;
formValues: Record<string, unknown>;
};

const extensions = [loadLanguage('json')?.extension ?? []];

export const PushEditorPreview = ({ workflow, step, formValues }: PushEditorPreviewProps) => {
const workflowSlug = workflow.workflowId;
const stepSlug = step.stepId;
const { editorValue, setEditorValue, previewStep, previewData, isPreviewPending } = useEditorPreview({
workflowSlug,
stepSlug,
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(editorValue));
}, [editorValue]);

useEffect(() => {
const timeout = setTimeout(() => {
if (contentRef.current) {
const rect = contentRef.current.getBoundingClientRect();
setHeight(rect.height);
}
}, 0);

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

return (
<PushTabsSection>
<div className="relative flex flex-col gap-3">
<div className="flex items-center gap-2.5 pb-2 text-sm font-medium">
<RiCellphoneFill className="size-3" />
<span>Push template editor</span>
</div>
<div className="flex flex-col items-center justify-center gap-4">
<PushPreview isPreviewPending={isPreviewPending} previewData={previewData} />
<div className="flex w-full items-center gap-3 rounded-md border border-neutral-100 bg-neutral-50 px-3 py-2.5">
<span className="w-1 self-stretch rounded-full bg-neutral-500" />
<span className="flex-1 text-xs font-medium text-neutral-600">
This preview shows how your message will appear on mobile. Actual rendering may vary by device.
</span>
</div>
</div>
<Accordion type="single" collapsible value={accordionValue} onValueChange={setAccordionValue}>
<AccordionItem value="payload">
<AccordionTrigger>
<div className="flex items-center gap-1">
<Code2 className="size-5" />
Configure preview
</div>
</AccordionTrigger>
<AccordionContent
ref={contentRef}
className="flex flex-col gap-2"
style={{ '--radix-collapsible-content-height': `${height}px` } as CSSProperties}
>
<Editor
value={editorValue}
onChange={setEditorValue}
lang="json"
extensions={extensions}
className="border-neutral-alpha-200 bg-background text-foreground-600 mx-0 mt-0 rounded-lg border border-dashed p-3"
/>
{payloadError && <p className="text-destructive text-xs">{payloadError}</p>}
<Button
size="xs"
type="button"
variant="outline"
className="self-end"
onClick={() => {
try {
previewStep();
setPayloadError('');
} catch (e) {
setPayloadError(String(e));
}
}}
>
Apply
</Button>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
</PushTabsSection>
);
};
Loading

0 comments on commit 9fee67c

Please sign in to comment.