create-nextblock 0.0.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 (206) hide show
  1. package/bin/create-nextblock.js +997 -0
  2. package/package.json +25 -0
  3. package/scripts/sync-template.js +284 -0
  4. package/templates/nextblock-template/.env.example +37 -0
  5. package/templates/nextblock-template/.swcrc +30 -0
  6. package/templates/nextblock-template/README.md +194 -0
  7. package/templates/nextblock-template/app/(auth-pages)/forgot-password/page.tsx +57 -0
  8. package/templates/nextblock-template/app/(auth-pages)/layout.tsx +9 -0
  9. package/templates/nextblock-template/app/(auth-pages)/post-sign-in/page.tsx +28 -0
  10. package/templates/nextblock-template/app/(auth-pages)/sign-in/page.tsx +67 -0
  11. package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +70 -0
  12. package/templates/nextblock-template/app/ToasterProvider.tsx +17 -0
  13. package/templates/nextblock-template/app/[slug]/PageClientContent.tsx +147 -0
  14. package/templates/nextblock-template/app/[slug]/page.tsx +145 -0
  15. package/templates/nextblock-template/app/[slug]/page.utils.ts +183 -0
  16. package/templates/nextblock-template/app/actions/email.ts +31 -0
  17. package/templates/nextblock-template/app/actions/formActions.ts +65 -0
  18. package/templates/nextblock-template/app/actions/languageActions.ts +130 -0
  19. package/templates/nextblock-template/app/actions/postActions.ts +80 -0
  20. package/templates/nextblock-template/app/actions.ts +146 -0
  21. package/templates/nextblock-template/app/api/process-image/route.ts +210 -0
  22. package/templates/nextblock-template/app/api/revalidate/route.ts +86 -0
  23. package/templates/nextblock-template/app/api/revalidate-log/route.ts +23 -0
  24. package/templates/nextblock-template/app/api/upload/presigned-url/route.ts +106 -0
  25. package/templates/nextblock-template/app/api/upload/proxy/route.ts +84 -0
  26. package/templates/nextblock-template/app/auth/callback/route.ts +58 -0
  27. package/templates/nextblock-template/app/blog/[slug]/PostClientContent.tsx +169 -0
  28. package/templates/nextblock-template/app/blog/[slug]/page.tsx +177 -0
  29. package/templates/nextblock-template/app/blog/[slug]/page.utils.ts +136 -0
  30. package/templates/nextblock-template/app/blog/page.tsx +77 -0
  31. package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +321 -0
  32. package/templates/nextblock-template/app/cms/blocks/actions.ts +434 -0
  33. package/templates/nextblock-template/app/cms/blocks/components/BackgroundSelector.tsx +348 -0
  34. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorArea.tsx +567 -0
  35. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +98 -0
  36. package/templates/nextblock-template/app/cms/blocks/components/BlockTypeCard.tsx +58 -0
  37. package/templates/nextblock-template/app/cms/blocks/components/BlockTypeSelector.tsx +62 -0
  38. package/templates/nextblock-template/app/cms/blocks/components/ColumnEditor.tsx +276 -0
  39. package/templates/nextblock-template/app/cms/blocks/components/DeleteBlockButtonClient.tsx +47 -0
  40. package/templates/nextblock-template/app/cms/blocks/components/EditableBlock.tsx +182 -0
  41. package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +120 -0
  42. package/templates/nextblock-template/app/cms/blocks/components/SectionConfigPanel.tsx +133 -0
  43. package/templates/nextblock-template/app/cms/blocks/components/SortableBlockItem.tsx +46 -0
  44. package/templates/nextblock-template/app/cms/blocks/editors/ButtonBlockEditor.tsx +85 -0
  45. package/templates/nextblock-template/app/cms/blocks/editors/FormBlockEditor.tsx +182 -0
  46. package/templates/nextblock-template/app/cms/blocks/editors/HeadingBlockEditor.tsx +111 -0
  47. package/templates/nextblock-template/app/cms/blocks/editors/ImageBlockEditor.tsx +150 -0
  48. package/templates/nextblock-template/app/cms/blocks/editors/PostsGridBlockEditor.tsx +79 -0
  49. package/templates/nextblock-template/app/cms/blocks/editors/SectionBlockEditor.tsx +337 -0
  50. package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +81 -0
  51. package/templates/nextblock-template/app/cms/blocks/editors/VideoEmbedBlockEditor.tsx +64 -0
  52. package/templates/nextblock-template/app/cms/components/ConfirmationModal.tsx +51 -0
  53. package/templates/nextblock-template/app/cms/components/ContentLanguageSwitcher.tsx +145 -0
  54. package/templates/nextblock-template/app/cms/components/CopyContentFromLanguage.tsx +203 -0
  55. package/templates/nextblock-template/app/cms/components/LanguageFilterSelect.tsx +69 -0
  56. package/templates/nextblock-template/app/cms/dashboard/page.tsx +247 -0
  57. package/templates/nextblock-template/app/cms/layout.tsx +10 -0
  58. package/templates/nextblock-template/app/cms/media/UploadFolderContext.tsx +22 -0
  59. package/templates/nextblock-template/app/cms/media/[id]/edit/page.tsx +80 -0
  60. package/templates/nextblock-template/app/cms/media/actions.ts +577 -0
  61. package/templates/nextblock-template/app/cms/media/components/DeleteMediaButtonClient.tsx +53 -0
  62. package/templates/nextblock-template/app/cms/media/components/FolderNavigator.tsx +273 -0
  63. package/templates/nextblock-template/app/cms/media/components/FolderTree.tsx +122 -0
  64. package/templates/nextblock-template/app/cms/media/components/MediaEditForm.tsx +157 -0
  65. package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +275 -0
  66. package/templates/nextblock-template/app/cms/media/components/MediaImage.tsx +70 -0
  67. package/templates/nextblock-template/app/cms/media/components/MediaPickerDialog.tsx +195 -0
  68. package/templates/nextblock-template/app/cms/media/components/MediaUploadForm.tsx +362 -0
  69. package/templates/nextblock-template/app/cms/media/page.tsx +120 -0
  70. package/templates/nextblock-template/app/cms/navigation/[id]/edit/page.tsx +101 -0
  71. package/templates/nextblock-template/app/cms/navigation/actions.ts +358 -0
  72. package/templates/nextblock-template/app/cms/navigation/components/DeleteNavItemButton.tsx +52 -0
  73. package/templates/nextblock-template/app/cms/navigation/components/NavigationItemForm.tsx +248 -0
  74. package/templates/nextblock-template/app/cms/navigation/components/NavigationLanguageSwitcher.tsx +132 -0
  75. package/templates/nextblock-template/app/cms/navigation/components/NavigationMenuDnd.tsx +701 -0
  76. package/templates/nextblock-template/app/cms/navigation/components/SortableNavItem.tsx +98 -0
  77. package/templates/nextblock-template/app/cms/navigation/new/page.tsx +26 -0
  78. package/templates/nextblock-template/app/cms/navigation/page.tsx +102 -0
  79. package/templates/nextblock-template/app/cms/navigation/utils.ts +51 -0
  80. package/templates/nextblock-template/app/cms/pages/[id]/edit/EditPageClient.tsx +121 -0
  81. package/templates/nextblock-template/app/cms/pages/[id]/edit/page.tsx +79 -0
  82. package/templates/nextblock-template/app/cms/pages/actions.ts +241 -0
  83. package/templates/nextblock-template/app/cms/pages/components/DeletePageButtonClient.tsx +47 -0
  84. package/templates/nextblock-template/app/cms/pages/components/PageForm.tsx +253 -0
  85. package/templates/nextblock-template/app/cms/pages/new/page.tsx +52 -0
  86. package/templates/nextblock-template/app/cms/pages/page.tsx +232 -0
  87. package/templates/nextblock-template/app/cms/posts/[id]/edit/page.tsx +183 -0
  88. package/templates/nextblock-template/app/cms/posts/actions.ts +309 -0
  89. package/templates/nextblock-template/app/cms/posts/components/DeletePostButtonClient.tsx +55 -0
  90. package/templates/nextblock-template/app/cms/posts/components/PostForm.tsx +419 -0
  91. package/templates/nextblock-template/app/cms/posts/new/page.tsx +21 -0
  92. package/templates/nextblock-template/app/cms/posts/page.tsx +192 -0
  93. package/templates/nextblock-template/app/cms/revisions/JsonDiffView.tsx +86 -0
  94. package/templates/nextblock-template/app/cms/revisions/RevisionHistoryButton.tsx +201 -0
  95. package/templates/nextblock-template/app/cms/revisions/actions.ts +84 -0
  96. package/templates/nextblock-template/app/cms/revisions/service.ts +344 -0
  97. package/templates/nextblock-template/app/cms/revisions/utils.ts +127 -0
  98. package/templates/nextblock-template/app/cms/settings/copyright/actions.ts +68 -0
  99. package/templates/nextblock-template/app/cms/settings/copyright/components/CopyrightForm.tsx +78 -0
  100. package/templates/nextblock-template/app/cms/settings/copyright/page.tsx +32 -0
  101. package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +117 -0
  102. package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +216 -0
  103. package/templates/nextblock-template/app/cms/settings/languages/[id]/edit/page.tsx +77 -0
  104. package/templates/nextblock-template/app/cms/settings/languages/actions.ts +261 -0
  105. package/templates/nextblock-template/app/cms/settings/languages/components/DeleteLanguageButton.tsx +76 -0
  106. package/templates/nextblock-template/app/cms/settings/languages/components/LanguageForm.tsx +167 -0
  107. package/templates/nextblock-template/app/cms/settings/languages/new/page.tsx +34 -0
  108. package/templates/nextblock-template/app/cms/settings/languages/page.tsx +156 -0
  109. package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +19 -0
  110. package/templates/nextblock-template/app/cms/settings/logos/actions.ts +114 -0
  111. package/templates/nextblock-template/app/cms/settings/logos/components/LogoForm.tsx +177 -0
  112. package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +11 -0
  113. package/templates/nextblock-template/app/cms/settings/logos/page.tsx +118 -0
  114. package/templates/nextblock-template/app/cms/settings/logos/types.ts +8 -0
  115. package/templates/nextblock-template/app/cms/users/[id]/edit/page.tsx +91 -0
  116. package/templates/nextblock-template/app/cms/users/actions.ts +156 -0
  117. package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +71 -0
  118. package/templates/nextblock-template/app/cms/users/components/UserForm.tsx +138 -0
  119. package/templates/nextblock-template/app/cms/users/page.tsx +183 -0
  120. package/templates/nextblock-template/app/favicon.ico +0 -0
  121. package/templates/nextblock-template/app/globals.css +401 -0
  122. package/templates/nextblock-template/app/layout.tsx +191 -0
  123. package/templates/nextblock-template/app/lib/sitemap-utils.ts +68 -0
  124. package/templates/nextblock-template/app/page.tsx +109 -0
  125. package/templates/nextblock-template/app/providers.tsx +43 -0
  126. package/templates/nextblock-template/app/robots.txt/route.ts +19 -0
  127. package/templates/nextblock-template/app/sitemap.xml/route.ts +63 -0
  128. package/templates/nextblock-template/app/unauthorized/page.tsx +27 -0
  129. package/templates/nextblock-template/backup/backup_2025-06-19.sql +8057 -0
  130. package/templates/nextblock-template/backup/backup_2025-06-20.sql +8159 -0
  131. package/templates/nextblock-template/backup/backup_2025-07-08.sql +8411 -0
  132. package/templates/nextblock-template/backup/backup_2025-07-09.sql +8442 -0
  133. package/templates/nextblock-template/backup/backup_2025-07-10.sql +8442 -0
  134. package/templates/nextblock-template/backup/backup_2025-10-01.sql +8803 -0
  135. package/templates/nextblock-template/backup/backup_2025-10-02.sql +9749 -0
  136. package/templates/nextblock-template/components/BlockRenderer.tsx +119 -0
  137. package/templates/nextblock-template/components/FooterNavigation.tsx +33 -0
  138. package/templates/nextblock-template/components/Header.tsx +42 -0
  139. package/templates/nextblock-template/components/HtmlScriptExecutor.tsx +47 -0
  140. package/templates/nextblock-template/components/LanguageSwitcher.tsx +103 -0
  141. package/templates/nextblock-template/components/ResponsiveNav.tsx +372 -0
  142. package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +17 -0
  143. package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +93 -0
  144. package/templates/nextblock-template/components/blocks/PostsGridClient.tsx +180 -0
  145. package/templates/nextblock-template/components/blocks/renderers/ButtonBlockRenderer.tsx +92 -0
  146. package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +69 -0
  147. package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +98 -0
  148. package/templates/nextblock-template/components/blocks/renderers/HeadingBlockRenderer.tsx +41 -0
  149. package/templates/nextblock-template/components/blocks/renderers/HeroBlockRenderer.tsx +240 -0
  150. package/templates/nextblock-template/components/blocks/renderers/ImageBlockRenderer.tsx +79 -0
  151. package/templates/nextblock-template/components/blocks/renderers/PostsGridBlockRenderer.tsx +33 -0
  152. package/templates/nextblock-template/components/blocks/renderers/SectionBlockRenderer.tsx +189 -0
  153. package/templates/nextblock-template/components/blocks/renderers/TextBlockRenderer.tsx +31 -0
  154. package/templates/nextblock-template/components/blocks/renderers/VideoEmbedBlockRenderer.tsx +59 -0
  155. package/templates/nextblock-template/components/blocks/renderers/inline/AlertWidgetRenderer.tsx +51 -0
  156. package/templates/nextblock-template/components/blocks/renderers/inline/CtaWidgetRenderer.tsx +40 -0
  157. package/templates/nextblock-template/components/blocks/types.ts +8 -0
  158. package/templates/nextblock-template/components/env-var-warning.tsx +33 -0
  159. package/templates/nextblock-template/components/form-message.tsx +26 -0
  160. package/templates/nextblock-template/components/header-auth.tsx +71 -0
  161. package/templates/nextblock-template/components/submit-button.tsx +23 -0
  162. package/templates/nextblock-template/components/theme-switcher.tsx +78 -0
  163. package/templates/nextblock-template/context/AuthContext.tsx +138 -0
  164. package/templates/nextblock-template/context/CurrentContentContext.tsx +42 -0
  165. package/templates/nextblock-template/context/LanguageContext.tsx +206 -0
  166. package/templates/nextblock-template/docs/cms-application-overview.md +56 -0
  167. package/templates/nextblock-template/docs/cms-architecture-overview.md +73 -0
  168. package/templates/nextblock-template/docs/files-structure.md +426 -0
  169. package/templates/nextblock-template/docs/tiptap-bundle-optimization-summary.md +174 -0
  170. package/templates/nextblock-template/eslint.config.mjs +28 -0
  171. package/templates/nextblock-template/index.d.ts +5 -0
  172. package/templates/nextblock-template/lib/blocks/README.md +670 -0
  173. package/templates/nextblock-template/lib/blocks/blockRegistry.ts +1001 -0
  174. package/templates/nextblock-template/lib/ui/ColorPicker.ts +1 -0
  175. package/templates/nextblock-template/lib/ui/ConfirmationDialog.ts +1 -0
  176. package/templates/nextblock-template/lib/ui/CustomSelectWithInput.ts +1 -0
  177. package/templates/nextblock-template/lib/ui/Skeleton.ts +1 -0
  178. package/templates/nextblock-template/lib/ui/avatar.ts +1 -0
  179. package/templates/nextblock-template/lib/ui/badge.ts +1 -0
  180. package/templates/nextblock-template/lib/ui/button.ts +1 -0
  181. package/templates/nextblock-template/lib/ui/card.ts +1 -0
  182. package/templates/nextblock-template/lib/ui/checkbox.ts +1 -0
  183. package/templates/nextblock-template/lib/ui/dialog.ts +1 -0
  184. package/templates/nextblock-template/lib/ui/dropdown-menu.ts +1 -0
  185. package/templates/nextblock-template/lib/ui/input.ts +1 -0
  186. package/templates/nextblock-template/lib/ui/label.ts +1 -0
  187. package/templates/nextblock-template/lib/ui/popover.ts +1 -0
  188. package/templates/nextblock-template/lib/ui/progress.ts +1 -0
  189. package/templates/nextblock-template/lib/ui/select.ts +1 -0
  190. package/templates/nextblock-template/lib/ui/separator.ts +1 -0
  191. package/templates/nextblock-template/lib/ui/table.ts +1 -0
  192. package/templates/nextblock-template/lib/ui/textarea.ts +1 -0
  193. package/templates/nextblock-template/lib/ui/tooltip.ts +1 -0
  194. package/templates/nextblock-template/lib/ui/ui.ts +1 -0
  195. package/templates/nextblock-template/middleware.ts +206 -0
  196. package/templates/nextblock-template/next-env.d.ts +6 -0
  197. package/templates/nextblock-template/next.config.js +99 -0
  198. package/templates/nextblock-template/package.json +52 -0
  199. package/templates/nextblock-template/postcss.config.js +6 -0
  200. package/templates/nextblock-template/project.json +7 -0
  201. package/templates/nextblock-template/public/.gitkeep +0 -0
  202. package/templates/nextblock-template/scripts/backfill-image-meta.ts +149 -0
  203. package/templates/nextblock-template/scripts/backup.js +53 -0
  204. package/templates/nextblock-template/scripts/test-bundle-optimization.js +114 -0
  205. package/templates/nextblock-template/tailwind.config.ts +19 -0
  206. package/templates/nextblock-template/tsconfig.json +62 -0
