@btst/stack 1.5.0 → 1.5.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 (53) hide show
  1. package/dist/packages/better-stack/src/plugins/blog/client/components/pages/edit-post-page.internal.cjs +7 -6
  2. package/dist/packages/better-stack/src/plugins/blog/client/components/pages/edit-post-page.internal.mjs +7 -6
  3. package/dist/packages/better-stack/src/plugins/blog/client/components/pages/home-page.internal.cjs +9 -7
  4. package/dist/packages/better-stack/src/plugins/blog/client/components/pages/home-page.internal.mjs +9 -7
  5. package/dist/packages/better-stack/src/plugins/blog/client/components/pages/new-post-page.internal.cjs +7 -6
  6. package/dist/packages/better-stack/src/plugins/blog/client/components/pages/new-post-page.internal.mjs +7 -6
  7. package/dist/packages/better-stack/src/plugins/blog/client/components/pages/post-page.internal.cjs +7 -5
  8. package/dist/packages/better-stack/src/plugins/blog/client/components/pages/post-page.internal.mjs +7 -5
  9. package/dist/packages/better-stack/src/plugins/blog/client/components/pages/tag-page.internal.cjs +5 -3
  10. package/dist/packages/better-stack/src/plugins/blog/client/components/pages/tag-page.internal.mjs +5 -3
  11. package/dist/packages/better-stack/src/plugins/blog/client/plugin.cjs +6 -6
  12. package/dist/packages/better-stack/src/plugins/blog/client/plugin.mjs +6 -6
  13. package/dist/packages/better-stack/src/plugins/cms/client/components/pages/content-editor-page.internal.cjs +19 -2
  14. package/dist/packages/better-stack/src/plugins/cms/client/components/pages/content-editor-page.internal.mjs +19 -2
  15. package/dist/packages/better-stack/src/plugins/cms/client/components/pages/content-list-page.internal.cjs +19 -6
  16. package/dist/packages/better-stack/src/plugins/cms/client/components/pages/content-list-page.internal.mjs +19 -6
  17. package/dist/packages/better-stack/src/plugins/cms/client/components/pages/dashboard-page.internal.cjs +18 -2
  18. package/dist/packages/better-stack/src/plugins/cms/client/components/pages/dashboard-page.internal.mjs +18 -2
  19. package/dist/packages/better-stack/src/plugins/cms/client/plugin.cjs +112 -22
  20. package/dist/packages/better-stack/src/plugins/cms/client/plugin.mjs +112 -22
  21. package/dist/packages/{better-stack/src/plugins/blog/client/components/shared → ui/src/hooks}/use-route-lifecycle.cjs +8 -9
  22. package/dist/packages/{better-stack/src/plugins/blog/client/components/shared → ui/src/hooks}/use-route-lifecycle.mjs +1 -2
  23. package/dist/plugins/blog/api/index.d.cts +1 -1
  24. package/dist/plugins/blog/api/index.d.mts +1 -1
  25. package/dist/plugins/blog/api/index.d.ts +1 -1
  26. package/dist/plugins/blog/client/hooks/index.d.cts +2 -2
  27. package/dist/plugins/blog/client/hooks/index.d.mts +2 -2
  28. package/dist/plugins/blog/client/hooks/index.d.ts +2 -2
  29. package/dist/plugins/blog/client/index.d.cts +1 -1
  30. package/dist/plugins/blog/client/index.d.mts +1 -1
  31. package/dist/plugins/blog/client/index.d.ts +1 -1
  32. package/dist/plugins/blog/query-keys.d.cts +2 -2
  33. package/dist/plugins/blog/query-keys.d.mts +2 -2
  34. package/dist/plugins/blog/query-keys.d.ts +2 -2
  35. package/dist/plugins/cms/client/index.d.cts +66 -1
  36. package/dist/plugins/cms/client/index.d.mts +66 -1
  37. package/dist/plugins/cms/client/index.d.ts +66 -1
  38. package/package.json +1 -1
  39. package/src/plugins/blog/client/components/pages/edit-post-page.internal.tsx +4 -3
  40. package/src/plugins/blog/client/components/pages/home-page.internal.tsx +4 -2
  41. package/src/plugins/blog/client/components/pages/new-post-page.internal.tsx +4 -3
  42. package/src/plugins/blog/client/components/pages/post-page.internal.tsx +4 -2
  43. package/src/plugins/blog/client/components/pages/tag-page.internal.tsx +4 -2
  44. package/src/plugins/blog/client/plugin.tsx +10 -9
  45. package/src/plugins/cms/client/components/pages/content-editor-page.internal.tsx +21 -3
  46. package/src/plugins/cms/client/components/pages/content-list-page.internal.tsx +21 -6
  47. package/src/plugins/cms/client/components/pages/dashboard-page.internal.tsx +20 -3
  48. package/src/plugins/cms/client/index.ts +1 -1
  49. package/src/plugins/cms/client/plugin.tsx +236 -25
  50. package/src/plugins/blog/client/components/shared/use-route-lifecycle.tsx +0 -68
  51. package/dist/shared/{stack.CcI4sYJP.d.ts → stack.DLhzx1-D.d.cts} +1 -1
  52. package/dist/shared/{stack.CcI4sYJP.d.cts → stack.DLhzx1-D.d.mts} +1 -1
  53. package/dist/shared/{stack.CcI4sYJP.d.mts → stack.DLhzx1-D.d.ts} +1 -1
