create-nextblock 0.16.4 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +22 -0
- package/docker-template/.env.docker.example +69 -69
- package/docker-template/docker/db/init/99-jwt.sql +6 -6
- package/docker-template/docker/db/init/99-roles.sql +25 -25
- package/docker-template/docker/kong/kong.yml +112 -112
- package/docker-template/scripts/docker-setup.mjs +310 -310
- package/package.json +1 -1
- package/templates/nextblock-template/.browserslistrc +11 -11
- package/templates/nextblock-template/.swcrc +30 -30
- package/templates/nextblock-template/CLAUDE.md +26 -0
- package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +46 -46
- package/templates/nextblock-template/app/(auth-pages)/two-factor/page.tsx +51 -51
- package/templates/nextblock-template/app/.well-known/ucp/route.ts +16 -16
- package/templates/nextblock-template/app/actions/consent.ts +57 -57
- package/templates/nextblock-template/app/actions/twoFactorEmail.ts +22 -22
- package/templates/nextblock-template/app/api/ai/cortex/build-widget/route.ts +153 -153
- package/templates/nextblock-template/app/api/ai/generate-blocks/route.ts +96 -96
- package/templates/nextblock-template/app/api/brand/email-logo/route.ts +48 -48
- package/templates/nextblock-template/app/api/checkout/freemius/sync/route.ts +29 -29
- package/templates/nextblock-template/app/api/cms/check-updates/route.ts +44 -44
- package/templates/nextblock-template/app/api/cms/full-backup/export/route.ts +33 -33
- package/templates/nextblock-template/app/api/cms/full-backup/restore/route.ts +63 -63
- package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +270 -205
- package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +8096 -7198
- package/templates/nextblock-template/app/api/cron/sync-currencies/route.ts +39 -39
- package/templates/nextblock-template/app/api/custom-blocks/db-relations/route.ts +92 -92
- package/templates/nextblock-template/app/api/custom-blocks/editor-definitions/route.ts +43 -43
- package/templates/nextblock-template/app/api/media/library/route.ts +69 -69
- package/templates/nextblock-template/app/api/media/r2-presigned/route.ts +53 -53
- package/templates/nextblock-template/app/api/visual-editing/block-draft/route.ts +47 -47
- package/templates/nextblock-template/app/api/visual-editing/product-draft/route.ts +47 -47
- package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +442 -441
- package/templates/nextblock-template/app/article/[slug]/page.utils.ts +4 -1
- package/templates/nextblock-template/app/checkout/UcpCartHydrator.tsx +20 -20
- package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +241 -241
- package/templates/nextblock-template/app/cms/blocks/components/CustomBlockEditorPreview.tsx +160 -160
- package/templates/nextblock-template/app/cms/blocks/editors/DynamicCustomBlockEditor.tsx +167 -167
- package/templates/nextblock-template/app/cms/components/ConnectGitHubButton.tsx +122 -122
- package/templates/nextblock-template/app/cms/components/CortexAiActiveContext.tsx +23 -23
- package/templates/nextblock-template/app/cms/components/SeoScoreBadge.tsx +3 -3
- package/templates/nextblock-template/app/cms/components/SystemAlertsBanner.tsx +112 -112
- package/templates/nextblock-template/app/cms/components/TablePagination.tsx +136 -136
- package/templates/nextblock-template/app/cms/components/TwoFactorReminderBanner.tsx +45 -45
- package/templates/nextblock-template/app/cms/components/github-connect-actions.ts +102 -102
- package/templates/nextblock-template/app/cms/components/system-alerts-actions.ts +31 -31
- package/templates/nextblock-template/app/cms/coupons/[id]/edit/page.tsx +16 -16
- package/templates/nextblock-template/app/cms/coupons/page.tsx +16 -16
- package/templates/nextblock-template/app/cms/custom-blocks/[id]/edit/page.tsx +66 -66
- package/templates/nextblock-template/app/cms/custom-blocks/actions.ts +519 -519
- package/templates/nextblock-template/app/cms/custom-blocks/components/BlocksLibraryTransferControls.tsx +256 -256
- package/templates/nextblock-template/app/cms/custom-blocks/components/DBRelationSelect.tsx +384 -384
- package/templates/nextblock-template/app/cms/custom-blocks/components/ImageR2Picker.tsx +221 -221
- package/templates/nextblock-template/app/cms/custom-blocks/new/page.tsx +12 -12
- package/templates/nextblock-template/app/cms/custom-blocks/page.tsx +438 -438
- package/templates/nextblock-template/app/cms/dashboard/components/DashboardComponents.tsx +200 -200
- package/templates/nextblock-template/app/cms/dashboard/components/DashboardOnboarding.tsx +130 -130
- package/templates/nextblock-template/app/cms/import-export/actions.ts +226 -226
- package/templates/nextblock-template/app/cms/pages/page.tsx +273 -273
- package/templates/nextblock-template/app/cms/posts/page.tsx +251 -251
- package/templates/nextblock-template/app/cms/products/categories/page.tsx +12 -12
- package/templates/nextblock-template/app/cms/products/new/page.tsx +135 -135
- package/templates/nextblock-template/app/cms/promotions/PromotionsWorkspace.tsx +456 -456
- package/templates/nextblock-template/app/cms/promotions/actions.ts +115 -115
- package/templates/nextblock-template/app/cms/promotions/page.tsx +31 -31
- package/templates/nextblock-template/app/cms/settings/backup-restore/BackupRestoreWorkspace.tsx +1004 -1004
- package/templates/nextblock-template/app/cms/settings/backup-restore/page.tsx +29 -29
- package/templates/nextblock-template/app/cms/settings/bot-protection/page.tsx +24 -24
- package/templates/nextblock-template/app/cms/settings/email/page.tsx +28 -28
- package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +276 -276
- package/templates/nextblock-template/app/cms/settings/google-analytics/page.tsx +26 -26
- package/templates/nextblock-template/app/cms/settings/logos/components/DeleteLogoButton.tsx +21 -21
- package/templates/nextblock-template/app/cms/settings/logos/components/SetActiveLogoButton.tsx +42 -42
- package/templates/nextblock-template/app/cms/settings/logos/components/SiteSeoSettingsForm.tsx +133 -133
- package/templates/nextblock-template/app/cms/settings/privacy/page.tsx +27 -27
- package/templates/nextblock-template/app/cms/settings/registration/page.tsx +27 -27
- package/templates/nextblock-template/app/cms/settings/security/page.tsx +33 -33
- package/templates/nextblock-template/app/lib/site-settings.ts +105 -105
- package/templates/nextblock-template/app/lib/ucp/protocol.ts +190 -190
- package/templates/nextblock-template/app/lib/ucp/server.test.ts +56 -56
- package/templates/nextblock-template/app/setup/SetupWizard.tsx +678 -678
- package/templates/nextblock-template/app/setup/layout.tsx +13 -13
- package/templates/nextblock-template/app/setup/page.tsx +111 -111
- package/templates/nextblock-template/app/ucp/v1/carts/[id]/cancel/route.ts +38 -38
- package/templates/nextblock-template/app/ucp/v1/carts/[id]/route.ts +68 -68
- package/templates/nextblock-template/app/ucp/v1/carts/route.ts +35 -35
- package/templates/nextblock-template/app/ucp/v1/catalog/lookup/route.ts +35 -35
- package/templates/nextblock-template/app/ucp/v1/catalog/product/route.ts +35 -35
- package/templates/nextblock-template/app/ucp/v1/catalog/search/route.ts +34 -34
- package/templates/nextblock-template/components/CartTranslator.tsx +210 -210
- package/templates/nextblock-template/components/DeferredCartTranslator.tsx +51 -51
- package/templates/nextblock-template/components/DeferredGlobalSearch.tsx +68 -68
- package/templates/nextblock-template/components/DeferredGoogleAnalytics.tsx +70 -70
- package/templates/nextblock-template/components/FeatureImageHero.tsx +47 -47
- package/templates/nextblock-template/components/GlobalSearch.tsx +557 -557
- package/templates/nextblock-template/components/Header.tsx +38 -38
- package/templates/nextblock-template/components/PublicEnvBootstrap.tsx +30 -30
- package/templates/nextblock-template/components/ResponsiveNav.tsx +14 -14
- package/templates/nextblock-template/components/auth/AuthBotProtection.tsx +182 -182
- package/templates/nextblock-template/components/blocks/PostsGridClient.tsx +48 -48
- package/templates/nextblock-template/components/blocks/TestimonialBlock.tsx +9 -9
- package/templates/nextblock-template/components/blocks/publicRendererLoaders.ts +25 -25
- package/templates/nextblock-template/components/blocks/renderers/ButtonBlockRenderer.tsx +92 -92
- package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +11 -0
- package/templates/nextblock-template/components/blocks/renderers/PostsGridBlockRenderer.tsx +24 -24
- package/templates/nextblock-template/components/blocks/renderers/TestimonialBlockRenderer.tsx +57 -57
- package/templates/nextblock-template/components/blocks/renderers/inline/AlertWidgetRenderer.tsx +2 -2
- package/templates/nextblock-template/components/blocks/renderers/inline/CtaWidgetRenderer.tsx +2 -2
- package/templates/nextblock-template/components/media/YouTubeFacade.tsx +105 -105
- package/templates/nextblock-template/components/media/youtube-embed-replace.tsx +32 -32
- package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +170 -170
- package/templates/nextblock-template/components/privacy/ConsentGatedAnalytics.tsx +70 -70
- package/templates/nextblock-template/components/renderers/CachedDynamicLayoutEngine.tsx +28 -28
- package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.test.tsx +166 -166
- package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.tsx +471 -471
- package/templates/nextblock-template/components/visual-editing/DeferredVisualEditing.tsx +21 -21
- package/templates/nextblock-template/context/language-rest-client.ts +32 -32
- package/templates/nextblock-template/docker/db/init/99-jwt.sql +6 -6
- package/templates/nextblock-template/docker/db/init/99-roles.sql +25 -25
- package/templates/nextblock-template/docker/kong/kong.yml +112 -112
- package/templates/nextblock-template/docs/02-ECOMMERCE-CAPABILITIES.md +364 -364
- package/templates/nextblock-template/docs/04-DATABASE-AND-AUTH.md +11 -0
- package/templates/nextblock-template/docs/07-BLOCK-SDK-AND-EXTENSIBILITY.md +146 -146
- package/templates/nextblock-template/docs/08-NEXTBLOCK-CORTEX-AI-ARCHITECTURE.md +19 -0
- package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +222 -222
- package/templates/nextblock-template/lib/app-secrets.ts +39 -39
- package/templates/nextblock-template/lib/auth/cookies.ts +47 -47
- package/templates/nextblock-template/lib/auth/crypto.ts +45 -45
- package/templates/nextblock-template/lib/auth/trustedDevices.ts +92 -92
- package/templates/nextblock-template/lib/blocks/README.md +13 -13
- package/templates/nextblock-template/lib/botProtection/verify.ts +134 -134
- package/templates/nextblock-template/lib/cms/payments-reminder.test.ts +135 -135
- package/templates/nextblock-template/lib/cms/payments-reminder.ts +105 -105
- package/templates/nextblock-template/lib/cms-transfer/types.ts +145 -145
- package/templates/nextblock-template/lib/custom-block-definitions.ts +87 -87
- package/templates/nextblock-template/lib/custom-block-r2-upload-shared.ts +178 -178
- package/templates/nextblock-template/lib/custom-block-r2-upload.test.ts +140 -140
- package/templates/nextblock-template/lib/custom-block-r2-upload.ts +88 -88
- package/templates/nextblock-template/lib/custom-block-relations.test.ts +227 -227
- package/templates/nextblock-template/lib/custom-block-relations.ts +279 -279
- package/templates/nextblock-template/lib/custom-block-safelist.ts +14 -14
- package/templates/nextblock-template/lib/editor/dynamic-extension-core.test.ts +172 -172
- package/templates/nextblock-template/lib/editor/dynamic-extension-core.ts +213 -213
- package/templates/nextblock-template/lib/editor/dynamic-extension-loader.ts +22 -22
- package/templates/nextblock-template/lib/editor/dynamic-extensions.tsx +193 -193
- package/templates/nextblock-template/lib/email/branding-format.test.ts +133 -133
- package/templates/nextblock-template/lib/email/branding-format.ts +123 -123
- package/templates/nextblock-template/lib/email/branding.ts +76 -76
- package/templates/nextblock-template/lib/full-backup/manifest.test.ts +121 -121
- package/templates/nextblock-template/lib/full-backup/manifest.ts +206 -206
- package/templates/nextblock-template/lib/logos/active-logo.ts +53 -53
- package/templates/nextblock-template/lib/media/resolveMediaUrl.ts +56 -54
- package/templates/nextblock-template/lib/media/youtube.ts +99 -99
- package/templates/nextblock-template/lib/onboarding/actions.ts +31 -31
- package/templates/nextblock-template/lib/privacy/consent-client.ts +57 -57
- package/templates/nextblock-template/lib/privacy/contact-emails.ts +64 -64
- package/templates/nextblock-template/lib/privacy/settings.ts +115 -115
- package/templates/nextblock-template/lib/privacy/types.ts +69 -69
- package/templates/nextblock-template/lib/promotions/server.test.ts +74 -74
- package/templates/nextblock-template/lib/promotions/server.ts +741 -741
- package/templates/nextblock-template/lib/resolve-block-relations.test.ts +142 -142
- package/templates/nextblock-template/lib/resolve-block-relations.ts +255 -255
- package/templates/nextblock-template/lib/seo/page-document.ts +4 -4
- package/templates/nextblock-template/lib/setup/actions.ts +460 -460
- package/templates/nextblock-template/lib/setup/env-status.ts +125 -125
- package/templates/nextblock-template/lib/setup/env-write.ts +111 -111
- package/templates/nextblock-template/lib/setup/migrations-bundle.ts +42 -12
- package/templates/nextblock-template/lib/setup/provisioning.ts +59 -59
- package/templates/nextblock-template/lib/setup/schema-apply.ts +408 -408
- package/templates/nextblock-template/lib/setup/system-config.ts +105 -105
- package/templates/nextblock-template/lib/setup/types.ts +18 -18
- package/templates/nextblock-template/lib/storage/provider.ts +66 -66
- package/templates/nextblock-template/lib/storage/supabase-storage.ts +103 -103
- package/templates/nextblock-template/lib/updates/github-device.ts +206 -206
- package/templates/nextblock-template/lib/updates/repo-identity.ts +56 -56
- package/templates/nextblock-template/lib/visual-editing/draft-content.test.ts +105 -105
- package/templates/nextblock-template/lib/visual-editing/draft-route.test.ts +42 -42
- package/templates/nextblock-template/lib/visual-editing/edit-info.test.ts +143 -143
- package/templates/nextblock-template/lib/visual-editing/edit-info.ts +94 -94
- package/templates/nextblock-template/lib/visual-editing/product-drafts.test.ts +81 -81
- package/templates/nextblock-template/lib/zod-config.ts +5 -5
- package/templates/nextblock-template/next-env.d.ts +1 -0
- package/templates/nextblock-template/package.json +1 -1
- package/templates/nextblock-template/public/images/cortex_post.webp +0 -0
- package/templates/nextblock-template/public/images/update_nextblock.webp +0 -0
- package/templates/nextblock-template/scripts/docker-setup.mjs +310 -310
- package/templates/nextblock-template/scripts/validate-editor-block-schema.ts +112 -112
- package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +98 -98
- package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +62 -62
- package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +537 -537
- package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +58 -58
- package/templates/nextblock-template/scripts/verify-custom-block-definitions.ts +188 -188
- package/templates/nextblock-template/scripts/verify-dynamic-custom-block-extensions.ts +123 -123
- package/templates/nextblock-template/scripts/verify-dynamic-layout-engine.tsx +133 -133
- package/templates/nextblock-template/scripts/verify-milestone-2-custom-blocks.ts +65 -65
- package/templates/nextblock-template/tools/deploy-supabase.js +159 -159
- package/templates/nextblock-template/types/jsdom.d.ts +6 -6
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// components/blocks/PostsGridClient.tsx
|
|
2
2
|
'use client';
|
|
3
3
|
|
|
4
|
-
import React, { useState, useEffect } from 'react';
|
|
5
|
-
import Link from 'next/link';
|
|
6
|
-
import { useLanguage } from '../../context/LanguageContext';
|
|
7
|
-
import type { PostWithMediaDimensions } from './types';
|
|
8
|
-
import Image from 'next/image';
|
|
9
|
-
import { Button } from '@nextblock-cms/ui';
|
|
10
|
-
import PostCardSkeleton from './PostCardSkeleton'; // Added import
|
|
4
|
+
import React, { useState, useEffect } from 'react';
|
|
5
|
+
import Link from 'next/link';
|
|
6
|
+
import { useLanguage } from '../../context/LanguageContext';
|
|
7
|
+
import type { PostWithMediaDimensions } from './types';
|
|
8
|
+
import Image from 'next/image';
|
|
9
|
+
import { Button } from '@nextblock-cms/ui';
|
|
10
|
+
import PostCardSkeleton from './PostCardSkeleton'; // Added import
|
|
11
11
|
|
|
12
12
|
interface PostsGridClientProps {
|
|
13
13
|
initialPosts: PostWithMediaDimensions[];
|
|
@@ -20,10 +20,10 @@ interface PostsGridClientProps {
|
|
|
20
20
|
fetchAction: (languageId: number, page: number, limit: number) => Promise<{ posts: PostWithMediaDimensions[], totalCount: number, error?: string }>;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
const DEFAULT_FEATURE_IMAGE_WIDTH = 1600;
|
|
24
|
-
const DEFAULT_FEATURE_IMAGE_HEIGHT = 900;
|
|
25
|
-
|
|
26
|
-
const PostsGridClient: React.FC<PostsGridClientProps> = ({
|
|
23
|
+
const DEFAULT_FEATURE_IMAGE_WIDTH = 1600;
|
|
24
|
+
const DEFAULT_FEATURE_IMAGE_HEIGHT = 900;
|
|
25
|
+
|
|
26
|
+
const PostsGridClient: React.FC<PostsGridClientProps> = ({
|
|
27
27
|
initialPosts,
|
|
28
28
|
initialPage,
|
|
29
29
|
postsPerPage,
|
|
@@ -32,10 +32,10 @@ const PostsGridClient: React.FC<PostsGridClientProps> = ({
|
|
|
32
32
|
languageId,
|
|
33
33
|
showPagination,
|
|
34
34
|
fetchAction,
|
|
35
|
-
}) => {
|
|
36
|
-
const { currentLocale } = useLanguage();
|
|
37
|
-
const [currentPage, setCurrentPage] = useState(initialPage);
|
|
38
|
-
const [posts, setPosts] = useState<PostWithMediaDimensions[]>(initialPosts);
|
|
35
|
+
}) => {
|
|
36
|
+
const { currentLocale } = useLanguage();
|
|
37
|
+
const [currentPage, setCurrentPage] = useState(initialPage);
|
|
38
|
+
const [posts, setPosts] = useState<PostWithMediaDimensions[]>(initialPosts);
|
|
39
39
|
const [isLoading, setIsLoading] = useState(false);
|
|
40
40
|
const [error, setError] = useState<string | null>(null);
|
|
41
41
|
// Initialize skeletonCount to postsPerPage, or a sensible minimum if initialPosts is empty.
|
|
@@ -112,40 +112,40 @@ const PostsGridClient: React.FC<PostsGridClientProps> = ({
|
|
|
112
112
|
<PostCardSkeleton key={`skeleton-${index}`} />
|
|
113
113
|
))
|
|
114
114
|
) : posts.length > 0 ? (
|
|
115
|
-
posts.map((post) => (
|
|
116
|
-
<Link href={`/article/${post.slug}`} key={post.id} className="block group h-full">
|
|
115
|
+
posts.map((post) => (
|
|
116
|
+
<Link href={`/article/${post.slug}`} key={post.id} className="block group h-full">
|
|
117
117
|
<div className="border rounded-lg overflow-hidden shadow-sm hover:shadow-md transition-shadow bg-card text-card-foreground h-full flex flex-col">
|
|
118
118
|
{/* Basic Post Card Structure - Enhanced with Feature Image */}
|
|
119
|
-
{post.feature_image_url ? (
|
|
120
|
-
<div className="aspect-video overflow-hidden">
|
|
121
|
-
<Image
|
|
122
|
-
src={post.feature_image_url}
|
|
123
|
-
alt={`Feature image for ${post.title}`}
|
|
124
|
-
width={post.feature_image_width && post.feature_image_width > 0 ? post.feature_image_width : DEFAULT_FEATURE_IMAGE_WIDTH}
|
|
125
|
-
height={post.feature_image_height && post.feature_image_height > 0 ? post.feature_image_height : DEFAULT_FEATURE_IMAGE_HEIGHT}
|
|
126
|
-
sizes={imageSizes}
|
|
127
|
-
loading="lazy"
|
|
128
|
-
placeholder={post.blur_data_url ? 'blur' : 'empty'}
|
|
129
|
-
blurDataURL={post.blur_data_url ?? undefined}
|
|
130
|
-
quality={60}
|
|
131
|
-
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
|
|
132
|
-
/>
|
|
133
|
-
</div>
|
|
134
|
-
) : null}
|
|
135
|
-
<div className="p-4 flex flex-1 flex-col">
|
|
136
|
-
<div className="mb-3 flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
|
|
137
|
-
<span className="inline-flex items-center rounded-full border border-slate-200 bg-slate-50 px-2.5 py-1 font-semibold text-slate-700">
|
|
138
|
-
{post.label?.trim() || 'Article'}
|
|
139
|
-
</span>
|
|
140
|
-
<span>
|
|
141
|
-
{post.estimated_read_time_minutes} {currentLocale === 'fr' ? 'min de lecture' : 'min read'}
|
|
142
|
-
</span>
|
|
143
|
-
</div>
|
|
144
|
-
<h3 className="text-lg font-semibold mb-2 group-hover:text-primary">{post.title}</h3>
|
|
145
|
-
{post.excerpt && <p className="text-sm text-muted-foreground mb-3 line-clamp-3">{post.excerpt}</p>}
|
|
146
|
-
<div className="mt-auto pt-2">
|
|
147
|
-
<span className="text-xs text-primary group-hover:underline">Read more</span>
|
|
148
|
-
</div>
|
|
119
|
+
{post.feature_image_url ? (
|
|
120
|
+
<div className="aspect-video overflow-hidden">
|
|
121
|
+
<Image
|
|
122
|
+
src={post.feature_image_url}
|
|
123
|
+
alt={`Feature image for ${post.title}`}
|
|
124
|
+
width={post.feature_image_width && post.feature_image_width > 0 ? post.feature_image_width : DEFAULT_FEATURE_IMAGE_WIDTH}
|
|
125
|
+
height={post.feature_image_height && post.feature_image_height > 0 ? post.feature_image_height : DEFAULT_FEATURE_IMAGE_HEIGHT}
|
|
126
|
+
sizes={imageSizes}
|
|
127
|
+
loading="lazy"
|
|
128
|
+
placeholder={post.blur_data_url ? 'blur' : 'empty'}
|
|
129
|
+
blurDataURL={post.blur_data_url ?? undefined}
|
|
130
|
+
quality={60}
|
|
131
|
+
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
|
|
132
|
+
/>
|
|
133
|
+
</div>
|
|
134
|
+
) : null}
|
|
135
|
+
<div className="p-4 flex flex-1 flex-col">
|
|
136
|
+
<div className="mb-3 flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
|
|
137
|
+
<span className="inline-flex items-center rounded-full border border-slate-200 bg-slate-50 px-2.5 py-1 font-semibold text-slate-700">
|
|
138
|
+
{post.label?.trim() || 'Article'}
|
|
139
|
+
</span>
|
|
140
|
+
<span>
|
|
141
|
+
{post.estimated_read_time_minutes} {currentLocale === 'fr' ? 'min de lecture' : 'min read'}
|
|
142
|
+
</span>
|
|
143
|
+
</div>
|
|
144
|
+
<h3 className="text-lg font-semibold mb-2 group-hover:text-primary">{post.title}</h3>
|
|
145
|
+
{post.excerpt && <p className="text-sm text-muted-foreground mb-3 line-clamp-3">{post.excerpt}</p>}
|
|
146
|
+
<div className="mt-auto pt-2">
|
|
147
|
+
<span className="text-xs text-primary group-hover:underline">Read more</span>
|
|
148
|
+
</div>
|
|
149
149
|
</div>
|
|
150
150
|
</div>
|
|
151
151
|
</Link>
|
|
@@ -181,4 +181,4 @@ const PostsGridClient: React.FC<PostsGridClientProps> = ({
|
|
|
181
181
|
);
|
|
182
182
|
};
|
|
183
183
|
|
|
184
|
-
export default PostsGridClient;
|
|
184
|
+
export default PostsGridClient;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { z } from '../../lib/zod-config';
|
|
3
|
-
import { BlockConfig, BlockProps, BlockEditorProps } from '@nextblock-cms/sdk';
|
|
4
|
-
import { Avatar, AvatarFallback, AvatarImage } from '@nextblock-cms/ui';
|
|
5
|
-
import { Card, CardContent } from '@nextblock-cms/ui';
|
|
6
|
-
import { Input } from '@nextblock-cms/ui';
|
|
7
|
-
import { Label } from '@nextblock-cms/ui';
|
|
8
|
-
import { Textarea } from '@nextblock-cms/ui';
|
|
9
|
-
import { MessageSquareQuote } from 'lucide-react';
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { z } from '../../lib/zod-config';
|
|
3
|
+
import { BlockConfig, BlockProps, BlockEditorProps } from '@nextblock-cms/sdk';
|
|
4
|
+
import { Avatar, AvatarFallback, AvatarImage } from '@nextblock-cms/ui';
|
|
5
|
+
import { Card, CardContent } from '@nextblock-cms/ui';
|
|
6
|
+
import { Input } from '@nextblock-cms/ui';
|
|
7
|
+
import { Label } from '@nextblock-cms/ui';
|
|
8
|
+
import { Textarea } from '@nextblock-cms/ui';
|
|
9
|
+
import { MessageSquareQuote } from 'lucide-react';
|
|
10
10
|
|
|
11
11
|
// 1. Define the Schema
|
|
12
12
|
export const TestimonialSchema = z.object({
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import type { ComponentType } from "react";
|
|
2
|
-
import type { BlockType } from "../../lib/blocks/blockRegistry";
|
|
3
|
-
|
|
4
|
-
type PublicBlockRendererLoader = () => Promise<{
|
|
5
|
-
default: ComponentType<any>;
|
|
6
|
-
}>;
|
|
7
|
-
|
|
8
|
-
const publicBlockRendererLoaders: Partial<
|
|
9
|
-
Record<BlockType | "hero", PublicBlockRendererLoader>
|
|
10
|
-
> = {
|
|
11
|
-
text: () => import("./renderers/TextBlockRenderer"),
|
|
12
|
-
heading: () => import("./renderers/HeadingBlockRenderer"),
|
|
13
|
-
image: () => import("./renderers/ImageBlockRenderer"),
|
|
14
|
-
button: () => import("./renderers/ButtonBlockRenderer"),
|
|
15
|
-
posts_grid: () => import("./renderers/PostsGridBlockRenderer"),
|
|
16
|
-
video_embed: () => import("./renderers/VideoEmbedBlockRenderer"),
|
|
17
|
-
section: () => import("./renderers/SectionBlockRenderer"),
|
|
18
|
-
hero: () => import("./renderers/SectionBlockRenderer"),
|
|
19
|
-
form: () => import("./renderers/FormBlockRenderer"),
|
|
20
|
-
testimonial: () => import("./renderers/TestimonialBlockRenderer"),
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export function getPublicBlockRendererLoader(blockType: string) {
|
|
24
|
-
return publicBlockRendererLoaders[blockType as BlockType | "hero"];
|
|
25
|
-
}
|
|
1
|
+
import type { ComponentType } from "react";
|
|
2
|
+
import type { BlockType } from "../../lib/blocks/blockRegistry";
|
|
3
|
+
|
|
4
|
+
type PublicBlockRendererLoader = () => Promise<{
|
|
5
|
+
default: ComponentType<any>;
|
|
6
|
+
}>;
|
|
7
|
+
|
|
8
|
+
const publicBlockRendererLoaders: Partial<
|
|
9
|
+
Record<BlockType | "hero", PublicBlockRendererLoader>
|
|
10
|
+
> = {
|
|
11
|
+
text: () => import("./renderers/TextBlockRenderer"),
|
|
12
|
+
heading: () => import("./renderers/HeadingBlockRenderer"),
|
|
13
|
+
image: () => import("./renderers/ImageBlockRenderer"),
|
|
14
|
+
button: () => import("./renderers/ButtonBlockRenderer"),
|
|
15
|
+
posts_grid: () => import("./renderers/PostsGridBlockRenderer"),
|
|
16
|
+
video_embed: () => import("./renderers/VideoEmbedBlockRenderer"),
|
|
17
|
+
section: () => import("./renderers/SectionBlockRenderer"),
|
|
18
|
+
hero: () => import("./renderers/SectionBlockRenderer"),
|
|
19
|
+
form: () => import("./renderers/FormBlockRenderer"),
|
|
20
|
+
testimonial: () => import("./renderers/TestimonialBlockRenderer"),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function getPublicBlockRendererLoader(blockType: string) {
|
|
24
|
+
return publicBlockRendererLoaders[blockType as BlockType | "hero"];
|
|
25
|
+
}
|
|
@@ -1,92 +1,92 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import React from "react";
|
|
3
|
-
import Link from "next/link";
|
|
4
|
-
import { buttonVariants } from "@nextblock-cms/ui";
|
|
5
|
-
import { cn } from "@nextblock-cms/utils";
|
|
6
|
-
import type { VisualEditAttributes } from "../../../lib/visual-editing/types";
|
|
7
|
-
|
|
8
|
-
export type ButtonBlockContent = {
|
|
9
|
-
text?: string;
|
|
10
|
-
url?: string;
|
|
11
|
-
variant?: 'default' | 'outline' | 'secondary' | 'ghost' | 'link';
|
|
12
|
-
size?: 'default' | 'sm' | 'lg' | 'full';
|
|
13
|
-
position?: 'left' | 'center' | 'right';
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
interface ButtonBlockRendererProps {
|
|
17
|
-
content: ButtonBlockContent;
|
|
18
|
-
languageId: number; // This prop seems unused
|
|
19
|
-
visualEditAttributes?: VisualEditAttributes;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const ButtonBlockRenderer: React.FC<ButtonBlockRendererProps> = ({
|
|
23
|
-
content,
|
|
24
|
-
visualEditAttributes,
|
|
25
|
-
}) => {
|
|
26
|
-
const isExternal =
|
|
27
|
-
content.url?.startsWith("http") ||
|
|
28
|
-
content.url?.startsWith("mailto:") ||
|
|
29
|
-
content.url?.startsWith("tel:");
|
|
30
|
-
const isAnchor = content.url?.startsWith("#");
|
|
31
|
-
|
|
32
|
-
const buttonText = content.text || "Button";
|
|
33
|
-
// Map variant name if needed or pass directly if it matches
|
|
34
|
-
// The migration might have uppercase 'DEFAULT' or 'OUTLINE', ensure lowercase
|
|
35
|
-
const variantRaw = content.variant?.toLowerCase() || "default";
|
|
36
|
-
// Cast to specific allowed variants for TS safety if strictly typed, else as any
|
|
37
|
-
const variant = (variantRaw === 'default' || variantRaw === 'outline' || variantRaw === 'secondary' || variantRaw === 'ghost' || variantRaw === 'link' || variantRaw === 'destructive')
|
|
38
|
-
? variantRaw
|
|
39
|
-
: 'default';
|
|
40
|
-
|
|
41
|
-
const sizeRaw = content.size?.toLowerCase() || "default";
|
|
42
|
-
const size = (sizeRaw === 'default' || sizeRaw === 'sm' || sizeRaw === 'lg' || sizeRaw === 'icon')
|
|
43
|
-
? sizeRaw
|
|
44
|
-
: 'default';
|
|
45
|
-
|
|
46
|
-
const buttonPosition = content.position || "left";
|
|
47
|
-
|
|
48
|
-
const alignmentClasses = {
|
|
49
|
-
left: "justify-start text-left",
|
|
50
|
-
center: "justify-center text-center",
|
|
51
|
-
right: "justify-end text-right",
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
// We use buttonVariants directly to avoid 'asChild' composition issues with Radix Slot
|
|
55
|
-
// This ensures we just render a clean Link or Anchor with the correct classes.
|
|
56
|
-
const classes = cn(
|
|
57
|
-
buttonVariants({ variant, size }),
|
|
58
|
-
variant === 'outline' && "text-foreground", // Ensure outline text is visible
|
|
59
|
-
"no-underline" // Force no-underline for links
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
return (
|
|
63
|
-
<div
|
|
64
|
-
className={cn("my-6 flex w-full", alignmentClasses[buttonPosition])}
|
|
65
|
-
{...visualEditAttributes}
|
|
66
|
-
>
|
|
67
|
-
{/* Case 1: Internal link */}
|
|
68
|
-
{!isExternal && !isAnchor && !!content.url ? (
|
|
69
|
-
<Link href={content.url} className={classes}>
|
|
70
|
-
{buttonText}
|
|
71
|
-
</Link>
|
|
72
|
-
) : /* Case 2: External or Anchor link */
|
|
73
|
-
(isExternal || isAnchor) && !!content.url ? (
|
|
74
|
-
<a
|
|
75
|
-
href={content.url}
|
|
76
|
-
target={isExternal ? "_blank" : undefined}
|
|
77
|
-
rel={isExternal ? "noopener noreferrer" : undefined}
|
|
78
|
-
className={classes}
|
|
79
|
-
>
|
|
80
|
-
{buttonText}
|
|
81
|
-
</a>
|
|
82
|
-
) : (
|
|
83
|
-
/* Case 3: No URL - render a disabled fake button */
|
|
84
|
-
<button disabled className={cn(classes, "opacity-50 cursor-not-allowed")}>
|
|
85
|
-
{buttonText}
|
|
86
|
-
</button>
|
|
87
|
-
)}
|
|
88
|
-
</div>
|
|
89
|
-
);
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
export default ButtonBlockRenderer;
|
|
1
|
+
"use client";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import { buttonVariants } from "@nextblock-cms/ui";
|
|
5
|
+
import { cn } from "@nextblock-cms/utils";
|
|
6
|
+
import type { VisualEditAttributes } from "../../../lib/visual-editing/types";
|
|
7
|
+
|
|
8
|
+
export type ButtonBlockContent = {
|
|
9
|
+
text?: string;
|
|
10
|
+
url?: string;
|
|
11
|
+
variant?: 'default' | 'outline' | 'secondary' | 'ghost' | 'link';
|
|
12
|
+
size?: 'default' | 'sm' | 'lg' | 'full';
|
|
13
|
+
position?: 'left' | 'center' | 'right';
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
interface ButtonBlockRendererProps {
|
|
17
|
+
content: ButtonBlockContent;
|
|
18
|
+
languageId: number; // This prop seems unused
|
|
19
|
+
visualEditAttributes?: VisualEditAttributes;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const ButtonBlockRenderer: React.FC<ButtonBlockRendererProps> = ({
|
|
23
|
+
content,
|
|
24
|
+
visualEditAttributes,
|
|
25
|
+
}) => {
|
|
26
|
+
const isExternal =
|
|
27
|
+
content.url?.startsWith("http") ||
|
|
28
|
+
content.url?.startsWith("mailto:") ||
|
|
29
|
+
content.url?.startsWith("tel:");
|
|
30
|
+
const isAnchor = content.url?.startsWith("#");
|
|
31
|
+
|
|
32
|
+
const buttonText = content.text || "Button";
|
|
33
|
+
// Map variant name if needed or pass directly if it matches
|
|
34
|
+
// The migration might have uppercase 'DEFAULT' or 'OUTLINE', ensure lowercase
|
|
35
|
+
const variantRaw = content.variant?.toLowerCase() || "default";
|
|
36
|
+
// Cast to specific allowed variants for TS safety if strictly typed, else as any
|
|
37
|
+
const variant = (variantRaw === 'default' || variantRaw === 'outline' || variantRaw === 'secondary' || variantRaw === 'ghost' || variantRaw === 'link' || variantRaw === 'destructive')
|
|
38
|
+
? variantRaw
|
|
39
|
+
: 'default';
|
|
40
|
+
|
|
41
|
+
const sizeRaw = content.size?.toLowerCase() || "default";
|
|
42
|
+
const size = (sizeRaw === 'default' || sizeRaw === 'sm' || sizeRaw === 'lg' || sizeRaw === 'icon')
|
|
43
|
+
? sizeRaw
|
|
44
|
+
: 'default';
|
|
45
|
+
|
|
46
|
+
const buttonPosition = content.position || "left";
|
|
47
|
+
|
|
48
|
+
const alignmentClasses = {
|
|
49
|
+
left: "justify-start text-left",
|
|
50
|
+
center: "justify-center text-center",
|
|
51
|
+
right: "justify-end text-right",
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// We use buttonVariants directly to avoid 'asChild' composition issues with Radix Slot
|
|
55
|
+
// This ensures we just render a clean Link or Anchor with the correct classes.
|
|
56
|
+
const classes = cn(
|
|
57
|
+
buttonVariants({ variant, size }),
|
|
58
|
+
variant === 'outline' && "text-foreground", // Ensure outline text is visible
|
|
59
|
+
"no-underline" // Force no-underline for links
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div
|
|
64
|
+
className={cn("my-6 flex w-full", alignmentClasses[buttonPosition])}
|
|
65
|
+
{...visualEditAttributes}
|
|
66
|
+
>
|
|
67
|
+
{/* Case 1: Internal link */}
|
|
68
|
+
{!isExternal && !isAnchor && !!content.url ? (
|
|
69
|
+
<Link href={content.url} className={classes}>
|
|
70
|
+
{buttonText}
|
|
71
|
+
</Link>
|
|
72
|
+
) : /* Case 2: External or Anchor link */
|
|
73
|
+
(isExternal || isAnchor) && !!content.url ? (
|
|
74
|
+
<a
|
|
75
|
+
href={content.url}
|
|
76
|
+
target={isExternal ? "_blank" : undefined}
|
|
77
|
+
rel={isExternal ? "noopener noreferrer" : undefined}
|
|
78
|
+
className={classes}
|
|
79
|
+
>
|
|
80
|
+
{buttonText}
|
|
81
|
+
</a>
|
|
82
|
+
) : (
|
|
83
|
+
/* Case 3: No URL - render a disabled fake button */
|
|
84
|
+
<button disabled className={cn(classes, "opacity-50 cursor-not-allowed")}>
|
|
85
|
+
{buttonText}
|
|
86
|
+
</button>
|
|
87
|
+
)}
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export default ButtonBlockRenderer;
|
package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx
CHANGED
|
@@ -51,6 +51,16 @@ type CmsImageMetadata = {
|
|
|
51
51
|
height: number;
|
|
52
52
|
sizes: string;
|
|
53
53
|
priority?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Serve the bundled file as-is instead of through the image optimizer.
|
|
56
|
+
*
|
|
57
|
+
* The optimizer re-encodes at `quality` 60 and prefers AVIF, which is the right
|
|
58
|
+
* trade for photos but smears the fine text and 1px strokes of a rendered
|
|
59
|
+
* diagram, and its `sizes`-driven variant is then upscaled to the figure width.
|
|
60
|
+
* A diagram that is already an optimized webp at 2x the widest figure is
|
|
61
|
+
* sharper and not much heavier served untouched.
|
|
62
|
+
*/
|
|
63
|
+
unoptimized?: boolean;
|
|
54
64
|
};
|
|
55
65
|
|
|
56
66
|
type HtmlImageProps = {
|
|
@@ -215,6 +225,7 @@ function renderOptimizedCmsImage(attribs: Record<string, string>) {
|
|
|
215
225
|
height={image.height}
|
|
216
226
|
sizes={sizes}
|
|
217
227
|
quality={60}
|
|
228
|
+
unoptimized={image.unoptimized === true}
|
|
218
229
|
priority={priority}
|
|
219
230
|
fetchPriority={priority ? 'high' : undefined}
|
|
220
231
|
loading={priority ? undefined : 'lazy'}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import PostsGridBlock from "../../blocks/PostsGridBlock";
|
|
3
|
-
import type { Database } from "@nextblock-cms/db";
|
|
4
|
-
import type { VisualEditAttributes } from "../../../lib/visual-editing/types";
|
|
1
|
+
import React from "react";
|
|
2
|
+
import PostsGridBlock from "../../blocks/PostsGridBlock";
|
|
3
|
+
import type { Database } from "@nextblock-cms/db";
|
|
4
|
+
import type { VisualEditAttributes } from "../../../lib/visual-editing/types";
|
|
5
5
|
|
|
6
6
|
type Block = Database['public']['Tables']['blocks']['Row'];
|
|
7
7
|
type PostsGridBlockContent = {
|
|
@@ -12,27 +12,27 @@ type PostsGridBlockContent = {
|
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
interface PostsGridBlockRendererProps {
|
|
15
|
-
content: PostsGridBlockContent;
|
|
16
|
-
languageId: number;
|
|
17
|
-
block: Block;
|
|
18
|
-
visualEditAttributes?: VisualEditAttributes;
|
|
19
|
-
}
|
|
15
|
+
content: PostsGridBlockContent;
|
|
16
|
+
languageId: number;
|
|
17
|
+
block: Block;
|
|
18
|
+
visualEditAttributes?: VisualEditAttributes;
|
|
19
|
+
}
|
|
20
20
|
|
|
21
21
|
const PostsGridBlockRenderer: React.FC<PostsGridBlockRendererProps> = ({
|
|
22
|
-
content,
|
|
23
|
-
languageId,
|
|
24
|
-
block,
|
|
25
|
-
visualEditAttributes,
|
|
26
|
-
}) => {
|
|
27
|
-
void content;
|
|
28
|
-
return (
|
|
29
|
-
<div {...visualEditAttributes}>
|
|
30
|
-
<PostsGridBlock
|
|
31
|
-
block={block}
|
|
32
|
-
languageId={languageId}
|
|
33
|
-
/>
|
|
34
|
-
</div>
|
|
35
|
-
);
|
|
36
|
-
};
|
|
22
|
+
content,
|
|
23
|
+
languageId,
|
|
24
|
+
block,
|
|
25
|
+
visualEditAttributes,
|
|
26
|
+
}) => {
|
|
27
|
+
void content;
|
|
28
|
+
return (
|
|
29
|
+
<div {...visualEditAttributes}>
|
|
30
|
+
<PostsGridBlock
|
|
31
|
+
block={block}
|
|
32
|
+
languageId={languageId}
|
|
33
|
+
/>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
37
|
|
|
38
38
|
export default PostsGridBlockRenderer;
|
package/templates/nextblock-template/components/blocks/renderers/TestimonialBlockRenderer.tsx
CHANGED
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Avatar, AvatarFallback, AvatarImage } from "@nextblock-cms/ui";
|
|
3
|
-
import { Card, CardContent } from "@nextblock-cms/ui";
|
|
4
|
-
import { MessageSquareQuote } from "lucide-react";
|
|
5
|
-
import type { VisualEditAttributes } from "../../../lib/visual-editing/types";
|
|
6
|
-
|
|
7
|
-
type TestimonialBlockContent = {
|
|
8
|
-
quote?: string;
|
|
9
|
-
author_name?: string;
|
|
10
|
-
author_title?: string;
|
|
11
|
-
image_url?: string;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
interface TestimonialBlockRendererProps {
|
|
15
|
-
content: TestimonialBlockContent;
|
|
16
|
-
visualEditAttributes?: VisualEditAttributes;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const TestimonialBlockRenderer: React.FC<TestimonialBlockRendererProps> = ({
|
|
20
|
-
content,
|
|
21
|
-
visualEditAttributes,
|
|
22
|
-
}) => {
|
|
23
|
-
const authorName = content.author_name || "Customer";
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<div className="container m-8" {...visualEditAttributes}>
|
|
27
|
-
<Card className="h-full">
|
|
28
|
-
<CardContent className="pt-6 flex flex-col gap-4 h-full">
|
|
29
|
-
<MessageSquareQuote className="w-8 h-8 text-primary/40" />
|
|
30
|
-
{content.quote && (
|
|
31
|
-
<blockquote className="flex-grow text-lg italic text-muted-foreground">
|
|
32
|
-
"{content.quote}"
|
|
33
|
-
</blockquote>
|
|
34
|
-
)}
|
|
35
|
-
<div className="flex items-center gap-3 mt-4">
|
|
36
|
-
<Avatar>
|
|
37
|
-
{content.image_url && (
|
|
38
|
-
<AvatarImage src={content.image_url} alt={authorName} />
|
|
39
|
-
)}
|
|
40
|
-
<AvatarFallback>{authorName.slice(0, 2).toUpperCase()}</AvatarFallback>
|
|
41
|
-
</Avatar>
|
|
42
|
-
<div>
|
|
43
|
-
<div className="font-semibold">{authorName}</div>
|
|
44
|
-
{content.author_title && (
|
|
45
|
-
<div className="text-sm text-muted-foreground">
|
|
46
|
-
{content.author_title}
|
|
47
|
-
</div>
|
|
48
|
-
)}
|
|
49
|
-
</div>
|
|
50
|
-
</div>
|
|
51
|
-
</CardContent>
|
|
52
|
-
</Card>
|
|
53
|
-
</div>
|
|
54
|
-
);
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
export default TestimonialBlockRenderer;
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Avatar, AvatarFallback, AvatarImage } from "@nextblock-cms/ui";
|
|
3
|
+
import { Card, CardContent } from "@nextblock-cms/ui";
|
|
4
|
+
import { MessageSquareQuote } from "lucide-react";
|
|
5
|
+
import type { VisualEditAttributes } from "../../../lib/visual-editing/types";
|
|
6
|
+
|
|
7
|
+
type TestimonialBlockContent = {
|
|
8
|
+
quote?: string;
|
|
9
|
+
author_name?: string;
|
|
10
|
+
author_title?: string;
|
|
11
|
+
image_url?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
interface TestimonialBlockRendererProps {
|
|
15
|
+
content: TestimonialBlockContent;
|
|
16
|
+
visualEditAttributes?: VisualEditAttributes;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const TestimonialBlockRenderer: React.FC<TestimonialBlockRendererProps> = ({
|
|
20
|
+
content,
|
|
21
|
+
visualEditAttributes,
|
|
22
|
+
}) => {
|
|
23
|
+
const authorName = content.author_name || "Customer";
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className="container m-8" {...visualEditAttributes}>
|
|
27
|
+
<Card className="h-full">
|
|
28
|
+
<CardContent className="pt-6 flex flex-col gap-4 h-full">
|
|
29
|
+
<MessageSquareQuote className="w-8 h-8 text-primary/40" />
|
|
30
|
+
{content.quote && (
|
|
31
|
+
<blockquote className="flex-grow text-lg italic text-muted-foreground">
|
|
32
|
+
"{content.quote}"
|
|
33
|
+
</blockquote>
|
|
34
|
+
)}
|
|
35
|
+
<div className="flex items-center gap-3 mt-4">
|
|
36
|
+
<Avatar>
|
|
37
|
+
{content.image_url && (
|
|
38
|
+
<AvatarImage src={content.image_url} alt={authorName} />
|
|
39
|
+
)}
|
|
40
|
+
<AvatarFallback>{authorName.slice(0, 2).toUpperCase()}</AvatarFallback>
|
|
41
|
+
</Avatar>
|
|
42
|
+
<div>
|
|
43
|
+
<div className="font-semibold">{authorName}</div>
|
|
44
|
+
{content.author_title && (
|
|
45
|
+
<div className="text-sm text-muted-foreground">
|
|
46
|
+
{content.author_title}
|
|
47
|
+
</div>
|
|
48
|
+
)}
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</CardContent>
|
|
52
|
+
</Card>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export default TestimonialBlockRenderer;
|
package/templates/nextblock-template/components/blocks/renderers/inline/AlertWidgetRenderer.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
interface AlertWidgetRendererProps {
|
|
1
|
+
interface AlertWidgetRendererProps {
|
|
2
2
|
type: 'info' | 'warning' | 'notification' | 'danger';
|
|
3
3
|
title: string;
|
|
4
4
|
message: string;
|
|
@@ -46,4 +46,4 @@ const AlertWidgetRenderer = ({ type, title, message, align, size, textAlign }: A
|
|
|
46
46
|
);
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
-
export default AlertWidgetRenderer;
|
|
49
|
+
export default AlertWidgetRenderer;
|
package/templates/nextblock-template/components/blocks/renderers/inline/CtaWidgetRenderer.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import Link from 'next/link';
|
|
1
|
+
import Link from 'next/link';
|
|
2
2
|
|
|
3
3
|
interface CtaWidgetRendererProps {
|
|
4
4
|
text: string;
|
|
@@ -35,4 +35,4 @@ const CtaWidgetRenderer = ({ text, url, style, size, textAlign }: CtaWidgetRende
|
|
|
35
35
|
);
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
export default CtaWidgetRenderer;
|
|
38
|
+
export default CtaWidgetRenderer;
|