@@ -0,0 +1,177 @@
1
+ // app/blog/[slug]/page.tsx
2
+ import React from 'react';
3
+ // Remove or alias the problematic import if only used by other functions:
4
+ // import { createClient } from "@nextblock-cms/db/server";
5
+ import { createClient as createSupabaseJsClient } from '@supabase/supabase-js'; // Import base client
6
+ import { notFound } from "next/navigation";
7
+ import type { Metadata } from 'next';
8
+ import PostClientContent from "./PostClientContent";
9
+
10
+ import { getPostDataBySlug } from "./page.utils";
11
+ import BlockRenderer from "../../../components/BlockRenderer";
12
+ import { getSsgSupabaseClient } from "@nextblock-cms/db"; // Correct import
13
+ import type { HeroBlockContent } from '../../../lib/blocks/blockRegistry';
14
+
15
+ export const dynamicParams = true;
16
+ export const revalidate = 3600;
17
+
18
+ interface ResolvedPostParams {
19
+ slug: string;
20
+ }
21
+
22
+ interface PostPageProps {
23
+ params: Promise<ResolvedPostParams>;
24
+ }
25
+
26
+ interface PostTranslation {
27
+ slug: string;
28
+ languages: {
29
+ code: string;
30
+ }[];
31
+ }
32
+
33
+ export async function generateStaticParams(): Promise<ResolvedPostParams[]> {
34
+ // Use a new Supabase client instance that doesn't rely on cookies
35
+ const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
36
+ const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
37
+
38
+ if (!supabaseUrl || !supabaseAnonKey) {
39
+ throw new Error('Missing Supabase environment variables');
40
+ }
41
+
42
+ const supabase = createSupabaseJsClient(supabaseUrl, supabaseAnonKey);
43
+
44
+ const { data: posts, error } = await supabase
45
+ .from("posts")
46
+ .select("slug")
47
+ .eq("status", "published")
48
+ .or(`published_at.is.null,published_at.lte.${new Date().toISOString()}`);
49
+
50
+ if (error || !posts) {
51
+ console.error("SSG (Posts): Error fetching post slugs for static params", error);
52
+ return [];
53
+ }
54
+ return posts.map((post) => ({ slug: post.slug }));
55
+ }
56
+
57
+ // Generate metadata for the specific post slug
58
+ export async function generateMetadata(
59
+ { params: paramsPromise }: PostPageProps,
60
+ ): Promise<Metadata> {
61
+ const params = await paramsPromise; // Await the promise to get the actual params
62
+ const postData = await getPostDataBySlug(params.slug);
63
+
64
+ if (!postData) {
65
+ return {
66
+ title: "Post Not Found",
67
+ description: "The post you are looking for does not exist or is not yet published.",
68
+ };
69
+ }
70
+
71
+ const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || "";
72
+ const supabase = getSsgSupabaseClient();
73
+ const { data: languages } = await supabase.from('languages').select('id, code');
74
+ const { data: postTranslations } = await supabase
75
+ .from('posts')
76
+ .select('language_id, slug')
77
+ .eq('translation_group_id', postData.translation_group_id)
78
+ .eq('status', 'published')
79
+ .or(`published_at.is.null,published_at.lte.${new Date().toISOString()}`);
80
+
81
+ const alternates: { [key: string]: string } = {};
82
+ if (languages && postTranslations) {
83
+ postTranslations.forEach(pt => {
84
+ const langInfo = languages.find(l => l.id === pt.language_id);
85
+ if (langInfo) {
86
+ alternates[langInfo.code] = `${siteUrl}/blog/${pt.slug}`;
87
+ }
88
+ });
89
+ }
90
+
91
+ return {
92
+ title: postData.meta_title || postData.title,
93
+ description: postData.meta_description || postData.excerpt || "",
94
+ openGraph: {
95
+ title: postData.meta_title || postData.title,
96
+ description: postData.meta_description || postData.excerpt || "",
97
+ type: 'article',
98
+ publishedTime: postData.published_at || postData.created_at,
99
+ url: `${siteUrl}/blog/${params.slug}`,
100
+ images: postData.feature_image_url
101
+ ? [
102
+ {
103
+ url: postData.feature_image_url,
104
+ // You can optionally add width, height, and alt here if known
105
+ // width: 1200, // Example
106
+ // height: 630, // Example
107
+ // alt: postData.meta_title || postData.title, // Example
108
+ },
109
+ ]
110
+ : undefined, // Or an empty array if you prefer: [],
111
+ },
112
+ alternates: {
113
+ canonical: `${siteUrl}/blog/${params.slug}`,
114
+ languages: Object.keys(alternates).length > 0 ? alternates : undefined,
115
+ },
116
+ };
117
+ }
118
+
119
+ // Server Component: Fetches data for the specific slug and passes to Client Component
120
+ export default async function DynamicPostPage({ params: paramsPromise }: PostPageProps) { // Destructure the promise
121
+ const params = await paramsPromise; // Await the promise
122
+ const initialPostData = await getPostDataBySlug(params.slug);
123
+
124
+ if (!initialPostData) {
125
+ notFound();
126
+ }
127
+
128
+ const translatedSlugs: { [key: string]: string } = {};
129
+ if (initialPostData.translation_group_id) {
130
+ const supabase = getSsgSupabaseClient(); // Use SSG client
131
+ const { data: translations } = await supabase
132
+ .from("posts")
133
+ .select("slug, languages!inner(code)")
134
+ .eq("translation_group_id", initialPostData.translation_group_id)
135
+ .eq("status", "published")
136
+ .or(`published_at.is.null,published_at.lte.${new Date().toISOString()}`);
137
+
138
+ if (translations) {
139
+ translations.forEach((translation: PostTranslation) => {
140
+ if (translation.languages && translation.languages.length > 0 && typeof translation.languages[0].code === 'string' && translation.slug) {
141
+ translatedSlugs[translation.languages[0].code] = translation.slug;
142
+ }
143
+ });
144
+ }
145
+ }
146
+
147
+ let lcpImageUrl: string | null = null;
148
+ const r2BaseUrl = process.env.NEXT_PUBLIC_R2_BASE_URL || "";
149
+
150
+ if (initialPostData && initialPostData.blocks && r2BaseUrl) {
151
+ const heroBlock = initialPostData.blocks.find(block => block.block_type === 'hero');
152
+ if (heroBlock) {
153
+ const heroContent = heroBlock.content as unknown as HeroBlockContent;
154
+ if (
155
+ heroContent.background &&
156
+ heroContent.background.type === "image" &&
157
+ heroContent.background.image &&
158
+ heroContent.background.image.object_key
159
+ ) {
160
+ lcpImageUrl = `${r2BaseUrl}/${heroContent.background.image.object_key}`;
161
+ }
162
+ }
163
+ }
164
+
165
+ const postBlocks = initialPostData ? <BlockRenderer blocks={initialPostData.blocks} languageId={initialPostData.language_id} /> : null;
166
+
167
+ return (
168
+ <>
169
+ {lcpImageUrl && (
170
+ <link rel="preload" as="image" href={lcpImageUrl} />
171
+ )}
172
+ <PostClientContent initialPostData={initialPostData} currentSlug={params.slug} translatedSlugs={translatedSlugs}>
173
+ {postBlocks}
174
+ </PostClientContent>
175
+ </>
176
+ );
177
+ }
@@ -0,0 +1,136 @@
1
+ // app/blog/[slug]/page.utils.ts
2
+ import { createClient } from "@nextblock-cms/db/server";
3
+ import type { Database } from "@nextblock-cms/db";
4
+
5
+ type PostType = Database['public']['Tables']['posts']['Row'];
6
+ type BlockType = Database['public']['Tables']['blocks']['Row'];
7
+
8
+ // Define a more specific type for the content of an Image Block
9
+ export type ImageBlockContent = {
10
+ media_id: string | null;
11
+ object_key?: string; // Optional because it's added later
12
+ blur_data_url?: string | null;
13
+ };
14
+
15
+ interface SectionOrHeroBlockContent {
16
+ [key: string]: unknown;
17
+ background?: {
18
+ type?: 'image' | 'color';
19
+ image?: {
20
+ media_id?: string;
21
+ object_key?: string;
22
+ blur_data_url?: string;
23
+ };
24
+ };
25
+ }
26
+ // Includes logic to fetch object_key for image blocks.
27
+ export async function getPostDataBySlug(slug: string): Promise<(PostType & { blocks: BlockType[]; language_code: string; language_id: number; translation_group_id: string; feature_image_url?: string | null; feature_image_blur_data_url?: string | null; }) | null> {
28
+ const supabase = createClient();
29
+
30
+ const { data: postData, error: postError } = await supabase
31
+ .from("posts")
32
+ .select(`
33
+ *,
34
+ languages!inner (id, code),
35
+ blocks (*),
36
+ media ( object_key, blur_data_url )
37
+ `)
38
+ .eq("slug", slug) // Find the post by its unique slug for this language
39
+ .eq("status", "published")
40
+ .or(`published_at.is.null,published_at.lte.${new Date().toISOString()}`) // Check published_at
41
+ .order('order', { foreignTable: 'blocks', ascending: true })
42
+ .maybeSingle();
43
+
44
+ if (postError || !postData) {
45
+ if(postError) console.error(`Error fetching post data for slug '${slug}':`, postError);
46
+ return null;
47
+ }
48
+
49
+ // Ensure language information is correctly extracted
50
+ const langInfo = postData.languages as unknown as { id: number; code: string };
51
+ if (!langInfo || !langInfo.id || !langInfo.code) {
52
+ console.error(`Language information missing or incomplete for post slug '${slug}'. DB response:`, postData.languages);
53
+ if (!postData.language_id) return null;
54
+ const {data: fallbackLang} = await supabase.from("languages").select("code").eq("id", postData.language_id).single();
55
+ if (!fallbackLang) return null;
56
+ Object.assign(langInfo, {id: postData.language_id, code: fallbackLang.code });
57
+ }
58
+
59
+ if (!postData.translation_group_id) {
60
+ console.error(`Post with slug '${slug}' is missing a translation_group_id.`);
61
+ return null;
62
+ }
63
+
64
+ let blocksWithMediaData: BlockType[] = postData.blocks || [];
65
+ if (blocksWithMediaData.length > 0) {
66
+ const mediaIds = blocksWithMediaData
67
+ .map(block => {
68
+ if (block.block_type === 'image') {
69
+ return (block.content as ImageBlockContent)?.media_id;
70
+ }
71
+ if (block.block_type === 'section' || block.block_type === 'hero') {
72
+ const content = block.content as SectionOrHeroBlockContent;
73
+ if (content.background?.type === 'image' && content.background?.image?.media_id) {
74
+ return content.background.image.media_id;
75
+ }
76
+ }
77
+ return null;
78
+ })
79
+ .filter((id): id is string => id !== null && typeof id === 'string');
80
+
81
+ if (mediaIds.length > 0) {
82
+ const { data: mediaItems, error: mediaError } = await supabase
83
+ .from('media')
84
+ .select('id, object_key, blur_data_url')
85
+ .in('id', mediaIds);
86
+
87
+ if (mediaError) {
88
+ console.error("SSG (Posts): Error fetching media items for blocks:", mediaError);
89
+ } else if (mediaItems) {
90
+ const mediaMap = new Map(mediaItems.map(m => [m.id, { object_key: m.object_key, blur_data_url: m.blur_data_url }]));
91
+ blocksWithMediaData = blocksWithMediaData.map(block => {
92
+ if (block.block_type === 'image') {
93
+ const content = block.content as ImageBlockContent;
94
+ if (content.media_id) {
95
+ const mediaData = mediaMap.get(content.media_id);
96
+ if (mediaData) {
97
+ return { ...block, content: { ...content, object_key: mediaData.object_key, blur_data_url: mediaData.blur_data_url } };
98
+ }
99
+ }
100
+ }
101
+ if (block.block_type === 'section' || block.block_type === 'hero') {
102
+ const content = block.content as SectionOrHeroBlockContent;
103
+ if (content.background?.type === 'image' && content.background?.image?.media_id) {
104
+ const mediaData = mediaMap.get(content.background.image.media_id);
105
+ if (mediaData) {
106
+ const newContent = {
107
+ ...content,
108
+ background: {
109
+ ...content.background,
110
+ image: {
111
+ ...content.background.image,
112
+ object_key: mediaData.object_key,
113
+ blur_data_url: mediaData.blur_data_url,
114
+ },
115
+ },
116
+ };
117
+ return { ...block, content: newContent };
118
+ }
119
+ }
120
+ }
121
+ return block;
122
+ });
123
+ }
124
+ }
125
+ }
126
+
127
+ return {
128
+ ...postData,
129
+ blocks: blocksWithMediaData,
130
+ language_code: langInfo.code,
131
+ language_id: langInfo.id,
132
+ translation_group_id: postData.translation_group_id,
133
+ feature_image_url: postData.media?.object_key ? `${process.env.NEXT_PUBLIC_R2_BASE_URL}/${postData.media.object_key}` : null,
134
+ feature_image_blur_data_url: postData.media?.blur_data_url,
135
+ } as (PostType & { blocks: BlockType[]; language_code: string; language_id: number; translation_group_id: string; feature_image_url?: string | null; feature_image_blur_data_url?: string | null; });
136
+ }
@@ -0,0 +1,77 @@
1
+ // app/blog/page.tsx
2
+ import React from 'react';
3
+ import { getSsgSupabaseClient } from "@nextblock-cms/db";
4
+ import { notFound } from "next/navigation";
5
+ import type { Metadata } from 'next';
6
+ import PageClientContent from "../[slug]/PageClientContent";
7
+ import { getPageDataBySlug } from "../[slug]/page.utils";
8
+ import BlockRenderer from "../../components/BlockRenderer";
9
+ import { getPageTranslations } from '@/app/actions/languageActions'; // Added import
10
+
11
+ export const dynamicParams = true;
12
+ export const revalidate = 3600;
13
+
14
+ export async function generateMetadata(): Promise<Metadata> {
15
+ const slug = "blog"; // Hardcoded slug
16
+ const pageData = await getPageDataBySlug(slug);
17
+
18
+ if (!pageData) {
19
+ return { title: "Blog Page Not Found" };
20
+ }
21
+
22
+ const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || "";
23
+ const supabase = getSsgSupabaseClient();
24
+ const { data: languages } = await supabase.from('languages').select('id, code');
25
+ const { data: pageTranslations } = await supabase
26
+ .from('pages')
27
+ .select('language_id, slug')
28
+ .eq('translation_group_id', pageData.translation_group_id)
29
+ .eq('status', 'published');
30
+
31
+ const alternates: { [key: string]: string } = {};
32
+ if (languages && pageTranslations) {
33
+ pageTranslations.forEach(pt => {
34
+ const langInfo = languages.find(l => l.id === pt.language_id);
35
+ if (langInfo) {
36
+ alternates[langInfo.code] = `${siteUrl}/${pt.slug}`;
37
+ }
38
+ });
39
+ }
40
+
41
+ return {
42
+ title: pageData.meta_title || pageData.title,
43
+ description: pageData.meta_description || "",
44
+ alternates: {
45
+ canonical: `${siteUrl}/${slug}`,
46
+ languages: Object.keys(alternates).length > 0 ? alternates : undefined,
47
+ },
48
+ };
49
+ }
50
+
51
+ export default async function BlogPage() {
52
+ const slug = "blog"; // Hardcoded slug
53
+ const pageData = await getPageDataBySlug(slug);
54
+
55
+ if (!pageData) {
56
+ notFound();
57
+ }
58
+
59
+ const translatedSlugs: { [key: string]: string } = {};
60
+ // Ensure pageData and translation_group_id are available before fetching translations
61
+ if (pageData && pageData.translation_group_id) {
62
+ const translations = await getPageTranslations(pageData.translation_group_id);
63
+ translations.forEach(t => {
64
+ if (t.language_code && t.slug) { // Ensure both properties exist
65
+ translatedSlugs[t.language_code] = t.slug;
66
+ }
67
+ });
68
+ }
69
+
70
+ const pageBlocks = pageData ? <BlockRenderer blocks={pageData.blocks} languageId={pageData.language_id} /> : null;
71
+
72
+ return (
73
+ <PageClientContent initialPageData={pageData} currentSlug={slug} translatedSlugs={translatedSlugs}>
74
+ {pageBlocks}
75
+ </PageClientContent>
76
+ );
77
+ }