@@ -12,23 +12,88 @@ import 'zod';
12
12
  * Context passed to loader hooks
13
13
  */
14
14
  interface LoaderContext {
15
+ /** Current route path */
15
16
  path: string;
17
+ /** Route parameters (e.g., { typeSlug: "product", id: "123" }) */
16
18
  params?: Record<string, string>;
19
+ /** Whether rendering on server (true) or client (false) */
17
20
  isSSR: boolean;
21
+ /** Base URL for API calls */
18
22
  apiBaseURL: string;
23
+ /** Path where the API is mounted */
19
24
  apiBasePath: string;
25
+ /** Optional headers for the request */
20
26
  headers?: Headers;
27
+ /** Additional context properties */
28
+ [key: string]: unknown;
29
+ }
30
+ /**
31
+ * Hooks for CMS client plugin
32
+ * All hooks are optional and allow consumers to customize behavior
33
+ */
34
+ interface CMSClientHooks {
35
+ /**
36
+ * Called before loading the dashboard page. Return false to cancel loading.
37
+ * @param context - Loader context with path, params, etc.
38
+ */
39
+ beforeLoadDashboard?: (context: LoaderContext) => Promise<boolean> | boolean;
40
+ /**
41
+ * Called after the dashboard is loaded.
42
+ * @param context - Loader context
43
+ */
44
+ afterLoadDashboard?: (context: LoaderContext) => Promise<void> | void;
45
+ /**
46
+ * Called before loading a content list page. Return false to cancel loading.
47
+ * @param typeSlug - The content type slug
48
+ * @param context - Loader context
49
+ */
50
+ beforeLoadContentList?: (typeSlug: string, context: LoaderContext) => Promise<boolean> | boolean;
51
+ /**
52
+ * Called after a content list is loaded.
53
+ * @param typeSlug - The content type slug
54
+ * @param context - Loader context
55
+ */
56
+ afterLoadContentList?: (typeSlug: string, context: LoaderContext) => Promise<void> | void;
57
+ /**
58
+ * Called before loading the content editor page. Return false to cancel loading.
59
+ * @param typeSlug - The content type slug
60
+ * @param id - The content item ID (undefined for new items)
61
+ * @param context - Loader context
62
+ */
63
+ beforeLoadContentEditor?: (typeSlug: string, id: string | undefined, context: LoaderContext) => Promise<boolean> | boolean;
64
+ /**
65
+ * Called after the content editor is loaded.
66
+ * @param typeSlug - The content type slug
67
+ * @param id - The content item ID (undefined for new items)
68
+ * @param context - Loader context
69
+ */
70
+ afterLoadContentEditor?: (typeSlug: string, id: string | undefined, context: LoaderContext) => Promise<void> | void;
71
+ /**
72
+ * Called when a loading error occurs.
73
+ * Use this for redirects on authorization failures.
74
+ * @param error - The error that occurred
75
+ * @param context - Loader context
76
+ */
77
+ onLoadError?: (error: Error, context: LoaderContext) => Promise<void> | void;
21
78
  }
22
79
  /**
23
80
  * Configuration for CMS client plugin
24
81
  */
