Browse Source

feat: replace dynamic faq with static page

quick fix for #1074
friedger-patch-6
Alexander Graebe 4 years ago
committed by Alexander Graebe
parent
commit
109dc2e5d4
  1. 4
      README.md
  2. 66
      src/common/data/faq.ts
  3. 61
      src/components/faq.tsx
  4. 2
      src/pages/ecosystem/stacks-token-holders.md
  5. 2
      src/pages/ecosystem/stacks-token.md
  6. 24
      src/pages/references/faqs.md
  7. 109
      src/pages/references/faqs/[slug].tsx

4
README.md

@ -34,7 +34,3 @@ The `src/_data/cli-reference.json` file is generated from the `stx` subcommand `
There is a json file that is generated via the `stacks-blockchain` repo, which automatically brings it over to this repo
via a github action.
### FAQs
All of the FAQs found at `/reference/faqs` are pulled dynamically from the zendesk api and rendered in this project.

66
src/common/data/faq.ts

@ -1,66 +0,0 @@
import { convertRemoteDataToMDX } from '@common/data/mdx';
import TurndownService from 'turndown';
import { slugify } from '@common/utils';
import { getBetterNames } from '@common/utils/faqs';
const fetchSections = async () => {
const res = await fetch('https://blockstack.zendesk.com/api/v2/help_center/en-us/sections.json');
const { sections } = await res.json();
return sections;
};
const fetchArticles = async (id: number) => {
const res = await fetch(
`https://blockstack.zendesk.com/api/v2/help_center/en-us/sections/${id}/articles.json?per_page=100`
);
const { articles } = await res.json();
return articles;
};
// This function gets called at build time
export async function getStaticPaths() {
const sections = await fetchSections();
const paths = sections.map(section => ({
params: { slug: slugify(getBetterNames(section.id).title) },
}));
return { paths, fallback: false };
}
const getSectionBySlug = (sections, slug) =>
sections.find(s => {
const { title } = getBetterNames(s.id);
const _slug = slugify(title);
return _slug === slug;
});
export async function getStaticProps(context) {
const sections = await fetchSections();
let articles = [];
if (context?.params?.slug) {
const section = getSectionBySlug(sections, context.params.slug);
const _articles = await fetchArticles(section.id);
const turndownService = new TurndownService();
// we convert html to markdown so we can process it with remark/rehype,
// eg external links open in new window
const md = _articles.map(faq => ({
...faq,
body: turndownService.turndown(faq.body),
}));
// convert it to MDX with next-mdx-remote
const body = await convertRemoteDataToMDX(md, 'body');
articles = _articles.map((faq, index) => ({
...faq,
body: body[index],
}));
}
return {
props: {
sections,
articles,
...context,
},
revalidate: 60 * 60 * 12, // 12 hours
};
}

61
src/components/faq.tsx

@ -1,61 +0,0 @@
import React from 'react';
import { Box, space, color, Grid } from '@stacks/ui';
import { Text } from '@components/typography';
import { slugify } from '@common/utils';
import { getCapsizeStyles, getHeadingStyles } from '@components/mdx/typography';
import { HoverImage } from '@components/hover-image';
import { useTouchable } from '@common/hooks/use-touchable';
import Link from 'next/link';
import { getBetterNames } from '@common/utils/faqs';
const FloatingLink = ({ href, ...props }: any) => (
<Link href={href} {...props} passHref>
<Box as="a" position="absolute" size="100%" zIndex={999} left={0} top={0} />
</Link>
);
const SectionCard = ({ section }) => {
const { hover, active, bind } = useTouchable({
behavior: 'button',
});
const { title, description, img } = getBetterNames(section.id);
return (
<Box
color={color('text-title')}
_hover={{ cursor: 'pointer', color: color('accent') }}
position="relative"
{...bind}
>
<FloatingLink href="/references/faqs/[slug]" as={`/references/faqs/${slugify(title)}`} />
<HoverImage isHovered={hover || active} src={img} />
<Box>
<Text color="currentColor" {...getHeadingStyles('h3')}>
{title}
</Text>
<Box>
<Text
display={'block'}
color={color('text-body')}
mt={space('base-loose')}
{...getCapsizeStyles(16, 26)}
>
{description}
</Text>
</Box>
</Box>
</Box>
);
};
export const FAQs = React.memo(({ articles, sections }: any) => {
return (
<Grid
gridTemplateColumns={['repeat(1, 1fr)', 'repeat(2, 1fr)', 'repeat(2, 1fr)', 'repeat(2, 1fr)']}
gridColumnGap={space('extra-loose')}
gridRowGap="64px"
px={['extra-loose', 'extra-loose', 0, 0]}
>
{sections.map(section => {
return <SectionCard key={section.id} section={section} />;
})}
</Grid>
);
});

2
src/pages/ecosystem/stacks-token-holders.md

@ -43,4 +43,4 @@ registered and when they will unlock.
## Have more questions?
For a list of frequent questions and answers about STX tokens, [see the Stacks token FAQs](/references/faqs/stacks-token).
For a list of frequent questions and answers about STX tokens, [see the Stacks token FAQs](/references/faqs).

