diff --git a/apps/api/src/app/content-templates/content-templates.controller.ts b/apps/api/src/app/content-templates/content-templates.controller.ts index 0bdae1d8214..2f6296e385b 100644 --- a/apps/api/src/app/content-templates/content-templates.controller.ts +++ b/apps/api/src/app/content-templates/content-templates.controller.ts @@ -119,6 +119,7 @@ export class ContentTemplatesController { public previewPush( @UserSession() user: IJwtPayload, @Body('content') content: string, + @Body('title') title: string, @Body('payload') payload: any, @Body('locale') locale?: string ) { @@ -130,6 +131,7 @@ export class ContentTemplatesController { content, payload, locale, + title, }), this.initiateTranslations.bind(this) ); diff --git a/apps/web/public/static/images/mobilePreview/android.webp b/apps/web/public/static/images/mobilePreview/android.webp new file mode 100644 index 00000000000..f231b5496a1 Binary files /dev/null and b/apps/web/public/static/images/mobilePreview/android.webp differ diff --git a/apps/web/public/static/images/mobilePreview/iphone.webp b/apps/web/public/static/images/mobilePreview/iphone.webp new file mode 100644 index 00000000000..d00552e0803 Binary files /dev/null and b/apps/web/public/static/images/mobilePreview/iphone.webp differ diff --git a/apps/web/src/api/content-templates.ts b/apps/web/src/api/content-templates.ts index 4db155314a3..3a3ef119620 100644 --- a/apps/web/src/api/content-templates.ts +++ b/apps/web/src/api/content-templates.ts @@ -56,3 +56,17 @@ export async function previewSms({ }): Promise<{ content: string }> { return api.post('/v1/content-templates/preview/sms', { content, payload, locale }); } + +export async function previewPush({ + content, + payload, + locale, + title, +}: { + content?: string | IEmailBlock[]; + title?: string; + payload?: string; + locale?: string; +}): Promise<{ content: string; title: string }> { + return api.post('/v1/content-templates/preview/push', { content, payload, locale, title }); +} diff --git a/apps/web/src/api/hooks/index.ts b/apps/web/src/api/hooks/index.ts index f6211520fab..e323bcf5df5 100644 --- a/apps/web/src/api/hooks/index.ts +++ b/apps/web/src/api/hooks/index.ts @@ -6,3 +6,4 @@ export * from './notification-templates'; export * from './useGetLocalesFromContent'; export * from './usePreviewEmail'; export * from './usePreviewSms'; +export * from './usePreviewPush'; diff --git a/apps/web/src/api/hooks/usePreviewPush.tsx b/apps/web/src/api/hooks/usePreviewPush.tsx new file mode 100644 index 00000000000..239aca13da0 --- /dev/null +++ b/apps/web/src/api/hooks/usePreviewPush.tsx @@ -0,0 +1,48 @@ +import { errorMessage } from '@novu/design-system'; +import { IEmailBlock } from '@novu/shared'; +import { useMutation, UseMutationOptions } from '@tanstack/react-query'; +import { useCallback } from 'react'; +import { previewPush } from '../content-templates'; + +type PayloadType = { + content?: string | IEmailBlock[]; + payload: string; + title?: string; + locale?: string; +}; + +type ResultType = { content: string; title: string }; + +type ErrorType = { error: string; message: string; statusCode: number }; + +export const usePreviewPush = (options: UseMutationOptions = {}) => { + const { mutateAsync, isLoading } = useMutation( + ({ content, payload, locale, title }) => previewPush({ content, payload, locale, title }), + + { + onError: (e: any) => { + errorMessage(e.message || 'Unexpected error'); + }, + onSuccess: (result, variables, context) => { + options?.onSuccess?.(result, variables, context); + }, + } + ); + + const getPushPreviewCallback = useCallback( + async ({ content, payload, locale, title }: PayloadType) => { + await mutateAsync({ + content, + payload, + locale, + title, + }); + }, + [mutateAsync] + ); + + return { + getPushPreview: getPushPreviewCallback, + isLoading, + }; +}; diff --git a/apps/web/src/components/workflow/Preview/chat/ChatContent.tsx b/apps/web/src/components/workflow/Preview/chat/ChatContent.tsx deleted file mode 100644 index bd6831fad79..00000000000 --- a/apps/web/src/components/workflow/Preview/chat/ChatContent.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import styled from '@emotion/styled'; -import { Group, Skeleton, Stack, useMantineColorScheme } from '@mantine/core'; -import { colors, Text } from '@novu/design-system'; -import { useState } from 'react'; -import { NovuGreyIcon, PreviewEditOverlay } from '../common'; -import { When } from '../../../utils/When'; - -export function ChatContent({ isLoading, content, error }) { - const [isEditOverlayVisible, setIsEditOverlayVisible] = useState(false); - const { colorScheme } = useMantineColorScheme(); - const isDark = colorScheme === 'dark'; - - const handleMouseEnter = () => { - setIsEditOverlayVisible(true); - }; - - const handleMouseLeave = () => { - setIsEditOverlayVisible(false); - }; - - return ( - - - - - - - - - - {isEditOverlayVisible && } -
- -
- - - - Your App - - APP - now - - {error && error.template?.content && error.template?.content?.message ? ( - {error.template.content.message} - ) : ( - {content} - )} - -
-
- ); -} - -const PillStyled = styled.div<{ isDark: boolean }>` - background-color: ${({ isDark }) => (isDark ? colors.B40 : colors.BGLight)}; - border-radius: 4px; - padding: 0px 6px; - align-items: center; - color: ${({ isDark }) => (isDark ? colors.B80 : colors.B40)}; - font-size: 10px; - font-weight: 400; - line-height: 20px; -`; diff --git a/apps/web/src/components/workflow/Preview/common/PreviewDateIcon.tsx b/apps/web/src/components/workflow/Preview/common/PreviewDateIcon.tsx deleted file mode 100644 index 1ec702fb077..00000000000 --- a/apps/web/src/components/workflow/Preview/common/PreviewDateIcon.tsx +++ /dev/null @@ -1,9 +0,0 @@ -export const PreviewDateIcon = () => ( - - - -); diff --git a/apps/web/src/components/workflow/Preview/index.ts b/apps/web/src/components/workflow/Preview/index.ts deleted file mode 100644 index 54e324a0e00..00000000000 --- a/apps/web/src/components/workflow/Preview/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './chat'; -export * from './Email'; diff --git a/apps/web/src/components/workflow/preview/chat/ChatContent.tsx b/apps/web/src/components/workflow/preview/chat/ChatContent.tsx new file mode 100644 index 00000000000..5f8ed882732 --- /dev/null +++ b/apps/web/src/components/workflow/preview/chat/ChatContent.tsx @@ -0,0 +1,79 @@ +import styled from '@emotion/styled'; +import { Group, Skeleton, Stack, useMantineColorScheme } from '@mantine/core'; +import { colors, Text } from '@novu/design-system'; + +import { NovuGreyIcon, PreviewEditOverlay } from '../common'; +import { When } from '../../../utils/When'; +import { useHover } from '../../../../hooks'; + +export function ChatContent({ isLoading, content, errorMsg }) { + const { isHovered, onMouseEnter, onMouseLeave } = useHover(); + const { colorScheme } = useMantineColorScheme(); + const isDark = colorScheme === 'dark'; + + return ( + + {isHovered && } + + + + + + + + + +
+ +
+ + + + Your App + + APP + now + + {errorMsg ? ( + {errorMsg} + ) : ( + {content} + )} + +
+
+
+ ); +} + +const PillStyled = styled.div<{ isDark: boolean }>` + background-color: ${({ isDark }) => (isDark ? colors.B40 : colors.BGLight)}; + border-radius: 0.25rem; + padding: 0px 6px; + align-items: center; + color: ${({ isDark }) => (isDark ? colors.B80 : colors.B40)}; + font-size: 10px; + font-weight: 400; + line-height: 1.25rem; +`; + +const ContentAndOVerlayWrapperStyled = styled.div` + position: relative; + padding-top: 1.5rem; + padding-bottom: 1.25rem; +`; diff --git a/apps/web/src/components/workflow/Preview/chat/ChatInput.tsx b/apps/web/src/components/workflow/preview/chat/ChatInput.tsx similarity index 100% rename from apps/web/src/components/workflow/Preview/chat/ChatInput.tsx rename to apps/web/src/components/workflow/preview/chat/ChatInput.tsx diff --git a/apps/web/src/components/workflow/Preview/chat/ChatPreview.tsx b/apps/web/src/components/workflow/preview/chat/ChatPreview.tsx similarity index 90% rename from apps/web/src/components/workflow/Preview/chat/ChatPreview.tsx rename to apps/web/src/components/workflow/preview/chat/ChatPreview.tsx index 5fdefacf6c3..485e7b9bbb4 100644 --- a/apps/web/src/components/workflow/Preview/chat/ChatPreview.tsx +++ b/apps/web/src/components/workflow/preview/chat/ChatPreview.tsx @@ -1,18 +1,18 @@ -import { Divider, useMantineColorScheme } from '@mantine/core'; +import { Divider, Flex, useMantineColorScheme } from '@mantine/core'; import { colors, Text } from '@novu/design-system'; import { useAuthController } from '@novu/shared-web'; import { useMutation } from '@tanstack/react-query'; import { useEffect, useState } from 'react'; import { useFormContext, useWatch } from 'react-hook-form'; +import { previewChat } from '../../../../api/content-templates'; import { getLocalesFromContent } from '../../../../api/translations'; import { IForm } from '../../../../pages/templates/components/formTypes'; -import { useStepFormErrors } from '../../../../pages/templates/hooks/useStepFormErrors'; +import { useStepFormCombinedErrors } from '../../../../pages/templates/hooks/useStepFormCombinedErrors'; import { useStepFormPath } from '../../../../pages/templates/hooks/useStepFormPath'; +import { errorMessage } from '../../../../utils/notifications'; import { LocaleSelect } from '../common'; import { ChatContent } from './ChatContent'; import { ChatInput } from './ChatInput'; -import { previewChat } from '../../../../api/content-templates'; -import { errorMessage } from '../../../../utils/notifications'; export function ChatPreview() { const { organization } = useAuthController(); @@ -30,7 +30,8 @@ export function ChatPreview() { const { control } = useFormContext(); const path = useStepFormPath(); - const error = useStepFormErrors(); + + const errorMsg = useStepFormCombinedErrors(); const content = useWatch({ name: `${path}.template.content`, @@ -75,14 +76,14 @@ export function ChatPreview() { return (
-
+ -
+ - +
); diff --git a/apps/web/src/components/workflow/Preview/chat/index.ts b/apps/web/src/components/workflow/preview/chat/index.ts similarity index 100% rename from apps/web/src/components/workflow/Preview/chat/index.ts rename to apps/web/src/components/workflow/preview/chat/index.ts diff --git a/apps/web/src/components/workflow/Preview/common/Mobile.tsx b/apps/web/src/components/workflow/preview/common/EmailMobile.tsx similarity index 94% rename from apps/web/src/components/workflow/Preview/common/Mobile.tsx rename to apps/web/src/components/workflow/preview/common/EmailMobile.tsx index b71049a5b36..719c3e20c09 100644 --- a/apps/web/src/components/workflow/Preview/common/Mobile.tsx +++ b/apps/web/src/components/workflow/preview/common/EmailMobile.tsx @@ -23,7 +23,7 @@ const useStyles = createStyles((theme) => ({ }, })); -export const Mobile = ({ children }) => { +export const EmailMobile = ({ children }) => { const { classes } = useStyles(); return ( diff --git a/apps/web/src/components/workflow/Preview/common/EmojiIcon.tsx b/apps/web/src/components/workflow/preview/common/EmojiIcon.tsx similarity index 100% rename from apps/web/src/components/workflow/Preview/common/EmojiIcon.tsx rename to apps/web/src/components/workflow/preview/common/EmojiIcon.tsx diff --git a/apps/web/src/components/workflow/Preview/common/LocaleSelect.tsx b/apps/web/src/components/workflow/preview/common/LocaleSelect.tsx similarity index 100% rename from apps/web/src/components/workflow/Preview/common/LocaleSelect.tsx rename to apps/web/src/components/workflow/preview/common/LocaleSelect.tsx diff --git a/apps/web/src/components/workflow/Preview/common/NovuGreyIcon.tsx b/apps/web/src/components/workflow/preview/common/NovuGreyIcon.tsx similarity index 100% rename from apps/web/src/components/workflow/Preview/common/NovuGreyIcon.tsx rename to apps/web/src/components/workflow/preview/common/NovuGreyIcon.tsx diff --git a/apps/web/src/components/workflow/Preview/common/PreviewEditOverlay.tsx b/apps/web/src/components/workflow/preview/common/PreviewEditOverlay.tsx similarity index 96% rename from apps/web/src/components/workflow/Preview/common/PreviewEditOverlay.tsx rename to apps/web/src/components/workflow/preview/common/PreviewEditOverlay.tsx index b27b827899e..7386b2fb9ad 100644 --- a/apps/web/src/components/workflow/Preview/common/PreviewEditOverlay.tsx +++ b/apps/web/src/components/workflow/preview/common/PreviewEditOverlay.tsx @@ -40,8 +40,7 @@ export function PreviewEditOverlay() { } const OverlayStyled = styled(Overlay)` - backdrop-filter: blur(2px); - border-radius: 8px; border: 1px solid ${colors.B30}; background: rgba(41, 41, 51, 0.8); + border-radius: 12px; `; diff --git a/apps/web/src/components/workflow/Preview/common/PreviewUserIcon.tsx b/apps/web/src/components/workflow/preview/common/PreviewUserIcon.tsx similarity index 100% rename from apps/web/src/components/workflow/Preview/common/PreviewUserIcon.tsx rename to apps/web/src/components/workflow/preview/common/PreviewUserIcon.tsx diff --git a/apps/web/src/components/workflow/Preview/common/SendIcon.tsx b/apps/web/src/components/workflow/preview/common/SendIcon.tsx similarity index 100% rename from apps/web/src/components/workflow/Preview/common/SendIcon.tsx rename to apps/web/src/components/workflow/preview/common/SendIcon.tsx diff --git a/apps/web/src/pages/templates/components/phone-simulator/AndroidIndicatorsIcon.tsx b/apps/web/src/components/workflow/preview/common/icons/android/AndroidIndicatorsIcon.tsx similarity index 100% rename from apps/web/src/pages/templates/components/phone-simulator/AndroidIndicatorsIcon.tsx rename to apps/web/src/components/workflow/preview/common/icons/android/AndroidIndicatorsIcon.tsx diff --git a/apps/web/src/pages/templates/components/phone-simulator/AndroidKeyboard.tsx b/apps/web/src/components/workflow/preview/common/icons/android/AndroidKeyboard.tsx similarity index 100% rename from apps/web/src/pages/templates/components/phone-simulator/AndroidKeyboard.tsx rename to apps/web/src/components/workflow/preview/common/icons/android/AndroidKeyboard.tsx diff --git a/apps/web/src/components/workflow/preview/common/icons/android/index.ts b/apps/web/src/components/workflow/preview/common/icons/android/index.ts new file mode 100644 index 00000000000..ab8d65809e3 --- /dev/null +++ b/apps/web/src/components/workflow/preview/common/icons/android/index.ts @@ -0,0 +1,2 @@ +export * from './AndroidIndicatorsIcon'; +export * from './AndroidKeyboard'; diff --git a/apps/web/src/components/workflow/preview/common/icons/index.ts b/apps/web/src/components/workflow/preview/common/icons/index.ts new file mode 100644 index 00000000000..4c8e4f8c9df --- /dev/null +++ b/apps/web/src/components/workflow/preview/common/icons/index.ts @@ -0,0 +1,2 @@ +export * from './iphone'; +export * from './android'; diff --git a/apps/web/src/pages/templates/components/phone-simulator/IOSIndicatorsIcon.tsx b/apps/web/src/components/workflow/preview/common/icons/iphone/IOSIndicatorsIcon.tsx similarity index 100% rename from apps/web/src/pages/templates/components/phone-simulator/IOSIndicatorsIcon.tsx rename to apps/web/src/components/workflow/preview/common/icons/iphone/IOSIndicatorsIcon.tsx diff --git a/apps/web/src/pages/templates/components/phone-simulator/IOSKeyboard.tsx b/apps/web/src/components/workflow/preview/common/icons/iphone/IOSKeyboard.tsx similarity index 100% rename from apps/web/src/pages/templates/components/phone-simulator/IOSKeyboard.tsx rename to apps/web/src/components/workflow/preview/common/icons/iphone/IOSKeyboard.tsx diff --git a/apps/web/src/pages/templates/components/phone-simulator/TimeIcon.tsx b/apps/web/src/components/workflow/preview/common/icons/iphone/TimeIcon.tsx similarity index 100% rename from apps/web/src/pages/templates/components/phone-simulator/TimeIcon.tsx rename to apps/web/src/components/workflow/preview/common/icons/iphone/TimeIcon.tsx diff --git a/apps/web/src/components/workflow/preview/common/icons/iphone/index.ts b/apps/web/src/components/workflow/preview/common/icons/iphone/index.ts new file mode 100644 index 00000000000..461d06f940c --- /dev/null +++ b/apps/web/src/components/workflow/preview/common/icons/iphone/index.ts @@ -0,0 +1,3 @@ +export * from './IOSIndicatorsIcon'; +export * from './IOSKeyboard'; +export * from './TimeIcon'; diff --git a/apps/web/src/components/workflow/Preview/common/index.ts b/apps/web/src/components/workflow/preview/common/index.ts similarity index 74% rename from apps/web/src/components/workflow/Preview/common/index.ts rename to apps/web/src/components/workflow/preview/common/index.ts index 779e9d18762..e69c2857403 100644 --- a/apps/web/src/components/workflow/Preview/common/index.ts +++ b/apps/web/src/components/workflow/preview/common/index.ts @@ -1,8 +1,8 @@ export * from './LocaleSelect'; -export * from './Mobile'; -export * from './PreviewDateIcon'; +export * from './EmailMobile'; export * from './PreviewEditOverlay'; export * from './PreviewUserIcon'; export * from './NovuGreyIcon'; export * from './EmojiIcon'; export * from './SendIcon'; +export * from './phone-simulator'; diff --git a/apps/web/src/pages/templates/components/phone-simulator/MobileSimulator.styles.tsx b/apps/web/src/components/workflow/preview/common/phone-simulator/MobileSimulator.styles.tsx similarity index 69% rename from apps/web/src/pages/templates/components/phone-simulator/MobileSimulator.styles.tsx rename to apps/web/src/components/workflow/preview/common/phone-simulator/MobileSimulator.styles.tsx index c2f1859a83b..9bcd1007b24 100644 --- a/apps/web/src/pages/templates/components/phone-simulator/MobileSimulator.styles.tsx +++ b/apps/web/src/components/workflow/preview/common/phone-simulator/MobileSimulator.styles.tsx @@ -1,11 +1,12 @@ import styled from '@emotion/styled'; import { colors, shadows } from '@novu/design-system'; - -import { TimeIcon } from './TimeIcon'; +import { TimeIcon } from '../icons'; const BORDER_RADIUS_PX = 40; +const LIGHT_THEME_BACKGROUND = 'rgba(255, 255, 255, 0.4)'; +const DARK_THEME_BACKGROUND = 'rgba(0, 0, 0, 0.6)'; -export const MobileSimulatorBody = styled.div` +export const MobileSimulatorBody = styled.div<{ withBackground: boolean; isIOS: boolean }>` position: relative; display: flex; flex-direction: column; @@ -17,6 +18,21 @@ export const MobileSimulatorBody = styled.div` border: 1.5rem solid ${({ theme }) => (theme.colorScheme === 'dark' ? colors.B15 : colors.BGLight)}; box-shadow: ${shadows.dark}; background: ${({ theme }) => (theme.colorScheme === 'dark' ? '#4b4b51' : colors.white)}; + + ${({ withBackground, isIOS, theme }) => { + if (withBackground) { + return ` + background-position: center; + background-repeat: no-repeat; + background: linear-gradient(0deg, ${ + theme.colorScheme === 'dark' ? DARK_THEME_BACKGROUND : LIGHT_THEME_BACKGROUND + } 0%, ${theme.colorScheme === 'dark' ? DARK_THEME_BACKGROUND : LIGHT_THEME_BACKGROUND} 100%), + url(/static/images/mobilePreview/${isIOS ? 'iphone' : 'android'}.webp) no-repeat center + center / cover, + lightgray; + `; + } + }} `; export const Notch = styled.div` diff --git a/apps/web/src/pages/templates/components/phone-simulator/MobileSimulator.tsx b/apps/web/src/components/workflow/preview/common/phone-simulator/MobileSimulator.tsx similarity index 57% rename from apps/web/src/pages/templates/components/phone-simulator/MobileSimulator.tsx rename to apps/web/src/components/workflow/preview/common/phone-simulator/MobileSimulator.tsx index b34f10ba4cb..4334d0c54db 100644 --- a/apps/web/src/pages/templates/components/phone-simulator/MobileSimulator.tsx +++ b/apps/web/src/components/workflow/preview/common/phone-simulator/MobileSimulator.tsx @@ -1,11 +1,9 @@ import { useState } from 'react'; +import { useMantineColorScheme } from '@mantine/core'; +import { ChannelTypeEnum } from '@novu/shared'; +import { When } from '@novu/design-system'; import { PhonePlatformSwitch } from './PhonePlatformSwitch'; -import { IOSIndicatorsIcon } from './IOSIndicatorsIcon'; -import { AndroidIndicatorsIcon } from './AndroidIndicatorsIcon'; -import { IOSKeyboard } from './IOSKeyboard'; -import { AndroidKeyboard } from './AndroidKeyboard'; -import { useMantineColorScheme } from '@mantine/core'; import { IndicatorsContainer, MobileSimulatorBody, @@ -14,13 +12,20 @@ import { TimeIconStyled, Camera, } from './MobileSimulator.styles'; +import { AndroidIndicatorsIcon, IOSIndicatorsIcon, IOSKeyboard, AndroidKeyboard } from '../icons'; -export const MobileSimulator: React.FC = ({ children }) => { +export const MobileSimulator = ({ + children, + withBackground, +}: { + withBackground: boolean; + children: React.ReactNode; +}) => { const [isIOS, setIsIOS] = useState(true); const { colorScheme } = useMantineColorScheme(); return ( - + {isIOS ? : } @@ -30,11 +35,13 @@ export const MobileSimulator: React.FC = ({ children }) => { setIsIOS((old) => !old)} /> {children} - {isIOS ? ( - - ) : ( - - )} + + {isIOS ? ( + + ) : ( + + )} + ); }; diff --git a/apps/web/src/pages/templates/components/phone-simulator/PhonePlatformSwitch.styles.tsx b/apps/web/src/components/workflow/preview/common/phone-simulator/PhonePlatformSwitch.styles.tsx similarity index 100% rename from apps/web/src/pages/templates/components/phone-simulator/PhonePlatformSwitch.styles.tsx rename to apps/web/src/components/workflow/preview/common/phone-simulator/PhonePlatformSwitch.styles.tsx diff --git a/apps/web/src/pages/templates/components/phone-simulator/PhonePlatformSwitch.tsx b/apps/web/src/components/workflow/preview/common/phone-simulator/PhonePlatformSwitch.tsx similarity index 100% rename from apps/web/src/pages/templates/components/phone-simulator/PhonePlatformSwitch.tsx rename to apps/web/src/components/workflow/preview/common/phone-simulator/PhonePlatformSwitch.tsx diff --git a/apps/web/src/pages/templates/components/phone-simulator/index.ts b/apps/web/src/components/workflow/preview/common/phone-simulator/index.ts similarity index 100% rename from apps/web/src/pages/templates/components/phone-simulator/index.ts rename to apps/web/src/components/workflow/preview/common/phone-simulator/index.ts diff --git a/apps/web/src/components/workflow/Preview/Email/EmailPreview.tsx b/apps/web/src/components/workflow/preview/email/EmailPreview.tsx similarity index 96% rename from apps/web/src/components/workflow/Preview/Email/EmailPreview.tsx rename to apps/web/src/components/workflow/preview/email/EmailPreview.tsx index 5252b1f571e..3b9a63be048 100644 --- a/apps/web/src/components/workflow/Preview/Email/EmailPreview.tsx +++ b/apps/web/src/components/workflow/preview/email/EmailPreview.tsx @@ -1,22 +1,18 @@ import { Grid, JsonInput, useMantineTheme } from '@mantine/core'; -import type { IEmailBlock, MessageTemplateContentType } from '@novu/shared'; -import { useMutation } from '@tanstack/react-query'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useFormContext, useWatch } from 'react-hook-form'; import { Button, colors, inputStyles } from '@novu/design-system'; -import { previewEmail } from '../../../../api/content-templates'; -import { When } from '../../../utils/When'; +import { IS_DOCKER_HOSTED, useDataRef } from '@novu/shared-web'; +import { useGetLocalesFromContent, usePreviewEmail } from '../../../../api/hooks'; import { useActiveIntegrations, useAuthController, useProcessVariables } from '../../../../hooks'; -import { errorMessage } from '../../../../utils/notifications'; import type { IForm } from '../../../../pages/templates/components/formTypes'; import { useStepFormErrors } from '../../../../pages/templates/hooks/useStepFormErrors'; import { useStepFormPath } from '../../../../pages/templates/hooks/useStepFormPath'; +import { When } from '../../../utils/When'; import { PreviewMobile } from './PreviewMobile'; import { PreviewWeb } from './PreviewWeb'; -import { useGetLocalesFromContent, usePreviewEmail } from '../../../../api/hooks'; -import { IS_DOCKER_HOSTED, useDataRef } from '@novu/shared-web'; export const EmailPreview = ({ showVariables = true, view }: { view: string; showVariables?: boolean }) => { const { control } = useFormContext(); diff --git a/apps/web/src/components/workflow/Preview/Email/PreviewMobile.cy.tsx b/apps/web/src/components/workflow/preview/email/PreviewMobile.cy.tsx similarity index 100% rename from apps/web/src/components/workflow/Preview/Email/PreviewMobile.cy.tsx rename to apps/web/src/components/workflow/preview/email/PreviewMobile.cy.tsx diff --git a/apps/web/src/components/workflow/Preview/Email/PreviewMobile.tsx b/apps/web/src/components/workflow/preview/email/PreviewMobile.tsx similarity index 98% rename from apps/web/src/components/workflow/Preview/Email/PreviewMobile.tsx rename to apps/web/src/components/workflow/preview/email/PreviewMobile.tsx index 41e0d3ca37a..808c7d1444d 100644 --- a/apps/web/src/components/workflow/Preview/Email/PreviewMobile.tsx +++ b/apps/web/src/components/workflow/preview/email/PreviewMobile.tsx @@ -8,7 +8,7 @@ import { IFormStep } from '../../../../pages/templates/components/formTypes'; import { EmailIntegrationInfo } from '../../../../pages/templates/editor/EmailIntegrationInfo'; import { When } from '../../../utils/When'; import { LocaleSelect } from '../common/LocaleSelect'; -import { Mobile } from '../common/Mobile'; +import { EmailMobile } from '../common/EmailMobile'; import { PreviewEditOverlay } from '../common/PreviewEditOverlay'; import { PreviewUserIcon } from '../common/PreviewUserIcon'; import { ContentSkeleton, HeaderSkeleton } from './Skeleton'; @@ -95,7 +95,7 @@ export const PreviewMobile = ({ return ( <> - +
-
+ ); }; diff --git a/apps/web/src/components/workflow/Preview/Email/PreviewMobileInbox/DateArrow.tsx b/apps/web/src/components/workflow/preview/email/PreviewMobileInbox/DateArrow.tsx similarity index 100% rename from apps/web/src/components/workflow/Preview/Email/PreviewMobileInbox/DateArrow.tsx rename to apps/web/src/components/workflow/preview/email/PreviewMobileInbox/DateArrow.tsx diff --git a/apps/web/src/components/workflow/Preview/Email/PreviewMobileInbox/InboxItem.tsx b/apps/web/src/components/workflow/preview/email/PreviewMobileInbox/InboxItem.tsx similarity index 100% rename from apps/web/src/components/workflow/Preview/Email/PreviewMobileInbox/InboxItem.tsx rename to apps/web/src/components/workflow/preview/email/PreviewMobileInbox/InboxItem.tsx diff --git a/apps/web/src/components/workflow/Preview/Email/PreviewMobileInbox/index.tsx b/apps/web/src/components/workflow/preview/email/PreviewMobileInbox/index.tsx similarity index 95% rename from apps/web/src/components/workflow/Preview/Email/PreviewMobileInbox/index.tsx rename to apps/web/src/components/workflow/preview/email/PreviewMobileInbox/index.tsx index f15d7dbc29b..2feee75e182 100644 --- a/apps/web/src/components/workflow/Preview/Email/PreviewMobileInbox/index.tsx +++ b/apps/web/src/components/workflow/preview/email/PreviewMobileInbox/index.tsx @@ -2,7 +2,7 @@ import { createStyles, Group } from '@mantine/core'; import { format } from 'date-fns'; import { colors } from '@novu/design-system'; import { EmailIntegrationInfo } from '../../../../../pages/templates/editor/EmailIntegrationInfo'; -import { Mobile } from '../../common/Mobile'; +import { EmailMobile } from '../../common/EmailMobile'; import { DateArrow } from './DateArrow'; import { ItemSkeleton } from './InboxItem'; @@ -55,7 +55,7 @@ export const PreviewMobileInbox = ({ return ( <> - +
Inbox
@@ -74,7 +74,7 @@ export const PreviewMobileInbox = ({
- +
); diff --git a/apps/web/src/components/workflow/Preview/Email/PreviewSegment/MobileIcon.tsx b/apps/web/src/components/workflow/preview/email/PreviewSegment/MobileIcon.tsx similarity index 100% rename from apps/web/src/components/workflow/Preview/Email/PreviewSegment/MobileIcon.tsx rename to apps/web/src/components/workflow/preview/email/PreviewSegment/MobileIcon.tsx diff --git a/apps/web/src/components/workflow/Preview/Email/PreviewSegment/WebIcon.tsx b/apps/web/src/components/workflow/preview/email/PreviewSegment/WebIcon.tsx similarity index 100% rename from apps/web/src/components/workflow/Preview/Email/PreviewSegment/WebIcon.tsx rename to apps/web/src/components/workflow/preview/email/PreviewSegment/WebIcon.tsx diff --git a/apps/web/src/components/workflow/Preview/Email/PreviewWeb.cy.tsx b/apps/web/src/components/workflow/preview/email/PreviewWeb.cy.tsx similarity index 100% rename from apps/web/src/components/workflow/Preview/Email/PreviewWeb.cy.tsx rename to apps/web/src/components/workflow/preview/email/PreviewWeb.cy.tsx diff --git a/apps/web/src/components/workflow/Preview/Email/PreviewWeb.tsx b/apps/web/src/components/workflow/preview/email/PreviewWeb.tsx similarity index 74% rename from apps/web/src/components/workflow/Preview/Email/PreviewWeb.tsx rename to apps/web/src/components/workflow/preview/email/PreviewWeb.tsx index 2f8be320b01..2c4d28bed72 100644 --- a/apps/web/src/components/workflow/Preview/Email/PreviewWeb.tsx +++ b/apps/web/src/components/workflow/preview/email/PreviewWeb.tsx @@ -12,7 +12,7 @@ import { PreviewEditOverlay } from '../common/PreviewEditOverlay'; import { PreviewUserIcon } from '../common/PreviewUserIcon'; import { ContentSkeleton, HeaderSkeleton } from './Skeleton'; -const useStyles = createStyles((theme, { error }: { error: boolean }) => ({ +const useStyles = createStyles((theme, { error, isBlur }: { error: boolean; isBlur: boolean }) => ({ browser: { backgroundColor: theme.colorScheme === 'dark' ? colors.B15 : colors.B98, borderRadius: '8px', @@ -51,6 +51,7 @@ const useStyles = createStyles((theme, { error }: { error: boolean }) => ({ flex: 1, border: error ? `1px solid ${colors.error}` : 'none', position: 'relative', + filter: isBlur ? 'blur(2px)' : 'none', }, contentContainer: { padding: '24px', @@ -73,6 +74,12 @@ const useStyles = createStyles((theme, { error }: { error: boolean }) => ({ padding: '15px', textAlign: 'center', }, + overlayContainer: { + position: 'relative', + flex: 1, + display: 'flex', + flexDirection: 'column', + }, })); export const PreviewWeb = ({ @@ -96,10 +103,13 @@ export const PreviewWeb = ({ selectedLocale?: string; locales: any[]; }) => { - const { classes } = useStyles({ error: !!(error && error.template?.content && error.template?.content?.message) }); - const [isEditOverlayVisible, setIsEditOverlayVisible] = useState(false); + const { classes } = useStyles({ + error: !!(error && error.template?.content && error.template?.content?.message), + isBlur: isEditOverlayVisible, + }); + const handleMouseEnter = () => { if (showEditOverlay) { setIsEditOverlayVisible(true); @@ -161,33 +171,35 @@ export const PreviewWeb = ({
- -
- - +
+ + - - {isEditOverlayVisible && } +
+ + + + + ( +
+ + Oops! We've recognized some glitch in this HTML. Please give it another look! + +
+ )} + resetKeys={[content]} + > + + <> + +
- ( -
- - Oops! We've recognized some glitch in this HTML. Please give it another look! - -
+ {error && error.template?.content && error.template?.content?.message && ( + {error?.template?.content?.message} )} - resetKeys={[content]} - > - - <> - -
- - {error && error.template?.content && error.template?.content?.message && ( - {error?.template?.content?.message} - )} -
+ +
diff --git a/apps/web/src/components/workflow/Preview/Email/Skeleton/ContentSkeleton.tsx b/apps/web/src/components/workflow/preview/email/Skeleton/ContentSkeleton.tsx similarity index 100% rename from apps/web/src/components/workflow/Preview/Email/Skeleton/ContentSkeleton.tsx rename to apps/web/src/components/workflow/preview/email/Skeleton/ContentSkeleton.tsx diff --git a/apps/web/src/components/workflow/Preview/Email/Skeleton/HeaderSkeleton.tsx b/apps/web/src/components/workflow/preview/email/Skeleton/HeaderSkeleton.tsx similarity index 100% rename from apps/web/src/components/workflow/Preview/Email/Skeleton/HeaderSkeleton.tsx rename to apps/web/src/components/workflow/preview/email/Skeleton/HeaderSkeleton.tsx diff --git a/apps/web/src/components/workflow/Preview/Email/Skeleton/index.ts b/apps/web/src/components/workflow/preview/email/Skeleton/index.ts similarity index 100% rename from apps/web/src/components/workflow/Preview/Email/Skeleton/index.ts rename to apps/web/src/components/workflow/preview/email/Skeleton/index.ts diff --git a/apps/web/src/components/workflow/Preview/Email/index.ts b/apps/web/src/components/workflow/preview/email/index.ts similarity index 100% rename from apps/web/src/components/workflow/Preview/Email/index.ts rename to apps/web/src/components/workflow/preview/email/index.ts diff --git a/apps/web/src/components/workflow/preview/index.ts b/apps/web/src/components/workflow/preview/index.ts new file mode 100644 index 00000000000..c9a99465711 --- /dev/null +++ b/apps/web/src/components/workflow/preview/index.ts @@ -0,0 +1,4 @@ +export * from './chat'; +export * from './email'; +export * from './push'; +export * from './sms'; diff --git a/apps/web/src/components/workflow/preview/push/Content.styles.ts b/apps/web/src/components/workflow/preview/push/Content.styles.ts new file mode 100644 index 00000000000..6e780b7f7d2 --- /dev/null +++ b/apps/web/src/components/workflow/preview/push/Content.styles.ts @@ -0,0 +1,35 @@ +import styled from '@emotion/styled'; +import { colors } from '@novu/design-system'; + +export const ContentStyled = styled.div<{ isBlur: boolean }>` + display: flex; + padding: 1rem; + flex-direction: column; + align-items: flex-start; + gap: 1rem; + flex-shrink: 0; + border-radius: 0.75rem; + background: rgba(255, 255, 255, 0.6); + position: relative; + overflow: hidden; + ${({ isBlur }) => isBlur && 'filter: blur(2px)'}; +`; + +export const ContentWrapperStyled = styled.div` + padding: 0.5rem; + margin-top: 4.5rem; +`; + +export const ContentAndOVerlayWrapperStyled = styled.div<{ isError: boolean }>` + overflow: hidden; + position: relative; + border-radius: 0.75rem; + ${({ isError }) => isError && `border: 1px solid ${colors.error};`} +`; + +export const ContentHeaderStyled = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; +`; diff --git a/apps/web/src/components/workflow/preview/push/Content.tsx b/apps/web/src/components/workflow/preview/push/Content.tsx new file mode 100644 index 00000000000..fa675a10671 --- /dev/null +++ b/apps/web/src/components/workflow/preview/push/Content.tsx @@ -0,0 +1,127 @@ +import styled from '@emotion/styled'; +import { Flex, Group, Skeleton, Stack, useMantineColorScheme } from '@mantine/core'; +import { colors, Text } from '@novu/design-system'; +import { useFormContext, useWatch } from 'react-hook-form'; + +import { useHover } from '../../../../hooks'; +import { IForm } from '../../../../pages/templates/components/formTypes'; +import { usePreviewPushTemplate } from '../../../../pages/templates/hooks/usePreviewPushTemplate'; +import { useStepFormPath } from '../../../../pages/templates/hooks/useStepFormPath'; +import { useTemplateLocales } from '../../../../pages/templates/hooks/useTemplateLocales'; +import { LocaleSelect, PreviewEditOverlay } from '../common'; +import { NovuGreyIcon } from '../common/NovuGreyIcon'; +import { + ContentAndOVerlayWrapperStyled, + ContentHeaderStyled, + ContentStyled, + ContentWrapperStyled, +} from './Content.styles'; + +export default function Content() { + const { isHovered, onMouseEnter, onMouseLeave } = useHover(); + const { colorScheme } = useMantineColorScheme(); + const isDark = colorScheme === 'dark'; + + const { control } = useFormContext(); + const path = useStepFormPath(); + + const title = useWatch({ + name: `${path}.template.title`, + control, + }); + + const content = useWatch({ + name: `${path}.template.content`, + control, + }); + + const { selectedLocale, locales, areLocalesLoading, onLocaleChange } = useTemplateLocales({ + content: content as string, + title: title, + }); + + const { isPreviewLoading, parsedPreviewState, templateError } = usePreviewPushTemplate(selectedLocale); + + return ( + + + + + + {isHovered && } + + {isPreviewLoading ? ( + + ) : ( + <> + + + + + Your App + + + now + +
+ + {parsedPreviewState.title || ''} + + + {parsedPreviewState.content || ''} + +
+ + )} +
+
+ + {templateError && !isPreviewLoading && {templateError}} +
+ ); +} + +const Skeletons = () => { + return ( + <> + + + + ); +}; + +const HeaderSkeleton = () => { + return ( + + + + + + + + ); +}; + +const ContentSkeleton = () => { + return ( + + + + + ); +}; + +const SkeletonStyled = styled(Skeleton)` + &::before { + background: rgba(0, 0, 0, 0.08); + } + + &::after { + background: rgba(0, 0, 0, 0.08); + } +`; diff --git a/apps/web/src/components/workflow/preview/push/PushPreview.tsx b/apps/web/src/components/workflow/preview/push/PushPreview.tsx new file mode 100644 index 00000000000..b9335b06a7f --- /dev/null +++ b/apps/web/src/components/workflow/preview/push/PushPreview.tsx @@ -0,0 +1,12 @@ +import { MobileSimulator } from '../common'; +import Content from './Content'; + +export function PushPreview() { + return ( +
+ + + +
+ ); +} diff --git a/apps/web/src/components/workflow/preview/push/index.ts b/apps/web/src/components/workflow/preview/push/index.ts new file mode 100644 index 00000000000..d1231f30f2e --- /dev/null +++ b/apps/web/src/components/workflow/preview/push/index.ts @@ -0,0 +1 @@ +export * from './PushPreview'; diff --git a/apps/web/src/pages/templates/components/SmsBubble.styles.tsx b/apps/web/src/components/workflow/preview/sms/SmsBubble.styles.tsx similarity index 100% rename from apps/web/src/pages/templates/components/SmsBubble.styles.tsx rename to apps/web/src/components/workflow/preview/sms/SmsBubble.styles.tsx diff --git a/apps/web/src/pages/templates/components/SmsBubble.tsx b/apps/web/src/components/workflow/preview/sms/SmsBubble.tsx similarity index 100% rename from apps/web/src/pages/templates/components/SmsBubble.tsx rename to apps/web/src/components/workflow/preview/sms/SmsBubble.tsx diff --git a/apps/web/src/pages/templates/components/SmsPreview.tsx b/apps/web/src/components/workflow/preview/sms/SmsPreview.tsx similarity index 60% rename from apps/web/src/pages/templates/components/SmsPreview.tsx rename to apps/web/src/components/workflow/preview/sms/SmsPreview.tsx index 2bb277c5c80..22d84cc4c9a 100644 --- a/apps/web/src/pages/templates/components/SmsPreview.tsx +++ b/apps/web/src/components/workflow/preview/sms/SmsPreview.tsx @@ -1,11 +1,12 @@ import styled from '@emotion/styled'; import { colors } from '@novu/design-system'; - -import { LocaleSelect } from '../../../components/workflow/Preview/common'; -import { usePreviewSmsTemplate } from '../hooks/usePreviewSmsTemplate'; -import { useNavigateToStepEditor } from '../hooks/useNavigateToStepEditor'; -import { useTemplateLocales } from '../hooks/useTemplateLocales'; -import { MobileSimulator } from './phone-simulator'; +import { useFormContext, useWatch } from 'react-hook-form'; +import { IForm } from '../../../../pages/templates/components/formTypes'; +import { useNavigateToStepEditor } from '../../../../pages/templates/hooks/useNavigateToStepEditor'; +import { usePreviewSmsTemplate } from '../../../../pages/templates/hooks/usePreviewSmsTemplate'; +import { useStepFormPath } from '../../../../pages/templates/hooks/useStepFormPath'; +import { useTemplateLocales } from '../../../../pages/templates/hooks/useTemplateLocales'; +import { LocaleSelect, MobileSimulator } from '../common'; import { SmsBubble } from './SmsBubble'; const BodyContainer = styled.div` @@ -27,11 +28,22 @@ const LocaleSelectStyled = styled(LocaleSelect)` export const SmsPreview = () => { const { navigateToStepEditor } = useNavigateToStepEditor(); - const { selectedLocale, locales, areLocalesLoading, onLocaleChange } = useTemplateLocales(); + + const { control } = useFormContext(); + const path = useStepFormPath(); + const templateContent = useWatch({ + name: `${path}.template.content`, + control, + }); + + const { selectedLocale, locales, areLocalesLoading, onLocaleChange } = useTemplateLocales({ + content: templateContent as string, + }); + const { isPreviewContentLoading, previewContent, templateContentError } = usePreviewSmsTemplate(selectedLocale); return ( - + { switch (channel) { @@ -25,7 +24,7 @@ const PreviewComponent = ({ channel }: { channel: StepTypeEnum }) => { return ; case StepTypeEnum.PUSH: - return <>PUSH; + return ; case StepTypeEnum.DELAY: return <>DELAY; diff --git a/apps/web/src/pages/templates/components/email-editor/EmailMessagesCards.tsx b/apps/web/src/pages/templates/components/email-editor/EmailMessagesCards.tsx index 8cf1600770c..f1f69dbec90 100644 --- a/apps/web/src/pages/templates/components/email-editor/EmailMessagesCards.tsx +++ b/apps/web/src/pages/templates/components/email-editor/EmailMessagesCards.tsx @@ -2,13 +2,13 @@ import { useState } from 'react'; import { EmailContentCard } from './EmailContentCard'; import { useAuthContext } from '../../../../components/providers/AuthProvider'; import { When } from '../../../../components/utils/When'; -import { EmailPreview } from '../../../../components/workflow/Preview'; +import { EmailPreview } from '../../../../components/workflow/preview'; import { EditorPreviewSwitch } from '../EditorPreviewSwitch'; import { Grid, SegmentedControl, useMantineTheme } from '@mantine/core'; import { TestSendEmail } from './TestSendEmail'; import { colors } from '@novu/design-system'; -import { MobileIcon } from '../../../../components/workflow/Preview/Email/PreviewSegment/MobileIcon'; -import { WebIcon } from '../../../../components/workflow/Preview/Email/PreviewSegment/WebIcon'; +import { MobileIcon } from '../../../../components/workflow/preview/email/PreviewSegment/MobileIcon'; +import { WebIcon } from '../../../../components/workflow/preview/email/PreviewSegment/WebIcon'; import { useHotkeys } from '@mantine/hooks'; import { VariablesManagement } from './variables-management/VariablesManagement'; import { diff --git a/apps/web/src/pages/templates/hooks/usePreviewPushTemplate.ts b/apps/web/src/pages/templates/hooks/usePreviewPushTemplate.ts new file mode 100644 index 00000000000..1789389f350 --- /dev/null +++ b/apps/web/src/pages/templates/hooks/usePreviewPushTemplate.ts @@ -0,0 +1,59 @@ +import { useDataRef } from '@novu/shared-web'; +import { useEffect, useState } from 'react'; +import { useFormContext, useWatch } from 'react-hook-form'; +import { usePreviewPush } from '../../../api/hooks'; +import { useProcessVariables } from '../../../hooks'; +import { IForm } from '../components/formTypes'; +import { useStepFormCombinedErrors } from './useStepFormCombinedErrors'; +import { useStepFormPath } from './useStepFormPath'; + +export const usePreviewPushTemplate = (locale?: string) => { + const { control } = useFormContext(); + const path = useStepFormPath(); + const templateError = useStepFormCombinedErrors(); + const templateContent = useWatch({ + name: `${path}.template.content`, + control, + }); + + const templateTitle = useWatch({ + name: `${path}.template.title`, + control, + }); + + const templateVariables = useWatch({ + name: `${path}.template.variables`, + control, + }); + + const [parsedPreviewState, setParsedPreviewState] = useState({ + title: templateTitle, + content: templateContent as string, + }); + + const processedVariables = useProcessVariables(templateVariables); + + const previewData = useDataRef({ templateContent, templateTitle, processedVariables }); + + const { isLoading, getPushPreview } = usePreviewPush({ + onSuccess: (result) => { + setParsedPreviewState({ + content: result.content, + title: result.title, + }); + }, + }); + + useEffect(() => { + getPushPreview({ + locale, + content: previewData.current.templateContent, + payload: previewData.current.processedVariables, + title: previewData.current.templateTitle, + }); + }, [getPushPreview, locale, previewData]); + + const isPreviewLoading = !templateError && isLoading; + + return { parsedPreviewState, isPreviewLoading, templateError }; +}; diff --git a/apps/web/src/pages/templates/hooks/useStepFormCombinedErrors.ts b/apps/web/src/pages/templates/hooks/useStepFormCombinedErrors.ts new file mode 100644 index 00000000000..dd2abb18fae --- /dev/null +++ b/apps/web/src/pages/templates/hooks/useStepFormCombinedErrors.ts @@ -0,0 +1,17 @@ +import { formatErrorMessage, mapStepErrors } from '../shared/errors'; +import { useStepFormErrors } from './useStepFormErrors'; + +export const useStepFormCombinedErrors = () => { + const error = useStepFormErrors(); + + if (!error || !error?.template) { + return ''; + } + + const stepErrorsArray = error?.template; + + const formattedError = mapStepErrors(stepErrorsArray); + const combinedError = formatErrorMessage(formattedError); + + return combinedError; +}; diff --git a/apps/web/src/pages/templates/hooks/useTemplateLocales.ts b/apps/web/src/pages/templates/hooks/useTemplateLocales.ts index a48603e11e7..22b420f2ee3 100644 --- a/apps/web/src/pages/templates/hooks/useTemplateLocales.ts +++ b/apps/web/src/pages/templates/hooks/useTemplateLocales.ts @@ -1,27 +1,28 @@ import { useEffect, useState } from 'react'; -import { useFormContext, useWatch } from 'react-hook-form'; import { useGetLocalesFromContent } from '../../../api/hooks'; import { useAuthController, useDataRef } from '../../../hooks'; -import { IForm } from '../components/formTypes'; -import { useStepFormPath } from './useStepFormPath'; -export const useTemplateLocales = () => { +export const useTemplateLocales = ({ content, title }: { content: string; title?: string }) => { const { organization } = useAuthController(); const [selectedLocale, setSelectedLocale] = useState(''); - const { control } = useFormContext(); - const path = useStepFormPath(); - const templateContent = useWatch({ - name: `${path}.template.content`, - control, - }); - const previewData = useDataRef({ templateContent }); + + const previewData = useDataRef({ content, title }); const { data: locales, isLoading: areLocalesLoading, getLocalesFromContent } = useGetLocalesFromContent(); useEffect(() => { + let combinedContent = previewData.current.content; + /* + * combining title and content to get locales based upon variables in both title and content + * The api is not concerned about the content type, it will parse the given string and return the locales + */ + if (previewData.current.title) { + combinedContent += ` ${previewData.current.title}`; + } + getLocalesFromContent({ - content: previewData.current.templateContent, + content: combinedContent, }); }, [getLocalesFromContent, previewData]); diff --git a/apps/web/src/pages/templates/shared/errors.ts b/apps/web/src/pages/templates/shared/errors.ts index d0cd9a203dd..c3b853dfad0 100644 --- a/apps/web/src/pages/templates/shared/errors.ts +++ b/apps/web/src/pages/templates/shared/errors.ts @@ -53,9 +53,7 @@ export function getStepErrors(index: number | string, stepsErrors?: FieldErrors< const stepErrors = stepsErrors[index]?.template; if (stepErrors) { - const keys = Object.keys(stepErrors); - - return keys.map((key) => stepErrors[key]?.message); + return mapStepErrors(stepErrors); } const digestMetadataErrors = stepsErrors[index]?.digestMetadata; @@ -73,6 +71,16 @@ export function getStepErrors(index: number | string, stepsErrors?: FieldErrors< return []; } +export function mapStepErrors(stepErrors) { + if (stepErrors) { + const keys = Object.keys(stepErrors); + + return keys.map((key) => stepErrors[key]?.message); + } + + return []; +} + export function getVariantErrors( stepIndex: number | string, stepsErrors?: FieldErrors['steps'] diff --git a/packages/application-generic/src/usecases/compile-step-template/compile-step-template.command.ts b/packages/application-generic/src/usecases/compile-step-template/compile-step-template.command.ts index 879ab7532d2..ee440ddf54b 100644 --- a/packages/application-generic/src/usecases/compile-step-template/compile-step-template.command.ts +++ b/packages/application-generic/src/usecases/compile-step-template/compile-step-template.command.ts @@ -6,6 +6,9 @@ export class CompileStepTemplateCommand extends EnvironmentWithUserCommand { @IsDefined() content: string; + @IsOptional() + title?: string; + @IsDefined() payload: any; // eslint-disable-line @typescript-eslint/no-explicit-any diff --git a/packages/application-generic/src/usecases/compile-step-template/compile-step-template.usecase.ts b/packages/application-generic/src/usecases/compile-step-template/compile-step-template.usecase.ts index f1ba936caa9..c2a5bd3a253 100644 --- a/packages/application-generic/src/usecases/compile-step-template/compile-step-template.usecase.ts +++ b/packages/application-generic/src/usecases/compile-step-template/compile-step-template.usecase.ts @@ -44,15 +44,21 @@ export class CompileStepTemplate extends CompileTemplateBase { let content = ''; + let title: string | undefined = undefined; + try { content = await this.compileStepTemplate(command.content, payload); + + if (command.title) { + title = await this.compileStepTemplate(command.title, payload); + } } catch (e: any) { throw new ApiException( e?.message || `Message content could not be generated` ); } - return { content }; + return { content, title }; } private async compileStepTemplate(