25
82
  interface CMSClientConfig {
83
+ /** Base URL for API calls (e.g., "http://localhost:3000") */
26
84
  apiBaseURL: string;
85
+ /** Path where the API is mounted (e.g., "/api/data") */
27
86
  apiBasePath: string;
87
+ /** Base URL of your site */
28
88
  siteBaseURL: string;
89
+ /** Path where pages are mounted (e.g., "/pages") */
29
90
  siteBasePath: string;
91
+ /** React Query client instance for caching */
30
92
  queryClient: QueryClient;
93
+ /** Optional headers for SSR (e.g., forwarding cookies) */
31
94
  headers?: Headers;
95
+ /** Optional hooks for customizing behavior (authorization, redirects, etc.) */
96
+ hooks?: CMSClientHooks;
32
97
  }
33
98
  /**
34
99
  * CMS client plugin
@@ -341,4 +406,4 @@ interface CMSFileUploadProps extends AutoFormInputComponentProps {
341
406
  declare function CMSFileUpload({ label, isRequired, fieldConfigItem, fieldProps, field, uploadImage, }: CMSFileUploadProps): react_jsx_runtime.JSX.Element;
342
407
 
343
408
  export { AutoFormInputComponentProps, CMSFileUpload, cmsClientPlugin };
344
- export type { CMSClientConfig, CMSFileUploadProps, CMSLocalization, CMSPluginOverrides, LoaderContext, RouteContext };
409
+ export type { CMSClientConfig, CMSClientHooks, CMSFileUploadProps, CMSLocalization, CMSPluginOverrides, LoaderContext, RouteContext };
@@ -12,23 +12,88 @@ import 'zod';
12
12
  * Context passed to loader hooks
13
13
  */
14
14
  interface LoaderContext {
15
+ /** Current route path */
15
16
  path: string;
17
+ /** Route parameters (e.g., { typeSlug: "product", id: "123" }) */
16
18
  params?: Record<string, string>;
19
+ /** Whether rendering on server (true) or client (false) */
17
20
  isSSR: boolean;
21
+ /** Base URL for API calls */
18
22
  apiBaseURL: string;
23
+ /** Path where the API is mounted */
19
24
  apiBasePath: string;
25
+ /** Optional headers for the request */
20
26
  headers?: Headers;
27
+ /** Additional context properties */
28
+ [key: string]: unknown;
29
+ }
30
+ /**
31
+ * Hooks for CMS client plugin
32
+ * All hooks are optional and allow consumers to customize behavior
33
+ */
34
+ interface CMSClientHooks {
35
+ /**
36
+ * Called before loading the dashboard page. Return false to cancel loading.
37
+ * @param context - Loader context with path, params, etc.
38
+ */
39
+ beforeLoadDashboard?: (context: LoaderContext) => Promise<boolean> | boolean;
40
+ /**
41
+ * Called after the dashboard is loaded.
42
+ * @param context - Loader context
43
+ */
44
+ afterLoadDashboard?: (context: LoaderContext) => Promise<void> | void;
45
+ /**
46
+ * Called before loading a content list page. Return false to cancel loading.
47
+ * @param typeSlug - The content type slug
48
+ * @param context - Loader context
49
+ */
50
+ beforeLoadContentList?: (typeSlug: string, context: LoaderContext) => Promise<boolean> | boolean;
51
+ /**
52
+ * Called after a content list is loaded.
53
+ * @param typeSlug - The content type slug
54
+ * @param context - Loader context
55
+ */
56
+ afterLoadContentList?: (typeSlug: string, context: LoaderContext) => Promise<void> | void;
57
+ /**
58
+ * Called before loading the content editor page. Return false to cancel loading.
59
+ * @param typeSlug - The content type slug
60
+ * @param id - The content item ID (undefined for new items)
61
+ * @param context - Loader context
62
+ */
63
+ beforeLoadContentEditor?: (typeSlug: string, id: string | undefined, context: LoaderContext) => Promise<boolean> | boolean;
64
+ /**
65
+ * Called after the content editor is loaded.
66
+ * @param typeSlug - The content type slug
67
+ * @param id - The content item ID (undefined for new items)
68
+ * @param context - Loader context
69
+ */
70
+ afterLoadContentEditor?: (typeSlug: string, id: string | undefined, context: LoaderContext) => Promise<void> | void;
71
+ /**
72
+ * Called when a loading error occurs.
73
+ * Use this for redirects on authorization failures.
74
+ * @param error - The error that occurred
75
+ * @param context - Loader context
76
+ */
77
+ onLoadError?: (error: Error, context: LoaderContext) => Promise<void> | void;
21
78
  }