2
src/pages/ecosystem/stacks-token.md

@ -41,7 +41,7 @@ holders under a predetermined unlocking schedule. The events on the unlocking
schedule are the same for each investor, **the dates of these events** depend on the
holder's purchase date.
-> **Note:** If you are a token holder and would like to review your unlocking schedule, visit the [For current token holders](/references/faqs/stacks-token) page in this documentation.
-> **Note:** If you are a token holder and would like to review your unlocking schedule, visit the [For current token holders](/references/faqs) page in this documentation.
The genesis block launch makes possible the following interactions:

24
src/pages/references/faqs.md

@ -1,10 +1,24 @@
---
title: FAQs
description: A knowledge base of question and answers related to the Stacks ecosystem.
duration: ''
description: Find answers related to the Stacks ecosystem.
---
import { FAQs } from '@components/faq'
export { getStaticProps } from '@common/data/faq'
## General Information
<FAQs sections={props.sections} />
Learn more about the user-owned internet on Bitcoin and the Stacks ecosystem on [stacks.co](https://stacks.co).
## Apps and Smart Contracts
Developers, get started building apps and contracts on the [developer page at stacks.co](https://www.stacks.co/developers).
## Stacks Network
Learn more about the network behind the user-owned internet on Bitcoin in the [Understand Stacks chapter](https://docs.blockstack.org/understand-stacks/overview) in the docs.
## Stacks Token
Stacks fuel apps and smart contracts on Bitcoin. Learn more at [stackstoken.com](https://stackstoken.com/faq).
## Stacks Wallet
Download and find resources about the Stacks Wallet by Hiro at [hiro.so](https://www.hiro.so/wallet).

109
src/pages/references/faqs/[slug].tsx

@ -1,109 +0,0 @@
import React from 'react';
import { Components } from '@components/mdx';
import { Box, Flex, ChevronIcon, space, color, Grid } from '@stacks/ui';
import hydrate from 'next-mdx-remote/hydrate';
import { Accordion, AccordionItem, AccordionButton, AccordionPanel } from '@reach/accordion';
import { border } from '@common/utils';
import { useRouter } from 'next/router';
import { useActiveHeading } from '@common/hooks/use-active-heading';
import { BackButton } from '@components/back-button';
import Head from 'next/head';
import { MDContents } from '@components/mdx/md-contents';
export { getStaticProps, getStaticPaths } from '@common/data/faq';
import { slugify, getSlug } from '@common/utils';
import { PageTop } from '@components/page-top';
import { getBetterNames } from '@common/utils/faqs';
const FAQItem = React.memo(({ faq, ...rest }: any) => {
const id = slugify(faq.title);
const { isActive } = useActiveHeading(id);
return (
<Components.section>
<Box as={AccordionItem} borderBottom={border()} {...rest}>
<Flex
as={AccordionButton}
_hover={{ color: color('accent') }}
{...{
display: 'flex',
width: '100%',
outline: 'none',
bg: 'transparent',
border: '0',
alignItems: 'center',
justifyContent: 'space-between',
py: space('extra-loose'),
textAlign: 'left',
color: isActive ? color('accent') : color('text-title'),
_hover: {
cursor: 'pointer',
color: color('accent'),
},
}}
>
<Components.h4 my="0px !important" id={id} color="currentColor">
{faq.title}
</Components.h4>
<Box color={color('text-caption')} pl={space('base-loose')}>
<ChevronIcon direction="down" size="22px" />
</Box>
</Flex>
<Box pb={space('extra-loose')} as={AccordionPanel}>
{hydrate(faq.body, { components: Components })}
</Box>
</Box>
</Components.section>
);
});
const FaqItems = ({ articles }) => {
const router = useRouter();
const slug = getSlug(router.asPath);
const slugIndex = articles.findIndex(faq => slugify(faq.title) === slug);
const [index, setIndex] = React.useState(slugIndex !== -1 ? slugIndex : 0);
const handleIndexChange = (value: number) => {
setIndex(value);
};
return (
<Box
pr={['extra-loose', 'extra-loose', 'base-loose', 'base-loose']}
pl={['extra-loose', 'extra-loose', '0', '0']}
>
<BackButton href="/references/faqs" mb={0} />
<Accordion multiple collapsible defaultIndex={index} onChange={handleIndexChange}>
{articles
// @ts-ignore
.sort((a, b) => new Date(a.created_at) - new Date(b.created_at))
.map((faq, _index) => {
return <FAQItem faq={faq} key={_index} />;
})}
</Accordion>
</Box>
);
};
const FaqPage = props => {
const { articles, sections, params } = props;
const section = sections.find(s => {
const { title } = getBetterNames(s.id);
const slug = slugify(title);
return slug === params.slug;
});
const { title, description } = getBetterNames(section.id);
return (
<>
<Head>
<title>{title} | Stacks</title>
<meta name="description" content={description} />
</Head>
<MDContents pageTop={() => <PageTop title={title} description={description} />} headings={[]}>
<FaqItems articles={articles} />
</MDContents>
</>
);
};
export default FaqPage;
Loading…
Cancel
Save