diff --git a/.cspell.json b/.cspell.json index e305b5b9e84..db69b9bae5e 100644 --- a/.cspell.json +++ b/.cspell.json @@ -690,7 +690,8 @@ "xyflow", "Sonner", "sonner", - "cmdk" + "cmdk", + "Keymap" ], "flagWords": [], "patterns": [ diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index 50e609f86ec..6339857fab9 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -25,8 +25,8 @@ "@monaco-editor/react": "^4.6.0", "@novu/react": "workspace:*", "@novu/shared": "workspace:*", - "@radix-ui/react-avatar": "^1.1.1", "@radix-ui/react-accordion": "^1.2.1", + "@radix-ui/react-avatar": "^1.1.1", "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-icons": "^1.3.0", @@ -43,10 +43,14 @@ "@segment/analytics-next": "^1.73.0", "@sentry/react": "^8.35.0", "@tanstack/react-query": "^5.59.6", + "@uiw/codemirror-theme-white": "^4.23.6", + "@uiw/codemirror-themes": "^4.23.6", + "@uiw/react-codemirror": "^4.23.6", "@xyflow/react": "^12.3.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cmdk": "1.0.0", + "codemirror-lang-liquid": "^1.0.0", "date-fns": "^4.1.0", "framer-motion": "^11.3.19", "lodash.debounce": "^4.0.8", diff --git a/apps/dashboard/src/components/primitives/editor.tsx b/apps/dashboard/src/components/primitives/editor.tsx new file mode 100644 index 00000000000..d6d0e78a172 --- /dev/null +++ b/apps/dashboard/src/components/primitives/editor.tsx @@ -0,0 +1,64 @@ +import { useEffect, useRef } from 'react'; +import { useCodeMirror, EditorView } from '@uiw/react-codemirror'; +import { LiquidHTML } from 'codemirror-lang-liquid'; +import { cva, VariantProps } from 'class-variance-authority'; +import createTheme from '@uiw/codemirror-themes'; + +const editorVariants = cva('-mx-1 -mt-[2px] h-full w-full flex-1 [&_.cm-focused]:outline-none', { + variants: { + size: { + default: 'text-xs [&_.cm-editor]:py-1', + md: 'text-sm [&_.cm-editor]:py-2', + }, + }, + defaultVariants: { + size: 'default', + }, +}); + +const theme = createTheme({ + theme: 'light', + styles: [], + settings: { + background: 'transparent', + lineHighlight: 'transparent', + }, +}); + +type EditorProps = { + value: string; + placeholder?: string; + className?: string; + height?: string; + onChange: (val: string) => void; +} & VariantProps; + +export const Editor = ({ value, placeholder, className, height, size, onChange }: EditorProps) => { + const editor = useRef(null); + const { setContainer } = useCodeMirror({ + extensions: [LiquidHTML({}), EditorView.lineWrapping], + height, + placeholder, + basicSetup: { + lineNumbers: false, + foldGutter: false, + defaultKeymap: false, + highlightActiveLine: false, + highlightActiveLineGutter: false, + indentOnInput: false, + searchKeymap: false, + }, + container: editor.current, + value, + onChange, + theme, + }); + + useEffect(() => { + if (editor.current) { + setContainer(editor.current); + } + }, [setContainer]); + + return
; +}; diff --git a/apps/dashboard/src/components/primitives/url-input.tsx b/apps/dashboard/src/components/primitives/url-input.tsx index ec5976936d3..2eb1c041c7c 100644 --- a/apps/dashboard/src/components/primitives/url-input.tsx +++ b/apps/dashboard/src/components/primitives/url-input.tsx @@ -1,9 +1,8 @@ -'use client'; - +import { forwardRef } from 'react'; +import { RedirectTargetEnum } from '@novu/shared'; import { Input, InputField, InputFieldProps, InputProps } from '@/components/primitives/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/primitives/select'; -import { RedirectTargetEnum } from '@novu/shared'; -import { forwardRef } from 'react'; +import { Editor } from './editor'; type URLValue = { type: string; @@ -14,23 +13,35 @@ type URLInputProps = Omit & { options: string[]; value: URLValue; onChange: (value: URLValue) => void; + asEditor?: boolean; } & Pick; export const URLInput = forwardRef((props, ref) => { - const { options, value, onChange, size, ...rest } = props; + const { options, value, onChange, size = 'default', asEditor = false, placeholder, ...rest } = props; return (
- onChange({ ...value, url: e.target.value })} - {...rest} - /> + {asEditor ? ( + onChange({ ...value, url: val })} + height={size === 'md' ? '38px' : '30px'} + /> + ) : ( + onChange({ ...value, url: e.target.value })} + {...rest} + /> + )} + @@ -217,6 +218,7 @@ const ConfigureActionPopover = ( options={urlTargetTypes} value={field.value} onChange={(val) => field.onChange(val)} + asEditor /> diff --git a/apps/dashboard/src/components/workflow-editor/steps/edit-step-sidebar.tsx b/apps/dashboard/src/components/workflow-editor/steps/edit-step-sidebar.tsx index 2e994545cc7..686578a3fd3 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/edit-step-sidebar.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/edit-step-sidebar.tsx @@ -14,13 +14,13 @@ import { StepEditor } from './step-editor'; const transitionSetting = { ease: [0.29, 0.83, 0.57, 0.99], duration: 0.4 }; export const EditStepSidebar = () => { - const { workflowId = '', stepId = '' } = useParams<{ workflowId: string; stepId: string }>(); + const { workflowSlug = '', stepId = '' } = useParams<{ workflowSlug: string; stepId: string }>(); const navigate = useNavigate(); const form = useForm>({ mode: 'onSubmit', resolver: zodResolver(workflowSchema) }); const { reset, setError } = form; const { workflow, error } = useFetchWorkflow({ - workflowSlug: workflowId, + workflowSlug, }); const step = useMemo(() => workflow?.steps.find((el) => el._id === stepId), [stepId, workflow]); diff --git a/apps/dashboard/src/components/workflow-editor/steps/in-app-editor.tsx b/apps/dashboard/src/components/workflow-editor/steps/in-app-editor.tsx index 1fbfd331b53..06475529404 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/in-app-editor.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/in-app-editor.tsx @@ -1,21 +1,22 @@ +import { useState } from 'react'; import { RiEdit2Line, RiInformationFill, RiPencilRuler2Line } from 'react-icons/ri'; import { Cross2Icon } from '@radix-ui/react-icons'; import { useNavigate } from 'react-router-dom'; import { useFormContext } from 'react-hook-form'; import * as z from 'zod'; +import { RedirectTargetEnum } from '@novu/shared'; import { Button } from '@/components/primitives/button'; import { Separator } from '@/components/primitives/separator'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/primitives/tabs'; import { Notification5Fill } from '@/components/icons'; import { AvatarPicker } from '@/components/primitives/form/avatar-picker'; -import { Input, InputField } from '@/components/primitives/input'; -import { Textarea } from '@/components/primitives/textarea'; +import { InputField } from '@/components/primitives/input'; import { workflowSchema } from '../schema'; import { ActionPicker } from '../action-picker'; import { URLInput } from '@/components/primitives/url-input'; import { urlTargetTypes } from '@/utils/url'; -import { RedirectTargetEnum } from '@novu/shared'; +import { Editor } from '@/components/primitives/editor'; const tabsContentClassName = 'h-full w-full px-3 py-3.5'; @@ -23,6 +24,9 @@ export const InAppEditor = () => { const navigate = useNavigate(); const { formState } = useFormContext>(); + const [subject, setSubject] = useState(''); + const [body, setBody] = useState(''); + return (
@@ -63,11 +67,13 @@ export const InAppEditor = () => {
- - + +
-