22
79
  /**
23
80
  * Configuration for CMS client plugin
24
81
  */
25
82
  interface CMSClientConfig {
83
+ /** Base URL for API calls (e.g., "http://localhost:3000") */
26
84
  apiBaseURL: string;
85
+ /** Path where the API is mounted (e.g., "/api/data") */
27
86
  apiBasePath: string;
87
+ /** Base URL of your site */
28
88
  siteBaseURL: string;
89
+ /** Path where pages are mounted (e.g., "/pages") */
29
90
  siteBasePath: string;
91
+ /** React Query client instance for caching */
30
92
  queryClient: QueryClient;
93
+ /** Optional headers for SSR (e.g., forwarding cookies) */
31
94
  headers?: Headers;
95
+ /** Optional hooks for customizing behavior (authorization, redirects, etc.) */
96
+ hooks?: CMSClientHooks;
32
97
  }
33
98
  /**
34
99
  * CMS client plugin
@@ -341,4 +406,4 @@ interface CMSFileUploadProps extends AutoFormInputComponentProps {
341
406
  declare function CMSFileUpload({ label, isRequired, fieldConfigItem, fieldProps, field, uploadImage, }: CMSFileUploadProps): react_jsx_runtime.JSX.Element;
342
407
 
343
408
  export { AutoFormInputComponentProps, CMSFileUpload, cmsClientPlugin };
344
- export type { CMSClientConfig, CMSFileUploadProps, CMSLocalization, CMSPluginOverrides, LoaderContext, RouteContext };
409
+ export type { CMSClientConfig, CMSClientHooks, CMSFileUploadProps, CMSLocalization, CMSPluginOverrides, LoaderContext, RouteContext };
@@ -12,23 +12,88 @@ import 'zod';
12
12
  * Context passed to loader hooks
13
13
  */
14
14
  interface LoaderContext {
15
+ /** Current route path */
15
16
  path: string;
17
+ /** Route parameters (e.g., { typeSlug: "product", id: "123" }) */
16
18
  params?: Record<string, string>;
19
+ /** Whether rendering on server (true) or client (false) */
17
20
  isSSR: boolean;
21
+ /** Base URL for API calls */
18
22
  apiBaseURL: string;
23
+ /** Path where the API is mounted */
19
24
  apiBasePath: string;
25
+ /** Optional headers for the request */
20
26
  headers?: Headers;
27
+ /** Additional context properties */
28
+ [key: string]: unknown;
29
+ }
30
+ /**
31
+ * Hooks for CMS client plugin
32
+ * All hooks are optional and allow consumers to customize behavior
33
+ */
34
+ interface CMSClientHooks {
35
+ /**
36
+ * Called before loading the dashboard page. Return false to cancel loading.
37
+ * @param context - Loader context with path, params, etc.
38
+ */
39
+ beforeLoadDashboard?: (context: LoaderContext) => Promise<boolean> | boolean;
40
+ /**
41
+ * Called after the dashboard is loaded.
42
+ * @param context - Loader context
43
+ */
44
+ afterLoadDashboard?: (context: LoaderContext) => Promise<void> | void;
45
+ /**
46
+ * Called before loading a content list page. Return false to cancel loading.
47
+ * @param typeSlug - The content type slug
48
+ * @param context - Loader context
49
+ */
50
+ beforeLoadContentList?: (typeSlug: string, context: LoaderContext) => Promise<boolean> | boolean;
51
+ /**
52
+ * Called after a content list is loaded.
53
+ * @param typeSlug - The content type slug
54
+ * @param context - Loader context
55
+ */
56
+ afterLoadContentList?: (typeSlug: string, context: LoaderContext) => Promise<void> | void;
57
+ /**
58
+ * Called before loading the content editor page. Return false to cancel loading.
59
+ * @param typeSlug - The content type slug
60
+ * @param id - The content item ID (undefined for new items)
61
+ * @param context - Loader context
62
+ */
63
+ beforeLoadContentEditor?: (typeSlug: string, id: string | undefined, context: LoaderContext) => Promise<boolean> | boolean;
64
+ /**
65
+ * Called after the content editor is loaded.
66
+ * @param typeSlug - The content type slug
67
+ * @param id - The content item ID (undefined for new items)
68
+ * @param context - Loader context
69
+ */
70
+ afterLoadContentEditor?: (typeSlug: string, id: string | undefined, context: LoaderContext) => Promise<void> | void;
71
+ /**
72
+ * Called when a loading error occurs.
73
+ * Use this for redirects on authorization failures.
74
+ * @param error - The error that occurred
75
+ * @param context - Loader context
76
+ */
77
+ onLoadError?: (error: Error, context: LoaderContext) => Promise<void> | void;
21
78
  }
