@btst/stack 2.5.6 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/README.md +23 -0
  2. package/dist/client/components/index.d.cts +9 -9
  3. package/dist/client/components/index.d.mts +9 -9
  4. package/dist/client/components/index.d.ts +9 -9
  5. package/dist/components/auto-form/index.cjs +8 -0
  6. package/dist/components/auto-form/index.d.cts +26 -0
  7. package/dist/components/auto-form/index.d.mts +26 -0
  8. package/dist/components/auto-form/index.d.ts +26 -0
  9. package/dist/components/auto-form/index.mjs +1 -0
  10. package/dist/components/empty/index.cjs +12 -0
  11. package/dist/components/empty/index.d.cts +15 -0
  12. package/dist/components/empty/index.d.mts +15 -0
  13. package/dist/components/empty/index.d.ts +15 -0
  14. package/dist/components/empty/index.mjs +1 -0
  15. package/dist/components/form-builder/index.cjs +16 -0
  16. package/dist/components/form-builder/index.d.cts +385 -0
  17. package/dist/components/form-builder/index.d.mts +385 -0
  18. package/dist/components/form-builder/index.d.ts +385 -0
  19. package/dist/components/form-builder/index.mjs +4 -0
  20. package/dist/components/markdown/index.cjs +9 -0
  21. package/dist/components/markdown/index.d.cts +30 -0
  22. package/dist/components/markdown/index.d.mts +30 -0
  23. package/dist/components/markdown/index.d.ts +30 -0
  24. package/dist/components/markdown/index.mjs +2 -0
  25. package/dist/components/markdown/style.css +394 -0
  26. package/dist/components/minimal-tiptap/style.css +548 -0
  27. package/dist/components/multi-select/index.cjs +7 -0
  28. package/dist/components/multi-select/index.d.cts +78 -0
  29. package/dist/components/multi-select/index.d.mts +78 -0
  30. package/dist/components/multi-select/index.d.ts +78 -0
  31. package/dist/components/multi-select/index.mjs +1 -0
  32. package/dist/components/search-select/index.cjs +7 -0
  33. package/dist/components/search-select/index.d.cts +15 -0
  34. package/dist/components/search-select/index.d.mts +15 -0
  35. package/dist/components/search-select/index.d.ts +15 -0
  36. package/dist/components/search-select/index.mjs +1 -0
  37. package/dist/components/stepped-auto-form/index.cjs +7 -0
  38. package/dist/components/stepped-auto-form/index.d.cts +37 -0
  39. package/dist/components/stepped-auto-form/index.d.mts +37 -0
  40. package/dist/components/stepped-auto-form/index.d.ts +37 -0
  41. package/dist/components/stepped-auto-form/index.mjs +1 -0
  42. package/dist/components/ui-builder/style.css +552 -0
  43. package/dist/node_modules/.pnpm/@dnd-kit_core@6.3.1_react-dom@19.2.0_react@19.2.0__react@19.2.0/node_modules/@dnd-kit/core/dist/core.esm.cjs +1 -1
  44. package/dist/node_modules/.pnpm/@dnd-kit_core@6.3.1_react-dom@19.2.0_react@19.2.0__react@19.2.0/node_modules/@dnd-kit/core/dist/core.esm.mjs +1 -1
  45. package/dist/node_modules/.pnpm/@dnd-kit_sortable@10.0.0_@dnd-kit_core@6.3.1_react-dom@19.2.0_react@19.2.0__react@19.2.0__react@19.2.0/node_modules/@dnd-kit/sortable/dist/sortable.esm.mjs +2 -2
  46. package/dist/node_modules/.pnpm/@floating-ui_core@1.7.3/node_modules/@floating-ui/core/dist/floating-ui.core.mjs +1 -1
  47. package/dist/node_modules/.pnpm/@floating-ui_dom@1.7.4/node_modules/@floating-ui/dom/dist/floating-ui.dom.mjs +3 -3
  48. package/dist/node_modules/.pnpm/@floating-ui_react-dom@2.1.6_react-dom@19.2.0_react@19.2.0__react@19.2.0/node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.mjs +1 -1
  49. package/dist/packages/stack/src/plugins/ai-chat/client/components/shared/default-error.cjs +1 -1
  50. package/dist/packages/stack/src/plugins/ai-chat/client/components/shared/default-error.mjs +1 -1
  51. package/dist/packages/stack/src/plugins/ai-chat/client/plugin.cjs +44 -35
  52. package/dist/packages/stack/src/plugins/ai-chat/client/plugin.mjs +44 -35
  53. package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor-with-overrides.cjs +23 -0
  54. package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor-with-overrides.mjs +21 -0
  55. package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor.cjs +11 -12
  56. package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor.mjs +11 -12
  57. package/dist/packages/stack/src/plugins/blog/client/components/forms/post-forms.cjs +2 -2
  58. package/dist/packages/stack/src/plugins/blog/client/components/forms/post-forms.mjs +2 -2
  59. package/dist/packages/stack/src/plugins/blog/client/components/shared/default-error.cjs +1 -1
  60. package/dist/packages/stack/src/plugins/blog/client/components/shared/default-error.mjs +1 -1
  61. package/dist/packages/stack/src/plugins/blog/client/components/shared/search-input.cjs +0 -3
  62. package/dist/packages/stack/src/plugins/blog/client/components/shared/search-input.mjs +1 -1
  63. package/dist/packages/stack/src/plugins/blog/client/components/shared/search-modal.cjs +0 -3
  64. package/dist/packages/stack/src/plugins/blog/client/components/shared/search-modal.mjs +1 -1
  65. package/dist/packages/stack/src/plugins/blog/client/hooks/use-debounce.cjs +22 -0
  66. package/dist/packages/stack/src/plugins/blog/client/hooks/use-debounce.mjs +23 -2
  67. package/dist/packages/stack/src/plugins/blog/client/plugin.cjs +12 -6
  68. package/dist/packages/stack/src/plugins/blog/client/plugin.mjs +12 -6
  69. package/dist/packages/stack/src/plugins/cms/client/components/shared/default-error.cjs +1 -1
  70. package/dist/packages/stack/src/plugins/cms/client/components/shared/default-error.mjs +1 -1
  71. package/dist/packages/stack/src/plugins/cms/client/plugin.cjs +38 -26
  72. package/dist/packages/stack/src/plugins/cms/client/plugin.mjs +38 -26
  73. package/dist/packages/stack/src/plugins/form-builder/client/components/shared/default-error.cjs +1 -1
  74. package/dist/packages/stack/src/plugins/form-builder/client/components/shared/default-error.mjs +1 -1
  75. package/dist/packages/stack/src/plugins/form-builder/client/plugin.cjs +32 -20
  76. package/dist/packages/stack/src/plugins/form-builder/client/plugin.mjs +32 -20
  77. package/dist/packages/stack/src/plugins/kanban/client/components/shared/default-error.cjs +1 -1
  78. package/dist/packages/stack/src/plugins/kanban/client/components/shared/default-error.mjs +1 -1
  79. package/dist/packages/stack/src/plugins/kanban/client/plugin.cjs +6 -3
  80. package/dist/packages/stack/src/plugins/kanban/client/plugin.mjs +6 -3
  81. package/dist/packages/stack/src/plugins/ui-builder/client/components/page-renderer.cjs +1 -1
  82. package/dist/packages/stack/src/plugins/ui-builder/client/components/page-renderer.mjs +1 -1
  83. package/dist/packages/stack/src/plugins/ui-builder/client/components/shared/default-error.cjs +1 -1
  84. package/dist/packages/stack/src/plugins/ui-builder/client/components/shared/default-error.mjs +1 -1
  85. package/dist/packages/stack/src/plugins/ui-builder/client/plugin.cjs +24 -15
  86. package/dist/packages/stack/src/plugins/ui-builder/client/plugin.mjs +24 -15
  87. package/dist/packages/ui/src/components/auto-form/index.cjs +12 -2
  88. package/dist/packages/ui/src/components/auto-form/index.mjs +9 -2
  89. package/dist/packages/ui/src/components/auto-form/stepped-auto-form.cjs +2 -2
  90. package/dist/packages/ui/src/components/empty.cjs +28 -0
  91. package/dist/packages/ui/src/components/empty.mjs +27 -1
  92. package/dist/packages/ui/src/components/form-builder/components/index.mjs +2 -2
  93. package/dist/packages/ui/src/components/form-builder/edit-field-dialog.cjs +1 -1
  94. package/dist/packages/ui/src/components/form-builder/index.mjs +2 -2
  95. package/dist/packages/ui/src/components/form.mjs +1 -1
  96. package/dist/packages/ui/src/components/kanban.mjs +1 -1
  97. package/dist/packages/ui/src/components/multi-select.mjs +2 -2
  98. package/dist/packages/ui/src/components/search-select.cjs +13 -3
  99. package/dist/packages/ui/src/components/search-select.mjs +14 -4
  100. package/dist/packages/ui/src/components/ui-builder/internal/config-panel.cjs +1 -1
  101. package/dist/packages/ui/src/components/ui-builder/internal/props-panel.cjs +1 -1
  102. package/dist/plugins/ai-chat/client/index.d.cts +17 -4
  103. package/dist/plugins/ai-chat/client/index.d.mts +17 -4
  104. package/dist/plugins/ai-chat/client/index.d.ts +17 -4
  105. package/dist/plugins/blog/api/index.d.cts +2 -2
  106. package/dist/plugins/blog/api/index.d.mts +2 -2
  107. package/dist/plugins/blog/api/index.d.ts +2 -2
  108. package/dist/plugins/blog/client/hooks/index.cjs +3 -0
  109. package/dist/plugins/blog/client/hooks/index.d.cts +7 -226
  110. package/dist/plugins/blog/client/hooks/index.d.mts +7 -226
  111. package/dist/plugins/blog/client/hooks/index.d.ts +7 -226
  112. package/dist/plugins/blog/client/hooks/index.mjs +1 -0
  113. package/dist/plugins/blog/client/index.d.cts +46 -22
  114. package/dist/plugins/blog/client/index.d.mts +46 -22
  115. package/dist/plugins/blog/client/index.d.ts +46 -22
  116. package/dist/plugins/blog/query-keys.d.cts +2 -2
  117. package/dist/plugins/blog/query-keys.d.mts +2 -2
  118. package/dist/plugins/blog/query-keys.d.ts +2 -2
  119. package/dist/plugins/cms/client/index.d.cts +36 -15
  120. package/dist/plugins/cms/client/index.d.mts +36 -15
  121. package/dist/plugins/cms/client/index.d.ts +36 -15
  122. package/dist/plugins/form-builder/client/components/index.d.cts +2 -2
  123. package/dist/plugins/form-builder/client/components/index.d.mts +2 -2
  124. package/dist/plugins/form-builder/client/components/index.d.ts +2 -2
  125. package/dist/plugins/form-builder/client/index.d.cts +33 -15
  126. package/dist/plugins/form-builder/client/index.d.mts +33 -15
  127. package/dist/plugins/form-builder/client/index.d.ts +33 -15
  128. package/dist/plugins/kanban/api/index.d.cts +1 -1
  129. package/dist/plugins/kanban/api/index.d.mts +1 -1
  130. package/dist/plugins/kanban/api/index.d.ts +1 -1
  131. package/dist/plugins/kanban/client/components/index.d.cts +5 -5
  132. package/dist/plugins/kanban/client/components/index.d.mts +5 -5
  133. package/dist/plugins/kanban/client/components/index.d.ts +5 -5
  134. package/dist/plugins/kanban/client/index.d.cts +25 -10
  135. package/dist/plugins/kanban/client/index.d.mts +25 -10
  136. package/dist/plugins/kanban/client/index.d.ts +25 -10
  137. package/dist/plugins/kanban/query-keys.d.cts +1 -1
  138. package/dist/plugins/kanban/query-keys.d.mts +1 -1
  139. package/dist/plugins/kanban/query-keys.d.ts +1 -1
  140. package/dist/plugins/route-docs/client/index.d.cts +4 -4
  141. package/dist/plugins/route-docs/client/index.d.mts +4 -4
  142. package/dist/plugins/route-docs/client/index.d.ts +4 -4
  143. package/dist/plugins/ui-builder/client/components/index.d.cts +3 -3
  144. package/dist/plugins/ui-builder/client/components/index.d.mts +3 -3
  145. package/dist/plugins/ui-builder/client/components/index.d.ts +3 -3
  146. package/dist/plugins/ui-builder/client/hooks/index.d.cts +3 -3
  147. package/dist/plugins/ui-builder/client/hooks/index.d.mts +3 -3
  148. package/dist/plugins/ui-builder/client/hooks/index.d.ts +3 -3
  149. package/dist/plugins/ui-builder/client/index.d.cts +32 -18
  150. package/dist/plugins/ui-builder/client/index.d.mts +32 -18
  151. package/dist/plugins/ui-builder/client/index.d.ts +32 -18
  152. package/dist/plugins/ui-builder/index.d.cts +3 -3
  153. package/dist/plugins/ui-builder/index.d.mts +3 -3
  154. package/dist/plugins/ui-builder/index.d.ts +3 -3
  155. package/dist/shared/{stack.B2DwzF3r.d.ts → stack.ASwEoINr.d.ts} +1 -1
  156. package/dist/shared/{stack.C5ZSOJGJ.d.cts → stack.B1srlBud.d.mts} +1 -1
  157. package/dist/shared/stack.B8vT-Yt4.d.mts +228 -0
  158. package/dist/shared/stack.BAT540yW.d.ts +228 -0
  159. package/dist/shared/{stack.D0QupDcQ.d.ts → stack.BK9Z2dcL.d.ts} +1 -1
  160. package/dist/shared/stack.BwA7trxA.d.cts +228 -0
  161. package/dist/shared/{stack.Cl7ok_cY.d.cts → stack.CFECM0ew.d.cts} +1 -1
  162. package/dist/shared/stack.CZMWR72v.d.cts +10 -0
  163. package/dist/shared/stack.CZMWR72v.d.mts +10 -0
  164. package/dist/shared/stack.CZMWR72v.d.ts +10 -0
  165. package/dist/shared/{stack.VMmQdbsJ.d.mts → stack.DVtk5CNw.d.mts} +1 -1
  166. package/dist/shared/{stack.B8_74ror.d.ts → stack.DXnclTG7.d.ts} +4 -4
  167. package/dist/shared/{stack.C21-LFX8.d.cts → stack.DaZM10cp.d.cts} +4 -4
  168. package/dist/shared/{stack.Dq4qVr1F.d.mts → stack.DmpPDPxA.d.cts} +1 -1
  169. package/dist/shared/{stack.CL4mKxe7.d.mts → stack.cfCkioTe.d.mts} +4 -4
  170. package/dist/shared/stack.fdi94T4S.d.cts +291 -0
  171. package/dist/shared/stack.fdi94T4S.d.mts +291 -0
  172. package/dist/shared/stack.fdi94T4S.d.ts +291 -0
  173. package/package.json +115 -4
  174. package/src/__tests__/page-component-overrides.test.tsx +147 -0
  175. package/src/components/auto-form/index.ts +12 -0
  176. package/src/components/empty/index.ts +8 -0
  177. package/src/components/form-builder/index.ts +23 -0
  178. package/src/components/kanban/index.ts +9 -0
  179. package/src/components/markdown/index.ts +5 -0
  180. package/src/components/markdown/style.css +3 -0
  181. package/src/components/minimal-tiptap/index.ts +5 -0
  182. package/src/components/minimal-tiptap/style.css +1 -0
  183. package/src/components/multi-select/index.ts +5 -0
  184. package/src/components/search-select/index.ts +1 -0
  185. package/src/components/stepped-auto-form/index.ts +5 -0
  186. package/src/components/ui-builder/index.ts +50 -0
  187. package/src/components/ui-builder/style.css +5 -0
  188. package/src/plugins/ai-chat/client/components/shared/default-error.tsx +1 -1
  189. package/src/plugins/ai-chat/client/plugin.tsx +60 -32
  190. package/src/plugins/blog/client/components/forms/markdown-editor-with-overrides.tsx +29 -0
  191. package/src/plugins/blog/client/components/forms/markdown-editor.tsx +24 -21
  192. package/src/plugins/blog/client/components/forms/post-forms.tsx +2 -2
  193. package/src/plugins/blog/client/components/shared/default-error.tsx +2 -1
  194. package/src/plugins/blog/client/components/shared/posts-list.tsx +1 -1
  195. package/src/plugins/blog/client/components/shared/search-input.tsx +0 -2
  196. package/src/plugins/blog/client/components/shared/search-modal.tsx +0 -2
  197. package/src/plugins/blog/client/hooks/index.tsx +1 -0
  198. package/src/plugins/blog/client/plugin.tsx +41 -6
  199. package/src/plugins/cms/client/components/shared/default-error.tsx +3 -2
  200. package/src/plugins/cms/client/plugin.tsx +65 -32
  201. package/src/plugins/form-builder/client/components/shared/default-error.tsx +3 -2
  202. package/src/plugins/form-builder/client/plugin.tsx +56 -23
  203. package/src/plugins/kanban/client/components/shared/default-error.tsx +3 -2
  204. package/src/plugins/kanban/client/plugin.tsx +23 -3
  205. package/src/plugins/ui-builder/client/components/page-renderer.tsx +5 -3
  206. package/src/plugins/ui-builder/client/components/shared/default-error.tsx +3 -2
  207. package/src/plugins/ui-builder/client/plugin.tsx +41 -15
  208. package/dist/shared/stack.8nldKomx.d.cts +0 -114
  209. package/dist/shared/stack.8nldKomx.d.mts +0 -114
  210. package/dist/shared/stack.8nldKomx.d.ts +0 -114
  211. package/dist/shared/{stack.CxNeGV2z.d.mts → stack.B8D4r97Z.d.mts} +6 -6
  212. package/dist/shared/{stack.BWp0hcm9.d.cts → stack.BQmuNl5p.d.cts} +3 -3
  213. package/dist/shared/{stack.BWp0hcm9.d.mts → stack.BQmuNl5p.d.mts} +3 -3
  214. package/dist/shared/{stack.BWp0hcm9.d.ts → stack.BQmuNl5p.d.ts} +3 -3
  215. package/dist/shared/{stack.BFcg0tDz.d.ts → stack.DgKOwl20.d.ts} +6 -6
  216. package/dist/shared/{stack.DSxTDZBQ.d.cts → stack.uWSqCWAb.d.cts} +6 -6
