diff --git a/apps/web/src/api/content-templates.ts b/apps/web/src/api/content-templates.ts index 59d3760ca95..4db155314a3 100644 --- a/apps/web/src/api/content-templates.ts +++ b/apps/web/src/api/content-templates.ts @@ -44,3 +44,15 @@ export async function previewChat({ }) { return api.post('/v1/content-templates/preview/chat', { content, payload, locale }); } + +export async function previewSms({ + content, + payload, + locale, +}: { + content?: string | IEmailBlock[]; + payload: string; + locale?: string; +}): Promise<{ content: string }> { + return api.post('/v1/content-templates/preview/sms', { content, payload, locale }); +} diff --git a/apps/web/src/api/hooks/index.ts b/apps/web/src/api/hooks/index.ts index 877cb7a0f29..f6211520fab 100644 --- a/apps/web/src/api/hooks/index.ts +++ b/apps/web/src/api/hooks/index.ts @@ -5,3 +5,4 @@ export * from './useWebhookSupportStatus'; export * from './notification-templates'; export * from './useGetLocalesFromContent'; export * from './usePreviewEmail'; +export * from './usePreviewSms'; diff --git a/apps/web/src/api/hooks/notification-templates/useCreateDigestDemoWorkflow.ts b/apps/web/src/api/hooks/notification-templates/useCreateDigestDemoWorkflow.ts index 6b2ef8a16eb..bf8ab2efa64 100644 --- a/apps/web/src/api/hooks/notification-templates/useCreateDigestDemoWorkflow.ts +++ b/apps/web/src/api/hooks/notification-templates/useCreateDigestDemoWorkflow.ts @@ -1,7 +1,8 @@ import { useCallback } from 'react'; import { useMutation } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; -import { ICreateNotificationTemplateDto, INotificationTemplate, StepTypeEnum } from '@novu/shared'; +import { StepTypeEnum } from '@novu/shared'; +import type { IResponseError, ICreateNotificationTemplateDto, INotificationTemplate } from '@novu/shared'; import { createTemplate } from '../../notification-templates'; import { parseUrl } from '../../../utils/routeUtils'; @@ -16,7 +17,7 @@ export const useCreateDigestDemoWorkflow = () => { const { groups, loading: areNotificationGroupLoading } = useNotificationGroup(); const { mutateAsync: createNotificationTemplate, isLoading: isCreating } = useMutation< INotificationTemplate & { __source?: string }, - { error: string; message: string; statusCode: number }, + IResponseError, { template: ICreateNotificationTemplateDto; params: { __source?: string } } >((data) => createTemplate(data.template, data.params), { onSuccess: (template) => { diff --git a/apps/web/src/api/hooks/notification-templates/useUpdateTemplate.ts b/apps/web/src/api/hooks/notification-templates/useUpdateTemplate.ts index 3f8fdc40e5b..a08c08d2587 100644 --- a/apps/web/src/api/hooks/notification-templates/useUpdateTemplate.ts +++ b/apps/web/src/api/hooks/notification-templates/useUpdateTemplate.ts @@ -1,5 +1,5 @@ import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'; -import { INotificationTemplate, IUpdateNotificationTemplateDto } from '@novu/shared'; +import type { IResponseError, INotificationTemplate, IUpdateNotificationTemplateDto } from '@novu/shared'; import { updateTemplate } from '../../notification-templates'; import { QueryKeys } from '../../query.keys'; @@ -7,7 +7,7 @@ import { QueryKeys } from '../../query.keys'; export const useUpdateTemplate = ( options: UseMutationOptions< INotificationTemplate, - { error: string; message: string; statusCode: number }, + IResponseError, { id: string; data: Partial } > = {} ) => { @@ -15,7 +15,7 @@ export const useUpdateTemplate = ( const { mutateAsync: updateTemplateMutation, ...rest } = useMutation< INotificationTemplate, - { error: string; message: string; statusCode: number }, + IResponseError, { id: string; data: Partial } >(({ id, data }) => updateTemplate(id, data), { ...options, diff --git a/apps/web/src/api/hooks/useDeleteIntegration.ts b/apps/web/src/api/hooks/useDeleteIntegration.ts index bd13801ab7c..5bdc4bca47f 100644 --- a/apps/web/src/api/hooks/useDeleteIntegration.ts +++ b/apps/web/src/api/hooks/useDeleteIntegration.ts @@ -1,38 +1,28 @@ import { MutationOptions, useMutation, useQueryClient } from '@tanstack/react-query'; +import type { IResponseError } from '@novu/shared'; import { deleteIntegration } from '../integration'; import { QueryKeys } from '../query.keys'; export const useDeleteIntegration = ( - options: MutationOptions< - {}, - { error: string; message: string; statusCode: number }, - { - id: string; - name: string; - } - > = {} + options: MutationOptions<{}, IResponseError, { id: string; name: string }> = {} ) => { const queryClient = useQueryClient(); - const { mutate: deleteIntegrationMutate, ...rest } = useMutation< - {}, - { error: string; message: string; statusCode: number }, + const { mutate: deleteIntegrationMutate, ...rest } = useMutation<{}, IResponseError, { id: string; name: string }>( + ({ id }) => deleteIntegration(id), { - id: string; - name: string; - } - >(({ id }) => deleteIntegration(id), { - ...options, - onSuccess: async (data, variables, context) => { - options?.onSuccess?.(data, variables, context); + ...options, + onSuccess: async (data, variables, context) => { + options?.onSuccess?.(data, variables, context); - await queryClient.refetchQueries({ - predicate: ({ queryKey }) => - queryKey.includes(QueryKeys.integrationsList) || queryKey.includes(QueryKeys.activeIntegrations), - }); - }, - }); + await queryClient.refetchQueries({ + predicate: ({ queryKey }) => + queryKey.includes(QueryKeys.integrationsList) || queryKey.includes(QueryKeys.activeIntegrations), + }); + }, + } + ); return { deleteIntegration: deleteIntegrationMutate, diff --git a/apps/web/src/api/hooks/useGetLocalesFromContent.ts b/apps/web/src/api/hooks/useGetLocalesFromContent.ts index 66448b4bdaf..ecfe6b22eb0 100644 --- a/apps/web/src/api/hooks/useGetLocalesFromContent.ts +++ b/apps/web/src/api/hooks/useGetLocalesFromContent.ts @@ -1,8 +1,9 @@ import { errorMessage } from '@novu/design-system'; -import { IEmailBlock } from '@novu/shared'; +import type { IResponseError, IEmailBlock } from '@novu/shared'; import { IS_DOCKER_HOSTED } from '@novu/shared-web'; import { useMutation } from '@tanstack/react-query'; import { useCallback } from 'react'; + import { getLocalesFromContent } from '../translations'; export interface ILocale { @@ -26,14 +27,11 @@ export const useGetLocalesFromContent = () => { mutateAsync: getLocalesFromContentMutation, isLoading, data, - } = useMutation( - ({ content }) => getLocalesFromContent({ content }), - { - onError: (e: any) => { - errorMessage(e.message || 'Unexpected error'); - }, - } - ); + } = useMutation(({ content }) => getLocalesFromContent({ content }), { + onError: (e) => { + errorMessage(e.message || 'Unexpected error'); + }, + }); const getLocalesFromContentCallback = useCallback( async ({ content }: Payload) => { diff --git a/apps/web/src/api/hooks/useMakePrimaryIntegration.ts b/apps/web/src/api/hooks/useMakePrimaryIntegration.ts index 6f3eb0148fb..8d3c31b072c 100644 --- a/apps/web/src/api/hooks/useMakePrimaryIntegration.ts +++ b/apps/web/src/api/hooks/useMakePrimaryIntegration.ts @@ -1,4 +1,5 @@ import { useMutation, useQueryClient, UseMutationOptions } from '@tanstack/react-query'; +import type { IResponseError } from '@novu/shared'; import { errorMessage } from '../../utils/notifications'; import type { IntegrationEntity } from '../../pages/integrations/types'; @@ -7,37 +8,28 @@ import { QueryKeys } from '../query.keys'; import { setIntegrationAsPrimary } from '../integration'; export const useMakePrimaryIntegration = ( - options: UseMutationOptions< - IntegrationEntity, - { error: string; message: string; statusCode: number }, - { - id: string; - } - > = {} + options: UseMutationOptions = {} ) => { const queryClient = useQueryClient(); - const { mutate: makePrimaryIntegration, ...rest } = useMutation< - IntegrationEntity, - { error: string; message: string; statusCode: number }, + const { mutate: makePrimaryIntegration, ...rest } = useMutation( + ({ id }) => setIntegrationAsPrimary(id), { - id: string; + ...options, + onSuccess: (integration, variables, context) => { + successMessage(`${integration.name} provider instance is activated and marked as the primary instance`); + queryClient.refetchQueries({ + predicate: ({ queryKey }) => + queryKey.includes(QueryKeys.integrationsList) || queryKey.includes(QueryKeys.activeIntegrations), + }); + options?.onSuccess?.(integration, variables, context); + }, + onError: (e: any, variables, context) => { + errorMessage(e.message || 'Unexpected error'); + options?.onError?.(e, variables, context); + }, } - >(({ id }) => setIntegrationAsPrimary(id), { - ...options, - onSuccess: (integration, variables, context) => { - successMessage(`${integration.name} provider instance is activated and marked as the primary instance`); - queryClient.refetchQueries({ - predicate: ({ queryKey }) => - queryKey.includes(QueryKeys.integrationsList) || queryKey.includes(QueryKeys.activeIntegrations), - }); - options?.onSuccess?.(integration, variables, context); - }, - onError: (e: any, variables, context) => { - errorMessage(e.message || 'Unexpected error'); - options?.onError?.(e, variables, context); - }, - }); + ); return { makePrimaryIntegration, diff --git a/apps/web/src/api/hooks/usePreviewEmail.ts b/apps/web/src/api/hooks/usePreviewEmail.ts index b67dbcf407d..e6766af031e 100644 --- a/apps/web/src/api/hooks/usePreviewEmail.ts +++ b/apps/web/src/api/hooks/usePreviewEmail.ts @@ -1,5 +1,5 @@ import { errorMessage } from '@novu/design-system'; -import { IEmailBlock, MessageTemplateContentType } from '@novu/shared'; +import type { IResponseError, IEmailBlock, MessageTemplateContentType } from '@novu/shared'; import { IS_DOCKER_HOSTED } from '@novu/shared-web'; import { useMutation, UseMutationOptions } from '@tanstack/react-query'; import { useCallback } from 'react'; @@ -16,10 +16,8 @@ export type PayloadType = { export type ResultType = { html: string; subject: string }; -type ErrorType = { error: string; message: string; statusCode: number }; - -export const usePreviewEmail = (options: UseMutationOptions = {}) => { - const { mutateAsync, isLoading } = useMutation( +export const usePreviewEmail = (options: UseMutationOptions = {}) => { + const { mutateAsync, isLoading } = useMutation( ({ content, payload, contentType, layoutId, locale, subject }) => previewEmail({ content, payload, contentType, layoutId, locale, subject }), diff --git a/apps/web/src/api/hooks/usePreviewSms.ts b/apps/web/src/api/hooks/usePreviewSms.ts new file mode 100644 index 00000000000..9ccf4b3bf0c --- /dev/null +++ b/apps/web/src/api/hooks/usePreviewSms.ts @@ -0,0 +1,49 @@ +import { useMutation, UseMutationOptions } from '@tanstack/react-query'; +import { useCallback } from 'react'; +import { errorMessage } from '@novu/design-system'; +import type { IEmailBlock, IResponseError } from '@novu/shared'; +import { IS_DOCKER_HOSTED } from '@novu/shared-web'; + +import { previewSms } from '../content-templates'; + +type PayloadType = { + content?: string | IEmailBlock[]; + payload: string; + locale?: string; +}; + +type ResultType = { content: string }; + +export const usePreviewSms = (options: UseMutationOptions = {}) => { + const { mutateAsync, isLoading } = useMutation( + ({ content, payload, locale }) => previewSms({ content, payload, locale }), + { + onError: (e) => { + errorMessage(e.message || 'Unexpected error'); + }, + onSuccess: (result, variables, context) => { + options?.onSuccess?.(result, variables, context); + }, + } + ); + + const getSmsPreview = useCallback( + ({ content, payload, locale }: PayloadType) => { + if (IS_DOCKER_HOSTED) { + return; + } + + return mutateAsync({ + content, + payload, + locale, + }); + }, + [mutateAsync] + ); + + return { + getSmsPreview, + isLoading, + }; +}; diff --git a/apps/web/src/api/hooks/useUpdateIntegration.ts b/apps/web/src/api/hooks/useUpdateIntegration.ts index 2f58172f371..e391604bc63 100644 --- a/apps/web/src/api/hooks/useUpdateIntegration.ts +++ b/apps/web/src/api/hooks/useUpdateIntegration.ts @@ -1,5 +1,5 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { IUpdateIntegrationBodyDto } from '@novu/shared'; +import type { IResponseError, IUpdateIntegrationBodyDto } from '@novu/shared'; import { errorMessage } from '../../utils/notifications'; import { updateIntegration } from '../integration'; @@ -12,7 +12,7 @@ export const useUpdateIntegration = (integrationId: string) => { const { mutateAsync: updateIntegrationMutation, isLoading: isLoadingUpdate } = useMutation< IntegrationEntity, - { error: string; message: string; statusCode: number }, + IResponseError, { id: string; data: IUpdateIntegrationBodyDto; diff --git a/apps/web/src/components/layout/components/OrganizationSelect.tsx b/apps/web/src/components/layout/components/OrganizationSelect.tsx index 125c77a28c9..a717f527d7d 100644 --- a/apps/web/src/components/layout/components/OrganizationSelect.tsx +++ b/apps/web/src/components/layout/components/OrganizationSelect.tsx @@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import * as capitalize from 'lodash.capitalize'; import styled from '@emotion/styled'; -import { IOrganizationEntity } from '@novu/shared'; +import type { IResponseError, IOrganizationEntity } from '@novu/shared'; import { Select } from '@novu/design-system'; import { addOrganization, switchOrganization } from '../../../api/organization'; @@ -20,15 +20,13 @@ export default function OrganizationSelect() { const { isLoading: loadingAddOrganization, mutateAsync: createOrganization } = useMutation< IOrganizationEntity, - { error: string; message: string; statusCode: number }, + IResponseError, string >((name) => addOrganization(name)); - const { mutateAsync: changeOrganization } = useMutation< - string, - { error: string; message: string; statusCode: number }, - string - >((id) => switchOrganization(id)); + const { mutateAsync: changeOrganization } = useMutation((id) => + switchOrganization(id) + ); const switchOrgCallback = useCallback( async (organizationId: string | string[] | null) => { diff --git a/apps/web/src/components/quick-start/in-app-onboarding/TriggerNode.tsx b/apps/web/src/components/quick-start/in-app-onboarding/TriggerNode.tsx index 35c10e0d5c2..348a22cf0d1 100644 --- a/apps/web/src/components/quick-start/in-app-onboarding/TriggerNode.tsx +++ b/apps/web/src/components/quick-start/in-app-onboarding/TriggerNode.tsx @@ -1,13 +1,13 @@ import { Handle, Position } from 'react-flow-renderer'; - import { Button, colors, shadows, Text, Title, BoltOutlinedGradient, Playground } from '@novu/design-system'; - import styled from '@emotion/styled'; import { createStyles, Group, Popover, Stack, useMantineColorScheme } from '@mantine/core'; -import { ActorTypeEnum, INotificationTemplate, StepTypeEnum, SystemAvatarIconEnum } from '@novu/shared'; +import { ActorTypeEnum, StepTypeEnum, SystemAvatarIconEnum } from '@novu/shared'; +import type { IResponseError, INotificationTemplate } from '@novu/shared'; import { useMutation } from '@tanstack/react-query'; import { useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; + import { createTemplate, testTrigger } from '../../../api/notification-templates'; import { useEffectOnce, useNotificationGroup, useTemplates } from '../../../hooks'; import { @@ -65,7 +65,7 @@ function TriggerButton({ setOpened }: { setOpened: (value: boolean) => void }) { const { mutate: createNotificationTemplate, isLoading: createTemplateLoading } = useMutation< INotificationTemplate & { __source?: string }, - { error: string; message: string; statusCode: number }, + IResponseError, { template: ICreateNotificationTemplateDto; params: { __source?: string } } >((data) => createTemplate(data.template, data.params), { onError: (error) => { diff --git a/apps/web/src/components/workflow/Preview/common/LocaleSelect.tsx b/apps/web/src/components/workflow/Preview/common/LocaleSelect.tsx index 5b991cc779f..685fcd80d8b 100644 --- a/apps/web/src/components/workflow/Preview/common/LocaleSelect.tsx +++ b/apps/web/src/components/workflow/Preview/common/LocaleSelect.tsx @@ -1,19 +1,33 @@ import styled from '@emotion/styled'; import { SelectItemProps, Group } from '@mantine/core'; -import { Select, Text } from '@novu/design-system'; +import { Select, ISelectProps, Text } from '@novu/design-system'; import { forwardRef } from 'react'; import { IS_DOCKER_HOSTED } from '../../../../config'; const rightSectionWidth = 20; -export function LocaleSelect({ locales, value, isLoading, onLocaleChange }) { +export function LocaleSelect({ + locales, + value, + isLoading, + onLocaleChange, + className, + dropdownPosition, +}: { + locales: { langName: string; langIso: string }[]; + value?: string; + isLoading?: boolean; + onLocaleChange: (val: string) => void; + className?: string; + dropdownPosition?: ISelectProps['dropdownPosition']; +}) { // Do not render locale select if self-hosted or no locale or only one locale if (IS_DOCKER_HOSTED || locales.length < 2) { return null; } return ( - +