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,119 @@
1
+ // components/BlockRenderer.tsx
2
+ import React from "react";
3
+ import dynamic from "next/dynamic";
4
+ import type { Database } from "@nextblock-cms/db";
5
+ import { getBlockDefinition, type SectionBlockContent, type BlockType } from "../lib/blocks/blockRegistry";
6
+
7
+ type Block = Database['public']['Tables']['blocks']['Row'];
8
+ import HeroBlockRenderer from "./blocks/renderers/HeroBlockRenderer"; // Static import for LCP
9
+ import ClientTextBlockRenderer from "./blocks/renderers/ClientTextBlockRenderer"; // Static import for client component
10
+
11
+ interface BlockRendererProps {
12
+ blocks: Block[];
13
+ languageId: number;
14
+ }
15
+
16
+ interface DynamicBlockRendererProps {
17
+ block: Block;
18
+ languageId: number;
19
+ }
20
+
21
+ // Dynamic renderer component that handles the dynamic import logic for non-LCP blocks
22
+ const DynamicBlockRenderer: React.FC<DynamicBlockRendererProps> = ({
23
+ block,
24
+ languageId,
25
+ }) => {
26
+ const blockDefinition = getBlockDefinition(block.block_type as BlockType);
27
+
28
+ if (!blockDefinition) {
29
+ return (
30
+ <div
31
+ key={block.id}
32
+ className="my-4 p-4 border rounded bg-destructive/10 text-destructive"
33
+ >
34
+ <p>
35
+ <strong>Unsupported block type:</strong> {block.block_type}
36
+ </p>
37
+ <pre className="text-xs whitespace-pre-wrap">
38
+ {JSON.stringify(block.content, null, 2)}
39
+ </pre>
40
+ </div>
41
+ );
42
+ }
43
+
44
+ // Handle the text block type by rendering the client component wrapper
45
+ if (block.block_type === 'text') {
46
+ return <ClientTextBlockRenderer content={block.content as any} languageId={languageId} />;
47
+ }
48
+
49
+ // Create dynamic component with proper SSR handling for other blocks
50
+ const RendererComponent = dynamic(
51
+ () => import(`./blocks/renderers/${blockDefinition.rendererComponentFilename}`),
52
+ {
53
+ loading: () => (
54
+ <div className="my-4 p-4 border rounded-lg">
55
+ <div className="h-8 w-1/2 mb-4 bg-muted/40 animate-pulse rounded" />
56
+ <div className="h-4 w-full mb-2 bg-muted/40 animate-pulse rounded" />
57
+ <div className="h-4 w-full mb-2 bg-muted/40 animate-pulse rounded" />
58
+ <div className="h-4 w-3/4 bg-muted/40 animate-pulse rounded" />
59
+ </div>
60
+ ),
61
+ ssr: true,
62
+ }
63
+ ) as React.ComponentType<any>;
64
+
65
+ // Handle different prop requirements for different renderers
66
+ // PostsGridBlockRenderer needs the full block object
67
+ if (block.block_type === 'posts_grid') {
68
+ return (
69
+ <RendererComponent
70
+ content={block.content}
71
+ languageId={languageId}
72
+ block={block}
73
+ />
74
+ );
75
+ }
76
+
77
+ return (
78
+ <RendererComponent
79
+ content={block.content}
80
+ languageId={languageId}
81
+ />
82
+ );
83
+ };
84
+
85
+ const BlockRenderer: React.FC<BlockRendererProps> = ({
86
+ blocks,
87
+ languageId,
88
+ }) => {
89
+ if (!blocks || blocks.length === 0) {
90
+ return null;
91
+ }
92
+
93
+ return (
94
+ <>
95
+ {blocks.map((block) => {
96
+ // Statically render the Hero block for LCP optimization
97
+ if (block.block_type === 'hero') {
98
+ return (
99
+ <HeroBlockRenderer
100
+ key={block.id}
101
+ content={block.content as unknown as SectionBlockContent}
102
+ languageId={languageId}
103
+ />
104
+ );
105
+ }
106
+ // Dynamically render all other blocks
107
+ return (
108
+ <DynamicBlockRenderer
109
+ key={block.id}
110
+ block={block}
111
+ languageId={languageId}
112
+ />
113
+ );
114
+ })}
115
+ </>
116
+ );
117
+ };
118
+
119
+ export default BlockRenderer;
@@ -0,0 +1,33 @@
1
+ import Link from 'next/link';
2
+ import type { Database } from '@nextblock-cms/db';
3
+
4
+ type NavigationItem = Database['public']['Tables']['navigation_items']['Row'];
5
+
6
+ interface FooterNavigationProps {
7
+ navItems: NavigationItem[];
8
+ }
9
+
10
+ export default function FooterNavigation({ navItems }: FooterNavigationProps) {
11
+ if (navItems.length === 0) {
12
+ return null;
13
+ }
14
+
15
+ const renderNavItems = (items: NavigationItem[]) =>
16
+ items
17
+ .filter((item) => !item.parent_id)
18
+ .map((item) => (
19
+ <Link
20
+ key={item.id}
21
+ href={item.url}
22
+ className="text-sm text-muted-foreground hover:text-foreground hover:underline px-2 py-1"
23
+ >
24
+ {item.label}
25
+ </Link>
26
+ ));
27
+
28
+ return (
29
+ <nav className="flex flex-wrap justify-center items-center gap-x-4 gap-y-2" aria-label="Footer navigation">
30
+ {renderNavItems(navItems)}
31
+ </nav>
32
+ );
33
+ }
@@ -0,0 +1,42 @@
1
+ // components/Header.tsx
2
+ import type { Database } from '@nextblock-cms/db';
3
+ import HeaderAuth from './header-auth';
4
+ import LanguageSwitcher from './LanguageSwitcher';
5
+ import ResponsiveNav from './ResponsiveNav';
6
+
7
+ type NavigationItem = Database['public']['Tables']['navigation_items']['Row'];
8
+ type Logo =
9
+ Database['public']['Tables']['logos']['Row'] & {
10
+ media: (Database['public']['Tables']['media']['Row'] & { alt_text: string | null }) | null;
11
+ };
12
+
13
+ interface HeaderProps {
14
+ navItems: NavigationItem[];
15
+ canAccessCms: boolean;
16
+ logo: Logo | null;
17
+ currentPageData?: { slug: string; translation_group_id: string | null };
18
+ siteTitle: string;
19
+ }
20
+
21
+ export default function Header({
22
+ navItems,
23
+ canAccessCms,
24
+ logo,
25
+ currentPageData,
26
+ siteTitle,
27
+ }: HeaderProps) {
28
+ return (
29
+ <ResponsiveNav
30
+ homeLinkHref="/"
31
+ navItems={navItems}
32
+ canAccessCms={canAccessCms}
33
+ cmsDashboardLinkHref="/cms/dashboard"
34
+ headerAuthComponent={<HeaderAuth />}
35
+ languageSwitcherComponent={
36
+ <LanguageSwitcher currentPageData={currentPageData} />
37
+ }
38
+ logo={logo}
39
+ siteTitle={siteTitle}
40
+ />
41
+ );
42
+ }
@@ -0,0 +1,47 @@
1
+ "use client";
2
+
3
+ import React, { useEffect, useRef } from "react";
4
+
5
+ interface HtmlScriptExecutorProps {
6
+ html: string; // should contain one or more <script> tags
7
+ }
8
+
9
+ // Executes inline scripts in a string by converting them to blob URLs
10
+ // Requires CSP: script-src includes blob:
11
+ export const HtmlScriptExecutor: React.FC<HtmlScriptExecutorProps> = ({ html }) => {
12
+ const ref = useRef<HTMLDivElement>(null);
13
+
14
+ useEffect(() => {
15
+ const root = ref.current;
16
+ if (!root) return;
17
+ root.innerHTML = html;
18
+
19
+ const created: string[] = [];
20
+ const scripts = Array.from(root.querySelectorAll("script"));
21
+
22
+ scripts.forEach((old) => {
23
+ const s = document.createElement("script");
24
+ for (const attr of Array.from(old.attributes)) s.setAttribute(attr.name, attr.value);
25
+ if (old.src) {
26
+ s.src = old.src;
27
+ } else {
28
+ const blob = new Blob([old.textContent || ""], { type: old.type || "text/javascript" });
29
+ const url = URL.createObjectURL(blob);
30
+ s.src = url;
31
+ s.async = false;
32
+ created.push(url);
33
+ }
34
+ old.parentNode?.insertBefore(s, old);
35
+ old.remove();
36
+ });
37
+
38
+ return () => {
39
+ created.forEach((u) => { try { URL.revokeObjectURL(u); } catch (error) { console.error('Failed to revoke blob URL:', error); } });
40
+ };
41
+ }, [html]);
42
+
43
+ return <div ref={ref} style={{ display: 'none' }} />;
44
+ };
45
+
46
+ export default HtmlScriptExecutor;
47
+
@@ -0,0 +1,103 @@
1
+ // components/LanguageSwitcher.tsx
2
+ 'use client';
3
+
4
+ import { useLanguage } from '@/context/LanguageContext';
5
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@nextblock-cms/ui';
6
+ import { useRouter, usePathname } from 'next/navigation';
7
+ import { getPageTranslations, getPageMetadataBySlugAndLocale } from '@/app/actions/languageActions';
8
+ import type { Language } from '@/app/actions/languageActions';
9
+
10
+ interface CurrentPageInfo {
11
+ slug: string;
12
+ translation_group_id: string | null;
13
+ }
14
+
15
+ interface LanguageSwitcherProps {
16
+ currentPageData?: CurrentPageInfo;
17
+ }
18
+
19
+ export default function LanguageSwitcher({ currentPageData }: LanguageSwitcherProps) {
20
+ const { currentLocale, setCurrentLocale, availableLanguages, isLoadingLanguages } = useLanguage();
21
+ const router = useRouter();
22
+ const pathname = usePathname();
23
+
24
+ if (isLoadingLanguages || availableLanguages.length <= 1) {
25
+ return null;
26
+ }
27
+
28
+ const handleValueChange = async (newLocaleCode: string) => {
29
+ await setCurrentLocale(newLocaleCode);
30
+
31
+ let targetPath = pathname; // Default to current path
32
+
33
+ // Determine if it's a homepage (e.g., /en, /fr, or just /)
34
+ const isHomePage = pathname === '/' || availableLanguages.some(lang => pathname === `/${lang.code}`);
35
+
36
+ if (isHomePage) {
37
+ targetPath = '/'; // For any homepage, new language path is root
38
+ } else {
39
+ // Extract slug from pathname (remove leading slash)
40
+ const currentSlug = pathname.startsWith('/') ? pathname.slice(1) : pathname;
41
+
42
+ let pageMetadata = currentPageData;
43
+
44
+ // If currentPageData is not provided, try to fetch it
45
+ if (!pageMetadata && currentSlug) {
46
+ try {
47
+ const fetchedMetadata = await getPageMetadataBySlugAndLocale(currentSlug, currentLocale);
48
+ if (fetchedMetadata) {
49
+ pageMetadata = fetchedMetadata;
50
+ }
51
+ } catch (error) {
52
+ console.error('Error fetching current page metadata:', error);
53
+ }
54
+ }
55
+
56
+ if (pageMetadata?.translation_group_id) {
57
+ try {
58
+ const translations = await getPageTranslations(pageMetadata.translation_group_id);
59
+ const foundTranslation = translations.find(t => t.language_code === newLocaleCode);
60
+
61
+ if (foundTranslation) {
62
+ targetPath = `/${foundTranslation.slug}`;
63
+ } else {
64
+ // Original warning, without the [LanguageSwitcher] prefix
65
+ console.warn(`No translation found for ${pageMetadata.slug} to ${newLocaleCode}. Falling back to current path.`);
66
+ }
67
+ } catch (error) {
68
+ // Original error, without the [LanguageSwitcher] prefix
69
+ console.error("Error fetching page translations:", error);
70
+ }
71
+ } else {
72
+ // Original warning, without the [LanguageSwitcher] prefix
73
+ console.warn(`No translation_group_id for page: ${pageMetadata?.slug || currentSlug}. Current path will be used.`);
74
+ }
75
+ }
76
+
77
+ setTimeout(() => {
78
+ if (pathname !== targetPath) {
79
+ router.push(targetPath);
80
+ } else {
81
+ // If path is the same, refresh to ensure content updates for the new locale
82
+ router.refresh();
83
+ }
84
+ }, 50); // Adjust delay as needed
85
+ };
86
+
87
+ return (
88
+ <div className="flex items-center">
89
+ <Select value={currentLocale} onValueChange={handleValueChange} aria-label="Language Switcher">
90
+ <SelectTrigger className="h-9 text-xs sm:text-sm" aria-label="Language Switcher">
91
+ <SelectValue placeholder="Language" aria-label="Language Switcher"/>
92
+ </SelectTrigger>
93
+ <SelectContent>
94
+ {availableLanguages.map((lang: Language) => (
95
+ <SelectItem key={lang.code} value={lang.code}>
96
+ {lang.name}
97
+ </SelectItem>
98
+ ))}
99
+ </SelectContent>
100
+ </Select>
101
+ </div>
102
+ );
103
+ }