Migrate documentation to Mintlify and configure 301 redirects (#15502)

## Summary
Completes the migration of all documentation from twenty-website to a
new Mintlify-powered documentation site at docs.twenty.com.

## Changes Made

### New Package: `twenty-docs`
-  Created new Mintlify documentation package
-  Migrated 95 content pages (user-guide, developers, twenty-ui)
-  Migrated 81 images
-  Converted all custom components to Mintlify native components
-  Configured navigation with 2 tabs and 94 pages
-  Added Helper AI Agent with searchArticles tool for docs search

### Updated: `twenty-website`
-  Added 11 redirect rules (301 permanent) in next.config.js
-  Removed all documentation content (111 files)
-  Removed documentation routes (user-guide, developers, twenty-ui)
-  Removed documentation components (9 files)
-  Updated keystatic.config.ts
-  Preserved all marketing/release pages

### Updated: Core Files
-  Updated README.md - docs links point to docs.twenty.com
-  Updated CONTRIBUTING.md - code quality link updated
-  Updated SupportDropdown.tsx - user guide link updated
-  Updated Footer.tsx - user guide link updated
This commit is contained in:
Abdul Rahman
2025-10-31 22:14:14 +05:30
committed by GitHub
parent 5f13e6fcf4
commit 9f97be67b1
186 changed files with 254 additions and 11616 deletions
@@ -1,26 +0,0 @@
import { type DocsArticlesProps } from '@/content/user-guide/constants/getDocsArticles';
export const constructSections = (
docsArticleCards: DocsArticlesProps[],
isSection: boolean,
): { name: string; info: string }[] => {
if (isSection) {
return [
{
name: docsArticleCards[0]?.topic,
info: docsArticleCards[0]?.sectionInfo,
},
];
} else {
return Array.from(
new Map(
docsArticleCards
.filter((guide) => guide.numberOfFiles > 0)
.map((guide) => [guide.section, guide]),
).values(),
).map((guide) => ({
name: guide.section,
info: guide.sectionInfo,
}));
}
};
@@ -1,6 +0,0 @@
import { getPost } from '@/app/_server-utils/get-posts';
export async function fetchArticleFromSlug(slug: string, basePath: string) {
const effectiveSlug = slug && slug.length > 0 ? slug : 'home';
return await getPost(effectiveSlug, basePath);
}
@@ -1,11 +0,0 @@
import { type DocsArticlesProps } from '@/content/user-guide/constants/getDocsArticles';
export const filterDocsIndex = (
docsIndex: DocsArticlesProps[],
sectionName: string,
): DocsArticlesProps[] => {
return docsIndex.filter(
(guide) =>
guide.section.includes(sectionName) && guide.title.includes(guide.topic),
);
};
@@ -1,35 +0,0 @@
import { type DocsArticlesProps } from '@/content/user-guide/constants/getDocsArticles';
export const getCardPath = (
card: DocsArticlesProps,
basePath: string,
isSection: boolean,
sectionName?: string,
) => {
const isPlayground = [
'core-api-rest',
'metadata-api-rest',
'core-api-graphql',
'metadata-api-graphql',
];
if (isPlayground.includes(card.fileName)) {
const apiType = card.fileName.includes('rest') ? 'rest-api' : 'graphql';
const apiName = card.fileName.includes('core') ? 'core' : 'metadata';
return `/developers/${apiType}/${apiName}`;
} else if (card.fileName.includes('storybook')) {
return 'https://storybook.twenty.com';
} else if (card.fileName.includes('components')) {
return `/twenty-ui`;
} else {
if (sectionName) {
return card.numberOfFiles > 1
? `${basePath}section/${sectionName}/${card.fileName}`
: `${basePath}${card.fileName}`;
} else {
return card.numberOfFiles > 1 && !isSection
? `${basePath}/section/${card.fileName}`
: `${basePath}/${card.fileName}`;
}
}
};
@@ -1,27 +0,0 @@
import {
IconBook,
IconCode,
IconComponents,
IconGitPullRequest,
IconTool,
} from '@tabler/icons-react';
import { Theme } from '@/app/_components/ui/theme/theme';
export const getSectionIcon = (section: string): JSX.Element => {
const iconSize = Theme.icon.size.md;
const sectionIcons: Record<string, JSX.Element> = {
'Getting started': <IconCode size={iconSize} />,
Contributing: <IconGitPullRequest size={iconSize} />,
Extending: <IconTool size={iconSize} />,
Components: <IconComponents size={iconSize} />,
Developers: <IconCode size={iconSize} />,
};
for (const key of Object.keys(sectionIcons)) {
if (section.includes(key)) {
return sectionIcons[key];
}
}
return <IconBook size={iconSize} />;
};