diff --git a/packages/twenty-website/src/app/_components/user-guide/TableContent.tsx b/packages/twenty-website/src/app/_components/user-guide/TableContent.tsx
new file mode 100644
index 000000000000..d11bfeb346eb
--- /dev/null
+++ b/packages/twenty-website/src/app/_components/user-guide/TableContent.tsx
@@ -0,0 +1,149 @@
+'use client';
+import { useEffect, useState } from 'react';
+import styled from '@emotion/styled';
+import { usePathname } from 'next/navigation';
+
+import mq from '@/app/_components/ui/theme/mq';
+import { Theme } from '@/app/_components/ui/theme/theme';
+import { useHeadsObserver } from '@/app/user-guide/hooks/useHeadsObserver';
+
+const StyledContainer = styled.div`
+ ${mq({
+ display: ['none', 'none', 'flex'],
+ flexDirection: 'column',
+ background: `${Theme.background.secondary}`,
+ borderLeft: `1px solid ${Theme.background.transparent.medium}`,
+ borderBottom: `1px solid ${Theme.background.transparent.medium}`,
+ padding: `0px ${Theme.spacing(6)}`,
+ })};
+ width: 300px;
+ min-width: 300px;
+`;
+
+const StyledNav = styled.nav`
+ width: 220px;
+ min-width: 220px;
+ align-self: flex-start;
+ padding: 32px 0px;
+ position: -webkit-sticky;
+ position: sticky;
+ top: 70px;
+ max-height: calc(100vh - 70px);
+ overflow: auto;
+`;
+
+const StyledUnorderedList = styled.ul`
+ list-style-type: none;
+ padding: 0;
+`;
+
+const StyledList = styled.li`
+ margin: 12px 0px;
+`;
+
+const StyledLink = styled.a`
+ text-decoration: none;
+ font-size: 12px;
+ font-family: var(--font-inter);
+ color: ${Theme.text.color.tertiary};
+ &:hover {
+ color: ${Theme.text.color.secondary};
+ }
+ &:active {
+ color: ${Theme.text.color.primary};
+ font-weight: 500 !important;
+ }
+`;
+
+const StyledHeadingText = styled.div`
+ font-size: ${Theme.font.size.sm};
+ color: ${Theme.text.color.quarternary};
+ margin-bottom: 20px;
+`;
+
+const getStyledHeading = (level: number) => {
+ switch (level) {
+ case 3:
+ return {
+ marginLeft: 10,
+ };
+ case 4:
+ return {
+ marginLeft: 20,
+ };
+ case 5:
+ return {
+ marginLeft: 30,
+ };
+ default:
+ return undefined;
+ }
+};
+
+interface HeadingType {
+ id: string;
+ elem: HTMLElement;
+ className: string;
+ text: string;
+ level: number;
+}
+
+const UserGuideTableContents = () => {
+ const [headings, setHeadings] = useState
([]);
+ const pathname = usePathname();
+ const { activeText } = useHeadsObserver(pathname);
+
+ useEffect(() => {
+ const nodes: HTMLElement[] = Array.from(
+ document.querySelectorAll('h2, h3, h4, h5'),
+ );
+ const elements: HeadingType[] = nodes.map(
+ (elem): HeadingType => ({
+ id: elem.id,
+ elem: elem,
+ className: elem.className,
+ text: elem.innerText,
+ level: Number(elem.nodeName.charAt(1)),
+ }),
+ );
+
+ setHeadings(elements);
+ }, [pathname]);
+
+ return (
+
+
+ Table of Content
+
+ {headings.map((heading) => (
+
+ {
+ e.preventDefault();
+ const yOffset = -70;
+ const y =
+ heading.elem.getBoundingClientRect().top +
+ window.scrollY +
+ yOffset;
+
+ window.scrollTo({ top: y, behavior: 'smooth' });
+ }}
+ style={{
+ fontWeight: activeText === heading.text ? 'bold' : 'normal',
+ }}
+ >
+ {heading.text}
+
+
+ ))}
+
+
+
+ );
+};
+
+export default UserGuideTableContents;
diff --git a/packages/twenty-website/src/app/_components/user-guide/UserGuideCard.tsx b/packages/twenty-website/src/app/_components/user-guide/UserGuideCard.tsx
index 1984a174b460..5b32e88a9b02 100644
--- a/packages/twenty-website/src/app/_components/user-guide/UserGuideCard.tsx
+++ b/packages/twenty-website/src/app/_components/user-guide/UserGuideCard.tsx
@@ -3,7 +3,7 @@ import styled from '@emotion/styled';
import { useRouter } from 'next/navigation';
import { Theme } from '@/app/_components/ui/theme/theme';
-import { UserGuideHomeCardsType } from '@/content/user-guide/constants/UserGuideHomeCards';
+import { UserGuideArticlesProps } from '@/content/user-guide/constants/getUserGuideArticles';
const StyledContainer = styled.div`
color: ${Theme.border.color.plain};
@@ -13,15 +13,10 @@ const StyledContainer = styled.div`
display: flex;
flex-direction: column;
cursor: pointer;
- width: 340px;
&:hover {
box-shadow: -8px 8px 0px -4px ${Theme.color.gray60};
}
-
- @media (max-width: 385px) {
- width: 280px;
- }
`;
const StyledHeading = styled.div`
@@ -38,26 +33,34 @@ const StyledSubHeading = styled.div`
font-size: ${Theme.font.size.xs};
color: ${Theme.text.color.secondary};
font-family: ${Theme.font.family};
- padding: 0 16px 24px;
+ margin: 0 16px 24px;
font-weight: ${Theme.font.weight.regular};
line-height: 21px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
`;
const StyledImage = styled.img`
border-bottom: 1.5px solid #14141429;
+ height: 160px;
`;
export default function UserGuideCard({
card,
}: {
- card: UserGuideHomeCardsType;
+ card: UserGuideArticlesProps;
}) {
const router = useRouter();
return (
- router.push(`/user-guide/${card.url}`)}>
+ router.push(`/user-guide/${card.fileName}`)}
+ >
{card.title}
- {card.subtitle}
+ {card.info}
);
}
diff --git a/packages/twenty-website/src/app/_components/user-guide/UserGuideContent.tsx b/packages/twenty-website/src/app/_components/user-guide/UserGuideContent.tsx
index 18515168ae76..d553040cbaf7 100644
--- a/packages/twenty-website/src/app/_components/user-guide/UserGuideContent.tsx
+++ b/packages/twenty-website/src/app/_components/user-guide/UserGuideContent.tsx
@@ -10,24 +10,43 @@ import { FileContent } from '@/app/_server-utils/get-posts';
const StyledContainer = styled('div')`
${mq({
- width: ['100%', '70%', '60%'],
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
borderBottom: `1px solid ${Theme.background.transparent.medium}`,
fontFamily: `${Theme.font.family}`,
})};
+ width: 100%;
+ min-height: calc(100vh - 50px);
+
+ @media (min-width: 990px) {
+ justify-content: flex-start;
+ }
`;
const StyledWrapper = styled.div`
- width: 79.3%;
- padding: ${Theme.spacing(10)} 0px ${Theme.spacing(20)} 0px;
+ @media (max-width: 450px) {
+ padding: ${Theme.spacing(10)} 30px ${Theme.spacing(20)};
+ }
+
+ @media (min-width: 451px) and (max-width: 800px) {
+ padding: ${Theme.spacing(10)} 50px ${Theme.spacing(20)};
+ width: 440px;
+ }
+
+ @media (min-width: 801px) {
+ max-width: 720px;
+ margin: ${Theme.spacing(10)} 92px ${Theme.spacing(20)};
+ }
`;
const StyledHeader = styled.div`
display: flex;
flex-direction: column;
gap: ${Theme.spacing(8)};
+ @media (min-width: 450px) and (max-width: 800px) {
+ width: 340px;
+ }
`;
const StyledHeading = styled.div`
@@ -74,33 +93,14 @@ const StyledImageContainer = styled.div`
align-items: center;
overflow: hidden;
border-radius: 16px;
- height: 340px;
max-width: fit-content;
- @media (max-width: 414px) {
- height: 160px;
- }
-
- @media (min-width: 415px) and (max-width: 800px) {
- height: 240px;
- }
- @media (min-width: 1500px) {
- height: 450px;
- }
-
img {
- height: 340px;
-
- @media (max-width: 414px) {
- height: 160px;
- }
-
- @media (min-width: 415px) and (max-width: 800px) {
- height: 240px;
- }
-
- @media (min-width: 1500px) {
- height: 450px;
+ height: 100%;
+ max-width: 100%;
+ width: 100%;
+ @media (min-width: 1000px) {
+ width: 720px;
}
}
`;
diff --git a/packages/twenty-website/src/app/_components/user-guide/UserGuideMain.tsx b/packages/twenty-website/src/app/_components/user-guide/UserGuideMain.tsx
index 1c30d935354f..7149076390d9 100644
--- a/packages/twenty-website/src/app/_components/user-guide/UserGuideMain.tsx
+++ b/packages/twenty-website/src/app/_components/user-guide/UserGuideMain.tsx
@@ -4,7 +4,7 @@ import styled from '@emotion/styled';
import mq from '@/app/_components/ui/theme/mq';
import { Theme } from '@/app/_components/ui/theme/theme';
import UserGuideCard from '@/app/_components/user-guide/UserGuideCard';
-import { USER_GUIDE_HOME_CARDS } from '@/content/user-guide/constants/UserGuideHomeCards';
+import { UserGuideArticlesProps } from '@/content/user-guide/constants/getUserGuideArticles';
const StyledContainer = styled.div`
${mq({
@@ -21,11 +21,13 @@ const StyledWrapper = styled.div`
flex-direction: column;
width: 100%;
- @media (max-width: 800px) {
- padding: ${Theme.spacing(10)} 24px ${Theme.spacing(20)};
+ @media (max-width: 450px) {
+ padding: ${Theme.spacing(10)} 30px ${Theme.spacing(20)};
+ align-items: center;
}
- @media (max-width: 800px) {
+ @media (min-width: 450px) and (max-width: 800px) {
+ padding: ${Theme.spacing(10)} 50px ${Theme.spacing(20)};
align-items: center;
}
`;
@@ -35,24 +37,22 @@ const StyledTitle = styled.div`
color: ${Theme.text.color.quarternary};
font-weight: ${Theme.font.weight.medium};
margin-bottom: 32px;
- @media (min-width: 380px) and (max-width: 800px) {
+ width: 100%;
+
+ @media (min-width: 450px) and (max-width: 800px) {
width: 340px;
margin-bottom: 24px;
}
- @media (max-width: 380px) {
- width: 280px;
- }
`;
const StyledHeader = styled.div`
display: flex;
flex-direction: column;
gap: 0px;
- @media (min-width: 380px) and (max-width: 800px) {
+ width: 100%;
+ @media (min-width: 450px) and (max-width: 1200px) {
width: 340px;
- }
- @media (max-width: 380px) {
- width: 280px;
+ margin-bottom: 24px;
}
`;
@@ -87,15 +87,19 @@ const StyledContent = styled.div`
gridTemplateColumns: 'auto auto',
gap: `${Theme.spacing(6)}`,
})};
- @media (max-width: 810px) {
- align-items: center;
- }
- @media (min-width: 1200px) {
- justify-content: left;
+ @media (min-width: 450px) {
+ justify-content: flex-start;
+ width: 340px;
}
`;
-export default function UserGuideMain() {
+interface UserGuideProps {
+ userGuideArticleCards: UserGuideArticlesProps[];
+}
+
+export default function UserGuideMain({
+ userGuideArticleCards,
+}: UserGuideProps) {
return (
@@ -107,7 +111,7 @@ export default function UserGuideMain() {
- {USER_GUIDE_HOME_CARDS.map((card) => {
+ {userGuideArticleCards.map((card) => {
return ;
})}
diff --git a/packages/twenty-website/src/app/_components/user-guide/UserGuideMainLayout.tsx b/packages/twenty-website/src/app/_components/user-guide/UserGuideMainLayout.tsx
new file mode 100644
index 000000000000..dc661153e361
--- /dev/null
+++ b/packages/twenty-website/src/app/_components/user-guide/UserGuideMainLayout.tsx
@@ -0,0 +1,46 @@
+'use client';
+import { ReactNode } from 'react';
+import styled from '@emotion/styled';
+import { usePathname } from 'next/navigation';
+
+import mq from '@/app/_components/ui/theme/mq';
+import { Theme } from '@/app/_components/ui/theme/theme';
+import UserGuideTableContents from '@/app/_components/user-guide/TableContent';
+import UserGuideSidebar from '@/app/_components/user-guide/UserGuideSidebar';
+import { UserGuideArticlesProps } from '@/content/user-guide/constants/getUserGuideArticles';
+
+const StyledContainer = styled.div`
+ width: 100%;
+ display: flex;
+ flex-direction: row;
+ border-bottom: 1px solid ${Theme.background.transparent.medium};
+ min-height: calc(100vh - 50px);
+`;
+
+const StyledEmptySideBar = styled.div`
+ ${mq({
+ width: '20%',
+ display: ['none', 'none', ''],
+ })};
+`;
+
+export const UserGuideMainLayout = ({
+ children,
+ userGuideIndex,
+}: {
+ children: ReactNode;
+ userGuideIndex: UserGuideArticlesProps[];
+}) => {
+ const pathname = usePathname();
+ return (
+
+
+ {children}
+ {pathname === '/user-guide' ? (
+
+ ) : (
+
+ )}
+
+ );
+};
diff --git a/packages/twenty-website/src/app/_components/user-guide/UserGuideSidebar.tsx b/packages/twenty-website/src/app/_components/user-guide/UserGuideSidebar.tsx
index 2d798f2476e9..9d20baa01de4 100644
--- a/packages/twenty-website/src/app/_components/user-guide/UserGuideSidebar.tsx
+++ b/packages/twenty-website/src/app/_components/user-guide/UserGuideSidebar.tsx
@@ -7,19 +7,24 @@ import { IconBook } from '@/app/_components/ui/icons';
import mq from '@/app/_components/ui/theme/mq';
import { Theme } from '@/app/_components/ui/theme/theme';
import UserGuideSidebarSection from '@/app/_components/user-guide/UserGuideSidebarSection';
-import { USER_GUIDE_INDEX } from '@/content/user-guide/constants/UserGuideIndex';
+import { UserGuideArticlesProps } from '@/content/user-guide/constants/getUserGuideArticles';
const StyledContainer = styled.div`
${mq({
- width: ['20%', '30%', '20%'],
display: ['none', 'flex', 'flex'],
flexDirection: 'column',
background: `${Theme.background.secondary}`,
borderRight: `1px solid ${Theme.background.transparent.medium}`,
borderBottom: `1px solid ${Theme.background.transparent.medium}`,
- padding: `${Theme.spacing(10)} ${Theme.spacing(3)}`,
+ padding: `${Theme.spacing(10)} ${Theme.spacing(4)}`,
gap: `${Theme.spacing(6)}`,
- })};
+ })}
+ width: 300px;
+ min-width: 300px;
+ overflow: scroll;
+ height: calc(100vh - 60px);
+ position: sticky;
+ top: 64px;
`;
const StyledHeading = styled.div`
@@ -52,8 +57,13 @@ const StyledHeadingText = styled.div`
color: ${Theme.text.color.secondary};
`;
-const UserGuideSidebar = () => {
+const UserGuideSidebar = ({
+ userGuideIndex,
+}: {
+ userGuideIndex: UserGuideArticlesProps[];
+}) => {
const router = useRouter();
+
return (
@@ -64,13 +74,7 @@ const UserGuideSidebar = () => {
User Guide
- {Object.entries(USER_GUIDE_INDEX).map(([heading, subtopics]) => (
-
- ))}
+
);
};
diff --git a/packages/twenty-website/src/app/_components/user-guide/UserGuideSidebarSection.tsx b/packages/twenty-website/src/app/_components/user-guide/UserGuideSidebarSection.tsx
index 57c5323cfa51..79f42929052e 100644
--- a/packages/twenty-website/src/app/_components/user-guide/UserGuideSidebarSection.tsx
+++ b/packages/twenty-website/src/app/_components/user-guide/UserGuideSidebarSection.tsx
@@ -6,13 +6,18 @@ import { usePathname, useRouter } from 'next/navigation';
import { IconChevronDown, IconChevronRight } from '@/app/_components/ui/icons';
import { Theme } from '@/app/_components/ui/theme/theme';
-import { IndexSubtopic } from '@/content/user-guide/constants/UserGuideIndex';
+import { UserGuideArticlesProps } from '@/content/user-guide/constants/getUserGuideArticles';
+import { groupArticlesByTopic } from '@/content/user-guide/constants/groupArticlesByTopic';
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
`;
+const StyledIndex = styled.div`
+ margin-bottom: 24px;
+`;
+
const StyledTitle = styled.div`
cursor: pointer;
display: flex;
@@ -70,53 +75,80 @@ const StyledIcon = styled.div`
align-items: center;
`;
-const StyledRectangle = styled.div<{ isselected: boolean }>`
- height: 100%;
+const StyledRectangle = styled.div<{ isselected: boolean; isHovered: boolean }>`
+ height: ${(props) =>
+ props.isselected ? '95%' : props.isHovered ? '70%' : '100%'};
width: 2px;
background: ${(props) =>
props.isselected
? Theme.border.color.plain
- : Theme.background.transparent.light};
+ : props.isHovered
+ ? Theme.background.transparent.strong
+ : Theme.background.transparent.light};
+ transition: height 0.2s ease-in-out;
`;
+interface TopicsState {
+ [topic: string]: boolean;
+}
+
const UserGuideSidebarSection = ({
- title,
- subTopics,
+ userGuideIndex,
}: {
- title: string;
- subTopics: IndexSubtopic[];
+ userGuideIndex: UserGuideArticlesProps[];
}) => {
- const [isUnfolded, setUnfoldedState] = useState(true);
const pathname = usePathname();
const router = useRouter();
+ const topics = groupArticlesByTopic(userGuideIndex);
+ const [hoveredItem, setHoveredItem] = useState(null);
+
+ const [unfolded, setUnfolded] = useState(() =>
+ Object.keys(topics).reduce((acc: TopicsState, topic: string) => {
+ acc[topic] = true;
+ return acc;
+ }, {}),
+ );
+
+ const toggleFold = (topic: string) => {
+ setUnfolded((prev: TopicsState) => ({ ...prev, [topic]: !prev[topic] }));
+ };
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}
-
- );
- })}
+ {Object.entries(topics).map(([topic, cards]) => (
+
+ toggleFold(topic)}>
+ {unfolded[topic] ? (
+
+
+
+ ) : (
+
+
+
+ )}
+ {topic}
+
+ {unfolded[topic] &&
+ cards.map((card) => {
+ const isselected = pathname === `/user-guide/${card.fileName}`;
+ return (
+ router.push(`/user-guide/${card.fileName}`)}
+ onMouseEnter={() => setHoveredItem(card.title)}
+ onMouseLeave={() => setHoveredItem(null)}
+ >
+
+ {card.title}
+
+ );
+ })}
+
+ ))}
);
};
diff --git a/packages/twenty-website/src/app/_components/user-guide/UserGuideTableContents.tsx b/packages/twenty-website/src/app/_components/user-guide/UserGuideTableContents.tsx
deleted file mode 100644
index 7e03abc4d51e..000000000000
--- a/packages/twenty-website/src/app/_components/user-guide/UserGuideTableContents.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-'use client';
-
-import styled from '@emotion/styled';
-import { useRouter } from 'next/navigation';
-
-import mq from '@/app/_components/ui/theme/mq';
-import { Theme } from '@/app/_components/ui/theme/theme';
-
-const StyledContainer = styled.div`
- ${mq({
- width: '20%',
- display: ['none', 'none', 'flex'],
- flexDirection: 'column',
- background: `${Theme.background.secondary}`,
- borderLeft: `1px solid ${Theme.background.transparent.medium}`,
- borderBottom: `1px solid ${Theme.background.transparent.medium}`,
- padding: `${Theme.spacing(10)} ${Theme.spacing(6)}`,
- gap: `${Theme.spacing(6)}`,
- 'body nav': {
- display: ['none', 'none', ''],
- },
- })};
-`;
-
-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 Content
-
-
-
- );
-};
-
-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
deleted file mode 100644
index 28160311c2d5..000000000000
--- a/packages/twenty-website/src/app/_components/user-guide/UserGuideTocComponent.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-interface Heading {
- id: string;
- value: string;
-}
-
-const UserGuideTocComponent = ({ headings }: { headings: Heading[] }) => {
- return (
-
- );
-};
-
-export default UserGuideTocComponent;
diff --git a/packages/twenty-website/src/app/_server-utils/get-posts.tsx b/packages/twenty-website/src/app/_server-utils/get-posts.tsx
index fec2ae72feda..d05ce9837e34 100644
--- a/packages/twenty-website/src/app/_server-utils/get-posts.tsx
+++ b/packages/twenty-website/src/app/_server-utils/get-posts.tsx
@@ -1,9 +1,7 @@
import { ReactElement } from 'react';
-import { toc } from '@jsdevtools/rehype-toc';
import fs from 'fs';
import { compileMDX } from 'next-mdx-remote/rsc';
import path from 'path';
-import rehypeSlug from 'rehype-slug';
import gfm from 'remark-gfm';
import ArticleEditContent from '@/app/_components/ui/layout/articles/ArticleEditContent';
@@ -103,7 +101,7 @@ async function parseFrontMatterAndCategory(
return parsedDirectory;
}
-export async function compileMDXFile(filePath: string, addToc = true) {
+export async function compileMDXFile(filePath: string) {
const fileContent = fs.readFileSync(filePath, 'utf8');
const compiled = await compileMDX<{ title: string; position?: number }>({
source: fileContent,
@@ -123,7 +121,6 @@ export async function compileMDXFile(filePath: string, addToc = true) {
mdxOptions: {
development: process.env.NODE_ENV === 'development',
remarkPlugins: [gfm],
- rehypePlugins: [rehypeSlug, ...(addToc ? [toc] : [])],
},
},
});
@@ -147,7 +144,7 @@ export async function getPost(
if (!fs.existsSync(filePath)) {
return null;
}
- const { content, frontmatter } = await compileMDXFile(filePath, true);
+ const { content, frontmatter } = await compileMDXFile(filePath);
return {
content,
diff --git a/packages/twenty-website/src/app/user-guide/hooks/useHeadsObserver.tsx b/packages/twenty-website/src/app/user-guide/hooks/useHeadsObserver.tsx
new file mode 100644
index 000000000000..24ee7ae267bd
--- /dev/null
+++ b/packages/twenty-website/src/app/user-guide/hooks/useHeadsObserver.tsx
@@ -0,0 +1,25 @@
+import { useEffect, useRef, useState } from 'react';
+
+export function useHeadsObserver(location: string) {
+ const [activeText, setActiveText] = useState('');
+ const observer = useRef(null);
+ useEffect(() => {
+ const handleObsever = (entries: any[]) => {
+ entries.forEach((entry) => {
+ if (entry?.isIntersecting) {
+ setActiveText(entry.target.innerText);
+ }
+ });
+ };
+
+ observer.current = new IntersectionObserver(handleObsever, {
+ rootMargin: '0% 0% -85% 0px',
+ });
+
+ const elements = document.querySelectorAll('h2, h3, h4, h5');
+ elements.forEach((elem) => observer.current?.observe(elem));
+ return () => observer.current?.disconnect();
+ }, [location]);
+
+ return { activeText };
+}
diff --git a/packages/twenty-website/src/app/user-guide/layout.tsx b/packages/twenty-website/src/app/user-guide/layout.tsx
index 163e36385144..b9af90a52ad0 100644
--- a/packages/twenty-website/src/app/user-guide/layout.tsx
+++ b/packages/twenty-website/src/app/user-guide/layout.tsx
@@ -1,38 +1,13 @@
-'use client';
import { ReactNode } from 'react';
-import styled from '@emotion/styled';
-import { usePathname } from 'next/navigation';
-import mq from '@/app/_components/ui/theme/mq';
-import { Theme } from '@/app/_components/ui/theme/theme';
-import UserGuideSidebar from '@/app/_components/user-guide/UserGuideSidebar';
-import UserGuideTableContents from '@/app/_components/user-guide/UserGuideTableContents';
-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`
- ${mq({
- width: '20%',
- display: ['none', 'none', ''],
- })};
-`;
+import { UserGuideMainLayout } from '@/app/_components/user-guide/UserGuideMainLayout';
+import { getUserGuideArticles } from '@/content/user-guide/constants/getUserGuideArticles';
export default function UserGuideLayout({ children }: { children: ReactNode }) {
- const pathname = usePathname();
+ const userGuideIndex = getUserGuideArticles();
return (
-
-
+
{children}
- {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
index c847ddaba3f8..665cb5296558 100644
--- a/packages/twenty-website/src/app/user-guide/page.tsx
+++ b/packages/twenty-website/src/app/user-guide/page.tsx
@@ -1,4 +1,5 @@
import UserGuideMain from '@/app/_components/user-guide/UserGuideMain';
+import { getUserGuideArticles } from '@/content/user-guide/constants/getUserGuideArticles';
export const metadata = {
title: 'Twenty - User Guide',
@@ -10,5 +11,7 @@ export const metadata = {
export const dynamic = 'force-dynamic';
export default async function UserGuideHome() {
- return ;
+ const userGuideArticleCards = getUserGuideArticles();
+
+ return ;
}
diff --git a/packages/twenty-website/src/content/user-guide/constants/UserGuideHomeCards.ts b/packages/twenty-website/src/content/user-guide/constants/UserGuideHomeCards.ts
deleted file mode 100644
index df083476e04b..000000000000
--- a/packages/twenty-website/src/content/user-guide/constants/UserGuideHomeCards.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-export type UserGuideHomeCardsType = {
- url: string;
- title: string;
- subtitle: string;
- image: string;
-};
-
-export const USER_GUIDE_HOME_CARDS: 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-workspace',
- title: 'Create a Workspace',
- subtitle: 'Custom objects store unique info in workspaces.',
- image: '/images/user-guide/home/create-a-workspace.png',
- },
- {
- url: 'import-export-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: '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/content/user-guide/constants/UserGuideIndex.ts b/packages/twenty-website/src/content/user-guide/constants/UserGuideIndex.ts
index 1a392ff16006..2401ffc5f04e 100644
--- a/packages/twenty-website/src/content/user-guide/constants/UserGuideIndex.ts
+++ b/packages/twenty-website/src/content/user-guide/constants/UserGuideIndex.ts
@@ -1,35 +1,26 @@
-export type IndexSubtopic = {
- title: string;
- url: string;
-};
-
-export type IndexHeading = {
- [heading: string]: IndexSubtopic[];
-};
-
export const USER_GUIDE_INDEX = {
'Getting Started': [
- { title: 'What is Twenty', url: 'what-is-twenty' },
- { title: 'Create a Workspace', url: 'create-workspace' },
+ { fileName: 'what-is-twenty' },
+ { fileName: 'create-workspace' },
],
Objects: [
- { title: 'Objects', url: 'objects' },
- { title: 'Fields', url: 'fields' },
- { title: 'Views, Sort and Filter', url: 'views-sort-filter' },
- { title: 'Table Views', url: 'table-views' },
- { title: 'Kanban Views', url: 'kanban-views' },
- { title: 'Import/Export Data', url: 'import-export-data' },
+ { fileName: 'objects' },
+ { fileName: 'fields' },
+ { fileName: 'views-sort-filter' },
+ { fileName: 'table-views' },
+ { fileName: 'kanban-views' },
+ { fileName: 'import-export-data' },
],
Functions: [
- { title: 'Emails', url: 'emails' },
- { title: 'Notes', url: 'notes' },
- { title: 'Tasks', url: 'tasks' },
- { title: 'Integrations', url: 'integrations' },
- { title: 'API and Webhooks', url: 'api-webhooks' },
+ { fileName: 'emails' },
+ { fileName: 'notes' },
+ { fileName: 'tasks' },
+ { fileName: 'integrations' },
+ { fileName: 'api-webhooks' },
],
Other: [
- { title: 'Glossary', url: 'glossary' },
- { title: 'Tips', url: 'tips' },
- { title: 'Github', url: 'github' },
+ { fileName: 'glossary' },
+ { fileName: 'tips' },
+ { fileName: 'github' },
],
};
diff --git a/packages/twenty-website/src/content/user-guide/constants/getUserGuideArticles.tsx b/packages/twenty-website/src/content/user-guide/constants/getUserGuideArticles.tsx
new file mode 100644
index 000000000000..d101f7f1f0fa
--- /dev/null
+++ b/packages/twenty-website/src/content/user-guide/constants/getUserGuideArticles.tsx
@@ -0,0 +1,36 @@
+import fs from 'fs';
+import matter from 'gray-matter';
+
+import { USER_GUIDE_INDEX } from '@/content/user-guide/constants/UserGuideIndex';
+
+export interface UserGuideArticlesProps {
+ title: string;
+ info: string;
+ image: string;
+ fileName: string;
+ topic: string;
+}
+
+export function getUserGuideArticles() {
+ const guides: UserGuideArticlesProps[] = [];
+
+ for (const [topic, files] of Object.entries(USER_GUIDE_INDEX)) {
+ files.forEach(({ fileName }) => {
+ const filePath = `src/content/user-guide/${fileName}.mdx`;
+ if (fs.existsSync(filePath)) {
+ const fileContent = fs.readFileSync(filePath, 'utf-8');
+ const { data: frontmatter } = matter(fileContent);
+
+ guides.push({
+ title: frontmatter.title || '',
+ info: frontmatter.info || '',
+ image: frontmatter.image || '',
+ fileName: fileName,
+ topic: topic,
+ });
+ }
+ });
+ }
+
+ return guides;
+}
diff --git a/packages/twenty-website/src/content/user-guide/constants/groupArticlesByTopic.tsx b/packages/twenty-website/src/content/user-guide/constants/groupArticlesByTopic.tsx
new file mode 100644
index 000000000000..9a9029e01582
--- /dev/null
+++ b/packages/twenty-website/src/content/user-guide/constants/groupArticlesByTopic.tsx
@@ -0,0 +1,14 @@
+import { UserGuideArticlesProps } from '@/content/user-guide/constants/getUserGuideArticles';
+
+export const groupArticlesByTopic = (
+ items: UserGuideArticlesProps[],
+): Record => {
+ return items.reduce(
+ (acc, item) => {
+ acc[item.topic] = acc[item.topic] || [];
+ acc[item.topic].push(item);
+ return acc;
+ },
+ {} as Record,
+ );
+};