@@ -9,25 +9,24 @@ import { listener, listenerCtx } from "@milkdown/kit/plugin/listener";
9
9
  import { Slice } from "@milkdown/kit/prose/model";
10
10
  import { Selection } from "@milkdown/kit/prose/state";
11
11
  import { useLayoutEffect, useRef, useState } from "react";
12
- import { usePluginOverrides } from "@btst/stack/context";
13
- import type { BlogPluginOverrides } from "../../overrides";
14
- import { BLOG_LOCALIZATION } from "../../localization";
12
+
13
+ export interface MarkdownEditorProps {
14
+ value?: string;
15
+ onChange?: (markdown: string) => void;
16
+ className?: string;
17
+ /** Optional image upload handler. When provided, enables image upload in the editor. */
18
+ uploadImage?: (file: File) => Promise<string>;
19
+ /** Placeholder text shown when the editor is empty. */
20
+ placeholder?: string;
21
+ }
15
22
 
16
23
  export function MarkdownEditor({
17
24
  value,
18
25
  onChange,
19
26
  className,
20
- }: {
21
- value?: string;
22
- onChange?: (markdown: string) => void;
23
- className?: string;
24
- }) {
25
- const { uploadImage, localization } = usePluginOverrides<
26
- BlogPluginOverrides,
27
- Partial<BlogPluginOverrides>
28
- >("blog", {
29
- localization: BLOG_LOCALIZATION,
30
- });
27
+ uploadImage,
28
+ placeholder = "Write something...",
29
+ }: MarkdownEditorProps) {
31
30
  const containerRef = useRef<HTMLDivElement | null>(null);
32
31
  const crepeRef = useRef<Crepe | null>(null);
33
32
  const isReadyRef = useRef(false);
@@ -52,14 +51,18 @@ export function MarkdownEditor({
52
51
  defaultValue: initialValueRef.current,
53
52
  featureConfigs: {
54
53
  [CrepeFeature.Placeholder]: {
55
- text: localization.BLOG_FORMS_EDITOR_PLACEHOLDER,
56
- },
57
- [CrepeFeature.ImageBlock]: {
58
- onUpload: async (file) => {
59
- const url = await uploadImage(file);
60
- return url;
61
- },
54
+ text: placeholder,
62
55
  },
56
+ ...(uploadImage
57
+ ? {
58
+ [CrepeFeature.ImageBlock]: {
59
+ onUpload: async (file: File) => {
60
+ const url = await uploadImage(file);
61
+ return url;
62
+ },
63
+ },
64
+ }
65
+ : {}),
63
66
  },
64
67
  });
65
68
 
@@ -52,8 +52,8 @@ import { z } from "zod";
52
52
  import { FeaturedImageField } from "./image-field";
53
53
 
54
54
  const MarkdownEditor = lazy(() =>
55
- import("./markdown-editor").then((module) => ({
56
- default: module.MarkdownEditor,
55
+ import("./markdown-editor-with-overrides").then((module) => ({
56
+ default: module.MarkdownEditorWithOverrides,
57
57
  })),
58
58
  );
59
59
  import { BLOG_LOCALIZATION } from "../../localization";
@@ -18,6 +18,7 @@ export function DefaultError({ error }: FallbackProps) {
18
18
  const message =
19
19
  process.env.NODE_ENV === "production"
20
20
  ? localization.BLOG_GENERIC_ERROR_MESSAGE
21
- : (error?.message ?? localization.BLOG_GENERIC_ERROR_MESSAGE);
21
+ : ((error instanceof Error ? error.message : undefined) ??
22
+ localization.BLOG_GENERIC_ERROR_MESSAGE);
22
23
  return <ErrorPlaceholder title={title} message={message} />;
23
24
  }
@@ -2,7 +2,7 @@ import { usePluginOverrides } from "@btst/stack/context";
2
2
  import type { SerializedPost } from "../../../types";
3
3
  import { Button } from "@workspace/ui/components/button";
4
4
  import { EmptyList } from "./empty-list";
5
- import SearchInput from "./search-input";
5
+ import { SearchInput } from "./search-input";
6
6
  import type { BlogPluginOverrides } from "../../overrides";
7
7
  import { PostCard as DefaultPostCard } from "./post-card";
8
8
  import { BLOG_LOCALIZATION } from "../../localization";
@@ -142,5 +142,3 @@ export function SearchInput({
142
142
  />
143
143
  );
144
144
  }
145
-
146
- export default SearchInput;
@@ -158,5 +158,3 @@ export function SearchModal<T extends SearchResult>({
158
158
  </>
159
159
  );
160
160
  }
161
-
162
- export default SearchModal;
@@ -1 +1,2 @@
1
1
  export * from "./blog-hooks";
2
+ export * from "./use-debounce";
@@ -5,6 +5,7 @@ import {
5
5
  runClientHookWithShim,
6
6
  } from "@btst/stack/plugins/client";
7
7
  import { createRoute } from "@btst/yar";
8
+ import type { ComponentType } from "react";
8
9
  import type { QueryClient } from "@tanstack/react-query";
9
10
  import type { BlogApiRouter } from "../api";
10
11
  import { createBlogQueryKeys } from "../query-keys";
@@ -84,6 +85,26 @@ export interface BlogClientConfig {
84
85
 
85
86
  /** Optional headers for SSR (e.g., forwarding cookies) */
86
87
  headers?: Headers;
88
+
89
+ /**
90
+ * Optional page component overrides.
91
+ * Replace any plugin page with a custom React component.
92
+ * The built-in component is used as the fallback when not provided.
93
+ */
94
+ pageComponents?: {
95
+ /** Replaces the published posts list page */
96
+ posts?: ComponentType;
97
+ /** Replaces the drafts list page */
98
+ drafts?: ComponentType;
99
+ /** Replaces the new post page */
100
+ newPost?: ComponentType;
101
+ /** Replaces the single post page */
102
+ post?: ComponentType<{ slug: string }>;
103
+ /** Replaces the edit post page */
104
+ editPost?: ComponentType<{ slug: string }>;
105
+ /** Replaces the tag posts page */
106
+ tag?: ComponentType<{ tagSlug: string }>;
107
+ };
87
108
  }
88
109
 
89
110
  /**
@@ -700,43 +721,57 @@ export const blogClientPlugin = (config: BlogClientConfig) =>
700
721
 
701
722
  routes: () => ({
702
723
  posts: createRoute("/blog", () => {
724
+ const CustomPosts = config.pageComponents?.posts;
703
725
  return {
704
- PageComponent: () => <HomePageComponent published={true} />,
726
+ PageComponent:
727
+ CustomPosts ?? (() => <HomePageComponent published={true} />),
705
728
  loader: createPostsLoader(true, config),
706
729
  meta: createPostsListMeta(true, config),
707
730
  };
708
731
  }),
709
732
  drafts: createRoute("/blog/drafts", () => {
733
+ const CustomDrafts = config.pageComponents?.drafts;
710
734
  return {
711
- PageComponent: () => <HomePageComponent published={false} />,
735
+ PageComponent:
736
+ CustomDrafts ?? (() => <HomePageComponent published={false} />),
712
737
  loader: createPostsLoader(false, config),
713
738
  meta: createPostsListMeta(false, config),
714
739
  };
715
740
  }),
716
741
  newPost: createRoute("/blog/new", () => {
742
+ const CustomNewPost = config.pageComponents?.newPost;
717
743
  return {
718
- PageComponent: NewPostPageComponent,
744
+ PageComponent: CustomNewPost ?? NewPostPageComponent,
719
745
  loader: createNewPostLoader(config),
720
746
  meta: createNewPostMeta(config),
721
747
  };
722
748
  }),
723
749
  editPost: createRoute("/blog/:slug/edit", ({ params: { slug } }) => {
750
+ const CustomEditPost = config.pageComponents?.editPost;
724
751
  return {
725
- PageComponent: () => <EditPostPageComponent slug={slug} />,
752
+ PageComponent: CustomEditPost
753
+ ? () => <CustomEditPost slug={slug} />
754
+ : () => <EditPostPageComponent slug={slug} />,
726
755
  loader: createPostLoader(slug, config, `/blog/${slug}/edit`),
727
756
  meta: createEditPostMeta(slug, config),
728
757
  };
729
758
  }),
730
759
  tag: createRoute("/blog/tag/:tagSlug", ({ params: { tagSlug } }) => {
760
+ const CustomTag = config.pageComponents?.tag;
731
761
  return {
732
- PageComponent: () => <TagPageComponent tagSlug={tagSlug} />,
762
+ PageComponent: CustomTag
763
+ ? () => <CustomTag tagSlug={tagSlug} />
764
+ : () => <TagPageComponent tagSlug={tagSlug} />,
733
765
  loader: createTagLoader(tagSlug, config),
734
766
  meta: createTagMeta(tagSlug, config),
735
767
  };
736
768
  }),
737
769
  post: createRoute("/blog/:slug", ({ params: { slug } }) => {
770
+ const CustomPost = config.pageComponents?.post;
738
771
  return {
739
- PageComponent: () => <PostPageComponent slug={slug} />,
772
+ PageComponent: CustomPost
773
+ ? () => <CustomPost slug={slug} />
774
+ : () => <PostPageComponent slug={slug} />,
740
775
  loader: createPostLoader(slug, config),
741
776
  meta: createPostMeta(slug, config),
742
777
  };
@@ -4,7 +4,7 @@ import { AlertCircle } from "lucide-react";
4
4
  import { Button } from "@workspace/ui/components/button";
5
5
 
6
6
  interface DefaultErrorProps {
7
- error: Error;
7
+ error: unknown;
8
8
  resetErrorBoundary?: () => void;
9
9
  }
10
10
 
@@ -18,7 +18,8 @@ export function DefaultError({ error, resetErrorBoundary }: DefaultErrorProps) {
18
18
  Something went wrong
19
19
  </h3>
20
20
  <p className="text-sm text-muted-foreground mb-4 max-w-sm">
21
- {error.message || "An unexpected error occurred"}
21
+ {(error instanceof Error ? error.message : undefined) ||
22
+ "An unexpected error occurred"}
22
23
  </p>
23
24
  {resetErrorBoundary && (
24
25
  <Button variant="outline" onClick={resetErrorBoundary}>
@@ -6,6 +6,7 @@ import {
6
6
  runClientHookWithShim,
7
7
  } from "@btst/stack/plugins/client";
8
8
  import { createRoute } from "@btst/yar";
9
+ import type { ComponentType } from "react";
9
10
  import type { QueryClient } from "@tanstack/react-query";
10
11
  import type { CMSApiRouter } from "../api";
11
12
  import { createCMSQueryKeys } from "../query-keys";
@@ -129,6 +130,22 @@ export interface CMSClientConfig {
129
130
  headers?: Headers;
130
131
  /** Optional hooks for customizing behavior (authorization, redirects, etc.) */
131
132
  hooks?: CMSClientHooks;
133
+
134
+ /**
135
+ * Optional page component overrides.
136
+ * Replace any plugin page with a custom React component.
137
+ * The built-in component is used as the fallback when not provided.
138
+ */
139
+ pageComponents?: {
140
+ /** Replaces the CMS dashboard page */
141
+ dashboard?: ComponentType;
142
+ /** Replaces the content list page */
143
+ contentList?: ComponentType<{ typeSlug: string }>;
144
+ /** Replaces the new content editor page */
145
+ newContent?: ComponentType<{ typeSlug: string }>;
146
+ /** Replaces the edit content editor page */
147
+ editContent?: ComponentType<{ typeSlug: string; id: string }>;
148
+ };
132
149
  }
133
150
 
134
151
  /**
@@ -469,38 +486,54 @@ export const cmsClientPlugin = (config: CMSClientConfig) =>
469
486
  name: "cms",
470
487
 
471
488
  routes: () => ({
472
- dashboard: createRoute("/cms", () => ({
473
- PageComponent: () => <DashboardPageComponent />,
474
- loader: createDashboardLoader(config),
475
- meta: createDashboardMeta(),
476
- })),
477
-
478
- contentList: createRoute("/cms/:typeSlug", ({ params }) => ({
479
- PageComponent: () => (
480
- <ContentListPageComponent typeSlug={params.typeSlug} />
481
- ),
482
- loader: createContentListLoader(params.typeSlug, config),
483
- meta: createContentListMeta(params.typeSlug, config),
484
- })),
485
-
486
- newContent: createRoute("/cms/:typeSlug/new", ({ params }) => ({
487
- PageComponent: () => (
488
- <ContentEditorPageComponent typeSlug={params.typeSlug} />
489
- ),
490
- loader: createContentEditorLoader(params.typeSlug, undefined, config),
491
- meta: createContentEditorMeta(params.typeSlug, undefined, config),
492
- })),
493
-
494
- editContent: createRoute("/cms/:typeSlug/:id", ({ params }) => ({
495
- PageComponent: () => (
496
- <ContentEditorPageComponent
497
- typeSlug={params.typeSlug}
498
- id={params.id}
499
- />
500
- ),
501
- loader: createContentEditorLoader(params.typeSlug, params.id, config),
502
- meta: createContentEditorMeta(params.typeSlug, params.id, config),
503
- })),
489
+ dashboard: createRoute("/cms", () => {
490
+ const CustomDashboard = config.pageComponents?.dashboard;
491
+ return {
492
+ PageComponent: CustomDashboard ?? (() => <DashboardPageComponent />),
493
+ loader: createDashboardLoader(config),
494
+ meta: createDashboardMeta(),
495
+ };
496
+ }),
497
+
498
+ contentList: createRoute("/cms/:typeSlug", ({ params }) => {
499
+ const CustomContentList = config.pageComponents?.contentList;
500
+ return {
501
+ PageComponent: CustomContentList
502
+ ? () => <CustomContentList typeSlug={params.typeSlug} />
503
+ : () => <ContentListPageComponent typeSlug={params.typeSlug} />,
504
+ loader: createContentListLoader(params.typeSlug, config),
505
+ meta: createContentListMeta(params.typeSlug, config),
506
+ };
507
+ }),
508
+
509
+ newContent: createRoute("/cms/:typeSlug/new", ({ params }) => {
510
+ const CustomNewContent = config.pageComponents?.newContent;
511
+ return {
512
+ PageComponent: CustomNewContent
513
+ ? () => <CustomNewContent typeSlug={params.typeSlug} />
514
+ : () => <ContentEditorPageComponent typeSlug={params.typeSlug} />,
515
+ loader: createContentEditorLoader(params.typeSlug, undefined, config),
516
+ meta: createContentEditorMeta(params.typeSlug, undefined, config),
517
+ };
518
+ }),
519
+
520
+ editContent: createRoute("/cms/:typeSlug/:id", ({ params }) => {
521
+ const CustomEditContent = config.pageComponents?.editContent;
522
+ return {
523
+ PageComponent: CustomEditContent
524
+ ? () => (
525
+ <CustomEditContent typeSlug={params.typeSlug} id={params.id} />
526
+ )
527
+ : () => (
528
+ <ContentEditorPageComponent
529
+ typeSlug={params.typeSlug}
530
+ id={params.id}
531
+ />
532
+ ),
533
+ loader: createContentEditorLoader(params.typeSlug, params.id, config),
534
+ meta: createContentEditorMeta(params.typeSlug, params.id, config),
535
+ };
536
+ }),
504
537
  }),
505
538
 
506
539
  sitemap: async () => {
@@ -4,7 +4,7 @@ import { AlertCircle } from "lucide-react";
4
4
  import { Button } from "@workspace/ui/components/button";
5
5
 
6
6
  interface DefaultErrorProps {
7
- error: Error;
7
+ error: unknown;
8
8
  resetErrorBoundary?: () => void;
9
9
  }
10
10
 
@@ -18,7 +18,8 @@ export function DefaultError({ error, resetErrorBoundary }: DefaultErrorProps) {
18
18
  Something went wrong
19
19
  </h3>
20
20
  <p className="text-sm text-muted-foreground mb-4 max-w-sm">
21
- {error.message || "An unexpected error occurred"}
21
+ {(error instanceof Error ? error.message : undefined) ||
22
+ "An unexpected error occurred"}
22
23
  </p>
23
24
  {resetErrorBoundary && (
24
25
  <Button variant="outline" onClick={resetErrorBoundary}>
@@ -7,6 +7,7 @@ import {
7
7
  runClientHookWithShim,
8
8
  } from "@btst/stack/plugins/client";
9
9
  import { createRoute } from "@btst/yar";
10
+ import type { ComponentType } from "react";
10
11
  import type { QueryClient } from "@tanstack/react-query";
11
12
  import type { FormBuilderApiRouter } from "../api";
12
13
  import { createFormBuilderQueryKeys } from "../query-keys";
@@ -126,6 +127,22 @@ export interface FormBuilderClientConfig {
126
127
  headers?: Headers;
127
128
  /** Optional hooks for customizing behavior (authorization, redirects, etc.) */
128
129
  hooks?: FormBuilderClientHooks;
130
+
131
+ /**
132
+ * Optional page component overrides.
133
+ * Replace any plugin page with a custom React component.
134
+ * The built-in component is used as the fallback when not provided.
135
+ */
136
+ pageComponents?: {
137
+ /** Replaces the form list page */
138
+ formList?: ComponentType;
139
+ /** Replaces the new form page */
140
+ newForm?: ComponentType;
141
+ /** Replaces the form editor page */
142
+ editForm?: ComponentType<{ id: string }>;
143
+ /** Replaces the form submissions page */
144
+ submissions?: ComponentType<{ formId: string }>;
145
+ };
129
146
  }
130
147
 
131
148
  /**
@@ -476,29 +493,45 @@ export const formBuilderClientPlugin = (config: FormBuilderClientConfig) =>
476
493
  name: "form-builder",
477
494
 
478
495
  routes: () => ({
479
- formList: createRoute("/forms", () => ({
480
- PageComponent: () => <FormListPageComponent />,
481
- loader: createFormListLoader(config),
482
- meta: createFormListMeta(),
483
- })),
484
-
485
- newForm: createRoute("/forms/new", () => ({
486
- PageComponent: () => <FormBuilderPageComponent />,
487
- loader: createFormBuilderLoader(undefined, config),
488
- meta: createFormBuilderMeta(undefined, config),
489
- })),
490
-
491
- editForm: createRoute("/forms/:id/edit", ({ params }) => ({
492
- PageComponent: () => <FormBuilderPageComponent id={params.id} />,
493
- loader: createFormBuilderLoader(params.id, config),
494
- meta: createFormBuilderMeta(params.id, config),
495
- })),
496
-
497
- submissions: createRoute("/forms/:id/submissions", ({ params }) => ({
498
- PageComponent: () => <SubmissionsPageComponent formId={params.id} />,
499
- loader: createSubmissionsLoader(params.id, config),
500
- meta: createSubmissionsMeta(params.id, config),
501
- })),
496
+ formList: createRoute("/forms", () => {
497
+ const CustomFormList = config.pageComponents?.formList;
498
+ return {
499
+ PageComponent: CustomFormList ?? (() => <FormListPageComponent />),
500
+ loader: createFormListLoader(config),
501
+ meta: createFormListMeta(),
502
+ };
503
+ }),
504
+
505
+ newForm: createRoute("/forms/new", () => {
506
+ const CustomNewForm = config.pageComponents?.newForm;
507
+ return {
508
+ PageComponent: CustomNewForm ?? (() => <FormBuilderPageComponent />),
509
+ loader: createFormBuilderLoader(undefined, config),
510
+ meta: createFormBuilderMeta(undefined, config),
511
+ };
512
+ }),
513
+
514
+ editForm: createRoute("/forms/:id/edit", ({ params }) => {
515
+ const CustomEditForm = config.pageComponents?.editForm;
516
+ return {
517
+ PageComponent: CustomEditForm
518
+ ? () => <CustomEditForm id={params.id} />
519
+ : () => <FormBuilderPageComponent id={params.id} />,
520
+ loader: createFormBuilderLoader(params.id, config),
521
+ meta: createFormBuilderMeta(params.id, config),
522
+ };
523
+ }),
524
+
525
+ submissions: createRoute("/forms/:id/submissions", ({ params }) => {
526
+ const CustomSubmissions = config.pageComponents?.submissions;
527
+ return {
528
+ PageComponent: CustomSubmissions
529
+ ? () => <CustomSubmissions formId={params.id} />
530
+ : () => <SubmissionsPageComponent formId={params.id} />,
531
+ loader: createSubmissionsLoader(params.id, config),
532
+ meta: createSubmissionsMeta(params.id, config),
533
+ };
534
+ }),
502
535
  }),
503
536
 
504
537
  sitemap: async () => {
@@ -4,7 +4,7 @@ import { AlertCircle, RefreshCw } from "lucide-react";
4
4
  import { Button } from "@workspace/ui/components/button";
5
5
 
6
6
  interface DefaultErrorProps {
7
- error?: Error;
7
+ error?: unknown;
8
8
  reset?: () => void;
9
9
  }
10
10
 
@@ -19,7 +19,8 @@ export function DefaultError({ error, reset }: DefaultErrorProps) {
19
19
  </div>
20
20
  <h3 className="text-lg font-semibold mb-2">Something went wrong</h3>
21
21
  <p className="text-muted-foreground max-w-md mb-4">
22
- {error?.message || "An unexpected error occurred. Please try again."}
22
+ {(error instanceof Error ? error.message : undefined) ||
23
+ "An unexpected error occurred. Please try again."}
23
24
  </p>
24
25
  {reset && (
25
26
  <Button onClick={reset} variant="outline">
@@ -5,6 +5,7 @@ import {
5
5
  runClientHookWithShim,
6
6
  } from "@btst/stack/plugins/client";
7
7
  import { createRoute } from "@btst/yar";
8
+ import type { ComponentType } from "react";
8
9
  import type { QueryClient } from "@tanstack/react-query";
9
10
  import type { KanbanApiRouter } from "../api";
10
11
  import { createKanbanQueryKeys } from "../query-keys";
@@ -79,6 +80,20 @@ export interface KanbanClientConfig {
79
80
 
80
81
  /** Optional headers for SSR (e.g., forwarding cookies) */
81
82
  headers?: Headers;
83
+
84
+ /**
85
+ * Optional page component overrides.
86
+ * Replace any plugin page with a custom React component.
87
+ * The built-in component is used as the fallback when not provided.
88
+ */
89
+ pageComponents?: {
90
+ /** Replaces the boards list page */
91
+ boards?: ComponentType;
92
+ /** Replaces the new board page */
93
+ newBoard?: ComponentType;
94
+ /** Replaces the board detail page */
95
+ board?: ComponentType<{ boardId: string }>;
96
+ };
82
97
  }
83
98
 
84
99
  /**
@@ -407,22 +422,27 @@ export const kanbanClientPlugin = (config: KanbanClientConfig) =>
407
422
 
408
423
  routes: () => ({
409
424
  boards: createRoute("/kanban", () => {
425
+ const CustomBoards = config.pageComponents?.boards;
410
426
  return {
411
- PageComponent: () => <BoardsListPageComponent />,
427
+ PageComponent: CustomBoards ?? (() => <BoardsListPageComponent />),
412
428
  loader: createBoardsLoader(config),
413
429
  meta: createBoardsListMeta(config),
414
430
  };
415
431
  }),
416
432
  newBoard: createRoute("/kanban/new", () => {
433
+ const CustomNewBoard = config.pageComponents?.newBoard;
417
434
  return {
418
- PageComponent: NewBoardPageComponent,
435
+ PageComponent: CustomNewBoard ?? NewBoardPageComponent,
419
436
  loader: createNewBoardLoader(config),
420
437
  meta: createNewBoardMeta(config),
421
438
  };
422
439
  }),
423
440
  board: createRoute("/kanban/:boardId", ({ params: { boardId } }) => {
441
+ const CustomBoard = config.pageComponents?.board;
424
442
  return {
425
- PageComponent: () => <BoardPageComponent boardId={boardId} />,
443
+ PageComponent: CustomBoard
444
+ ? () => <CustomBoard boardId={boardId} />
445
+ : () => <BoardPageComponent boardId={boardId} />,
426
446
  loader: createBoardLoader(boardId, config),
427
447
  meta: createBoardMeta(boardId, config),
428
448
  };
@@ -29,13 +29,15 @@ function DefaultLoadingComponent(): ReactNode {
29
29
  /**
30
30
  * Default error component for PageRenderer
31
31
  */
32
- function DefaultErrorComponent({ error }: { error: Error }): ReactNode {
32
+ function DefaultErrorComponent({ error }: { error: unknown }): ReactNode {
33
33
  return (
34
34
  <div className="flex flex-col items-center justify-center min-h-[200px] p-4">
35
35
  <div className="text-destructive font-medium">
36
36
  {uiBuilderLocalization.pageRenderer.error}
37
37
  </div>
38
- <div className="text-sm text-muted-foreground mt-2">{error.message}</div>
38
+ <div className="text-sm text-muted-foreground mt-2">
39
+ {error instanceof Error ? error.message : String(error)}
40
+ </div>
39
41
  </div>
40
42
  );
41
43
  }
@@ -65,7 +67,7 @@ export interface PageRendererProps {
65
67
  /** Custom loading component */
66
68
  LoadingComponent?: ComponentType;
67
69
  /** Custom error component */
68
- ErrorComponent?: ComponentType<{ error: Error }>;
70
+ ErrorComponent?: ComponentType<{ error: unknown }>;
69
71
  /** Custom not found component */
70
72
  NotFoundComponent?: ComponentType;
71
73
  /** Additional className for the container */
@@ -4,7 +4,7 @@ import { AlertCircle } from "lucide-react";
4
4
  import { Button } from "@workspace/ui/components/button";
5
5
 
6
6
  interface DefaultErrorProps {
7
- error: Error;
7
+ error: unknown;
8
8
  resetErrorBoundary?: () => void;
9
9
  }
10
10
 
@@ -18,7 +18,8 @@ export function DefaultError({ error, resetErrorBoundary }: DefaultErrorProps) {
18
18
  Something went wrong
19
19
  </h3>
20
20
  <p className="text-sm text-muted-foreground mb-4 max-w-sm">
21
- {error.message || "An unexpected error occurred"}
21
+ {(error instanceof Error ? error.message : undefined) ||
22
+ "An unexpected error occurred"}
22
23
  </p>
23
24
  {resetErrorBoundary && (
24
25
  <Button variant="outline" onClick={resetErrorBoundary}>