chore(lp): 📦️ Import existing Landing page
This commit is contained in:
202
apps/landing-page/pages/blog/[slug].tsx
Normal file
202
apps/landing-page/pages/blog/[slug].tsx
Normal file
@ -0,0 +1,202 @@
|
||||
import { GetStaticPropsContext } from 'next'
|
||||
import { NotionBlock, NotionText } from 'notion-blocks-chakra-ui'
|
||||
import React from 'react'
|
||||
import { getPage, getBlocks, getFullDatabase } from '../../lib/notion'
|
||||
import Image from 'next/image'
|
||||
import {
|
||||
Stack,
|
||||
Container,
|
||||
Button,
|
||||
VStack,
|
||||
Heading,
|
||||
HStack,
|
||||
Text,
|
||||
} from '@chakra-ui/react'
|
||||
import {
|
||||
Page,
|
||||
Block,
|
||||
TitlePropertyValue,
|
||||
RichTextPropertyValue,
|
||||
CheckboxPropertyValue,
|
||||
} from '@notionhq/client/build/src/api-types'
|
||||
import { Footer } from 'components/common/Footer'
|
||||
import { Navbar } from 'components/common/Navbar/Navbar'
|
||||
import { NextChakraLink } from 'components/common/nextChakraAdapters/NextChakraLink'
|
||||
import { SocialMetaTags } from 'components/common/SocialMetaTags'
|
||||
|
||||
export default function Post({
|
||||
page,
|
||||
blocks,
|
||||
}: {
|
||||
page: Page
|
||||
blocks: Block[]
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<Stack
|
||||
alignItems="center"
|
||||
w="full"
|
||||
overflowX="hidden"
|
||||
pb={20}
|
||||
minH="calc(100vh - 267px)"
|
||||
spacing={10}
|
||||
>
|
||||
{page && (
|
||||
<SocialMetaTags
|
||||
title={
|
||||
(page.properties.Name as TitlePropertyValue).title[0]?.plain_text
|
||||
}
|
||||
description={
|
||||
(page.properties.Description as RichTextPropertyValue)
|
||||
.rich_text[0]?.plain_text
|
||||
}
|
||||
currentUrl={`https://www.typebot.io/blog/${
|
||||
(page.properties.Slug as RichTextPropertyValue).rich_text[0]
|
||||
?.plain_text
|
||||
}`}
|
||||
imagePreviewUrl={
|
||||
(page.properties.Thumbnail as RichTextPropertyValue).rich_text[0]
|
||||
?.plain_text
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Navbar />
|
||||
|
||||
<Container as="article" maxW="900px">
|
||||
{((page?.properties?.Published as CheckboxPropertyValue | undefined)
|
||||
?.checkbox ||
|
||||
!page) && (
|
||||
<Button
|
||||
as={NextChakraLink}
|
||||
href="/blog"
|
||||
colorScheme="gray"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
>
|
||||
{'<'} Blog
|
||||
</Button>
|
||||
)}
|
||||
{page ? (
|
||||
<>
|
||||
<VStack>
|
||||
<Heading as="h1" fontSize="5xl" textAlign="center" mt={6}>
|
||||
<NotionText
|
||||
text={(page.properties.Name as TitlePropertyValue).title}
|
||||
/>
|
||||
</Heading>
|
||||
<Heading
|
||||
fontSize="md"
|
||||
fontWeight="normal"
|
||||
textAlign="center"
|
||||
textColor="gray.500"
|
||||
>
|
||||
<NotionText
|
||||
text={
|
||||
(page.properties.Description as RichTextPropertyValue)
|
||||
.rich_text
|
||||
}
|
||||
/>
|
||||
</Heading>
|
||||
{(page.properties.Author as RichTextPropertyValue | undefined)
|
||||
?.rich_text[0]?.plain_text && (
|
||||
<Author
|
||||
author={
|
||||
(page.properties.Author as RichTextPropertyValue)
|
||||
.rich_text[0]?.plain_text
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</VStack>
|
||||
<Stack mt={6} spacing={4} maxW="700px" mx="auto">
|
||||
{blocks.map((block) => (
|
||||
<NotionBlock key={block.id} block={block} />
|
||||
))}
|
||||
</Stack>
|
||||
</>
|
||||
) : (
|
||||
<Text textAlign="center">Blog post not found</Text>
|
||||
)}
|
||||
</Container>
|
||||
</Stack>
|
||||
<Footer />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const getStaticPaths = async () => {
|
||||
if (!process.env.NOTION_DATABASE_ID)
|
||||
throw new Error("Couldn't find NOTION_DATABASE_ID")
|
||||
const database = await getFullDatabase(process.env.NOTION_DATABASE_ID)
|
||||
return {
|
||||
paths: database.filter(pageWithSlugAndId).map((page) => ({
|
||||
params: {
|
||||
slug: (page.properties.Slug as RichTextPropertyValue).rich_text[0]
|
||||
.plain_text,
|
||||
id: page.id,
|
||||
},
|
||||
})),
|
||||
fallback: true,
|
||||
}
|
||||
}
|
||||
|
||||
const pageWithSlugAndId = (page: Page) =>
|
||||
(page.properties.Slug as RichTextPropertyValue).rich_text[0]?.plain_text &&
|
||||
page.id
|
||||
|
||||
const Author = ({ author }: { author: string }) => {
|
||||
return (
|
||||
<HStack>
|
||||
<Image
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
src={/\(([^)]+)\)/.exec(author)![0].slice(1, -1)}
|
||||
width="30px"
|
||||
height="30px"
|
||||
className="rounded-full"
|
||||
alt="Author's picture"
|
||||
/>
|
||||
<Text>{author.split(' (')[0]}</Text>
|
||||
</HStack>
|
||||
)
|
||||
}
|
||||
export const getStaticProps = async (
|
||||
context: GetStaticPropsContext<{ slug: string; locale: 'fr' | 'en' }>
|
||||
) => {
|
||||
if (!process.env.NOTION_DATABASE_ID)
|
||||
throw new Error("Couldn't find NOTION_DATABASE_ID")
|
||||
if (!context.params) throw new Error("Couldn't find params")
|
||||
const { slug } = context.params
|
||||
const page = await getPage(process.env.NOTION_DATABASE_ID, slug)
|
||||
if (!page?.id) return
|
||||
const blocks = await getBlocks(page?.id)
|
||||
|
||||
const childBlocks = await Promise.all(
|
||||
blocks
|
||||
.filter((block) => block.has_children)
|
||||
.map(async (block) => {
|
||||
return {
|
||||
id: block.id,
|
||||
children: await getBlocks(block.id),
|
||||
}
|
||||
})
|
||||
)
|
||||
const blocksWithChildren = blocks.map((block) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
if (block.has_children && !block[block.type].children) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
block[block.type]['children'] = childBlocks.find(
|
||||
(x) => x.id === block.id
|
||||
)?.children
|
||||
}
|
||||
return block
|
||||
})
|
||||
|
||||
return {
|
||||
props: {
|
||||
page,
|
||||
blocks: blocksWithChildren,
|
||||
},
|
||||
revalidate: 1,
|
||||
}
|
||||
}
|
155
apps/landing-page/pages/blog/index.tsx
Normal file
155
apps/landing-page/pages/blog/index.tsx
Normal file
@ -0,0 +1,155 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
Box,
|
||||
Heading,
|
||||
Image,
|
||||
Text,
|
||||
Container,
|
||||
VStack,
|
||||
SimpleGrid,
|
||||
Flex,
|
||||
} from '@chakra-ui/react'
|
||||
import { getDatabase } from '../../lib/notion'
|
||||
import {
|
||||
DatePropertyValue,
|
||||
Page,
|
||||
RichText,
|
||||
RichTextPropertyValue,
|
||||
TitlePropertyValue,
|
||||
} from '@notionhq/client/build/src/api-types'
|
||||
import { NotionText } from 'notion-blocks-chakra-ui'
|
||||
import { Footer } from 'components/common/Footer'
|
||||
import { Navbar } from 'components/common/Navbar/Navbar'
|
||||
import { NextChakraLink } from 'components/common/nextChakraAdapters/NextChakraLink'
|
||||
import { SocialMetaTags } from 'components/common/SocialMetaTags'
|
||||
|
||||
const ArticleList = ({ posts }: { posts: Page[] }) => {
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
w="full"
|
||||
flexDir="column"
|
||||
>
|
||||
<SocialMetaTags
|
||||
title="Blog"
|
||||
description="Keep up to date with the latest news related to Typebot. Learn
|
||||
about conversationnal forms and how to convert more."
|
||||
currentUrl={`https://www.typebot.io/blog`}
|
||||
imagePreviewUrl={`https://www.typebot.io/images/previews/blog.png`}
|
||||
/>
|
||||
<Navbar />
|
||||
<VStack maxW="1200px" mt={20} pb={56}>
|
||||
<VStack maxW="700px">
|
||||
<Heading as="h1" fontSize="5xl">
|
||||
Blog
|
||||
</Heading>
|
||||
<Heading
|
||||
fontSize="md"
|
||||
fontWeight="normal"
|
||||
textAlign="center"
|
||||
textColor="gray.500"
|
||||
>
|
||||
Keep up to date with the latest news related to Typebot. Learn
|
||||
about conversationnal forms and how to convert more.
|
||||
</Heading>
|
||||
</VStack>
|
||||
|
||||
<Container maxW="1200px">
|
||||
<SimpleGrid columns={[1, 2, 3]} mt={6} py={4} spacing={10}>
|
||||
{posts.map((post) => (
|
||||
<BlogPost
|
||||
key={post.id}
|
||||
slug={`/${
|
||||
(post.properties.Slug as RichTextPropertyValue).rich_text[0]
|
||||
?.plain_text
|
||||
}`}
|
||||
title={
|
||||
(post.properties.Name as TitlePropertyValue).title[0]
|
||||
?.plain_text
|
||||
}
|
||||
description={
|
||||
(post.properties.Description as RichTextPropertyValue)
|
||||
.rich_text
|
||||
}
|
||||
imageSrc={
|
||||
(post.properties.Thumbnail as RichTextPropertyValue)
|
||||
.rich_text[0]?.plain_text
|
||||
}
|
||||
date={
|
||||
new Date(
|
||||
(post.properties.Created as DatePropertyValue)?.date
|
||||
?.start ?? ''
|
||||
)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Container>
|
||||
</VStack>
|
||||
</Flex>
|
||||
<Footer />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
type BlogPostProps = {
|
||||
slug: string
|
||||
title: string
|
||||
description: RichText[]
|
||||
imageSrc: string
|
||||
date: Date
|
||||
}
|
||||
|
||||
const BlogPost = ({
|
||||
slug,
|
||||
title,
|
||||
description,
|
||||
imageSrc,
|
||||
date,
|
||||
}: BlogPostProps) => (
|
||||
<NextChakraLink
|
||||
href={'/blog' + slug}
|
||||
w="100%"
|
||||
shadow="lg"
|
||||
p={4}
|
||||
rounded="lg"
|
||||
_hover={{ textDecoration: 'none' }}
|
||||
>
|
||||
<Box borderRadius="lg" overflow="hidden">
|
||||
<Image
|
||||
transform="scale(1.0)"
|
||||
src={imageSrc}
|
||||
objectFit="contain"
|
||||
width="100%"
|
||||
transition="0.3s ease-in-out"
|
||||
_hover={{
|
||||
transform: 'scale(1.05)',
|
||||
}}
|
||||
alt="title thumbnail"
|
||||
/>
|
||||
</Box>
|
||||
<Heading fontSize="xl" marginTop="4">
|
||||
{title}
|
||||
</Heading>
|
||||
<NotionText text={description} as="p" fontSize="md" marginTop="2" />
|
||||
<Text textColor="gray.400" fontSize="sm" mt={2}>
|
||||
{date.toDateString()}
|
||||
</Text>
|
||||
</NextChakraLink>
|
||||
)
|
||||
|
||||
export const getStaticProps = async () => {
|
||||
if (!process.env.NOTION_DATABASE_ID)
|
||||
throw new Error("Couldn't find NOTION_DATABASE_ID")
|
||||
const database = await getDatabase(process.env.NOTION_DATABASE_ID)
|
||||
return {
|
||||
props: {
|
||||
posts: database,
|
||||
},
|
||||
revalidate: 1,
|
||||
}
|
||||
}
|
||||
|
||||
export default ArticleList
|
Reference in New Issue
Block a user