diff --git a/apps/web/app/(marketing)/blog/[slug]/page.tsx b/apps/web/app/(marketing)/blog/[slug]/page.tsx index 804f9b415..215f9e3e6 100644 --- a/apps/web/app/(marketing)/blog/[slug]/page.tsx +++ b/apps/web/app/(marketing)/blog/[slug]/page.tsx @@ -36,7 +36,7 @@ export async function generateMetadata({ title, description, type: 'article', - publishedTime: publishedAt?.toDateString(), + publishedTime: publishedAt, url: post.url, images: image ? [ diff --git a/apps/web/app/(marketing)/blog/_components/post-header.tsx b/apps/web/app/(marketing)/blog/_components/post-header.tsx index 7fbff4201..e9222228e 100644 --- a/apps/web/app/(marketing)/blog/_components/post-header.tsx +++ b/apps/web/app/(marketing)/blog/_components/post-header.tsx @@ -20,7 +20,7 @@ export const PostHeader: React.FC<{
- +
diff --git a/apps/web/app/(marketing)/blog/_components/post-preview.tsx b/apps/web/app/(marketing)/blog/_components/post-preview.tsx index 5ea463565..2641b4cf6 100644 --- a/apps/web/app/(marketing)/blog/_components/post-preview.tsx +++ b/apps/web/app/(marketing)/blog/_components/post-preview.tsx @@ -50,7 +50,7 @@ export function PostPreview({
- +
diff --git a/apps/web/app/(marketing)/blog/page.tsx b/apps/web/app/(marketing)/blog/page.tsx index 86df0eb59..18926d722 100644 --- a/apps/web/app/(marketing)/blog/page.tsx +++ b/apps/web/app/(marketing)/blog/page.tsx @@ -1,3 +1,5 @@ +import { unstable_cache as cache } from 'next/dist/server/web/spec-extension/unstable-cache'; + import { createCmsClient } from '@kit/cms'; import { If } from '@kit/ui/if'; import { Trans } from '@kit/ui/trans'; @@ -18,22 +20,33 @@ export const generateMetadata = async () => { }; }; +const getContentItems = cache( + async (language: string | undefined, limit: number, offset: number) => { + const client = await createCmsClient(); + + return client.getContentItems({ + collection: 'posts', + limit, + offset, + language, + sortBy: 'publishedAt', + sortDirection: 'desc', + }); + }, +); + async function BlogPage({ searchParams }: { searchParams: { page: string } }) { const { t, resolvedLanguage: language } = await createI18nServerInstance(); - const cms = await createCmsClient(); const page = searchParams.page ? parseInt(searchParams.page) : 0; const limit = 10; const offset = page * limit; - const { items: posts, total } = await cms.getContentItems({ - collection: 'posts', + const { total, items: posts } = await getContentItems( + language, limit, offset, - language, - sortBy: 'publishedAt', - sortDirection: 'desc', - }); + ); return ( <> diff --git a/apps/web/app/(marketing)/docs/[...slug]/page.tsx b/apps/web/app/(marketing)/docs/[...slug]/page.tsx index 7b183c74e..b9b5600eb 100644 --- a/apps/web/app/(marketing)/docs/[...slug]/page.tsx +++ b/apps/web/app/(marketing)/docs/[...slug]/page.tsx @@ -1,5 +1,4 @@ -import { cache } from 'react'; - +import { unstable_cache as cache } from 'next/cache'; import { notFound } from 'next/navigation'; import { ContentRenderer, createCmsClient } from '@kit/cms'; diff --git a/apps/web/app/(marketing)/docs/page.tsx b/apps/web/app/(marketing)/docs/page.tsx index 902091dcd..6d571bb85 100644 --- a/apps/web/app/(marketing)/docs/page.tsx +++ b/apps/web/app/(marketing)/docs/page.tsx @@ -1,3 +1,5 @@ +import { unstable_cache as cache } from 'next/cache'; + import { createCmsClient } from '@kit/cms'; import { PageBody } from '@kit/ui/page'; @@ -14,14 +16,18 @@ export const generateMetadata = async () => { }; }; -async function DocsPage() { +const getContentItems = cache(async (resolvedLanguage: string | undefined) => { const client = await createCmsClient(); - const { t, resolvedLanguage } = await createI18nServerInstance(); - const { items } = await client.getContentItems({ + return client.getContentItems({ collection: 'documentation', language: resolvedLanguage, }); +}); + +async function DocsPage() { + const { t, resolvedLanguage } = await createI18nServerInstance(); + const { items } = await getContentItems(resolvedLanguage); // Filter out any docs that have a parentId, as these are children of other docs const cards = items.filter((item) => !item.parentId); diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index 2cd75f400..789c26526 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -1,32 +1,17 @@ -import { Urbanist as HeadingFont, Inter as SansFont } from 'next/font/google'; import Head from 'next/head'; -import { cookies, headers } from 'next/headers'; +import { cookies } from 'next/headers'; import { Toaster } from '@kit/ui/sonner'; import { cn } from '@kit/ui/utils'; +import { CsrfTokenMeta } from '~/components/csrf-token-meta'; import { RootProviders } from '~/components/root-providers'; -import appConfig from '~/config/app.config'; +import { heading, sans } from '~/lib/fonts'; import { createI18nServerInstance } from '~/lib/i18n/i18n.server'; +import { rootMetadata } from '~/lib/root-metdata'; import '../styles/globals.css'; -const sans = SansFont({ - subsets: ['latin'], - variable: '--font-sans', - fallback: ['system-ui', 'Helvetica Neue', 'Helvetica', 'Arial'], - preload: true, - weight: ['300', '400', '500', '600', '700'], -}); - -const heading = HeadingFont({ - subsets: ['latin'], - variable: '--font-heading', - fallback: ['system-ui', 'Helvetica Neue', 'Helvetica', 'Arial'], - preload: true, - weight: ['500', '700'], -}); - export default async function RootLayout({ children, }: { @@ -34,9 +19,10 @@ export default async function RootLayout({ }) { const { language } = await createI18nServerInstance(); const theme = getTheme(); + const className = getClassName(theme); return ( - + @@ -71,33 +57,4 @@ function getTheme() { return cookies().get('theme')?.value; } -export const metadata = { - title: appConfig.name, - description: appConfig.description, - metadataBase: new URL(appConfig.url), - openGraph: { - url: appConfig.url, - siteName: appConfig.name, - description: appConfig.description, - }, - twitter: { - card: 'summary_large_image', - title: appConfig.title, - description: appConfig.description, - }, - icons: { - icon: '/images/favicon/favicon.ico', - shortcut: '/shortcut-icon.png', - apple: '/images/favicon/apple-touch-icon.png', - other: { - rel: 'apple-touch-icon-precomposed', - url: '/apple-touch-icon-precomposed.png', - }, - }, -}; - -function CsrfTokenMeta() { - const csrf = headers().get('x-csrf-token') ?? ''; - - return ; -} +export const metadata = rootMetadata; diff --git a/apps/web/components/csrf-token-meta.tsx b/apps/web/components/csrf-token-meta.tsx new file mode 100644 index 000000000..5e40cd37d --- /dev/null +++ b/apps/web/components/csrf-token-meta.tsx @@ -0,0 +1,12 @@ +import { headers } from 'next/headers'; + +/** + * @description This component is used to render the CSRF token as a meta tag. + * this tag can be retrieved for use in forms that require CSRF protection. + * @constructor + */ +export function CsrfTokenMeta() { + const csrf = headers().get('x-csrf-token') ?? ''; + + return ; +} diff --git a/apps/web/lib/fonts.ts b/apps/web/lib/fonts.ts new file mode 100644 index 000000000..85572e07a --- /dev/null +++ b/apps/web/lib/fonts.ts @@ -0,0 +1,30 @@ +import { Urbanist as HeadingFont, Inter as SansFont } from 'next/font/google'; + +/** + * @sans + * @description Define here the sans font. + * By default, it uses the Inter font from Google Fonts. + */ +const sans = SansFont({ + subsets: ['latin'], + variable: '--font-sans', + fallback: ['system-ui', 'Helvetica Neue', 'Helvetica', 'Arial'], + preload: true, + weight: ['300', '400', '500', '600', '700'], +}); + +/** + * @heading + * @description Define here the heading font. + * By default, it uses the Urbanist font from Google Fonts. + */ +const heading = HeadingFont({ + subsets: ['latin'], + variable: '--font-heading', + fallback: ['system-ui', 'Helvetica Neue', 'Helvetica', 'Arial'], + preload: true, + weight: ['500', '700'], +}); + +// we export these fonts into the root layout +export { sans, heading }; diff --git a/apps/web/lib/root-metdata.ts b/apps/web/lib/root-metdata.ts new file mode 100644 index 000000000..d8aea6fcc --- /dev/null +++ b/apps/web/lib/root-metdata.ts @@ -0,0 +1,32 @@ +import { Metadata } from 'next'; + +import appConfig from '~/config/app.config'; + +/** + * @name rootMetadata + * @description Define the root metadata for the application. + */ +export const rootMetadata: Metadata = { + title: appConfig.name, + description: appConfig.description, + metadataBase: new URL(appConfig.url), + openGraph: { + url: appConfig.url, + siteName: appConfig.name, + description: appConfig.description, + }, + twitter: { + card: 'summary_large_image', + title: appConfig.title, + description: appConfig.description, + }, + icons: { + icon: '/images/favicon/favicon.ico', + shortcut: '/shortcut-icon.png', + apple: '/images/favicon/apple-touch-icon.png', + other: { + rel: 'apple-touch-icon-precomposed', + url: '/apple-touch-icon-precomposed.png', + }, + }, +}; diff --git a/packages/cms/core/src/cms-client.ts b/packages/cms/core/src/cms-client.ts index 7d15732b2..aa8d886d8 100644 --- a/packages/cms/core/src/cms-client.ts +++ b/packages/cms/core/src/cms-client.ts @@ -6,7 +6,7 @@ export namespace Cms { url: string; description: string | undefined; content: unknown; - publishedAt: Date; + publishedAt: string; image: string | undefined; slug: string; categories: Category[]; diff --git a/packages/cms/keystatic/src/client.ts b/packages/cms/keystatic/src/client.ts index d5b816541..c8555cb6b 100644 --- a/packages/cms/keystatic/src/client.ts +++ b/packages/cms/keystatic/src/client.ts @@ -16,8 +16,6 @@ export class KeystaticClient implements CmsClient { const docs = await reader.collections[collection].all(); - console.log(docs); - const startOffset = options?.offset ?? 0; const endOffset = startOffset + (options?.limit ?? 10); @@ -144,7 +142,7 @@ export class KeystaticClient implements CmsClient { url: item.slug, slug: item.slug, description: item.entry.description, - publishedAt, + publishedAt: publishedAt.toISOString(), content, image: item.entry.image ?? undefined, categories: