Integrate Keystatic to edit twenty.com content (#10709)
This PR introduces Keystatic to let us edit twenty.com's content with a CMS. For now, we'll focus on creating release notes through Keystatic as it uses quite simple Markdown. Other types of content will need some refactoring to work with Keystatic. https://github.com/user-attachments/assets/e9f85bbf-daff-4b41-bc97-d1baf63758b2 --------- Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
committed by
GitHub
parent
6b4d3ed025
commit
2c465bd42e
@ -0,0 +1,3 @@
|
||||
export default function Page() {
|
||||
return null;
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { makePage } from '@keystatic/next/ui/app';
|
||||
import config from '../../../../keystatic.config';
|
||||
|
||||
export default makePage(config);
|
||||
@ -0,0 +1,5 @@
|
||||
import KeystaticApp from './keystatic';
|
||||
|
||||
export default function Layout() {
|
||||
return <KeystaticApp />;
|
||||
}
|
||||
21
packages/twenty-website/src/app/(cms)/layout.tsx
Normal file
21
packages/twenty-website/src/app/(cms)/layout.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Twenty.com',
|
||||
description: 'Open Source CRM',
|
||||
icons: '/images/core/logo.svg',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
<div className="container">{children}</div>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@ -2,6 +2,7 @@ export const dynamic = 'force-dynamic';
|
||||
|
||||
import { Metadata } from 'next';
|
||||
|
||||
import { getContributorActivity } from '@/app/(public)/contributors/utils/get-contributor-activity';
|
||||
import { ActivityLog } from '@/app/_components/contributors/ActivityLog';
|
||||
import { Breadcrumb } from '@/app/_components/contributors/Breadcrumb';
|
||||
import { ContentContainer } from '@/app/_components/contributors/ContentContainer';
|
||||
@ -11,7 +12,6 @@ import { ProfileSharing } from '@/app/_components/contributors/ProfileSharing';
|
||||
import { PullRequests } from '@/app/_components/contributors/PullRequests';
|
||||
import { ThankYou } from '@/app/_components/contributors/ThankYou';
|
||||
import { Background } from '@/app/_components/oss-friends/Background';
|
||||
import { getContributorActivity } from '@/app/contributors/utils/get-contributor-activity';
|
||||
|
||||
export function generateMetadata({
|
||||
params,
|
||||
@ -1,9 +1,8 @@
|
||||
'use client';
|
||||
import React from 'react';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const GraphQlPlayground = dynamic(
|
||||
() => import('../../../_components/playground/graphql-playground'),
|
||||
() => import('@/app/_components/playground/graphql-playground'),
|
||||
{ ssr: false },
|
||||
);
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
'use client';
|
||||
import React from 'react';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const GraphQlPlayground = dynamic(
|
||||
() => import('../../../_components/playground/graphql-playground'),
|
||||
() => import('@/app/_components/playground/graphql-playground'),
|
||||
{ ssr: false },
|
||||
);
|
||||
|
||||
const CoreGraphql = () => {
|
||||
const MetadataGraphql = () => {
|
||||
return <GraphQlPlayground subDoc={'metadata'} />;
|
||||
};
|
||||
|
||||
export default CoreGraphql;
|
||||
export default MetadataGraphql;
|
||||
@ -1,11 +1,11 @@
|
||||
import { Metadata } from 'next';
|
||||
import { Gabarito, Inter } from 'next/font/google';
|
||||
import { PublicEnvScript } from 'next-runtime-env';
|
||||
import { Gabarito, Inter } from 'next/font/google';
|
||||
|
||||
import { AppHeader } from '@/app/_components/ui/layout/header';
|
||||
|
||||
import { FooterDesktop } from './_components/ui/layout/FooterDesktop';
|
||||
import EmotionRootStyleRegistry from './emotion-root-style-registry';
|
||||
import { FooterDesktop } from '../_components/ui/layout/FooterDesktop';
|
||||
import EmotionRootStyleRegistry from '../emotion-root-style-registry';
|
||||
|
||||
import './layout.css';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ContentContainer } from './_components/ui/layout/ContentContainer';
|
||||
import { ContentContainer } from '../_components/ui/layout/ContentContainer';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { getReleases } from '@/app/releases/utils/get-releases';
|
||||
import { getReleases } from '@/app/(public)/releases/utils/get-releases';
|
||||
|
||||
export interface ReleaseNote {
|
||||
slug: string;
|
||||
@ -1,15 +1,14 @@
|
||||
import React from 'react';
|
||||
import { desc } from 'drizzle-orm';
|
||||
import { Metadata } from 'next';
|
||||
|
||||
import { ReleaseContainer } from '@/app/_components/releases/ReleaseContainer';
|
||||
import { Title } from '@/app/_components/releases/StyledTitle';
|
||||
import { ContentContainer } from '@/app/_components/ui/layout/ContentContainer';
|
||||
import {
|
||||
getMdxReleasesContent,
|
||||
getReleases,
|
||||
} from '@/app/releases/utils/get-releases';
|
||||
import { getVisibleReleases } from '@/app/releases/utils/get-visible-releases';
|
||||
} from '@/app/(public)/releases/utils/get-releases';
|
||||
import { getVisibleReleases } from '@/app/(public)/releases/utils/get-visible-releases';
|
||||
import { ReleaseContainer } from '@/app/_components/releases/ReleaseContainer';
|
||||
import { Title } from '@/app/_components/releases/StyledTitle';
|
||||
import { ContentContainer } from '@/app/_components/ui/layout/ContentContainer';
|
||||
import { findAll } from '@/database/database';
|
||||
import { GithubReleases, githubReleasesModel } from '@/database/model';
|
||||
import { pgGithubReleasesModel } from '@/database/schema-postgres';
|
||||
@ -4,7 +4,7 @@ import { compileMDX } from 'next-mdx-remote/rsc';
|
||||
import { JSXElementConstructor, ReactElement } from 'react';
|
||||
import gfm from 'remark-gfm';
|
||||
|
||||
import { ReleaseNote } from '@/app/releases/api/route';
|
||||
import { ReleaseNote } from '@/app/(public)/releases/api/route';
|
||||
import { compareSemanticVersions } from '@/shared-utils/compareSemanticVersions';
|
||||
|
||||
// WARNING: This API is used by twenty-front, not just by twenty-website
|
||||
@ -1,5 +1,5 @@
|
||||
import { ReleaseNote } from '@/app/releases/api/route';
|
||||
import { getFormattedReleaseNumber } from '@/app/releases/utils/get-formatted-release-number';
|
||||
import { ReleaseNote } from '@/app/(public)/releases/api/route';
|
||||
import { getFormattedReleaseNumber } from '@/app/(public)/releases/utils/get-formatted-release-number';
|
||||
|
||||
export const getVisibleReleases = (
|
||||
releaseNotes: ReleaseNote[],
|
||||
@ -3,9 +3,9 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { TimeRange } from '@nivo/calendar';
|
||||
|
||||
import { getActivityEndDate } from '@/app/(public)/contributors/utils/get-activity-end-date';
|
||||
import { CardContainer } from '@/app/_components/contributors/CardContainer';
|
||||
import { Title } from '@/app/_components/contributors/Title';
|
||||
import { getActivityEndDate } from '@/app/contributors/utils/get-activity-end-date';
|
||||
|
||||
const CalendarContentContainer = styled.div`
|
||||
@media (max-width: 890px) {
|
||||
|
||||
@ -11,7 +11,7 @@ import { DocsArticlesProps } from '@/content/user-guide/constants/getDocsArticle
|
||||
import { getSectionIcon } from '@/shared-utils/getSectionIcons';
|
||||
|
||||
import '@docsearch/css';
|
||||
import '../../user-guide/algolia.css';
|
||||
import '../../(public)/user-guide/algolia.css';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
${mq({
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
'use client';
|
||||
import { useEffect, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { motion } from 'framer-motion';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { useHeadsObserver } from '@/app/(public)/user-guide/hooks/useHeadsObserver';
|
||||
import ClientOnly from '@/app/_components/docs/ClientOnly';
|
||||
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({
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
'use client';
|
||||
|
||||
import { JSXElementConstructor, ReactElement } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Gabarito } from 'next/font/google';
|
||||
import { JSXElementConstructor, ReactElement } from 'react';
|
||||
|
||||
import { ReleaseNote } from '@/app/(public)/releases/api/route';
|
||||
import { ArticleContent } from '@/app/_components/ui/layout/articles/ArticleContent';
|
||||
import MotionContainer from '@/app/_components/ui/layout/LoaderAnimation';
|
||||
import { Theme } from '@/app/_components/ui/theme/theme';
|
||||
import { ReleaseNote } from '@/app/releases/api/route';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
width: 810px;
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
'use client';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { ReleaseNote } from '@/app/(public)/releases/api/route';
|
||||
import { getGithubReleaseDateFromReleaseNote } from '@/app/(public)/releases/utils/get-github-release-date-from-release-note';
|
||||
import { Line } from '@/app/_components/releases/Line';
|
||||
import { Release } from '@/app/_components/releases/Release';
|
||||
import { ReleaseNote } from '@/app/releases/api/route';
|
||||
import { getGithubReleaseDateFromReleaseNote } from '@/app/releases/utils/get-github-release-date-from-release-note';
|
||||
import { GithubReleases } from '@/database/model';
|
||||
|
||||
interface ReleaseProps {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { format } from 'date-fns';
|
||||
import { ImageResponse } from 'next/og';
|
||||
|
||||
import { getContributorActivity } from '@/app/(public)/contributors/utils/get-contributor-activity';
|
||||
import {
|
||||
backgroundImage,
|
||||
container,
|
||||
@ -16,7 +17,6 @@ import {
|
||||
profileUsernameHeader,
|
||||
styledContributorAvatar,
|
||||
} from '@/app/api/contributors/[slug]/og.png/style';
|
||||
import { getContributorActivity } from '@/app/contributors/utils/get-contributor-activity';
|
||||
|
||||
const GABARITO_FONT_CDN_URL =
|
||||
'https://fonts.cdnfonts.com/s/105143/Gabarito-Medium-BF651cdf1f3f18e.woff';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getContributorActivity } from '@/app/contributors/utils/get-contributor-activity';
|
||||
import { getContributorActivity } from '@/app/(public)/contributors/utils/get-contributor-activity';
|
||||
import { executePartialSync } from '@/github/execute-partial-sync';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
import { makeRouteHandler } from '@keystatic/next/route-handler';
|
||||
import config from '../../../../../keystatic.config';
|
||||
|
||||
export const { POST, GET } = makeRouteHandler({
|
||||
config,
|
||||
});
|
||||
@ -1,8 +1,8 @@
|
||||
import { desc } from 'drizzle-orm';
|
||||
|
||||
import { getGithubReleaseDateFromReleaseNote } from '@/app/releases/utils/get-github-release-date-from-release-note';
|
||||
import { getReleases } from '@/app/releases/utils/get-releases';
|
||||
import { getVisibleReleases } from '@/app/releases/utils/get-visible-releases';
|
||||
import { getGithubReleaseDateFromReleaseNote } from '@/app/(public)/releases/utils/get-github-release-date-from-release-note';
|
||||
import { getReleases } from '@/app/(public)/releases/utils/get-releases';
|
||||
import { getVisibleReleases } from '@/app/(public)/releases/utils/get-visible-releases';
|
||||
import { findAll } from '@/database/database';
|
||||
import { GithubReleases, githubReleasesModel } from '@/database/model';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user