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 echo usecase and more tracking for getting started #5451

Closed
wants to merge 12 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { colors, Text, IconMenuBook } from '@novu/design-system';

import { Link } from '../consts/shared';
import { OnboardingUseCasesTabsEnum } from '../consts/OnboardingUseCasesTabsEnum';
import * as capitalize from 'lodash.capitalize';

interface IAdditionInformationLinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
channel: OnboardingUseCasesTabsEnum;
Expand All @@ -12,7 +13,7 @@ export function AdditionInformationLink({ channel, ...linkProps }: IAdditionInfo
return (
<StyledLink {...linkProps}>
<IconMenuBook />
<StyledText>Learn about {channel}</StyledText>
<StyledText>Learn about {channel === OnboardingUseCasesTabsEnum.ECHO ? capitalize(channel) : channel}</StyledText>
</StyledLink>
);
}
Expand Down
45 changes: 45 additions & 0 deletions apps/web/src/pages/get-started/components/CodeSnippet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Input, inputStyles } from '@novu/design-system';
import { useClipboard } from '@mantine/hooks';
import { css, cx } from '../../../styled-system/css';
import { ClipboardIconButton } from '../../../components';

const codeValueInputClassName = css({
'& input': {
border: 'none !important',
background: 'surface.popover !important',
color: 'typography.text.secondary !important',
fontFamily: 'mono !important',
davidsoderberg marked this conversation as resolved.
Show resolved Hide resolved
},
});

interface ICodeSnippetProps {
command: string;
onClick?: () => void;
className?: string;
'data-test-id'?: string;
}

