diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index 99d665079db..16c52fa790e 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -45,6 +45,7 @@ "@radix-ui/react-switch": "^1.1.1", "@radix-ui/react-tabs": "^1.1.1", "@radix-ui/react-tooltip": "^1.1.3", + "@radix-ui/react-visually-hidden": "^1.1.0", "@rjsf/core": "^5.22.3", "@rjsf/utils": "^5.20.0", "@rjsf/validator-ajv8": "^5.17.1", diff --git a/apps/dashboard/src/components/primitives/sheet.tsx b/apps/dashboard/src/components/primitives/sheet.tsx index 6cd52913961..5c9c83cefc4 100644 --- a/apps/dashboard/src/components/primitives/sheet.tsx +++ b/apps/dashboard/src/components/primitives/sheet.tsx @@ -13,6 +13,8 @@ const SheetClose = SheetPrimitive.Close; const SheetPortal = SheetPrimitive.Portal; +const SheetContentBase = SheetPrimitive.Content; + const SheetOverlay = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef @@ -104,6 +106,7 @@ export { SheetOverlay, SheetTrigger, SheetClose, + SheetContentBase, SheetContent, SheetHeader, SheetFooter, diff --git a/apps/dashboard/src/components/primitives/tabs.tsx b/apps/dashboard/src/components/primitives/tabs.tsx index 78570186c44..a68e15e3d87 100644 --- a/apps/dashboard/src/components/primitives/tabs.tsx +++ b/apps/dashboard/src/components/primitives/tabs.tsx @@ -55,7 +55,7 @@ TabsTrigger.displayName = TabsPrimitive.Trigger.displayName; const tabsContentVariants = cva('focus-visible:outline-none', { variants: { variant: { - default: 'ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2', + default: '', regular: 'mt-2', }, }, diff --git a/apps/dashboard/src/components/primitives/visually-hidden.tsx b/apps/dashboard/src/components/primitives/visually-hidden.tsx new file mode 100644 index 00000000000..1a7a33b6b8b --- /dev/null +++ b/apps/dashboard/src/components/primitives/visually-hidden.tsx @@ -0,0 +1,5 @@ +import * as VisuallyHiddenAll from '@radix-ui/react-visually-hidden'; + +const VisuallyHidden = VisuallyHiddenAll.Root; + +export { VisuallyHidden }; 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 1c7bc926a5d..b42f350b5f8 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 @@ -1,16 +1,25 @@ import { useMemo } from 'react'; import { motion } from 'framer-motion'; -import { useParams } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; -import { Sheet, SheetOverlay, SheetPortal } from '@/components/primitives/sheet'; +import { + Sheet, + SheetContentBase, + SheetDescription, + SheetOverlay, + SheetPortal, + SheetTitle, +} from '@/components/primitives/sheet'; import { useFetchWorkflow } from '@/hooks/use-fetch-workflow'; import { StepEditor } from '@/components/workflow-editor/steps/step-editor'; import { useFetchStep } from '@/hooks/use-fetch-step'; +import { VisuallyHidden } from '@/components/primitives/visually-hidden'; const transitionSetting = { ease: [0.29, 0.83, 0.57, 0.99], duration: 0.4 }; export const EditStepSidebar = () => { const { workflowSlug = '', stepSlug = '' } = useParams<{ workflowSlug: string; stepSlug: string }>(); + const navigate = useNavigate(); const { workflow } = useFetchWorkflow({ workflowSlug, @@ -19,6 +28,10 @@ export const EditStepSidebar = () => { const { step } = useFetchStep({ workflowSlug, stepSlug }); const stepType = useMemo(() => workflow?.steps.find((el) => el.slug === stepSlug)?.type, [stepSlug, workflow]); + const handleCloseSidebar = () => { + navigate('../', { relative: 'path' }); + }; + return ( @@ -36,24 +49,30 @@ export const EditStepSidebar = () => { transition={transitionSetting} /> - - {/* TODO: show loading indicator */} - {workflow && step && stepType && } - + + + + + + + {/* TODO: show loading indicator */} + {workflow && step && stepType && } + + ); 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 9e9ef58299c..998b0dca961 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,14 +1,14 @@ import { useState, useEffect, useMemo } from 'react'; -import { RiEdit2Line, RiPencilRuler2Line } from 'react-icons/ri'; +import { RiAlertFill, RiEdit2Line, RiPencilRuler2Line } from 'react-icons/ri'; import { Cross2Icon } from '@radix-ui/react-icons'; -import { useNavigate, useParams } from 'react-router-dom'; +import { useBlocker, useNavigate, useParams } from 'react-router-dom'; import { FieldValues, useForm, useWatch } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { type WorkflowResponseDto, type StepDataDto, type StepUpdateDto } from '@novu/shared'; import { Form } from '@/components/primitives/form/form'; import { Notification5Fill } from '@/components/icons'; -import { Button } from '@/components/primitives/button'; +import { Button, buttonVariants } from '@/components/primitives/button'; import { Separator } from '@/components/primitives/separator'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/primitives/tabs'; import { InAppEditorPreview } from '@/components/workflow-editor/steps/in-app/in-app-editor-preview'; @@ -23,6 +23,16 @@ import { CustomStepControls } from '../controls/custom-step-controls'; import { useStep } from '../use-step'; import { flattenIssues } from '../../step-utils'; import { useWorkflowEditorContext } from '../../hooks'; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from '@/components/primitives/alert-dialog'; const tabsContentClassName = 'h-full w-full px-3 py-3.5'; @@ -55,7 +65,7 @@ export const InAppTabs = ({ workflow, step }: { workflow: WorkflowResponseDto; s }, [controlErrors, setError]); const { previewStep, data: previewData } = usePreviewStep(); - const { updateWorkflow } = useUpdateWorkflow({ + const { isPending, updateWorkflow } = useUpdateWorkflow({ onSuccess: (data) => { resetWorkflowForm(data); showToast({ @@ -128,75 +138,112 @@ export const InAppTabs = ({ workflow, step }: { workflow: WorkflowResponseDto; s [formValues] ); + const blocker = useBlocker(formState.isDirty || isPending); + return ( -
- { - event.preventDefault(); - event.stopPropagation(); - form.handleSubmit(onSubmit)(event); - }} - > - -
-
- - Configure Template + <> + + { + event.preventDefault(); + event.stopPropagation(); + form.handleSubmit(onSubmit)(event); + }} + > + +
+
+ + Configure Template +
+ + + + Editor + + + + Preview + + + + +
+ + + + + + + { + previewStep({ + stepSlug, + workflowSlug, + data: { controlValues: form.getValues() as FieldValues, previewPayload: JSON.parse(editorValue) }, + }); + }} + /> + + +
+ +
+
+ + + + + +
+
- - - - Editor - - - - Preview - - +
+ You might lose your progress + + This editor form has some unsaved changes. Save progress before you leave. + +
+
- -
- - - - - - { - previewStep({ - stepSlug, - workflowSlug, - data: { controlValues: form.getValues() as FieldValues, previewPayload: JSON.parse(editorValue) }, - }); - }} - /> - - -
- -
-
- - + + + blocker.reset?.()}>Cancel + blocker.proceed?.()} + className={buttonVariants({ variant: 'destructive' })} + > + Proceed anyway + + + + + ); }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 700db0aff16..3695a2ef4a4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -726,6 +726,9 @@ importers: '@radix-ui/react-tooltip': specifier: ^1.1.3 version: 1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-visually-hidden': + specifier: ^1.1.0 + version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@rjsf/core': specifier: ^5.22.3 version: 5.22.3(@rjsf/utils@5.20.1(react@18.3.1))(react@18.3.1)