Marketing website improvements (#3169)

* Website improvement

* Improve website design

* Start writing script for user guide

* Begin adding user guide
This commit is contained in:
Félix Malfait 2023-12-27 16:14:42 +01:00 committed by GitHub
parent c08d8ef838
commit 3d5a364e29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 478 additions and 105 deletions

1
.gitignore vendored
View File

@ -16,3 +16,4 @@
!.yarn/releases !.yarn/releases
!.yarn/sdks !.yarn/sdks
!.yarn/versions !.yarn/versions
.vercel

View File

@ -6,8 +6,7 @@ sidebar_custom_props:
--- ---
To use the REST API, you will need an API key. To use the REST API, you will need an API key.
Connect to your Twenty account and follow the following Connect to your Twenty account ang do to Setting > Developers to generate one.
[documentation](user-guide/integrations/generating-api-keys.mdx) to generate one
## Using Postman? ## Using Postman?

View File

@ -1,4 +0,0 @@
{
"label": "Basics",
"position": 1
}

View File

@ -1,4 +0,0 @@
{
"label": "Integrations",
"position": 2
}

View File

@ -1,4 +0,0 @@
{
"label": "Others",
"position": 3
}

View File

@ -75,11 +75,6 @@ const config = {
type: "search", type: "search",
position: "left", position: "left",
}, },
{
to: "/user-guide",
label: "User Guide",
position: "right",
},
{ {
to: "https://github.com/twentyhq/twenty/releases", to: "https://github.com/twentyhq/twenty/releases",
label: "Releases", label: "Releases",

View File

@ -1,16 +1,21 @@
This is a [Next.js](https://nextjs.org/) project.
# Twenty-Website
This used for the marketing website (twenty.com).
This is not related in anyway to the main app, which you can find in twenty-front and twenty-server.
## Getting Started ## Getting Started
First, run the development server: We're using Nest.JS
From the root directory:
```bash ```bash
yarn dev nx run twenty-website:dev
``` ```
Then open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. Or to build in prod:
```bash
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. nx run twenty-website:build
nx run twenty-website:start
```

View File

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 120 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 MiB

After

Width:  |  Height:  |  Size: 4.2 MiB

View File

Before

Width:  |  Height:  |  Size: 865 KiB

After

Width:  |  Height:  |  Size: 865 KiB

View File

Before

Width:  |  Height:  |  Size: 861 KiB

After

Width:  |  Height:  |  Size: 861 KiB

View File

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 126 KiB

View File

Before

Width:  |  Height:  |  Size: 145 KiB

After

Width:  |  Height:  |  Size: 145 KiB

View File

Before

Width:  |  Height:  |  Size: 183 KiB

After

Width:  |  Height:  |  Size: 183 KiB

View File

@ -1,8 +0,0 @@
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);
}

View File

@ -1,4 +0,0 @@
export default async function BlogPost({ params }: { params: { slug: string } }) {
const posts = {};
return <>Blog Post: {params.slug}</>;
}

View File

@ -1,6 +0,0 @@
export default async function BlogHome() {
const posts = {};
return <>Blog Home</>;
}

View File

@ -1,11 +1,18 @@
'use client' 'use client'
import styled from '@emotion/styled'
const Container = styled.div`
display: flex;
flex-direction: column;
width: 600px;
@media(max-width: 809px) {
width: 100%;
}`;
export const ContentContainer = ({children}: {children?: React.ReactNode}) => { export const ContentContainer = ({children}: {children?: React.ReactNode}) => {
return ( return (
<div style={{ <Container>{children}</Container>
width: '600px',
display: 'flex',
flexDirection: 'column',
}}>{children}</div>
) )
} }

View File

@ -2,7 +2,7 @@
import styled from '@emotion/styled' import styled from '@emotion/styled'
import { Logo } from './Logo'; import { Logo } from './Logo';
import { DiscordIcon, GithubIcon, LinkedInIcon, XIcon } from "./Icons"; import { DiscordIcon, GithubIcon2, LinkedInIcon, XIcon } from "./Icons";
const FooterContainer = styled.div` const FooterContainer = styled.div`
@ -11,6 +11,9 @@ const FooterContainer = styled.div`
flex-direction: column; flex-direction: column;
color: rgb(129, 129, 129); color: rgb(129, 129, 129);
gap: 32px; gap: 32px;
@media(max-width: 809px) {
display: none;
}
`; `;
const LeftSideFooter = styled.div` const LeftSideFooter = styled.div`
@ -21,7 +24,9 @@ const LeftSideFooter = styled.div`
const RightSideFooter = styled.div` const RightSideFooter = styled.div`
display: flex; display: flex;
justify-content: space-between;`; justify-content: space-between;
gap: 48px;
height: 146px;`;
const RightSideFooterColumn = styled.div` const RightSideFooterColumn = styled.div`
width: 160px; width: 160px;
@ -46,7 +51,7 @@ const RightSideFooterColumnTitle = styled.div`
export const FooterNav = () => { export const FooterDesktop = () => {
return <FooterContainer> return <FooterContainer>
<div style={{ width: '100%', display: 'flex', flexDirection: 'row', justifyContent:'space-between'}}> <div style={{ width: '100%', display: 'flex', flexDirection: 'row', justifyContent:'space-between'}}>
<LeftSideFooter> <LeftSideFooter>
@ -58,8 +63,8 @@ export const FooterNav = () => {
<RightSideFooter> <RightSideFooter>
<RightSideFooterColumn> <RightSideFooterColumn>
<RightSideFooterColumnTitle>Company</RightSideFooterColumnTitle> <RightSideFooterColumnTitle>Company</RightSideFooterColumnTitle>
<RightSideFooterLink href='/'>Pricing</RightSideFooterLink> <RightSideFooterLink href='/pricing'>Pricing</RightSideFooterLink>
<RightSideFooterLink href='/'>Story</RightSideFooterLink> <RightSideFooterLink href='/story'>Story</RightSideFooterLink>
</RightSideFooterColumn> </RightSideFooterColumn>
<RightSideFooterColumn> <RightSideFooterColumn>
<RightSideFooterColumnTitle>Resources</RightSideFooterColumnTitle> <RightSideFooterColumnTitle>Resources</RightSideFooterColumnTitle>
@ -91,7 +96,7 @@ export const FooterNav = () => {
<XIcon size='M'/> <XIcon size='M'/>
</a> </a>
<a href="https://github.com/twentyhq/twenty" target="_blank"> <a href="https://github.com/twentyhq/twenty" target="_blank">
<GithubIcon size='M'/> <GithubIcon2 size='M'/>
</a> </a>
<a href="https://www.linkedin.com/company/twenty" target="_blank"> <a href="https://www.linkedin.com/company/twenty" target="_blank">
<LinkedInIcon size='M'/> <LinkedInIcon size='M'/>

View File

@ -22,14 +22,18 @@ const Nav = styled.nav`
overflow: visible; overflow: visible;
padding: 12px 16px 12px 16px; padding: 12px 16px 12px 16px;
position: relative; position: relative;
border-color: rgba(20, 20, 20, 0.08);
transform-origin: 50% 50% 0px; transform-origin: 50% 50% 0px;
border-bottom: 1px solid var(--Borders-Light, #F1F1F1); border-bottom: 1px solid rgba(20, 20, 20, 0.08);
@media(max-width: 809px) {
display: none;
}
`; `;
const LinkList = styled.div` const LinkList = styled.div`
display:flex; display:flex;
flex-direction: row; flex-direction: row;
gap: 2px;
`; `;
const ListItem = styled.a` const ListItem = styled.a`
@ -78,8 +82,7 @@ const StyledButton = styled.div`
const CallToActionContainer = styled.div` const CallToActionContainer = styled.div`
display: flex; display: flex;
align-items: center; align-items: center;
gap: 16px; gap: 8px;
a { a {
text-decoration: none; text-decoration: none;
} }
@ -89,6 +92,7 @@ const LinkNextToCTA = styled.a`
display: flex; display: flex;
align-items: center; align-items: center;
color: rgb(71, 71, 71); color: rgb(71, 71, 71);
padding: 0px 16px 0px 16px;
span { span {
text-decoration: underline; text-decoration: underline;
}`; }`;
@ -112,7 +116,7 @@ const ExternalArrow = () => {
} }
export const HeaderNav = () => { export const HeaderDesktop = () => {
const isTwentyDev = false; const isTwentyDev = false;
@ -124,8 +128,8 @@ export const HeaderNav = () => {
<LinkList> <LinkList>
<ListItem href="/pricing">Pricing</ListItem> <ListItem href="/pricing">Pricing</ListItem>
<ListItem href="/story">Story</ListItem> <ListItem href="/story">Story</ListItem>
<ListItem href="http://docs.twenty.com">Docs <ExternalArrow /></ListItem> <ListItem href="https://docs.twenty.com">Docs <ExternalArrow /></ListItem>
<ListItem href="http://docs.twenty.com"><GithubIcon color='rgb(71,71,71)' /> 5.7k <ExternalArrow /></ListItem> <ListItem href="https://github.com/twentyhq/twenty"><GithubIcon color='rgb(71,71,71)' /> 5.7k <ExternalArrow /></ListItem>
</LinkList> </LinkList>
<CallToAction /> <CallToAction />
</Nav>; </Nav>;

View File

@ -0,0 +1,176 @@
'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;
justify-content: space-between;
align-items: center;
overflow: visible;
padding: 0 12px;
position: relative;
transform-origin: 50% 50% 0px;
border-bottom: 1px solid rgba(20, 20, 20, 0.08);
height: 64px;
width: 100%;
@media(min-width: 810px) {
display: none;
}
`;
const LinkList = styled.div`
display:flex;
flex-direction: column;
`;
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: 8px;
a {
text-decoration: none;
}
`;
const LinkNextToCTA = styled.a`
display: flex;
align-items: center;
color: rgb(71, 71, 71);
padding: 0px 16px 0px 16px;
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>
}
const HamburgerContainer = styled.div`
height: 44px;
width: 44px;
cursor: pointer;
position: relative;
input {
height: 44px;
width: 44px;
opacity: 0;
}`;
const HamburgerLine1 = styled.div`
height: 2px;
left: calc(50.00000000000002% - 20px / 2);
position: absolute;
top: calc(37.50000000000002% - 2px / 2);
width: 20px;
border-radius: 10px;
background-color: rgb(179, 179, 179);`;
const HamburgerLine2 = styled.div`
height: 2px;
left: calc(50.00000000000002% - 20px / 2);
position: absolute;
top: calc(62.50000000000002% - 2px / 2);
width: 20px;
border-radius: 10px;
background-color: rgb(179, 179, 179);`;
const NavOpen = styled.div`
display:none;`;
export const HeaderMobile = () => {
const isTwentyDev = false;
return <Nav>
<LogoContainer>
<Logo />
{isTwentyDev && <LogoAddon className={IBMPlexMono.className}>for Developers</LogoAddon>}
</LogoContainer>
<HamburgerContainer>
<input type="checkbox" />
<HamburgerLine1 />
<HamburgerLine2 />
</HamburgerContainer>
<NavOpen>
<LinkList>
<ListItem href="/pricing">Pricing</ListItem>
<ListItem href="/story">Story</ListItem>
<ListItem href="https://docs.twenty.com">Docs <ExternalArrow /></ListItem>
<ListItem href="https://github.com/twentyhq/twenty"><GithubIcon color='rgb(71,71,71)' /> 5.7k <ExternalArrow /></ListItem>
</LinkList>
<CallToAction />
</NavOpen>
</Nav>;
};

View File

@ -1,4 +1,4 @@
const getSize = size => { const getSize = (size: string) => {
switch(size) { switch(size) {
case 'S': case 'S':
return '14px'; return '14px';
@ -36,6 +36,16 @@ export const DiscordIcon = ({size = 'S', color = 'rgb(179, 179, 179)'}) => {
export const XIcon = ({size = 'S', color = 'rgb(179, 179, 179)'}) => { export const XIcon = ({size = 'S', color = 'rgb(179, 179, 179)'}) => {
let dimension = getSize(size); let dimension = getSize(size);
return <div style={{width: dimension, height: dimension}}> 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> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22" id="svg2382164700">
<path d="M 15.418 19.037 L 3.44 3.637 C 3.311 3.471 3.288 3.247 3.381 3.058 C 3.473 2.87 3.665 2.75 3.875 2.75 L 6.148 2.75 C 6.318 2.75 6.478 2.829 6.582 2.963 L 18.56 18.363 C 18.689 18.529 18.712 18.753 18.619 18.942 C 18.527 19.13 18.335 19.25 18.125 19.25 L 15.852 19.25 C 15.682 19.25 15.522 19.171 15.418 19.037 Z" fill="transparent" strokeWidth="1.38" strokeMiterlimit="10" stroke={color}></path>
<path d="M 18.333 2.75 L 3.667 19.25" fill="transparent" strokeWidth="1.38" strokeLinecap="round" strokeMiterlimit="10" stroke={color}></path>
</svg>
</div>
}
export const GithubIcon2 = ({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="M208.31,75.68A59.78,59.78,0,0,0,202.93,28,8,8,0,0,0,196,24a59.75,59.75,0,0,0-48,24H124A59.75,59.75,0,0,0,76,24a8,8,0,0,0-6.93,4,59.78,59.78,0,0,0-5.38,47.68A58.14,58.14,0,0,0,56,104v8a56.06,56.06,0,0,0,48.44,55.47A39.8,39.8,0,0,0,96,192v8H72a24,24,0,0,1-24-24A40,40,0,0,0,8,136a8,8,0,0,0,0,16,24,24,0,0,1,24,24,40,40,0,0,0,40,40H96v16a8,8,0,0,0,16,0V192a24,24,0,0,1,48,0v40a8,8,0,0,0,16,0V192a39.8,39.8,0,0,0-8.44-24.53A56.06,56.06,0,0,0,216,112v-8A58.14,58.14,0,0,0,208.31,75.68ZM200,112a40,40,0,0,1-40,40H112a40,40,0,0,1-40-40v-8a41.74,41.74,0,0,1,6.9-22.48A8,8,0,0,0,80,73.83a43.81,43.81,0,0,1,.79-33.58,43.88,43.88,0,0,1,32.32,20.06A8,8,0,0,0,119.82,64h32.35a8,8,0,0,0,6.74-3.69,43.87,43.87,0,0,1,32.32-20.06A43.81,43.81,0,0,1,192,73.83a8.09,8.09,0,0,0,1,7.65A41.72,41.72,0,0,1,200,104Z" fill={color}></path></g></svg>
</div> </div>
} }

View File

@ -0,0 +1,7 @@
import Image from 'next/image'
export const PostImage = ({ sources, style }: { sources: { light: string, dark: string }, style?: React.CSSProperties }) => {
return <Image src={sources.light} style={style} alt={sources.light} />
}

View File

@ -0,0 +1,26 @@
*, *::before, *::after {
box-sizing: border-box;
font-smooth: antialiased;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
box-sizing: border-box;
padding: 0;
margin: 0;
word-wrap: break-word;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
}
a {
color: rgb(129, 129, 129);
&:hover {
color: #000;
}
}

View File

@ -2,19 +2,22 @@ import type { Metadata } from 'next'
import { Gabarito } from 'next/font/google' import { Gabarito } from 'next/font/google'
import EmotionRootStyleRegistry from './emotion-root-style-registry' import EmotionRootStyleRegistry from './emotion-root-style-registry'
import styled from '@emotion/styled' import styled from '@emotion/styled'
import { HeaderNav } from './components/HeaderNav' import { HeaderDesktop } from './components/HeaderDesktop'
import { FooterNav } from './components/FooterNav' import { FooterDesktop } from './components/FooterDesktop'
import { HeaderMobile } from '@/app/components/HeaderMobile'
import './layout.css'
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'Twenty.dev', title: 'Twenty.dev',
description: 'Twenty for Developer', description: 'Twenty for Developer',
icons: '/favicon.ico', icons: '/images/core/logo.svg',
} }
const gabarito = Gabarito({ const gabarito = Gabarito({
weight: ['400', '500'], weight: ['400', '500'],
subsets: ['latin'], subsets: ['latin'],
display: 'swap', display: 'swap',
adjustFontFallback: false
}) })
@ -25,17 +28,14 @@ export default function RootLayout({
}) { }) {
return ( return (
<html lang="en" className={gabarito.className}> <html lang="en" className={gabarito.className}>
<body style={{ <body>
margin: 0,
WebkitFontSmoothing: "antialiased",
MozOsxFontSmoothing: "grayscale"
}}>
<EmotionRootStyleRegistry> <EmotionRootStyleRegistry>
<HeaderNav /> <HeaderDesktop />
<div style={{display: 'flex', flexDirection: 'column', alignItems: 'center'}}> <div className="container">
<HeaderMobile />
{children} {children}
</div> </div>
<FooterNav /> <FooterDesktop />
</EmotionRootStyleRegistry> </EmotionRootStyleRegistry>
</body> </body>
</html> </html>

View File

@ -1,10 +1,8 @@
import { GetStaticProps } from 'next'
import { MDXRemote } from 'next-mdx-remote/rsc'
import { compileMDX } from 'next-mdx-remote/rsc' import { compileMDX } from 'next-mdx-remote/rsc'
import gfm from 'remark-gfm'; import gfm from 'remark-gfm';
import { ContentContainer } from '../components/ContentContainer'; import { ContentContainer } from '../components/ContentContainer';
import { visit } from 'unist-util-visit';
import remarkBehead from 'remark-behead'; import remarkBehead from 'remark-behead';
import type { Metadata } from 'next'
interface Release { interface Release {
@ -14,9 +12,14 @@ interface Release {
} }
export const metadata: Metadata= {
title: 'Twenty - Releases',
description: 'Latest releases of Twenty',
}
const Home = async () => { const Home = async () => {
const res = await fetch(`${process.env.BASE_URL}/api/github`); const response = await fetch('https://api.github.com/repos/twentyhq/twenty/releases');
const data: Release[] = await res.json(); const data: Release[] = await response.json();
const releases = await Promise.all( const releases = await Promise.all(
data.map(async (release) => { data.map(async (release) => {
@ -51,11 +54,11 @@ interface Release {
<ContentContainer> <ContentContainer>
<h1>Releases</h1> <h1>Releases</h1>
{releases.map(release => ( {releases.map((release, index) => (
<div key={release.id} <div key={release.id}
style={{ style={{
padding: '24px 0px 24px 0px', padding: '24px 0px 24px 0px',
borderBottom: '1px solid #ccc', borderBottom: index === releases.length - 1 ? 'none' : '1px solid #ccc',
}}> }}>
<h2>{release.name}</h2> <h2>{release.name}</h2>
<div>{release.body}</div> <div>{release.body}</div>

View File

@ -0,0 +1,11 @@
import { getPost } from "@/app/user-guide/get-posts";
export default async function BlogPost({ params }: { params: { slug: string[] } }) {
const post = await getPost(params.slug as string[]);
console.log(post);
return <div>
<h1>{post?.itemInfo.title}</h1>
<div>{post?.content}</div>
</div>;
}

View File

@ -0,0 +1,97 @@
import fs from 'fs';
import path from 'path';
import { compileMDX } from 'next-mdx-remote/rsc';
import { ReactElement } from 'react';
interface ItemInfo {
title: string;
position?: number;
path: string;
type: 'file' | 'directory';
}
export interface FileContent {
content: ReactElement;
itemInfo: ItemInfo;
}
export interface Directory {
[key: string]: FileContent | Directory | ItemInfo;
itemInfo: ItemInfo;
}
const basePath = '/src/content/user-guide';
async function getFiles(filePath: string, position: number = 0): Promise<Directory> {
const entries = fs.readdirSync(filePath, { withFileTypes: true });
const urlpath = path.toString().split(basePath);
const pathName = urlpath.length > 1 ? urlpath[1] : path.basename(filePath);
console.log(pathName);
const directory: Directory = {
itemInfo: {
title: path.basename(filePath),
position,
type: 'directory',
path: pathName,
},
};
for (const entry of entries) {
if (entry.isDirectory()) {
directory[entry.name] = await getFiles(path.join(filePath, entry.name), position++);
} else if (entry.isFile() && path.extname(entry.name) === '.mdx') {
const fileContent = fs.readFileSync(path.join(filePath, entry.name), 'utf8');
const { content, frontmatter } = await compileMDX<{ title: string, position?: number }>({ source: fileContent, options: { parseFrontmatter: true } });
directory[entry.name] = { content, itemInfo: {...frontmatter, type: 'file', path: pathName + "/" + entry.name.replace(/\.mdx$/, '')} };
}
}
return directory;
}
async function parseFrontMatterAndCategory(directory: Directory, dirPath: string): Promise<Directory> {
const parsedDirectory: Directory = {
itemInfo: directory.itemInfo,
};
for (const entry in directory) {
if (entry !== 'itemInfo' && directory[entry] instanceof Object) {
parsedDirectory[entry] = await parseFrontMatterAndCategory(directory[entry] as Directory, path.join(dirPath, entry));
}
}
const categoryPath = path.join(dirPath, '_category_.json');
if (fs.existsSync(categoryPath)) {
const categoryJson: ItemInfo = JSON.parse(fs.readFileSync(categoryPath, 'utf8'));
parsedDirectory.itemInfo = categoryJson;
}
return parsedDirectory;
}
export async function getPosts(): Promise<Directory> {
const postsDirectory = path.join(process.cwd(), basePath);
const directory = await getFiles(postsDirectory);
return parseFrontMatterAndCategory(directory, postsDirectory);
}
export async function getPost(slug: string[]): Promise<FileContent | null> {
const postsDirectory = path.join(process.cwd(), basePath);
const modifiedSlug = slug.join('/');
const filePath = path.join(postsDirectory, `${modifiedSlug}.mdx`);
console.log(filePath);
if (!fs.existsSync(filePath)) {
return null;
}
const fileContent = fs.readFileSync(filePath, 'utf8');
const { content, frontmatter } = await compileMDX<{ title: string, position?: number }>({ source: fileContent, options: { parseFrontmatter: true } });
return { content, itemInfo: {...frontmatter, type: 'file', path: modifiedSlug }};
}

View File

@ -0,0 +1,47 @@
import { ContentContainer } from '@/app/components/ContentContainer';
import { getPosts, Directory, FileContent } from '@/app/user-guide/get-posts';
import Link from 'next/link';
const DirectoryItem = ({ name, item }: { name: string, item: Directory | FileContent }) => {
if ('content' in item) {
// If the item is a file, we render a link.
return (
<div key={name}>
<Link href={`/user-guide/${item.itemInfo.path}`}>
{item.itemInfo.title}
</Link>
</div>
);
} else {
// If the item is a directory, we render the title and the items in the directory.
return (
<div key={name}>
<h2>{item.itemInfo.title}</h2>
{Object.entries(item).map(([childName, childItem]) => {
if (childName !== 'itemInfo') {
return <DirectoryItem key={childName} name={childName} item={childItem as Directory | FileContent} />;
}
})}
</div>
);
}
};
export default async function BlogHome() {
const posts = await getPosts();
return <ContentContainer>
<h1>User Guide</h1>
<div>
{Object.entries(posts).map(([name, item]) => {
if (name !== 'itemInfo') {
return <DirectoryItem key={name} name={name} item={item as Directory | FileContent} />;
}
})}
</div>
</ContentContainer>;
}

View File

@ -0,0 +1,4 @@
{
"title": "Basics",
"position": 1
}

View File

@ -1,5 +1,5 @@
--- ---
title: User Guide title: Get started
displayed_sidebar: userSidebar displayed_sidebar: userSidebar
sidebar_class_name: hidden sidebar_class_name: hidden
sidebar_position: 0 sidebar_position: 0
@ -8,7 +8,6 @@ sidebar_custom_props:
isSidebarRoot: true isSidebarRoot: true
--- ---
import ThemedImage from '@theme/ThemedImage';
# Welcome to Twenty's User Guide # Welcome to Twenty's User Guide
@ -46,13 +45,13 @@ There are multiple ways to create notes and tasks. Learn more about [Notes](./ba
You can also upload and attach files for each record. To do so, expand a record, and head over to the <b>Files</b> tab. You'll then see the `+ Add file` button. You can also upload and attach files for each record. To do so, expand a record, and head over to the <b>Files</b> tab. You'll then see the `+ Add file` button.
<ThemedImage sources={{light: "../img/user-guide/attach-files-to-records-light.png", dark:"../img/user-guide/attach-files-to-records-dark.png"}} style={{width:'100%', maxWidth:'800px'}}/> <img src="/images/user-guide/attach-files-to-records-light.png" style={{width:'100%', maxWidth:'800px'}}/>
## Add Records To Favorites ## Add Records To Favorites
You can add records to your favorites for quick access. To do so, expand the record you want to add, and click on the heart icon on the top right. You'll now be able to see your favorite records in your sidebar right above your workspace. You can add records to your favorites for quick access. To do so, expand the record you want to add, and click on the heart icon on the top right. You'll now be able to see your favorite records in your sidebar right above your workspace.
<ThemedImage sources={{light: "../img/user-guide/view-favorite-records-light.png", dark:"../img/user-guide/view-favorite-records-dark.png"}} style={{width:'100%', maxWidth:'800px'}}/> <img src="/images/user-guide/view-favorite-records-light.png" style={{width:'100%', maxWidth:'800px'}}/>
## Import data ## Import data

View File

@ -1,11 +1,10 @@
--- ---
title: Creating Custom Objects title: Custom Objects
sidebar_position: 1 sidebar_position: 1
sidebar_custom_props: sidebar_custom_props:
icon: TbAugmentedReality icon: TbAugmentedReality
--- ---
import ThemedImage from '@theme/ThemedImage';
Objects are structures that allow you to store data (records, attributes, and values) specific to an organization. Twenty provides both standard and custom objects. Objects are structures that allow you to store data (records, attributes, and values) specific to an organization. Twenty provides both standard and custom objects.
@ -22,7 +21,7 @@ To create a new custom object:
<br/> <br/>
<ThemedImage sources={{light: "/img/user-guide/view-all-objects-light.png", dark:"/img/user-guide/view-all-objects-dark.png"}} style={{width:'100%', maxWidth:'800px'}}/> <img src="/images/user-guide/view-all-objects-light.png" style={{width:'100%', maxWidth:'800px'}}/>
<br/> <br/>
@ -31,7 +30,7 @@ To create a new custom object:
<br/> <br/>
<div style={{textAlign: 'center'}}> <div style={{textAlign: 'center'}}>
<img src="/img/user-guide/create-custom-object.gif" alt="Create custom object" /> <img src="/images/user-guide/create-custom-object.gif" alt="Create custom object" />
</div> </div>
<br/> <br/>
@ -39,5 +38,5 @@ To create a new custom object:
4. Once you create your custom object, you'll be able to manage it. You can edit the name, icon and description, view the different fields, and add more fields. 4. Once you create your custom object, you'll be able to manage it. You can edit the name, icon and description, view the different fields, and add more fields.
<div style={{textAlign: 'center'}}> <div style={{textAlign: 'center'}}>
<img src="/img/user-guide/manage-custom-object.png" alt="Create custom object" /> <img src="/images/user-guide/manage-custom-object.png" alt="Create custom object" />
</div> </div>

View File

@ -5,7 +5,7 @@ sidebar_custom_props:
icon: TbNote icon: TbNote
--- ---
import ThemedImage from '@theme/ThemedImage'; import PostImage from '@theme/PostImage';
Easily create a note to keep track of important information. Easily create a note to keep track of important information.
@ -13,7 +13,7 @@ Easily create a note to keep track of important information.
To attach a note to the record, go to the <b>Notes</b> tab of a record page and click on `+ New Note`. You can also format, comment, and upload images to your notes. To attach a note to the record, go to the <b>Notes</b> tab of a record page and click on `+ New Note`. You can also format, comment, and upload images to your notes.
<ThemedImage sources={{light: "/img/user-guide/create-new-note-light.png", dark:"/img/user-guide/create-new-note-dark.png"}} style={{width:'100%', maxWidth:'800px'}}/> <img src="images/user-guide/create-new-note-light.png" style={{width:'100%', maxWidth:'800px'}}/>
## Format Notes ## Format Notes

View File

@ -5,11 +5,11 @@ sidebar_custom_props:
icon: TbTargetArrow icon: TbTargetArrow
--- ---
import ThemedImage from '@theme/ThemedImage'; import PostImage from '@theme/PostImage';
All opportunities are presented in a Kanban board, where each column represents the stage of your workflow and each card represents a record. For each card, you can list the amount, close date, probability, and the point of contact. You can also move each card between stages as it goes through your workflow. All opportunities are presented in a Kanban board, where each column represents the stage of your workflow and each card represents a record. For each card, you can list the amount, close date, probability, and the point of contact. You can also move each card between stages as it goes through your workflow.
<ThemedImage sources={{light: "/img/user-guide/all-opportunities-light.png", dark:"/img/user-guide/all-opportunities-dark.png"}} style={{width:'100%', maxWidth:'800px'}}/> <img src="/images/user-guide/all-opportunities-light.png" style={{width:'100%', maxWidth:'800px'}}/>
## Add and delete stages ## Add and delete stages
@ -26,7 +26,7 @@ It's easy to add filters and update your view to focus on only the most importan
- You can also add another filter by following the same steps or clicking on the `+ Add filter` button on top of the columns. - You can also add another filter by following the same steps or clicking on the `+ Add filter` button on top of the columns.
- To remove a filter condition, simply click on the <b>X</b> next to the attribute you used to filter the records. - To remove a filter condition, simply click on the <b>X</b> next to the attribute you used to filter the records.
<ThemedImage sources={{light: "/img/user-guide/filter-opportunities-light.png", dark:"/img/user-guide/filter-opportunities-dark.png"}} style={{width:'100%', maxWidth:'800px'}}/> <img src="/images/user-guide/filter-opportunities-light.png" style={{width:'100%', maxWidth:'800px'}}/>
<br/> <br/>
@ -41,10 +41,10 @@ You can configure your kanban board to display some fields and hide others. By d
You can also rearrange the order of fields by holding down the field name and dragging it to where you want it. You can also rearrange the order of fields by holding down the field name and dragging it to where you want it.
<ThemedImage sources={{light: "/img/user-guide/display-fields-light.png", dark:"/img/user-guide/display-fields-dark.png"}} style={{width:'100%', maxWidth:'800px'}}/> <img src="/images/user-guide/display-fields-light.png" style={{width:'100%', maxWidth:'800px'}}/>
<br/> <br/>
You can also hide all the fields, and get an overview of all the opportunities at a glance. To do so, click on <b>Options</b> on the top right and turn on the toggle in front of the <b>Compact view</b> option. You can also hide all the fields, and get an overview of all the opportunities at a glance. To do so, click on <b>Options</b> on the top right and turn on the toggle in front of the <b>Compact view</b> option.
<ThemedImage sources={{light: "/img/user-guide/compact-opportunities-view-light.png", dark:"/img/user-guide/compact-opportunities-view-dark.png"}} style={{width:'100%', maxWidth:'800px'}}/> <img src="/images/user-guide/compact-opportunities-view-light.png"style={{width:'100%', maxWidth:'800px'}}/>

View File

@ -5,11 +5,11 @@ sidebar_custom_props:
icon: TbChecklist icon: TbChecklist
--- ---
import ThemedImage from '@theme/ThemedImage'; import PostImage from '../../../components/PostImage'
You can find all the tasks from across your workspace in the <b>Tasks</b> window in your sidebar. You can also find a dedicated tab for Tasks on each record so you can add and edit tasks directly from each record. Alternatively, you can click on the `+` button on the top right of each record page and then click on <b>Task</b> to create a new task. You can find all the tasks from across your workspace in the <b>Tasks</b> window in your sidebar. You can also find a dedicated tab for Tasks on each record so you can add and edit tasks directly from each record. Alternatively, you can click on the `+` button on the top right of each record page and then click on <b>Task</b> to create a new task.
<ThemedImage sources={{light: "/img/user-guide/create-new-task-light.png", dark:"/img/user-guide/create-new-task-dark.png"}} style={{width:'100%', maxWidth:'800px'}}/> <img src="/images/user-guide/create-new-task-light.png" style={{width:'100%', maxWidth:'800px'}}/>
## Tasks page ## Tasks page

View File

@ -0,0 +1,4 @@
{
"title": "Integrations",
"position": 2
}

View File

@ -21,7 +21,7 @@ Sync Twenty with 3000+ apps using [Zapier](https://zapier.com/), and automate yo
6. Enter your API key and click on 'Yes, Continue to Twenty.' 6. Enter your API key and click on 'Yes, Continue to Twenty.'
<div style={{textAlign: 'center'}}> <div style={{textAlign: 'center'}}>
<img src="/img/user-guide/connect-zapier.png" alt="Connect Twenty to Zapier" /> <img src="/images/user-guide/connect-zapier.png" alt="Connect Twenty to Zapier" />
</div> </div>
You can now continue creating your automation! You can now continue creating your automation!

View File

@ -0,0 +1,4 @@
{
"title": "Others",
"position": 3
}