create-nextblock 0.13.1 → 0.13.3

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 (34) hide show
  1. package/package.json +1 -1
  2. package/templates/nextblock-template/app/api/ai/global-agent/route.ts +287 -48
  3. package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +238 -209
  4. package/templates/nextblock-template/app/cms/blocks/components/BackgroundSelector.tsx +103 -8
  5. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorArea.tsx +31 -2
  6. package/templates/nextblock-template/app/cms/blocks/components/ColumnEditor.tsx +37 -15
  7. package/templates/nextblock-template/app/cms/blocks/components/EditableBlock.tsx +26 -15
  8. package/templates/nextblock-template/app/cms/blocks/editors/ImageBlockEditor.tsx +123 -46
  9. package/templates/nextblock-template/app/cms/blocks/editors/SectionBlockEditor.tsx +8 -1
  10. package/templates/nextblock-template/app/cms/components/CortexGlobalAgentChat.tsx +62 -22
  11. package/templates/nextblock-template/app/cms/media/import-external-image.ts +289 -0
  12. package/templates/nextblock-template/app/cms/pages/[id]/edit/EditPageClient.tsx +13 -10
  13. package/templates/nextblock-template/app/cms/pages/[id]/edit/page.tsx +14 -3
  14. package/templates/nextblock-template/app/cms/pages/actions.ts +59 -6
  15. package/templates/nextblock-template/app/cms/posts/[id]/edit/page.tsx +21 -11
  16. package/templates/nextblock-template/app/cms/posts/actions.ts +45 -0
  17. package/templates/nextblock-template/app/cms/products/[id]/edit/page.tsx +11 -9
  18. package/templates/nextblock-template/app/cms/settings/cortex-ai/StoredCortexAiSettingsClient.tsx +463 -227
  19. package/templates/nextblock-template/app/cms/settings/cortex-ai/actions.ts +220 -1
  20. package/templates/nextblock-template/app/cms/settings/cortex-ai/page.tsx +11 -0
  21. package/templates/nextblock-template/app/lib/homepage.ts +36 -0
  22. package/templates/nextblock-template/app/lib/sitemap-utils.ts +13 -6
  23. package/templates/nextblock-template/app/page.tsx +55 -12
  24. package/templates/nextblock-template/components/blocks/renderers/ImageBlockRenderer.tsx +56 -0
  25. package/templates/nextblock-template/components/blocks/renderers/SectionBlockRenderer.tsx +60 -30
  26. package/templates/nextblock-template/components/blocks/renderers/StockPhotoCredit.tsx +167 -0
  27. package/templates/nextblock-template/components/visual-editing/NextblockVisualEditing.tsx +14 -2
  28. package/templates/nextblock-template/docs/08-NEXTBLOCK-CORTEX-AI-ARCHITECTURE.md +94 -6
  29. package/templates/nextblock-template/docs/09-LIVE-DRAFT-MODE.md +7 -1
  30. package/templates/nextblock-template/lib/blocks/blockRegistry.ts +29 -3
  31. package/templates/nextblock-template/lib/search/server.ts +11 -1
  32. package/templates/nextblock-template/lib/setup/migrations-bundle.ts +82 -72
  33. package/templates/nextblock-template/package.json +1 -1
  34. package/templates/nextblock-template/proxy.ts +5 -0
@@ -3,13 +3,13 @@ import React from "react";
3
3
  import { Separator } from "@nextblock-cms/ui";
4
4
  import { createClient } from "@nextblock-cms/db/server";
5
5
  import PostForm from "../../components/PostForm";
6
- import { updatePost } from "../../actions";
6
+ import { updatePost, publishPost } from "../../actions";
7
7
  import type { Database } from "@nextblock-cms/db";
8
8
  import { notFound, redirect } from "next/navigation";
9
9
  import BlockEditorArea from "../../../blocks/components/BlockEditorArea";
10
10
  import Link from "next/link";
