diff --git a/apps/web/src/constants/experimentsConstants.ts b/apps/web/src/constants/experimentsConstants.ts new file mode 100644 index 00000000000..2720076d019 --- /dev/null +++ b/apps/web/src/constants/experimentsConstants.ts @@ -0,0 +1 @@ +export const OnboardingExperimentV2ModalKey = 'nv_onboarding_modal'; diff --git a/apps/web/src/pages/auth/components/QuestionnaireForm.tsx b/apps/web/src/pages/auth/components/QuestionnaireForm.tsx index 1b9f2404cb7..d698ba91821 100644 --- a/apps/web/src/pages/auth/components/QuestionnaireForm.tsx +++ b/apps/web/src/pages/auth/components/QuestionnaireForm.tsx @@ -26,6 +26,7 @@ import { useVercelIntegration, useVercelParams } from '../../../hooks'; import { ROUTES } from '../../../constants/routes.enum'; import { DynamicCheckBox } from './dynamic-checkbox/DynamicCheckBox'; import styled from '@emotion/styled/macro'; +import { OnboardingExperimentV2ModalKey } from '../../../constants/experimentsConstants'; export function QuestionnaireForm() { const [loading, setLoading] = useState(); @@ -66,7 +67,7 @@ export function QuestionnaireForm() { const createDto: ICreateOrganizationDto = { ...rest, name: organizationName }; const organization = await createOrganizationMutation(createDto); const organizationResponseToken = await api.post(`/v1/auth/organizations/${organization._id}/switch`, {}); - + localStorage.setItem(OnboardingExperimentV2ModalKey, 'true'); setToken(organizationResponseToken); } diff --git a/apps/web/src/pages/quick-start/components/OnboardingExperimentModal.tsx b/apps/web/src/pages/quick-start/components/OnboardingExperimentModal.tsx new file mode 100644 index 00000000000..4aaf6e36e00 --- /dev/null +++ b/apps/web/src/pages/quick-start/components/OnboardingExperimentModal.tsx @@ -0,0 +1,125 @@ +import { useState } from 'react'; +import { Modal, useMantineTheme, Grid } from '@mantine/core'; + +import styled from '@emotion/styled'; +import { colors, shadows, Title, Button } from '@novu/design-system'; +import { useAuthContext, useSegment } from '@novu/shared-web'; +import { useCreateOnboardingExperimentWorkflow } from '../../../api/hooks/notification-templates/useCreateOnboardingExperimentWorkflow'; +import { OnboardingExperimentV2ModalKey } from '../../../constants/experimentsConstants'; +import { OnBoardingAnalyticsEnum } from '../consts'; + +export function OnboardingExperimentModal() { + const [opened, setOpened] = useState(true); + const theme = useMantineTheme(); + const segment = useSegment(); + const { currentOrganization } = useAuthContext(); + const { + createOnboardingExperimentWorkflow, + isLoading: IsCreateOnboardingExpWorkflowLoading, + isDisabled: isIsCreateOnboardingExpWorkflowDisabled, + } = useCreateOnboardingExperimentWorkflow(); + const handleOnClose = () => { + setOpened(true); + }; + + return ( + What would you like to do first?} + sx={{ backdropFilter: 'blur(10px)' }} + shadow={theme.colorScheme === 'dark' ? shadows.dark : shadows.medium} + radius="md" + size="lg" + onClose={handleOnClose} + centered + withCloseButton={false} + > + + + + Send test notification + Learn how to setup a workflow and send your first email notification. + { + segment.track(OnBoardingAnalyticsEnum.ONBOARDING_EXPERIMENT_TEST_NOTIFICATION, { + action: 'Modal - Send test notification', + experiment_id: '2024-w15-onb', + _organization: currentOrganization?._id, + }); + localStorage.removeItem(OnboardingExperimentV2ModalKey); + createOnboardingExperimentWorkflow(); + }} + > + Send test notification now + + + + + + Look around + Start exploring the Novu app on your own terms + { + segment.track(OnBoardingAnalyticsEnum.ONBOARDING_EXPERIMENT_TEST_NOTIFICATION, { + action: 'Modal - Get started', + experiment_id: '2024-w15-onb', + _organization: currentOrganization?._id, + }); + localStorage.removeItem(OnboardingExperimentV2ModalKey); + setOpened(false); + }} + > + Get started + + + + + + ); +} + +const ChannelCard = styled.div` + display: flex; + justify-content: space-between; + flex-direction: column; + max-width: 230px; +`; + +const TitleRow = styled.div` + display: flex; + align-items: center; + font-size: 20px; + line-height: 32px; + margin-bottom: 8px; +`; + +const Description = styled.div` + flex: auto; + font-size: 16px; + line-height: 20px; + margin-bottom: 20px; + color: ${colors.B60}; + height: 60px; +`; + +const StyledButton = styled(Button)` + width: fit-content; + outline: none; +`; diff --git a/apps/web/src/pages/quick-start/steps/GetStarted.tsx b/apps/web/src/pages/quick-start/steps/GetStarted.tsx index 74618334d5c..4a01f78f9b0 100644 --- a/apps/web/src/pages/quick-start/steps/GetStarted.tsx +++ b/apps/web/src/pages/quick-start/steps/GetStarted.tsx @@ -9,6 +9,9 @@ import { ChannelsConfiguration } from '../components/ChannelsConfiguration'; import { GetStartedLayout } from '../components/layout/GetStartedLayout'; import { NavButton } from '../components/NavButton'; import { getStartedSteps, OnBoardingAnalyticsEnum } from '../consts'; +import { OnboardingExperimentModal } from '../components/OnboardingExperimentModal'; +import { useAuthContext } from '@novu/shared-web'; +import { OnboardingExperimentV2ModalKey } from '../../../constants/experimentsConstants'; const ChannelsConfigurationHolder = styled.div` display: flex; @@ -25,16 +28,25 @@ const ChannelsConfigurationHolder = styled.div` export function GetStarted() { const segment = useSegment(); + const { currentOrganization } = useAuthContext(); const [clickedChannel, setClickedChannel] = useState<{ open: boolean; channelType?: ChannelTypeEnum; }>({ open: false }); + const isOnboardingModalEnabled = localStorage.getItem(OnboardingExperimentV2ModalKey) === 'true'; + const onIntegrationModalClose = () => setClickedChannel({ open: false }); useEffect(() => { segment.track(OnBoardingAnalyticsEnum.CONFIGURE_PROVIDER_VISIT); - }, [segment]); + if (isOnboardingModalEnabled) { + segment.track('Welcome modal open - [Onboarding]', { + experiment_id: '2024-w15-onb', + _organization: currentOrganization?._id, + }); + } + }, [currentOrganization?._id, isOnboardingModalEnabled, segment]); function handleOnClick() { segment.track(OnBoardingAnalyticsEnum.CONFIGURE_PROVIDER_NAVIGATION_NEXT_PAGE_CLICK); @@ -60,6 +72,7 @@ export function GetStarted() { /> + {isOnboardingModalEnabled && } ); } diff --git a/apps/web/src/pages/templates/components/TriggerSnippetTabs.tsx b/apps/web/src/pages/templates/components/TriggerSnippetTabs.tsx index 19c7e846d5a..e7d31ec13f7 100644 --- a/apps/web/src/pages/templates/components/TriggerSnippetTabs.tsx +++ b/apps/web/src/pages/templates/components/TriggerSnippetTabs.tsx @@ -66,8 +66,7 @@ novu.trigger('${identifier}', ${JSON.stringify( 2 ) .replace(/"([^"]+)":/g, '$1:') - .replace(/"/g, "'") - .replaceAll('\n', '\n ')}); + .replace(/"/g, "'")}); `; return ( @@ -85,19 +84,19 @@ export const getCurlTriggerSnippet = ( snippet?: Record ) => { const curlSnippet = `curl --location --request POST '${API_ROOT}/v1/events/trigger' \\ - --header 'Authorization: ApiKey ' \\ - --header 'Content-Type: application/json' \\ - --data-raw '${JSON.stringify( - { - name: identifier, - to, - payload, - overrides, - ...snippet, - }, - null, - 2 - ).replaceAll('\n', '\n ')}' +--header 'Authorization: ApiKey ' \\ +--header 'Content-Type: application/json' \\ +--data-raw '${JSON.stringify( + { + name: identifier, + to, + payload, + overrides, + ...snippet, + }, + null, + 2 + )}' `; return ( diff --git a/apps/web/src/pages/templates/workflow/WorkflowEditor.tsx b/apps/web/src/pages/templates/workflow/WorkflowEditor.tsx index fcd04da28a3..61b3ac9c551 100644 --- a/apps/web/src/pages/templates/workflow/WorkflowEditor.tsx +++ b/apps/web/src/pages/templates/workflow/WorkflowEditor.tsx @@ -293,7 +293,7 @@ const WorkflowEditor = () => { }} data-test-id="get-snippet-btn" > - {tagsIncludesOnboarding ? 'Test Notification Now' : 'Get Snippet'} + Trigger Notification