create-nextblock 0.2.33 → 0.2.34

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 (32) hide show
  1. package/package.json +1 -1
  2. package/templates/nextblock-template/app/[slug]/PageClientContent.tsx +67 -67
  3. package/templates/nextblock-template/app/[slug]/page.tsx +4 -4
  4. package/templates/nextblock-template/app/cms/blocks/actions.ts +5 -5
  5. package/templates/nextblock-template/app/cms/blocks/components/BackgroundSelector.tsx +348 -350
  6. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorArea.tsx +13 -16
  7. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +1 -1
  8. package/templates/nextblock-template/app/cms/blocks/components/ColumnEditor.tsx +24 -42
  9. package/templates/nextblock-template/app/cms/blocks/components/EditableBlock.tsx +6 -6
  10. package/templates/nextblock-template/app/cms/blocks/editors/SectionBlockEditor.tsx +35 -56
  11. package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +81 -81
  12. package/templates/nextblock-template/app/cms/media/actions.ts +12 -12
  13. package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +3 -3
  14. package/templates/nextblock-template/app/cms/media/components/MediaUploadForm.tsx +1 -1
  15. package/templates/nextblock-template/app/cms/media/page.tsx +120 -120
  16. package/templates/nextblock-template/app/cms/revisions/JsonDiffView.tsx +86 -87
  17. package/templates/nextblock-template/app/cms/revisions/RevisionHistoryButton.tsx +10 -16
  18. package/templates/nextblock-template/app/cms/revisions/service.ts +344 -344
  19. package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +1 -1
  20. package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +0 -1
  21. package/templates/nextblock-template/app/providers.tsx +2 -2
  22. package/templates/nextblock-template/components/BlockRenderer.tsx +9 -9
  23. package/templates/nextblock-template/components/ResponsiveNav.tsx +22 -22
  24. package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -12
  25. package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +26 -26
  26. package/templates/nextblock-template/components/blocks/renderers/HeroBlockRenderer.tsx +41 -41
  27. package/templates/nextblock-template/components/blocks/renderers/ImageBlockRenderer.tsx +7 -7
  28. package/templates/nextblock-template/components/theme-switcher.tsx +78 -78
  29. package/templates/nextblock-template/eslint.config.mjs +35 -37
  30. package/templates/nextblock-template/lib/blocks/blockRegistry.ts +10 -10
  31. package/templates/nextblock-template/next-env.d.ts +6 -6
  32. package/templates/nextblock-template/package.json +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nextblock",
3
- "version": "0.2.33",
3
+ "version": "0.2.34",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -1,19 +1,19 @@
1
1
  // app/[slug]/PageClientContent.tsx
2
2
  "use client";
3
3
 
