📝 Improve blog capabilities and components
This commit is contained in:
@ -36,3 +36,17 @@ summary: 'A short summary of the blog post.'
|
||||
```
|
||||
|
||||
By default the og image is generated from the title of the blog post. If you want to use a custom og image, you can specify a `image` field in the frontmatter.
|
||||
|
||||
All images need to be placed under the `public/images/blog/<POST_FILENAME>` folder where `<POST_FILENAME>` is the name of the mdx file you are creating/editing.
|
||||
|
||||
Here are all the components you can use in your blog post:
|
||||
|
||||
- `Image`: To display an image. Example: `<Image src="/images/blog/my-awesome-blog-post/image.jpg" alt="My awesome image" />`
|
||||
- `Callout`: To display a callout. Example: `<Callout status="info">This is an info callout</Callout>`. You can provide a `status` prop with the value `info`, `warning`, `success`, or `error`.
|
||||
- `Tweet`: To embed a tweet. Example: `<Tweet id="1234567890123456789" />`
|
||||
- `Typebot`: To embed a typebot as a Standard component. Example: `<Typebot typebot="<YOUR_BOT_PUBLIC_ID>" />`. You can provide the same props as the [Standard component](../../deploy/web/libraries/react#standard).
|
||||
- `YouTube`: To embed a YouTube video. Example: `<YouTube id="<YOUTUBE_VIDEO_ID>" />`
|
||||
- `Loom`: To embed a Loom video. Example: `<Loom id="<LOOM_VIDEO_ID>" />`
|
||||
- `Cta`: To display a call-to-action that redirects to Typebot. Example: `<Cta />`
|
||||
|
||||
For rendering tables you should use the native html table related tags.
|
||||
|
@ -12033,6 +12033,9 @@
|
||||
"content": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"maxBubbleWidth": {
|
||||
"type": "number"
|
||||
},
|
||||
|
@ -1,9 +1,29 @@
|
||||
/* eslint-disable jsx-a11y/alt-text */
|
||||
'use client'
|
||||
|
||||
import { Link } from '@chakra-ui/next-js'
|
||||
import { Heading, Stack, Text } from '@chakra-ui/react'
|
||||
import {
|
||||
Alert,
|
||||
AlertIcon,
|
||||
Heading,
|
||||
Stack,
|
||||
Table,
|
||||
TableCaption,
|
||||
TableContainer,
|
||||
Tbody,
|
||||
Td,
|
||||
Text,
|
||||
Tfoot,
|
||||
Th,
|
||||
Thead,
|
||||
Tr,
|
||||
Image,
|
||||
} from '@chakra-ui/react'
|
||||
import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote'
|
||||
import { highlight } from 'sugar-high'
|
||||
import { Tweet } from './Tweet'
|
||||
import { Standard } from '@typebot.io/nextjs'
|
||||
import { EndCta } from '@/components/Homepage/EndCta'
|
||||
|
||||
type Props = {
|
||||
metadata: {
|
||||
@ -14,7 +34,7 @@ type Props = {
|
||||
}
|
||||
|
||||
export const Post = ({ metadata, mdxSource }: Props) => (
|
||||
<Stack spacing={10} my="20">
|
||||
<Stack spacing={10} my="20" w="full">
|
||||
<Stack mx="auto" w="full" maxW="65ch">
|
||||
<Heading>{metadata.title}</Heading>
|
||||
<Text>{formatDate(metadata.publishedAt)}</Text>
|
||||
@ -23,7 +43,7 @@ export const Post = ({ metadata, mdxSource }: Props) => (
|
||||
mx="auto"
|
||||
spacing={0}
|
||||
as="article"
|
||||
className="prose prose-quoteless prose-neutral prose-invert"
|
||||
className="prose prose-quoteless prose-neutral prose-invert max-w-none w-full px-3 sm:px-0"
|
||||
>
|
||||
<MDXRemote
|
||||
{...mdxSource}
|
||||
@ -42,6 +62,87 @@ export const Post = ({ metadata, mdxSource }: Props) => (
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
link: (props: any) => <Link {...props} />,
|
||||
Image: (props) => (
|
||||
<Image rounded="md" maxW={['full', '65ch']} {...props} />
|
||||
),
|
||||
Callout: ({ children, ...props }) => (
|
||||
<Alert rounded="md" {...props}>
|
||||
<AlertIcon />
|
||||
{children}
|
||||
</Alert>
|
||||
),
|
||||
Tweet,
|
||||
Typebot: Standard,
|
||||
Youtube: ({ id }: { id: string }) => (
|
||||
<div className="w-full">
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
paddingBottom: '64.63195691202873%',
|
||||
height: 0,
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<iframe
|
||||
src={`https://www.youtube.com/embed/${id}`}
|
||||
allowFullScreen
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
></iframe>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
Loom: ({ id }: { id: string }) => (
|
||||
<div className="w-full">
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
paddingBottom: '64.63195691202873%',
|
||||
height: 0,
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<iframe
|
||||
src={`https://www.loom.com/embed/${id}`}
|
||||
allowFullScreen
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
></iframe>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
Cta: (props) => (
|
||||
<EndCta
|
||||
{...props}
|
||||
style={{ maxWidth: 'none' }}
|
||||
w="full"
|
||||
height="70vh"
|
||||
className="w-full"
|
||||
bgGradient={undefined}
|
||||
/>
|
||||
),
|
||||
table: (props) => (
|
||||
<TableContainer>
|
||||
<Table {...props} />
|
||||
</TableContainer>
|
||||
),
|
||||
thead: Thead,
|
||||
tbody: Tbody,
|
||||
th: Th,
|
||||
td: Td,
|
||||
tfoot: Tfoot,
|
||||
tr: Tr,
|
||||
caption: TableCaption,
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
|
36
apps/landing-page/app/blog/[slug]/Tweet.tsx
Normal file
36
apps/landing-page/app/blog/[slug]/Tweet.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import { getTweet } from 'react-tweet/api'
|
||||
import { EmbeddedTweet, TweetNotFound, type TweetProps } from 'react-tweet'
|
||||
import './tweet.css'
|
||||
|
||||
const TweetContent = async ({ id, components, onError }: TweetProps) => {
|
||||
let error
|
||||
const tweet = id
|
||||
? await getTweet(id).catch((err) => {
|
||||
if (onError) {
|
||||
error = onError(err)
|
||||
} else {
|
||||
console.error(err)
|
||||
error = err
|
||||
}
|
||||
})
|
||||
: undefined
|
||||
|
||||
if (!tweet) {
|
||||
const NotFound = components?.TweetNotFound || TweetNotFound
|
||||
return <NotFound error={error} />
|
||||
}
|
||||
|
||||
return <EmbeddedTweet tweet={tweet} components={components} />
|
||||
}
|
||||
|
||||
export const ReactTweet = (props: TweetProps) => <TweetContent {...props} />
|
||||
|
||||
export async function Tweet({ id }: { id: string }) {
|
||||
return (
|
||||
<div className="tweet my-6">
|
||||
<div className={`flex justify-center`}>
|
||||
<ReactTweet id={id} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
63
apps/landing-page/app/blog/[slug]/tweet.css
Normal file
63
apps/landing-page/app/blog/[slug]/tweet.css
Normal file
@ -0,0 +1,63 @@
|
||||
/* Light theme (default) */
|
||||
.tweet .react-tweet-theme {
|
||||
/* margin is handled by our wrappers */
|
||||
--tweet-container-margin: 0;
|
||||
--tweet-font-family: inherit;
|
||||
--tweet-font-color: inherit;
|
||||
|
||||
--tweet-bg-color: #222;
|
||||
--tweet-bg-color-hover: var(--tweet-bg-color);
|
||||
--tweet-quoted-bg-color-hover: rgba(255, 255, 255, 0.03);
|
||||
--tweet-border: 1px solid #333;
|
||||
--tweet-color-blue-secondary: theme('colors.white');
|
||||
--tweet-color-blue-secondary-hover: #333;
|
||||
--tweet-font-color-secondary: theme('colors.gray.400');
|
||||
|
||||
/* Common properties for both themes */
|
||||
--tweet-quoted-bg-color-hover: rgba(0, 0, 0, 0.03);
|
||||
--tweet-border: 1px solid rgb(64, 64, 64);
|
||||
--tweet-skeleton-gradient: linear-gradient(
|
||||
270deg,
|
||||
#fafafa,
|
||||
#eaeaea,
|
||||
#eaeaea,
|
||||
#fafafa
|
||||
);
|
||||
--tweet-color-red-primary: rgb(249, 24, 128);
|
||||
--tweet-color-red-primary-hover: rgba(249, 24, 128, 0.1);
|
||||
--tweet-color-green-primary: rgb(0, 186, 124);
|
||||
--tweet-color-green-primary-hover: rgba(0, 186, 124, 0.1);
|
||||
--tweet-twitter-icon-color: var(--tweet-font-color);
|
||||
--tweet-verified-old-color: rgb(130, 154, 171);
|
||||
--tweet-verified-blue-color: var(--tweet-color-blue-primary);
|
||||
|
||||
--tweet-actions-font-weight: 500;
|
||||
--tweet-replies-font-weight: 500;
|
||||
}
|
||||
|
||||
/* Common styles for both themes */
|
||||
.tweet .react-tweet-theme p {
|
||||
font-size: inherit;
|
||||
line-height: 1.3rem;
|
||||
}
|
||||
|
||||
.tweet .react-tweet-theme p a {
|
||||
@apply border-b transition-[border-color] border-gray-500 text-white hover:border-white;
|
||||
}
|
||||
|
||||
/* Remove link underline on hover for both themes */
|
||||
.tweet .react-tweet-theme p a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.tweet a div {
|
||||
@apply font-medium tracking-tight;
|
||||
}
|
||||
|
||||
.tweet div[class*='mediaWrapper'] {
|
||||
max-height: 250px;
|
||||
}
|
||||
|
||||
.tweet .react-tweet-theme img {
|
||||
margin: 0;
|
||||
}
|
@ -4,7 +4,6 @@ import type { Metadata } from 'next'
|
||||
import { Header } from 'components/common/Header/Header'
|
||||
import { Footer } from 'components/common/Footer'
|
||||
import { Providers } from './providers'
|
||||
import { EndCta } from '@/components/Homepage/EndCta'
|
||||
import 'assets/style.css'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@ -33,7 +32,6 @@ export default function RootLayout({
|
||||
<Providers>
|
||||
<Header />
|
||||
{children}
|
||||
<EndCta />
|
||||
<Footer />
|
||||
</Providers>
|
||||
</body>
|
||||
|
@ -13,3 +13,9 @@
|
||||
--sh-keyword: #f47067;
|
||||
--sh-comment: #a19595;
|
||||
}
|
||||
|
||||
.prose > * {
|
||||
max-width: 65ch;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
@ -1,11 +1,18 @@
|
||||
'use client'
|
||||
|
||||
import { Heading, Button, Text, Flex, VStack } from '@chakra-ui/react'
|
||||
import {
|
||||
Heading,
|
||||
Button,
|
||||
Text,
|
||||
Flex,
|
||||
VStack,
|
||||
StackProps,
|
||||
} from '@chakra-ui/react'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
import { BackgroundPolygons } from './Hero/BackgroundPolygons'
|
||||
|
||||
export const EndCta = () => {
|
||||
export const EndCta = (props: StackProps) => {
|
||||
return (
|
||||
<VStack
|
||||
as="section"
|
||||
@ -14,6 +21,7 @@ export const EndCta = () => {
|
||||
bgGradient="linear(to-b, gray.900, gray.800)"
|
||||
height="100vh"
|
||||
justifyContent="center"
|
||||
{...props}
|
||||
>
|
||||
<BackgroundPolygons />
|
||||
<VStack
|
||||
@ -29,7 +37,7 @@ export const EndCta = () => {
|
||||
letterSpacing="tight"
|
||||
data-aos="fade-up"
|
||||
>
|
||||
Take your forms to the next level
|
||||
Improve conversion and user engagement with typebots
|
||||
</Heading>
|
||||
<Flex>
|
||||
<Button
|
||||
@ -46,7 +54,7 @@ export const EndCta = () => {
|
||||
</Flex>
|
||||
|
||||
<Text color="gray.400" data-aos="fade-up" data-aos-delay="400">
|
||||
No trial. Generous, unlimited <strong>free</strong> plan.
|
||||
No trial. Generous <strong>free</strong> plan.
|
||||
</Text>
|
||||
</VStack>
|
||||
</VStack>
|
||||
|
@ -3,6 +3,18 @@ title: 'Blog post example'
|
||||
summary: 'A short summary of the blog post.'
|
||||
---
|
||||
|
||||
This is a blog post example.
|
||||
<Image src="/images/builder-screenshot.png" alt="awesome image" />
|
||||
|
||||
This can be deleted once we published the first blog post.
|
||||
<Callout rounded='md'>
|
||||
|
||||
This is a callout. It can be used to highlight important information.
|
||||
|
||||
</Callout>
|
||||
|
||||
<Tweet id="1780513200565285038" />
|
||||
|
||||
Occaecat nostrud fugiat aliqua non deserunt ad adipisicing amet anim do commodo aliquip ipsum esse. Aute nisi voluptate nisi excepteur nulla velit incididunt aute laborum culpa Lorem magna reprehenderit. Aliqua cupidatat nisi ut nisi. Cillum irure enim officia aute nulla. Sunt culpa deserunt sunt. Laborum ipsum ad proident cillum officia culpa enim do id quis eiusmod.
|
||||
|
||||
<Cta />
|
||||
|
||||
Occaecat nostrud fugiat aliqua non deserunt ad adipisicing amet anim do commodo aliquip ipsum esse. Aute nisi voluptate nisi excepteur nulla velit incididunt aute laborum culpa Lorem magna reprehenderit. Aliqua cupidatat nisi ut nisi. Cillum irure enim officia aute nulla. Sunt culpa deserunt sunt. Laborum ipsum ad proident cillum officia culpa enim do id quis eiusmod.
|
||||
|
@ -26,6 +26,7 @@
|
||||
"next-mdx-remote": "4.4.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-tweet": "3.2.1",
|
||||
"sugar-high": "0.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
32
pnpm-lock.yaml
generated
32
pnpm-lock.yaml
generated
@ -508,6 +508,9 @@ importers:
|
||||
react-dom:
|
||||
specifier: 18.2.0
|
||||
version: 18.2.0(react@18.2.0)
|
||||
react-tweet:
|
||||
specifier: ^3.2.1
|
||||
version: 3.2.1(react-dom@18.2.0)(react@18.2.0)
|
||||
sugar-high:
|
||||
specifier: 0.6.0
|
||||
version: 0.6.0
|
||||
@ -9302,6 +9305,12 @@ packages:
|
||||
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
|
||||
dev: false
|
||||
|
||||
/@swc/helpers@0.5.10:
|
||||
resolution: {integrity: sha512-CU+RF9FySljn7HVSkkjiB84hWkvTaI3rtLvF433+jRSBL2hMu3zX5bGhHS8C80SM++h4xy8hBSnUHFQHmRXSBw==}
|
||||
dependencies:
|
||||
tslib: 2.6.0
|
||||
dev: false
|
||||
|
||||
/@swc/helpers@0.5.2:
|
||||
resolution: {integrity: sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==}
|
||||
dependencies:
|
||||
@ -20347,6 +20356,19 @@ packages:
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/react-tweet@3.2.1(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-dktP3RMuwRB4pnSDocKpSsW5Hq1IXRW6fONkHhxT5EBIXsKZzdQuI70qtub1XN2dtZdkJWWxfBm/Q+kN+vRYFA==}
|
||||
peerDependencies:
|
||||
react: '>= 18.0.0'
|
||||
react-dom: '>= 18.0.0'
|
||||
dependencies:
|
||||
'@swc/helpers': 0.5.10
|
||||
clsx: 2.0.0
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
swr: 2.2.5(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/react-universal-interface@0.6.2(react@18.2.0)(tslib@2.6.0):
|
||||
resolution: {integrity: sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==}
|
||||
peerDependencies:
|
||||
@ -21747,6 +21769,16 @@ packages:
|
||||
use-sync-external-store: 1.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/swr@2.2.5(react@18.2.0):
|
||||
resolution: {integrity: sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==}
|
||||
peerDependencies:
|
||||
react: ^16.11.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
client-only: 0.0.1
|
||||
react: 18.2.0
|
||||
use-sync-external-store: 1.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/swrev@4.0.0:
|
||||
resolution: {integrity: sha512-LqVcOHSB4cPGgitD1riJ1Hh4vdmITOp+BkmfmXRh4hSF/t7EnS4iD+SOTmq7w5pPm/SiPeto4ADbKS6dHUDWFA==}
|
||||
dev: false
|
||||
|
Reference in New Issue
Block a user