create-nextblock 0.1.0 → 0.2.0

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 (42) hide show
  1. package/bin/create-nextblock.js +1193 -920
  2. package/package.json +6 -2
  3. package/scripts/sync-template.js +279 -276
  4. package/templates/nextblock-template/.env.example +1 -14
  5. package/templates/nextblock-template/README.md +1 -1
  6. package/templates/nextblock-template/app/[slug]/PageClientContent.tsx +67 -40
  7. package/templates/nextblock-template/app/[slug]/page.tsx +45 -10
  8. package/templates/nextblock-template/app/[slug]/page.utils.ts +92 -45
  9. package/templates/nextblock-template/app/api/revalidate/route.ts +15 -15
  10. package/templates/nextblock-template/app/{blog → article}/[slug]/PostClientContent.tsx +45 -43
  11. package/templates/nextblock-template/app/{blog → article}/[slug]/page.tsx +108 -98
  12. package/templates/nextblock-template/app/{blog → article}/[slug]/page.utils.ts +10 -3
  13. package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +25 -19
  14. package/templates/nextblock-template/app/cms/blocks/actions.ts +1 -1
  15. package/templates/nextblock-template/app/cms/posts/[id]/edit/page.tsx +1 -1
  16. package/templates/nextblock-template/app/cms/posts/actions.ts +47 -44
  17. package/templates/nextblock-template/app/cms/posts/page.tsx +2 -2
  18. package/templates/nextblock-template/app/cms/settings/languages/actions.ts +16 -15
  19. package/templates/nextblock-template/app/layout.tsx +9 -9
  20. package/templates/nextblock-template/app/lib/sitemap-utils.ts +52 -52
  21. package/templates/nextblock-template/app/sitemap.xml/route.ts +2 -2
  22. package/templates/nextblock-template/components/ResponsiveNav.tsx +22 -16
  23. package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -7
  24. package/templates/nextblock-template/components/blocks/PostsGridClient.tsx +25 -26
  25. package/templates/nextblock-template/package.json +1 -1
  26. package/templates/nextblock-template/proxy.ts +4 -4
  27. package/templates/nextblock-template/public/images/NBcover.webp +0 -0
  28. package/templates/nextblock-template/public/images/developer.webp +0 -0
  29. package/templates/nextblock-template/public/images/nextblock-logo-small.webp +0 -0
  30. package/templates/nextblock-template/public/images/nx-graph.webp +0 -0
  31. package/templates/nextblock-template/public/images/programmer-upscaled.webp +0 -0
  32. package/templates/nextblock-template/scripts/backup.js +142 -47
  33. package/templates/nextblock-template/scripts/restore-working.js +102 -0
  34. package/templates/nextblock-template/scripts/restore.js +434 -0
  35. package/templates/nextblock-template/app/blog/page.tsx +0 -77
  36. package/templates/nextblock-template/backup/backup_2025-06-19.sql +0 -8057
  37. package/templates/nextblock-template/backup/backup_2025-06-20.sql +0 -8159
  38. package/templates/nextblock-template/backup/backup_2025-07-08.sql +0 -8411
  39. package/templates/nextblock-template/backup/backup_2025-07-09.sql +0 -8442
  40. package/templates/nextblock-template/backup/backup_2025-07-10.sql +0 -8442
  41. package/templates/nextblock-template/backup/backup_2025-10-01.sql +0 -8803
  42. package/templates/nextblock-template/backup/backup_2025-10-02.sql +0 -9749
