From ded27758c50443feeaa746648d3b5d2116b7640a Mon Sep 17 00:00:00 2001 From: olliethedev <3martynov@gmail.com> Date: Thu, 12 Mar 2026 16:09:51 -0400 Subject: [PATCH 01/45] feat: comments plugin --- docs/content/docs/cli.mdx | 6 + docs/content/docs/meta.json | 1 + docs/content/docs/plugins/blog.mdx | 26 + docs/content/docs/plugins/comments.mdx | 423 +++++++++++++ docs/content/docs/plugins/kanban.mdx | 26 + e2e/tests/smoke.comments.spec.ts | 349 +++++++++++ examples/nextjs/app/globals.css | 1 + examples/nextjs/app/pages/layout.tsx | 32 + examples/nextjs/lib/stack-client.tsx | 10 + examples/nextjs/lib/stack.ts | 21 + examples/react-router/app/app.css | 1 + .../react-router/app/lib/stack-client.tsx | 9 + examples/react-router/app/lib/stack.ts | 8 + .../react-router/app/routes/pages/_layout.tsx | 21 + examples/tanstack/src/lib/stack-client.tsx | 9 + examples/tanstack/src/lib/stack.ts | 8 + examples/tanstack/src/routes/pages/route.tsx | 21 + examples/tanstack/src/styles/globals.css | 1 + packages/stack/build.config.ts | 6 + packages/stack/package.json | 66 +++ packages/stack/registry/btst-blog.json | 15 +- packages/stack/registry/btst-comments.json | 98 ++++ packages/stack/registry/btst-kanban.json | 4 +- packages/stack/registry/registry.json | 25 +- packages/stack/scripts/build-registry.ts | 11 +- .../components/pages/post-page.internal.tsx | 23 +- .../components/shared/post-navigation.tsx | 5 - .../shared/recent-posts-carousel.tsx | 6 +- .../plugins/blog/client/hooks/blog-hooks.tsx | 41 +- .../src/plugins/blog/client/overrides.ts | 27 +- .../stack/src/plugins/comments/api/getters.ts | 263 +++++++++ .../stack/src/plugins/comments/api/index.ts | 21 + .../src/plugins/comments/api/mutations.ts | 183 ++++++ .../stack/src/plugins/comments/api/plugin.ts | 337 +++++++++++ .../plugins/comments/api/query-key-defs.ts | 89 +++ .../src/plugins/comments/api/serializers.ts | 36 ++ .../stack/src/plugins/comments/client.css | 2 + .../client/components/comment-count.tsx | 66 +++ .../client/components/comment-form.tsx | 96 +++ .../client/components/comment-thread.tsx | 555 ++++++++++++++++++ .../comments/client/components/index.tsx | 11 + .../pages/moderation-page.internal.tsx | 505 ++++++++++++++++ .../components/pages/moderation-page.tsx | 68 +++ .../pages/resource-comments-page.internal.tsx | 214 +++++++ .../pages/resource-comments-page.tsx | 91 +++ .../plugins/comments/client/hooks/index.tsx | 11 + .../comments/client/hooks/use-comments.tsx | 417 +++++++++++++ .../src/plugins/comments/client/index.ts | 10 + .../src/plugins/comments/client/overrides.ts | 81 +++ .../src/plugins/comments/client/plugin.tsx | 118 ++++ packages/stack/src/plugins/comments/db.ts | 77 +++ .../stack/src/plugins/comments/query-keys.ts | 140 +++++ .../stack/src/plugins/comments/schemas.ts | 39 ++ packages/stack/src/plugins/comments/style.css | 15 + packages/stack/src/plugins/comments/types.ts | 67 +++ .../components/pages/board-page.internal.tsx | 73 ++- .../src/plugins/kanban/client/overrides.ts | 28 +- packages/ui/src/components/when-visible.tsx | 65 ++ 58 files changed, 4889 insertions(+), 89 deletions(-) create mode 100644 docs/content/docs/plugins/comments.mdx create mode 100644 e2e/tests/smoke.comments.spec.ts create mode 100644 packages/stack/registry/btst-comments.json create mode 100644 packages/stack/src/plugins/comments/api/getters.ts create mode 100644 packages/stack/src/plugins/comments/api/index.ts create mode 100644 packages/stack/src/plugins/comments/api/mutations.ts create mode 100644 packages/stack/src/plugins/comments/api/plugin.ts create mode 100644 packages/stack/src/plugins/comments/api/query-key-defs.ts create mode 100644 packages/stack/src/plugins/comments/api/serializers.ts create mode 100644 packages/stack/src/plugins/comments/client.css create mode 100644 packages/stack/src/plugins/comments/client/components/comment-count.tsx create mode 100644 packages/stack/src/plugins/comments/client/components/comment-form.tsx create mode 100644 packages/stack/src/plugins/comments/client/components/comment-thread.tsx create mode 100644 packages/stack/src/plugins/comments/client/components/index.tsx create mode 100644 packages/stack/src/plugins/comments/client/components/pages/moderation-page.internal.tsx create mode 100644 packages/stack/src/plugins/comments/client/components/pages/moderation-page.tsx create mode 100644 packages/stack/src/plugins/comments/client/components/pages/resource-comments-page.internal.tsx create mode 100644 packages/stack/src/plugins/comments/client/components/pages/resource-comments-page.tsx create mode 100644 packages/stack/src/plugins/comments/client/hooks/index.tsx create mode 100644 packages/stack/src/plugins/comments/client/hooks/use-comments.tsx create mode 100644 packages/stack/src/plugins/comments/client/index.ts create mode 100644 packages/stack/src/plugins/comments/client/overrides.ts create mode 100644 packages/stack/src/plugins/comments/client/plugin.tsx create mode 100644 packages/stack/src/plugins/comments/db.ts create mode 100644 packages/stack/src/plugins/comments/query-keys.ts create mode 100644 packages/stack/src/plugins/comments/schemas.ts create mode 100644 packages/stack/src/plugins/comments/style.css create mode 100644 packages/stack/src/plugins/comments/types.ts create mode 100644 packages/ui/src/components/when-visible.tsx diff --git a/docs/content/docs/cli.mdx b/docs/content/docs/cli.mdx index fafb0aff..244fb555 100644 --- a/docs/content/docs/cli.mdx +++ b/docs/content/docs/cli.mdx @@ -124,3 +124,9 @@ Because the CLI executes your config file to extract the `dbSchema`, there are a ```bash SOME_VAR=value npx @btst/cli generate --config=lib/stack.ts --orm=prisma --output=schema.prisma ``` + +or using dotenv-cli: + +```bash +npx dotenv-cli -e .env.local -- npx @btst/cli generate --orm drizzle --config lib/stack.ts --output db/btst-schema.ts +``` \ No newline at end of file diff --git a/docs/content/docs/meta.json b/docs/content/docs/meta.json index dddd0005..02f60719 100644 --- a/docs/content/docs/meta.json +++ b/docs/content/docs/meta.json @@ -17,6 +17,7 @@ "plugins/form-builder", "plugins/ui-builder", "plugins/kanban", + "plugins/comments", "plugins/open-api", "plugins/route-docs", "plugins/better-auth-ui", diff --git a/docs/content/docs/plugins/blog.mdx b/docs/content/docs/plugins/blog.mdx index aa8894b0..12dd6cdf 100644 --- a/docs/content/docs/plugins/blog.mdx +++ b/docs/content/docs/plugins/blog.mdx @@ -509,6 +509,32 @@ overrides={{ }} ``` +**Slot overrides:** + +| Override | Type | Description | +|----------|------|-------------| +| `postBottomSlot` | `(post: SerializedPost) => ReactNode` | Render additional content below each blog post — use to embed a `CommentThread` | + +```tsx +import { CommentThread } from "@btst/stack/plugins/comments/client/components" + +overrides={{ + blog: { + // ... + postBottomSlot: (post) => ( + + ), + } +}} +``` + ## React Data Hooks and Types You can import the hooks from `"@btst/stack/plugins/blog/client/hooks"` to use in your components. diff --git a/docs/content/docs/plugins/comments.mdx b/docs/content/docs/plugins/comments.mdx new file mode 100644 index 00000000..934a4b1e --- /dev/null +++ b/docs/content/docs/plugins/comments.mdx @@ -0,0 +1,423 @@ +--- +title: Comments Plugin +description: Threaded comments with moderation, likes, replies, and embeddable CommentThread component +--- + +import { Tabs, Tab } from "fumadocs-ui/components/tabs"; +import { Callout } from "fumadocs-ui/components/callout"; + +The Comments plugin adds threaded commenting to any resource in your application — blog posts, Kanban tasks, CMS content, or your own custom pages. Comments are displayed with the embeddable `CommentThread` component and managed via a built-in moderation dashboard. + +**Key Features:** +- **Threaded replies** — Top-level comments and nested replies +- **Like system** — One like per user, optimistic UI updates, denormalized counter +- **Edit support** — Authors can edit their own comments; an "edited" timestamp is shown +- **Moderation dashboard** — Tabbed view (Pending / Approved / Spam) with bulk actions +- **Server-side user resolution** — `resolveUser` hook to embed author name and avatar in API responses +- **Optimistic updates** — New comments appear instantly with a "Pending approval" badge when `autoApprove: false` +- **Scroll-into-view lazy loading** — `CommentThread` is mounted only when it scrolls into the viewport + +## Installation + + +Ensure you followed the general [framework installation guide](/installation) first. + + +### 1. Add Plugin to Backend API + +Register the comments backend plugin in your `stack.ts` file: + +```ts title="lib/stack.ts" +import { stack } from "@btst/stack" +import { commentsBackendPlugin } from "@btst/stack/plugins/comments/api" + +const { handler, dbSchema } = stack({ + basePath: "/api/data", + plugins: { + comments: commentsBackendPlugin({ + // Automatically approve comments (default: false — requires moderation) + autoApprove: false, + + // Resolve author display name and avatar from your auth system + resolveUser: async (authorId) => { + const user = await db.users.findById(authorId) + return user + ? { name: user.displayName, avatarUrl: user.avatarUrl } + : null + }, + + // Lifecycle hooks + onBeforePost: async (comment, ctx) => { + const session = await getSession(ctx.headers) + if (!session?.user) throw new Error("Authentication required") + if (comment.authorId !== session.user.id) + throw new Error("Forbidden") + }, + onAfterPost: async (comment, ctx) => { + console.log("New comment posted:", comment.id) + }, + onAfterApprove: async (comment, ctx) => { + // Send notification to comment author + await sendApprovalEmail(comment.authorId) + }, + }) + }, + adapter: (db) => createMemoryAdapter(db)({}) +}) + +export { handler, dbSchema } +``` + +### 2. Add Plugin to Client + +Register the comments client plugin in your `stack-client.tsx` file: + +```tsx title="lib/stack-client.tsx" +import { createStackClient } from "@btst/stack/client" +import { commentsClientPlugin } from "@btst/stack/plugins/comments/client" +import { QueryClient } from "@tanstack/react-query" + +const getBaseURL = () => + process.env.BASE_URL || "http://localhost:3000" + +export const getStackClient = (queryClient: QueryClient) => { + const baseURL = getBaseURL() + return createStackClient({ + plugins: { + comments: commentsClientPlugin({ + queryClient, + siteBaseURL: baseURL, + siteBasePath: "/pages", + }), + }, + queryClient, + }) +} +``` + +### 3. Add CSS Import + + + +```css title="app/globals.css" +@import "@btst/stack/plugins/comments/css"; +``` + + +```css title="app/app.css" +@import "@btst/stack/plugins/comments/css"; +``` + + +```css title="src/styles/globals.css" +@import "@btst/stack/plugins/comments/css"; +``` + + + +### 4. Configure Overrides + +Add comments overrides to your layout file. You must also register the `CommentsPluginOverrides` type: + + + +```tsx title="app/pages/layout.tsx" +import type { CommentsPluginOverrides } from "@btst/stack/plugins/comments/client" + +type PluginOverrides = { + // ... existing plugins + comments: CommentsPluginOverrides +} + +// Inside your StackProvider overrides: +overrides={{ + comments: { + apiBaseURL: baseURL, + apiBasePath: "/api/data", + + // Access control for admin routes + onBeforeModerationPageRendered: async (context) => { + const session = await getSession() + if (!session?.user?.isAdmin) throw new Error("Admin access required") + }, + } +}} +``` + + +```tsx title="app/routes/pages/_layout.tsx" +import type { CommentsPluginOverrides } from "@btst/stack/plugins/comments/client" + +type PluginOverrides = { + // ... existing plugins + comments: CommentsPluginOverrides +} + +// Inside your StackProvider overrides: +overrides={{ + comments: { + apiBaseURL: baseURL, + apiBasePath: "/api/data", + onBeforeModerationPageRendered: async (context) => { + const session = await getSession() + if (!session?.user?.isAdmin) throw new Error("Admin access required") + }, + } +}} +``` + + +```tsx title="src/routes/pages/route.tsx" +import type { CommentsPluginOverrides } from "@btst/stack/plugins/comments/client" + +type PluginOverrides = { + // ... existing plugins + comments: CommentsPluginOverrides +} + +// Inside your StackProvider overrides: +overrides={{ + comments: { + apiBaseURL: baseURL, + apiBasePath: "/api/data", + onBeforeModerationPageRendered: async (context) => { + const session = await getSession() + if (!session?.user?.isAdmin) throw new Error("Admin access required") + }, + } +}} +``` + + + +## Embedding Comments + +The `CommentThread` component can be embedded anywhere — below a blog post, inside a Kanban task dialog, or on a custom page. + +```tsx +import { CommentThread } from "@btst/stack/plugins/comments/client/components" + + +``` + +### Props + +| Prop | Type | Required | Description | +|------|------|----------|-------------| +| `resourceId` | `string` | ✓ | Identifier for the resource (e.g. post slug, task ID) | +| `resourceType` | `string` | ✓ | Type of resource (`"blog-post"`, `"kanban-task"`, etc.) | +| `apiBaseURL` | `string` | ✓ | Base URL for API requests | +| `apiBasePath` | `string` | ✓ | Path prefix where the API is mounted | +| `currentUserId` | `string` | — | Authenticated user ID — enables edit/delete/pending badge | +| `loginHref` | `string` | — | Login page URL shown to unauthenticated users | +| `components.Input` | `ComponentType` | — | Custom input component (default: `