From 6f59523bd130a038d1f29c1084ff7428b5fb1349 Mon Sep 17 00:00:00 2001 From: "Josh Berman [SSW]" <137844305+joshbermanssw@users.noreply.github.com> Date: Wed, 22 Jan 2025 14:18:27 +1100 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=8F=20Blogs=20to=20App=20Router=20(#27?= =?UTF-8?q?22)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * majority of blogs to app rtr * fix recentPosts * add notFounds()'s and 404 page * make not-found into client server * app router migration tasks – routing * making usePathname components client * re-adding lost stuff? * AI generated fix for build errors * Revert "AI generated fix for build errors" This reverts commit 905db7cf2cbb7cc2e7ec787fe1a1406d1724cffa. * use client division to stop build errors * adding in a forgotten generatestatic params * update next-seo * rm 404.tsx * add extra logging and remove excerpt * updating the blog parameter list * removing console logs * blog pagination prerender * css fix – dropdown width * global error handling, more cleanly * making global error client * global 404? * testing swc tranform issue * re-adding the force swc transforms * re-adding the pages 404 * fix app router notfound page * increase llama size (betty) * Update app/blog/[...slug]/page.tsx Co-authored-by: Caleb Williams [SSW] <65635198+Calinator444@users.noreply.github.com> * Update app/blog/page/[page_index]/page.tsx Co-authored-by: Caleb Williams [SSW] <65635198+Calinator444@users.noreply.github.com> * Introduce BlogPageClientProps * rm log * removed NotFound() * rm unused tinaProps * rm global-error for not-found * rm unused prop * rm 404 * added seo * added optional date * make last edited optional * fix typing * typing v6.0 * make dynamic params true for testing * add googlebot * add hotjar, clarity and robots * fix import directory * test not client * change data collection and revert hiding notfoundclient * add error catching blocks * rm not found * try a global error * improve notFounding'ing * add extra NotFound() * push new lock --------- Co-authored-by: isaaclombardssw Co-authored-by: Caleb Williams [SSW] <65635198+Calinator444@users.noreply.github.com> --- app/blog/[...slug]/BlogPageClient.tsx | 76 ++++++++ app/blog/[...slug]/BlogType.ts | 40 ++++ app/blog/[...slug]/page.tsx | 110 +++++++++++ app/blog/page.tsx | 21 ++ .../page/[page_index]/BlogIndexPageClient.tsx | 78 ++++++++ app/blog/page/[page_index]/page.tsx | 81 ++++++++ app/docs/[...slug]/DocsPagesClient.tsx | 25 +-- app/docs/[...slug]/page.tsx | 63 +++--- app/docs/page.tsx | 1 - app/error.tsx | 15 ++ app/layout.tsx | 40 +++- app/not-found-client.tsx | 46 +++++ app/not-found.tsx | 14 ++ .../Blogs/BlogPagination.tsx | 81 ++++++++ .../DocumentationNavigation.tsx | 27 +-- .../utils/extractTextFromBody.tsx | 19 ++ .../utils/formatDate.tsx | 12 ++ .../DocumentationNavigation.tsx | 27 ++- components/blocks/RecentPosts/RecentPosts.tsx | 37 +++- components/fallback-placeholder.tsx | 31 --- components/layout/DocsLayout.tsx | 7 +- components/layout/DocsTextWrapper.tsx | 57 +++--- components/layout/Layout.tsx | 28 +-- components/ui/BlogPagination.tsx | 7 +- pages/404.tsx | 142 -------------- pages/blog/[slug].tsx | 176 ----------------- pages/blog/index.tsx | 5 - pages/blog/page/[page_index].tsx | 183 ------------------ pnpm-lock.yaml | 96 +++++---- public/robots.txt | 1 - tina/tina-lock.json | 2 +- 31 files changed, 837 insertions(+), 711 deletions(-) create mode 100644 app/blog/[...slug]/BlogPageClient.tsx create mode 100644 app/blog/[...slug]/BlogType.ts create mode 100644 app/blog/[...slug]/page.tsx create mode 100644 app/blog/page.tsx create mode 100644 app/blog/page/[page_index]/BlogIndexPageClient.tsx create mode 100644 app/blog/page/[page_index]/page.tsx create mode 100644 app/error.tsx create mode 100644 app/not-found-client.tsx create mode 100644 app/not-found.tsx create mode 100644 components/AppRouterMigrationComponents/Blogs/BlogPagination.tsx create mode 100644 components/AppRouterMigrationComponents/utils/extractTextFromBody.tsx create mode 100644 components/AppRouterMigrationComponents/utils/formatDate.tsx delete mode 100644 components/fallback-placeholder.tsx delete mode 100644 pages/404.tsx delete mode 100644 pages/blog/[slug].tsx delete mode 100644 pages/blog/index.tsx delete mode 100644 pages/blog/page/[page_index].tsx diff --git a/app/blog/[...slug]/BlogPageClient.tsx b/app/blog/[...slug]/BlogPageClient.tsx new file mode 100644 index 0000000000..20c984d1f0 --- /dev/null +++ b/app/blog/[...slug]/BlogPageClient.tsx @@ -0,0 +1,76 @@ +'use client'; + +import React from 'react'; +import { formatDate } from 'components/AppRouterMigrationComponents/utils/formatDate'; +import { docAndBlogComponents } from 'components/tinaMarkdownComponents/docAndBlogComponents'; +import { DocsPagination } from 'components/ui'; +import { TinaMarkdown } from 'tinacms/dist/rich-text'; +import { BlogPageClientProps } from './BlogType'; + + +const BlogPageClient: React.FC = ({ data }) => { + const blogPostData = data.post; + + const postedDate = formatDate(blogPostData.date); + const lastEditedDate = blogPostData.last_edited + ? formatDate(blogPostData.last_edited) + : null; + + const previousPage = blogPostData.prev + ? { + slug: blogPostData.prev.id.slice(7, -4), + title: blogPostData.prev.title, + } + : null; + + const nextPage = blogPostData.next + ? { + slug: blogPostData.next.id.slice(7, -4), + title: blogPostData.next.title, + } + : null; + + return ( +
+ +
+
+
+ {postedDate} +
+ By + {blogPostData.author} +
+
+
+ +
+ + {lastEditedDate && ( +
+ Last Edited: {lastEditedDate} +
+ )} + +
+
+
+ ); +}; + +function BlogPageTitle({ title }: { title: string }) { + const blogTitleStyling = + 'leading-[1.3] max-w-[9em] bg-gradient-to-r from-orange-400 via-orange-500 to-orange-600 ' + + 'text-transparent bg-clip-text font-tuner mx-auto text-4xl md:text-5xl lg:text-6xl'; + + return ( +
+
{title}
+
+ ); +} + +export default BlogPageClient; diff --git a/app/blog/[...slug]/BlogType.ts b/app/blog/[...slug]/BlogType.ts new file mode 100644 index 0000000000..455e772b24 --- /dev/null +++ b/app/blog/[...slug]/BlogType.ts @@ -0,0 +1,40 @@ +import { TinaMarkdownContent } from "tinacms/dist/rich-text"; + + +interface Sys { + filename: string; + basename: string; + breadcrumbs: string[]; + path: string; + relativePath: string; + extension: string; +} + +interface Seo { + title: string; + description: string; +} + +interface PostSummary { + id: string; + title: string; +} + +export interface BlogPost { + _sys: Sys; + id: string; + title: string; + date: string; + last_edited: string | null; + author: string; + seo: Seo | null; + prev: PostSummary | null; + next: PostSummary | null; + body: TinaMarkdownContent; +} + +export interface BlogPageClientProps { + data: { + post: BlogPost; + }; +} \ No newline at end of file diff --git a/app/blog/[...slug]/page.tsx b/app/blog/[...slug]/page.tsx new file mode 100644 index 0000000000..71c9eb6912 --- /dev/null +++ b/app/blog/[...slug]/page.tsx @@ -0,0 +1,110 @@ +import { notFound } from 'next/navigation'; +import client from 'tina/__generated__/client'; +import BlogPageClient from './BlogPageClient'; +import { TinaMarkdownContent } from 'tinacms/dist/rich-text'; +import { BlogPost } from './BlogType'; + +export async function generateStaticParams() { + let allPosts = []; + let hasNextPage = true; + let after: string | null = null; + + while (hasNextPage) { + try { + const postsResponse = await client.queries.postConnection({ after }); + + const edges = postsResponse?.data?.postConnection?.edges || []; + const pageInfo = postsResponse?.data?.postConnection?.pageInfo || { + hasNextPage: false, + endCursor: null, + }; + + allPosts = allPosts.concat( + edges.map((post) => ({ + slug: [post?.node?._sys?.filename], + })) + ); + + hasNextPage = pageInfo.hasNextPage; + after = pageInfo.endCursor; + } catch (error) { + console.error('Error during static params generation:', error); + notFound(); + } + } + return allPosts; +} + +export const dynamicParams = true; + +export async function generateMetadata({ + params, +}: { + params: { slug: string[] }; +}) { + const slugPath = params.slug.join('/'); + const vars = { relativePath: `${slugPath}.mdx` }; + + try { + const { data } = await client.queries.getExpandedPostDocument(vars); + + if (!data?.post) { + console.warn(`No metadata found for slug: ${slugPath}`); + return notFound(); + } + + return { + title: `${data.post.title} | TinaCMS Blog`, + openGraph: { + title: data.post.title, + }, + }; + } catch (error) { + console.error('Error generating metadata:', error); + return notFound(); + } +} + +export default async function BlogPage({ + params, +}: { + params: { slug: string[] }; +}) { + const slugPath = params.slug.join('/'); + const vars = { relativePath: `${slugPath}.mdx` }; + + try { + const res = await client.queries.getExpandedPostDocument(vars); + + if (!res.data?.post) { + console.warn(`No post found for slug: ${slugPath}`); + return notFound(); + } + + const fetchedPost = res.data.post; + + const post: BlogPost = { + _sys: fetchedPost._sys, + id: fetchedPost.id, + title: fetchedPost.title, + date: fetchedPost.date || '', + last_edited: fetchedPost.last_edited ?? null, + author: fetchedPost.author || '', + seo: fetchedPost.seo + ? { + title: fetchedPost.seo.title || 'Default SEO Title', + description: + fetchedPost.seo.description || 'Default SEO Description', + } + : null, + prev: fetchedPost.prev ?? null, + next: fetchedPost.next ?? null, + body: fetchedPost.body as TinaMarkdownContent, + }; + + return ; + } catch (error) { + console.error(`Error fetching post for slug: ${slugPath}`, error); + return notFound(); + } +} diff --git a/app/blog/page.tsx b/app/blog/page.tsx new file mode 100644 index 0000000000..7fe48b0b2c --- /dev/null +++ b/app/blog/page.tsx @@ -0,0 +1,21 @@ +import BlogPaginationPage from './page/[page_index]/page'; + +export async function generateMetadata(){ + + const title = 'TinaCMS Blog'; + const description = 'Stay updated with the TinaCMS blog. Get tips, guides and the latest news on content management and development'; + return{ + title: title, + description: description, + openGraph: { + title: title, + description: description, + url: 'https://tinacms.org/blog', + } + } +} + +export default async function BlogPage() { + const params = { page_index: '1' }; + return await BlogPaginationPage({ params }); +} diff --git a/app/blog/page/[page_index]/BlogIndexPageClient.tsx b/app/blog/page/[page_index]/BlogIndexPageClient.tsx new file mode 100644 index 0000000000..932c39e305 --- /dev/null +++ b/app/blog/page/[page_index]/BlogIndexPageClient.tsx @@ -0,0 +1,78 @@ +'use client'; + +import { DynamicLink } from 'components/ui'; +import React from 'react'; +import { formatDate } from 'components/AppRouterMigrationComponents/utils/formatDate'; +import { MarkdownContent } from 'components/layout'; +import NewBlogPagination from 'components/AppRouterMigrationComponents/Blogs/BlogPagination'; +import { extractTextFromBody } from 'components/AppRouterMigrationComponents/utils/extractTextFromBody'; + +interface Post { + id: string; + title: string; + date?: string; + author?: string; + body?: string; + seo?: { + title?: string; + description?: string; + }; + _sys: { + filename: string; + basename: string; + breadcrumbs: string[]; + path: string; + relativePath: string; + extension: string; + }; +} + + +interface BlogPageClientProps { + currentPageIndexNumber: number; + numberOfPages: number; + postsForPageIndex: Post[]; +} +export default function BlogIndexPageClient({ + currentPageIndexNumber: pageIndex, + postsForPageIndex: posts, + numberOfPages: numPages, +}: BlogPageClientProps) { + + return ( +
+
+ {posts.map((post) => ( + +
+

+ {post.title} +

+
+
+

+ By + {post.author} +

+

{formatDate(post.date || '')}

+
+
+ +
+ +
+
+
+
+ ))} +
+ +
+
+
+ ); +} \ No newline at end of file diff --git a/app/blog/page/[page_index]/page.tsx b/app/blog/page/[page_index]/page.tsx new file mode 100644 index 0000000000..7837461b23 --- /dev/null +++ b/app/blog/page/[page_index]/page.tsx @@ -0,0 +1,81 @@ +import client from 'tina/__generated__/client'; +import { glob } from 'fast-glob'; +import BlogIndexPageClient from './BlogIndexPageClient'; +import { notFound } from 'next/navigation'; + +const POSTS_PER_PAGE = 8; + +export async function generateStaticParams() { + const contentDir = './content/blog/'; + const files = await glob(`${contentDir}**/*.mdx`); + const numFiles = Math.ceil(files.length / POSTS_PER_PAGE); + return Array.from(Array(numFiles).keys()).map((page) => ({ + page_index: (page + 1).toString(), + })); +} + +export async function generateMetadata({ params }: { params: { page_index: string } }) { + const title = 'TinaCMS Blog'; + const description = + 'Stay updated with the TinaCMS blog. Get tips, guides and the latest news on content management and development'; + const pageIndex = params.page_index; + const url = `https://tinacms.org/blog/page/${pageIndex}`; + return { + title: title, + description: description, + openGraph: { + title: title, + description: description, + url: url, + }, + }; +} + +export default async function BlogPaginationPage({ + params, +}: { + params: { page_index: string }; +}) { + const contentDir = './content/blog/'; + const posts = await glob(`${contentDir}**/*.mdx`); + const numPages = Math.ceil(posts.length / POSTS_PER_PAGE); + const pageIndex = parseInt(params.page_index) || 1; + const startIndex = (pageIndex - 1) * POSTS_PER_PAGE; + + let postResponse = null; + try { + postResponse = await client.queries.postConnection({ + first: posts.length, + sort: 'date', + }); + } catch (err) { + console.error('Error fetching postConnection:', err); + notFound() + } + + let reversedPosts = []; + try { + reversedPosts = postResponse?.data?.postConnection?.edges + ?.map((edge) => edge?.node) + ?.filter(Boolean) + ?.reverse(); + } catch (err) { + console.error('Error processing posts:', err); + notFound() + } + + const finalisedPostData = reversedPosts.slice( + startIndex, + startIndex + POSTS_PER_PAGE + ); + + return ( + <> + + + ); +} diff --git a/app/docs/[...slug]/DocsPagesClient.tsx b/app/docs/[...slug]/DocsPagesClient.tsx index edbed0dd89..a7f6cb5a75 100644 --- a/app/docs/[...slug]/DocsPagesClient.tsx +++ b/app/docs/[...slug]/DocsPagesClient.tsx @@ -4,15 +4,23 @@ import MainDocsBodyHeader from 'components/AppRouterMigrationComponents/Docs/doc import TocOverflowButton from 'components/AppRouterMigrationComponents/Docs/docsMain/tocOverflowButton'; import { LeftHandSideParentContainer } from 'components/AppRouterMigrationComponents/Docs/docsSearch/SearchNavigation'; import ToC from 'components/AppRouterMigrationComponents/Docs/toc'; +import { useTocListener } from 'components/AppRouterMigrationComponents/Docs/toc_helper'; +import { formatDate } from 'components/AppRouterMigrationComponents/utils/formatDate'; import { screenResizer } from 'components/hooks/ScreenResizer'; import { docAndBlogComponents } from 'components/tinaMarkdownComponents/docAndBlogComponents'; import { DocsPagination } from 'components/ui'; +import { useTina } from 'tinacms/dist/react'; import { TinaMarkdown } from 'tinacms/dist/rich-text'; -import { useTocListener } from 'components/AppRouterMigrationComponents/Docs/toc_helper'; -export default function DocsClient(props) { - const { PageTableOfContents, NavigationDocsData } = props.props; - const DocumentationData = props.tinaProps.data.doc; +export default function DocsClient({ props }) { + const { data } = useTina({ + query: props.query, + variables: props.variables, + data: props.data, + }); + + const { PageTableOfContents, NavigationDocsData } = props; + const DocumentationData = data.doc; const allData = [DocumentationData, PageTableOfContents, NavigationDocsData]; @@ -31,14 +39,7 @@ export default function DocsClient(props) { }; const lastEdited = DocumentationData?.last_edited; - const date = lastEdited === null ? null : new Date(lastEdited); - const formattedDate = date - ? date.toLocaleDateString('en-US', { - year: 'numeric', - month: 'long', - day: 'numeric', - }) - : ''; + const formattedDate = formatDate(lastEdited); const gridClass = isScreenSmallerThan840 ? 'grid-cols-1' : isScreenSmallerThan1200 diff --git a/app/docs/[...slug]/page.tsx b/app/docs/[...slug]/page.tsx index d921617e27..b463fe7823 100644 --- a/app/docs/[...slug]/page.tsx +++ b/app/docs/[...slug]/page.tsx @@ -1,24 +1,26 @@ -import { TinaClient } from 'app/tina-client'; -import { docAndBlogComponents } from 'components/tinaMarkdownComponents/docAndBlogComponents'; import { glob } from 'fast-glob'; -import { NextSeo } from 'next-seo'; import { notFound } from 'next/navigation'; import client from 'tina/__generated__/client'; -import { TinaMarkdown } from 'tinacms/dist/rich-text'; import { getDocsNav } from 'utils/docs/getDocProps'; import getTableOfContents from 'utils/docs/getTableOfContents'; import DocsClient from './DocsPagesClient'; export async function generateStaticParams() { - const contentDir = './content/docs/'; + try{ + const contentDir = './content/docs/'; const files = await glob(`${contentDir}**/*.mdx`); - return files .filter((file) => !file.endsWith('index.mdx')) .map((file) => { const path = file.substring(contentDir.length, file.length - 4); // Remove "./content/docs/" and ".mdx" return { slug: path.split('/') }; }); + } catch(error) + { + console.error(error); + notFound() + } + } export async function generateMetadata({ @@ -27,16 +29,21 @@ export async function generateMetadata({ params: { slug: string[] }; }) { const slug = params.slug.join('/'); - const { data } = await client.queries.doc({ relativePath: `${slug}.mdx` }); + try { + const { data } = await client.queries.doc({ relativePath: `${slug}.mdx` }); - return { - title: `${data.doc.seo?.title || data.doc.title} | 🦙 TinaCMS Docs`, - description: data.doc.seo?.description || '', - openGraph: { - title: data.doc.title, - description: data.doc.seo?.description, - }, - }; + return { + title: `${data.doc.seo?.title || data.doc.title} | TinaCMS Docs`, + description: data.doc.seo?.description || '', + openGraph: { + title: data.doc.title, + description: data.doc.seo?.description, + }, + }; + } catch (error) { + console.error('Error generating metadata:', error); + return notFound(); + } } export default async function DocPage({ @@ -55,20 +62,22 @@ export default async function DocPage({ const docData = results.data.doc; const PageTableOfContents = getTableOfContents(docData.body.children); + const props = { + query: results.query, + variables: results.variables, + data: results.data, + PageTableOfContents, + DocumentationData: docData, + NavigationDocsData: navDocData, + }; + return ( - +
+ +
); } catch (error) { - notFound(); + console.error('Found an error catching data:', error); + return notFound(); } } diff --git a/app/docs/page.tsx b/app/docs/page.tsx index ed568ed90f..6dadde2586 100644 --- a/app/docs/page.tsx +++ b/app/docs/page.tsx @@ -31,7 +31,6 @@ export default async function DocsPage() { /> ); } catch (error) { - notFound(); } } diff --git a/app/error.tsx b/app/error.tsx new file mode 100644 index 0000000000..798b762b89 --- /dev/null +++ b/app/error.tsx @@ -0,0 +1,15 @@ +'use client'; + +import { useEffect } from 'react'; +import { useRouter } from 'next/navigation'; + +export default function GlobalError({ error }: { error: Error }) { + const router = useRouter(); + + useEffect(() => { + console.error('Global Error:', error); + router.replace('/not-found'); + }, [error, router]); + + return null; +} diff --git a/app/layout.tsx b/app/layout.tsx index a9081f784d..ee3a0e03a8 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,4 +1,4 @@ -import './global.css' +import './global.css'; import { GoogleTagManager } from '@next/third-parties/google'; import AdminLink from 'components/AppRouterMigrationComponents/AdminLink'; import { CloudBanner } from 'components/AppRouterMigrationComponents/CloudBanner'; @@ -18,8 +18,7 @@ const TinaChatBot = dynamic( export const metadata = { title: data.seoDefaultTitle, descripton: data.description, - icons: - { + icons: { icon: '/favicon/favicon.ico', }, openGraph: { @@ -35,13 +34,13 @@ export const metadata = { }, ], }, - twitter:{ + twitter: { title: data.seoDefaultTitle, description: data.description, card: 'summary_large_image', site: data.social.twitterHandle, }, -} +}; export default async function RootLayout({ children, @@ -51,13 +50,42 @@ export default async function RootLayout({ return ( + + - {children} + +