create-nextblock 0.2.31 → 0.2.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/package.json +1 -1
  2. package/scripts/sync-template.js +70 -52
  3. package/templates/nextblock-template/app/[slug]/PageClientContent.tsx +67 -67
  4. package/templates/nextblock-template/app/[slug]/page.tsx +4 -4
  5. package/templates/nextblock-template/app/cms/blocks/actions.ts +10 -10
  6. package/templates/nextblock-template/app/cms/blocks/components/BackgroundSelector.tsx +348 -348
  7. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorArea.tsx +8 -8
  8. package/templates/nextblock-template/app/cms/blocks/components/EditableBlock.tsx +10 -10
  9. package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +81 -81
  10. package/templates/nextblock-template/app/cms/media/actions.ts +35 -35
  11. package/templates/nextblock-template/app/cms/media/page.tsx +120 -120
  12. package/templates/nextblock-template/app/cms/revisions/JsonDiffView.tsx +86 -86
  13. package/templates/nextblock-template/app/cms/revisions/service.ts +344 -344
  14. package/templates/nextblock-template/app/providers.tsx +2 -2
  15. package/templates/nextblock-template/components/BlockRenderer.tsx +9 -9
  16. package/templates/nextblock-template/components/ResponsiveNav.tsx +22 -22
  17. package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -12
  18. package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +26 -26
  19. package/templates/nextblock-template/components/blocks/renderers/HeroBlockRenderer.tsx +41 -41
  20. package/templates/nextblock-template/components/blocks/renderers/ImageBlockRenderer.tsx +7 -7
  21. package/templates/nextblock-template/components/theme-switcher.tsx +78 -78
  22. package/templates/nextblock-template/eslint.config.mjs +35 -37
  23. package/templates/nextblock-template/lib/blocks/blockRegistry.ts +19 -19
  24. package/templates/nextblock-template/next-env.d.ts +6 -6
  25. package/templates/nextblock-template/package.json +1 -1
@@ -4,7 +4,7 @@ import { ThemeProvider } from "next-themes";
4
4
  import { AuthProvider } from "@/context/AuthContext";
5
5
  import { LanguageProvider } from "@/context/LanguageContext";
6
6
  import { CurrentContentProvider } from "@/context/CurrentContentContext";
7
- import { TranslationsProvider } from '@nextblock-cms/utils';
7
+ import { TranslationsProvider } from '@nextblock-cms/utils';
8
8
 