@@ -1,18 +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
-
11
- type PageType = Database['public']['Tables']['pages']['Row'];
12
- type BlockType = Database['public']['Tables']['blocks']['Row'];
13
-
14
- interface PageClientContentProps {
15
- 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;
16
17
  currentSlug: string; // The slug of the currently viewed page
17
18
  children: React.ReactNode;
18
19
  translatedSlugs?: { [key: string]: string };
@@ -42,36 +43,62 @@ interface PageClientContentProps {
42
43
  // }
43
44
 
44
45
 
45
- export default function PageClientContent({ initialPageData, currentSlug, children, translatedSlugs }: PageClientContentProps) {
46
- const { currentLocale, isLoadingLanguages } = useLanguage();
47
- const { currentContent, setCurrentContent } = useCurrentContent();
48
- const router = useRouter();
49
- // currentPageData is the data for the slug currently in the URL.
50
- // It's initially set by the server for the slug it resolved.
51
- const [currentPageData] = useState(initialPageData);
52
- const [isLoadingTargetLang, setIsLoadingTargetLang] = useState(false);
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(), []);
53
55
 
54
56
  // Memoize pageId and pageSlug
55
- const pageId = useMemo(() => currentPageData?.id, [currentPageData?.id]);
56
- const pageSlug = useMemo(() => currentPageData?.slug, [currentPageData?.slug]);
57
-
58
- useEffect(() => {
59
- if (currentLocale && currentPageData && currentPageData.language_code !== currentLocale && translatedSlugs) {
60
- // Current page's language doesn't match context, try to navigate to translated version
61
- setIsLoadingTargetLang(true);
62
- const targetSlug = translatedSlugs[currentLocale];
63
-
64
- if (targetSlug && targetSlug !== currentSlug) {
65
- router.push(`/${targetSlug}`); // Navigate to the translated slug's URL
66
- } else if (targetSlug && targetSlug === currentSlug) {
67
- // Already on the correct page for the selected language, do nothing or refresh data if needed
68
- } else {
69
- console.warn(`No published translation found for group ${currentPageData.translation_group_id} in language ${currentLocale} using pre-fetched slugs.`);
70
- // Optionally, provide feedback to the user that translation is not available
71
- }
72
- setIsLoadingTargetLang(false);
73
- }
74
- }, [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)
75
102
 
76
103
  // Update HTML lang attribute based on the *actually displayed* content's language
77
104
  useEffect(() => {
@@ -4,12 +4,15 @@ import { getSsgSupabaseClient } from "@nextblock-cms/db/server";
4
4
  import { notFound } from "next/navigation";
5
5
  import type { Metadata } from 'next';
6
6
  import PageClientContent from "./PageClientContent";
7
- import { getPageDataBySlug } from "./page.utils";
8
- import BlockRenderer from "../../components/BlockRenderer";
9
- import type { HeroBlockContent } from '../../lib/blocks/blockRegistry';
7
+ import { getPageDataBySlug } from "./page.utils";
8
+ import BlockRenderer from "../../components/BlockRenderer";
9
+ import type { HeroBlockContent } from '../../lib/blocks/blockRegistry';
10
+ import { cookies, headers } from "next/headers";
10
11
 
11
- export const dynamicParams = true;
12
- export const revalidate = 3600;
12
+ export const dynamicParams = true;
13
+ export const revalidate = 360;
14
+ export const dynamic = 'force-dynamic'; // keeps per-request locale; paired with short revalidate
15
+ export const fetchCache = 'force-no-store';
13
16
 
14
17
  interface ResolvedPageParams {
15
18
  slug: string;
@@ -43,8 +46,24 @@ export async function generateStaticParams(): Promise<ResolvedPageParams[]> {
43
46
  export async function generateMetadata(
44
47
  { params: paramsPromise }: PageProps,
45
48
  ): Promise<Metadata> {
46
- const params = await paramsPromise;
47
- const pageData = await getPageDataBySlug(params.slug);
49
+ const params = await paramsPromise;
50
+ let preferredLocale: string | undefined;
51
+ try {
52
+ const store = await cookies();
53
+ preferredLocale = store.get("NEXT_USER_LOCALE")?.value || store.get("NEXT_LOCALE")?.value;
54
+ } catch {
55
+ preferredLocale = undefined;
56
+ }
57
+ if (!preferredLocale) {
58
+ try {
59
+ const hdrs = await headers();
60
+ const al = hdrs.get("accept-language");
61
+ if (al) preferredLocale = al.split(",")[0]?.split("-")[0];
62
+ } catch {
63
+ // ignore header lookup errors
64
+ }
65
+ }
66
+ const pageData = await getPageDataBySlug(params.slug, preferredLocale);
48
67
 
49
68
  if (!pageData) {
50
69
  return { title: "Page Not Found" };
@@ -86,9 +105,25 @@ export async function generateMetadata(
86
105
  };
87
106
  }
88
107
 
89
- export default async function DynamicPage({ params: paramsPromise }: PageProps) {
90
- const params = await paramsPromise;
91
- const pageData = await getPageDataBySlug(params.slug);
108
+ export default async function DynamicPage({ params: paramsPromise }: PageProps) {
109
+ const params = await paramsPromise;
110
+ let preferredLocale: string | undefined;
111
+ try {
112
+ const store = await cookies();
113
+ preferredLocale = store.get("NEXT_USER_LOCALE")?.value || store.get("NEXT_LOCALE")?.value;
114
+ } catch {
115
+ preferredLocale = undefined;
116
+ }
117
+ if (!preferredLocale) {
118
+ try {
119
+ const hdrs = await headers();
120
+ const al = hdrs.get("accept-language");
121
+ if (al) preferredLocale = al.split(",")[0]?.split("-")[0];
122
+ } catch {
123
+ // ignore header lookup errors
124
+ }
125
+ }
126
+ const pageData = await getPageDataBySlug(params.slug, preferredLocale);
92
127
 
93
128
  if (!pageData) {
94
129
  notFound();
@@ -24,51 +24,98 @@ interface SectionOrHeroBlockContent {
24
24
  }
25
25
 
26
26
  // Interface to represent a page object after the initial database query and selection
27
- interface SelectedPageType extends PageType { // Assumes PageType includes fields like id, slug, status, language_id, translation_group_id
28
- language_details: { id: number; code: string } | null; // From the join; kept nullable due to original code's caution
29
- blocks: BlockType[];
30
- }
31
-
32
- export async function getPageDataBySlug(slug: string): Promise<(PageType & { blocks: BlockType[]; language_code: string; language_id: number; translation_group_id: string | null; }) | null> {
33
- const supabase = getSsgSupabaseClient();
34
-
35
- // Optimized query with specific field selection instead of *
36
- const { data: candidatePagesData, error: pageError } = await supabase
37
- .from("pages")
38
- .select(`
39
- id, slug, title, meta_title, meta_description, status, language_id, translation_group_id, author_id, created_at, updated_at,
40
- language_details:languages!inner(id, code),
41
- blocks (id, page_id, block_type, content, order)
42
- `)
43
- .eq("slug", slug)
44
- .eq("status", "published")
45
- .order('order', { foreignTable: 'blocks', ascending: true });
46
-
47
- if (pageError) {
48
- return null;
49
- }
50
-
51
- const candidatePages: SelectedPageType[] = (candidatePagesData || []).map(page => ({
52
- ...page,
53
- language_details: Array.isArray(page.language_details) ? page.language_details[0] : page.language_details
54
- })) as SelectedPageType[];
55
-
56
- if (candidatePages.length === 0) {
57
- return null;
58
- }
59
-
60
- let selectedPage: SelectedPageType | null = null;
61
-
62
- if (candidatePages.length === 1) {
63
- selectedPage = candidatePages[0];
64
- } else {
65
- const enPage = candidatePages.find(p => p.language_details && p.language_details.code === 'en');
66
- if (enPage) {
67
- selectedPage = enPage;
68
- } else {
69
- return null;
70
- }
71
- }
27
+ interface SelectedPageType extends PageType { // Assumes PageType includes fields like id, slug, status, language_id, translation_group_id
28
+ language_details: { id: number; code: string } | null; // From the join; kept nullable due to original code's caution
29
+ blocks: BlockType[];
30
+ }
31
+
32
+ export async function getPageDataBySlug(
33
+ slug: string,
34
+ preferredLanguageCode?: string,
35
+ ): Promise<(PageType & { blocks: BlockType[]; language_code: string; language_id: number; translation_group_id: string | null; }) | null> {
36
+ const supabase = getSsgSupabaseClient();
37
+
38
+ const baseSelect = `
39
+ id, slug, title, meta_title, meta_description, status, language_id, translation_group_id, author_id, created_at, updated_at,
40
+ language_details:languages!inner(id, code),
41
+ blocks (id, page_id, block_type, content, order)
42
+ `;
43
+
44
+ const toSelected = (rows: any[] | null | undefined): SelectedPageType[] =>
45
+ (rows || []).map(page => ({
46
+ ...page,
47
+ language_details: Array.isArray(page.language_details) ? page.language_details[0] : page.language_details,
48
+ })) as SelectedPageType[];
49
+
50
+ let candidatePages: SelectedPageType[] = [];
51
+
52
+ // First try to fetch the preferred language explicitly when provided
53
+ if (preferredLanguageCode) {
54
+ const { data: preferredData, error: preferredError } = await supabase
55
+ .from("pages")
56
+ .select(baseSelect)
57
+ .eq("slug", slug)
58
+ .eq("status", "published")
59
+ .eq("languages.code", preferredLanguageCode)
60
+ .order('order', { foreignTable: 'blocks', ascending: true })
61
+ .maybeSingle();
62
+ if (!preferredError && preferredData) {
63
+ candidatePages = toSelected([preferredData]);
64
+ }
65
+ }
66
+
67
+ // Fallback: fetch all published pages with this slug
68
+ if (candidatePages.length === 0) {
69
+ const { data: candidatePagesData, error: pageError } = await supabase
70
+ .from("pages")
71
+ .select(baseSelect)
72
+ .eq("slug", slug)
73
+ .eq("status", "published")
74
+ .order('order', { foreignTable: 'blocks', ascending: true });
75
+
76
+ if (pageError) {
77
+ return null;
78
+ }
79
+ candidatePages = toSelected(candidatePagesData);
80
+ }
81
+
82
+ if (candidatePages.length === 0) {
83
+ return null;
84
+ }
85
+
86
+ let selectedPage: SelectedPageType | null = null;
87
+
88
+ if (preferredLanguageCode) {
89
+ selectedPage = candidatePages.find(
90
+ p => p.language_details && p.language_details.code === preferredLanguageCode,
91
+ ) || null;
92
+ }
93
+
94
+ if (!selectedPage && candidatePages.length === 1) {
95
+ selectedPage = candidatePages[0];
96
+ }
97
+
98
+ if (!selectedPage) {
99
+ // Prefer default language if available
100
+ const { data: defaultLang } = await supabase
101
+ .from('languages')
102
+ .select('id, code')
103
+ .eq('is_default', true)
104
+ .maybeSingle();
105
+ if (defaultLang) {
106
+ const match = candidatePages.find(p => p.language_details && p.language_details.id === defaultLang.id);
107
+ if (match) selectedPage = match;
108
+ }
109
+ }
110
+
111
+ if (!selectedPage) {
112
+ const enPage = candidatePages.find(p => p.language_details && p.language_details.code === 'en');
113
+ if (enPage) {
114
+ selectedPage = enPage;
115
+ } else {
116
+ selectedPage = candidatePages[0];
117
+ }
118
+ }
72
119
 
73
120
  if (!selectedPage) {
74
121
  return null;
@@ -42,8 +42,8 @@ export async function POST(request: NextRequest) {
42
42
 
43
43
  if (table === 'pages') {
44
44
  pathToRevalidate = `/${relevantRecord.slug}`;
45
- } else if (table === 'posts') {
46
- pathToRevalidate = `/blog/${relevantRecord.slug}`;
45
+ } else if (table === 'posts') {
46
+ pathToRevalidate = `/article/${relevantRecord.slug}`;
47
47
  } else {
48
48
  console.log(`Revalidation not configured for table: ${table}`);
49
49
  return NextResponse.json({ message: `Revalidation not configured for table: ${table}` }, { status: 200 }); // Acknowledge but don't process
@@ -59,19 +59,19 @@ export async function POST(request: NextRequest) {
59
59
  await revalidatePath(normalizedPath, 'page');
60
60
  console.log(`Successfully revalidated path: ${normalizedPath}`);
61
61
 
62
- // Additionally, if it's a blog post, you might want to revalidate the main blog listing page.
63
- if (table === 'posts') {
64
- // Assuming your main blog listing page is at '/blog' or similar.
65
- // This path needs to be known and consistent.
66
- // If your blog listing is at the root of the language segment (e.g. /en/blog),
67
- // and you are NOT using [lang] in URL, then the path is just '/blog'.
68
- // However, if your LanguageContext means /blog shows different content per lang,
69
- // revalidating just '/blog' will rebuild its default language version.
70
- // Client-side fetches would still get latest for other languages.
71
- // For now, let's revalidate a generic /blog path if it exists.
72
- // await revalidatePath('/blog', 'page'); // Example: revalidate main blog listing
73
- // console.log("Also attempted to revalidate /blog listing page.");
74
- }
62
+ // Additionally, if it's an article, you might want to revalidate the main listing page.
63
+ if (table === 'posts') {
64
+ // Assuming your main articles listing page is at '/articles' or similar.
65
+ // This path needs to be known and consistent.
66
+ // If your listing is at the root of the language segment (e.g. /en/articles),
67
+ // and you are NOT using [lang] in URL, then the path is just '/articles'.
68
+ // However, if your LanguageContext means /articles shows different content per lang,
69
+ // revalidating just '/articles' will rebuild its default language version.
70
+ // Client-side fetches would still get latest for other languages.
71
+ // For now, let's revalidate a generic /articles path if it exists.
72
+ // await revalidatePath('/articles', 'page'); // Example: revalidate main listing
73
+ // console.log("Also attempted to revalidate /articles listing page.");
74
+ }
75
75
 
76
76
  return NextResponse.json({ revalidated: true, revalidatedPath: normalizedPath, now: Date.now() });
77
77
  } catch (err: unknown) {
@@ -1,7 +1,7 @@
1
- // app/blog/[slug]/PostClientContent.tsx
1
+ // app/article/[slug]/PostClientContent.tsx
2
2
  "use client";
3
3
 
4
- import React, { useState, useEffect, useMemo } from 'react';
4
+ import React, { useState, useEffect, useMemo } from 'react';
5
5
  import { useRouter } from 'next/navigation';
6
6
  import Image from 'next/image';
7
7
  import type { Database } from "@nextblock-cms/db";
@@ -17,22 +17,24 @@ export type ImageBlockContent = {
17
17
  import { useCurrentContent } from '@/context/CurrentContentContext';
18
18
  import Link from 'next/link';
19
19
 
20
- interface PostClientContentProps {
21
- initialPostData: (PostType & { blocks: BlockType[]; language_code: string; language_id: number; translation_group_id: string; feature_image_url?: string | null; }) | null;
22
- currentSlug: string; // The slug of the currently viewed page/post
23
- children: React.ReactNode;
24
- translatedSlugs?: { [key: string]: string };
25
- }
26
-
27
- export default function PostClientContent({ initialPostData, currentSlug, children, translatedSlugs }: PostClientContentProps) {
28
- const { currentLocale, isLoadingLanguages } = useLanguage();
29
- const { currentContent, setCurrentContent } = useCurrentContent();
30
- const router = useRouter();
31
-
32
- // currentPostData is always for the slug in the URL.
33
- // It's initially set by the server. It only changes if the URL itself changes (which happens on language switch).
34
- const [currentPostData, setCurrentPostData] = useState(initialPostData);
35
- const [isLoadingTargetLang, setIsLoadingTargetLang] = useState(false); // For feedback during navigation
20
+ interface PostClientContentProps {
21
+ initialPostData: (PostType & { blocks: BlockType[]; language_code: string; language_id: number; translation_group_id: string; feature_image_url?: string | null; }) | null;
22
+ currentSlug: string; // The slug of the currently viewed page/post
23
+ children: React.ReactNode;
24
+ translatedSlugs?: { [key: string]: string };
25
+ }
26
+
27
+ export default function PostClientContent({ initialPostData, currentSlug, children, translatedSlugs }: PostClientContentProps) {
28
+ const { currentLocale, isLoadingLanguages } = useLanguage();
29
+ const { currentContent, setCurrentContent } = useCurrentContent();
30
+ const router = useRouter();
31
+
32
+ const currentPrefix = "articles";
33
+
34
+ // currentPostData is always for the slug in the URL.
35
+ // It's initially set by the server. It only changes if the URL itself changes (which happens on language switch).
36
+ const [currentPostData, setCurrentPostData] = useState(initialPostData);
37
+ const [isLoadingTargetLang, setIsLoadingTargetLang] = useState(false); // For feedback during navigation
36
38
 
37
39
  // Memoize postId and postSlug
38
40
  const postId = useMemo(() => currentPostData?.id, [currentPostData?.id]);
@@ -40,20 +42,20 @@ export default function PostClientContent({ initialPostData, currentSlug, childr
40
42
 
41
43
  // This effect handles navigation when the language context changes
42
44
  useEffect(() => {
43
- if (!isLoadingLanguages && currentLocale && initialPostData && initialPostData.language_code !== currentLocale && translatedSlugs) {
44
- // The current page's language (from initialPostData.language_code)
45
- // does not match the user's selected language (currentLocale).
46
- // We need to find the slug for the currentLocale version of this post and navigate.
47
- setIsLoadingTargetLang(true);
48
- const targetSlug = translatedSlugs[currentLocale];
49
-
50
- if (targetSlug && targetSlug !== currentSlug) {
51
- router.push(`/blog/${targetSlug}`); // Navigate to the translated slug's URL
52
- } else if (!targetSlug) {
53
- console.warn(`No published translation found for post group ${initialPostData.translation_group_id} in language ${currentLocale} using pre-fetched slugs.`);
54
- // Optionally, provide user feedback here (e.g., a toast message)
55
- // For now, the user remains on the current page.
56
- }
45
+ if (!isLoadingLanguages && currentLocale && initialPostData && initialPostData.language_code !== currentLocale && translatedSlugs) {
46
+ // The current page's language (from initialPostData.language_code)
47
+ // does not match the user's selected language (currentLocale).
48
+ // We need to find the slug for the currentLocale version of this post and navigate.
49
+ setIsLoadingTargetLang(true);
50
+ const targetSlug = translatedSlugs[currentLocale];
51
+
52
+ if (targetSlug && targetSlug !== currentSlug) {
53
+ router.push(`/article/${targetSlug}`); // Navigate to the translated slug's URL
54
+ } else if (!targetSlug) {
55
+ console.warn(`No published translation found for post group ${initialPostData.translation_group_id} in language ${currentLocale} using pre-fetched slugs.`);
56
+ // Optionally, provide user feedback here (e.g., a toast message)
57
+ // For now, the user remains on the current page.
58
+ }
57
59
  // If targetSlug === currentSlug, we are already on the correct page for the selected language.
58
60
  setIsLoadingTargetLang(false);
59
61
  }
@@ -114,25 +116,25 @@ export default function PostClientContent({ initialPostData, currentSlug, childr
114
116
  // This is a fallback or could indicate an issue if reached.
115
117
  return (
116
118
  <div className="container mx-auto px-4 py-8 text-center">
117
- <h1 className="text-2xl font-bold mb-4">Post Not Found</h1>
118
- <p className="text-muted-foreground">The post for slug &quot;{currentSlug}&quot; could not be loaded.</p>
119
- <p className="mt-4">
120
- <Link href="/blog" className="text-primary hover:underline">Back to Blog</Link>
121
- <span className="mx-2">|</span>
122
- <Link href="/" className="text-primary hover:underline">Go to Homepage</Link>
123
- </p>
124
- </div>
125
- );
119
+ <h1 className="text-2xl font-bold mb-4">Article Not Found</h1>
120
+ <p className="text-muted-foreground">The article for slug &quot;{currentSlug}&quot; could not be loaded.</p>
121
+ <p className="mt-4">
122
+ <Link href={`/${currentPrefix}`} className="text-primary hover:underline">Back to Articles</Link>
123
+ <span className="mx-2">|</span>
124
+ <Link href="/" className="text-primary hover:underline">Go to Homepage</Link>
125
+ </p>
126
+ </div>
127
+ );
126
128
  }
127
129
 
128
130
  // If initialPostData was null but we are still loading language context or trying to navigate
129
131
  if (!currentPostData && (isLoadingLanguages || isLoadingTargetLang)) {
130
- return <div className="container mx-auto px-4 py-20 text-center"><p>Loading post content...</p></div>;
132
+ return <div className="container mx-auto px-4 py-20 text-center"><p>Loading article content...</p></div>;
131
133
  }
132
134
 
133
135
  // If after all attempts, currentPostData is still null (should be caught by notFound in server component ideally)
134
136
  if (!currentPostData) {
135
- return <div className="container mx-auto px-4 py-20 text-center"><p>Could not load post content for &quot;{currentSlug}&quot;.</p></div>;
137
+ return <div className="container mx-auto px-4 py-20 text-center"><p>Could not load article content for &quot;{currentSlug}&quot;.</p></div>;
136
138
  }
137
139
 
138
140
  return (