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.
- package/bin/create-nextblock.js +997 -0
- package/package.json +25 -0
- package/scripts/sync-template.js +284 -0
- package/templates/nextblock-template/.env.example +37 -0
- package/templates/nextblock-template/.swcrc +30 -0
- package/templates/nextblock-template/README.md +194 -0
- package/templates/nextblock-template/app/(auth-pages)/forgot-password/page.tsx +57 -0
- package/templates/nextblock-template/app/(auth-pages)/layout.tsx +9 -0
- package/templates/nextblock-template/app/(auth-pages)/post-sign-in/page.tsx +28 -0
- package/templates/nextblock-template/app/(auth-pages)/sign-in/page.tsx +67 -0
- package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +70 -0
- package/templates/nextblock-template/app/ToasterProvider.tsx +17 -0
- package/templates/nextblock-template/app/[slug]/PageClientContent.tsx +147 -0
- package/templates/nextblock-template/app/[slug]/page.tsx +145 -0
- package/templates/nextblock-template/app/[slug]/page.utils.ts +183 -0
- package/templates/nextblock-template/app/actions/email.ts +31 -0
- package/templates/nextblock-template/app/actions/formActions.ts +65 -0
- package/templates/nextblock-template/app/actions/languageActions.ts +130 -0
- package/templates/nextblock-template/app/actions/postActions.ts +80 -0
- package/templates/nextblock-template/app/actions.ts +146 -0
- package/templates/nextblock-template/app/api/process-image/route.ts +210 -0
- package/templates/nextblock-template/app/api/revalidate/route.ts +86 -0
- package/templates/nextblock-template/app/api/revalidate-log/route.ts +23 -0
- package/templates/nextblock-template/app/api/upload/presigned-url/route.ts +106 -0
- package/templates/nextblock-template/app/api/upload/proxy/route.ts +84 -0
- package/templates/nextblock-template/app/auth/callback/route.ts +58 -0
- package/templates/nextblock-template/app/blog/[slug]/PostClientContent.tsx +169 -0
- package/templates/nextblock-template/app/blog/[slug]/page.tsx +177 -0
- package/templates/nextblock-template/app/blog/[slug]/page.utils.ts +136 -0
- package/templates/nextblock-template/app/blog/page.tsx +77 -0
- package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +321 -0
- package/templates/nextblock-template/app/cms/blocks/actions.ts +434 -0
- package/templates/nextblock-template/app/cms/blocks/components/BackgroundSelector.tsx +348 -0
- package/templates/nextblock-template/app/cms/blocks/components/BlockEditorArea.tsx +567 -0
- package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +98 -0
- package/templates/nextblock-template/app/cms/blocks/components/BlockTypeCard.tsx +58 -0
- package/templates/nextblock-template/app/cms/blocks/components/BlockTypeSelector.tsx +62 -0
- package/templates/nextblock-template/app/cms/blocks/components/ColumnEditor.tsx +276 -0
- package/templates/nextblock-template/app/cms/blocks/components/DeleteBlockButtonClient.tsx +47 -0
- package/templates/nextblock-template/app/cms/blocks/components/EditableBlock.tsx +182 -0
- package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +120 -0
- package/templates/nextblock-template/app/cms/blocks/components/SectionConfigPanel.tsx +133 -0
- package/templates/nextblock-template/app/cms/blocks/components/SortableBlockItem.tsx +46 -0
- package/templates/nextblock-template/app/cms/blocks/editors/ButtonBlockEditor.tsx +85 -0
- package/templates/nextblock-template/app/cms/blocks/editors/FormBlockEditor.tsx +182 -0
- package/templates/nextblock-template/app/cms/blocks/editors/HeadingBlockEditor.tsx +111 -0
- package/templates/nextblock-template/app/cms/blocks/editors/ImageBlockEditor.tsx +150 -0
- package/templates/nextblock-template/app/cms/blocks/editors/PostsGridBlockEditor.tsx +79 -0
- package/templates/nextblock-template/app/cms/blocks/editors/SectionBlockEditor.tsx +337 -0
- package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +81 -0
- package/templates/nextblock-template/app/cms/blocks/editors/VideoEmbedBlockEditor.tsx +64 -0
- package/templates/nextblock-template/app/cms/components/ConfirmationModal.tsx +51 -0
- package/templates/nextblock-template/app/cms/components/ContentLanguageSwitcher.tsx +145 -0
- package/templates/nextblock-template/app/cms/components/CopyContentFromLanguage.tsx +203 -0
- package/templates/nextblock-template/app/cms/components/LanguageFilterSelect.tsx +69 -0
- package/templates/nextblock-template/app/cms/dashboard/page.tsx +247 -0
- package/templates/nextblock-template/app/cms/layout.tsx +10 -0
- package/templates/nextblock-template/app/cms/media/UploadFolderContext.tsx +22 -0
- package/templates/nextblock-template/app/cms/media/[id]/edit/page.tsx +80 -0
- package/templates/nextblock-template/app/cms/media/actions.ts +577 -0
- package/templates/nextblock-template/app/cms/media/components/DeleteMediaButtonClient.tsx +53 -0
- package/templates/nextblock-template/app/cms/media/components/FolderNavigator.tsx +273 -0
- package/templates/nextblock-template/app/cms/media/components/FolderTree.tsx +122 -0
- package/templates/nextblock-template/app/cms/media/components/MediaEditForm.tsx +157 -0
- package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +275 -0
- package/templates/nextblock-template/app/cms/media/components/MediaImage.tsx +70 -0
- package/templates/nextblock-template/app/cms/media/components/MediaPickerDialog.tsx +195 -0
- package/templates/nextblock-template/app/cms/media/components/MediaUploadForm.tsx +362 -0
- package/templates/nextblock-template/app/cms/media/page.tsx +120 -0
- package/templates/nextblock-template/app/cms/navigation/[id]/edit/page.tsx +101 -0
- package/templates/nextblock-template/app/cms/navigation/actions.ts +358 -0
- package/templates/nextblock-template/app/cms/navigation/components/DeleteNavItemButton.tsx +52 -0
- package/templates/nextblock-template/app/cms/navigation/components/NavigationItemForm.tsx +248 -0
- package/templates/nextblock-template/app/cms/navigation/components/NavigationLanguageSwitcher.tsx +132 -0
- package/templates/nextblock-template/app/cms/navigation/components/NavigationMenuDnd.tsx +701 -0
- package/templates/nextblock-template/app/cms/navigation/components/SortableNavItem.tsx +98 -0
- package/templates/nextblock-template/app/cms/navigation/new/page.tsx +26 -0
- package/templates/nextblock-template/app/cms/navigation/page.tsx +102 -0
- package/templates/nextblock-template/app/cms/navigation/utils.ts +51 -0
- package/templates/nextblock-template/app/cms/pages/[id]/edit/EditPageClient.tsx +121 -0
- package/templates/nextblock-template/app/cms/pages/[id]/edit/page.tsx +79 -0
- package/templates/nextblock-template/app/cms/pages/actions.ts +241 -0
- package/templates/nextblock-template/app/cms/pages/components/DeletePageButtonClient.tsx +47 -0
- package/templates/nextblock-template/app/cms/pages/components/PageForm.tsx +253 -0
- package/templates/nextblock-template/app/cms/pages/new/page.tsx +52 -0
- package/templates/nextblock-template/app/cms/pages/page.tsx +232 -0
- package/templates/nextblock-template/app/cms/posts/[id]/edit/page.tsx +183 -0
- package/templates/nextblock-template/app/cms/posts/actions.ts +309 -0
- package/templates/nextblock-template/app/cms/posts/components/DeletePostButtonClient.tsx +55 -0
- package/templates/nextblock-template/app/cms/posts/components/PostForm.tsx +419 -0
- package/templates/nextblock-template/app/cms/posts/new/page.tsx +21 -0
- package/templates/nextblock-template/app/cms/posts/page.tsx +192 -0
- package/templates/nextblock-template/app/cms/revisions/JsonDiffView.tsx +86 -0
- package/templates/nextblock-template/app/cms/revisions/RevisionHistoryButton.tsx +201 -0
- package/templates/nextblock-template/app/cms/revisions/actions.ts +84 -0
- package/templates/nextblock-template/app/cms/revisions/service.ts +344 -0
- package/templates/nextblock-template/app/cms/revisions/utils.ts +127 -0
- package/templates/nextblock-template/app/cms/settings/copyright/actions.ts +68 -0
- package/templates/nextblock-template/app/cms/settings/copyright/components/CopyrightForm.tsx +78 -0
- package/templates/nextblock-template/app/cms/settings/copyright/page.tsx +32 -0
- package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +117 -0
- package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +216 -0
- package/templates/nextblock-template/app/cms/settings/languages/[id]/edit/page.tsx +77 -0
- package/templates/nextblock-template/app/cms/settings/languages/actions.ts +261 -0
- package/templates/nextblock-template/app/cms/settings/languages/components/DeleteLanguageButton.tsx +76 -0
- package/templates/nextblock-template/app/cms/settings/languages/components/LanguageForm.tsx +167 -0
- package/templates/nextblock-template/app/cms/settings/languages/new/page.tsx +34 -0
- package/templates/nextblock-template/app/cms/settings/languages/page.tsx +156 -0
- package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +19 -0
- package/templates/nextblock-template/app/cms/settings/logos/actions.ts +114 -0
- package/templates/nextblock-template/app/cms/settings/logos/components/LogoForm.tsx +177 -0
- package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +11 -0
- package/templates/nextblock-template/app/cms/settings/logos/page.tsx +118 -0
- package/templates/nextblock-template/app/cms/settings/logos/types.ts +8 -0
- package/templates/nextblock-template/app/cms/users/[id]/edit/page.tsx +91 -0
- package/templates/nextblock-template/app/cms/users/actions.ts +156 -0
- package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +71 -0
- package/templates/nextblock-template/app/cms/users/components/UserForm.tsx +138 -0
- package/templates/nextblock-template/app/cms/users/page.tsx +183 -0
- package/templates/nextblock-template/app/favicon.ico +0 -0
- package/templates/nextblock-template/app/globals.css +401 -0
- package/templates/nextblock-template/app/layout.tsx +191 -0
- package/templates/nextblock-template/app/lib/sitemap-utils.ts +68 -0
- package/templates/nextblock-template/app/page.tsx +109 -0
- package/templates/nextblock-template/app/providers.tsx +43 -0
- package/templates/nextblock-template/app/robots.txt/route.ts +19 -0
- package/templates/nextblock-template/app/sitemap.xml/route.ts +63 -0
- package/templates/nextblock-template/app/unauthorized/page.tsx +27 -0
- package/templates/nextblock-template/backup/backup_2025-06-19.sql +8057 -0
- package/templates/nextblock-template/backup/backup_2025-06-20.sql +8159 -0
- package/templates/nextblock-template/backup/backup_2025-07-08.sql +8411 -0
- package/templates/nextblock-template/backup/backup_2025-07-09.sql +8442 -0
- package/templates/nextblock-template/backup/backup_2025-07-10.sql +8442 -0
- package/templates/nextblock-template/backup/backup_2025-10-01.sql +8803 -0
- package/templates/nextblock-template/backup/backup_2025-10-02.sql +9749 -0
- package/templates/nextblock-template/components/BlockRenderer.tsx +119 -0
- package/templates/nextblock-template/components/FooterNavigation.tsx +33 -0
- package/templates/nextblock-template/components/Header.tsx +42 -0
- package/templates/nextblock-template/components/HtmlScriptExecutor.tsx +47 -0
- package/templates/nextblock-template/components/LanguageSwitcher.tsx +103 -0
- package/templates/nextblock-template/components/ResponsiveNav.tsx +372 -0
- package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +17 -0
- package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +93 -0
- package/templates/nextblock-template/components/blocks/PostsGridClient.tsx +180 -0
- package/templates/nextblock-template/components/blocks/renderers/ButtonBlockRenderer.tsx +92 -0
- package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +69 -0
- package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +98 -0
- package/templates/nextblock-template/components/blocks/renderers/HeadingBlockRenderer.tsx +41 -0
- package/templates/nextblock-template/components/blocks/renderers/HeroBlockRenderer.tsx +240 -0
- package/templates/nextblock-template/components/blocks/renderers/ImageBlockRenderer.tsx +79 -0
- package/templates/nextblock-template/components/blocks/renderers/PostsGridBlockRenderer.tsx +33 -0
- package/templates/nextblock-template/components/blocks/renderers/SectionBlockRenderer.tsx +189 -0
- package/templates/nextblock-template/components/blocks/renderers/TextBlockRenderer.tsx +31 -0
- package/templates/nextblock-template/components/blocks/renderers/VideoEmbedBlockRenderer.tsx +59 -0
- package/templates/nextblock-template/components/blocks/renderers/inline/AlertWidgetRenderer.tsx +51 -0
- package/templates/nextblock-template/components/blocks/renderers/inline/CtaWidgetRenderer.tsx +40 -0
- package/templates/nextblock-template/components/blocks/types.ts +8 -0
- package/templates/nextblock-template/components/env-var-warning.tsx +33 -0
- package/templates/nextblock-template/components/form-message.tsx +26 -0
- package/templates/nextblock-template/components/header-auth.tsx +71 -0
- package/templates/nextblock-template/components/submit-button.tsx +23 -0
- package/templates/nextblock-template/components/theme-switcher.tsx +78 -0
- package/templates/nextblock-template/context/AuthContext.tsx +138 -0
- package/templates/nextblock-template/context/CurrentContentContext.tsx +42 -0
- package/templates/nextblock-template/context/LanguageContext.tsx +206 -0
- package/templates/nextblock-template/docs/cms-application-overview.md +56 -0
- package/templates/nextblock-template/docs/cms-architecture-overview.md +73 -0
- package/templates/nextblock-template/docs/files-structure.md +426 -0
- package/templates/nextblock-template/docs/tiptap-bundle-optimization-summary.md +174 -0
- package/templates/nextblock-template/eslint.config.mjs +28 -0
- package/templates/nextblock-template/index.d.ts +5 -0
- package/templates/nextblock-template/lib/blocks/README.md +670 -0
- package/templates/nextblock-template/lib/blocks/blockRegistry.ts +1001 -0
- package/templates/nextblock-template/lib/ui/ColorPicker.ts +1 -0
- package/templates/nextblock-template/lib/ui/ConfirmationDialog.ts +1 -0
- package/templates/nextblock-template/lib/ui/CustomSelectWithInput.ts +1 -0
- package/templates/nextblock-template/lib/ui/Skeleton.ts +1 -0
- package/templates/nextblock-template/lib/ui/avatar.ts +1 -0
- package/templates/nextblock-template/lib/ui/badge.ts +1 -0
- package/templates/nextblock-template/lib/ui/button.ts +1 -0
- package/templates/nextblock-template/lib/ui/card.ts +1 -0
- package/templates/nextblock-template/lib/ui/checkbox.ts +1 -0
- package/templates/nextblock-template/lib/ui/dialog.ts +1 -0
- package/templates/nextblock-template/lib/ui/dropdown-menu.ts +1 -0
- package/templates/nextblock-template/lib/ui/input.ts +1 -0
- package/templates/nextblock-template/lib/ui/label.ts +1 -0
- package/templates/nextblock-template/lib/ui/popover.ts +1 -0
- package/templates/nextblock-template/lib/ui/progress.ts +1 -0
- package/templates/nextblock-template/lib/ui/select.ts +1 -0
- package/templates/nextblock-template/lib/ui/separator.ts +1 -0
- package/templates/nextblock-template/lib/ui/table.ts +1 -0
- package/templates/nextblock-template/lib/ui/textarea.ts +1 -0
- package/templates/nextblock-template/lib/ui/tooltip.ts +1 -0
- package/templates/nextblock-template/lib/ui/ui.ts +1 -0
- package/templates/nextblock-template/middleware.ts +206 -0
- package/templates/nextblock-template/next-env.d.ts +6 -0
- package/templates/nextblock-template/next.config.js +99 -0
- package/templates/nextblock-template/package.json +52 -0
- package/templates/nextblock-template/postcss.config.js +6 -0
- package/templates/nextblock-template/project.json +7 -0
- package/templates/nextblock-template/public/.gitkeep +0 -0
- package/templates/nextblock-template/scripts/backfill-image-meta.ts +149 -0
- package/templates/nextblock-template/scripts/backup.js +53 -0
- package/templates/nextblock-template/scripts/test-bundle-optimization.js +114 -0
- package/templates/nextblock-template/tailwind.config.ts +19 -0
- 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
|
+
}
|