11
- import { Button } from "@nextblock-cms/ui";
12
- import { ArrowLeft, Eye, FilePenLine } from "lucide-react";
11
+ import { Button, ViewLiveButton } from "@nextblock-cms/ui";
12
+ import { ArrowLeft, FilePenLine } from "lucide-react";
13
13
  import ContentLanguageSwitcher from "../../../components/ContentLanguageSwitcher";
14
14
  import { getActiveLanguagesServerSide } from "@nextblock-cms/db/server";
15
15
  import { normalizeContentDraftRow } from "../../../../../lib/visual-editing/draft-content";
@@ -30,7 +30,7 @@ interface PostWithBlocks extends PostType {
30
30
  translation_group_id: string;
31
31
  }
32
32
 
33
- async function getPostDataWithBlocks(id: number): Promise<{ post: PostWithBlocks; hasDraft: boolean } | null> {
33
+ async function getPostDataWithBlocks(id: number): Promise<{ post: PostWithBlocks; hasDraft: boolean; liveStatus: string; liveSlug: string } | null> {
34
34
  const supabase = createClient();
35
35
  const { data: postData, error: postError } = await supabase
36
36
  .from("posts")
@@ -86,7 +86,14 @@ async function getPostDataWithBlocks(id: number): Promise<{ post: PostWithBlocks
86
86
  };
87
87
  }
88
88
 
89
- return { post: postWithBlocks, hasDraft };
89
+ return {
90
+ post: postWithBlocks,
91
+ hasDraft,
92
+ // The LIVE row's status/slug (before the draft overlay above) — the "View
93
+ // Live" button must reflect what is actually published, not the draft.
94
+ liveStatus: postData.status as string,
95
+ liveSlug: postData.slug as string,
96
+ };
90
97
  }
91
98
 
