Forms
diff --git a/demos/form-builder/next.config.ts b/demos/form-builder/next.config.ts
index 2e3fbd8..46abc09 100644
--- a/demos/form-builder/next.config.ts
+++ b/demos/form-builder/next.config.ts
@@ -2,9 +2,7 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = {
async redirects() {
- return [
- { source: "/", destination: "/pages/form-builder", permanent: false },
- ];
+ return [{ source: "/", destination: "/pages/forms", permanent: false }];
},
};
diff --git a/docs/content/docs/demos/form-builder.mdx b/docs/content/docs/demos/form-builder.mdx
index b1f73ea..5a4866d 100644
--- a/docs/content/docs/demos/form-builder.mdx
+++ b/docs/content/docs/demos/form-builder.mdx
@@ -17,7 +17,7 @@ A standalone Next.js demo showcasing the `form-builder` plugin. Two sample forms
[Open in StackBlitz →](https://stackblitz.com/github/better-stack-ai/better-stack/tree/main/demos/form-builder?startScript=dev&file=lib%2Fstack.ts)
From 778f8891afa35b95ba64dda5152c4a5ac79d8c85 Mon Sep 17 00:00:00 2001
From: olliethedev <3martynov@gmail.com>
Date: Fri, 6 Mar 2026 16:08:00 -0500
Subject: [PATCH 4/6] fix: update key for AiChatPluginOverrides in layout to
use hyphenated format
---
demos/ai-chat/app/pages/layout.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/demos/ai-chat/app/pages/layout.tsx b/demos/ai-chat/app/pages/layout.tsx
index 3856470..6c94754 100644
--- a/demos/ai-chat/app/pages/layout.tsx
+++ b/demos/ai-chat/app/pages/layout.tsx
@@ -9,7 +9,7 @@ import type { AiChatPluginOverrides } from "@btst/stack/plugins/ai-chat/client";
import { getOrCreateQueryClient } from "@/lib/query-client";
type PluginOverrides = {
- aiChat: AiChatPluginOverrides;
+ "ai-chat": AiChatPluginOverrides;
};
const hasApiKey =
@@ -33,7 +33,7 @@ export default function PagesLayout({
basePath="/pages"
overrides={{
- aiChat: {
+ "ai-chat": {
apiBaseURL: baseURL,
apiBasePath: "/api/data",
mode: "public",
From 475e289ac439486ec30e42f362b39c5bfd8a3a4e Mon Sep 17 00:00:00 2001
From: olliethedev <3martynov@gmail.com>
Date: Fri, 6 Mar 2026 16:09:40 -0500
Subject: [PATCH 5/6] feat: add articles page and individual article view with
dynamic metadata
---
demos/cms/app/pages/articles/[slug]/page.tsx | 82 ++++++++++++++++++++
demos/cms/app/pages/articles/page.tsx | 67 ++++++++++++++++
demos/cms/app/pages/layout.tsx | 6 ++
3 files changed, 155 insertions(+)
create mode 100644 demos/cms/app/pages/articles/[slug]/page.tsx
create mode 100644 demos/cms/app/pages/articles/page.tsx
diff --git a/demos/cms/app/pages/articles/[slug]/page.tsx b/demos/cms/app/pages/articles/[slug]/page.tsx
new file mode 100644
index 0000000..92ae0e9
--- /dev/null
+++ b/demos/cms/app/pages/articles/[slug]/page.tsx
@@ -0,0 +1,82 @@
+import { myStack } from "@/lib/stack";
+import Link from "next/link";
+import { notFound } from "next/navigation";
+import type { Metadata } from "next";
+
+export const dynamic = "force-dynamic";
+
+type ArticleData = {
+ title: string;
+ summary: string;
+ body: string;
+ publishedAt?: string;
+ published: boolean;
+};
+
+export async function generateMetadata({
+ params,
+}: {
+ params: Promise<{ slug: string }>;
+}): Promise {
+ const { slug } = await params;
+ const item = await myStack.api.cms.getContentItemBySlug("article", slug);
+ if (!item) return { title: "Not Found" };
+ const data = item.parsedData as ArticleData;
+ return {
+ title: data.title,
+ description: data.summary,
+ };
+}
+
+export default async function ArticlePage({
+ params,
+}: {
+ params: Promise<{ slug: string }>;
+}) {
+ const { slug } = await params;
+ const item = await myStack.api.cms.getContentItemBySlug("article", slug);
+
+ if (!item) notFound();
+
+ const data = item.parsedData as ArticleData;
+
+ return (
+
+
+ ← All articles
+
+
+
+
+ {data.title}
+ {data.publishedAt && (
+
+ )}
+
+
+
+ {data.summary}
+
+
+
+ {data.body.split("\n\n").map((paragraph, i) =>
+ paragraph.trim() ? (
+
+ {paragraph.trim()}
+
+ ) : null,
+ )}
+
+
+
+ );
+}
diff --git a/demos/cms/app/pages/articles/page.tsx b/demos/cms/app/pages/articles/page.tsx
new file mode 100644
index 0000000..2c8ec79
--- /dev/null
+++ b/demos/cms/app/pages/articles/page.tsx
@@ -0,0 +1,67 @@
+import { myStack } from "@/lib/stack";
+import Link from "next/link";
+
+export const dynamic = "force-dynamic";
+
+type ArticleData = {
+ title: string;
+ summary: string;
+ body: string;
+ publishedAt?: string;
+ published: boolean;
+};
+
+export default async function ArticlesPage() {
+ const { items } = await myStack.api.cms.getAllContentItems("article");
+ const published = items.filter(
+ (item) => (item.parsedData as ArticleData).published,
+ );
+
+ return (
+
+
+
Articles
+
+ Content managed with the BTST CMS plugin.
+
+
+
+ {published.length === 0 ? (
+
No published articles yet.
+ ) : (
+
+ {published.map((item) => {
+ const data = item.parsedData as ArticleData;
+ return (
+
+
+
+
+ {data.title}
+
+
+ {data.summary}
+
+
+ {data.publishedAt && (
+
+ )}
+
+
+ );
+ })}
+
+ )}
+
+ );
+}
diff --git a/demos/cms/app/pages/layout.tsx b/demos/cms/app/pages/layout.tsx
index 28ccac9..a4d9c73 100644
--- a/demos/cms/app/pages/layout.tsx
+++ b/demos/cms/app/pages/layout.tsx
@@ -47,6 +47,12 @@ export default function PagesLayout({
BTST CMS Demo
+
+ Articles
+
Date: Fri, 6 Mar 2026 16:10:03 -0500
Subject: [PATCH 6/6] feat: add 'Live Demo' link to the form-builder layout for
enhanced navigation
---
demos/form-builder/app/pages/layout.tsx | 6 +
demos/form-builder/app/submit/[slug]/page.tsx | 103 ++++++++++++++++++
2 files changed, 109 insertions(+)
create mode 100644 demos/form-builder/app/submit/[slug]/page.tsx
diff --git a/demos/form-builder/app/pages/layout.tsx b/demos/form-builder/app/pages/layout.tsx
index ad85f24..7d482b5 100644
--- a/demos/form-builder/app/pages/layout.tsx
+++ b/demos/form-builder/app/pages/layout.tsx
@@ -53,6 +53,12 @@ export default function PagesLayout({
>
Forms
+
+ Live Demo
+
getOrCreateQueryClient());
+ const baseURL =
+ typeof window !== "undefined"
+ ? window.location.origin
+ : "http://localhost:3000";
+
+ return (
+
+
+ basePath=""
+ overrides={{
+ "form-builder": {
+ apiBaseURL: baseURL,
+ apiBasePath: "/api/data",
+ navigate: (path) => router.push(path),
+ refresh: () => router.refresh(),
+ Link: ({ href, ...props }) => (
+
+ ),
+ },
+ }}
+ >
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function FormLoadingState() {
+ return (
+
+ );
+}
+
+function FormErrorState({ error }: { error: Error }) {
+ return (
+
+
+
+
Form not found
+
+ {error?.message ||
+ "This form doesn't exist or is no longer accepting submissions."}
+
+
+
+ Browse all forms
+
+
+ );
+}