Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
8 changes: 6 additions & 2 deletions .devcontainer/frontend/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@
"esbenp.prettier-vscode",
"bradlc.vscode-tailwindcss",
"dsznajder.es7-react-js-snippets",
"csstools.postcss"
"csstools.postcss",
"graphql.vscode-graphql",
"esbenp.prettier-vscode",
"anthropic.claude-code",
"openai.chatgpt"
]
}
},
"forwardPorts": [3000],
"postCreateCommand": "node --version && npm --version"
"postCreateCommand": "node --version && npm --version && npm i -g @anthropic-ai/claude-code"
}
8 changes: 6 additions & 2 deletions website/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,14 @@ typings/
# dotenv environment variable files
.env*

# gatsby files
# gatsby files (legacy)
.cache/
public
/graphql-types.ts

# next.js
.next/
out/
next-env.d.ts

# Mac files
.DS_Store
96 changes: 96 additions & 0 deletions website/app/blog/[...slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React from "react";

import {
getAllBlogPosts,
getBlogPostBySlug,
getPaginatedPosts,
getPostsPerPage,
} from "@/lib/blog";
import { compileMdxContent } from "@/lib/mdx";
import { createMetadata } from "@/lib/metadata";
import { BlogPostPage } from "@/lib/blog-post-page";
import { BlogListPage } from "@/lib/blog-list-page";

interface PageProps {
params: Promise<{ slug: string[] }>;
}

export async function generateStaticParams() {
const posts = getAllBlogPosts();
const postsPerPage = getPostsPerPage();
const totalPages = Math.ceil(posts.length / postsPerPage);

const params: { slug: string[] }[] = [];

// Pagination pages (page 2+)
for (let i = 2; i <= totalPages; i++) {
params.push({ slug: [String(i)] });
}

// Blog post pages (year/month/day/slug)
for (const post of posts) {
const parts = post.slug.replace(/^\/blog\//, "").split("/");
params.push({ slug: parts });
}

return params;
}

export async function generateMetadata({ params }: PageProps) {
const { slug } = await params;

// Pagination page
if (slug.length === 1 && /^\d+$/.test(slug[0])) {
return createMetadata({ title: `Blog - Page ${slug[0]}` });
}

// Blog post (year/month/day/slug)
if (slug.length === 4) {
const postSlug = `/blog/${slug.join("/")}`;
const post = getBlogPostBySlug(postSlug);

return createMetadata({
title: post?.title || "Blog Post",
description: post?.description,
isArticle: true,
imageUrl: post?.featuredImage,
});
}

return createMetadata({ title: "Blog" });
}

export default async function BlogCatchAllPage({ params }: PageProps) {
const { slug } = await params;

// Pagination page (e.g., /blog/2, /blog/3)
if (slug.length === 1 && /^\d+$/.test(slug[0])) {
const pageNum = parseInt(slug[0], 10);
const { posts, totalPages } = getPaginatedPosts(pageNum);

return (
<BlogListPage
posts={posts}
currentPage={pageNum}
totalPages={totalPages}
linkPrefix="/blog"
/>
);
}

// Blog post page (e.g., /blog/2024/01/15/my-post)
if (slug.length === 4) {
const postSlug = `/blog/${slug.join("/")}`;
const post = getBlogPostBySlug(postSlug);

if (!post) {
return <div>Post not found</div>;
Comment thread
tobias-tengler marked this conversation as resolved.
Outdated
}

const { mdxSource } = await compileMdxContent(post.content);

return <BlogPostPage post={post} mdxSource={mdxSource} />;
}

return <div>Not found</div>;
}
20 changes: 20 additions & 0 deletions website/app/blog/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from "react";

import { getPaginatedPosts } from "@/lib/blog";
import { BlogListPage } from "@/lib/blog-list-page";
import { createMetadata } from "@/lib/metadata";

export const metadata = createMetadata({ title: "Blog" });

export default function BlogPage() {
const { posts, totalPages } = getPaginatedPosts(1);

return (
<BlogListPage
posts={posts}
currentPage={1}
totalPages={totalPages}
linkPrefix="/blog"
/>
);
}
45 changes: 45 additions & 0 deletions website/app/blog/tags/[tag]/[page]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from "react";

import { getAllTags, getPostsByTag, getPostsPerPage } from "@/lib/blog";
import { BlogListPage } from "@/lib/blog-list-page";
import { createMetadata } from "@/lib/metadata";

interface PageProps {
params: Promise<{ tag: string; page: string }>;
}

export async function generateStaticParams() {
const tags = getAllTags();
const postsPerPage = getPostsPerPage();
const params: { tag: string; page: string }[] = [];

for (const tag of tags) {
const { totalPages } = getPostsByTag(tag, 1);
for (let i = 2; i <= totalPages; i++) {
params.push({ tag, page: String(i) });
}
}

return params;
}

export async function generateMetadata({ params }: PageProps) {
const { tag, page } = await params;
return createMetadata({ title: `Blog - ${tag} - Page ${page}` });
}

export default async function BlogTagPaginatedPage({ params }: PageProps) {
const { tag, page } = await params;
const pageNum = parseInt(page, 10);
const { posts, totalPages } = getPostsByTag(tag, pageNum);

return (
<BlogListPage
posts={posts}
currentPage={pageNum}
totalPages={totalPages}
linkPrefix={`/blog/tags/${tag}`}
tag={tag}
/>
);
}
33 changes: 33 additions & 0 deletions website/app/blog/tags/[tag]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from "react";

import { getAllTags, getPostsByTag } from "@/lib/blog";
import { BlogListPage } from "@/lib/blog-list-page";
import { createMetadata } from "@/lib/metadata";

interface PageProps {
params: Promise<{ tag: string }>;
}

export async function generateStaticParams() {
return getAllTags().map((tag) => ({ tag }));
}

export async function generateMetadata({ params }: PageProps) {
const { tag } = await params;
return createMetadata({ title: `Blog - ${tag}` });
}

export default async function BlogTagPage({ params }: PageProps) {
const { tag } = await params;
const { posts, totalPages } = getPostsByTag(tag, 1);

return (
<BlogListPage
posts={posts}
currentPage={1}
totalPages={totalPages}
linkPrefix={`/blog/tags/${tag}`}
tag={tag}
/>
);
}
55 changes: 55 additions & 0 deletions website/app/docs/[...slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from "react";

import { getAllDocPages, getDocPageBySlug, getDocsConfig } from "@/lib/docs";
import { compileMdxContent, extractHeadings } from "@/lib/mdx";
import { createMetadata } from "@/lib/metadata";
import { DocPageView } from "@/lib/doc-page-view";

interface PageProps {
params: Promise<{ slug: string[] }>;
}

export async function generateStaticParams() {
const pages = getAllDocPages();

return pages.map((page) => ({
slug: page.slug.replace(/^\/docs\//, "").split("/"),
}));
}

export async function generateMetadata({ params }: PageProps) {
const { slug } = await params;
const fullSlug = "/docs/" + slug.join("/");
const page = getDocPageBySlug(fullSlug);

const title = page?.frontmatter?.title || slug[slug.length - 1] || "Documentation";

return createMetadata({
title,
description: page?.frontmatter?.description,
});
}

export default async function DocPage({ params }: PageProps) {
const { slug } = await params;
const fullSlug = "/docs/" + slug.join("/");
const page = getDocPageBySlug(fullSlug);

if (!page) {
return <div>Page not found: {fullSlug}</div>;
Comment thread
tobias-tengler marked this conversation as resolved.
Outdated
}

const { mdxSource } = await compileMdxContent(page.content, page.originPath);
const docsConfig = getDocsConfig();
const headings = extractHeadings(page.content);

return (
<DocPageView
page={page}
mdxSource={mdxSource}
docsConfig={docsConfig}
slug={slug}
headings={headings}
/>
);
}
20 changes: 20 additions & 0 deletions website/app/docs/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { redirect } from "next/navigation";

import { getDocsConfig } from "@/lib/docs";

function getRedirectPath(): string {
const products = getDocsConfig();
const hotchocolate = products.find((p) => p.path === "hotchocolate");
const target = hotchocolate || products[0];

if (target) {
const version = target.latestStableVersion;
return `/docs/${target.path}${version ? `/${version}` : ""}`;
}

return "/";
}

export default function DocsIndex() {
redirect(getRedirectPath());
}
9 changes: 9 additions & 0 deletions website/app/help/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from "react";

import { getRecentBlogPostTeasers } from "@/lib/blog";
import HelpPage from "@/page-components/help";

export default function Page() {
const recentPosts = getRecentBlogPostTeasers();
return <HelpPage recentPosts={recentPosts} />;
}
30 changes: 30 additions & 0 deletions website/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from "react";
import type { Metadata } from "next";

import { Providers } from "@/lib/providers";
import { siteMetadata } from "@/lib/site-config";

export const metadata: Metadata = {
title: siteMetadata.title,
description: siteMetadata.description,
};

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<head>
<link
href="https://fonts.googleapis.com/css2?family=Radio+Canada:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
</head>
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}
Loading
Loading