22
79
  /**
23
80
  * Configuration for CMS client plugin
24
81
  */
25
82
  interface CMSClientConfig {
83
+ /** Base URL for API calls (e.g., "http://localhost:3000") */
26
84
  apiBaseURL: string;
85
+ /** Path where the API is mounted (e.g., "/api/data") */
27
86
  apiBasePath: string;
87
+ /** Base URL of your site */
28
88
  siteBaseURL: string;
89
+ /** Path where pages are mounted (e.g., "/pages") */
29
90
  siteBasePath: string;
91
+ /** React Query client instance for caching */
30
92
  queryClient: QueryClient;
93
+ /** Optional headers for SSR (e.g., forwarding cookies) */
31
94
  headers?: Headers;
95
+ /** Optional hooks for customizing behavior (authorization, redirects, etc.) */
96
+ hooks?: CMSClientHooks;
32
97
  }
33
98
  /**
34
99
  * CMS client plugin
@@ -341,4 +406,4 @@ interface CMSFileUploadProps extends AutoFormInputComponentProps {
341
406
  declare function CMSFileUpload({ label, isRequired, fieldConfigItem, fieldProps, field, uploadImage, }: CMSFileUploadProps): react_jsx_runtime.JSX.Element;
342
407
 
343
408
  export { AutoFormInputComponentProps, CMSFileUpload, cmsClientPlugin };
344
- export type { CMSClientConfig, CMSFileUploadProps, CMSLocalization, CMSPluginOverrides, LoaderContext, RouteContext };
409
+ export type { CMSClientConfig, CMSClientHooks, CMSFileUploadProps, CMSLocalization, CMSPluginOverrides, LoaderContext, RouteContext };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btst/stack",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
4
4
  "description": "A composable, plugin-based library for building full-stack applications.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -6,18 +6,18 @@ import { PageHeader } from "../shared/page-header";
6
6
  import { PageWrapper } from "../shared/page-wrapper";
7
7
  import { BLOG_LOCALIZATION } from "../../localization";
8
8
  import type { BlogPluginOverrides } from "../../overrides";
9
- import { useRouteLifecycle } from "../shared/use-route-lifecycle";
9
+ import { useRouteLifecycle } from "@workspace/ui/hooks/use-route-lifecycle";
10
10
 
11
11
  // Internal component with actual page content
12
12
  export function EditPostPage({ slug }: { slug: string }) {
13
- const { localization } = usePluginOverrides<
13
+ const overrides = usePluginOverrides<
14
14
  BlogPluginOverrides,
15
15
  Partial<BlogPluginOverrides>
16
16
  >("blog", {
17
17
  localization: BLOG_LOCALIZATION,
18
18
  });
19
+ const { localization, navigate } = overrides;
19
20
  const basePath = useBasePath();
20
- const { navigate } = usePluginOverrides<BlogPluginOverrides>("blog");
21
21
 
22
22
  // Call lifecycle hooks
23
23
  useRouteLifecycle({
@@ -27,6 +27,7 @@ export function EditPostPage({ slug }: { slug: string }) {
27
27
  params: { slug },
28
28
  isSSR: typeof window === "undefined",
29
29
  },
30
+ overrides,
30
31
  beforeRenderHook: (overrides, context) => {
31
32
  if (overrides.onBeforeEditPostPageRendered) {
32
33
  return overrides.onBeforeEditPostPageRendered(slug, context);
@@ -9,16 +9,17 @@ import { useSuspensePosts } from "../../hooks/blog-hooks";
9
9
  import { BLOG_LOCALIZATION } from "../../localization";
10
10
  import { usePluginOverrides } from "@btst/stack/context";
11
11
  import type { BlogPluginOverrides } from "../../overrides";
12
- import { useRouteLifecycle } from "../shared/use-route-lifecycle";
12
+ import { useRouteLifecycle } from "@workspace/ui/hooks/use-route-lifecycle";
13
13
 
14
14
  // Internal component with actual page content
15
15
  export function HomePage({ published }: { published: boolean }) {
16
- const { localization } = usePluginOverrides<
16
+ const overrides = usePluginOverrides<
17
17
  BlogPluginOverrides,
18
18
  Partial<BlogPluginOverrides>
19
19
  >("blog", {
20
20
  localization: BLOG_LOCALIZATION,
21
21
  });
22
+ const { localization } = overrides;
22
23
 
23
24
  // Call lifecycle hooks
24
25
  useRouteLifecycle({
@@ -28,6 +29,7 @@ export function HomePage({ published }: { published: boolean }) {
28
29
  isSSR: typeof window === "undefined",
29
30
  published,
30
31
  },
32
+ overrides,
31
33
  beforeRenderHook: (overrides, context) => {
32
34
  if (published && overrides.onBeforePostsPageRendered) {
33
35
  return overrides.onBeforePostsPageRendered(context);
@@ -6,17 +6,17 @@ import { PageHeader } from "../shared/page-header";
6
6
  import { PageWrapper } from "../shared/page-wrapper";
7
7
  import type { BlogPluginOverrides } from "../../overrides";
8
8
  import { BLOG_LOCALIZATION } from "../../localization";
9
- import { useRouteLifecycle } from "../shared/use-route-lifecycle";
9
+ import { useRouteLifecycle } from "@workspace/ui/hooks/use-route-lifecycle";
10
10
 
11
11
  // Internal component with actual page content
12
12
  export function NewPostPage() {
13
- const { localization } = usePluginOverrides<
13
+ const overrides = usePluginOverrides<
14
14
  BlogPluginOverrides,
15
15
  Partial<BlogPluginOverrides>
16
16
  >("blog", {
17
17
  localization: BLOG_LOCALIZATION,
18
18
  });
19
- const { navigate } = usePluginOverrides<BlogPluginOverrides>("blog");
19
+ const { localization, navigate } = overrides;
20
20
  const basePath = useBasePath();
21
21
 
22
22
  // Call lifecycle hooks
@@ -26,6 +26,7 @@ export function NewPostPage() {
26
26
  path: "/blog/new",
27
27
  isSSR: typeof window === "undefined",
28
28
  },
29
+ overrides,
29
30
  beforeRenderHook: (overrides, context) => {
30
31
  if (overrides.onBeforeNewPostPageRendered) {
31
32
  return overrides.onBeforeNewPostPageRendered(context);
@@ -17,19 +17,20 @@ import { BLOG_LOCALIZATION } from "../../localization";
17
17
  import { PostNavigation } from "../shared/post-navigation";
18
18
  import { RecentPostsCarousel } from "../shared/recent-posts-carousel";
19
19
  import { Badge } from "@workspace/ui/components/badge";
20
- import { useRouteLifecycle } from "../shared/use-route-lifecycle";
20
+ import { useRouteLifecycle } from "@workspace/ui/hooks/use-route-lifecycle";
21
21
  import { OnThisPage, OnThisPageSelect } from "../shared/on-this-page";
22
22
  import type { SerializedPost } from "../../../types";
23
23
 
24
24
  // Internal component with actual page content
25
25
  export function PostPage({ slug }: { slug: string }) {
26
- const { Image, localization } = usePluginOverrides<
26
+ const overrides = usePluginOverrides<
27
27
  BlogPluginOverrides,
28
28
  Partial<BlogPluginOverrides>
29
29
  >("blog", {
30
30
  Image: DefaultImage,
31
31
  localization: BLOG_LOCALIZATION,
32
32
  });
33
+ const { Image, localization } = overrides;
33
34
 
34
35
  // Call lifecycle hooks
35
36
  useRouteLifecycle({
@@ -39,6 +40,7 @@ export function PostPage({ slug }: { slug: string }) {
39
40
  params: { slug },
40
41
  isSSR: typeof window === "undefined",
41
42
  },
43
+ overrides,
42
44
  beforeRenderHook: (overrides, context) => {
43
45
  if (overrides.onBeforePostPageRendered) {
44
46
  return overrides.onBeforePostPageRendered(slug, context);
@@ -10,16 +10,17 @@ import { BLOG_LOCALIZATION } from "../../localization";
10
10
  import { usePluginOverrides } from "@btst/stack/context";
11
11
  import type { BlogPluginOverrides } from "../../overrides";
12
12
  import { useTags } from "../../hooks/blog-hooks";
13
- import { useRouteLifecycle } from "../shared/use-route-lifecycle";
13
+ import { useRouteLifecycle } from "@workspace/ui/hooks/use-route-lifecycle";
14
14
 
15
15
  // Internal component with actual page content
16
16
  export function TagPage({ tagSlug }: { tagSlug: string }) {
17
- const { localization } = usePluginOverrides<
17
+ const overrides = usePluginOverrides<
18
18
  BlogPluginOverrides,
19
19
  Partial<BlogPluginOverrides>
20
20
  >("blog", {
21
21
  localization: BLOG_LOCALIZATION,
22
22
  });
23
+ const { localization } = overrides;
23
24
 
24
25
  // Call lifecycle hooks
25
26
  useRouteLifecycle({
@@ -29,6 +30,7 @@ export function TagPage({ tagSlug }: { tagSlug: string }) {
29
30
  params: { tagSlug },
30
31
  isSSR: typeof window === "undefined",
31
32
  },
33
+ overrides,
32
34
  });
33
35
 
34
36
  const { tags } = useTags();
@@ -386,15 +386,16 @@ function createTagLoader(tagSlug: string, config: BlogClientConfig) {
386
386
  const tagsQuery = queries.tags.list();
387
387
  await queryClient.prefetchQuery(tagsQuery);
388
388
 
389
- if (hooks?.onLoadError) {
390
- const queryState = queryClient.getQueryState(listQuery.queryKey);
391
- if (queryState?.error) {
392
- const error =
393
- queryState.error instanceof Error
394
- ? queryState.error
395
- : new Error(String(queryState.error));
396
- await hooks.onLoadError(error, context);
397
- }
389
+ // Check if there was an error in either query
390
+ const listState = queryClient.getQueryState(listQuery.queryKey);
391
+ const tagsState = queryClient.getQueryState(tagsQuery.queryKey);
392
+ const queryError = listState?.error || tagsState?.error;
393
+ if (queryError && hooks?.onLoadError) {
394
+ const error =
395
+ queryError instanceof Error
396
+ ? queryError
397
+ : new Error(String(queryError));
398
+ await hooks.onLoadError(error, context);
398
399
  }
399
400
  } catch (error) {
400
401
  if (hooks?.onLoadError) {
@@ -15,6 +15,7 @@ import { EmptyState } from "../shared/empty-state";
15
15
  import { PageWrapper } from "../shared/page-wrapper";
16
16
  import { EditorSkeleton } from "../loading/editor-skeleton";
17
17
  import { CMS_LOCALIZATION } from "../../localization";
18
+ import { useRouteLifecycle } from "@workspace/ui/hooks/use-route-lifecycle";
18
19
 
19
20
  interface ContentEditorPageProps {
20
21
  typeSlug: string;
@@ -22,11 +23,28 @@ interface ContentEditorPageProps {
22
23
  }
23
24
 
24
25
  export function ContentEditorPage({ typeSlug, id }: ContentEditorPageProps) {
25
- const { navigate, localization: customLocalization } =
26
- usePluginOverrides<CMSPluginOverrides>("cms");
27
- const localization = { ...CMS_LOCALIZATION, ...customLocalization };
26
+ const overrides = usePluginOverrides<CMSPluginOverrides>("cms");
27
+ const { navigate } = overrides;
28
+ const localization = { ...CMS_LOCALIZATION, ...overrides.localization };
28
29
  const basePath = useBasePath();
29
30
 
31
+ // Call lifecycle hooks for authorization
32
+ useRouteLifecycle({
33
+ routeName: "contentEditor",
34
+ context: {
35
+ path: id ? `/cms/${typeSlug}/${id}` : `/cms/${typeSlug}/new`,
36
+ params: id ? { typeSlug, id } : { typeSlug },
37
+ isSSR: typeof window === "undefined",
38
+ },
39
+ overrides,
40
+ beforeRenderHook: (overrides, context) => {
41
+ if (overrides.onBeforeEditorRendered) {
42
+ return overrides.onBeforeEditorRendered(typeSlug, id ?? null, context);
43
+ }
44
+ return true;
45
+ },
46
+ });
47
+
30
48
  const { contentTypes } = useSuspenseContentTypes();
31
49
  const contentType = contentTypes.find((ct) => ct.slug === typeSlug);
32
50
 
@@ -20,6 +20,7 @@ import {
20
20
  import { EmptyState } from "../shared/empty-state";
21
21
  import { PageWrapper } from "../shared/page-wrapper";
22
22
  import { CMS_LOCALIZATION } from "../../localization";
23
+ import { useRouteLifecycle } from "@workspace/ui/hooks/use-route-lifecycle";
23
24
  import { toast } from "sonner";
24
25
 
25
26
  interface ContentListPageProps {
@@ -27,14 +28,28 @@ interface ContentListPageProps {
27
28
  }
28
29
 
29
30
  export function ContentListPage({ typeSlug }: ContentListPageProps) {
30
- const {
31
- navigate,
32
- Link,
33
- localization: customLocalization,
34
- } = usePluginOverrides<CMSPluginOverrides>("cms");
35
- const localization = { ...CMS_LOCALIZATION, ...customLocalization };
31
+ const overrides = usePluginOverrides<CMSPluginOverrides>("cms");
32
+ const { navigate, Link } = overrides;
33
+ const localization = { ...CMS_LOCALIZATION, ...overrides.localization };
36
34
  const basePath = useBasePath();
37
35
 
36
+ // Call lifecycle hooks for authorization
37
+ useRouteLifecycle({
38
+ routeName: "contentList",
39
+ context: {
40
+ path: `/cms/${typeSlug}`,
41
+ params: { typeSlug },
42
+ isSSR: typeof window === "undefined",
43
+ },
44
+ overrides,
45
+ beforeRenderHook: (overrides, context) => {
46
+ if (overrides.onBeforeListRendered) {
47
+ return overrides.onBeforeListRendered(typeSlug, context);
48
+ }
49
+ return true;
50
+ },
51
+ });
52
+
38
53
  const limit = 20;
39
54
 
40
55
  const { contentTypes } = useSuspenseContentTypes();
@@ -13,12 +13,29 @@ import { useSuspenseContentTypes } from "../../hooks";
13
13
  import { EmptyState } from "../shared/empty-state";
14
14
  import { PageWrapper } from "../shared/page-wrapper";
15
15
  import { CMS_LOCALIZATION } from "../../localization";
16
+ import { useRouteLifecycle } from "@workspace/ui/hooks/use-route-lifecycle";
16
17
 
17
18
  export function DashboardPage() {
18
- const { navigate, localization: customLocalization } =
19
- usePluginOverrides<CMSPluginOverrides>("cms");
20
- const localization = { ...CMS_LOCALIZATION, ...customLocalization };
19
+ const overrides = usePluginOverrides<CMSPluginOverrides>("cms");
20
+ const { navigate } = overrides;
21
+ const localization = { ...CMS_LOCALIZATION, ...overrides.localization };
21
22
  const basePath = useBasePath();
23
+
24
+ // Call lifecycle hooks for authorization
25
+ useRouteLifecycle({
26
+ routeName: "dashboard",
27
+ context: {
28
+ path: "/cms",
29
+ isSSR: typeof window === "undefined",
30
+ },
31
+ overrides,
32
+ beforeRenderHook: (overrides, context) => {
33
+ if (overrides.onBeforeDashboardRendered) {
34
+ return overrides.onBeforeDashboardRendered(context);
35
+ }
36
+ return true;
37
+ },
38
+ });
22
39
  const { contentTypes } = useSuspenseContentTypes();
23
40
 
24
41
  if (contentTypes.length === 0) {
@@ -1,5 +1,5 @@
1
1
  export { cmsClientPlugin } from "./plugin";
2
- export type { CMSClientConfig, LoaderContext } from "./plugin";
2
+ export type { CMSClientConfig, CMSClientHooks, LoaderContext } from "./plugin";
3
3
  export type { CMSPluginOverrides, RouteContext } from "./overrides";
4
4
  export type { CMSLocalization } from "./localization";
5
5