/**
* Read-only code snippet with copy-paste functionality
*/
export const CodeSnippet = ({ command, onClick, className, ...props }: ICodeSnippetProps) => {
const { copy, copied } = useClipboard();

const handleCopy = () => {
onClick?.();
copy(command);
};

return (
<Input
readOnly
className={cx(codeValueInputClassName, className)}
styles={inputStyles}
rightSection={
<ClipboardIconButton isCopied={copied} handleCopy={handleCopy} testId={'mail-server-domain-copy'} size={'16'} />
}
value={command}
data-test-id={props['data-test-id']}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ enum AnimationThemeEnum {
}

// uses `public` as the default base directory
const ROOT_ANIMATION_PATH = `animations/get-started`;
const ROOT_ANIMATION_PATH = `/animations/get-started`;
const STATE_MACHINE_NAME = 'SM';
const STATE_MACHINE_INPUT_NAME = 'theme';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { Digest, HalfClock, IconOutlineNotificationsActive, IconOutlinePublic, Translation } from '@novu/design-system';
import {
Digest,
HalfClock,
IconComputer,
IconOutlineNotificationsActive,
IconOutlinePublic,
Translation,
} from '@novu/design-system';
import { CSSProperties } from 'react';
import { OnboardingUseCasesTabsEnum } from '../../consts/OnboardingUseCasesTabsEnum';

Expand All @@ -10,6 +17,11 @@ export interface GetStartedTabConfig {
const ICON_STYLE: Partial<CSSProperties> = { height: 20, width: 20, marginBottom: '12px' };

export const TAB_CONFIGS: GetStartedTabConfig[] = [
{
value: OnboardingUseCasesTabsEnum.ECHO,
icon: <IconComputer style={ICON_STYLE} />,
title: 'Echo',
},
{
value: OnboardingUseCasesTabsEnum.IN_APP,
icon: <IconOutlineNotificationsActive style={ICON_STYLE} />,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { OnboardingWorkflowRouteEnum, OnboardingUseCase } from './types';
import { GetStartedAnimation } from '../components/GetStartedAnimation';
import { OpenWorkflowButton } from '../components/OpenWorkflowButton';
import { OnboardingUseCasesTabsEnum } from './OnboardingUseCasesTabsEnum';
import { StepTypeEnum } from '@novu/shared';

const USECASE_BLUEPRINT_IDENTIFIER = 'get-started-delay';

Expand All @@ -27,6 +28,8 @@ export const DelayUseCaseConst: OnboardingUseCase = {
href={ROUTES.INTEGRATIONS_CREATE}
target="_blank"
rel="noopener noreferrer"
event="Integration store"
channel={StepTypeEnum.DELAY}
/>
<StepText>.</StepText>
</StepDescription>
Expand Down Expand Up @@ -91,6 +94,8 @@ export const DelayUseCaseConst: OnboardingUseCase = {
href={ROUTES.ACTIVITIES}
target="_blank"
rel="noopener noreferrer"
event='Discover "activity feed"'
channel={StepTypeEnum.DELAY}
/>
<StepText>
to monitor notifications activity and see potential issues with a specific provider or channel.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { OpenWorkflowButton } from '../components/OpenWorkflowButton';
import { OnboardingUseCasesTabsEnum } from './OnboardingUseCasesTabsEnum';
import { DigestPlaygroundView } from './DigestUsecasePlaygroundView.const';
import { GetStartedTabsViewsEnum } from './GetStartedTabsViewsEnum';
import { StepTypeEnum } from '@novu/shared';

const USECASE_BLUEPRINT_IDENTIFIER = 'get-started-digest';

Expand All @@ -30,6 +31,8 @@ export const DigestUseCaseConst: OnboardingUseCase = {
href={ROUTES.INTEGRATIONS_CREATE}
target="_blank"
rel="noopener noreferrer"
event="Integration store"
channel={StepTypeEnum.DIGEST}
/>
<StepText>.</StepText>
</StepDescription>
Expand Down Expand Up @@ -97,6 +100,8 @@ export const DigestUseCaseConst: OnboardingUseCase = {
href={ROUTES.ACTIVITIES}
target="_blank"
rel="noopener noreferrer"
event='Discover "activity feed"'
channel={StepTypeEnum.DIGEST}
/>
<StepText>
to monitor notifications activity and see potential issues with a specific provider or channel.
Expand Down
91 changes: 91 additions & 0 deletions apps/web/src/pages/get-started/consts/EchoUseCase.const.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { StepDescription, StepText } from './shared';
import { OnboardingUseCase } from './types';
import { OnboardingUseCasesTabsEnum } from './OnboardingUseCasesTabsEnum';
import { useSegment } from '@novu/shared-web';
import { CodeSnippet } from '../components/CodeSnippet';
import { css } from '../../../styled-system/css';

const COMMAND = 'npx novu-labs@latest echo';

const EchoCodeSnippet = () => {
const segment = useSegment();

return (
<CodeSnippet
davidsoderberg marked this conversation as resolved.
Show resolved Hide resolved
command={COMMAND}
// value from designs
className={css({ maxW: '400px' })}
onClick={() => {
segment.track(`Copy echo command - [Get Started]`);
}}
/>
);
};

export const EchoUseCaseConst: OnboardingUseCase = {
title: 'Echo notifications center',
type: OnboardingUseCasesTabsEnum.ECHO,
description:
'Novu Echo SDK allows you to write notification workflows in your codebase' +
' locally right in your IDE as well as preview and edit the channel specific content in real-time.' +
' You can use Echo with React Email, MJML, or any other template generator.',
useCaseLink: 'https://docs.novu.co/echo/quickstart',
steps: [
{
title: 'Configure endpoint',
Description: function () {
return (
<StepDescription>
<StepText>To get started, open your terminal and launch the Dev Studio.</StepText>
<EchoCodeSnippet />
</StepDescription>
);
},
},
{
title: 'Create a workflow',
Description: function () {
return (
<StepDescription>
<StepText>
Create type-safe, validated, and version-controlled Workflows
<br /> with code, at the heart of event-driven system notifications.
davidsoderberg marked this conversation as resolved.
Show resolved Hide resolved
</StepText>
</StepDescription>
);
},
},
{
title: 'Create a step',
Description: function () {
return (
<StepDescription>
<StepText>
Steps send notifications to subscribers. Each step can have
<br /> customized content, aligning it with the provider's specifications.
</StepText>
</StepDescription>
);
},
},
{
title: 'Sync with Novu cloud',
davidsoderberg marked this conversation as resolved.
Show resolved Hide resolved
Description: function () {
return (
<StepDescription>
<StepText>
Echo is for building and debugging, when Novu Cloud handling all
<br /> logic and provider connections.
</StepText>
</StepDescription>
);
},
},
{
title: 'You’re ready to send notifications',
Description: function () {
return null;
},
},
],
};
12 changes: 10 additions & 2 deletions apps/web/src/pages/get-started/consts/InAppUseCase.const.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ChannelTypeEnum } from '@novu/shared';
import { ChannelTypeEnum, StepTypeEnum } from '@novu/shared';
import { ROUTES } from '@novu/shared-web';

import { useGetIntegrationsByChannel } from '../../integrations/useGetIntegrationsByChannel';
Expand Down Expand Up @@ -34,7 +34,13 @@ export const InAppUseCaseConst: OnboardingUseCase = {

return (
<StepDescription>
<GetStartedLink href={getInAppIntegrationUrl()} target="_blank" rel="noopener noreferrer">
<GetStartedLink
event="Create In-app provider"
href={getInAppIntegrationUrl()}
target="_blank"
rel="noopener noreferrer"
channel={StepTypeEnum.IN_APP}
>
Create In-app provider
</GetStartedLink>
<StepText>
Expand Down Expand Up @@ -85,6 +91,8 @@ export const InAppUseCaseConst: OnboardingUseCase = {
href={ROUTES.ACTIVITIES}
target="_blank"
rel="noopener noreferrer"
event='Discover "activity feed"'
channel={StepTypeEnum.IN_APP}
/>
<StepText>
to monitor notifications activity and see potential issues with a specific provider or channel.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ export const MultiChannelUseCaseConst: OnboardingUseCase = {
<StepText>
Novu has set up trial email and SMS providers for you. To expand your options, add more providers in the
</StepText>
<GetStartedLink href={ROUTES.INTEGRATIONS_CREATE} target="_blank" rel="noopener noreferrer">
<GetStartedLink
href={ROUTES.INTEGRATIONS_CREATE}
event="Integration store (multi-channel)"
target="_blank"
rel="noopener noreferrer"
>
{' Integration store'}
</GetStartedLink>
<StepText>.</StepText>
Expand Down Expand Up @@ -74,6 +79,7 @@ export const MultiChannelUseCaseConst: OnboardingUseCase = {
href={ROUTES.ACTIVITIES}
target="_blank"
rel="noopener noreferrer"
event='Discover "activity feed" (multi-channel)'
/>
<StepText>
to monitor notifications activity and see potential issues with a specific provider or channel.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export enum OnboardingUseCasesTabsEnum {
ECHO = 'echo',
IN_APP = 'in-app',
DIGEST = 'digest',
DELAY = 'delay',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ export const TranslationUseCaseConst: OnboardingUseCase = {
<StepText>
Novu has set up trial email and SMS providers for you. To expand your options, add more providers in the
</StepText>
<GetStartedLink href={ROUTES.INTEGRATIONS_CREATE} target="_blank" rel="noopener noreferrer">
<GetStartedLink
event="Integration store (translation)"
href={ROUTES.INTEGRATIONS_CREATE}
target="_blank"
rel="noopener noreferrer"
>
{' '}
Integration store
</GetStartedLink>
Expand All @@ -41,6 +46,7 @@ export const TranslationUseCaseConst: OnboardingUseCase = {
href={ROUTES.TRANSLATIONS}
target="_blank"
rel="noopener noreferrer"
event="Translations page"
/>
<StepText>.</StepText>
</StepDescription>
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/pages/get-started/consts/UseCases.const.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import { DelayUseCaseConst } from './DelayUseCase.const';
import { TranslationUseCaseConst } from './TranslationUseCase.const';
import { DigestUseCaseConst } from './DigestUseCase.const';
import { OnboardingUseCasesTabsEnum } from './OnboardingUseCasesTabsEnum';
import { EchoUseCaseConst } from './EchoUseCase.const';

export const UseCasesConst: OnboardingUseCases = {
[OnboardingUseCasesTabsEnum.IN_APP]: InAppUseCaseConst,
[OnboardingUseCasesTabsEnum.MULTI_CHANNEL]: MultiChannelUseCaseConst,
[OnboardingUseCasesTabsEnum.DELAY]: DelayUseCaseConst,
[OnboardingUseCasesTabsEnum.TRANSLATION]: TranslationUseCaseConst,
[OnboardingUseCasesTabsEnum.DIGEST]: DigestUseCaseConst,
[OnboardingUseCasesTabsEnum.ECHO]: EchoUseCaseConst,
};
24 changes: 22 additions & 2 deletions apps/web/src/pages/get-started/consts/shared.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import styled from '@emotion/styled';
import { Button, colors } from '@novu/design-system';
import { StepTypeEnum } from '@novu/shared';
import { useSegment } from '../../../components/providers/SegmentProvider';

export const StepText = styled.p`
Expand All @@ -16,8 +17,27 @@ export const StepButton = styled(Button)`
display: block;
`;

export function GetStartedLink({ children, ...linkProps }: React.AnchorHTMLAttributes<HTMLAnchorElement>) {
return <StyledLink {...linkProps}>{children}</StyledLink>;
export function GetStartedLink({
children,
...linkProps
}: React.AnchorHTMLAttributes<HTMLAnchorElement> & {
event?: string;
channel?: StepTypeEnum;
}) {
const segment = useSegment();

return (
<StyledLink
{...linkProps}
onClick={() => {
if (linkProps.event) {
segment.track(`${linkProps.event} - [Get Started]`, { href: linkProps.href, channel: linkProps.channel });
}
}}
>
{children}
</StyledLink>
);
}

export const StyledLink = styled.a`
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/pages/get-started/consts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export interface OnboardingUseCase {
steps: IOnboardingStep[];
useCaseLink: string;
type: OnboardingUseCasesTabsEnum;
Demo: React.ComponentType<UseCaseViewContext>;
Demo?: React.ComponentType<UseCaseViewContext>;
BottomSection?: React.ComponentType<UseCaseViewContext>;
views?: Partial<Record<GetStartedTabsViewsEnum, OnboardingUseCase>>;
}