diff --git a/.github/workflows/index.yml b/.github/workflows/index.yml
index 67468196e..abf22557d 100644
--- a/.github/workflows/index.yml
+++ b/.github/workflows/index.yml
@@ -40,7 +40,7 @@ jobs:
# Runs a single command using the runners shell
- name: Index Posts
- run: npm run build:index:momentum && npm run build:index:support
+ run: npm run build:index:momentum && npm run build:index:support && npm run build:index:analyst
env:
NEXT_PUBLIC_ALGOLIA_APP_ID: ${{secrets.NEXT_PUBLIC_ALGOLIA_APP_ID}}
ALGOLIA_SEARCH_ADMIN_KEY: ${{secrets.ALGOLIA_SEARCH_ADMIN_KEY}}
diff --git a/components/site/analystHomePageContent.tsx b/components/site/analystHomePageContent.tsx
new file mode 100644
index 000000000..163069aca
--- /dev/null
+++ b/components/site/analystHomePageContent.tsx
@@ -0,0 +1,75 @@
+import React from 'react';
+import { Feedback } from '@sparkpost/matchbox-icons';
+import Link from 'next/link';
+import BaseHomePageContent, { SimpleStyledLink } from './baseHomePageContent';
+
+/* #Content */
+const gettingStarted = {
+ title: 'Getting Started',
+ desc: 'Welcome to Analyst! Get familiar with our product and explore its features:',
+ content: [
+ {
+ url: '/analyst/general/',
+ title: 'General',
+ desc: 'General platform definitions & common account-level inquiries',
+ linkText: 'Learn More',
+ },
+ {
+ url: '/analyst/inbox-and-design-tracker/',
+ title: 'Inbox Tracker & Design Tracker',
+ desc: 'A collection of How-To Guides',
+ linkText: 'Learn More',
+ },
+ {
+ url: '/analyst/competitive-tracker/',
+ title: 'Competitve Tracker',
+ desc: 'Guides for optimizing your use of Competitive Tracker',
+ linkText: 'Learn More',
+ },
+ {
+ url: '/analyst/sfmc/',
+ title: 'Salesforce Marketing Cloud Integration',
+ desc: 'A set of guides on the installation and integration of the Inbox Tracker application',
+ linkText: 'Learn More',
+ },
+ {
+ url: '/analyst/privacy-gdpr/',
+ title: 'Privacy & GDPR',
+ desc: 'We’re committed to keeping your private information private, your data protected and secure and being transparent about our practices',
+ linkText: 'Learn More',
+ },
+ {
+ url: 'https://api.edatasource.com/docs',
+ title: 'API Docs',
+ desc: 'Check out our full API reference.',
+ linkText: 'View Documentation',
+ },
+ ],
+};
+
+const help = {
+ title: "Questions? We're here to help.",
+ desc: 'We’re always happy to help with code or other questions you might have!',
+ desc2: (
+ <>
+ To find the best resource for your question and fastest resolution, check out our
+
+ how to get help guide
+
+ >
+ ),
+ content: [
+ { text: 'Submit a ticket', url: 'mailto:support@emailanalyst.com', Icon: Feedback },
+ ],
+};
+
+const AnalystHomePageContent = () => {
+ return (
+
+ );
+};
+
+export default AnalystHomePageContent;
diff --git a/components/site/analystLayout.tsx b/components/site/analystLayout.tsx
new file mode 100644
index 000000000..396e65701
--- /dev/null
+++ b/components/site/analystLayout.tsx
@@ -0,0 +1,24 @@
+import Layout from './layout';
+import { NavigationItemProps } from './baseNavigation';
+import AnalystNavigation from 'components/site/analystNavigation';
+
+type LayoutProps = {
+ children?: React.ReactNode;
+ navigationData?: NavigationItemProps[];
+};
+
+const AnalystLayout = (props: LayoutProps): JSX.Element => {
+ const { children, navigationData } = props;
+ return (
+
+ }
+ >
+ {children}
+
+ );
+};
+
+export default AnalystLayout;
diff --git a/components/site/analystNavigation.tsx b/components/site/analystNavigation.tsx
new file mode 100644
index 000000000..c2ccd5bdf
--- /dev/null
+++ b/components/site/analystNavigation.tsx
@@ -0,0 +1,69 @@
+import React from 'react';
+import Link from 'next/link';
+import { Box } from '@sparkpost/matchbox';
+import {
+ DeviceHub,
+ Feedback,
+} from '@sparkpost/matchbox-icons';
+import useStatus from 'hooks/useStatus';
+import BaseNavigation, { NavigationProps, StyledLink, StatusColorMap } from './baseNavigation';
+
+const AnalystNavigation = (props: NavigationProps): JSX.Element | null => {
+ const { data = [], title, titleLink } = props;
+ const { status } = useStatus();
+
+ return (
+
+
+
+
+
+
+
+ Submit a Ticket
+
+
+
+
+
+
+
+
+
+
+ API Docs
+
+
+
+
+
+
+
+
+ {/* Placeholder until Circle icon is added to Matchbox */}
+
+
+
+
+ Service Status
+
+
+
+
+
+
+ );
+};
+
+export default AnalystNavigation;
diff --git a/components/site/baseHomePageContent.tsx b/components/site/baseHomePageContent.tsx
new file mode 100644
index 000000000..67a0caea4
--- /dev/null
+++ b/components/site/baseHomePageContent.tsx
@@ -0,0 +1,279 @@
+import React from 'react';
+import { Text, Stack, Box } from '@sparkpost/matchbox';
+import Link from 'next/link';
+import styled from 'styled-components';
+import css from '@styled-system/css';
+
+/* Types */
+
+type CardProps = {
+ url: string;
+ title: string;
+ desc: string;
+ linkText: string;
+};
+
+type GuideLinkProps = {
+ text: string;
+ url: string;
+};
+
+type SectionContentProps = {
+ title: string;
+ desc: string;
+ desc2?: JSX.Element;
+};
+
+type LibraryProps = {
+ name: string;
+ url: string;
+ Icon: () => JSX.Element;
+};
+
+type HelpLinkProps = {
+ text: string;
+ url: string;
+ Icon: React.ElementType;
+};
+
+type SimpleStyledLinkProp = {
+ $inlineBlock?: boolean;
+};
+
+export type BaseHomePageContentProps = {
+ gettingStarted?: { content: CardProps[] } & SectionContentProps;
+ migrationGuides?: { content: GuideLinkProps[] } & SectionContentProps;
+ clientLibraries?: { content: LibraryProps[] } & SectionContentProps;
+ help?: { content: HelpLinkProps[] } & SectionContentProps;
+};
+
+/* Styles */
+export const SimpleStyledLink = styled.a`
+ display: ${(props) => (props.$inlineBlock ? 'inline-block' : 'block')};
+ text-decoration: none;
+ transition: color 0.3s;
+ ${css({
+ marginY: '200',
+})}
+ &,
+ &:visited {
+ ${css({
+ color: 'gray.900',
+})}
+ }
+
+ &:hover {
+ ${css({
+ color: 'blue.700',
+})}
+ }
+
+ ${css({
+ fontWeight: 'normal',
+ textDecoration: 'underline',
+})}
+`;
+
+const BlueStyledLink = styled.a`
+ text-decoration: none;
+ ${css({
+ paddingY: '200',
+ paddingX: '400',
+ marginRight: '200',
+})}
+ transition: background-color .3s;
+ > svg {
+ ${css({
+ marginRight: '200',
+})}
+ }
+ &:hover {
+ ${css({
+ backgroundColor: 'blue.200',
+})}
+ }
+`;
+
+const StyledCard = styled(Box)`
+ &:before {
+ content: '';
+ display: block;
+ position: absolute;
+ top: -1px;
+ left: -1px;
+ right: 0;
+ width: calc(100% + 2px);
+ height: 4px;
+ ${css({
+ backgroundColor: 'blue.700',
+})}
+ }
+`;
+
+const BoxStyledLink = styled.a`
+ display: flex;
+ align-items: center;
+ width: 30%;
+ text-decoration: none;
+ border-radius: 1px;
+ border-width: 1px;
+ border-style: solid;
+ background-color: transparent;
+ transition: background-color 0.3s;
+ min-width: 165px;
+ ${css({
+ color: 'gray.900',
+ paddingX: '450',
+ paddingY: '300',
+ borderColor: 'gray.900',
+ marginBottom: '500',
+ fontSize: '200',
+ lineHeight: '200',
+})}
+ &:hover {
+ ${css({
+ backgroundColor: 'gray.300',
+ color: 'gray.900',
+})}
+ }
+ > svg {
+ ${css({
+ marginRight: '300',
+})}
+ }
+`;
+
+
+/* Components */
+const SectionContent = (props: SectionContentProps): JSX.Element => {
+ const { title, desc, desc2 } = props;
+ return (
+ <>
+
+ {title}
+
+
+ {desc}
+
+ {desc2 && {desc2}}
+ >
+ );
+};
+
+const GuideLink = (props: GuideLinkProps) => {
+ const { url, text } = props;
+ return (
+
+ {text}
+
+ );
+};
+
+const Card = (props: CardProps): JSX.Element => {
+ const { url, title, desc, linkText } = props;
+ return (
+
+
+
+ {title}
+
+
+ {desc}
+
+ {linkText}
+
+
+ );
+};
+
+const Library = (props: LibraryProps): JSX.Element => {
+ const { Icon, name, url } = props;
+ return (
+
+
+ {name}
+
+
+ );
+};
+
+const HelpLink = (props: HelpLinkProps): JSX.Element => {
+ const { Icon, text, url } = props;
+ return (
+
+
+ {text}
+
+
+ );
+};
+
+const BaseHomePageContent = (props: BaseHomePageContentProps) => {
+ const { gettingStarted, migrationGuides, clientLibraries, help } = props;
+ return (
+ <>
+
+ {gettingStarted && (<>
+
+
+ Help & API
+
+
+
+ {gettingStarted.content.map((cc, i) => {
+ return ;
+ })}
+
+
+ >)}
+ {migrationGuides && (<>
+
+
+
+
+ {migrationGuides.content.map((link, i) => {
+ return ;
+ })}
+
+
+ >)}
+ {clientLibraries && (<>
+
+
+
+
+ {clientLibraries.content.map((library, i) => {
+ return ;
+ })}
+
+
+ >)}
+ {help && (<>
+
+
+
+
+ {help.content.map((links, i) => {
+ return ;
+ })}
+
+
+ >)}
+
+ >
+ );
+};
+
+export default BaseHomePageContent;
diff --git a/components/site/baseNavigation.tsx b/components/site/baseNavigation.tsx
new file mode 100644
index 000000000..4778880cd
--- /dev/null
+++ b/components/site/baseNavigation.tsx
@@ -0,0 +1,185 @@
+import React from 'react';
+import { getWindow } from 'utils/ssr';
+import Link from 'next/link';
+import { Box, BoxProps, ScreenReaderOnly, styles } from '@sparkpost/matchbox';
+import {
+ KeyboardArrowDown,
+ KeyboardArrowUp,
+} from '@sparkpost/matchbox-icons';
+import styled from 'styled-components';
+import css from '@styled-system/css';
+import { tokens } from '@sparkpost/design-tokens';
+
+export interface NavigationItemProps {
+ title: string;
+ link: string;
+ items?: this[];
+ level?: number;
+ lastUpdated?: string;
+ description?: string;
+}
+
+export type NavigationProps = {
+ children?: React.ReactNode;
+ data?: NavigationItemProps[];
+ title: string;
+ titleLink: string;
+};
+
+export const StyledLink = styled.a<{ $active?: boolean; $level?: number }>`
+ display: block;
+ cursor: pointer;
+ overflow-wrap: anywhere;
+
+ &,
+ &:visited,
+ &:active {
+ text-decoration: none;
+ ${({ $active, $level }) =>
+ css({
+ py: '200',
+ pr: '700',
+ pl: `calc(${tokens.spacing_500} + ${tokens.spacing_200} * ${$level})`,
+ bg: $active ? 'blue.700' : 'transparent',
+ color: $active ? 'white' : 'gray.900',
+ })}
+ }
+
+ &:hover {
+ ${({ $active }) => {
+ return css({
+ bg: $active ? 'blue.700' : 'gray.200',
+ color: $active ? 'white' : 'gray.900',
+ });
+ }}
+ }
+`;
+
+export const StatusColorMap = {
+ none: 'green.700',
+ minor: 'yellow.400',
+ major: 'brand.orange',
+ critical: 'red.700',
+};
+
+const BaseNavigation = (props: NavigationProps): JSX.Element | null => {
+ const { data = [], title, titleLink, children } = props;
+
+ return (
+
+
+
+
+ {title}
+
+
+
+ {data.map((item, i) => (
+
+ ))}
+ {children}
+
+ );
+};
+
+const getActiveUrl = (location: Location | undefined) => {
+ if (!location) {
+ return '';
+ }
+
+ return location.pathname + location.hash;
+};
+
+const findActiveChild = (
+ items: NavigationItemProps[],
+ activeUrl: string,
+): NavigationItemProps | undefined => {
+ return items.find((item) => {
+ const hasDirectChild = item.link == activeUrl;
+
+ if (!hasDirectChild && item.items) {
+ return findActiveChild(item.items, activeUrl);
+ }
+
+ return hasDirectChild;
+ });
+};
+
+const Chevron = (props: { expanded: boolean }): JSX.Element => {
+ const { expanded } = props;
+ return expanded ? : ;
+};
+
+const StyledChevronWrapper = styled(Box) `
+ ${styles.buttonReset}
+ cursor: pointer;
+ &:hover {
+ ${({ $active }) => {
+ return css({
+ color: $active ? 'white' : 'blue.700',
+ });
+ }}
+ }
+ ${({ $active }) => {
+ return css({
+ color: $active ? 'white' : 'inherit',
+ });
+ }}
+`;
+
+const Item = (props: NavigationItemProps): JSX.Element => {
+ const { link, title, items, level = 0 } = props;
+ const [active, setActive] = React.useState(false);
+ const [expanded, setExpanded] = React.useState(false);
+
+ const environment = getWindow();
+ const activeUrl = getActiveUrl(environment?.location);
+ const hasActiveChild = items && findActiveChild(items, activeUrl);
+
+ React.useEffect(() => {
+ const isExpanded = Boolean(hasActiveChild) || activeUrl === link || activeUrl.includes(link);
+ const isActive = activeUrl === link;
+
+ setExpanded(isExpanded);
+ setActive(isActive);
+ }, [link, activeUrl]);
+
+ return (
+
+
+
+ {title}
+
+
+
+ {items && (
+ setExpanded(!expanded)}
+ aria-expanded={expanded}
+ aria-controls={`${link}__items`}
+ $active={active}
+ >
+ Expand Menu
+
+
+ )}
+
+ {expanded && items && items.map((item, i) => )}
+
+
+ );
+};
+
+export default BaseNavigation;
diff --git a/components/site/docsHomePageContent.tsx b/components/site/docsHomePageContent.tsx
index 068a9cece..f6b82bac0 100644
--- a/components/site/docsHomePageContent.tsx
+++ b/components/site/docsHomePageContent.tsx
@@ -1,156 +1,8 @@
import React from 'react';
-import { Text, Stack, Box } from '@sparkpost/matchbox';
import { Feedback, HelpOutline, QuestionAnswer } from '@sparkpost/matchbox-icons';
import Link from 'next/link';
-import styled from 'styled-components';
-import css from '@styled-system/css';
import { Elixir, PHP, Java, NodeMailer, Python, Node, Go } from './icons';
-
-/*
-████████╗██╗ ██╗██████╗ ███████╗███████╗
-╚══██╔══╝╚██╗ ██╔╝██╔══██╗██╔════╝██╔════╝
- ██║ ╚████╔╝ ██████╔╝█████╗ ███████╗
- ██║ ╚██╔╝ ██╔═══╝ ██╔══╝ ╚════██║
- ██║ ██║ ██║ ███████╗███████║
- ╚═╝ ╚═╝ ╚═╝ ╚══════╝╚══════╝
- */
-type CardProps = {
- url: string;
- title: string;
- desc: string;
- linkText: string;
-};
-
-type GuideLinkProps = {
- text: string;
- url: string;
-};
-
-type SectionContentProps = {
- title: string;
- desc: string;
- desc2?: JSX.Element;
-};
-
-type LibraryProps = {
- name: string;
- url: string;
- Icon: () => JSX.Element;
-};
-
-type HelpLinkProps = {
- text: string;
- url: string;
- Icon: React.ElementType;
-};
-
-type SimpleStyledLinkProp = {
- $inlineBlock?: boolean;
-};
-
-/*
-███████╗████████╗██╗ ██╗██╗ ███████╗███████╗
-██╔════╝╚══██╔══╝╚██╗ ██╔╝██║ ██╔════╝██╔════╝
-███████╗ ██║ ╚████╔╝ ██║ █████╗ ███████╗
-╚════██║ ██║ ╚██╔╝ ██║ ██╔══╝ ╚════██║
-███████║ ██║ ██║ ███████╗███████╗███████║
-╚══════╝ ╚═╝ ╚═╝ ╚══════╝╚══════╝╚══════╝
-*/
-
-const SimpleStyledLink = styled.a`
- display: ${(props) => (props.$inlineBlock ? 'inline-block' : 'block')};
- text-decoration: none;
- transition: color 0.3s;
- ${css({
- marginY: '200',
- })}
- &,
- &:visited {
- ${css({
- color: 'gray.900',
- })}
- }
-
- &:hover {
- ${css({
- color: 'blue.700',
- })}
- }
-
- ${css({
- fontWeight: 'normal',
- textDecoration: 'underline',
- })}
-`;
-
-const BlueStyledLink = styled.a`
- text-decoration: none;
- ${css({
- paddingY: '200',
- paddingX: '400',
- marginRight: '200',
- })}
- transition: background-color .3s;
- > svg {
- ${css({
- marginRight: '200',
- })}
- }
- &:hover {
- ${css({
- backgroundColor: 'blue.200',
- })}
- }
-`;
-
-const StyledCard = styled(Box)`
- &:before {
- content: '';
- display: block;
- position: absolute;
- top: -1px;
- left: -1px;
- right: 0;
- width: calc(100% + 2px);
- height: 4px;
- ${css({
- backgroundColor: 'blue.700',
- })}
- }
-`;
-
-const BoxStyledLink = styled.a`
- display: flex;
- align-items: center;
- width: 30%;
- text-decoration: none;
- border-radius: 1px;
- border-width: 1px;
- border-style: solid;
- background-color: transparent;
- transition: background-color 0.3s;
- min-width: 165px;
- ${css({
- color: 'gray.900',
- paddingX: '450',
- paddingY: '300',
- borderColor: 'gray.900',
- marginBottom: '500',
- fontSize: '200',
- lineHeight: '200',
- })}
- &:hover {
- ${css({
- backgroundColor: 'gray.300',
- color: 'gray.900',
- })}
- }
- > svg {
- ${css({
- marginRight: '300',
- })}
- }
-`;
+import BaseHomePageContent, { SimpleStyledLink } from './baseHomePageContent';
/*
██████╗ ██████╗ ███╗ ██╗████████╗███████╗███╗ ██╗████████╗
@@ -243,134 +95,14 @@ const help = {
],
};
-/*
- ██████╗ ██████╗ ███╗ ███╗██████╗ ██████╗ ███╗ ██╗███████╗███╗ ██╗████████╗███████╗
-██╔════╝██╔═══██╗████╗ ████║██╔══██╗██╔═══██╗████╗ ██║██╔════╝████╗ ██║╚══██╔══╝██╔════╝
-██║ ██║ ██║██╔████╔██║██████╔╝██║ ██║██╔██╗ ██║█████╗ ██╔██╗ ██║ ██║ ███████╗
-██║ ██║ ██║██║╚██╔╝██║██╔═══╝ ██║ ██║██║╚██╗██║██╔══╝ ██║╚██╗██║ ██║ ╚════██║
-╚██████╗╚██████╔╝██║ ╚═╝ ██║██║ ╚██████╔╝██║ ╚████║███████╗██║ ╚████║ ██║ ███████║
- ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝
-*/
-
-const SectionContent = (props: SectionContentProps): JSX.Element => {
- const { title, desc, desc2 } = props;
- return (
- <>
-
- {title}
-
-
- {desc}
-
- {desc2 && {desc2}}
- >
- );
-};
-
-const GuideLink = (props: GuideLinkProps) => {
- const { url, text } = props;
- return (
-
- {text}
-
- );
-};
-
-const Card = (props: CardProps): JSX.Element => {
- const { url, title, desc, linkText } = props;
- return (
-
-
-
- {title}
-
-
- {desc}
-
- {linkText}
-
-
- );
-};
-
-const Library = (props: LibraryProps): JSX.Element => {
- const { Icon, name, url } = props;
- return (
-
-
- {name}
-
-
- );
-};
-
-const HelpLink = (props: HelpLinkProps): JSX.Element => {
- const { Icon, text, url } = props;
- return (
-
-
- {text}
-
-
- );
-};
-
const DocsHomePageContent = () => {
return (
- <>
-
-
-
- Help & API
-
-
-
- {gettingStarted.content.map((cc, i) => {
- return ;
- })}
-
-
-
-
-
-
- {migrationGuides.content.map((link, i) => {
- return ;
- })}
-
-
-
-
-
-
- {clientLibraries.content.map((library, i) => {
- return ;
- })}
-
-
-
-
-
-
- {help.content.map((links, i) => {
- return ;
- })}
-
-
-
- >
+
);
};
diff --git a/components/site/docsIndexListPageContent.tsx b/components/site/docsIndexListPageContent.tsx
index ccdee6eda..e01e2a1a5 100644
--- a/components/site/docsIndexListPageContent.tsx
+++ b/components/site/docsIndexListPageContent.tsx
@@ -3,7 +3,7 @@ import Link from 'next/link';
import { Box, Tag, Button } from '@sparkpost/matchbox';
import { KeyboardArrowDown } from '@sparkpost/matchbox-icons';
import { formatDate } from 'utils/string';
-import type { NavigationItemProps } from 'components/site/navigation';
+import type { NavigationItemProps } from 'components/site/baseNavigation';
interface DocsIndexListPageContentProps {
navigationData: NavigationItemProps | undefined;
diff --git a/components/site/docsLayout.tsx b/components/site/docsLayout.tsx
index 16317ec5f..f8c082a21 100644
--- a/components/site/docsLayout.tsx
+++ b/components/site/docsLayout.tsx
@@ -1,5 +1,6 @@
import Layout from './layout';
-import Navigation, { NavigationItemProps } from './navigation';
+import { NavigationItemProps } from './baseNavigation';
+import Navigation from './navigation';
type LayoutProps = {
children?: React.ReactNode;
diff --git a/components/site/header.tsx b/components/site/header.tsx
index d9b5a455a..ea9be4540 100644
--- a/components/site/header.tsx
+++ b/components/site/header.tsx
@@ -13,6 +13,7 @@ const StyledButton = styled.button`
type HeaderProps = {
getActivatorProps?: () => object;
+ hideDrawerButtons?: boolean;
};
const Header = (props: HeaderProps) => {
@@ -32,9 +33,9 @@ const Header = (props: HeaderProps) => {
-
+ {!props.hideDrawerButtons && (
-
+ )}
>
)}
diff --git a/components/site/layout.tsx b/components/site/layout.tsx
index 091b198e6..b3c70a9c3 100644
--- a/components/site/layout.tsx
+++ b/components/site/layout.tsx
@@ -7,6 +7,7 @@ import LayoutInnerContent from './layoutInnerContent';
type LayoutProps = {
children?: React.ReactNode;
navigationComponent?: React.ReactNode;
+ hideDrawerButtons?: boolean;
};
const Layout = (props: LayoutProps): JSX.Element => {
@@ -23,10 +24,10 @@ const Layout = (props: LayoutProps): JSX.Element => {
{navigationComponent}
-
+ {!props.hideDrawerButtons && ()}
-
+
{navigationComponent}
diff --git a/components/site/layoutInnerContent.tsx b/components/site/layoutInnerContent.tsx
index 02223c040..baeb43830 100644
--- a/components/site/layoutInnerContent.tsx
+++ b/components/site/layoutInnerContent.tsx
@@ -6,6 +6,8 @@ import styled from 'styled-components';
type LayoutInnerContentProps = {
children?: React.ReactNode;
getActivatorProps?: () => object;
+ hideDrawerButtons?: boolean;
+
};
const StyledSkipToContent = styled(Box)`
@@ -36,7 +38,7 @@ const LayoutInnerContent = (props: LayoutInnerContentProps): JSX.Element => {
>
Skip to main content
-
+
{children}
);
diff --git a/components/site/navigation.tsx b/components/site/navigation.tsx
index 1f9dd520f..d1fe42ce5 100644
--- a/components/site/navigation.tsx
+++ b/components/site/navigation.tsx
@@ -1,94 +1,22 @@
import React from 'react';
-import { getWindow } from 'utils/ssr';
import Link from 'next/link';
-import { Box, BoxProps, ScreenReaderOnly, styles } from '@sparkpost/matchbox';
+import { Box } from '@sparkpost/matchbox';
import {
- KeyboardArrowDown,
- KeyboardArrowUp,
Forum,
PeopleOutline,
Code,
DeviceHub,
Feedback,
} from '@sparkpost/matchbox-icons';
-import styled from 'styled-components';
-import css from '@styled-system/css';
-import { tokens } from '@sparkpost/design-tokens';
import useStatus from 'hooks/useStatus';
-
-export interface NavigationItemProps {
- title: string;
- link: string;
- items?: this[];
- level?: number;
- lastUpdated?: string;
- description?: string;
-}
-
-type NavigationProps = {
- data?: NavigationItemProps[];
- title: string;
- titleLink: string;
-};
-
-const StyledLink = styled.a<{ $active?: boolean; $level?: number }>`
- display: block;
- cursor: pointer;
- overflow-wrap: anywhere;
-
- &,
- &:visited,
- &:active {
- text-decoration: none;
- ${({ $active, $level }) =>
- css({
- py: '200',
- pr: '700',
- pl: `calc(${tokens.spacing_500} + ${tokens.spacing_200} * ${$level})`,
- bg: $active ? 'blue.700' : 'transparent',
- color: $active ? 'white' : 'gray.900',
- })}
- }
-
- &:hover {
- ${({ $active }) => {
- return css({
- bg: $active ? 'blue.700' : 'gray.200',
- color: $active ? 'white' : 'gray.900',
- });
- }}
- }
-`;
-
-const StatusColorMap = {
- none: 'green.700',
- minor: 'yellow.400',
- major: 'brand.orange',
- critical: 'red.700',
-};
+import BaseNavigation, {NavigationProps, StyledLink, StatusColorMap} from './baseNavigation';
const Navigation = (props: NavigationProps): JSX.Element | null => {
const { data = [], title, titleLink } = props;
const { status } = useStatus();
return (
-
-
-
-
- {title}
-
-
-
- {data.map((item, i) => (
-
- ))}
+
@@ -170,101 +98,7 @@ const Navigation = (props: NavigationProps): JSX.Element | null => {
-
- );
-};
-
-const getActiveUrl = (location: Location | undefined) => {
- if (!location) {
- return '';
- }
-
- return location.pathname + location.hash;
-};
-
-const findActiveChild = (
- items: NavigationItemProps[],
- activeUrl: string,
-): NavigationItemProps | undefined => {
- return items.find((item) => {
- const hasDirectChild = item.link == activeUrl;
-
- if (!hasDirectChild && item.items) {
- return findActiveChild(item.items, activeUrl);
- }
-
- return hasDirectChild;
- });
-};
-
-const Chevron = (props: { expanded: boolean }): JSX.Element => {
- const { expanded } = props;
- return expanded ? : ;
-};
-
-const StyledChevronWrapper = styled(Box)`
- ${styles.buttonReset}
- cursor: pointer;
- &:hover {
- ${({ $active }) => {
- return css({
- color: $active ? 'white' : 'blue.700',
- });
- }}
- }
- ${({ $active }) => {
- return css({
- color: $active ? 'white' : 'inherit',
- });
- }}
-`;
-
-const Item = (props: NavigationItemProps): JSX.Element => {
- const { link, title, items, level = 0 } = props;
- const [active, setActive] = React.useState(false);
- const [expanded, setExpanded] = React.useState(false);
-
- const environment = getWindow();
- const activeUrl = getActiveUrl(environment?.location);
- const hasActiveChild = items && findActiveChild(items, activeUrl);
-
- React.useEffect(() => {
- const isExpanded = Boolean(hasActiveChild) || activeUrl === link || activeUrl.includes(link);
- const isActive = activeUrl === link;
-
- setExpanded(isExpanded);
- setActive(isActive);
- }, [link, activeUrl]);
-
- return (
-
-
-
- {title}
-
-
-
- {items && (
- setExpanded(!expanded)}
- aria-expanded={expanded}
- aria-controls={`${link}__items`}
- $active={active}
- >
- Expand Menu
-
-
- )}
-
- {expanded && items && items.map((item, i) => )}
-
-
+
);
};
diff --git a/components/site/status.tsx b/components/site/status.tsx
index b6c851dc2..0a271c0f8 100644
--- a/components/site/status.tsx
+++ b/components/site/status.tsx
@@ -20,7 +20,8 @@ const Status = () => {
src="https://cdn.statuspage.io/se-v2.js"
onLoad={() => {
if (environment && environment.StatusPage) {
- const sp = new environment.StatusPage.page({ page: '7ky1q6zd3fyp' });
+ const pathStartsWithAnalyst = /^\/analyst/.test(environment.location.pathname);
+ const sp = new environment.StatusPage.page({ page: pathStartsWithAnalyst ? 'hj13jvlxdqwx' : '7ky1q6zd3fyp' });
sp.status({
success: function (data: {
diff --git a/content/analyst/competitive-tracker/index.md b/content/analyst/competitive-tracker/index.md
new file mode 100644
index 000000000..9cd2d0834
--- /dev/null
+++ b/content/analyst/competitive-tracker/index.md
@@ -0,0 +1,5 @@
+---
+lastUpdated: '02/22/2023'
+title: 'Competitive Tracker'
+description: ''
+---
diff --git a/content/analyst/general/index.md b/content/analyst/general/index.md
new file mode 100644
index 000000000..f1ff18891
--- /dev/null
+++ b/content/analyst/general/index.md
@@ -0,0 +1,5 @@
+---
+lastUpdated: '02/22/2023'
+title: 'General'
+description: ''
+---
diff --git a/content/analyst/inbox-and-design-tracker/index.md b/content/analyst/inbox-and-design-tracker/index.md
new file mode 100644
index 000000000..0145708a6
--- /dev/null
+++ b/content/analyst/inbox-and-design-tracker/index.md
@@ -0,0 +1,5 @@
+---
+lastUpdated: '02/22/2023'
+title: 'Inbox Tracker & Design Tracker'
+description: ''
+---
diff --git a/content/analyst/privacy-gdpr/index.md b/content/analyst/privacy-gdpr/index.md
new file mode 100644
index 000000000..e3a5a43a8
--- /dev/null
+++ b/content/analyst/privacy-gdpr/index.md
@@ -0,0 +1,5 @@
+---
+lastUpdated: '02/22/2023'
+title: 'Privacy & GDPR'
+description: ''
+---
diff --git a/content/analyst/sfmc/index.md b/content/analyst/sfmc/index.md
new file mode 100644
index 000000000..4420e0db5
--- /dev/null
+++ b/content/analyst/sfmc/index.md
@@ -0,0 +1,5 @@
+---
+lastUpdated: '02/22/2023'
+title: 'Salesforce Marketing Cloud'
+description: ''
+---
diff --git a/lib/api.ts b/lib/api.ts
index 9ae6deeac..2e7277780 100644
--- a/lib/api.ts
+++ b/lib/api.ts
@@ -3,7 +3,7 @@ import path from 'path';
import glob from 'glob';
import matter from 'gray-matter';
-type CategoryOption = 'momentum' | 'docs';
+type CategoryOption = 'momentum' | 'docs' | 'analyst';
export const categoryPath = (category: CategoryOption): string => {
return path.join(
@@ -93,6 +93,34 @@ export const getSupportNavigation = () => {
return navigationData;
};
+/**
+ * Retrieves navigation data from /content/docs
+ */
+export const getAnalystSupportNavigation = () => {
+ // Get categories first
+ const categories = glob.sync('content/analyst/**/index.md');
+ const categoryData = categories.map((file) => {
+ const { data } = readFile(file);
+ const link = file.replace(/\/index.md$/, '/').replace(/^content/, '');
+ return { ...data, link };
+ });
+
+ // Then populate category items
+ const navigationData = categoryData.map((category) => {
+ const postsInCategory = glob.sync(`content${category.link}/!(index).md`);
+ return {
+ ...category,
+ items: postsInCategory.map((file) => {
+ const { data } = readFile(file);
+ const link = file.replace(/.md$/, '/').replace(/^content/, '');
+ return { ...data, link };
+ }),
+ };
+ });
+
+ return navigationData;
+};
+
/**
* Retrieves category information for breadcrumb friendly labels
*/
diff --git a/package.json b/package.json
index 75d9a6dc7..6e90d3495 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
"dev": "next dev",
"build": "next build",
"build:ssr": "next build && next export",
+ "build:index:analyst": "node ./scripts/analystIndex.mjs",
"build:index:momentum": "node ./scripts/momentumIndex.mjs",
"build:index:support": "node ./scripts/supportIndex.mjs",
"build:images": "rimraf public/content && node ./scripts/copyImages.mjs",
diff --git a/pages/analyst/[...slug].tsx b/pages/analyst/[...slug].tsx
new file mode 100644
index 000000000..d51038034
--- /dev/null
+++ b/pages/analyst/[...slug].tsx
@@ -0,0 +1,81 @@
+import { GetStaticProps, GetStaticPaths } from 'next';
+import {
+ getCategoryData,
+ getAnalystSupportNavigation,
+ getAllCategoryPostPaths,
+ getSingleCategoryPost,
+ categoryPath,
+} from 'lib/api';
+import { CategoriesProvider, Category } from 'context/categories';
+import SEO from 'components/site/seo';
+import Markdown from 'components/markdown';
+import AnalystLayout from 'components/site/analystLayout';
+import DocumentationContent from 'components/site/documentationContent';
+import IndexListPageContent from 'components/site/docsIndexListPageContent';
+import type { NavigationItemProps } from 'components/site/baseNavigation';
+import { useRouter } from 'next/router';
+
+type PostPageProps = {
+ content: string;
+ data: {
+ title?: string;
+ description?: string;
+ lastUpdated?: string;
+ };
+ navigationData?: NavigationItemProps[];
+ isIndex: boolean;
+ categoryData: Category[];
+};
+
+const PostPage = (props: PostPageProps): JSX.Element => {
+ const { content, data, navigationData, isIndex, categoryData } = props;
+ const router = useRouter();
+
+ // This is to check for a url that either contains a trailing slash or not (since netlify will show either)
+ const trailingSlashOrNo = (navData: NavigationItemProps): boolean => {
+ const linkComponents = navData.link.split('/');
+ linkComponents.pop();
+ const link = linkComponents.join('/');
+
+ return link === router.asPath || link + '/' === router.asPath;
+ };
+ return (
+
+
+
+
+ {isIndex && navigationData ? (
+
+ ) : (
+ {content}
+ )}
+
+
+
+ );
+};
+
+export const getStaticProps: GetStaticProps = async ({ params }) => {
+ if (!params?.slug) {
+ return { props: {} };
+ }
+
+ const { content, data, isIndex } = getSingleCategoryPost(params.slug, categoryPath('analyst')) || {};
+ const navigationData = getAnalystSupportNavigation() || [];
+ const categoryData = getCategoryData('analyst');
+ return { props: { content, data, navigationData, isIndex, categoryData } };
+};
+
+export const getStaticPaths: GetStaticPaths = async () => {
+ return {
+ paths: getAllCategoryPostPaths('analyst'),
+ fallback: false,
+ };
+};
+
+export default PostPage;
diff --git a/pages/analyst/index.tsx b/pages/analyst/index.tsx
new file mode 100644
index 000000000..83e11b03a
--- /dev/null
+++ b/pages/analyst/index.tsx
@@ -0,0 +1,35 @@
+import AnalystHomePageContent from 'components/site/analystHomePageContent';
+import { GetStaticProps } from 'next';
+import { getAnalystSupportNavigation, getCategoryData } from 'lib/api';
+import SEO from 'components/site/seo';
+import AnalystLayout from 'components/site/analystLayout';
+import type { NavigationItemProps } from 'components/site/baseNavigation';
+import { CategoriesProvider, Category } from 'context/categories';
+
+type IndexPageProps = {
+ navigationData?: NavigationItemProps[];
+ categoryData: Category[];
+};
+
+const IndexPage = (props: IndexPageProps): JSX.Element => {
+ const { categoryData, navigationData } = props;
+ return (
+
+
+
+
+
+
+ );
+};
+
+export const getStaticProps: GetStaticProps = async () => {
+ const navigationData = getAnalystSupportNavigation() || [];
+ const categoryData = getCategoryData('analyst');
+ return { props: { categoryData, navigationData } };
+};
+
+export default IndexPage;
diff --git a/pages/docs/[...slug].tsx b/pages/docs/[...slug].tsx
index 99ba90c01..84719883e 100644
--- a/pages/docs/[...slug].tsx
+++ b/pages/docs/[...slug].tsx
@@ -12,7 +12,7 @@ import Markdown from 'components/markdown';
import DocsLayout from 'components/site/docsLayout';
import DocumentationContent from 'components/site/documentationContent';
import DocsIndexListPageContent from 'components/site/docsIndexListPageContent';
-import type { NavigationItemProps } from 'components/site/navigation';
+import type { NavigationItemProps } from 'components/site/baseNavigation';
import { useRouter } from 'next/router';
type PostPageProps = {
diff --git a/pages/docs/index.tsx b/pages/docs/index.tsx
index 18dfdbbe4..ae67ab96d 100644
--- a/pages/docs/index.tsx
+++ b/pages/docs/index.tsx
@@ -4,7 +4,7 @@ import { getCategoryData } from 'lib/api';
import SEO from 'components/site/seo';
import DocsLayout from 'components/site/docsLayout';
import { getSupportNavigation } from 'lib/api';
-import type { NavigationItemProps } from 'components/site/navigation';
+import type { NavigationItemProps } from 'components/site/baseNavigation';
import { CategoriesProvider, Category } from 'context/categories';
type IndexPageProps = {
diff --git a/pages/submit-a-ticket.tsx b/pages/submit-a-ticket.tsx
index dd98c691e..afb5163ab 100644
--- a/pages/submit-a-ticket.tsx
+++ b/pages/submit-a-ticket.tsx
@@ -3,7 +3,7 @@ import { getSupportNavigation } from 'lib/api';
import { Box, Text, Button, Stack } from '@sparkpost/matchbox';
import SEO from 'components/site/seo';
import DocsLayout from 'components/site/docsLayout';
-import type { NavigationItemProps } from 'components/site/navigation';
+import type { NavigationItemProps } from 'components/site/baseNavigation';
type SubmitATicketPageProps = {
navigationData?: NavigationItemProps[];
diff --git a/scripts/analystIndex.mjs b/scripts/analystIndex.mjs
new file mode 100644
index 000000000..6098ad88d
--- /dev/null
+++ b/scripts/analystIndex.mjs
@@ -0,0 +1,23 @@
+#!/usr/bin/env node
+
+import fs from 'fs';
+import path from 'path';
+import glob from 'glob';
+import matter from 'gray-matter';
+import { buildIndex } from './algoliaIndex.mjs';
+
+const getAllAnalystPosts = () => {
+ return glob.sync(`content/analyst/**/!(index).md`).map((file) => {
+ const withoutExtensions = file.replace(/.md$/, '');
+ const url = withoutExtensions.replace(/^content\//, '/');
+ const filePath = path.join(path.join(process.cwd(), 'content'), `${url}.md`);
+
+ if (fs.existsSync(filePath)) {
+ return { url, file, ...matter(fs.readFileSync(filePath, 'utf8')) };
+ }
+ });
+};
+
+(async function () {
+ buildIndex('next_analyst_documentation', getAllAnalystPosts);
+})();