4
- import React, { useState, useEffect, useMemo } from 'react';
5
- import { useRouter } from 'next/navigation'; // For navigation on lang switch
6
- import type { Database } from "@nextblock-cms/db";
7
- import { useLanguage } from '@/context/LanguageContext';
8
- import { useCurrentContent } from '@/context/CurrentContentContext';
9
- import Link from 'next/link';
10
- import { createClient } from '@nextblock-cms/db';
11
-
12
- type PageType = Database['public']['Tables']['pages']['Row'];
13
- type BlockType = Database['public']['Tables']['blocks']['Row'];
14
-
15
- interface PageClientContentProps {
16
- initialPageData: (PageType & { blocks: BlockType[]; language_code: string; language_id: number; translation_group_id: string; }) | null;
4
+ import React, { useState, useEffect, useMemo } from 'react';
5
+ import { useRouter } from 'next/navigation'; // For navigation on lang switch
6
+ import type { Database } from "@nextblock-cms/db";
7
+ import { useLanguage } from '@/context/LanguageContext';
8
+ import { useCurrentContent } from '@/context/CurrentContentContext';
9
+ import Link from 'next/link';
10
+ import { createClient } from '@nextblock-cms/db';
11
+
12
+ type PageType = Database['public']['Tables']['pages']['Row'];
13
+ type BlockType = Database['public']['Tables']['blocks']['Row'];
14
+
15
+ interface PageClientContentProps {
16
+ initialPageData: (PageType & { blocks: BlockType[]; language_code: string; language_id: number; translation_group_id: string; }) | null;
17
17
  currentSlug: string; // The slug of the currently viewed page
18
18
  children: React.ReactNode;
19
19
  translatedSlugs?: { [key: string]: string };
@@ -43,62 +43,62 @@ interface PageClientContentProps {
43
43
  // }
44
44
 
45
45
 
46
- export default function PageClientContent({ initialPageData, currentSlug, children, translatedSlugs }: PageClientContentProps) {
47
- const { currentLocale, isLoadingLanguages } = useLanguage();
48
- const { currentContent, setCurrentContent } = useCurrentContent();
49
- const router = useRouter();
50
- // currentPageData is the data for the slug currently in the URL.
51
- // It's initially set by the server for the slug it resolved.
52
- const [currentPageData, setCurrentPageData] = useState(initialPageData);
53
- const [isLoadingTargetLang, setIsLoadingTargetLang] = useState(false);
54
- const supabase = useMemo(() => createClient(), []);
46
+ export default function PageClientContent({ initialPageData, currentSlug, children, translatedSlugs }: PageClientContentProps) {
47
+ const { currentLocale, isLoadingLanguages } = useLanguage();
48
+ const { currentContent, setCurrentContent } = useCurrentContent();
49
+ const router = useRouter();
50
+ // currentPageData is the data for the slug currently in the URL.
51
+ // It's initially set by the server for the slug it resolved.
52
+ const [currentPageData, setCurrentPageData] = useState(initialPageData);
53
+ const [isLoadingTargetLang, setIsLoadingTargetLang] = useState(false);
54
+ const supabase = useMemo(() => createClient(), []);
55
55
 
56
56
  // Memoize pageId and pageSlug
57
- const pageId = useMemo(() => currentPageData?.id, [currentPageData?.id]);
58
- const pageSlug = useMemo(() => currentPageData?.slug, [currentPageData?.id, currentLocale]); // include locale so updates propagate
59
-
60
- useEffect(() => {
61
- if (currentLocale && currentPageData && currentPageData.language_code !== currentLocale && translatedSlugs) {
62
- // Current page's language doesn't match context, try to navigate to translated version
63
- setIsLoadingTargetLang(true);
64
- const targetSlug = translatedSlugs[currentLocale];
65
-
66
- if (targetSlug && targetSlug !== currentSlug) {
67
- router.push(`/${targetSlug}`); // Navigate to the translated slug's URL
68
- } else if (targetSlug && targetSlug === currentSlug) {
69
- // Same slug across languages - refetch the page in the target language and update content
70
- (async () => {
71
- const { data, error } = await supabase
72
- .from("pages")
73
- .select("*, languages!inner(code), blocks(*)")
74
- .eq("slug", targetSlug)
75
- .eq("languages.code", currentLocale)
76
- .eq("status", "published")
77
- .order('order', { foreignTable: 'blocks', ascending: true })
78
- .maybeSingle();
79
-
80
- if (!error && data) {
81
- const langInfo = Array.isArray(data.languages) ? data.languages[0] : (data.languages as unknown as { code?: string });
82
- setCurrentPageData({
83
- ...(data as PageType),
84
- blocks: (data as any).blocks || [],
85
- language_code: langInfo?.code || currentLocale,
86
- language_id: data.language_id,
87
- translation_group_id: data.translation_group_id || currentPageData.translation_group_id,
88
- } as typeof currentPageData);
89
- } else {
90
- // fallback to refresh if fetch fails
91
- router.refresh();
92
- }
93
- setIsLoadingTargetLang(false);
94
- })();
95
- } else {
96
- console.warn(`No published translation found for group ${currentPageData.translation_group_id} in language ${currentLocale} using pre-fetched slugs.`);
97
- // Optionally, provide feedback to the user that translation is not available
98
- setIsLoadingTargetLang(false);
99
- }
100
- }
101
- }, [currentLocale, currentPageData, currentSlug, router, initialPageData, translatedSlugs]); // Rerun if initialPageData changes (e.g. after revalidation)
57
+ const pageId = useMemo(() => currentPageData?.id, [currentPageData?.id]);
58
+ const pageSlug = useMemo(() => currentPageData?.slug, [currentPageData?.id, currentLocale]); // include locale so updates propagate
59
+
60
+ useEffect(() => {
61
+ if (currentLocale && currentPageData && currentPageData.language_code !== currentLocale && translatedSlugs) {
62
+ // Current page's language doesn't match context, try to navigate to translated version
63
+ setIsLoadingTargetLang(true);
64
+ const targetSlug = translatedSlugs[currentLocale];
65
+
66
+ if (targetSlug && targetSlug !== currentSlug) {
67
+ router.push(`/${targetSlug}`); // Navigate to the translated slug's URL
68
+ } else if (targetSlug && targetSlug === currentSlug) {
69
+ // Same slug across languages - refetch the page in the target language and update content
70
+ (async () => {
71
+ const { data, error } = await supabase
72
+ .from("pages")
73
+ .select("*, languages!inner(code), blocks(*)")
74
+ .eq("slug", targetSlug)
75
+ .eq("languages.code", currentLocale)
76
+ .eq("status", "published")
77
+ .order('order', { foreignTable: 'blocks', ascending: true })
78
+ .maybeSingle();
79
+
80
+ if (!error && data) {
81
+ const langInfo = Array.isArray(data.languages) ? data.languages[0] : (data.languages as unknown as { code?: string });
82
+ setCurrentPageData({
83
+ ...(data as PageType),
84
+ blocks: (data as any).blocks || [],
85
+ language_code: langInfo?.code || currentLocale,
86
+ language_id: data.language_id,
87
+ translation_group_id: data.translation_group_id || currentPageData.translation_group_id,
88
+ } as typeof currentPageData);
89
+ } else {
90
+ // fallback to refresh if fetch fails
91
+ router.refresh();
92
+ }
93
+ setIsLoadingTargetLang(false);
94
+ })();
95
+ } else {
96
+ console.warn(`No published translation found for group ${currentPageData.translation_group_id} in language ${currentLocale} using pre-fetched slugs.`);
97
+ // Optionally, provide feedback to the user that translation is not available
98
+ setIsLoadingTargetLang(false);
99
+ }
100
+ }
101
+ }, [currentLocale, currentPageData, currentSlug, router, initialPageData, translatedSlugs]); // Rerun if initialPageData changes (e.g. after revalidation)
102
102
 
103
103
  // Update HTML lang attribute based on the *actually displayed* content's language
104
104
  useEffect(() => {
@@ -40,7 +40,7 @@ export async function generateStaticParams(): Promise<ResolvedPageParams[]> {
40
40
  console.error("SSG: Error fetching page slugs for static params:", error);
41
41
  return [];
42
42
  }
43
- return pages.map((page: { slug: string }) => ({ slug: page.slug }));
43
+ return pages.map((page) => ({ slug: page.slug }));
44
44
  }
45
45
 
46
46
  export async function generateMetadata(
@@ -87,8 +87,8 @@ export async function generateMetadata(
87
87
 
88
88
  const alternates: { [key: string]: string } = {};
89
89
  if (languages && pageTranslations) {
90
- pageTranslations.forEach((pt: { language_id: string; slug: string }) => {
91
- const langInfo = languages.find((l: { id: string; code: string }) => l.id === pt.language_id);
90
+ pageTranslations.forEach(pt => {
91
+ const langInfo = languages.find(l => l.id === pt.language_id);
92
92
  if (langInfo) {
93
93
  alternates[langInfo.code] = `${siteUrl}/${pt.slug}`;
94
94
  }
@@ -151,7 +151,7 @@ export default async function DynamicPage({ params: paramsPromise }: PageProps)
151
151
  const r2BaseUrl = process.env.NEXT_PUBLIC_R2_BASE_URL || "";
152
152
 
153
153
  if (pageData && pageData.blocks && r2BaseUrl) {
154
- const heroBlock = pageData.blocks.find((block: { block_type: string; content: unknown }) => block.block_type === 'hero');
154
+ const heroBlock = pageData.blocks.find(block => block.block_type === 'hero');
155
155
  if (heroBlock) {
156
156
  const heroContent = heroBlock.content as unknown as HeroBlockContent;
157
157
  if (
@@ -5,7 +5,7 @@ import { createClient } from "@nextblock-cms/db/server";
5
5
  import { revalidatePath } from "next/cache";
6
6
  import type { Database, Json } from "@nextblock-cms/db";
7
7
  import { getInitialContent, isValidBlockType } from "../../../lib/blocks/blockRegistry";
8
- import { getFullPageContent, getFullPostContent, type FullPageContent, type FullPostContent } from "../revisions/utils";
8
+ import { getFullPageContent, getFullPostContent } from "../revisions/utils";
9
9
  import { createPageRevision, createPostRevision } from "../revisions/service";
10
10
 
11
11
  type Block = Database['public']['Tables']['blocks']['Row'];
@@ -160,7 +160,7 @@ export async function updateBlock(blockId: number, newContent: unknown, pageId?:
160
160
  return { error: "Block not found." };
161
161
  }
162
162
 
163
- let prevContentAggregate: FullPageContent | FullPostContent | null = null;
163
+ let prevContentAggregate: Awaited<ReturnType<typeof getFullPageContent>> | Awaited<ReturnType<typeof getFullPostContent>> | null = null;
164
164
  if (existingBlock.page_id) {
165
165
  prevContentAggregate = await getFullPageContent(existingBlock.page_id);
166
166
  } else if (existingBlock.post_id) {
@@ -189,7 +189,7 @@ export async function updateBlock(blockId: number, newContent: unknown, pageId?:
189
189
  } else if (existingBlock.post_id) {
190
190
  const nextContentAggregate = await getFullPostContent(existingBlock.post_id, { overrideBlockId: blockId, overrideBlockContent: newContent });
191
191
  if (nextContentAggregate) {
192
- await createPostRevision(existingBlock.post_id, user.id, prevContentAggregate as FullPostContent, nextContentAggregate as FullPostContent);
192
+ await createPostRevision(existingBlock.post_id, user.id, prevContentAggregate as any, nextContentAggregate as any);
193
193
  }
194
194
  }
195
195
  }
@@ -250,7 +250,7 @@ export async function deleteBlock(blockId: number, pageId?: number | null, postI
250
250
  return { error: "Block not found." };
251
251
  }
252
252
 
253
- let previousAggregate: FullPageContent | FullPostContent | null = null;
253
+ let previousAggregate: Awaited<ReturnType<typeof getFullPageContent>> | Awaited<ReturnType<typeof getFullPostContent>> | null = null;
254
254
  if (existingBlock.page_id) {
255
255
  previousAggregate = await getFullPageContent(existingBlock.page_id);
256
256
  } else if (existingBlock.post_id) {
@@ -274,7 +274,7 @@ export async function deleteBlock(blockId: number, pageId?: number | null, postI
274
274
  } else if (existingBlock.post_id) {
275
275
  const nextAggregate = await getFullPostContent(existingBlock.post_id, { excludeDeletedBlockId: blockId });
276
276
  if (nextAggregate) {
277
- await createPostRevision(existingBlock.post_id, user.id, previousAggregate as FullPostContent, nextAggregate as FullPostContent);
277
+ await createPostRevision(existingBlock.post_id, user.id, previousAggregate as any, nextAggregate as any);
278
278
  }
279
279
  }
280
280
  }