Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add modal on get started page for onboarding experiment v2 #5401

Merged
merged 7 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/web/src/constants/experimentsConstants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const OnboardingExperimentV2ModalKey = 'nv_onboarding_modal';
3 changes: 2 additions & 1 deletion apps/web/src/pages/auth/components/QuestionnaireForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<boolean>();
Expand Down Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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);
};
Comment on lines +11 to +23
Copy link
Contributor

@coderabbitai coderabbitai bot Apr 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The modal component is initialized with the opened state set to true and never set to false in handleOnClose. This might prevent the modal from closing as expected. Consider setting opened to false in the handleOnClose function.

- setOpened(true);
+ setOpened(false);

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
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);
};
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(false);
};

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setOpened(false) in handleOnClose will allow the user to close modal by clicking outside the modal. As per experiment requirement, modal should be closed only when anyof the buttons are clicked

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jainpawan21, thank you for clarifying the intended behavior of the modal. It makes sense to restrict the modal closure to button clicks only, in line with the experiment's requirements. If there's anything else that needs to be adjusted or if you have further instructions, please let me know!


New Learnings Added
User: jainpawan21"
URL: https://github.com/novuhq/novu/pull/5401

Timestamp: 2024-04-12T10:38:28.678Z
Learning: The `OnboardingExperimentModal` should only be closed through button clicks as per the experiment requirements, not by clicking outside the modal.

Note: Learnings are effective only in the context of specific code segments. To apply general coding guidelines, please configure review instructions.


return (
<Modal
opened={opened}
overlayColor={theme.colorScheme === 'dark' ? colors.BGDark : colors.BGLight}
overlayOpacity={0.7}
styles={{
modal: {
backgroundColor: theme.colorScheme === 'dark' ? colors.B15 : colors.white,
width: '700px',
},
body: {
paddingTop: '5px',
paddingInline: '8px',
},
}}
title={<Title size={2}>What would you like to do first?</Title>}
sx={{ backdropFilter: 'blur(10px)' }}
shadow={theme.colorScheme === 'dark' ? shadows.dark : shadows.medium}
radius="md"
size="lg"
onClose={handleOnClose}
centered
withCloseButton={false}
>
<Grid>
<Grid.Col xs={12} md={6} mb={20}>
<ChannelCard>
<TitleRow> Send test notification</TitleRow>
<Description>Learn how to setup a workflow and send your first email notification.</Description>
<StyledButton
loading={IsCreateOnboardingExpWorkflowLoading}
disabled={isIsCreateOnboardingExpWorkflowDisabled}
pulse={true}
variant="gradient"
onClick={async () => {
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
</StyledButton>
</ChannelCard>
</Grid.Col>
<Grid.Col xs={12} md={6} mb={20}>
<ChannelCard>
<TitleRow> Look around</TitleRow>
<Description>Start exploring the Novu app on your own terms</Description>
<StyledButton
variant="outline"
onClick={async () => {
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
</StyledButton>
</ChannelCard>
</Grid.Col>
</Grid>
</Modal>
);
}

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;
`;
15 changes: 14 additions & 1 deletion apps/web/src/pages/quick-start/steps/GetStarted.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -60,6 +72,7 @@ export function GetStarted() {
/>
<ChannelsConfiguration setClickedChannel={setClickedChannel} />
</ChannelsConfigurationHolder>
{isOnboardingModalEnabled && <OnboardingExperimentModal />}
</GetStartedLayout>
);
}
Expand Down
29 changes: 14 additions & 15 deletions apps/web/src/pages/templates/components/TriggerSnippetTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ novu.trigger('${identifier}', ${JSON.stringify(
2
)
.replace(/"([^"]+)":/g, '$1:')
.replace(/"/g, "'")
.replaceAll('\n', '\n ')});
.replace(/"/g, "'")});
`;

return (
Expand All @@ -85,19 +84,19 @@ export const getCurlTriggerSnippet = (
snippet?: Record<string, unknown>
) => {
const curlSnippet = `curl --location --request POST '${API_ROOT}/v1/events/trigger' \\
--header 'Authorization: ApiKey <REPLACE_WITH_API_KEY>' \\
--header 'Content-Type: application/json' \\
--data-raw '${JSON.stringify(
{
name: identifier,
to,
payload,
overrides,
...snippet,
},
null,
2
).replaceAll('\n', '\n ')}'
--header 'Authorization: ApiKey <REPLACE_WITH_API_KEY>' \\
Copy link
Member Author

@jainpawan21 jainpawan21 Apr 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed code indentation for both nodejs and curl

Before 👇🏻

Screen.Recording.2024-04-12.at.5.10.02.PM.mov

After 👇🏻

Screen.Recording.2024-04-12.at.5.09.42.PM.mov

--header 'Content-Type: application/json' \\
--data-raw '${JSON.stringify(
{
name: identifier,
to,
payload,
overrides,
...snippet,
},
null,
2
)}'
`;

return (
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/pages/templates/workflow/WorkflowEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ const WorkflowEditor = () => {
}}
data-test-id="get-snippet-btn"
>
{tagsIncludesOnboarding ? 'Test Notification Now' : 'Get Snippet'}
Trigger Notification
</Button>
<Link data-test-id="settings-page" to="settings">
<Settings />
Expand Down
Loading