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,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
|
+
}
|