diff --git a/packages/twenty-website/public/images/user-guide/home/create-a-workspace.png b/packages/twenty-website/public/images/user-guide/home/create-a-workspace.png new file mode 100644 index 000000000..9df5a6fda Binary files /dev/null and b/packages/twenty-website/public/images/user-guide/home/create-a-workspace.png differ diff --git a/packages/twenty-website/public/images/user-guide/home/custom-objects.png b/packages/twenty-website/public/images/user-guide/home/custom-objects.png new file mode 100644 index 000000000..24ffedeef Binary files /dev/null and b/packages/twenty-website/public/images/user-guide/home/custom-objects.png differ diff --git a/packages/twenty-website/public/images/user-guide/home/import-your-data.png b/packages/twenty-website/public/images/user-guide/home/import-your-data.png new file mode 100644 index 000000000..8c092fa8d Binary files /dev/null and b/packages/twenty-website/public/images/user-guide/home/import-your-data.png differ diff --git a/packages/twenty-website/public/images/user-guide/home/what-is-twenty.png b/packages/twenty-website/public/images/user-guide/home/what-is-twenty.png new file mode 100644 index 000000000..4ae3d7bb1 Binary files /dev/null and b/packages/twenty-website/public/images/user-guide/home/what-is-twenty.png differ diff --git a/packages/twenty-website/src/app/components/Breadcrumbs.tsx b/packages/twenty-website/src/app/components/Breadcrumbs.tsx index 045dbd7be..0e7ebadc3 100644 --- a/packages/twenty-website/src/app/components/Breadcrumbs.tsx +++ b/packages/twenty-website/src/app/components/Breadcrumbs.tsx @@ -1,16 +1,25 @@ +'use client'; + import React from 'react'; import styled from '@emotion/styled'; +import { IconChevronLeft } from '@tabler/icons-react'; import Link from 'next/link'; +import { Theme } from '@/app/ui/theme/theme'; +import { DeviceType, useDeviceType } from '@/app/ui/utilities/useDeviceType'; + const Container = styled.div` display: flex; - gap: 8px; + gap: ${Theme.spacing(2)}; color: #b3b3b3; `; const InternalLinkItem = styled(Link)` text-decoration: none; color: #b3b3b3; + &:hover { + color: ${Theme.text.color.quarternary}; + } `; const ExternalLinkItem = styled.a` @@ -19,7 +28,26 @@ const ExternalLinkItem = styled.a` `; const ActivePage = styled.span` - color: #818181; + color: ${Theme.text.color.secondary}; + font-weight: ${Theme.font.weight.medium}; +`; + +const StyledSection = styled.div` + font-size: ${Theme.font.size.sm}; + font-weight: ${Theme.font.weight.medium}; + color: ${Theme.text.color.quarternary}; + display: flex; + flex-direction: row; + gap: ${Theme.spacing(2)}; +`; + +const StyledMobileContainer = styled.div` + display: flex; + flex-direction: row; + align-items: center; + gap: ${Theme.spacing(1)}; + color: ${Theme.text.color.quarternary}; + font-size: ${Theme.font.size.sm}; `; interface BreadcrumbsProps { @@ -37,17 +65,29 @@ export const Breadcrumbs = ({ activePage, separator, }: BreadcrumbsProps) => { + const isMobile = useDeviceType() === DeviceType.MOBILE; + if (isMobile) { + const lastItem = items[items.length - 1]; + return ( + + + + {lastItem.label} + + + ); + } return ( {items.map((item, index) => ( - + {item.isExternal ? ( {item.label} ) : ( {item.label} )} - {separator} - +
{separator}
+ ))} {activePage}
diff --git a/packages/twenty-website/src/app/components/user-guide/UserGuideCard.tsx b/packages/twenty-website/src/app/components/user-guide/UserGuideCard.tsx new file mode 100644 index 000000000..791e0d927 --- /dev/null +++ b/packages/twenty-website/src/app/components/user-guide/UserGuideCard.tsx @@ -0,0 +1,48 @@ +'use client'; +import styled from '@emotion/styled'; +import { useRouter } from 'next/navigation'; + +import { Theme } from '@/app/ui/theme/theme'; +import { UserGuideHomeCardsType } from '@/app/user-guide/constants/UserGuideHomeCards'; + +const StyledContainer = styled.div` + color: ${Theme.border.color.plain}; + border: 2px solid ${Theme.border.color.plain}; + border-radius: ${Theme.border.radius.md}; + padding: ${Theme.spacing(6)}; + gap: ${Theme.spacing(4)}; + display: flex; + flex-direction: column; + cursor: pointer; + width: 348px; +`; + +const StyledHeading = styled.div` + font-size: ${Theme.font.size.lg}; + color: ${Theme.text.color.primary}; +`; + +const StyledSubHeading = styled.div` + font-size: ${Theme.font.size.sm}; + color: ${Theme.text.color.secondary}; + font-family: ${Theme.font.family}; +`; + +const StyledImage = styled.img` + width: 300px; +`; + +export default function UserGuideCard({ + card, +}: { + card: UserGuideHomeCardsType; +}) { + const router = useRouter(); + return ( + router.push(`/user-guide/${card.url}`)}> + + {card.title} + {card.subtitle} + + ); +} diff --git a/packages/twenty-website/src/app/components/user-guide/UserGuideContent.tsx b/packages/twenty-website/src/app/components/user-guide/UserGuideContent.tsx new file mode 100644 index 000000000..71cce0ded --- /dev/null +++ b/packages/twenty-website/src/app/components/user-guide/UserGuideContent.tsx @@ -0,0 +1,106 @@ +'use client'; +import React from 'react'; +import styled from '@emotion/styled'; + +import { Breadcrumbs } from '@/app/components/Breadcrumbs'; +import { FileContent } from '@/app/get-posts'; +import { Theme } from '@/app/ui/theme/theme'; +import { DeviceType, useDeviceType } from '@/app/ui/utilities/useDeviceType'; + +const StyledContainer = styled.div<{ devicetype: string }>` + width: ${({ devicetype }) => + devicetype === DeviceType.TABLET + ? '70%' + : devicetype === DeviceType.DESKTOP + ? '60%' + : '100%'}; + display: flex; + flex-direction: row; + justify-content: center; + font-family: ${Theme.font.family}; + border-bottom: 1px solid ${Theme.background.transparent.medium}; +`; + +const StyledWrapper = styled.div` + width: 79.3%; + padding: ${Theme.spacing(10)} 0px ${Theme.spacing(20)} 0px; +`; + +const StyledHeader = styled.div` + display: flex; + flex-direction: column; + gap: ${Theme.spacing(8)}; +`; + +const StyledHeading = styled.div` + font-size: 40px; + font-weight: 700; +`; + +const StyledHeaderInfoSection = styled.div` + display: flex; + flex-direction: column; + gap: ${Theme.spacing(4)}; +`; + +const StyledHeaderInfoSectionTitle = styled.div` + font-size: ${Theme.font.size.sm}; + padding: ${Theme.spacing(2)} 0px; + color: ${Theme.text.color.secondary}; + font-weight: ${Theme.font.weight.medium}; +`; + +const StyledHeaderInfoSectionSub = styled.div` + display: flex; + flex-direction: column; + gap: ${Theme.spacing(4)}; + color: ${Theme.text.color.tertiary}; + font-family: ${Theme.font.family}; +`; + +const StyledRectangle = styled.div` + width: 100%; + height: 1px; + background: ${Theme.background.transparent.medium}; +`; + +export default function UserGuideContent({ item }: { item: FileContent }) { + const BREADCRUMB_ITEMS = [ + { + uri: '/user-guide', + label: 'User Guide', + }, + ]; + const deviceType = useDeviceType(); + return ( + + + + + {item.itemInfo.title} + {item.itemInfo.image && ( + {item.itemInfo.title} + )} + + + In this article + + + {item.itemInfo.info} + + + + +
{item.content}
+
+
+ ); +} diff --git a/packages/twenty-website/src/app/components/user-guide/UserGuideMain.tsx b/packages/twenty-website/src/app/components/user-guide/UserGuideMain.tsx new file mode 100644 index 000000000..cf416f6b2 --- /dev/null +++ b/packages/twenty-website/src/app/components/user-guide/UserGuideMain.tsx @@ -0,0 +1,90 @@ +'use client'; +import styled from '@emotion/styled'; + +import UserGuideCard from '@/app/components/user-guide/UserGuideCard'; +import { Theme } from '@/app/ui/theme/theme'; +import { DeviceType, useDeviceType } from '@/app/ui/utilities/useDeviceType'; +import { UserGuideHomeCards } from '@/app/user-guide/constants/UserGuideHomeCards'; + +const StyledContainer = styled.div<{ isMobile: boolean }>` + width: ${({ isMobile }) => (isMobile ? '100%' : '60%')}; + display: flex; + flex-direction: row; + justify-content: center; +`; + +const StyledWrapper = styled.div` + width: 79.3%; + padding: ${Theme.spacing(10)} 0px ${Theme.spacing(20)} 0px; + display: flex; + flex-direction: column; + gap: ${Theme.spacing(8)}; +`; + +const StyledHeader = styled.div` + display: flex; + flex-direction: column; + gap: 0px; +`; + +const StyledHeading = styled.h1` + line-height: 38px; + font-weight: 700; + font-size: 38px; + color: ${Theme.text.color.primary}; + margin: 0px; +`; + +const StyledSubHeading = styled.h1` + line-height: 12px; + font-family: ${Theme.font.family}; + font-size: ${Theme.font.size.sm}; + font-weight: ${Theme.font.weight.regular}; + color: ${Theme.text.color.tertiary}; +`; + +const StyledContentGrid = styled.div` + width: 100%; + padding-top: ${Theme.spacing(6)}; + display: grid; + grid-template-rows: auto auto; + grid-template-columns: auto auto; + gap: ${Theme.spacing(6)}; +`; + +const StyledContentFlex = styled.div` + width: 100%; + padding-top: ${Theme.spacing(6)}; + display: flex; + flex-direction: column; + gap: ${Theme.spacing(6)}; +`; + +export default function UserGuideMain() { + const deviceType = useDeviceType(); + return ( + + + + User Guide + + A brief guide to grasp the basics of Twenty + + + {deviceType === DeviceType.DESKTOP ? ( + + {UserGuideHomeCards.map((card) => { + return ; + })} + + ) : ( + + {UserGuideHomeCards.map((card) => { + return ; + })} + + )} + + + ); +} diff --git a/packages/twenty-website/src/app/components/user-guide/UserGuideSidebar.tsx b/packages/twenty-website/src/app/components/user-guide/UserGuideSidebar.tsx new file mode 100644 index 000000000..de419eb85 --- /dev/null +++ b/packages/twenty-website/src/app/components/user-guide/UserGuideSidebar.tsx @@ -0,0 +1,77 @@ +'use client'; + +import styled from '@emotion/styled'; +import { useRouter } from 'next/navigation'; + +import UserGuideSidebarSection from '@/app/components/user-guide/UserGuideSidebarSection'; +import { IconBook } from '@/app/ui/icons'; +import { Theme } from '@/app/ui/theme/theme'; +import { DeviceType, useDeviceType } from '@/app/ui/utilities/useDeviceType'; +import { UserGuideIndex } from '@/app/user-guide/constants/UserGuideIndex'; + +const StyledContainer = styled.div<{ isTablet: boolean }>` + width: ${({ isTablet }) => (isTablet ? '30%' : '20%')}; + background: ${Theme.background.secondary}; + display: flex; + flex-direction: column; + border-right: 1px solid ${Theme.background.transparent.medium}; + border-bottom: 1px solid ${Theme.background.transparent.medium}; + padding: ${Theme.spacing(10)} ${Theme.spacing(3)}; + gap: ${Theme.spacing(6)}; +`; + +const StyledHeading = styled.div` + display: flex; + flex-direction: row; + align-items: center; + gap: ${Theme.spacing(2)}; + font-size: ${Theme.font.size.sm}; + font-weight: ${Theme.font.weight.medium}; +`; + +const StyledIconContainer = styled.div` + width: 24px; + height: 24px; + display: flex; + flex-direction: row; + justify-content: center; + font-size: ${Theme.font.size.sm}; + font-weight: ${Theme.font.weight.medium}; + color: ${Theme.text.color.secondary}; + border: 1px solid ${Theme.text.color.secondary}; + border-radius: ${Theme.border.radius.sm}; + padding: ${Theme.spacing(1)} ${Theme.spacing(1)} ${Theme.spacing(1)} + ${Theme.spacing(1)}; +`; + +const StyledHeadingText = styled.div` + cursor: pointer; + font-size: ${Theme.font.size.sm}; + font-weight: ${Theme.font.weight.medium}; +`; + +const UserGuideSidebar = () => { + const router = useRouter(); + const isTablet = useDeviceType() === DeviceType.TABLET; + return ( + + + + + + router.push('/user-guide')}> + User Guide + + + {Object.entries(UserGuideIndex).map(([heading, subtopics]) => ( + + ))} + + ); +}; + +export default UserGuideSidebar; diff --git a/packages/twenty-website/src/app/components/user-guide/UserGuideSidebarSection.tsx b/packages/twenty-website/src/app/components/user-guide/UserGuideSidebarSection.tsx new file mode 100644 index 000000000..a098bf07b --- /dev/null +++ b/packages/twenty-website/src/app/components/user-guide/UserGuideSidebarSection.tsx @@ -0,0 +1,114 @@ +'use client'; + +import { useState } from 'react'; +import styled from '@emotion/styled'; +import { useRouter } from 'next/navigation'; +import { usePathname } from 'next/navigation'; + +import { IconChevronDown, IconChevronRight } from '@/app/ui/icons'; +import { Theme } from '@/app/ui/theme/theme'; +import { IndexSubtopic } from '@/app/user-guide/constants/UserGuideIndex'; + +const StyledContainer = styled.div` + display: flex; + flex-direction: column; +`; + +const StyledTitle = styled.div` + cursor: pointer; + display: flex; + flex-direction: row; + align-items: center; + gap: ${Theme.spacing(2)}; + color: ${Theme.text.color.quarternary}; + padding-bottom: ${Theme.spacing(2)}; + font-family: ${Theme.font.family}; + font-size: ${Theme.font.size.xs}; +`; + +const StyledSubTopicItem = styled.div<{ isselected: boolean }>` + cursor: pointer; + display: flex; + flex-direction: row; + align-items: center; + height: ${Theme.spacing(8)}; + color: ${(props) => + props.isselected ? Theme.text.color.primary : Theme.text.color.secondary}; + font-weight: ${(props) => + props.isselected ? Theme.font.weight.medium : Theme.font.weight.regular}; + font-family: ${Theme.font.family}; + font-size: ${Theme.font.size.xs}; + gap: 19px; + padding: ${(props) => + props.isselected ? '6px 12px 6px 11px' : '0px 12px 0px 11px'}; + background: ${(props) => + props.isselected + ? Theme.background.transparent.light + : Theme.background.secondary}; + border-radius: ${Theme.border.radius.md}; + text-decoration: none; + &:focus, + &:hover, + &:visited, + &:link, + &:active { + text-decoration: none; + } +`; + +const StyledIcon = styled.div` + padding: 0px 4px 0px 4px; +`; + +const StyledRectangle = styled.div<{ isselected: boolean }>` + height: 100%; + width: 2px; + background: ${(props) => + props.isselected + ? Theme.border.color.plain + : Theme.background.transparent.light}; +`; + +const UserGuideSidebarSection = ({ + title, + subTopics, +}: { + title: string; + subTopics: IndexSubtopic[]; +}) => { + const [isUnfolded, setUnfoldedState] = useState(true); + const pathname = usePathname(); + const router = useRouter(); + return ( + + setUnfoldedState(!isUnfolded)}> + {isUnfolded ? ( + + + + ) : ( + + + + )} +
{title}
+
+ {isUnfolded && + subTopics.map((subtopic, index) => { + const isselected = pathname === `/user-guide/${subtopic.url}`; + return ( + router.push(`/user-guide/${subtopic.url}`)} + > + + {subtopic.title} + + ); + })} +
+ ); +}; + +export default UserGuideSidebarSection; diff --git a/packages/twenty-website/src/app/components/user-guide/UserGuideTableContents.tsx b/packages/twenty-website/src/app/components/user-guide/UserGuideTableContents.tsx new file mode 100644 index 000000000..ba5e2315b --- /dev/null +++ b/packages/twenty-website/src/app/components/user-guide/UserGuideTableContents.tsx @@ -0,0 +1,41 @@ +'use client'; + +import styled from '@emotion/styled'; +import { useRouter } from 'next/navigation'; + +import { Theme } from '@/app/ui/theme/theme'; + +const StyledContainer = styled.div` + width: 20%; + background: ${Theme.background.secondary}; + display: flex; + flex-direction: column; + border-left: 1px solid ${Theme.background.transparent.medium}; + border-bottom: 1px solid ${Theme.background.transparent.medium}; + padding: ${Theme.spacing(10)} ${Theme.spacing(6)}; + gap: ${Theme.spacing(6)}; +`; + +const StyledContent = styled.div` + position: fixed; +`; + +const StyledHeadingText = styled.div` + font-size: ${Theme.font.size.sm}; + color: ${Theme.text.color.quarternary}; +`; + +const UserGuideTableContents = () => { + const router = useRouter(); + return ( + + + router.push('/user-guide')}> + Table of Contents + + + + ); +}; + +export default UserGuideTableContents; diff --git a/packages/twenty-website/src/app/components/user-guide/UserGuideTocComponent.tsx b/packages/twenty-website/src/app/components/user-guide/UserGuideTocComponent.tsx new file mode 100644 index 000000000..28160311c --- /dev/null +++ b/packages/twenty-website/src/app/components/user-guide/UserGuideTocComponent.tsx @@ -0,0 +1,21 @@ +interface Heading { + id: string; + value: string; +} + +const UserGuideTocComponent = ({ headings }: { headings: Heading[] }) => { + return ( +
+

Table of Contents

+ +
+ ); +}; + +export default UserGuideTocComponent; diff --git a/packages/twenty-website/src/app/get-posts.tsx b/packages/twenty-website/src/app/get-posts.tsx index 4141c0ca9..135ad64fa 100644 --- a/packages/twenty-website/src/app/get-posts.tsx +++ b/packages/twenty-website/src/app/get-posts.tsx @@ -1,5 +1,5 @@ import { ReactElement } from 'react'; -import rehypeToc from '@jsdevtools/rehype-toc'; +import { toc } from '@jsdevtools/rehype-toc'; import fs from 'fs'; import { compileMDX } from 'next-mdx-remote/rsc'; import path from 'path'; @@ -12,6 +12,8 @@ interface ItemInfo { path: string; type: 'file' | 'directory'; icon?: string; + info?: string; + image?: string; } export interface FileContent { @@ -99,14 +101,13 @@ async function parseFrontMatterAndCategory( export async function compileMDXFile(filePath: string, addToc: boolean = true) { const fileContent = fs.readFileSync(filePath, 'utf8'); - const compiled = await compileMDX<{ title: string; position?: number }>({ source: fileContent, options: { parseFrontmatter: true, mdxOptions: { remarkPlugins: [gfm], - rehypePlugins: [rehypeSlug, ...(addToc ? [rehypeToc] : [])], + rehypePlugins: [rehypeSlug, ...(addToc ? [toc] : [])], }, }, }); @@ -121,21 +122,19 @@ export async function getPosts(basePath: string): Promise { } export async function getPost( - slug: string[], + slug: string, basePath: string, ): Promise { const postsDirectory = path.join(process.cwd(), basePath); - const modifiedSlug = slug.join('/'); - const filePath = path.join(postsDirectory, `${modifiedSlug}.mdx`); + const filePath = path.join(postsDirectory, `${slug}.mdx`); if (!fs.existsSync(filePath)) { return null; } - - const { content, frontmatter } = await compileMDXFile(filePath); + const { content, frontmatter } = await compileMDXFile(filePath, true); return { content, - itemInfo: { ...frontmatter, type: 'file', path: modifiedSlug }, + itemInfo: { ...frontmatter, type: 'file', path: slug }, }; } diff --git a/packages/twenty-website/src/app/layout.css b/packages/twenty-website/src/app/layout.css index 9df57cb6e..49c2040c4 100644 --- a/packages/twenty-website/src/app/layout.css +++ b/packages/twenty-website/src/app/layout.css @@ -30,8 +30,24 @@ a { } nav.toc { - width: 200px; + width: 20%; position: fixed; - top: 100px; + top: 125px; right: 0; +} + +nav.toc ol{ + list-style-type: circle; +} + +nav.toc li{ + height: 32px; +} + +nav.toc a{ + color: #141414; + font-family: var(--font-inter); + font-size: 15px; + font-weight: 500; + text-decoration: none; } \ No newline at end of file diff --git a/packages/twenty-website/src/app/layout.tsx b/packages/twenty-website/src/app/layout.tsx index 50383869f..68ae29871 100644 --- a/packages/twenty-website/src/app/layout.tsx +++ b/packages/twenty-website/src/app/layout.tsx @@ -1,5 +1,5 @@ import { Metadata } from 'next'; -import { Gabarito } from 'next/font/google'; +import { Gabarito, Inter } from 'next/font/google'; import { HeaderMobile } from '@/app/components/HeaderMobile'; @@ -16,10 +16,19 @@ export const metadata: Metadata = { }; const gabarito = Gabarito({ - weight: ['400', '500'], + weight: ['400', '500', '600', '700'], subsets: ['latin'], display: 'swap', adjustFontFallback: false, + variable: '--font-gabarito', +}); + +const inter = Inter({ + weight: ['400', '500', '600', '700'], + subsets: ['latin'], + display: 'swap', + adjustFontFallback: false, + variable: '--font-inter', }); export default function RootLayout({ @@ -28,7 +37,7 @@ export default function RootLayout({ children: React.ReactNode; }) { return ( - + diff --git a/packages/twenty-website/src/app/ui/icons/index.ts b/packages/twenty-website/src/app/ui/icons/index.ts new file mode 100644 index 000000000..9ce102922 --- /dev/null +++ b/packages/twenty-website/src/app/ui/icons/index.ts @@ -0,0 +1,7 @@ +export type { TablerIconsProps } from '@tabler/icons-react'; +export { + IconBook, + IconChevronDown, + IconChevronLeft, + IconChevronRight, +} from '@tabler/icons-react'; diff --git a/packages/twenty-website/src/app/ui/theme/background.ts b/packages/twenty-website/src/app/ui/theme/background.ts new file mode 100644 index 000000000..f5597fbff --- /dev/null +++ b/packages/twenty-website/src/app/ui/theme/background.ts @@ -0,0 +1,13 @@ +import { Color, rgba } from './colors'; + +export const Background = { + primary: Color.white, + secondary: Color.gray10, + tertiary: Color.gray20, + transparent: { + strong: rgba(Color.gray60, 0.16), + medium: rgba(Color.gray60, 0.08), + light: rgba(Color.gray60, 0.06), + lighter: rgba(Color.gray60, 0.04), + }, +}; diff --git a/packages/twenty-website/src/app/ui/theme/border.ts b/packages/twenty-website/src/app/ui/theme/border.ts new file mode 100644 index 000000000..84b02f4ae --- /dev/null +++ b/packages/twenty-website/src/app/ui/theme/border.ts @@ -0,0 +1,19 @@ +import { Color } from './colors'; + +const common = { + radius: { + xs: '2px', + sm: '4px', + md: '8px', + xl: '20px', + pill: '999px', + rounded: '100%', + }, +}; + +export const Border = { + color: { + plain: Color.gray60, + }, + ...common, +}; diff --git a/packages/twenty-website/src/app/ui/theme/colors.ts b/packages/twenty-website/src/app/ui/theme/colors.ts new file mode 100644 index 000000000..dac86ad2b --- /dev/null +++ b/packages/twenty-website/src/app/ui/theme/colors.ts @@ -0,0 +1,24 @@ +import hexRgb from 'hex-rgb'; + +export const mainColors = { + white: '#ffffff', +}; + +export const secondaryColors = { + gray60: '#141414', + gray50: '#474747', + gray40: '#818181', + gray30: '#b3b3b3', + gray20: '#f1f1f1', + gray10: '#fafafa', +}; + +export const Color = { + ...mainColors, + ...secondaryColors, +}; + +export const rgba = (hex: string, alpha: number) => { + const rgb = hexRgb(hex, { format: 'array' }).slice(0, -1).join(','); + return `rgba(${rgb},${alpha})`; +}; diff --git a/packages/twenty-website/src/app/ui/theme/font.ts b/packages/twenty-website/src/app/ui/theme/font.ts new file mode 100644 index 000000000..f2628f10e --- /dev/null +++ b/packages/twenty-website/src/app/ui/theme/font.ts @@ -0,0 +1,14 @@ +export const Font = { + size: { + xs: '0.875rem', + sm: '1rem', + base: '1.125rem', + lg: '1.25rem', + xl: '1.5rem', + }, + weight: { + regular: 400, + medium: 500, + }, + family: 'Inter, sans-serif', +}; diff --git a/packages/twenty-website/src/app/ui/theme/icon.ts b/packages/twenty-website/src/app/ui/theme/icon.ts new file mode 100644 index 000000000..ca4170250 --- /dev/null +++ b/packages/twenty-website/src/app/ui/theme/icon.ts @@ -0,0 +1,13 @@ +export const Icon = { + size: { + sm: 14, + md: 16, + lg: 20, + xl: 40, + }, + stroke: { + sm: 1.6, + md: 2, + lg: 2.5, + }, +}; diff --git a/packages/twenty-website/src/app/ui/theme/text.ts b/packages/twenty-website/src/app/ui/theme/text.ts new file mode 100644 index 000000000..05dcf3df6 --- /dev/null +++ b/packages/twenty-website/src/app/ui/theme/text.ts @@ -0,0 +1,22 @@ +import { Color } from './colors'; + +export const Text = { + color: { + primary: Color.gray60, + secondary: Color.gray50, + tertiary: Color.gray40, + quarternary: Color.gray30, + Inverted: Color.white, + }, + lineHeight: { + lg: 1.5, + md: 1.2, + }, + + iconSizeMedium: 16, + iconSizeSmall: 14, + + iconStrikeLight: 1.6, + iconStrikeMedium: 2, + iconStrikeBold: 2.5, +}; diff --git a/packages/twenty-website/src/app/ui/theme/theme.ts b/packages/twenty-website/src/app/ui/theme/theme.ts new file mode 100644 index 000000000..fa4fb9a60 --- /dev/null +++ b/packages/twenty-website/src/app/ui/theme/theme.ts @@ -0,0 +1,18 @@ +import { Background } from '@/app/ui/theme/background'; +import { Border } from '@/app/ui/theme/border'; +import { Color } from '@/app/ui/theme/colors'; +import { Font } from '@/app/ui/theme/font'; +import { Icon } from '@/app/ui/theme/icon'; +import { Text } from '@/app/ui/theme/text'; + +export const Theme = { + color: Color, + border: Border, + background: Background, + text: Text, + spacingMultiplicator: 4, + icon: Icon, + font: Font, + spacing: (...args: number[]) => + args.map((multiplicator) => `${multiplicator * 4}px`).join(' '), +}; diff --git a/packages/twenty-website/src/app/ui/utilities/useDeviceType.ts b/packages/twenty-website/src/app/ui/utilities/useDeviceType.ts new file mode 100644 index 000000000..d60821189 --- /dev/null +++ b/packages/twenty-website/src/app/ui/utilities/useDeviceType.ts @@ -0,0 +1,22 @@ +import { useMediaQuery } from 'react-responsive'; + +export enum DeviceType { + DESKTOP = 'DESKTOP', + TABLET = 'TABLET', + MOBILE = 'MOBILE', +} + +export const useDeviceType = () => { + const isTablet = useMediaQuery({ + query: '(max-width: 1199px) and (min-width: 810px)', + }); + const isMobile = useMediaQuery({ query: '(max-width: 809px)' }); + + if (isMobile) { + return DeviceType.MOBILE; + } + if (isTablet) { + return DeviceType.TABLET; + } + return DeviceType.DESKTOP; +}; diff --git a/packages/twenty-website/src/app/user-guide/[[...slug]]/layout.tsx b/packages/twenty-website/src/app/user-guide/[[...slug]]/layout.tsx deleted file mode 100644 index 27cbeb49d..000000000 --- a/packages/twenty-website/src/app/user-guide/[[...slug]]/layout.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { FunctionComponent } from 'react'; -import * as TablerIcons from '@tabler/icons-react'; -import Link from 'next/link'; - -import { ContentContainer } from '@/app/components/ContentContainer'; -import { Directory, FileContent, getPosts } from '@/app/get-posts'; - -function loadIcon(iconName?: string) { - const name = iconName ? iconName : 'IconCategory'; - - try { - const icon = TablerIcons[ - name as keyof typeof TablerIcons - ] as FunctionComponent; - return icon as TablerIcons.Icon; - } catch (error) { - console.error('Icon not found:', iconName); - return null; - } -} - -const DirectoryItem = ({ - name, - item, -}: { - name: string; - item: Directory | FileContent; -}) => { - if ('content' in item) { - // If the item is a file, we render a link. - const Icon = loadIcon(item.itemInfo.icon); - - return ( -
- - {Icon ? : ''} - {item.itemInfo.title} - -
- ); - } else { - // If the item is a directory, we render the title and the items in the directory. - return ( -
-

- {item.itemInfo.title} -

- {Object.entries(item).map(([childName, childItem]) => { - if (childName !== 'itemInfo') { - return ( - - ); - } - })} -
- ); - } -}; - -export default async function UserGuideHome({ - children, -}: { - children: React.ReactNode; -}) { - const basePath = '/src/content/user-guide'; - - const posts = await getPosts(basePath); - - return ( - -
-
- {posts['home.mdx'] && ( - - )} - {Object.entries(posts).map(([name, item]) => { - if (name !== 'itemInfo' && name != 'home.mdx') { - return ( - - ); - } - })} -
-
- {children} -
-
-
- ); -} diff --git a/packages/twenty-website/src/app/user-guide/[[...slug]]/page.tsx b/packages/twenty-website/src/app/user-guide/[[...slug]]/page.tsx deleted file mode 100644 index af01ca094..000000000 --- a/packages/twenty-website/src/app/user-guide/[[...slug]]/page.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { getPost } from '@/app/get-posts'; - -export default async function UserGuideHome({ - params, -}: { - params: { slug: string[] }; -}) { - const basePath = '/src/content/user-guide'; - - const mainPost = await getPost( - params.slug && params.slug.length ? params.slug : ['home'], - basePath, - ); - - return ( -
-

{mainPost?.itemInfo.title}

-
{mainPost?.content}
-
- ); -} diff --git a/packages/twenty-website/src/app/user-guide/[slug]/page.tsx b/packages/twenty-website/src/app/user-guide/[slug]/page.tsx new file mode 100644 index 000000000..b796d56db --- /dev/null +++ b/packages/twenty-website/src/app/user-guide/[slug]/page.tsx @@ -0,0 +1,16 @@ +import UserGuideContent from '@/app/components/user-guide/UserGuideContent'; +import { getPost } from '@/app/get-posts'; + +export default async function UserGuideSlug({ + params, +}: { + params: { slug: string }; +}) { + const basePath = '/src/content/user-guide'; + + const mainPost = await getPost( + params.slug && params.slug.length ? params.slug : 'home', + basePath, + ); + return mainPost && ; +} diff --git a/packages/twenty-website/src/app/user-guide/constants/UserGuideHomeCards.ts b/packages/twenty-website/src/app/user-guide/constants/UserGuideHomeCards.ts new file mode 100644 index 000000000..7ee950904 --- /dev/null +++ b/packages/twenty-website/src/app/user-guide/constants/UserGuideHomeCards.ts @@ -0,0 +1,34 @@ +export type UserGuideHomeCardsType = { + url: string; + title: string; + subtitle: string; + image: string; +}; + +export const UserGuideHomeCards: UserGuideHomeCardsType[] = [ + { + url: 'what-is-twenty', + title: 'What is Twenty', + subtitle: + "A brief on Twenty's commitment to reshaping CRM with Open Source", + image: '/images/user-guide/home/what-is-twenty.png', + }, + { + url: 'create-a-workspace', + title: 'Create a Workspace', + subtitle: 'Custom objects store unique info in workspaces.', + image: '/images/user-guide/home/create-a-workspace.png', + }, + { + url: 'import-your-data', + title: 'Import your data', + subtitle: 'Easily create a note to keep track of important information.', + image: '/images/user-guide/home/import-your-data.png', + }, + { + url: 'custom-objects', + title: 'Custom Objects', + subtitle: 'Custom objects store unique info in workspaces.', + image: '/images/user-guide/home/custom-objects.png', + }, +]; diff --git a/packages/twenty-website/src/app/user-guide/constants/UserGuideIndex.ts b/packages/twenty-website/src/app/user-guide/constants/UserGuideIndex.ts new file mode 100644 index 000000000..e9a09b1a9 --- /dev/null +++ b/packages/twenty-website/src/app/user-guide/constants/UserGuideIndex.ts @@ -0,0 +1,29 @@ +export type IndexSubtopic = { + title: string; + url: string; +}; + +export type IndexHeading = { + [heading: string]: IndexSubtopic[]; +}; + +export const UserGuideIndex = { + 'Getting Started': [ + { title: 'What is Twenty', url: 'what-is-twenty' }, + { title: 'Create a Workspace', url: 'create-a-workspace' }, + { title: 'Import your data', url: 'import-your-data' }, + ], + Objects: [ + { title: 'People', url: 'people' }, + { title: 'Companies', url: 'companies' }, + { title: 'Opportunities', url: 'opportunities' }, + { title: 'Custom Objects', url: 'custom-objects' }, + { title: 'Remote Objects', url: 'remote-objects' }, + ], + Functions: [ + { title: 'Email', url: 'email' }, + { title: 'Calendar', url: 'calendar' }, + { title: 'Notes', url: 'notes' }, + { title: 'Tasks', url: 'tasks' }, + ], +}; diff --git a/packages/twenty-website/src/app/user-guide/layout.tsx b/packages/twenty-website/src/app/user-guide/layout.tsx new file mode 100644 index 000000000..1e799e7e7 --- /dev/null +++ b/packages/twenty-website/src/app/user-guide/layout.tsx @@ -0,0 +1,40 @@ +'use client'; +import { ReactNode } from 'react'; +import styled from '@emotion/styled'; +import { usePathname } from 'next/navigation'; + +import UserGuideSidebar from '@/app/components/user-guide/UserGuideSidebar'; +import UserGuideTableContents from '@/app/components/user-guide/UserGuideTableContents'; +import { Theme } from '@/app/ui/theme/theme'; +import { DeviceType, useDeviceType } from '@/app/ui/utilities/useDeviceType'; + +const StyledContainer = styled.div` + width: 100%; + display: flex; + flex-direction: row; + justify-content: space-between: + border-bottom: 1px solid ${Theme.background.transparent.medium}; +`; + +const StyledEmptySideBar = styled.div` + width: 20%; +`; + +export default function UserGuideLayout({ children }: { children: ReactNode }) { + const pathname = usePathname(); + const deviceType = useDeviceType(); + + return ( + + {deviceType !== DeviceType.MOBILE && } + {children} + {deviceType !== DeviceType.DESKTOP ? ( + <> + ) : pathname === '/user-guide' ? ( + + ) : ( + + )} + + ); +} diff --git a/packages/twenty-website/src/app/user-guide/page.tsx b/packages/twenty-website/src/app/user-guide/page.tsx new file mode 100644 index 000000000..5e4a71120 --- /dev/null +++ b/packages/twenty-website/src/app/user-guide/page.tsx @@ -0,0 +1,5 @@ +import UserGuideMain from '@/app/components/user-guide/UserGuideMain'; + +export default async function UserGuideHome() { + return ; +} diff --git a/packages/twenty-website/src/content/user-guide/home.mdx b/packages/twenty-website/src/content/user-guide/home.mdx index 7d99476b2..4f6d0865d 100644 --- a/packages/twenty-website/src/content/user-guide/home.mdx +++ b/packages/twenty-website/src/content/user-guide/home.mdx @@ -2,6 +2,7 @@ title: Get started position: 0 icon: IconUsers +info: This is homepage --- The purpose of this user guide is to help you learn how you can use Twenty to build the CRM you want. diff --git a/packages/twenty-website/src/content/user-guide/what-is-twenty.mdx b/packages/twenty-website/src/content/user-guide/what-is-twenty.mdx new file mode 100644 index 000000000..022dda1e5 --- /dev/null +++ b/packages/twenty-website/src/content/user-guide/what-is-twenty.mdx @@ -0,0 +1,57 @@ +--- +title: What is Twenty +position: 0 +icon: IconUsers +info: A brief on Twenty's commitment to reshaping CRM with Open Source +image: /images/user-guide/home/what-is-twenty.png +--- + +## What is Twenty + +The purpose of this user guide is to help you learn how you can use Twenty to build the CRM you want. + +## Quick Search + +You'll see a search bar at the top of your sidebar. You can also bring up the command bar with the `cmd`/`ctrl` + `k` shortcut to navigate through your workspace, and find people, companies, notes, and more. + +The command bar also supports other shortcuts for navigation. + +## Create Pre-filtered Views + +Twenty allows you to add filters to see data that meets certain criteria and hides the rest. You can apply multiple filters at once. + +To create a filter in your workspace, click Filter at the top right and select the attribute you'd like to filter your records by. Create your filter and then save changes in a new view by clicking `+ Create view`. + +The filtered view is now available to your whole team. + +## Add Stages For Opportunities + +You can also add more stages to the opportunities board. + +On the Opportunities page, click on Options, Stages, then `+ Add Stage`. + +You can also edit the stage by clicking on the name. + +## Create Notes and Tasks For Each Record + +You can attach notes and tasks to each record. With Notes, you can keep a record of any observations, comments, and interactions, and keep track of all items that require action with Tasks. + +There are multiple ways to create notes and tasks. Learn more about [Notes](./basics/notes.mdx) and [Tasks](./basics/tasks.mdx). + +## Upload Files For Each Record + +You can also upload and attach files for each record. To do so, expand a record, and head over to the Files tab. You'll then see the `+ Add file` button. + + + +## 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. + + + +## Import data + +You can easily import People and Companies data into Twenty from other apps using a .csv, .xslx, or .xsl file. In the Companies or People page, click on Options and then on Import. + +Upload your file, match the columns, and validate the data to import it. \ No newline at end of file