92
99
  export default async function EditPostPage(props: { params: Promise<{ id: string }> }) {
@@ -113,7 +120,7 @@ export default async function EditPostPage(props: { params: Promise<{ id: string
113
120
  return notFound();
114
121
  }
115
122
 
116
- const { post: postWithBlocks, hasDraft } = postDataResult;
123
+ const { post: postWithBlocks, hasDraft, liveStatus, liveSlug } = postDataResult;
117
124
 
118
125
  let initialFeatureImageUrl: string | null = null;
119
126
  let initialFeatureImageIdProp: string | null = null;
@@ -138,6 +145,8 @@ export default async function EditPostPage(props: { params: Promise<{ id: string
138
145
  const updatePostWithId = updatePost.bind(null, postId);
139
146
  const publicPostUrl = `/article/${postWithBlocks.slug}`;
140
147
  const draftModeUrl = `/api/draft/start?path=${encodeURIComponent(publicPostUrl)}`;
148
+ const isLive = liveStatus === "published";
149
+ const liveViewUrl = `/article/${liveSlug}`;
141
150
 
142
151
  return (
143
152
  <UploadFolderProvider defaultFolder={`posts/${postWithBlocks.slug}/`}>
@@ -181,11 +190,12 @@ export default async function EditPostPage(props: { params: Promise<{ id: string
181
190
  allSiteLanguages={allSiteLanguages}
182
191
  />
183
192
  )}
184
- <Button variant="outline" asChild>
185
- <Link href={publicPostUrl} target="_blank" rel="noopener noreferrer">
186
- <Eye className="mr-2 h-4 w-4" /> View Live Post
187
- </Link>
188
- </Button>
193
+ <ViewLiveButton
194
+ href={liveViewUrl}
195
+ isLive={isLive}
196
+ label="post"
197
+ publishAction={publishPost.bind(null, postId)}
198
+ />
189
199
  <Button variant="secondary" asChild>
190
200
  <a href={draftModeUrl} target="_blank" rel="noopener noreferrer">
191
201
  <FilePenLine className="mr-2 h-4 w-4" />
@@ -12,6 +12,51 @@ type PageStatus = Database['public']['Enums']['page_status'];
12
12
  import { encodedRedirect } from "@nextblock-cms/utils/server"; // Ensure this is correctly imported
13
13
  // --- createPost and updatePost functions to be updated similarly for error returns ---
14
14
 
15
+ /**
16
+ * Publish a post directly (status -> "published") so it becomes visible on the
17
+ * live site. Used by the draft-aware "View Live" button when an admin chooses to
18
+ * publish a still-draft post.
19
+ */
20
+ export async function publishPost(postId: number): Promise<{ error?: string } | void> {
21
+ const supabase = createClient();
22
+ const { data: { user } } = await supabase.auth.getUser();
23
+ if (!user) return { error: "User not authenticated." };
24
+
25
+ const nowIso = new Date().toISOString();
26
+ const { data: existing } = await supabase
27
+ .from("posts")
28
+ .select("published_at")
29
+ .eq("id", postId)
30
+ .maybeSingle();
31
+
32
+ // Publishing "now": if the post has no publish date or a future-scheduled one,
33
+ // set it to now so it isn't withheld by the public "published_at <= now" filter.
34
+ const currentPublishedAt = existing?.published_at;
35
+ const setPublishedNow =
36
+ !currentPublishedAt || new Date(currentPublishedAt).getTime() > Date.now();
37
+
38
+ const { data: post, error } = await supabase
39
+ .from("posts")
40
+ .update({
41
+ status: "published",
42
+ updated_at: nowIso,
43
+ ...(setPublishedNow ? { published_at: nowIso } : {}),
44
+ })
45
+ .eq("id", postId)
46
+ .select("slug")
47
+ .single();
48
+
49
+ if (error || !post) {
50
+ return { error: error?.message || "Could not publish the post." };
51
+ }
52
+
53
+ revalidatePath("/cms/posts");
54
+ revalidatePath(`/cms/posts/${postId}/edit`);
55
+ revalidatePath(`/article/${post.slug}`);
56
+ revalidatePath("/articles");
57
+ return {};
58
+ }
59
+
15
60
  export async function createPost(formData: FormData) {
16
61
  const supabase = createClient();
17
62
  const { data: { user } } = await supabase.auth.getUser();
@@ -1,7 +1,7 @@
1
1
  import { verifyPackageOnline, getActiveLanguagesServerSide } from '@nextblock-cms/db/server';
2
2
  import { redirect, notFound } from 'next/navigation';
3
3
  import Link from 'next/link';
4
- import { ArrowLeft, ChevronDown, ExternalLink } from 'lucide-react';
4
+ import { ArrowLeft, ChevronDown } from 'lucide-react';
5
5
  import {
6
6
  Badge,
7
7
  Button,
@@ -11,6 +11,7 @@ import {
11
11
  DropdownMenuLabel,
12
12
  DropdownMenuSeparator,
13
13
  DropdownMenuTrigger,
14
+ ViewLiveButton,
14
15
  } from '@nextblock-cms/ui';
15
16
  import ProductFormClientShell from '../../ProductFormClientShell';
16
17
  import {
@@ -21,6 +22,7 @@ import {
21
22
  getStoreConfigStatus,
22
23
  normalizeCurrencyRecord,
23
24
  updateProductAction,
25
+ publishProductAction,
24
26
  getCategoriesWithCount,
25
27
  getProductCategories,
26
28
  } from '@nextblock-cms/ecommerce/server';
@@ -265,14 +267,14 @@ export default async function EditProductPage({
265
267
  </DropdownMenu>
266
268
  ) : null}
267
269
 
268
- {product.slug && product.status === 'active' && (
269
- <Button variant="outline" asChild>
270
- <Link href={`/product/${product.slug}`} target="_blank">
271
- <ExternalLink className="w-4 h-4 mr-2" />
272
- View
273
- </Link>
274
- </Button>
275
- )}
270
+ {product.slug ? (
271
+ <ViewLiveButton
272
+ href={`/product/${product.slug}`}
273
+ isLive={product.status === 'active'}
274
+ label="product"
275
+ publishAction={publishProductAction.bind(null, product.id)}
276
+ />
277
+ ) : null}
276
278
  </div>
277
279
  </div>
278
280