📝 Add new blog structure
This commit is contained in:
38
apps/docs/contribute/guides/blog.mdx
Normal file
38
apps/docs/contribute/guides/blog.mdx
Normal file
@ -0,0 +1,38 @@
|
||||
---
|
||||
title: 'Contribute to the blog'
|
||||
sidebarTitle: 'Blog'
|
||||
icon: 'newspaper'
|
||||
---
|
||||
|
||||
The [official Typebot blog](https://typebot.io/blog) is a place where we share any ideas, original content related to the chatbot industry, and Typebot itself.
|
||||
|
||||
You are free to contribute to the blog or fix any typos you may find.
|
||||
|
||||
1. Head over to the [content folder](https://github.com/baptisteArno/typebot.io/tree/main/apps/landing-page/content) on the Github repo.
|
||||
2. Click on the blog post file you want to edit. Or create a new file by clicking on the `Add file` button.
|
||||
3. If you did not already have a fork of the repository, you will be prompted to create one.
|
||||
4. Once you're happy with your changes, hit `Commit changes...`.
|
||||
5. Click on `Create pull request`.
|
||||
6. Add a title and a description to describe your changes.
|
||||
7. Click on `Create pull request`.
|
||||
|
||||
It will be reviewed and merged if approved!
|
||||
|
||||
## New article guidelines
|
||||
|
||||
- The article should be related to chatbots, or Typebot.
|
||||
- The article should be written in English.
|
||||
- The article should be original content. No plagiarism.
|
||||
- The article should not be 100% AI-generated.
|
||||
|
||||
The mdx file should always start with the following frontmatter:
|
||||
|
||||
```md
|
||||
---
|
||||
title: 'My awesome blog post'
|
||||
publishedAt: '2023-11-19'
|
||||
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.
|
@ -40,8 +40,19 @@ Any contributions you make are **greatly appreciated**. There are many ways to c
|
||||
href="./guides/documentation"
|
||||
color="#97A0B1"
|
||||
>
|
||||
Help us improve the documentation by fixing typos, adding missing information
|
||||
or proposing new sections.
|
||||
Improve the documentation by fixing typos, adding missing information or
|
||||
proposing new sections.
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
title="Write official blog posts"
|
||||
icon="newspaper"
|
||||
iconType="duotone"
|
||||
href="./guides/blog"
|
||||
color="#97A0B1"
|
||||
>
|
||||
Write original content for Typebot's blog. Share your knowledge and ideas to a
|
||||
wider audience. The author will be credited.
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
|
@ -239,6 +239,7 @@
|
||||
"contribute/guides/local-installation",
|
||||
"contribute/guides/create-block",
|
||||
"contribute/guides/documentation",
|
||||
"contribute/guides/blog",
|
||||
"contribute/guides/translation"
|
||||
]
|
||||
},
|
||||
|
56
apps/landing-page/app/blog/Posts.tsx
Normal file
56
apps/landing-page/app/blog/Posts.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
'use client'
|
||||
import { Heading, Stack, Text } from '@chakra-ui/react'
|
||||
import { Link } from '@chakra-ui/next-js'
|
||||
|
||||
type Props = {
|
||||
allBlogs: {
|
||||
metadata: {
|
||||
title: string
|
||||
publishedAt: string
|
||||
}
|
||||
slug: string
|
||||
}[]
|
||||
}
|
||||
|
||||
export const Posts = ({ allBlogs }: Props) => (
|
||||
<Stack
|
||||
spacing={10}
|
||||
mx="auto"
|
||||
maxW="3xl"
|
||||
my="20"
|
||||
fontSize="17px"
|
||||
textAlign="justify"
|
||||
>
|
||||
<Heading>Latest blog posts:</Heading>
|
||||
<Stack>
|
||||
{allBlogs
|
||||
.filter((post) => post.metadata.publishedAt)
|
||||
.sort((a, b) => {
|
||||
if (
|
||||
new Date(a.metadata.publishedAt) > new Date(b.metadata.publishedAt)
|
||||
) {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
})
|
||||
.map((post) => (
|
||||
<Link key={post.slug} href={`/blog/${post.slug}`}>
|
||||
<Stack
|
||||
w="full"
|
||||
rounded="md"
|
||||
borderColor="gray.600"
|
||||
borderWidth={1}
|
||||
p="4"
|
||||
>
|
||||
<Heading as="h2" fontSize="2xl">
|
||||
{post.metadata.title}
|
||||
</Heading>
|
||||
<Text color="gray.500">
|
||||
{new Date(post.metadata.publishedAt).toDateString()}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Link>
|
||||
))}
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
80
apps/landing-page/app/blog/[slug]/Post.tsx
Normal file
80
apps/landing-page/app/blog/[slug]/Post.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
'use client'
|
||||
|
||||
import { Link } from '@chakra-ui/next-js'
|
||||
import { Heading, Stack, Text } from '@chakra-ui/react'
|
||||
import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote'
|
||||
import { highlight } from 'sugar-high'
|
||||
|
||||
type Props = {
|
||||
metadata: {
|
||||
title: string
|
||||
publishedAt: string
|
||||
}
|
||||
mdxSource: MDXRemoteSerializeResult
|
||||
}
|
||||
|
||||
export const Post = ({ metadata, mdxSource }: Props) => (
|
||||
<Stack spacing={10} my="20">
|
||||
<Stack mx="auto" w="full" maxW="65ch">
|
||||
<Heading>{metadata.title}</Heading>
|
||||
<Text>{formatDate(metadata.publishedAt)}</Text>
|
||||
</Stack>
|
||||
<Stack
|
||||
mx="auto"
|
||||
spacing={0}
|
||||
as="article"
|
||||
className="prose prose-quoteless prose-neutral prose-invert"
|
||||
>
|
||||
<MDXRemote
|
||||
{...mdxSource}
|
||||
components={{
|
||||
h1: (props) => <Heading as="h1" {...props} />,
|
||||
h2: (props) => <Heading as="h2" {...props} />,
|
||||
h3: (props) => <Heading as="h3" {...props} />,
|
||||
h4: (props) => <Heading as="h4" {...props} />,
|
||||
h5: (props) => <Heading as="h5" {...props} />,
|
||||
h6: (props) => <Heading as="h6" {...props} />,
|
||||
code: ({ children, ...props }) => {
|
||||
const codeHTML = highlight(children?.toString() ?? '')
|
||||
return (
|
||||
<code dangerouslySetInnerHTML={{ __html: codeHTML }} {...props} />
|
||||
)
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
link: (props: any) => <Link {...props} />,
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
|
||||
function formatDate(date: string) {
|
||||
const currentDate = new Date().getTime()
|
||||
if (!date.includes('T')) {
|
||||
date = `${date}T00:00:00`
|
||||
}
|
||||
const targetDate = new Date(date).getTime()
|
||||
const timeDifference = Math.abs(currentDate - targetDate)
|
||||
const daysAgo = Math.floor(timeDifference / (1000 * 60 * 60 * 24))
|
||||
|
||||
const fullDate = new Date(date).toLocaleString('en-us', {
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
})
|
||||
|
||||
if (daysAgo < 1) {
|
||||
return 'Today'
|
||||
} else if (daysAgo < 7) {
|
||||
return `${fullDate} (${daysAgo}d ago)`
|
||||
} else if (daysAgo < 30) {
|
||||
const weeksAgo = Math.floor(daysAgo / 7)
|
||||
return `${fullDate} (${weeksAgo}w ago)`
|
||||
} else if (daysAgo < 365) {
|
||||
const monthsAgo = Math.floor(daysAgo / 30)
|
||||
return `${fullDate} (${monthsAgo}mo ago)`
|
||||
} else {
|
||||
const yearsAgo = Math.floor(daysAgo / 365)
|
||||
return `${fullDate} (${yearsAgo}y ago)`
|
||||
}
|
||||
}
|
67
apps/landing-page/app/blog/[slug]/page.tsx
Normal file
67
apps/landing-page/app/blog/[slug]/page.tsx
Normal file
@ -0,0 +1,67 @@
|
||||
import type { Metadata } from 'next'
|
||||
import { notFound } from 'next/navigation'
|
||||
import { getBlogPosts } from '@/app/db/blog'
|
||||
import { Post } from './Post'
|
||||
import { serialize } from 'next-mdx-remote/serialize'
|
||||
import '@/assets/prose.css'
|
||||
import { env } from '@typebot.io/env'
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: { slug: string }
|
||||
}): Promise<Metadata | undefined> {
|
||||
const post = getBlogPosts().find(
|
||||
(post) => post.slug === params.slug && post.metadata.publishedAt
|
||||
)
|
||||
if (!post) {
|
||||
return
|
||||
}
|
||||
|
||||
const {
|
||||
title,
|
||||
publishedAt: publishedTime,
|
||||
summary: description,
|
||||
image,
|
||||
} = post.metadata
|
||||
const ogImage = image
|
||||
? `${env.LANDING_PAGE_URL}${image}`
|
||||
: `${env.LANDING_PAGE_URL}/og?title=${title}`
|
||||
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
type: 'article',
|
||||
publishedTime,
|
||||
url: `${env.LANDING_PAGE_URL}/blog/${post.slug}`,
|
||||
images: [
|
||||
{
|
||||
url: ogImage,
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title,
|
||||
description,
|
||||
images: [ogImage],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default async function Blog({ params }: { params: { slug: string } }) {
|
||||
const post = getBlogPosts().find(
|
||||
(post) => post.slug === params.slug && post.metadata.publishedAt
|
||||
)
|
||||
|
||||
if (!post) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
const mdxSource = await serialize(post.content)
|
||||
|
||||
return <Post metadata={post.metadata} mdxSource={mdxSource} />
|
||||
}
|
13
apps/landing-page/app/blog/page.tsx
Normal file
13
apps/landing-page/app/blog/page.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import { getBlogPosts } from '@/app/db/blog'
|
||||
import { Posts } from './Posts'
|
||||
|
||||
export const metadata = {
|
||||
title: 'Blog',
|
||||
description: 'Read my thoughts on software development, design, and more.',
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
const allBlogs = getBlogPosts()
|
||||
|
||||
return <Posts allBlogs={allBlogs} />
|
||||
}
|
60
apps/landing-page/app/db/blog.ts
Normal file
60
apps/landing-page/app/db/blog.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
type Metadata = {
|
||||
title: string
|
||||
publishedAt: string
|
||||
summary: string
|
||||
image?: string
|
||||
}
|
||||
|
||||
function parseFrontmatter(fileContent: string) {
|
||||
const frontmatterRegex = /---\s*([\s\S]*?)\s*---/
|
||||
const match = frontmatterRegex.exec(fileContent)
|
||||
const frontMatterBlock = match![1]
|
||||
const content = fileContent.replace(frontmatterRegex, '').trim()
|
||||
const frontMatterLines = frontMatterBlock.trim().split('\n')
|
||||
const metadata: Partial<Metadata> = {}
|
||||
|
||||
frontMatterLines.forEach((line) => {
|
||||
const [key, ...valueArr] = line.split(': ')
|
||||
let value = valueArr.join(': ').trim()
|
||||
value = value.replace(/^['"](.*)['"]$/, '$1') // Remove quotes
|
||||
metadata[key.trim() as keyof Metadata] = value
|
||||
})
|
||||
|
||||
return { metadata: metadata as Metadata, content }
|
||||
}
|
||||
|
||||
function getMDXFiles(dir: fs.PathLike) {
|
||||
return fs.readdirSync(dir).filter((file) => path.extname(file) === '.mdx')
|
||||
}
|
||||
|
||||
function readMDXFile(filePath: fs.PathOrFileDescriptor) {
|
||||
const rawContent = fs.readFileSync(filePath, 'utf-8')
|
||||
return parseFrontmatter(rawContent)
|
||||
}
|
||||
|
||||
function extractTweetIds(content: string) {
|
||||
const tweetMatches = content.match(/<StaticTweet\sid="[0-9]+"\s\/>/g)
|
||||
return tweetMatches?.map((tweet) => tweet.match(/[0-9]+/g)?.[0]) || []
|
||||
}
|
||||
|
||||
function getMDXData(dir: string) {
|
||||
const mdxFiles = getMDXFiles(dir)
|
||||
return mdxFiles.map((file) => {
|
||||
const { metadata, content } = readMDXFile(path.join(dir, file))
|
||||
const slug = path.basename(file, path.extname(file))
|
||||
const tweetIds = extractTweetIds(content)
|
||||
return {
|
||||
metadata,
|
||||
slug,
|
||||
tweetIds,
|
||||
content,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function getBlogPosts() {
|
||||
return getMDXData(path.join(process.cwd(), 'content'))
|
||||
}
|
42
apps/landing-page/app/layout.tsx
Normal file
42
apps/landing-page/app/layout.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
/* eslint-disable @next/next/no-sync-scripts */
|
||||
/* eslint-disable @next/next/no-page-custom-font */
|
||||
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 = {
|
||||
title: 'Typebot - Open-source conversational apps builder',
|
||||
description:
|
||||
'Powerful blocks to create unique chat experiences. Embed them anywhere on your apps and start collecting results like magic.',
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&family=Open+Sans:wght@400;500;600;700&family=Indie+Flower:wght@400&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<script src="/__ENV.js" />
|
||||
</head>
|
||||
|
||||
<body style={{ backgroundColor: '#171923' }}>
|
||||
<Providers>
|
||||
<Header />
|
||||
{children}
|
||||
<EndCta />
|
||||
<Footer />
|
||||
</Providers>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
59
apps/landing-page/app/og/route.tsx
Normal file
59
apps/landing-page/app/og/route.tsx
Normal file
@ -0,0 +1,59 @@
|
||||
import { ImageResponse } from 'next/og'
|
||||
import { NextRequest } from 'next/server'
|
||||
import { env } from '@typebot.io/env'
|
||||
|
||||
export const runtime = 'edge'
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const { searchParams } = req.nextUrl
|
||||
const postTitle = searchParams.get('title')
|
||||
|
||||
const font = fetch(
|
||||
new URL('../../assets/Outfit-Medium.ttf', import.meta.url)
|
||||
).then((res) => res.arrayBuffer())
|
||||
const fontData = await font
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'center',
|
||||
backgroundImage: `url(${env.LANDING_PAGE_URL}/images/og-bg.png)`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
marginLeft: 190,
|
||||
marginRight: 190,
|
||||
display: 'flex',
|
||||
fontSize: 130,
|
||||
fontFamily: 'Outfit',
|
||||
letterSpacing: '-0.05em',
|
||||
fontStyle: 'normal',
|
||||
color: 'white',
|
||||
lineHeight: '120px',
|
||||
whiteSpace: 'pre-wrap',
|
||||
}}
|
||||
>
|
||||
{postTitle}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
width: 1280,
|
||||
height: 720,
|
||||
fonts: [
|
||||
{
|
||||
name: 'Outfit',
|
||||
data: fontData,
|
||||
style: 'normal',
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
}
|
13
apps/landing-page/app/providers.tsx
Normal file
13
apps/landing-page/app/providers.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
'use client'
|
||||
|
||||
import { theme } from '@/lib/chakraTheme'
|
||||
import { ChakraProvider, ColorModeScript } from '@chakra-ui/react'
|
||||
|
||||
export function Providers({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<ChakraProvider theme={theme}>
|
||||
<ColorModeScript initialColorMode={theme.config.initialColorMode} />
|
||||
{children}
|
||||
</ChakraProvider>
|
||||
)
|
||||
}
|
BIN
apps/landing-page/assets/Outfit-Medium.ttf
Normal file
BIN
apps/landing-page/assets/Outfit-Medium.ttf
Normal file
Binary file not shown.
15
apps/landing-page/assets/prose.css
Normal file
15
apps/landing-page/assets/prose.css
Normal file
@ -0,0 +1,15 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--sh-class: #5395e5;
|
||||
--sh-identifier: white;
|
||||
--sh-sign: #8996a3;
|
||||
--sh-property: #5395e5;
|
||||
--sh-entity: #249a97;
|
||||
--sh-jsxliterals: #6266d1;
|
||||
--sh-string: #00a99a;
|
||||
--sh-keyword: #f47067;
|
||||
--sh-comment: #a19595;
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
'use client'
|
||||
|
||||
import { Heading, Button, Text, Flex, VStack } from '@chakra-ui/react'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
|
@ -1,3 +1,5 @@
|
||||
'use client'
|
||||
|
||||
import React, { ReactNode } from 'react'
|
||||
|
||||
import {
|
||||
|
@ -1,3 +1,5 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
Button,
|
||||
Flex,
|
||||
|
8
apps/landing-page/content/example.mdx
Normal file
8
apps/landing-page/content/example.mdx
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
title: 'Blog post example'
|
||||
summary: 'A short summary of the blog post.'
|
||||
---
|
||||
|
||||
This is a blog post example.
|
||||
|
||||
This can be deleted once we published the first blog post.
|
1
apps/landing-page/next-env.d.ts
vendored
1
apps/landing-page/next-env.d.ts
vendored
@ -1,5 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
/// <reference types="next/navigation-types/compat/navigation" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
|
@ -10,9 +10,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@chakra-ui/icon": "3.0.15",
|
||||
"@chakra-ui/next-js": "2.2.0",
|
||||
"@chakra-ui/react": "2.7.1",
|
||||
"@emotion/react": "11.11.1",
|
||||
"@emotion/styled": "11.11.0",
|
||||
"@typebot.io/billing": "workspace:*",
|
||||
"@typebot.io/lib": "workspace:*",
|
||||
"@typebot.io/nextjs": "workspace:*",
|
||||
"@typebot.io/prisma": "workspace:*",
|
||||
@ -21,13 +23,16 @@
|
||||
"focus-visible": "5.2.0",
|
||||
"framer-motion": "10.12.20",
|
||||
"next": "14.1.0",
|
||||
"next-mdx-remote": "4.4.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"@typebot.io/billing": "workspace:*"
|
||||
"sugar-high": "0.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.22.9",
|
||||
"@chakra-ui/styled-system": "2.9.1",
|
||||
"@tailwindcss/typography": "0.5.12",
|
||||
"@typebot.io/env": "workspace:*",
|
||||
"@typebot.io/tsconfig": "workspace:*",
|
||||
"@types/aos": "3.0.4",
|
||||
"@types/node": "20.4.2",
|
||||
@ -38,10 +43,10 @@
|
||||
"eslint": "8.44.0",
|
||||
"eslint-config-custom": "workspace:*",
|
||||
"next-runtime-env": "1.6.2",
|
||||
"@typebot.io/env": "workspace:*",
|
||||
"next-transpile-modules": "10.0.0",
|
||||
"postcss": "8.4.26",
|
||||
"prettier": "3.0.0",
|
||||
"tailwindcss": "3.3.3",
|
||||
"typescript": "5.3.2"
|
||||
}
|
||||
}
|
||||
|
6
apps/landing-page/postcss.config.js
Normal file
6
apps/landing-page/postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
BIN
apps/landing-page/public/images/og-bg.png
Normal file
BIN
apps/landing-page/public/images/og-bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 501 KiB |
8
apps/landing-page/tailwind.config.js
Normal file
8
apps/landing-page/tailwind.config.js
Normal file
@ -0,0 +1,8 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ['./app/blog/**/*.{js,ts,jsx,tsx,mdx}'],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [require('@tailwindcss/typography')],
|
||||
}
|
@ -3,8 +3,14 @@
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
"@/*": ["./*"]
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"strictNullChecks": true
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"]
|
||||
}
|
||||
|
Reference in New Issue
Block a user