From 62588c7aa50a0efdc5fd94e08dfc010cfe91b64f Mon Sep 17 00:00:00 2001 From: George Desipris <73396808+desiprisg@users.noreply.github.com> Date: Wed, 6 Nov 2024 11:41:04 +0200 Subject: [PATCH 01/10] feat(dashboard): Click to copy workflowId on workflow list (#6857) --- .../components/{ui => primitives}/command.tsx | 2 +- .../src/components/primitives/dialog.tsx | 4 +- .../components/primitives/hover-to-copy.tsx | 45 +++++++++ .../src/components/primitives/tag-input.tsx | 2 +- .../subscribers-stay-tuned-modal.tsx | 4 +- apps/dashboard/src/components/ui/dialog.tsx | 95 ------------------- .../dashboard/src/components/workflow-row.tsx | 5 +- 7 files changed, 55 insertions(+), 102 deletions(-) rename apps/dashboard/src/components/{ui => primitives}/command.tsx (98%) create mode 100644 apps/dashboard/src/components/primitives/hover-to-copy.tsx delete mode 100644 apps/dashboard/src/components/ui/dialog.tsx diff --git a/apps/dashboard/src/components/ui/command.tsx b/apps/dashboard/src/components/primitives/command.tsx similarity index 98% rename from apps/dashboard/src/components/ui/command.tsx rename to apps/dashboard/src/components/primitives/command.tsx index df15d293e93..4efab01afd7 100644 --- a/apps/dashboard/src/components/ui/command.tsx +++ b/apps/dashboard/src/components/primitives/command.tsx @@ -3,7 +3,7 @@ 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 { Dialog, DialogContent } from '@/components/primitives/dialog'; import { inputVariants } from '@/components/primitives/variants'; import { InputField } from '@/components/primitives/input'; diff --git a/apps/dashboard/src/components/primitives/dialog.tsx b/apps/dashboard/src/components/primitives/dialog.tsx index 686fcd90c6b..1824088c590 100644 --- a/apps/dashboard/src/components/primitives/dialog.tsx +++ b/apps/dashboard/src/components/primitives/dialog.tsx @@ -20,7 +20,7 @@ const DialogOverlay = React.forwardRef< {children} - + Close diff --git a/apps/dashboard/src/components/primitives/hover-to-copy.tsx b/apps/dashboard/src/components/primitives/hover-to-copy.tsx new file mode 100644 index 00000000000..62044e5d33b --- /dev/null +++ b/apps/dashboard/src/components/primitives/hover-to-copy.tsx @@ -0,0 +1,45 @@ +import { cn } from '@/utils/ui'; +import { HTMLAttributes, useRef, useState } from 'react'; +import { RiFileCopyLine } from 'react-icons/ri'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './tooltip'; + +type HoverToCopyProps = HTMLAttributes & { + valueToCopy: string; +}; + +export const HoverToCopy = (props: HoverToCopyProps) => { + const { valueToCopy, className, children, ...rest } = props; + const [isCopied, setIsCopied] = useState(false); + const triggerRef = useRef(null); + + const copyToClipboard = async (e: React.MouseEvent) => { + e.preventDefault(); + try { + await navigator.clipboard.writeText(valueToCopy); + setIsCopied(true); + setTimeout(() => setIsCopied(false), 1500); + } catch (err) { + console.error('Failed to copy text: ', err); + } + }; + + return ( + + +
+ {children} + + + +
+ { + e.preventDefault(); + }} + > +

{isCopied ? 'Copied!' : 'Click to copy'}