9
9
  export function Providers({ children, ...props }: { children: React.ReactNode;[key: string]: any; }) {
10
10
  const {
@@ -40,4 +40,4 @@ export function Providers({ children, ...props }: { children: React.ReactNode;[k
40
40
  </LanguageProvider>
41
41
  </AuthProvider>
42
42
  );
43
- }
43
+ }
@@ -2,7 +2,7 @@
2
2
  import React from "react";
3
3
  import dynamic from "next/dynamic";
4
4
  import type { Database } from "@nextblock-cms/db";
5
- import { getBlockDefinition, type SectionBlockContent, type BlockType } from "../lib/blocks/blockRegistry";
5
+ import { getBlockDefinition, type SectionBlockContent, type BlockType } from "../lib/blocks/blockRegistry";
6
6
 
7
7
  type Block = Database['public']['Tables']['blocks']['Row'];
8
8
  import HeroBlockRenderer from "./blocks/renderers/HeroBlockRenderer"; // Static import for LCP
@@ -50,14 +50,14 @@ const DynamicBlockRenderer: React.FC<DynamicBlockRendererProps> = ({
50
50
  const RendererComponent = dynamic(
51
51
  () => import(`./blocks/renderers/${blockDefinition.rendererComponentFilename}`),
52
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
- ),
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
61
  ssr: true,
62
62
  }
63
63
  ) as React.ComponentType<any>;
@@ -10,8 +10,8 @@ type Logo = Database['public']['Tables']['logos']['Row'] & { media: (Database['p
10
10
  type NavigationItem = Database['public']['Tables']['navigation_items']['Row'];
11
11
  import Image from 'next/image'
12
12
 
13
- const R2_BASE_URL = process.env.NEXT_PUBLIC_R2_BASE_URL || '';
14
- const FALLBACK_LOGO_PATH = '/images/nextblock-logo-small.webp';
13
+ const R2_BASE_URL = process.env.NEXT_PUBLIC_R2_BASE_URL || '';
14
+ const FALLBACK_LOGO_PATH = '/images/nextblock-logo-small.webp';
15
15
 
16
16
  // Define a type for hierarchical navigation items
17
17
  interface HierarchicalNavigationItem extends NavigationItem {
@@ -259,25 +259,25 @@ export default function ResponsiveNav({
259
259
  href={homeLinkHref}
260
260
  className="flex items-center space-x-2 rtl:space-x-reverse"
261
261
  >
262
- {logo && logo.media ? (
263
- <Image
264
- src={`${R2_BASE_URL}/${logo.media.object_key}`}
265
- alt={logo.media.alt_text || siteTitle || 'Nextblock'}
266
- width={logo.media.width || 100}
267
- height={logo.media.height || 32}
268
- className="h-14 w-auto object-contain"
269
- priority
270
- />
271
- ) : (
272
- <Image
273
- src={FALLBACK_LOGO_PATH}
274
- alt={siteTitle || 'Nextblock'}
275
- width={120}
276
- height={40}
277
- className="h-14 w-auto object-contain"
278
- priority
279
- />
280
- )}
262
+ {logo && logo.media ? (
263
+ <Image
264
+ src={`${R2_BASE_URL}/${logo.media.object_key}`}
265
+ alt={logo.media.alt_text || siteTitle || 'Nextblock'}
266
+ width={logo.media.width || 100}
267
+ height={logo.media.height || 32}
268
+ className="h-14 w-auto object-contain"
269
+ priority
270
+ />
271
+ ) : (
272
+ <Image
273
+ src={FALLBACK_LOGO_PATH}
274
+ alt={siteTitle || 'Nextblock'}
275
+ width={120}
276
+ height={40}
277
+ className="h-14 w-auto object-contain"
278
+ priority
279
+ />
280
+ )}
281
281
  </Link>
282
282
  {/* Desktop: Additional Nav items */}
283
283
  <div className="hidden md:flex items-baseline font-semibold ml-6 space-x-1"> {/* Adjusted space-x for items with internal padding */}
@@ -375,4 +375,4 @@ export default function ResponsiveNav({
375
375
  </div>
376
376
  </>
377
377
  );
378
- }
378
+ }
@@ -24,7 +24,7 @@ const PostsGridBlock: React.FC<PostsGridBlockProps> = async ({ block, languageId
24
24
 
25
25
  const supabase = createClient();
26
26
 
27
- const { data: postsData, error: queryError, count } = await supabase
27
+ const { data: postsData, error: queryError, count } = await supabase
28
28
  .from('posts')
29
29
  .select('id, title, slug, excerpt, published_at, language_id, status, created_at, updated_at, translation_group_id, feature_image_id, feature_media_object:media!feature_image_id(object_key, width, height)', { count: 'exact' })
30
30
  .eq('status', 'published')
@@ -40,18 +40,18 @@ const PostsGridBlock: React.FC<PostsGridBlockProps> = async ({ block, languageId
40
40
  console.error("Error fetching initial posts directly in PostsGridBlock:", queryError);
41
41
  postsError = queryError.message;
42
42
  } else {
43
- const buildMediaUrl = (objectKey?: string | null) => {
44
- if (!objectKey) return null;
45
- if (objectKey.startsWith('/')) return objectKey;
46
- const base = process.env.NEXT_PUBLIC_R2_BASE_URL || '';
47
- return base ? `${base}/${objectKey}` : objectKey;
48
- };
49
-
50
- initialPosts = (postsData as any)?.map((p: any) => {
43
+ const buildMediaUrl = (objectKey?: string | null) => {
44
+ if (!objectKey) return null;
45
+ if (objectKey.startsWith('/')) return objectKey;
46
+ const base = process.env.NEXT_PUBLIC_R2_BASE_URL || '';
47
+ return base ? `${base}/${objectKey}` : objectKey;
48
+ };
49
+
50
+ initialPosts = (postsData as any)?.map((p: any) => {
51
51
  // feature_media_object is an object here, not an array, due to the query structure media!feature_image_id(object_key, width, height)
52
52
  // Cast to 'unknown' then to the expected single object type to satisfy TypeScript, reflecting runtime reality.
53
- const mediaObject = p.feature_media_object as unknown as { object_key: string; width?: number | null; height?: number | null; blur_data_url?: string | null } | null;
54
- const imageUrl = buildMediaUrl(mediaObject?.object_key);
53
+ const mediaObject = p.feature_media_object as unknown as { object_key: string; width?: number | null; height?: number | null; blur_data_url?: string | null } | null;
54
+ const imageUrl = buildMediaUrl(mediaObject?.object_key);
55
55
  return {
56
56
  ...p,
57
57
  // Convert feature_media_object to array format to match the type
@@ -95,4 +95,4 @@ const PostsGridBlock: React.FC<PostsGridBlockProps> = async ({ block, languageId
95
95
  );
96
96
  };
97
97
 
98
- export default PostsGridBlock;
98
+ export default PostsGridBlock;
@@ -1,24 +1,24 @@
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";
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
8
 
9
9
  interface ClientTextBlockRendererProps {
10
10
  content: TextBlockContent;
11
11
  languageId: number;
12
12
  }
13
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,
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
22
  'data-title': title,
23
23
  'data-message': message,
24
24
  'data-align': align,
@@ -55,15 +55,15 @@ const ClientTextBlockRenderer: React.FC<ClientTextBlockRendererProps> = ({ conte
55
55
  />
56
56
  );
57
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
- };
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
68
 
69
- export default ClientTextBlockRenderer;
69
+ export default ClientTextBlockRenderer;
@@ -1,32 +1,32 @@
1
1
  // components/blocks/renderers/HeroBlockRenderer.tsx
2
- import React from "react";
2
+ import React from "react";
3
3
  import type { SectionBlockContent, Gradient } from "../../../lib/blocks/blockRegistry";
4
4
  import Image from 'next/image';
5
5
 
6
- const R2_BASE_URL = process.env.NEXT_PUBLIC_R2_BASE_URL || "";
7
- const HERO_BACKGROUND_DEFAULT_QUALITY = 60;
8
-
9
- function resolveImageQuality(value: unknown, fallback: number): number {
10
- if (value === undefined || value === null || value === '') {
11
- return fallback;
12
- }
13
-
14
- const numeric =
15
- typeof value === 'number'
16
- ? value
17
- : Number.parseInt(String(value), 10);
18
-
19
- if (!Number.isFinite(numeric)) {
20
- return fallback;
21
- }
22
-
23
- const rounded = Math.round(numeric);
24
- if (rounded < 1 || rounded > 100) {
25
- return fallback;
26
- }
27
-
28
- return rounded;
29
- }
6
+ const R2_BASE_URL = process.env.NEXT_PUBLIC_R2_BASE_URL || "";
7
+ const HERO_BACKGROUND_DEFAULT_QUALITY = 60;
8
+
9
+ function resolveImageQuality(value: unknown, fallback: number): number {
10
+ if (value === undefined || value === null || value === '') {
11
+ return fallback;
12
+ }
13
+
14
+ const numeric =
15
+ typeof value === 'number'
16
+ ? value
17
+ : Number.parseInt(String(value), 10);
18
+
19
+ if (!Number.isFinite(numeric)) {
20
+ return fallback;
21
+ }
22
+
23
+ const rounded = Math.round(numeric);
24
+ if (rounded < 1 || rounded > 100) {
25
+ return fallback;
26
+ }
27
+
28
+ return rounded;
29
+ }
30
30
 
31
31
  interface SectionBlockRendererProps {
32
32
  content: SectionBlockContent;
@@ -192,14 +192,14 @@ const HeroBlockRenderer: React.FC<SectionBlockRendererProps> = ({
192
192
  content,
193
193
  languageId,
194
194
  }) => {
195
- const { styles, className: backgroundClassName } = generateBackgroundStyles(content.background);
196
-
197
- const backgroundImage = content.background.type === 'image' ? content.background.image : undefined;
198
-
199
- if (backgroundImage) {
200
- delete styles.backgroundImage;
201
- }
202
- const heroImageQuality = resolveImageQuality(backgroundImage?.quality, HERO_BACKGROUND_DEFAULT_QUALITY);
195
+ const { styles, className: backgroundClassName } = generateBackgroundStyles(content.background);
196
+
197
+ const backgroundImage = content.background.type === 'image' ? content.background.image : undefined;
198
+
199
+ if (backgroundImage) {
200
+ delete styles.backgroundImage;
201
+ }
202
+ const heroImageQuality = resolveImageQuality(backgroundImage?.quality, HERO_BACKGROUND_DEFAULT_QUALITY);
203
203
 
204
204
  // Build CSS classes
205
205
  const containerClass = containerClasses[content.container_type] || containerClasses.container;
@@ -229,13 +229,13 @@ const HeroBlockRenderer: React.FC<SectionBlockRendererProps> = ({
229
229
  objectFit: backgroundImage.size || 'cover',
230
230
  objectPosition: backgroundImage.position || 'center'
231
231
  }}
232
- sizes="(max-width: 768px) 100vw, (max-width: 1200px) 100vw, 100vw"
233
- priority={true}
234
- fetchPriority="high"
235
- quality={heroImageQuality}
236
- {...imageProps}
237
- />
238
- )}
232
+ sizes="(max-width: 768px) 100vw, (max-width: 1200px) 100vw, 100vw"
233
+ priority={true}
234
+ fetchPriority="high"
235
+ quality={heroImageQuality}
236
+ {...imageProps}
237
+ />
238
+ )}
239
239
  {backgroundImage?.overlay?.gradient && (
240
240
  <div
241
241
  className="absolute inset-0"
@@ -261,4 +261,4 @@ const HeroBlockRenderer: React.FC<SectionBlockRendererProps> = ({
261
261
  );
262
262
  };
263
263
 
264
- export default HeroBlockRenderer;
264
+ export default HeroBlockRenderer;
@@ -19,12 +19,12 @@ interface ImageBlockRendererProps {
19
19
  priority?: boolean;
20
20
  }
21
21
 
22
- const ImageBlockRenderer: React.FC<ImageBlockRendererProps> = ({
23
- content,
24
- languageId,
25
- priority = false,
26
- }) => {
27
- void languageId;
22
+ const ImageBlockRenderer: React.FC<ImageBlockRendererProps> = ({
23
+ content,
24
+ languageId,
25
+ priority = false,
26
+ }) => {
27
+ void languageId;
28
28
  if (!content.media_id || !content.object_key) {
29
29
  return (
30
30
  <div className="my-4 p-4 border rounded text-center text-muted-foreground italic">
@@ -76,4 +76,4 @@ const ImageBlockRenderer: React.FC<ImageBlockRendererProps> = ({
76
76
  );
77
77
  };
78
78
 
79
- export default ImageBlockRenderer;
79
+ export default ImageBlockRenderer;
@@ -1,78 +1,78 @@
1
- "use client";
2
-
3
- import { Button } from "@nextblock-cms/ui";
4
- import {
5
- DropdownMenu,
6
- DropdownMenuContent,
7
- DropdownMenuRadioGroup,
8
- DropdownMenuRadioItem,
9
- DropdownMenuTrigger,
10
- } from "@nextblock-cms/ui";
11
- import { Laptop, Moon, Sun } from "lucide-react";
12
- import { useTheme } from "next-themes";
13
- import { useEffect, useState } from "react";
14
-
15
- const ThemeSwitcher = () => {
16
- const [mounted, setMounted] = useState(false);
17
- const { theme, setTheme } = useTheme();
18
-
19
- // useEffect only runs on the client, so now we can safely show the UI
20
- useEffect(() => {
21
- setMounted(true);
22
- }, []);
23
-
24
- if (!mounted) {
25
- return null;
26
- }
27
-
28
- const ICON_SIZE = 16;
29
-
30
- return (
31
- <DropdownMenu>
32
- <DropdownMenuTrigger asChild>
33
- <Button variant="ghost" size={"sm"} aria-label="Theme Switcher">
34
- {theme === "light" ? (
35
- <Sun
36
- key="light"
37
- size={ICON_SIZE}
38
- className={"text-muted-foreground"}
39
- />
40
- ) : theme === "dark" ? (
41
- <Moon
42
- key="dark"
43
- size={ICON_SIZE}
44
- className={"text-muted-foreground"}
45
- />
46
- ) : (
47
- <Laptop
48
- key="system"
49
- size={ICON_SIZE}
50
- className={"text-muted-foreground"}
51
- />
52
- )}
53
- </Button>
54
- </DropdownMenuTrigger>
55
- <DropdownMenuContent className="w-content" align="start">
56
- <DropdownMenuRadioGroup
57
- value={theme}
58
- onValueChange={(e) => setTheme(e)}
59
- >
60
- <DropdownMenuRadioItem className="flex gap-2" value="light">
61
- <Sun size={ICON_SIZE} className="text-muted-foreground" />{" "}
62
- <span>Light</span>
63
- </DropdownMenuRadioItem>
64
- <DropdownMenuRadioItem className="flex gap-2" value="dark">
65
- <Moon size={ICON_SIZE} className="text-muted-foreground" />{" "}
66
- <span>Dark</span>
67
- </DropdownMenuRadioItem>
68
- <DropdownMenuRadioItem className="flex gap-2" value="system">
69
- <Laptop size={ICON_SIZE} className="text-muted-foreground" />{" "}
70
- <span>System</span>
71
- </DropdownMenuRadioItem>
72
- </DropdownMenuRadioGroup>
73
- </DropdownMenuContent>
74
- </DropdownMenu>
75
- );
76
- };
77
-
78
- export { ThemeSwitcher };
1
+ "use client";
2
+
3
+ import { Button } from "@nextblock-cms/ui";
4
+ import {
5
+ DropdownMenu,
6
+ DropdownMenuContent,
7
+ DropdownMenuRadioGroup,
8
+ DropdownMenuRadioItem,
9
+ DropdownMenuTrigger,
10
+ } from "@nextblock-cms/ui";
11
+ import { Laptop, Moon, Sun } from "lucide-react";
12
+ import { useTheme } from "next-themes";
13
+ import { useEffect, useState } from "react";
14
+
15
+ const ThemeSwitcher = () => {
16
+ const [mounted, setMounted] = useState(false);
17
+ const { theme, setTheme } = useTheme();
18
+
19
+ // useEffect only runs on the client, so now we can safely show the UI
20
+ useEffect(() => {
21
+ setMounted(true);
22
+ }, []);
23
+
24
+ if (!mounted) {
25
+ return null;
26
+ }
27
+
28
+ const ICON_SIZE = 16;
29
+
30
+ return (
31
+ <DropdownMenu>
32
+ <DropdownMenuTrigger asChild>
33
+ <Button variant="ghost" size={"sm"} aria-label="Theme Switcher">
34
+ {theme === "light" ? (
35
+ <Sun
36
+ key="light"
37
+ size={ICON_SIZE}
38
+ className={"text-muted-foreground"}
39
+ />
40
+ ) : theme === "dark" ? (
41
+ <Moon
42
+ key="dark"
43
+ size={ICON_SIZE}
44
+ className={"text-muted-foreground"}
45
+ />
46
+ ) : (
47
+ <Laptop
48
+ key="system"
49
+ size={ICON_SIZE}
50
+ className={"text-muted-foreground"}
51
+ />
52
+ )}
53
+ </Button>
54
+ </DropdownMenuTrigger>
55
+ <DropdownMenuContent className="w-content" align="start">
56
+ <DropdownMenuRadioGroup
57
+ value={theme}
58
+ onValueChange={(e) => setTheme(e)}
59
+ >
60
+ <DropdownMenuRadioItem className="flex gap-2" value="light">
61
+ <Sun size={ICON_SIZE} className="text-muted-foreground" />{" "}
62
+ <span>Light</span>
63
+ </DropdownMenuRadioItem>
64
+ <DropdownMenuRadioItem className="flex gap-2" value="dark">
65
+ <Moon size={ICON_SIZE} className="text-muted-foreground" />{" "}
66
+ <span>Dark</span>
67
+ </DropdownMenuRadioItem>
68
+ <DropdownMenuRadioItem className="flex gap-2" value="system">
69
+ <Laptop size={ICON_SIZE} className="text-muted-foreground" />{" "}
70
+ <span>System</span>
71
+ </DropdownMenuRadioItem>
72
+ </DropdownMenuRadioGroup>
73
+ </DropdownMenuContent>
74
+ </DropdownMenu>
75
+ );
76
+ };
77
+
78
+ export { ThemeSwitcher };
@@ -1,37 +1,35 @@
1
- import nextPlugin from '@next/eslint-plugin-next';
2
- import tseslint from 'typescript-eslint';
3
-
4
- const config = [
5
- {
6
- ignores: ['.next/**/*', '**/next-env.d.ts', 'next-env.d.ts'],
7
- },
8
- ...tseslint.configs.recommended,
9
- {
10
- files: [
11
- '**/*.ts',
12
- '**/*.tsx',
13
- '**/*.js',
14
- '**/*.jsx',
15
- '**/*.mjs',
16
- '**/*.cjs',
17
- ],
18
- languageOptions: {
19
- parserOptions: {
20
- project: true,
21
- tsconfigRootDir: import.meta.dirname,
22
- },
23
- },
24
- plugins: {
25
- '@next/next': nextPlugin,
26
- },
27
- rules: {
28
- ...nextPlugin.configs.recommended.rules,
29
- ...nextPlugin.configs['core-web-vitals'].rules,
30
- '@next/next/no-html-link-for-pages': 'off',
31
- '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
32
- '@typescript-eslint/no-explicit-any': 'warn',
33
- },
34
- },
35
- ];
36
-
37
- export default config;
1
+ import nx from '@nx/eslint-plugin';
2
+ import baseConfig from '../../eslint.config.mjs';
3
+ import nextPlugin from '@next/eslint-plugin-next';
4
+
5
+ const nextRules = {
6
+ ...nextPlugin.configs.recommended.rules,
7
+ ...nextPlugin.configs['core-web-vitals'].rules,
8
+ };
9
+
10
+ const config = [
11
+ ...baseConfig,
12
+ ...nx.configs['flat/react-typescript'],
13
+ {
14
+ ignores: ['.next/**/*', '**/next-env.d.ts', 'apps/nextblock/next-env.d.ts'],
15
+ },
16
+ {
17
+ files: [
18
+ '**/*.ts',
19
+ '**/*.tsx',
20
+ '**/*.js',
21
+ '**/*.jsx',
22
+ '**/*.mjs',
23
+ '**/*.cjs',
24
+ ],
25
+ plugins: {
26
+ '@next/next': nextPlugin,
27
+ },
28
+ rules: {
29
+ ...nextRules,
30
+ '@next/next/no-html-link-for-pages': ['error', 'apps/nextblock/app'],
31
+ },
32
+ },
33
+ ];
34
+
35
+ export default config;
@@ -117,21 +117,21 @@ export interface SectionBlockContent {
117
117
  solid_color?: string;
118
118
  min_height?: string;
119
119
  gradient?: Gradient;
120
- image?: {
121
- media_id: string;
122
- object_key: string;
123
- alt_text?: string;
124
- width?: number;
125
- height?: number;
126
- blur_data_url?: string;
127
- size: 'cover' | 'contain';
128
- position: 'center' | 'top' | 'bottom' | 'left' | 'right';
129
- quality?: number | null;
130
- overlay?: {
131
- type: 'gradient';
132
- gradient: Gradient;
133
- };
134
- };
120
+ image?: {
121
+ media_id: string;
122
+ object_key: string;
123
+ alt_text?: string;
124
+ width?: number;
125
+ height?: number;
126
+ blur_data_url?: string;
127
+ size: 'cover' | 'contain';
128
+ position: 'center' | 'top' | 'bottom' | 'left' | 'right';
129
+ quality?: number | null;
130
+ overlay?: {
131
+ type: 'gradient';
132
+ gradient: Gradient;
133
+ };
134
+ };
135
135
  };
136
136
  /** Responsive column configuration */
137
137
  responsive_columns: {
@@ -972,9 +972,9 @@ export function getRequiredProperties(blockType: BlockType): string[] {
972
972
  const schema = getContentSchema(blockType);
973
973
  if (!schema) return [];
974
974
 
975
- return Object.entries(schema)
976
- .filter(([, def]) => def.required)
977
- .map(([name]) => name);
975
+ return Object.entries(schema)
976
+ .filter(([, def]) => def.required)
977
+ .map(([name]) => name);
978
978
  }
979
979
 
980
980
  /**
@@ -999,4 +999,4 @@ export function generateDefaultContent(blockType: BlockType): Record<string, any
999
999
  }
1000
1000
 
1001
1001
  return defaultContent;
1002
- }
1002
+ }
@@ -1,6 +1,6 @@
1
- /// <reference types="next" />
2
- /// <reference types="next/image-types/global" />
3
- import "./.next/dev/types/routes.d.ts";
4
-
5
- // NOTE: This file should not be edited
6
- // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+ import "./.next/dev/types/routes.d.ts";
4
+
5
+ // NOTE: This file should not be edited
6
+ // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.