Skip to content

Commit

Permalink
refactor: getting started page (#5477)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidsoderberg authored and antonjoel82 committed May 9, 2024
1 parent cf0b9b2 commit a1d2c15
Show file tree
Hide file tree
Showing 19 changed files with 302 additions and 25 deletions.
16 changes: 12 additions & 4 deletions apps/web/src/pages/get-started/GetStartedPage.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
import { Center, Loader } from '@mantine/core';
import { colors } from '@novu/design-system';
import { useSegment } from '@novu/shared-web';
import { useEffect } from 'react';
import PageContainer from '../../components/layout/components/PageContainer';
import PageHeader from '../../components/layout/components/PageHeader';
import { useAuthContext } from '../../components/providers/AuthProvider';
import { usePageViewTracking } from '../../hooks/usePageViewTracking';
import { GetStartedTabs } from './components/get-started-tabs/GetStartedTabs';
import { useGetStartedTabs } from './components/get-started-tabs/useGetStartedTabs';
import { css } from '../../styled-system/css';
import { EchoTab } from './components/get-started-tabs/EchoTab';

const PAGE_TITLE = 'Get started';

export function GetStartedPage() {
const { currentOrganization } = useAuthContext();
const { currentTab, setTab } = useGetStartedTabs();
const segment = useSegment();

usePageViewTracking();

useEffect(() => {
segment.track('Page visit - [Get Started]');
}, [segment]);

return (
<PageContainer title={PAGE_TITLE}>
<PageHeader title={PAGE_TITLE} />
{currentOrganization ? (
<GetStartedTabs currentTab={currentTab} setTab={setTab} />
<EchoTab
className={css({ marginTop: '-100', paddingLeft: '150', paddingRight: '150', paddingBottom: '100' })}
/>
) : (
<Center>
<Loader color={colors.error} size={32} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { colors, Text, IconMenuBook } from '@novu/design-system';

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

interface IAdditionInformationLinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
channel: OnboardingUseCasesTabsEnum;
Expand All @@ -12,7 +14,12 @@ export function AdditionInformationLink({ channel, ...linkProps }: IAdditionInfo
return (
<StyledLink {...linkProps}>
<IconMenuBook />
<StyledText>Learn about {channel}</StyledText>
<When truthy={channel !== OnboardingUseCasesTabsEnum.ECHO}>
<StyledText>Learn about {channel}</StyledText>
</When>
<When truthy={channel === OnboardingUseCasesTabsEnum.ECHO}>
<StyledText>Learn more</StyledText>
</When>
</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',
},
});

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
142 changes: 142 additions & 0 deletions apps/web/src/pages/get-started/components/get-started-tabs/EchoTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { useSegment } from '@novu/shared-web';
import { css, cx } from '../../../../styled-system/css';
import { Flex, styled } from '../../../../styled-system/jsx';
import { OnboardingUseCasesTabsEnum } from '../../consts/OnboardingUseCasesTabsEnum';
import { AdditionInformationLink } from '../AdditionInformationLink';
import { CodeSnippet } from '../CodeSnippet';
import { text, title } from '../../../../styled-system/recipes';
import { IconCellTower, IconCloudQueue, IconCode, IconHealthAndSafety } from '@novu/design-system';
import { DOMAttributes, useEffect } from 'react';

type CustomElement<T> = Partial<T & DOMAttributes<T> & { children: any }>;

declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace JSX {
interface IntrinsicElements {
['nv-echo-terminal']: CustomElement<any>;
}
}
}

const link = 'https://docs.novu.co/echo/quickstart';

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

const Text = styled('p', text);
const Title = styled('h2', title);
const SubTitle = styled('h3', title);

const columnText = css({ textStyle: 'text.main', marginTop: '50', maxW: '214px' });
const columnIcon = css({ marginBottom: '50' });
const mainText = css({ textStyle: 'text.main', maxW: '645px' });

export const EchoTab = ({ className }: { className?: string }) => {
const segment = useSegment();

const handleDocsLinkClick = () => {
segment.track(`Additional Info Link - [Get Started]`, { href: link, tab: OnboardingUseCasesTabsEnum.ECHO });
};

useEffect(() => {
if (!document.getElementById('echo-terminal-loader')) {
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/gh/novuhq/docs/echo-terminal.min.js';
script.id = 'echo-terminal-loader';
document.body.appendChild(script);
}
}, []);

useEffect(() => {
const tabs = document.getElementsByClassName('nv-terminal-tab');
for (let i = 0; i < tabs.length; i++) {
const tab = tabs[i];
tab.addEventListener('click', () => {
segment.track(`Code snippet tab clicked - [Get Started]`, {
language: tab.innerHTML,
});
});
}
});

return (
<Flex className={className} direction="row" alignItems="center" gap="300">
<div>
<Title variant="section" className={css({ marginTop: '100' })}>
Create notification workflows as code
</Title>
<Text variant="secondary" className={cx(css({ marginTop: '50' }), mainText)}>
With Novu, you write notification workflows in your codebase locally right in your IDE and preview and edit
the channel-specific content in real-time.
</Text>
<Text variant="secondary" className={cx(css({ marginTop: '125', marginBottom: '150' }), mainText)}>
Integrate React.Email, MJML, and other template engines easily.
</Text>
<SubTitle variant="subsection">Try it out now</SubTitle>
<Text variant="secondary" className={mainText}>
Open your terminal and launch the development studio
</Text>
<CodeSnippet
command={COMMAND}
className={cx(
css({
maxW: '400px',
marginTop: '50',
marginBottom: '250',
}),
css({
'& input': {
color: 'white !important',
},
})
)}
onClick={() => {
segment.track(`Copy Echo command - [Get Started]`);
}}
/>
<div className={css({ marginBottom: '300' })}>
<AdditionInformationLink
channel={OnboardingUseCasesTabsEnum.ECHO}
href={link}
onClick={handleDocsLinkClick}
/>
</div>
<Flex gap="150">
<div>
<IconCode size={32} className={columnIcon} />
<SubTitle variant="subsection">Bring your own code</SubTitle>
<Text variant="secondary" className={columnText}>
Write the workflows as functions in your codebase, version, and manage via Git.
</Text>
</div>
<div>
<IconCellTower size={32} className={columnIcon} />
<SubTitle variant="subsection">Limitless integrations</SubTitle>
<Text variant="secondary" className={columnText}>
Use React.email, MJML, or fetch templates from Braze, Hubspot, Sendgrid, more…
</Text>
</div>
</Flex>
<Flex gap="150" className={css({ marginTop: '150' })}>
<div>
<IconHealthAndSafety size={32} className={columnIcon} />
<SubTitle variant="subsection">Type safety</SubTitle>
<Text variant="secondary" className={columnText}>
Bring your own JSON schemas for full, end-to-end validation and type safety.
</Text>
</div>
<div>
<IconCloudQueue size={32} className={columnIcon} />
<SubTitle variant="subsection">Sync and build visually</SubTitle>
<Text variant="secondary" className={columnText}>
Sync local workflows with Novu Cloud and ease collaboration using the web editor.
</Text>
</div>
</Flex>
</div>
<div>
<nv-echo-terminal></nv-echo-terminal>
</div>
</Flex>
);
};
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 @@ -7,7 +14,7 @@ export interface GetStartedTabConfig {
icon: JSX.Element;
title: string;
}
const ICON_STYLE: Partial<CSSProperties> = { height: 20, width: 20, marginBottom: '12px' };
export const ICON_STYLE: Partial<CSSProperties> = { height: 20, width: 20, marginBottom: '12px' };

export const TAB_CONFIGS: GetStartedTabConfig[] = [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Container, Tabs } from '@mantine/core';
import { IconConnectedTv } from '@novu/design-system';
import { Outlet } from 'react-router-dom';
import { OnboardingUseCasesTabsEnum } from '../../consts/OnboardingUseCasesTabsEnum';
import { UseCasesConst } from '../../consts/UseCases.const';
import { GetStartedTab } from '../../layout/GetStartedTab';
import { GetStartedTabConfig, TAB_CONFIGS } from './GetStartedTabs.const';
import { GetStartedTabConfig, ICON_STYLE, TAB_CONFIGS } from './GetStartedTabs.const';
import useStyles from './GetStartedTabs.style';
import { useGetStartedTabs } from './useGetStartedTabs';
import { useGetStartedTabView } from './useGetStartedTabView';
import { EchoTab } from './EchoTab';

interface IGetStartedTabsProps extends ReturnType<typeof useGetStartedTabs> {
tabConfigs?: GetStartedTabConfig[];
Expand All @@ -33,6 +35,13 @@ export const GetStartedTabs: React.FC<IGetStartedTabsProps> = ({ tabConfigs = TA
mb={15}
>
<Tabs.List>
<Tabs.Tab
key={`tab-${OnboardingUseCasesTabsEnum.ECHO}`}
value={OnboardingUseCasesTabsEnum.ECHO}
icon={<IconConnectedTv style={ICON_STYLE} />}
>
Workflows
</Tabs.Tab>
{tabConfigs.map(({ value, icon, title }) => (
<Tabs.Tab key={`tab-${value}`} value={value} icon={icon}>
{title}
Expand All @@ -44,6 +53,9 @@ export const GetStartedTabs: React.FC<IGetStartedTabsProps> = ({ tabConfigs = TA
<GetStartedTab setView={setView} currentView={currentView} {...UseCasesConst[value]} />
</Tabs.Panel>
))}
<Tabs.Panel key={`tab-panel-${OnboardingUseCasesTabsEnum.ECHO}`} value={OnboardingUseCasesTabsEnum.ECHO}>
<EchoTab />
</Tabs.Panel>
</Tabs>
<Outlet />
</Container>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useSegment } from '@novu/shared-web';
import { OnboardingUseCasesTabsEnum } from '../../consts/OnboardingUseCasesTabsEnum';

const TAB_SEARCH_PARAM_NAME = 'tab';
const DEFAULT_TAB: OnboardingUseCasesTabsEnum = OnboardingUseCasesTabsEnum.IN_APP;
const DEFAULT_TAB: OnboardingUseCasesTabsEnum = OnboardingUseCasesTabsEnum.ECHO;

interface GetStartedTabSearchParams {
[TAB_SEARCH_PARAM_NAME]: OnboardingUseCasesTabsEnum;
Expand All @@ -21,7 +21,7 @@ export const useGetStartedTabs = () => {

const currentTab = (params.get(TAB_SEARCH_PARAM_NAME) as OnboardingUseCasesTabsEnum) ?? DEFAULT_TAB;
const setTab = (tab: OnboardingUseCasesTabsEnum) => {
segment.track('Click Use-case Tab - [Get Started]', { tab: tab });
segment.track('Tab click - [Get Started]', { tab: tab });

params.set(TAB_SEARCH_PARAM_NAME, tab);

Expand Down
5 changes: 5 additions & 0 deletions apps/web/src/pages/get-started/consts/DelayUseCase.const.tsx
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
5 changes: 5 additions & 0 deletions apps/web/src/pages/get-started/consts/DigestUseCase.const.tsx
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

0 comments on commit a1d2c15

Please sign in to comment.