+
+
+
+ ); +}; diff --git a/apps/dashboard/src/components/primitives/tag-input.tsx b/apps/dashboard/src/components/primitives/tag-input.tsx index 8a1373b3fbd..9550ab7445e 100644 --- a/apps/dashboard/src/components/primitives/tag-input.tsx +++ b/apps/dashboard/src/components/primitives/tag-input.tsx @@ -3,7 +3,7 @@ import { Badge } from '@/components/primitives/badge'; import { Popover, PopoverAnchor, PopoverContent } from '@/components/primitives/popover'; import { inputVariants } from '@/components/primitives/variants'; -import { CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command'; +import { CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/primitives/command'; import { cn } from '@/utils/ui'; import { Command } from 'cmdk'; import { forwardRef, useEffect, useState } from 'react'; diff --git a/apps/dashboard/src/components/side-navigation/subscribers-stay-tuned-modal.tsx b/apps/dashboard/src/components/side-navigation/subscribers-stay-tuned-modal.tsx index c60234ab67c..58d82d99f90 100644 --- a/apps/dashboard/src/components/side-navigation/subscribers-stay-tuned-modal.tsx +++ b/apps/dashboard/src/components/side-navigation/subscribers-stay-tuned-modal.tsx @@ -1,6 +1,6 @@ +import { Button } from '@/components/primitives/button'; +import { Dialog, DialogClose, DialogContent, DialogFooter, DialogTrigger } from '@/components/primitives/dialog'; import { ReactNode } from 'react'; -import { Dialog, DialogClose, DialogContent, DialogFooter, DialogTrigger } from '../primitives/dialog'; -import { Button } from '../primitives/button'; import { RiBookMarkedLine } from 'react-icons/ri'; export const SubscribersStayTunedModal = ({ children }: { children: ReactNode }) => { diff --git a/apps/dashboard/src/components/ui/dialog.tsx b/apps/dashboard/src/components/ui/dialog.tsx deleted file mode 100644 index 5b2c249f81f..00000000000 --- a/apps/dashboard/src/components/ui/dialog.tsx +++ /dev/null @@ -1,95 +0,0 @@ -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-row.tsx b/apps/dashboard/src/components/workflow-row.tsx index 3507ba4175e..93f3e3ef988 100644 --- a/apps/dashboard/src/components/workflow-row.tsx +++ b/apps/dashboard/src/components/workflow-row.tsx @@ -30,6 +30,7 @@ import { } from '@/components/primitives/tooltip'; import { RiMore2Fill, RiPlayCircleLine } from 'react-icons/ri'; import { useSyncWorkflow } from '@/hooks/use-sync-workflow'; +import { HoverToCopy } from '@/components/primitives/hover-to-copy'; type WorkflowRowProps = { workflow: WorkflowListResponseDto; @@ -68,7 +69,9 @@ export const WorkflowRow = ({ workflow }: WorkflowRowProps) => {
- + + + From 75ea0284db480927628a976ff35f59c388cb8da1 Mon Sep 17 00:00:00 2001 From: Pawan Jain Date: Wed, 6 Nov 2024 15:33:40 +0530 Subject: [PATCH 02/10] feat(api): add plain support service (#6848) Co-authored-by: Dima Grossman --- apps/api/src/.env.development | 1 + apps/api/src/.env.production | 1 + apps/api/src/.env.test | 1 + apps/api/src/.example.env | 1 + apps/api/src/app.module.ts | 3 + .../src/app/support/dto/create-thread.dto.ts | 8 + .../api/src/app/support/support.controller.ts | 148 ++++++++++++++++++ apps/api/src/app/support/support.module.ts | 12 ++ .../support/usecases/create-thread.command.ts | 24 +++ .../support/usecases/create-thread.usecase.ts | 30 ++++ apps/api/src/config/env.validators.ts | 1 + apps/web/src/api/support.ts | 7 + .../layout/components/SupportModal.tsx | 78 +++++++++ .../layout/components/v2/HeaderNav.tsx | 59 +++++-- apps/web/src/config/index.ts | 3 + libs/application-generic/package.json | 1 + libs/application-generic/src/.env.test | 1 + .../src/custom-providers/index.ts | 10 ++ .../application-generic/src/services/index.ts | 1 + .../src/services/support.service.ts | 77 +++++++++ .../src/components/textarea/Textarea.tsx | 2 +- pnpm-lock.yaml | 17 +- 22 files changed, 472 insertions(+), 14 deletions(-) create mode 100644 apps/api/src/app/support/dto/create-thread.dto.ts create mode 100644 apps/api/src/app/support/support.controller.ts create mode 100644 apps/api/src/app/support/support.module.ts create mode 100644 apps/api/src/app/support/usecases/create-thread.command.ts create mode 100644 apps/api/src/app/support/usecases/create-thread.usecase.ts create mode 100644 apps/web/src/api/support.ts create mode 100644 apps/web/src/components/layout/components/SupportModal.tsx create mode 100644 libs/application-generic/src/services/support.service.ts diff --git a/apps/api/src/.env.development b/apps/api/src/.env.development index 771c3ede632..96ed6084d36 100644 --- a/apps/api/src/.env.development +++ b/apps/api/src/.env.development @@ -88,3 +88,4 @@ HUBSPOT_PRIVATE_APP_ACCESS_TOKEN= CLERK_ISSUER_URL= CLERK_WEBHOOK_SECRET= +PLAIN_SUPPORT_KEY='PLAIN_SUPPORT_KEY' diff --git a/apps/api/src/.env.production b/apps/api/src/.env.production index 984679452bc..4b3b7986807 100644 --- a/apps/api/src/.env.production +++ b/apps/api/src/.env.production @@ -75,3 +75,4 @@ HUBSPOT_PRIVATE_APP_ACCESS_TOKEN= CLERK_ISSUER_URL= CLERK_WEBHOOK_SECRET= +PLAIN_SUPPORT_KEY='PLAIN_SUPPORT_KEY' diff --git a/apps/api/src/.env.test b/apps/api/src/.env.test index 6156beb5ce7..677cf42664b 100644 --- a/apps/api/src/.env.test +++ b/apps/api/src/.env.test @@ -115,3 +115,4 @@ CLERK_PEM_PUBLIC_KEY= TUNNEL_BASE_ADDRESS=example.com API_ROOT_URL=http://localhost:1337 +PLAIN_SUPPORT_KEY='PLAIN_SUPPORT_KEY' diff --git a/apps/api/src/.example.env b/apps/api/src/.example.env index 21affbfbe90..680633953b8 100644 --- a/apps/api/src/.example.env +++ b/apps/api/src/.example.env @@ -83,3 +83,4 @@ CLERK_ISSUER_URL= CLERK_LONG_LIVED_TOKEN= TUNNEL_BASE_ADDRESS= +PLAIN_SUPPORT_KEY='PLAIN_SUPPORT_KEY' diff --git a/apps/api/src/app.module.ts b/apps/api/src/app.module.ts index 297ec39b2e3..2dee00d4775 100644 --- a/apps/api/src/app.module.ts +++ b/apps/api/src/app.module.ts @@ -55,6 +55,9 @@ const enterpriseImports = (): Array { diff --git a/apps/web/src/api/support.ts b/apps/web/src/api/support.ts new file mode 100644 index 00000000000..b4f4607fd0e --- /dev/null +++ b/apps/web/src/api/support.ts @@ -0,0 +1,7 @@ +import { api } from './api.client'; + +export async function createThread({ threadText }) { + return await api.post('/v1/support/create-thread', { + text: threadText, + }); +} diff --git a/apps/web/src/components/layout/components/SupportModal.tsx b/apps/web/src/components/layout/components/SupportModal.tsx new file mode 100644 index 00000000000..3270dff1ea3 --- /dev/null +++ b/apps/web/src/components/layout/components/SupportModal.tsx @@ -0,0 +1,78 @@ +import { errorMessage, Modal, successMessage } from '@novu/design-system'; +import { css } from '@novu/novui/css'; +import { Button, Title, Textarea } from '@novu/novui'; +import { HStack, Box } from '@novu/novui/jsx'; +import { FC } from 'react'; +import { useForm, Controller } from 'react-hook-form'; +import { createThread } from '../../../api/support'; + +export type SupportModalProps = { + isOpen: boolean; + toggleOpen: () => void; +}; + +export const SupportModal: FC = ({ isOpen, toggleOpen }) => { + const { + control, + handleSubmit, + formState: { isValid }, + reset, + } = useForm({ + defaultValues: { + threadText: '', + }, + }); + + const onSubmit = async (data) => { + try { + await createThread({ threadText: data.threadText }); + successMessage('Thanks for contacting us! We will get back to you soon.'); + reset(); + toggleOpen(); + } catch (error) { + errorMessage('Something went wrong. Please reach out to us at support@novu.co'); + } + }; + + return ( + <> + Contact Us} onClose={toggleOpen}> + + ( +