8
packages/twenty-website/src/app/api/github/route.ts
Normal file
8
packages/twenty-website/src/app/api/github/route.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import {NextRequest, NextResponse} from "next/server";
|
||||
|
||||
export async function GET (request: NextRequest){
|
||||
const response = await fetch('https://api.github.com/repos/twentyhq/twenty/releases');
|
||||
const data = await response.json();
|
||||
|
||||
return NextResponse.json(data);
|
||||
}
|
||||
4
packages/twenty-website/src/app/blog/[slug]/page.tsx
Normal file
4
packages/twenty-website/src/app/blog/[slug]/page.tsx
Normal file
@ -0,0 +1,4 @@
|
||||
export default async function BlogPost({ params }: { params: { slug: string } }) {
|
||||
const posts = {};
|
||||
return <>Blog Post: {params.slug}</>;
|
||||
}
|
||||
0
packages/twenty-website/src/app/blog/list-posts.tsx
Normal file
0
packages/twenty-website/src/app/blog/list-posts.tsx
Normal file
6
packages/twenty-website/src/app/blog/page.tsx
Normal file
6
packages/twenty-website/src/app/blog/page.tsx
Normal file
@ -0,0 +1,6 @@
|
||||
|
||||
|
||||
export default async function BlogHome() {
|
||||
const posts = {};
|
||||
return <>Blog Home</>;
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
'use client'
|
||||
|
||||
export const ContentContainer = ({children}: {children?: React.ReactNode}) => {
|
||||
return (
|
||||
<div style={{
|
||||
width: '600px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}>{children}</div>
|
||||
)
|
||||
}
|
||||
106
packages/twenty-website/src/app/components/FooterNav.tsx
Normal file
106
packages/twenty-website/src/app/components/FooterNav.tsx
Normal file
@ -0,0 +1,106 @@
|
||||
'use client'
|
||||
|
||||
import styled from '@emotion/styled'
|
||||
import { Logo } from './Logo';
|
||||
import { DiscordIcon, GithubIcon, LinkedInIcon, XIcon } from "./Icons";
|
||||
|
||||
|
||||
const FooterContainer = styled.div`
|
||||
padding: 64px 96px 64px 96px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: rgb(129, 129, 129);
|
||||
gap: 32px;
|
||||
`;
|
||||
|
||||
const LeftSideFooter = styled.div`
|
||||
width: 36Opx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;`;
|
||||
|
||||
const RightSideFooter = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;`;
|
||||
|
||||
const RightSideFooterColumn = styled.div`
|
||||
width: 160px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
`;
|
||||
|
||||
const RightSideFooterLink = styled.a`
|
||||
color: rgb(129, 129, 129);
|
||||
text-decoration: none;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
color: #000;
|
||||
}`;
|
||||
|
||||
const RightSideFooterColumnTitle = styled.div`
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
color: #000;
|
||||
`;
|
||||
|
||||
|
||||
|
||||
export const FooterNav = () => {
|
||||
return <FooterContainer>
|
||||
<div style={{ width: '100%', display: 'flex', flexDirection: 'row', justifyContent:'space-between'}}>
|
||||
<LeftSideFooter>
|
||||
<Logo />
|
||||
<div>
|
||||
The #1 Open Source CRM
|
||||
</div>
|
||||
</LeftSideFooter>
|
||||
<RightSideFooter>
|
||||
<RightSideFooterColumn>
|
||||
<RightSideFooterColumnTitle>Company</RightSideFooterColumnTitle>
|
||||
<RightSideFooterLink href='/'>Pricing</RightSideFooterLink>
|
||||
<RightSideFooterLink href='/'>Story</RightSideFooterLink>
|
||||
</RightSideFooterColumn>
|
||||
<RightSideFooterColumn>
|
||||
<RightSideFooterColumnTitle>Resources</RightSideFooterColumnTitle>
|
||||
<RightSideFooterLink href='https://docs.twenty.com'>Documentation</RightSideFooterLink>
|
||||
<RightSideFooterLink href='/releases'>Changelog</RightSideFooterLink>
|
||||
</RightSideFooterColumn>
|
||||
<RightSideFooterColumn>
|
||||
<RightSideFooterColumnTitle>Other</RightSideFooterColumnTitle>
|
||||
<RightSideFooterLink href='/oss-friends'>OSS Friends</RightSideFooterLink>
|
||||
<RightSideFooterLink href='/legal/terms'>Terms of Service</RightSideFooterLink>
|
||||
<RightSideFooterLink href='/legal/privacy'>Privacy Policy</RightSideFooterLink>
|
||||
</RightSideFooterColumn>
|
||||
</RightSideFooter>
|
||||
</div>
|
||||
<div style={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent:'space-between',
|
||||
borderTop: '1px solid rgb(179, 179, 179)',
|
||||
paddingTop: '32px'
|
||||
}}>
|
||||
<div>
|
||||
<span style={{fontFamily: "Inter, sans-serif"}}>©</span>
|
||||
2023 Twenty PBC
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', justifyContent:'space-between', gap:'10px'}}>
|
||||
<a href="https://x.com/twentycrm" target="_blank">
|
||||
<XIcon size='M'/>
|
||||
</a>
|
||||
<a href="https://github.com/twentyhq/twenty" target="_blank">
|
||||
<GithubIcon size='M'/>
|
||||
</a>
|
||||
<a href="https://www.linkedin.com/company/twenty" target="_blank">
|
||||
<LinkedInIcon size='M'/>
|
||||
</a>
|
||||
<a href="https://discord.gg/UfGNZJfAG6" target="_blank">
|
||||
<DiscordIcon size='M' />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</FooterContainer>
|
||||
;
|
||||
}
|
||||
133
packages/twenty-website/src/app/components/HeaderNav.tsx
Normal file
133
packages/twenty-website/src/app/components/HeaderNav.tsx
Normal file
@ -0,0 +1,133 @@
|
||||
'use client'
|
||||
|
||||
import styled from '@emotion/styled'
|
||||
import { Logo } from './Logo';
|
||||
import { IBM_Plex_Mono } from 'next/font/google';
|
||||
import { GithubIcon } from './Icons';
|
||||
|
||||
const IBMPlexMono = IBM_Plex_Mono({
|
||||
weight: '500',
|
||||
subsets: ['latin'],
|
||||
display: 'swap',
|
||||
});
|
||||
|
||||
|
||||
|
||||
const Nav = styled.nav`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
overflow: visible;
|
||||
padding: 12px 16px 12px 16px;
|
||||
position: relative;
|
||||
border-color: rgba(20, 20, 20, 0.08);
|
||||
transform-origin: 50% 50% 0px;
|
||||
border-bottom: 1px solid var(--Borders-Light, #F1F1F1);
|
||||
`;
|
||||
|
||||
const LinkList = styled.div`
|
||||
display:flex;
|
||||
flex-direction: row;
|
||||
`;
|
||||
|
||||
const ListItem = styled.a`
|
||||
color: rgb(71, 71, 71);
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
height: 40px;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
&:hover {
|
||||
background-color: #F1F1F1;
|
||||
}
|
||||
`;
|
||||
|
||||
const LogoContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width:202px;`;
|
||||
|
||||
const LogoAddon = styled.div`
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 150%;
|
||||
`;
|
||||
|
||||
const StyledButton = styled.div`
|
||||
display: flex;
|
||||
height: 40px;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
align-items: center;
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
outline: inherit;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const CallToActionContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const LinkNextToCTA = styled.a`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: rgb(71, 71, 71);
|
||||
span {
|
||||
text-decoration: underline;
|
||||
}`;
|
||||
|
||||
const CallToAction = () => {
|
||||
return <CallToActionContainer>
|
||||
<LinkNextToCTA href="https://github.com/twentyhq/twenty">Sign in</LinkNextToCTA>
|
||||
<a href="#">
|
||||
<StyledButton>
|
||||
Get Started
|
||||
</StyledButton>
|
||||
</a>
|
||||
</CallToActionContainer>;
|
||||
};
|
||||
|
||||
|
||||
const ExternalArrow = () => {
|
||||
return <div style={{width:'14px', height:'14px', fill: 'rgb(179, 179, 179)'}}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" focusable="false" color="rgb(179, 179, 179)"><g color="rgb(179, 179, 179)" ><path d="M200,64V168a8,8,0,0,1-16,0V83.31L69.66,197.66a8,8,0,0,1-11.32-11.32L172.69,72H88a8,8,0,0,1,0-16H192A8,8,0,0,1,200,64Z"></path></g></svg>
|
||||
</div>
|
||||
|
||||
}
|
||||
|
||||
export const HeaderNav = () => {
|
||||
|
||||
const isTwentyDev = false;
|
||||
|
||||
return <Nav>
|
||||
<LogoContainer>
|
||||
<Logo />
|
||||
{isTwentyDev && <LogoAddon className={IBMPlexMono.className}>for Developers</LogoAddon>}
|
||||
</LogoContainer>
|
||||
<LinkList>
|
||||
<ListItem href="/pricing">Pricing</ListItem>
|
||||
<ListItem href="/story">Story</ListItem>
|
||||
<ListItem href="http://docs.twenty.com">Docs <ExternalArrow /></ListItem>
|
||||
<ListItem href="http://docs.twenty.com"><GithubIcon color='rgb(71,71,71)' /> 5.7k <ExternalArrow /></ListItem>
|
||||
</LinkList>
|
||||
<CallToAction />
|
||||
</Nav>;
|
||||
};
|
||||
|
||||
41
packages/twenty-website/src/app/components/Icons.tsx
Normal file
41
packages/twenty-website/src/app/components/Icons.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
const getSize = size => {
|
||||
switch(size) {
|
||||
case 'S':
|
||||
return '14px';
|
||||
case 'M':
|
||||
return '24px';
|
||||
case 'L':
|
||||
return '48px';
|
||||
default:
|
||||
return '14px';
|
||||
}
|
||||
};
|
||||
|
||||
export const GithubIcon = ({size = 'S', color = 'rgb(179, 179, 179)'}) => {
|
||||
let dimension = getSize(size);
|
||||
return <div style={{width: dimension, height: dimension}}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14"><path d="M 6.979 0 C 3.12 0 0 3.143 0 7.031 C 0 10.139 1.999 12.77 4.772 13.701 C 5.119 13.771 5.246 13.55 5.246 13.364 C 5.246 13.201 5.234 12.642 5.234 12.06 C 3.293 12.479 2.889 11.222 2.889 11.222 C 2.577 10.407 2.114 10.197 2.114 10.197 C 1.479 9.767 2.161 9.767 2.161 9.767 C 2.866 9.813 3.235 10.488 3.235 10.488 C 3.859 11.559 4.865 11.257 5.269 11.07 C 5.327 10.616 5.512 10.302 5.708 10.127 C 4.16 9.964 2.531 9.359 2.531 6.658 C 2.531 5.89 2.808 5.262 3.247 4.773 C 3.178 4.598 2.935 3.876 3.316 2.91 C 3.316 2.91 3.906 2.724 5.234 3.632 C 5.803 3.478 6.39 3.4 6.979 3.399 C 7.568 3.399 8.169 3.481 8.724 3.632 C 10.053 2.724 10.642 2.91 10.642 2.91 C 11.023 3.876 10.781 4.598 10.711 4.773 C 11.162 5.262 11.428 5.89 11.428 6.658 C 11.428 9.359 9.799 9.953 8.239 10.127 C 8.493 10.349 8.712 10.768 8.712 11.431 C 8.712 12.374 8.701 13.131 8.701 13.363 C 8.701 13.55 8.828 13.771 9.175 13.701 C 11.948 12.77 13.947 10.139 13.947 7.031 C 13.958 3.143 10.827 0 6.979 0 Z" fill={color}></path></svg>
|
||||
</div>
|
||||
}
|
||||
|
||||
export const LinkedInIcon = ({size = 'S', color = 'rgb(179, 179, 179)'}) => {
|
||||
let dimension = getSize(size);
|
||||
|
||||
return <div style={{width: dimension, height: dimension}}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" focusable="false" color={color} ><g color={color}><path d="M216,24H40A16,16,0,0,0,24,40V216a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V40A16,16,0,0,0,216,24Zm0,192H40V40H216V216ZM96,112v64a8,8,0,0,1-16,0V112a8,8,0,0,1,16,0Zm88,28v36a8,8,0,0,1-16,0V140a20,20,0,0,0-40,0v36a8,8,0,0,1-16,0V112a8,8,0,0,1,15.79-1.78A36,36,0,0,1,184,140ZM100,84A12,12,0,1,1,88,72,12,12,0,0,1,100,84Z" fill={color}></path></g></svg>
|
||||
</div>;
|
||||
}
|
||||
|
||||
export const DiscordIcon = ({size = 'S', color = 'rgb(179, 179, 179)'}) => {
|
||||
let dimension = getSize(size);
|
||||
return <div style={{width: dimension, height: dimension}}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" focusable="false" color={color} ><g color={color}><path d="M104,140a12,12,0,1,1-12-12A12,12,0,0,1,104,140Zm60-12a12,12,0,1,0,12,12A12,12,0,0,0,164,128Zm74.45,64.9-67,29.71a16.17,16.17,0,0,1-21.71-9.1l-8.11-22q-6.72.45-13.63.46t-13.63-.46l-8.11,22a16.18,16.18,0,0,1-21.71,9.1l-67-29.71a15.93,15.93,0,0,1-9.06-18.51L38,58A16.07,16.07,0,0,1,51,46.14l36.06-5.93a16.22,16.22,0,0,1,18.26,11.88l3.26,12.84Q118.11,64,128,64t19.4.93l3.26-12.84a16.21,16.21,0,0,1,18.26-11.88L205,46.14A16.07,16.07,0,0,1,218,58l29.53,116.38A15.93,15.93,0,0,1,238.45,192.9ZM232,178.28,202.47,62s0,0-.08,0L166.33,56a.17.17,0,0,0-.17,0l-2.83,11.14c5,.94,10,2.06,14.83,3.42A8,8,0,0,1,176,86.31a8.09,8.09,0,0,1-2.16-.3A172.25,172.25,0,0,0,128,80a172.25,172.25,0,0,0-45.84,6,8,8,0,1,1-4.32-15.4c4.82-1.36,9.78-2.48,14.82-3.42L89.83,56s0,0-.12,0h0L53.61,61.93a.17.17,0,0,0-.09,0L24,178.33,91,208a.23.23,0,0,0,.22,0L98,189.72a173.2,173.2,0,0,1-20.14-4.32A8,8,0,0,1,82.16,170,171.85,171.85,0,0,0,128,176a171.85,171.85,0,0,0,45.84-6,8,8,0,0,1,4.32,15.41A173.2,173.2,0,0,1,158,189.72L164.75,208a.22.22,0,0,0,.21,0Z" fill={color}></path></g></svg>
|
||||
</div>
|
||||
}
|
||||
|
||||
export const XIcon = ({size = 'S', color = 'rgb(179, 179, 179)'}) => {
|
||||
let dimension = getSize(size);
|
||||
return <div style={{width: dimension, height: dimension}}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" focusable="false" color={color} ><g color={color}><path d="M104,140a12,12,0,1,1-12-12A12,12,0,0,1,104,140Zm60-12a12,12,0,1,0,12,12A12,12,0,0,0,164,128Zm74.45,64.9-67,29.71a16.17,16.17,0,0,1-21.71-9.1l-8.11-22q-6.72.45-13.63.46t-13.63-.46l-8.11,22a16.18,16.18,0,0,1-21.71,9.1l-67-29.71a15.93,15.93,0,0,1-9.06-18.51L38,58A16.07,16.07,0,0,1,51,46.14l36.06-5.93a16.22,16.22,0,0,1,18.26,11.88l3.26,12.84Q118.11,64,128,64t19.4.93l3.26-12.84a16.21,16.21,0,0,1,18.26-11.88L205,46.14A16.07,16.07,0,0,1,218,58l29.53,116.38A15.93,15.93,0,0,1,238.45,192.9ZM232,178.28,202.47,62s0,0-.08,0L166.33,56a.17.17,0,0,0-.17,0l-2.83,11.14c5,.94,10,2.06,14.83,3.42A8,8,0,0,1,176,86.31a8.09,8.09,0,0,1-2.16-.3A172.25,172.25,0,0,0,128,80a172.25,172.25,0,0,0-45.84,6,8,8,0,1,1-4.32-15.4c4.82-1.36,9.78-2.48,14.82-3.42L89.83,56s0,0-.12,0h0L53.61,61.93a.17.17,0,0,0-.09,0L24,178.33,91,208a.23.23,0,0,0,.22,0L98,189.72a173.2,173.2,0,0,1-20.14-4.32A8,8,0,0,1,82.16,170,171.85,171.85,0,0,0,128,176a171.85,171.85,0,0,0,45.84-6,8,8,0,0,1,4.32,15.41A173.2,173.2,0,0,1,158,189.72L164.75,208a.22.22,0,0,0,.21,0Z" fill={color}></path></g></svg>
|
||||
</div>
|
||||
}
|
||||
16
packages/twenty-website/src/app/components/Logo.tsx
Normal file
16
packages/twenty-website/src/app/components/Logo.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import styled from "@emotion/styled";
|
||||
|
||||
const Link = styled.a`
|
||||
display:block;
|
||||
image-rendering: pixelated;
|
||||
flex-shrink: 0;
|
||||
background-size: 100% 100%;
|
||||
border-radius: 8px;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
background-image: url("images/core/logo.svg");
|
||||
opacity: 1;`;
|
||||
|
||||
export const Logo = () => {
|
||||
return <Link href="/" />;
|
||||
};
|
||||
@ -0,0 +1,47 @@
|
||||
'use client'
|
||||
|
||||
import { CacheProvider } from '@emotion/react'
|
||||
import createCache from '@emotion/cache'
|
||||
import { useServerInsertedHTML } from 'next/navigation'
|
||||
import { useState } from 'react'
|
||||
|
||||
export default function RootStyleRegistry({ children }) {
|
||||
const [{ cache, flush }] = useState(() => {
|
||||
const cache = createCache({ key: 'emotion-cache' })
|
||||
cache.compat = true
|
||||
const prevInsert = cache.insert
|
||||
let inserted = []
|
||||
cache.insert = (...args) => {
|
||||
const serialized = args[1]
|
||||
if (cache.inserted[serialized.name] === undefined) {
|
||||
inserted.push(serialized.name)
|
||||
}
|
||||
return prevInsert(...args)
|
||||
}
|
||||
const flush = () => {
|
||||
const prevInserted = inserted
|
||||
inserted = []
|
||||
return prevInserted
|
||||
}
|
||||
return { cache, flush }
|
||||
})
|
||||
|
||||
useServerInsertedHTML(() => {
|
||||
const names = flush()
|
||||
if (names.length === 0) return null
|
||||
let styles = ''
|
||||
for (const name of names) {
|
||||
styles += cache.inserted[name]
|
||||
}
|
||||
return (
|
||||
<style
|
||||
data-emotion={`${cache.key} ${names.join(' ')}`}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: styles,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
return <CacheProvider value={cache}>{children}</CacheProvider>
|
||||
}
|
||||
43
packages/twenty-website/src/app/layout.tsx
Normal file
43
packages/twenty-website/src/app/layout.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import type { Metadata } from 'next'
|
||||
import { Gabarito } from 'next/font/google'
|
||||
import EmotionRootStyleRegistry from './emotion-root-style-registry'
|
||||
import styled from '@emotion/styled'
|
||||
import { HeaderNav } from './components/HeaderNav'
|
||||
import { FooterNav } from './components/FooterNav'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Twenty.dev',
|
||||
description: 'Twenty for Developer',
|
||||
icons: '/favicon.ico',
|
||||
}
|
||||
|
||||
const gabarito = Gabarito({
|
||||
weight: ['400', '500'],
|
||||
subsets: ['latin'],
|
||||
display: 'swap',
|
||||
})
|
||||
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="en" className={gabarito.className}>
|
||||
<body style={{
|
||||
margin: 0,
|
||||
WebkitFontSmoothing: "antialiased",
|
||||
MozOsxFontSmoothing: "grayscale"
|
||||
}}>
|
||||
<EmotionRootStyleRegistry>
|
||||
<HeaderNav />
|
||||
<div style={{display: 'flex', flexDirection: 'column', alignItems: 'center'}}>
|
||||
{children}
|
||||
</div>
|
||||
<FooterNav />
|
||||
</EmotionRootStyleRegistry>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
14
packages/twenty-website/src/app/page.tsx
Normal file
14
packages/twenty-website/src/app/page.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import Image from 'next/image'
|
||||
|
||||
import { ContentContainer } from './components/ContentContainer'
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<ContentContainer>
|
||||
<div style={{ minHeight: '60vh', marginTop: '50px' }}>
|
||||
Part of the website is built directly with Framer, including the homepage. <br />
|
||||
We use Clouflare to split the traffic between the two sites.
|
||||
</div>
|
||||
</ContentContainer>
|
||||
)
|
||||
}
|
||||
68
packages/twenty-website/src/app/releases/page.tsx
Normal file
68
packages/twenty-website/src/app/releases/page.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
import { GetStaticProps } from 'next'
|
||||
import { MDXRemote } from 'next-mdx-remote/rsc'
|
||||
import { compileMDX } from 'next-mdx-remote/rsc'
|
||||
import gfm from 'remark-gfm';
|
||||
import { ContentContainer } from '../components/ContentContainer';
|
||||
import { visit } from 'unist-util-visit';
|
||||
import remarkBehead from 'remark-behead';
|
||||
|
||||
|
||||
interface Release {
|
||||
id: number;
|
||||
name: string;
|
||||
body: string;
|
||||
}
|
||||
|
||||
|
||||
const Home = async () => {
|
||||
const res = await fetch(`${process.env.BASE_URL}/api/github`);
|
||||
const data: Release[] = await res.json();
|
||||
|
||||
const releases = await Promise.all(
|
||||
data.map(async (release) => {
|
||||
let mdxSource;
|
||||
try {
|
||||
mdxSource = await compileMDX({
|
||||
source: release.body,
|
||||
options: {
|
||||
mdxOptions: {
|
||||
remarkPlugins: [
|
||||
gfm,
|
||||
[remarkBehead, { depth: 2 }],
|
||||
],
|
||||
}
|
||||
},
|
||||
});
|
||||
mdxSource = mdxSource.content;
|
||||
} catch(error) {
|
||||
console.error('An error occurred during MDX rendering:', error);
|
||||
mdxSource = `<p>Oops! Something went wrong.</p> ${error}`;;
|
||||
}
|
||||
|
||||
return {
|
||||
id: release.id,
|
||||
name: release.name,
|
||||
body: mdxSource,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<ContentContainer>
|
||||
<h1>Releases</h1>
|
||||
|
||||
{releases.map(release => (
|
||||
<div key={release.id}
|
||||
style={{
|
||||
padding: '24px 0px 24px 0px',
|
||||
borderBottom: '1px solid #ccc',
|
||||
}}>
|
||||
<h2>{release.name}</h2>
|
||||
<div>{release.body}</div>
|
||||
</div>
|
||||
))}
|
||||
</ContentContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home;
|
||||
Reference in New Issue
Block a user