From e8fd24ea793bf3627b0e65bdd666169c616b9ab8 Mon Sep 17 00:00:00 2001 From: George Desipris <73396808+desiprisg@users.noreply.github.com> Date: Mon, 21 Oct 2024 16:56:32 +0300 Subject: [PATCH] feat(dashboard): Tag input suggestions (#6728) --- .cspell.json | 3 +- apps/dashboard/package.json | 1 + .../src/components/create-workflow-button.tsx | 7 +- .../src/components/dashboard-layout.tsx | 4 +- .../src/components/edit-workflow-layout.tsx | 2 +- .../customer-support-button.tsx | 2 +- .../edit-bridge-url-button.tsx | 6 +- .../src/components/header-navigation/index.ts | 1 - .../src/components/primitives/form/index.ts | 1 - .../src/components/primitives/input.tsx | 23 ++-- .../src/components/primitives/popover.tsx | 12 +- .../src/components/primitives/tag-input.tsx | 119 +++++++++++------ .../src/components/primitives/variants.ts | 4 + .../side-navigation/free-trial-card.tsx | 2 +- .../src/components/side-navigation/index.ts | 1 - .../side-navigation/side-navigation.tsx | 2 +- apps/dashboard/src/components/ui/command.tsx | 125 ++++++++++++++++++ apps/dashboard/src/components/ui/dialog.tsx | 95 +++++++++++++ .../src/components/workflow-editor/index.ts | 2 - apps/dashboard/src/context/auth/index.ts | 3 - .../environment/environment-provider.tsx | 6 +- .../src/context/environment/index.ts | 2 - apps/dashboard/src/context/index.ts | 4 - .../src/hooks/use-billing-subscription.ts | 2 +- apps/dashboard/src/hooks/use-boot-intercom.ts | 2 +- apps/dashboard/src/hooks/use-tags-query.ts | 15 +++ apps/dashboard/src/pages/edit-workflow.tsx | 3 +- apps/dashboard/src/routes/protected-route.tsx | 2 +- apps/dashboard/src/routes/root.tsx | 4 +- apps/dashboard/src/utils/query-keys.ts | 1 + pnpm-lock.yaml | 47 +++++-- 31 files changed, 394 insertions(+), 109 deletions(-) delete mode 100644 apps/dashboard/src/components/header-navigation/index.ts delete mode 100644 apps/dashboard/src/components/primitives/form/index.ts delete mode 100644 apps/dashboard/src/components/side-navigation/index.ts create mode 100644 apps/dashboard/src/components/ui/command.tsx create mode 100644 apps/dashboard/src/components/ui/dialog.tsx delete mode 100644 apps/dashboard/src/components/workflow-editor/index.ts delete mode 100644 apps/dashboard/src/context/auth/index.ts delete mode 100644 apps/dashboard/src/context/environment/index.ts delete mode 100644 apps/dashboard/src/context/index.ts create mode 100644 apps/dashboard/src/hooks/use-tags-query.ts diff --git a/.cspell.json b/.cspell.json index dff7920e41e..0af9e2f3ec3 100644 --- a/.cspell.json +++ b/.cspell.json @@ -690,6 +690,7 @@ "xyflow", "Sonner", "sonner", + "cmdk" ], "flagWords": [], "patterns": [ @@ -787,6 +788,6 @@ "apps/web/src/studio/components/workflows/step-editor/editor/files.ts", "apps/web/src/pages/playground/web-container-configuration/sandbox-vite/*.ts", "apps/api/src/app/analytics/usecases/hubspot-identify-form/hubspot-identify-form.usecase.ts", - "apps/dashboard/eslint.config.js", + "apps/dashboard/eslint.config.js" ] } diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index 11a44a02c38..c761efa63dd 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -41,6 +41,7 @@ "@xyflow/react": "^12.3.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "cmdk": "1.0.0", "date-fns": "^4.1.0", "lodash.debounce": "^4.0.8", "lucide-react": "^0.439.0", diff --git a/apps/dashboard/src/components/create-workflow-button.tsx b/apps/dashboard/src/components/create-workflow-button.tsx index 3e8e2c361df..aff422372e8 100644 --- a/apps/dashboard/src/components/create-workflow-button.tsx +++ b/apps/dashboard/src/components/create-workflow-button.tsx @@ -1,6 +1,6 @@ import { createWorkflow } from '@/api/workflows'; import { Button } from '@/components/primitives/button'; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/primitives/form'; +import { FormField, FormItem, FormLabel, FormControl, FormMessage, Form } from '@/components/primitives/form/form'; import { Input, InputField } from '@/components/primitives/input'; import { Separator } from '@/components/primitives/separator'; import { @@ -16,6 +16,7 @@ import { import { TagInput } from '@/components/primitives/tag-input'; import { Textarea } from '@/components/primitives/textarea'; import { useEnvironment } from '@/context/environment/hooks'; +import { useTagsQuery } from '@/hooks/use-tags-query'; import { QueryKeys } from '@/utils/query-keys'; import { zodResolver } from '@hookform/resolvers/zod'; import { type CreateWorkflowDto, WorkflowCreationSourceEnum } from '@novu/shared'; @@ -54,6 +55,8 @@ export const CreateWorkflowButton = (props: CreateWorkflowButtonProps) => { setIsOpen(false); }, }); + const tagsQuery = useTagsQuery(); + const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { description: '', identifier: '', name: '', tags: [] }, @@ -147,7 +150,7 @@ export const CreateWorkflowButton = (props: CreateWorkflowButtonProps) => { Add tags - + tag.name) || []} {...field} /> diff --git a/apps/dashboard/src/components/dashboard-layout.tsx b/apps/dashboard/src/components/dashboard-layout.tsx index 1a2dafbe2f6..8977c31bca5 100644 --- a/apps/dashboard/src/components/dashboard-layout.tsx +++ b/apps/dashboard/src/components/dashboard-layout.tsx @@ -2,9 +2,9 @@ import { ReactNode } from 'react'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import { IntercomProvider } from 'react-use-intercom'; -import { SideNavigation } from './side-navigation'; -import { HeaderNavigation } from './header-navigation'; import { INTERCOM_APP_ID } from '@/config'; +import { SideNavigation } from '@/components/side-navigation/side-navigation'; +import { HeaderNavigation } from '@/components/header-navigation/header-navigation'; export const DashboardLayout = ({ children, diff --git a/apps/dashboard/src/components/edit-workflow-layout.tsx b/apps/dashboard/src/components/edit-workflow-layout.tsx index acebb9226a9..913db59aae0 100644 --- a/apps/dashboard/src/components/edit-workflow-layout.tsx +++ b/apps/dashboard/src/components/edit-workflow-layout.tsx @@ -2,8 +2,8 @@ import { ReactNode } from 'react'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import { IntercomProvider } from 'react-use-intercom'; -import { HeaderNavigation } from './header-navigation'; import { INTERCOM_APP_ID } from '@/config'; +import { HeaderNavigation } from '@/components/header-navigation/header-navigation'; export const EditWorkflowLayout = ({ children, diff --git a/apps/dashboard/src/components/header-navigation/customer-support-button.tsx b/apps/dashboard/src/components/header-navigation/customer-support-button.tsx index 3dedbf34e03..5d5658fb351 100644 --- a/apps/dashboard/src/components/header-navigation/customer-support-button.tsx +++ b/apps/dashboard/src/components/header-navigation/customer-support-button.tsx @@ -1,5 +1,5 @@ +import { useBootIntercom } from '@/hooks/use-boot-intercom'; import { RiCustomerService2Line } from 'react-icons/ri'; -import { useBootIntercom } from '@/hooks'; export const CustomerSupportButton = () => { useBootIntercom(); diff --git a/apps/dashboard/src/components/header-navigation/edit-bridge-url-button.tsx b/apps/dashboard/src/components/header-navigation/edit-bridge-url-button.tsx index dfc49270b96..82d70c598d3 100644 --- a/apps/dashboard/src/components/header-navigation/edit-bridge-url-button.tsx +++ b/apps/dashboard/src/components/header-navigation/edit-bridge-url-button.tsx @@ -8,10 +8,12 @@ import { cn } from '@/utils/ui'; import { Popover, PopoverContent, PopoverTrigger, PopoverPortal } from '../primitives/popover'; import { Button } from '../primitives/button'; import { Input, InputField } from '../primitives/input'; -import { useBridgeHealthCheck, useUpdateBridgeUrl, useValidateBridgeUrl } from '@/hooks'; import { ConnectionStatus } from '@/utils/types'; import { useEnvironment } from '@/context/environment/hooks'; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '../primitives/form'; +import { useBridgeHealthCheck } from '@/hooks/use-bridge-health-check'; +import { useValidateBridgeUrl } from '@/hooks/use-validate-bridge-url'; +import { useUpdateBridgeUrl } from '@/hooks/use-update-bridge-url'; +import { FormField, FormItem, FormLabel, FormControl, FormMessage, Form } from '@/components/primitives/form/form'; const formSchema = z.object({ bridgeUrl: z.string().url() }); diff --git a/apps/dashboard/src/components/header-navigation/index.ts b/apps/dashboard/src/components/header-navigation/index.ts deleted file mode 100644 index 4dade914253..00000000000 --- a/apps/dashboard/src/components/header-navigation/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './header-navigation'; diff --git a/apps/dashboard/src/components/primitives/form/index.ts b/apps/dashboard/src/components/primitives/form/index.ts deleted file mode 100644 index 698d687b924..00000000000 --- a/apps/dashboard/src/components/primitives/form/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './form'; diff --git a/apps/dashboard/src/components/primitives/input.tsx b/apps/dashboard/src/components/primitives/input.tsx index c96194d3f5e..ec02bddc581 100644 --- a/apps/dashboard/src/components/primitives/input.tsx +++ b/apps/dashboard/src/components/primitives/input.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { cn } from '@/utils/ui'; import { cva, VariantProps } from 'class-variance-authority'; +import { inputVariants } from '@/components/primitives/variants'; const inputFieldVariants = cva( 'text-foreground-950 flex w-full flex-nowrap items-center gap-1.5 rounded-md border bg-transparent shadow-sm transition-colors focus-within:outline-none focus-visible:outline-none hover:bg-neutral-50 has-[input:disabled]:cursor-not-allowed has-[input:disabled]:opacity-50 has-[input[value=""]]:text-foreground-400 has-[input:disabled]:bg-neutral-alpha-100 has-[input:disabled]:text-foreground-300', @@ -27,26 +28,20 @@ export type InputFieldProps = { children: React.ReactNode; className?: string } typeof inputFieldVariants >; -const InputField = ({ children, className, size, state }: InputFieldProps) => { - return
{children}
; -}; +const InputField = React.forwardRef(({ children, className, size, state }, ref) => { + return ( +
+ {children} +
+ ); +}); InputField.displayName = 'InputField'; export type InputProps = React.InputHTMLAttributes; const Input = React.forwardRef(({ className, type, ...props }, ref) => { - return ( - - ); + return ; }); Input.displayName = 'Input'; diff --git a/apps/dashboard/src/components/primitives/popover.tsx b/apps/dashboard/src/components/primitives/popover.tsx index cfbe4d24a05..a616a74c249 100644 --- a/apps/dashboard/src/components/primitives/popover.tsx +++ b/apps/dashboard/src/components/primitives/popover.tsx @@ -13,9 +13,9 @@ const PopoverPortal = PopoverPrimitive.Portal; const PopoverContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, align = 'center', sideOffset = 4, ...props }, ref) => ( - + React.ComponentPropsWithoutRef & { portal?: boolean } +>(({ className, align = 'center', portal = true, sideOffset = 4, ...props }, ref) => { + const body = ( - -)); + ); + + return portal ? {body} : body; +}); PopoverContent.displayName = PopoverPrimitive.Content.displayName; export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor, PopoverPortal }; diff --git a/apps/dashboard/src/components/primitives/tag-input.tsx b/apps/dashboard/src/components/primitives/tag-input.tsx index 065920f8a0a..e4a784d1ddc 100644 --- a/apps/dashboard/src/components/primitives/tag-input.tsx +++ b/apps/dashboard/src/components/primitives/tag-input.tsx @@ -1,20 +1,25 @@ 'use client'; import { Badge } from '@/components/primitives/badge'; -import { Input, InputField } from '@/components/primitives/input'; +import { Popover, PopoverAnchor, PopoverContent } from '@/components/primitives/popover'; +import { inputVariants } from '@/components/primitives/variants'; +import { CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command'; import { cn } from '@/utils/ui'; +import { Command } from 'cmdk'; import { forwardRef, useEffect, useState } from 'react'; import { RiCloseFill } from 'react-icons/ri'; type TagInputProps = React.InputHTMLAttributes & { value: string[]; + suggestions: string[]; onChange: (tags: string[]) => void; }; const TagInput = forwardRef((props, ref) => { - const { className, value, onChange, ...rest } = props; + const { className, suggestions, value, onChange, ...rest } = props; const [tags, setTags] = useState(value); const [inputValue, setInputValue] = useState(''); + const [isOpen, setIsOpen] = useState(false); useEffect(() => { setTags(value); @@ -27,6 +32,7 @@ const TagInput = forwardRef((props, ref) => { } onChange(newTags); setInputValue(''); + setIsOpen(false); }; const removeTag = (tag: string) => { @@ -39,48 +45,75 @@ const TagInput = forwardRef((props, ref) => { setInputValue(''); }; - const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.key === 'Enter') { - e.preventDefault(); - - if (inputValue === '') { - return; - } - - addTag(inputValue); - } - }; - return ( -
- - setInputValue(e.target.value)} - {...rest} - /> - -
- {tags.map((tag, index) => ( - - {tag} - - - ))} -
-
+ + +
+ + { + setInputValue(value); + setIsOpen(true); + }} + onFocusCapture={() => setIsOpen(true)} + onBlurCapture={() => setIsOpen(false)} + {...rest} + /> + +
+ {tags.map((tag, index) => ( + + {tag} + + + ))} +
+
+ { + e.preventDefault(); + }} + > + + + {inputValue !== '' && ( + { + addTag(inputValue); + }} + > + {inputValue} + + )} + {suggestions.map((tag) => ( + { + addTag(tag); + }} + > + {tag} + + ))} + + + +
+
); }); TagInput.displayName = 'TagInput'; diff --git a/apps/dashboard/src/components/primitives/variants.ts b/apps/dashboard/src/components/primitives/variants.ts index 04c3a273955..09a4ed1b423 100644 --- a/apps/dashboard/src/components/primitives/variants.ts +++ b/apps/dashboard/src/components/primitives/variants.ts @@ -115,3 +115,7 @@ export const stepVariants = cva( }, } ); + +export const inputVariants = cva( + 'file:text-foreground placeholder:text-foreground-400 flex h-full w-full bg-transparent text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none disabled:cursor-not-allowed' +); diff --git a/apps/dashboard/src/components/side-navigation/free-trial-card.tsx b/apps/dashboard/src/components/side-navigation/free-trial-card.tsx index 13828e0a664..10c55d27cc1 100644 --- a/apps/dashboard/src/components/side-navigation/free-trial-card.tsx +++ b/apps/dashboard/src/components/side-navigation/free-trial-card.tsx @@ -1,10 +1,10 @@ -import { useBillingSubscription } from '@/hooks'; import { LogoCircle } from '../icons'; import { RiArrowRightDoubleLine, RiInformationFill } from 'react-icons/ri'; import { Progress } from '../primitives/progress'; import { Button } from '../primitives/button'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, TooltipArrow } from '../primitives/tooltip'; import { LEGACY_ROUTES } from '@/utils/routes'; +import { useBillingSubscription } from '@/hooks/use-billing-subscription'; const transition = 'transition-all duration-300 ease-out'; diff --git a/apps/dashboard/src/components/side-navigation/index.ts b/apps/dashboard/src/components/side-navigation/index.ts deleted file mode 100644 index 4a61e7d72fa..00000000000 --- a/apps/dashboard/src/components/side-navigation/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './side-navigation'; diff --git a/apps/dashboard/src/components/side-navigation/side-navigation.tsx b/apps/dashboard/src/components/side-navigation/side-navigation.tsx index ad0b3643610..ee99dea1d49 100644 --- a/apps/dashboard/src/components/side-navigation/side-navigation.tsx +++ b/apps/dashboard/src/components/side-navigation/side-navigation.tsx @@ -18,8 +18,8 @@ import { OrganizationDropdown } from './organization-dropdown'; import { FreeTrialCard } from './free-trial-card'; import { buildRoute, LEGACY_ROUTES, ROUTES } from '@/utils/routes'; import { SubscribersStayTunedModal } from './subscribers-stay-tuned-modal'; -import { useTelemetry } from '@/hooks'; import { TelemetryEvent } from '@/utils/telemetry'; +import { useTelemetry } from '@/hooks/use-telemetry'; const linkVariants = cva( `flex items-center gap-2 text-sm py-1.5 px-2 rounded-lg focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring cursor-pointer`, diff --git a/apps/dashboard/src/components/ui/command.tsx b/apps/dashboard/src/components/ui/command.tsx new file mode 100644 index 00000000000..df15d293e93 --- /dev/null +++ b/apps/dashboard/src/components/ui/command.tsx @@ -0,0 +1,125 @@ +import * as React from 'react'; +import { type DialogProps } from '@radix-ui/react-dialog'; +import { Command as CommandPrimitive } from 'cmdk'; + +import { cn } from '@/utils/ui'; +import { Dialog, DialogContent } from '@/components/ui/dialog'; +import { inputVariants } from '@/components/primitives/variants'; +import { InputField } from '@/components/primitives/input'; + +const Command = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Command.displayName = CommandPrimitive.displayName; + +type CommandDialogProps = DialogProps; + +const CommandDialog = ({ children, ...props }: CommandDialogProps) => { + return ( + + + + {children} + + + + ); +}; + +const CommandInput = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); + +CommandInput.displayName = CommandPrimitive.Input.displayName; + +const CommandList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandList.displayName = CommandPrimitive.List.displayName; + +const CommandEmpty = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>((props, ref) => ); + +CommandEmpty.displayName = CommandPrimitive.Empty.displayName; + +const CommandGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandGroup.displayName = CommandPrimitive.Group.displayName; + +const CommandSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +CommandSeparator.displayName = CommandPrimitive.Separator.displayName; + +const CommandItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandItem.displayName = CommandPrimitive.Item.displayName; + +const CommandShortcut = ({ className, ...props }: React.HTMLAttributes) => { + return ; +}; +CommandShortcut.displayName = 'CommandShortcut'; + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +}; diff --git a/apps/dashboard/src/components/ui/dialog.tsx b/apps/dashboard/src/components/ui/dialog.tsx new file mode 100644 index 00000000000..5b2c249f81f --- /dev/null +++ b/apps/dashboard/src/components/ui/dialog.tsx @@ -0,0 +1,95 @@ +import * as React from 'react'; +import * as DialogPrimitive from '@radix-ui/react-dialog'; +import { Cross2Icon } from '@radix-ui/react-icons'; + +import { cn } from '@/utils/ui'; + +const Dialog = DialogPrimitive.Root; + +const DialogTrigger = DialogPrimitive.Trigger; + +const DialogPortal = DialogPrimitive.Portal; + +const DialogClose = DialogPrimitive.Close; + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)); +DialogContent.displayName = DialogPrimitive.Content.displayName; + +const DialogHeader = ({ className, ...props }: React.HTMLAttributes) => ( +
+); +DialogHeader.displayName = 'DialogHeader'; + +const DialogFooter = ({ className, ...props }: React.HTMLAttributes) => ( +
+); +DialogFooter.displayName = 'DialogFooter'; + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogTitle.displayName = DialogPrimitive.Title.displayName; + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogTrigger, + DialogClose, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +}; diff --git a/apps/dashboard/src/components/workflow-editor/index.ts b/apps/dashboard/src/components/workflow-editor/index.ts deleted file mode 100644 index 38c673f8e48..00000000000 --- a/apps/dashboard/src/components/workflow-editor/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './workflow-editor'; -export * from './workflow-editor-provider'; diff --git a/apps/dashboard/src/context/auth/index.ts b/apps/dashboard/src/context/auth/index.ts deleted file mode 100644 index 3137341d714..00000000000 --- a/apps/dashboard/src/context/auth/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './auth-provider'; -export * from './auth-context'; -export * from './hooks'; diff --git a/apps/dashboard/src/context/environment/environment-provider.tsx b/apps/dashboard/src/context/environment/environment-provider.tsx index 05ebd7dc97e..f2a8ebb299f 100644 --- a/apps/dashboard/src/context/environment/environment-provider.tsx +++ b/apps/dashboard/src/context/environment/environment-provider.tsx @@ -1,12 +1,12 @@ import { useCallback, useLayoutEffect, useMemo, useState } from 'react'; import { useLocation, useNavigate, useParams } from 'react-router-dom'; import type { IEnvironment } from '@novu/shared'; -import { useFetchEnvironments } from './hooks'; -import { useAuth } from '../auth'; -import { EnvironmentContext } from './environment-context'; import { getEnvironmentId, saveEnvironmentId } from '@/utils/environment'; import { BaseEnvironmentEnum } from '@/utils/types'; import { buildRoute, ROUTES } from '@/utils/routes'; +import { useAuth } from '@/context/auth/hooks'; +import { useFetchEnvironments } from '@/context/environment/hooks'; +import { EnvironmentContext } from '@/context/environment/environment-context'; function selectEnvironment(environments: IEnvironment[], selectedEnvironmentId?: string | null) { let environment: IEnvironment | undefined; diff --git a/apps/dashboard/src/context/environment/index.ts b/apps/dashboard/src/context/environment/index.ts deleted file mode 100644 index abdcb82a753..00000000000 --- a/apps/dashboard/src/context/environment/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './environment-provider'; -export * from './hooks'; diff --git a/apps/dashboard/src/context/index.ts b/apps/dashboard/src/context/index.ts deleted file mode 100644 index af426cefddc..00000000000 --- a/apps/dashboard/src/context/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './clerk-provider'; -export * from './segment'; -export * from './auth'; -export * from './environment'; diff --git a/apps/dashboard/src/hooks/use-billing-subscription.ts b/apps/dashboard/src/hooks/use-billing-subscription.ts index de9655b1dc6..0b623838ce8 100644 --- a/apps/dashboard/src/hooks/use-billing-subscription.ts +++ b/apps/dashboard/src/hooks/use-billing-subscription.ts @@ -2,9 +2,9 @@ import { useQuery } from '@tanstack/react-query'; import { useMemo } from 'react'; import { differenceInDays, isSameDay } from 'date-fns'; import type { GetSubscriptionDto } from '@novu/shared'; -import { useAuth } from '@/context'; import { getBillingSubscription } from '@/api/billing'; import { QueryKeys } from '@/utils/query-keys'; +import { useAuth } from '@/context/auth/hooks'; const today = new Date(); diff --git a/apps/dashboard/src/hooks/use-boot-intercom.ts b/apps/dashboard/src/hooks/use-boot-intercom.ts index d8428ddd061..966cd92ee75 100644 --- a/apps/dashboard/src/hooks/use-boot-intercom.ts +++ b/apps/dashboard/src/hooks/use-boot-intercom.ts @@ -3,7 +3,7 @@ import { useEffect } from 'react'; // @ts-ignore import { useIntercom } from 'react-use-intercom'; import { INTERCOM_APP_ID } from '../config'; -import { useAuth } from '@/context'; +import { useAuth } from '@/context/auth/hooks'; export function useBootIntercom() { const { currentOrganization, currentUser } = useAuth(); diff --git a/apps/dashboard/src/hooks/use-tags-query.ts b/apps/dashboard/src/hooks/use-tags-query.ts new file mode 100644 index 00000000000..c837464a05f --- /dev/null +++ b/apps/dashboard/src/hooks/use-tags-query.ts @@ -0,0 +1,15 @@ +import { useQuery } from '@tanstack/react-query'; +import { QueryKeys } from '@/utils/query-keys'; +import { useEnvironment } from '@/context/environment/hooks'; +import { getV2 } from '@/api/api.client'; + +export const useTagsQuery = () => { + const { currentEnvironment } = useEnvironment(); + const query = useQuery<{ data: { name: string }[] }>({ + queryKey: [QueryKeys.fetchWorkflow, currentEnvironment?._id], + queryFn: async () => await getV2(`/environments/${currentEnvironment!._id}/tags`), + enabled: !!currentEnvironment?._id, + }); + + return query; +}; diff --git a/apps/dashboard/src/pages/edit-workflow.tsx b/apps/dashboard/src/pages/edit-workflow.tsx index bd2938253cf..d8ed1c6f6e0 100644 --- a/apps/dashboard/src/pages/edit-workflow.tsx +++ b/apps/dashboard/src/pages/edit-workflow.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { useNavigate, useParams } from 'react-router-dom'; -import { WorkflowEditor, WorkflowEditorProvider } from '@/components/workflow-editor'; import { EditWorkflowLayout } from '@/components/edit-workflow-layout'; import { ArrowRight, RouteFill } from '@/components/icons'; import { @@ -12,6 +11,8 @@ import { BreadcrumbSeparator, } from '@/components/primitives/breadcrumb'; import { Button } from '@/components/primitives/button'; +import { WorkflowEditor } from '@/components/workflow-editor/workflow-editor'; +import { WorkflowEditorProvider } from '@/components/workflow-editor/workflow-editor-provider'; import { useEnvironment } from '@/context/environment/hooks'; import { useFetchWorkflow } from '@/hooks/use-fetch-workflow'; import { buildRoute, ROUTES } from '@/utils/routes'; diff --git a/apps/dashboard/src/routes/protected-route.tsx b/apps/dashboard/src/routes/protected-route.tsx index 8f725d53b2d..e536647d462 100644 --- a/apps/dashboard/src/routes/protected-route.tsx +++ b/apps/dashboard/src/routes/protected-route.tsx @@ -1,7 +1,7 @@ import { Navigate } from 'react-router-dom'; import { SignedIn, SignedOut } from '@clerk/clerk-react'; -import { EnvironmentProvider } from '@/context'; import { ROUTES } from '@/utils/routes'; +import { EnvironmentProvider } from '@/context/environment/environment-provider'; export const ProtectedRoute = ({ children }: { children: React.ReactNode }) => { return ( diff --git a/apps/dashboard/src/routes/root.tsx b/apps/dashboard/src/routes/root.tsx index 80e0a5f99d4..f0121ecee3a 100644 --- a/apps/dashboard/src/routes/root.tsx +++ b/apps/dashboard/src/routes/root.tsx @@ -1,7 +1,9 @@ import { Outlet } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { AuthProvider, ClerkProvider, SegmentProvider } from '@/context'; import { HelmetProvider } from 'react-helmet-async'; +import { SegmentProvider } from '@/context/segment'; +import { AuthProvider } from '@/context/auth/auth-provider'; +import { ClerkProvider } from '@/context/clerk-provider'; const queryClient = new QueryClient(); diff --git a/apps/dashboard/src/utils/query-keys.ts b/apps/dashboard/src/utils/query-keys.ts index c1cca6d9bb5..bdf14e2bbc7 100644 --- a/apps/dashboard/src/utils/query-keys.ts +++ b/apps/dashboard/src/utils/query-keys.ts @@ -4,4 +4,5 @@ export const QueryKeys = Object.freeze({ bridgeHealthCheck: 'bridgeHealthCheck', fetchWorkflow: 'fetchWorkflow', fetchWorkflows: 'fetchWorkflows', + fetchTags: 'fetchTags', }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3c3890bd6d2..ac934e28bd0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -700,6 +700,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + cmdk: + specifier: 1.0.0 + version: 1.0.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) date-fns: specifier: ^4.1.0 version: 4.1.0 @@ -19297,6 +19300,12 @@ packages: resolution: {integrity: sha512-qkCtZ59BidfEwHltnJwkyVZn+XQojdAySM1D1gSeh11Z4pW1Kpolkyo53L5noc0nrxmIvyFwTmJRo4xs7FFLPw==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + cmdk@1.0.0: + resolution: {integrity: sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q==} + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + co@4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} @@ -34627,8 +34636,8 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.575.0(@aws-sdk/client-sts@3.575.0) - '@aws-sdk/client-sts': 3.575.0 + '@aws-sdk/client-sso-oidc': 3.575.0 + '@aws-sdk/client-sts': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) '@aws-sdk/core': 3.575.0 '@aws-sdk/credential-provider-node': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0) '@aws-sdk/middleware-host-header': 3.575.0 @@ -34829,8 +34838,8 @@ snapshots: '@aws-crypto/sha1-browser': 3.0.0 '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.575.0(@aws-sdk/client-sts@3.575.0) - '@aws-sdk/client-sts': 3.575.0 + '@aws-sdk/client-sso-oidc': 3.575.0 + '@aws-sdk/client-sts': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) '@aws-sdk/core': 3.575.0 '@aws-sdk/credential-provider-node': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0) '@aws-sdk/middleware-bucket-endpoint': 3.575.0 @@ -35056,11 +35065,11 @@ snapshots: - aws-crt optional: true - '@aws-sdk/client-sso-oidc@3.575.0(@aws-sdk/client-sts@3.575.0)': + '@aws-sdk/client-sso-oidc@3.575.0': dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sts': 3.575.0 + '@aws-sdk/client-sts': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) '@aws-sdk/core': 3.575.0 '@aws-sdk/credential-provider-node': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0) '@aws-sdk/middleware-host-header': 3.575.0 @@ -35099,7 +35108,6 @@ snapshots: '@smithy/util-utf8': 3.0.0 tslib: 2.7.0 transitivePeerDependencies: - - '@aws-sdk/client-sts' - aws-crt '@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0)': @@ -35484,11 +35492,11 @@ snapshots: - aws-crt optional: true - '@aws-sdk/client-sts@3.575.0': + '@aws-sdk/client-sts@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)': dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.575.0(@aws-sdk/client-sts@3.575.0) + '@aws-sdk/client-sso-oidc': 3.575.0 '@aws-sdk/core': 3.575.0 '@aws-sdk/credential-provider-node': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0) '@aws-sdk/middleware-host-header': 3.575.0 @@ -35527,6 +35535,7 @@ snapshots: '@smithy/util-utf8': 3.0.0 tslib: 2.7.0 transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' - aws-crt '@aws-sdk/client-sts@3.637.0': @@ -35756,7 +35765,7 @@ snapshots: '@aws-sdk/credential-provider-ini@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0)': dependencies: - '@aws-sdk/client-sts': 3.575.0 + '@aws-sdk/client-sts': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) '@aws-sdk/credential-provider-env': 3.575.0 '@aws-sdk/credential-provider-process': 3.575.0 '@aws-sdk/credential-provider-sso': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) @@ -36067,7 +36076,7 @@ snapshots: '@aws-sdk/credential-provider-web-identity@3.575.0(@aws-sdk/client-sts@3.575.0)': dependencies: - '@aws-sdk/client-sts': 3.575.0 + '@aws-sdk/client-sts': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) '@aws-sdk/types': 3.575.0 '@smithy/property-provider': 3.1.3 '@smithy/types': 3.3.0 @@ -36588,7 +36597,7 @@ snapshots: '@aws-sdk/token-providers@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)': dependencies: - '@aws-sdk/client-sso-oidc': 3.575.0(@aws-sdk/client-sts@3.575.0) + '@aws-sdk/client-sso-oidc': 3.575.0 '@aws-sdk/types': 3.575.0 '@smithy/property-provider': 3.1.3 '@smithy/shared-ini-file-loader': 3.1.4 @@ -36597,7 +36606,7 @@ snapshots: '@aws-sdk/token-providers@3.614.0(@aws-sdk/client-sso-oidc@3.575.0)': dependencies: - '@aws-sdk/client-sso-oidc': 3.575.0(@aws-sdk/client-sts@3.575.0) + '@aws-sdk/client-sso-oidc': 3.575.0 '@aws-sdk/types': 3.609.0 '@smithy/property-provider': 3.1.3 '@smithy/shared-ini-file-loader': 3.1.4 @@ -53540,7 +53549,7 @@ snapshots: '@storybook/components@8.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@radix-ui/react-dialog': 1.0.5(@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-dialog': 1.1.2(@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-slot': 1.1.0(@types/react@18.3.3)(react@18.3.1) '@storybook/client-logger': 8.1.1 '@storybook/csf': 0.1.7 @@ -60271,6 +60280,16 @@ snapshots: dependencies: mkdirp-infer-owner: 2.0.0 + cmdk@1.0.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): + dependencies: + '@radix-ui/react-dialog': 1.0.5(@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-primitive': 1.0.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) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + co@4.6.0: {} coa@2.0.2: