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,92 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import Link from "next/link";
|
|
3
|
+
export type ButtonBlockContent = {
|
|
4
|
+
text?: string;
|
|
5
|
+
url?: string;
|
|
6
|
+
variant?: 'default' | 'outline' | 'secondary' | 'ghost' | 'link';
|
|
7
|
+
size?: 'default' | 'sm' | 'lg';
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
interface ButtonBlockRendererProps {
|
|
11
|
+
content: ButtonBlockContent;
|
|
12
|
+
languageId: number; // This prop seems unused
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const ButtonBlockRenderer: React.FC<ButtonBlockRendererProps> = ({
|
|
16
|
+
content,
|
|
17
|
+
// languageId, // Unused
|
|
18
|
+
}) => {
|
|
19
|
+
const baseClasses =
|
|
20
|
+
"inline-flex items-center justify-center rounded-md border text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50";
|
|
21
|
+
const variantClasses: Record<string, string> = {
|
|
22
|
+
default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
|
23
|
+
outline:
|
|
24
|
+
"border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
|
25
|
+
secondary:
|
|
26
|
+
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
|
27
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
28
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
29
|
+
};
|
|
30
|
+
const sizeClasses: Record<string, string> = {
|
|
31
|
+
default: "h-10 px-4 py-2",
|
|
32
|
+
sm: "h-9 rounded-md px-3",
|
|
33
|
+
lg: "h-11 rounded-md px-8",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const isExternal =
|
|
37
|
+
content.url?.startsWith("http") ||
|
|
38
|
+
content.url?.startsWith("mailto:") ||
|
|
39
|
+
content.url?.startsWith("tel:");
|
|
40
|
+
const isAnchor = content.url?.startsWith("#");
|
|
41
|
+
|
|
42
|
+
const buttonText = content.text || "Button";
|
|
43
|
+
const buttonVariant = content.variant || "default";
|
|
44
|
+
const buttonSize = content.size || "default";
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div className="my-6 text-center">
|
|
48
|
+
{/* Case 1: Internal link (not external, not anchor, has URL) */}
|
|
49
|
+
{!isExternal && !isAnchor && !!content.url ? (
|
|
50
|
+
<Link
|
|
51
|
+
href={content.url}
|
|
52
|
+
className={[
|
|
53
|
+
baseClasses,
|
|
54
|
+
variantClasses[buttonVariant],
|
|
55
|
+
sizeClasses[buttonSize],
|
|
56
|
+
].join(" ")}
|
|
57
|
+
>
|
|
58
|
+
{buttonText}
|
|
59
|
+
</Link>
|
|
60
|
+
) : /* Case 2: External or Anchor link (has URL) */
|
|
61
|
+
(isExternal || isAnchor) && !!content.url ? (
|
|
62
|
+
<a
|
|
63
|
+
href={content.url} // content.url is guaranteed by the condition
|
|
64
|
+
target={isExternal ? "_blank" : undefined}
|
|
65
|
+
rel={isExternal ? "noopener noreferrer" : undefined}
|
|
66
|
+
className={[
|
|
67
|
+
baseClasses,
|
|
68
|
+
variantClasses[buttonVariant],
|
|
69
|
+
sizeClasses[buttonSize],
|
|
70
|
+
].join(" ")}
|
|
71
|
+
>
|
|
72
|
+
{buttonText}
|
|
73
|
+
</a>
|
|
74
|
+
) : (
|
|
75
|
+
/* Case 3: No URL or other edge cases - render a plain or disabled button */
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
className={[
|
|
79
|
+
baseClasses,
|
|
80
|
+
variantClasses[buttonVariant],
|
|
81
|
+
sizeClasses[buttonSize],
|
|
82
|
+
].join(" ")}
|
|
83
|
+
disabled={!content.url}
|
|
84
|
+
>
|
|
85
|
+
{buttonText}
|
|
86
|
+
</button>
|
|
87
|
+
)}
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export default ButtonBlockRenderer;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import parse, { HTMLReactParserOptions, Element } from 'html-react-parser';
|
|
5
|
+
import AlertWidgetRenderer from "./inline/AlertWidgetRenderer";
|
|
6
|
+
import CtaWidgetRenderer from "./inline/CtaWidgetRenderer";
|
|
7
|
+
import type { TextBlockContent } from "./TextBlockRenderer";
|
|
8
|
+
|
|
9
|
+
interface ClientTextBlockRendererProps {
|
|
10
|
+
content: TextBlockContent;
|
|
11
|
+
languageId: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const ClientTextBlockRenderer: React.FC<ClientTextBlockRendererProps> = ({ content, languageId }) => {
|
|
15
|
+
void languageId;
|
|
16
|
+
const options: HTMLReactParserOptions = {
|
|
17
|
+
replace: (domNode) => {
|
|
18
|
+
if (domNode instanceof Element && domNode.attribs) {
|
|
19
|
+
if (domNode.attribs['data-alert-widget'] !== undefined) {
|
|
20
|
+
const {
|
|
21
|
+
'data-type': type,
|
|
22
|
+
'data-title': title,
|
|
23
|
+
'data-message': message,
|
|
24
|
+
'data-align': align,
|
|
25
|
+
'data-size': size,
|
|
26
|
+
'data-text-align': textAlign,
|
|
27
|
+
} = domNode.attribs;
|
|
28
|
+
return (
|
|
29
|
+
<AlertWidgetRenderer
|
|
30
|
+
type={type as any}
|
|
31
|
+
title={title}
|
|
32
|
+
message={message}
|
|
33
|
+
align={align as any}
|
|
34
|
+
size={size as any}
|
|
35
|
+
textAlign={textAlign as any}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (domNode.attribs['data-cta-widget'] !== undefined) {
|
|
41
|
+
const {
|
|
42
|
+
'data-text': text,
|
|
43
|
+
'data-url': url,
|
|
44
|
+
'data-style': style,
|
|
45
|
+
'data-size': size,
|
|
46
|
+
'data-text-align': textAlign,
|
|
47
|
+
} = domNode.attribs;
|
|
48
|
+
return (
|
|
49
|
+
<CtaWidgetRenderer
|
|
50
|
+
text={text}
|
|
51
|
+
url={url}
|
|
52
|
+
style={style as any}
|
|
53
|
+
size={size as any}
|
|
54
|
+
textAlign={textAlign as any}
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div className="my-4 prose dark:prose-invert container mx-auto">
|
|
64
|
+
{parse(content.html_content || "", options)}
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export default ClientTextBlockRenderer;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { useActionState } from 'react';
|
|
4
|
+
import { useFormStatus } from 'react-dom';
|
|
5
|
+
import { handleFormSubmission } from '@/app/actions/formActions';
|
|
6
|
+
import type { FormBlockContent, FormField } from '@/lib/blocks/blockRegistry';
|
|
7
|
+
import { Input } from '@nextblock-cms/ui';
|
|
8
|
+
import { Textarea } from '@nextblock-cms/ui';
|
|
9
|
+
import { Label } from '@nextblock-cms/ui';
|
|
10
|
+
import { Button } from '@nextblock-cms/ui';
|
|
11
|
+
import { Checkbox } from '@nextblock-cms/ui';
|
|
12
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@nextblock-cms/ui';
|
|
13
|
+
|
|
14
|
+
interface FormBlockRendererProps {
|
|
15
|
+
content: FormBlockContent;
|
|
16
|
+
languageId: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function SubmitButton({ text }: { text: string }) {
|
|
20
|
+
const { pending } = useFormStatus();
|
|
21
|
+
return <Button type="submit" disabled={pending}>{pending ? 'Submitting...' : text}</Button>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const FormBlockRenderer: React.FC<FormBlockRendererProps> = ({ content }) => {
|
|
25
|
+
const [state, formAction] = useActionState(handleFormSubmission.bind(null, content.recipient_email), {
|
|
26
|
+
success: false,
|
|
27
|
+
message: '',
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (state.success) {
|
|
31
|
+
return <div className="p-4 rounded-md bg-green-100 text-green-800">{content.success_message}</div>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<form action={formAction} className="space-y-4 my-6 container mx-auto">
|
|
36
|
+
{content.fields.map((field: FormField) => (
|
|
37
|
+
<div key={field.temp_id} className="space-y-2">
|
|
38
|
+
<Label htmlFor={field.temp_id}>
|
|
39
|
+
{field.label} {field.is_required && <span className="text-red-500">*</span>}
|
|
40
|
+
</Label>
|
|
41
|
+
{renderField(field)}
|
|
42
|
+
</div>
|
|
43
|
+
))}
|
|
44
|
+
{state.message && !state.success && (
|
|
45
|
+
<p className="text-sm text-red-600">{state.message}</p>
|
|
46
|
+
)}
|
|
47
|
+
<SubmitButton text={content.submit_button_text} />
|
|
48
|
+
</form>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const renderField = (field: FormField) => {
|
|
53
|
+
const commonProps = {
|
|
54
|
+
id: field.temp_id,
|
|
55
|
+
name: field.label.toLowerCase().replace(/\s+/g, '_'),
|
|
56
|
+
placeholder: field.placeholder || '',
|
|
57
|
+
required: field.is_required,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
switch (field.field_type) {
|
|
61
|
+
case 'textarea':
|
|
62
|
+
return <Textarea {...commonProps} />;
|
|
63
|
+
case 'select':
|
|
64
|
+
return (
|
|
65
|
+
<Select name={commonProps.name} required={field.is_required}>
|
|
66
|
+
<SelectTrigger id={commonProps.id}><SelectValue placeholder={field.placeholder || 'Select an option'} /></SelectTrigger>
|
|
67
|
+
<SelectContent>
|
|
68
|
+
{field.options?.map(opt => <SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>)}
|
|
69
|
+
</SelectContent>
|
|
70
|
+
</Select>
|
|
71
|
+
);
|
|
72
|
+
case 'radio':
|
|
73
|
+
return (
|
|
74
|
+
<div className="space-y-2">
|
|
75
|
+
{field.options?.map(opt => (
|
|
76
|
+
<div key={opt.value} className="flex items-center gap-2">
|
|
77
|
+
<input type="radio" id={`${commonProps.id}-${opt.value}`} name={commonProps.name} value={opt.value} required={field.is_required} className="h-4 w-4"/>
|
|
78
|
+
<Label htmlFor={`${commonProps.id}-${opt.value}`}>{opt.label}</Label>
|
|
79
|
+
</div>
|
|
80
|
+
))}
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
case 'checkbox':
|
|
84
|
+
return (
|
|
85
|
+
<div className="flex items-center gap-2">
|
|
86
|
+
<Checkbox id={commonProps.id} name={commonProps.name} required={field.is_required} />
|
|
87
|
+
<Label htmlFor={commonProps.id} className="font-normal">{field.placeholder || "I agree"}</Label>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
case 'email':
|
|
91
|
+
return <Input type="email" {...commonProps} />;
|
|
92
|
+
case 'text':
|
|
93
|
+
default:
|
|
94
|
+
return <Input type="text" {...commonProps} />;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export default FormBlockRenderer;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { HeadingBlockContent } from "@/lib/blocks/blockRegistry";
|
|
3
|
+
|
|
4
|
+
interface HeadingBlockRendererProps {
|
|
5
|
+
content: HeadingBlockContent;
|
|
6
|
+
languageId: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const HeadingBlockRenderer: React.FC<HeadingBlockRendererProps> = ({
|
|
10
|
+
content,
|
|
11
|
+
languageId,
|
|
12
|
+
}) => {
|
|
13
|
+
void languageId;
|
|
14
|
+
// Ensure level is between 1 and 6, default to 2
|
|
15
|
+
const level =
|
|
16
|
+
typeof content.level === "number" &&
|
|
17
|
+
content.level >= 1 &&
|
|
18
|
+
content.level <= 6
|
|
19
|
+
? content.level
|
|
20
|
+
: 2;
|
|
21
|
+
const Tag: React.ElementType = `h${level}`;
|
|
22
|
+
|
|
23
|
+
let alignmentClass = "";
|
|
24
|
+
if (content.textAlign) {
|
|
25
|
+
alignmentClass = `text-${content.textAlign}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let colorClass = "";
|
|
29
|
+
if (content.textColor) {
|
|
30
|
+
colorClass = `text-${content.textColor}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const combinedClasses = `my-6 font-bold container mx-auto ${alignmentClass} ${colorClass}`.trim();
|
|
34
|
+
return (
|
|
35
|
+
<Tag className={combinedClasses}>
|
|
36
|
+
{content.text_content}
|
|
37
|
+
</Tag>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export default HeadingBlockRenderer;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// components/blocks/renderers/HeroBlockRenderer.tsx
|
|
2
|
+
import React from "react";
|
|
3
|
+
import type { SectionBlockContent, Gradient } from "../../../lib/blocks/blockRegistry";
|
|
4
|
+
import Image from 'next/image';
|
|
5
|
+
|
|
6
|
+
const R2_BASE_URL = process.env.NEXT_PUBLIC_R2_BASE_URL || "";
|
|
7
|
+
|
|
8
|
+
interface SectionBlockRendererProps {
|
|
9
|
+
content: SectionBlockContent;
|
|
10
|
+
languageId: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Container class mapping
|
|
14
|
+
const containerClasses = {
|
|
15
|
+
'full-width': 'w-full',
|
|
16
|
+
'container': 'container mx-auto px-4',
|
|
17
|
+
'container-sm': 'container mx-auto px-4 max-w-screen-sm',
|
|
18
|
+
'container-lg': 'container mx-auto px-4 max-w-screen-lg',
|
|
19
|
+
'container-xl': 'container mx-auto px-4 max-w-screen-xl'
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Column grid classes
|
|
23
|
+
const columnClasses = {
|
|
24
|
+
1: 'grid-cols-1',
|
|
25
|
+
2: 'grid-cols-1 md:grid-cols-2',
|
|
26
|
+
3: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
|
|
27
|
+
4: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-4'
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Gap classes
|
|
31
|
+
const gapClasses = {
|
|
32
|
+
none: 'gap-0',
|
|
33
|
+
sm: 'gap-2',
|
|
34
|
+
md: 'gap-4',
|
|
35
|
+
lg: 'gap-6',
|
|
36
|
+
xl: 'gap-8'
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Padding classes
|
|
40
|
+
const paddingClasses = {
|
|
41
|
+
none: '',
|
|
42
|
+
sm: 'py-2',
|
|
43
|
+
md: 'py-4',
|
|
44
|
+
lg: 'py-8',
|
|
45
|
+
xl: 'py-12'
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Background style generator
|
|
49
|
+
function generateBackgroundStyles(background: SectionBlockContent['background']) {
|
|
50
|
+
const styles: React.CSSProperties = {};
|
|
51
|
+
let className = '';
|
|
52
|
+
|
|
53
|
+
if (background.min_height) {
|
|
54
|
+
styles.minHeight = background.min_height;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
switch (background.type) {
|
|
58
|
+
case 'theme': {
|
|
59
|
+
// Theme-based backgrounds using CSS classes
|
|
60
|
+
const themeClasses = {
|
|
61
|
+
primary: 'bg-primary text-primary-foreground',
|
|
62
|
+
secondary: 'bg-secondary text-secondary-foreground',
|
|
63
|
+
muted: 'bg-muted text-muted-foreground',
|
|
64
|
+
accent: 'bg-accent text-accent-foreground',
|
|
65
|
+
destructive: 'bg-destructive text-destructive-foreground'
|
|
66
|
+
};
|
|
67
|
+
className = background.theme ? themeClasses[background.theme] || '' : '';
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
case 'solid':
|
|
72
|
+
styles.backgroundColor = background.solid_color;
|
|
73
|
+
break;
|
|
74
|
+
|
|
75
|
+
case 'gradient':
|
|
76
|
+
if (background.gradient) {
|
|
77
|
+
const { type, direction, stops } = background.gradient;
|
|
78
|
+
const gradientStops = stops.map(stop => `${stop.color} ${stop.position}%`).join(', ');
|
|
79
|
+
styles.background = `${type}-gradient(${direction || 'to right'}, ${gradientStops})`;
|
|
80
|
+
}
|
|
81
|
+
break;
|
|
82
|
+
|
|
83
|
+
case 'image':
|
|
84
|
+
if (background.image) {
|
|
85
|
+
const imageUrl = `${R2_BASE_URL}/${background.image.object_key}`;
|
|
86
|
+
styles.backgroundSize = background.image.size || 'cover';
|
|
87
|
+
styles.backgroundPosition = background.image.position || 'center';
|
|
88
|
+
|
|
89
|
+
let finalBackgroundImage = `url(${imageUrl})`;
|
|
90
|
+
|
|
91
|
+
if (background.image.overlay && background.image.overlay.gradient) {
|
|
92
|
+
const { type, direction, stops } = background.image.overlay.gradient;
|
|
93
|
+
const gradientStops = stops.map(stop => `${stop.color} ${stop.position}%`).join(', ');
|
|
94
|
+
const gradient = `${type}-gradient(${direction || 'to right'}, ${gradientStops})`;
|
|
95
|
+
finalBackgroundImage = `${gradient}, ${finalBackgroundImage}`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
styles.backgroundImage = finalBackgroundImage;
|
|
99
|
+
}
|
|
100
|
+
break;
|
|
101
|
+
|
|
102
|
+
default:
|
|
103
|
+
// No background
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return { styles, className };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function generateGradientString(gradient: Gradient) {
|
|
111
|
+
const { type, direction, stops } = gradient;
|
|
112
|
+
const gradientStops = stops.map((stop: { color: string; position: number }) => `${stop.color} ${stop.position}%`).join(', ');
|
|
113
|
+
return `${type}-gradient(${direction || 'to right'}, ${gradientStops})`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Static imports for critical hero block components
|
|
117
|
+
import TextBlockRenderer from './TextBlockRenderer';
|
|
118
|
+
import ImageBlockRenderer from './ImageBlockRenderer';
|
|
119
|
+
import ButtonBlockRenderer from './ButtonBlockRenderer';
|
|
120
|
+
import HeadingBlockRenderer from './HeadingBlockRenderer';
|
|
121
|
+
|
|
122
|
+
// Static nested block renderer component for hero blocks (no dynamic imports)
|
|
123
|
+
const StaticNestedBlockRenderer: React.FC<{
|
|
124
|
+
block: SectionBlockContent['column_blocks'][0][0];
|
|
125
|
+
languageId: number;
|
|
126
|
+
}> = ({ block, languageId }) => {
|
|
127
|
+
// Use static imports for common hero block components to eliminate loading delays
|
|
128
|
+
switch (block.block_type) {
|
|
129
|
+
case 'text':
|
|
130
|
+
return (
|
|
131
|
+
<TextBlockRenderer
|
|
132
|
+
content={block.content as any}
|
|
133
|
+
languageId={languageId}
|
|
134
|
+
/>
|
|
135
|
+
);
|
|
136
|
+
case 'heading':
|
|
137
|
+
return (
|
|
138
|
+
<HeadingBlockRenderer
|
|
139
|
+
content={block.content as any}
|
|
140
|
+
languageId={languageId}
|
|
141
|
+
/>
|
|
142
|
+
);
|
|
143
|
+
case 'image':
|
|
144
|
+
return (
|
|
145
|
+
<ImageBlockRenderer
|
|
146
|
+
content={block.content as any}
|
|
147
|
+
languageId={languageId}
|
|
148
|
+
priority={true}
|
|
149
|
+
/>
|
|
150
|
+
);
|
|
151
|
+
case 'button':
|
|
152
|
+
return (
|
|
153
|
+
<ButtonBlockRenderer
|
|
154
|
+
content={block.content as any}
|
|
155
|
+
languageId={languageId}
|
|
156
|
+
/>
|
|
157
|
+
);
|
|
158
|
+
default:
|
|
159
|
+
// Fallback for unsupported block types in hero
|
|
160
|
+
return (
|
|
161
|
+
<div className="p-2 border rounded bg-destructive/10 text-destructive text-sm">
|
|
162
|
+
<strong>Unsupported hero block type:</strong> {block.block_type}
|
|
163
|
+
</div>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const HeroBlockRenderer: React.FC<SectionBlockRendererProps> = ({
|
|
169
|
+
content,
|
|
170
|
+
languageId,
|
|
171
|
+
}) => {
|
|
172
|
+
const { styles, className: backgroundClassName } = generateBackgroundStyles(content.background);
|
|
173
|
+
|
|
174
|
+
const backgroundImage = content.background.type === 'image' ? content.background.image : undefined;
|
|
175
|
+
|
|
176
|
+
if (backgroundImage) {
|
|
177
|
+
delete styles.backgroundImage;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Build CSS classes
|
|
181
|
+
const containerClass = containerClasses[content.container_type] || containerClasses.container;
|
|
182
|
+
const gridClass = columnClasses[content.responsive_columns.desktop] || columnClasses[3];
|
|
183
|
+
const gapClass = gapClasses[content.column_gap] || gapClasses.md;
|
|
184
|
+
const paddingTopClass = paddingClasses[content.padding.top] || paddingClasses.md;
|
|
185
|
+
const paddingBottomClass = paddingClasses[content.padding.bottom] || paddingClasses.md;
|
|
186
|
+
|
|
187
|
+
const imageProps = backgroundImage?.blur_data_url
|
|
188
|
+
? {
|
|
189
|
+
placeholder: 'blur' as const,
|
|
190
|
+
blurDataURL: backgroundImage.blur_data_url,
|
|
191
|
+
}
|
|
192
|
+
: {};
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<section
|
|
196
|
+
className={`relative w-full flex items-center ${paddingTopClass} ${paddingBottomClass} ${backgroundClassName}`.trim()}
|
|
197
|
+
style={styles}
|
|
198
|
+
>
|
|
199
|
+
{backgroundImage && (
|
|
200
|
+
<Image
|
|
201
|
+
src={`${R2_BASE_URL}/${backgroundImage.object_key}`}
|
|
202
|
+
alt={backgroundImage.alt_text || 'Hero background image'}
|
|
203
|
+
fill
|
|
204
|
+
style={{
|
|
205
|
+
objectFit: backgroundImage.size || 'cover',
|
|
206
|
+
objectPosition: backgroundImage.position || 'center'
|
|
207
|
+
}}
|
|
208
|
+
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 100vw, 100vw"
|
|
209
|
+
priority={true}
|
|
210
|
+
fetchPriority="high"
|
|
211
|
+
quality={35}
|
|
212
|
+
{...imageProps}
|
|
213
|
+
/>
|
|
214
|
+
)}
|
|
215
|
+
{backgroundImage?.overlay?.gradient && (
|
|
216
|
+
<div
|
|
217
|
+
className="absolute inset-0"
|
|
218
|
+
style={{ background: generateGradientString(backgroundImage.overlay.gradient) }}
|
|
219
|
+
/>
|
|
220
|
+
)}
|
|
221
|
+
<div className={`${containerClass} relative`}>
|
|
222
|
+
<div className={`grid ${gridClass} ${gapClass}`}>
|
|
223
|
+
{content.column_blocks.map((columnBlocks, columnIndex) => (
|
|
224
|
+
<div key={`column-${columnIndex}`} className="min-h-0 space-y-4">
|
|
225
|
+
{(Array.isArray(columnBlocks) ? columnBlocks : []).map((block, blockIndex) => (
|
|
226
|
+
<StaticNestedBlockRenderer
|
|
227
|
+
key={`${block.block_type}-${columnIndex}-${blockIndex}`}
|
|
228
|
+
block={block}
|
|
229
|
+
languageId={languageId}
|
|
230
|
+
/>
|
|
231
|
+
))}
|
|
232
|
+
</div>
|
|
233
|
+
))}
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
</section>
|
|
237
|
+
);
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
export default HeroBlockRenderer;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import Image from "next/image";
|
|
3
|
+
|
|
4
|
+
export type ImageBlockContent = {
|
|
5
|
+
media_id: string | null;
|
|
6
|
+
object_key: string | null;
|
|
7
|
+
alt_text: string | null;
|
|
8
|
+
caption: string | null;
|
|
9
|
+
width: number | null;
|
|
10
|
+
height: number | null;
|
|
11
|
+
blur_data_url: string | null;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const R2_BASE_URL = process.env.NEXT_PUBLIC_R2_BASE_URL || "";
|
|
15
|
+
|
|
16
|
+
interface ImageBlockRendererProps {
|
|
17
|
+
content: ImageBlockContent;
|
|
18
|
+
languageId: number;
|
|
19
|
+
priority?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const ImageBlockRenderer: React.FC<ImageBlockRendererProps> = ({
|
|
23
|
+
content,
|
|
24
|
+
languageId,
|
|
25
|
+
priority = false,
|
|
26
|
+
}) => {
|
|
27
|
+
void languageId;
|
|
28
|
+
if (!content.media_id || !content.object_key) {
|
|
29
|
+
return (
|
|
30
|
+
<div className="my-4 p-4 border rounded text-center text-muted-foreground italic">
|
|
31
|
+
(Image block: Media not selected or object_key missing)
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (
|
|
37
|
+
typeof content.width !== "number" ||
|
|
38
|
+
typeof content.height !== "number" ||
|
|
39
|
+
content.width <= 0 ||
|
|
40
|
+
content.height <= 0
|
|
41
|
+
) {
|
|
42
|
+
return (
|
|
43
|
+
<div className="my-4 p-4 border rounded text-center text-muted-foreground italic">
|
|
44
|
+
(Image block: Image dimensions are missing or invalid)
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const displayImageUrl = `${R2_BASE_URL}/${content.object_key}`;
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div className="w-full">
|
|
53
|
+
<figure
|
|
54
|
+
className="my-6 text-center mx-auto max-w-full"
|
|
55
|
+
// Removed inline style: style={{ width: content.width }}
|
|
56
|
+
>
|
|
57
|
+
<Image
|
|
58
|
+
src={displayImageUrl}
|
|
59
|
+
alt={content.alt_text || ""}
|
|
60
|
+
width={content.width}
|
|
61
|
+
height={content.height}
|
|
62
|
+
sizes="(max-width: 768px) 100vw, (max-width: 1280px) 75vw, 66vw"
|
|
63
|
+
className="rounded-md border"
|
|
64
|
+
placeholder={content.blur_data_url ? "blur" : "empty"}
|
|
65
|
+
blurDataURL={content.blur_data_url || undefined}
|
|
66
|
+
priority={priority}
|
|
67
|
+
quality={60}
|
|
68
|
+
/>
|
|
69
|
+
{content.caption && (
|
|
70
|
+
<figcaption className="text-sm text-muted-foreground mt-2">
|
|
71
|
+
{content.caption}
|
|
72
|
+
</figcaption>
|
|
73
|
+
)}
|
|
74
|
+
</figure>
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export default ImageBlockRenderer;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import PostsGridBlock from "@/components/blocks/PostsGridBlock";
|
|
3
|
+
import type { Database } from "@nextblock-cms/db";
|
|
4
|
+
|
|
5
|
+
type Block = Database['public']['Tables']['blocks']['Row'];
|
|
6
|
+
type PostsGridBlockContent = {
|
|
7
|
+
title?: string;
|
|
8
|
+
postsPerPage?: number;
|
|
9
|
+
columns?: number;
|
|
10
|
+
showPagination?: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
interface PostsGridBlockRendererProps {
|
|
14
|
+
content: PostsGridBlockContent;
|
|
15
|
+
languageId: number;
|
|
16
|
+
block: Block;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const PostsGridBlockRenderer: React.FC<PostsGridBlockRendererProps> = ({
|
|
20
|
+
content,
|
|
21
|
+
languageId,
|
|
22
|
+
block,
|
|
23
|
+
}) => {
|
|
24
|
+
void content;
|
|
25
|
+
return (
|
|
26
|
+
<PostsGridBlock
|
|
27
|
+
block={block}
|
|
28
|
+
languageId={languageId}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default PostsGridBlockRenderer;
|