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,309 @@
|
|
|
1
|
+
// app/cms/posts/actions.ts
|
|
2
|
+
"use server";
|
|
3
|
+
|
|
4
|
+
import { createClient } from "@nextblock-cms/db/server";
|
|
5
|
+
import { revalidatePath } from "next/cache";
|
|
6
|
+
import { redirect } from "next/navigation";
|
|
7
|
+
import type { Database } from "@nextblock-cms/db";
|
|
8
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
9
|
+
|
|
10
|
+
type PageStatus = Database['public']['Enums']['page_status'];
|
|
11
|
+
import { encodedRedirect } from "@nextblock-cms/utils/server"; // Ensure this is correctly imported
|
|
12
|
+
import { getFullPostContent } from "../revisions/utils";
|
|
13
|
+
import { createPostRevision } from "../revisions/service";
|
|
14
|
+
|
|
15
|
+
// --- createPost and updatePost functions to be updated similarly for error returns ---
|
|
16
|
+
|
|
17
|
+
export async function createPost(formData: FormData) {
|
|
18
|
+
const supabase = createClient();
|
|
19
|
+
const { data: { user } } = await supabase.auth.getUser();
|
|
20
|
+
if (!user) {
|
|
21
|
+
return encodedRedirect("error", "/cms/posts/new", "User not authenticated.");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const featureImageIdStr_create = formData.get("feature_image_id") as string;
|
|
25
|
+
let featureImageId_create: string | null = null;
|
|
26
|
+
if (featureImageIdStr_create && featureImageIdStr_create.trim() !== "") {
|
|
27
|
+
featureImageId_create = featureImageIdStr_create;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const rawFormData = {
|
|
31
|
+
title: formData.get("title") as string,
|
|
32
|
+
slug: formData.get("slug") as string,
|
|
33
|
+
language_id: parseInt(formData.get("language_id") as string, 10),
|
|
34
|
+
status: formData.get("status") as PageStatus,
|
|
35
|
+
excerpt: formData.get("excerpt") as string || null,
|
|
36
|
+
published_at: formData.get("published_at") as string || null,
|
|
37
|
+
meta_title: formData.get("meta_title") as string || null,
|
|
38
|
+
meta_description: formData.get("meta_description") as string || null,
|
|
39
|
+
feature_image_id: featureImageId_create,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
if (!rawFormData.title || !rawFormData.slug || isNaN(rawFormData.language_id) || !rawFormData.status) {
|
|
43
|
+
return encodedRedirect("error", "/cms/posts/new", "Missing required fields: title, slug, language, or status.");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let publishedAtISO: string | null = null;
|
|
47
|
+
if (rawFormData.published_at) {
|
|
48
|
+
const parsedDate = new Date(rawFormData.published_at);
|
|
49
|
+
if (!isNaN(parsedDate.getTime())) publishedAtISO = parsedDate.toISOString();
|
|
50
|
+
else publishedAtISO = rawFormData.published_at;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const newTranslationGroupId = uuidv4();
|
|
54
|
+
|
|
55
|
+
const postData: UpsertPostPayload = {
|
|
56
|
+
...rawFormData,
|
|
57
|
+
published_at: publishedAtISO,
|
|
58
|
+
author_id: user.id,
|
|
59
|
+
translation_group_id: newTranslationGroupId,
|
|
60
|
+
feature_image_id: rawFormData.feature_image_id,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const { data: newPost, error: createError } = await supabase
|
|
64
|
+
.from("posts")
|
|
65
|
+
.insert(postData)
|
|
66
|
+
.select("id, title, slug, language_id, translation_group_id, excerpt, feature_image_id") // Added excerpt, feature_image_id
|
|
67
|
+
.single();
|
|
68
|
+
|
|
69
|
+
if (createError) {
|
|
70
|
+
console.error("Error creating post:", createError);
|
|
71
|
+
if (createError.code === '23505' && createError.message.includes('posts_language_id_slug_key')) {
|
|
72
|
+
return encodedRedirect("error", "/cms/posts/new", `The slug "${postData.slug}" already exists for the selected language. Please use a unique slug.`);
|
|
73
|
+
}
|
|
74
|
+
return encodedRedirect("error", "/cms/posts/new", `Failed to create post: ${createError.message}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let successMessage = "Post created successfully.";
|
|
78
|
+
|
|
79
|
+
if (newPost) {
|
|
80
|
+
const { data: languages, error: langError } = await supabase
|
|
81
|
+
.from("languages")
|
|
82
|
+
.select("id, code")
|
|
83
|
+
.neq("id", newPost.language_id);
|
|
84
|
+
|
|
85
|
+
if (langError) {
|
|
86
|
+
console.error("Error fetching other languages for post auto-creation:", langError);
|
|
87
|
+
} else if (languages && languages.length > 0) {
|
|
88
|
+
let placeholderCreations = 0;
|
|
89
|
+
for (const lang of languages) {
|
|
90
|
+
const placeholderSlug = generatePlaceholderSlug(newPost.title, lang.code);
|
|
91
|
+
const placeholderPostData: Omit<UpsertPostPayload, 'author_id'> & {author_id?: string | null} = {
|
|
92
|
+
language_id: lang.id,
|
|
93
|
+
title: `[${lang.code.toUpperCase()}] ${newPost.title}`,
|
|
94
|
+
slug: placeholderSlug,
|
|
95
|
+
status: 'draft',
|
|
96
|
+
published_at: null,
|
|
97
|
+
excerpt: `Placeholder for ${lang.code.toUpperCase()} translation. Original excerpt: ${newPost.excerpt || ''}`.substring(0, 250),
|
|
98
|
+
meta_title: null,
|
|
99
|
+
meta_description: null,
|
|
100
|
+
translation_group_id: newPost.translation_group_id,
|
|
101
|
+
author_id: user.id,
|
|
102
|
+
};
|
|
103
|
+
const { error: placeholderError } = await supabase.from("posts").insert(placeholderPostData);
|
|
104
|
+
if (placeholderError) {
|
|
105
|
+
console.error(`Error auto-creating post for language ${lang.code} (slug: ${placeholderSlug}):`, placeholderError);
|
|
106
|
+
} else {
|
|
107
|
+
placeholderCreations++;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (placeholderCreations > 0) {
|
|
111
|
+
successMessage += ` ${placeholderCreations} placeholder version(s) also created (draft status, please edit their slugs and content).`;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
revalidatePath("/cms/posts");
|
|
117
|
+
if (newPost?.slug) revalidatePath(`/blog/${newPost.slug}`);
|
|
118
|
+
|
|
119
|
+
if (newPost?.id) {
|
|
120
|
+
redirect(`/cms/posts/${newPost.id}/edit?success=${encodeURIComponent(successMessage)}`);
|
|
121
|
+
} else {
|
|
122
|
+
redirect(`/cms/posts?success=${encodeURIComponent(successMessage)}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function updatePost(postId: number, formData: FormData) {
|
|
127
|
+
const supabase = createClient();
|
|
128
|
+
const { data: { user } } = await supabase.auth.getUser();
|
|
129
|
+
const postEditPath = `/cms/posts/${postId}/edit`;
|
|
130
|
+
|
|
131
|
+
if (!user) return encodedRedirect("error", postEditPath, "User not authenticated.");
|
|
132
|
+
|
|
133
|
+
const { data: existingPost, error: fetchError } = await supabase
|
|
134
|
+
.from("posts")
|
|
135
|
+
.select("slug, translation_group_id, language_id")
|
|
136
|
+
.eq("id", postId)
|
|
137
|
+
.single();
|
|
138
|
+
|
|
139
|
+
if (fetchError || !existingPost) {
|
|
140
|
+
return encodedRedirect("error", "/cms/posts", "Original post not found or error fetching it.");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const featureImageIdStr_update = formData.get("feature_image_id") as string;
|
|
144
|
+
let featureImageId_update: string | null = null;
|
|
145
|
+
if (featureImageIdStr_update && featureImageIdStr_update.trim() !== "") {
|
|
146
|
+
featureImageId_update = featureImageIdStr_update;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const rawFormData = {
|
|
150
|
+
title: formData.get("title") as string,
|
|
151
|
+
slug: formData.get("slug") as string,
|
|
152
|
+
language_id: existingPost.language_id, // Use existing post's language_id
|
|
153
|
+
status: formData.get("status") as PageStatus,
|
|
154
|
+
excerpt: formData.get("excerpt") as string || null,
|
|
155
|
+
published_at: formData.get("published_at") as string || null,
|
|
156
|
+
meta_title: formData.get("meta_title") as string || null,
|
|
157
|
+
meta_description: formData.get("meta_description") as string || null,
|
|
158
|
+
feature_image_id: featureImageId_update,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
if (!rawFormData.title || !rawFormData.slug || isNaN(rawFormData.language_id) || !rawFormData.status) {
|
|
162
|
+
return encodedRedirect("error", postEditPath, "Missing required fields: title, slug, language, or status.");
|
|
163
|
+
}
|
|
164
|
+
if (rawFormData.language_id !== existingPost.language_id) {
|
|
165
|
+
return encodedRedirect("error", postEditPath, "Changing the language of an existing post version is not allowed. Create a new translation instead.");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let publishedAtISO: string | null = null;
|
|
169
|
+
if (rawFormData.published_at) {
|
|
170
|
+
const parsedDate = new Date(rawFormData.published_at);
|
|
171
|
+
if (!isNaN(parsedDate.getTime())) publishedAtISO = parsedDate.toISOString();
|
|
172
|
+
else publishedAtISO = rawFormData.published_at;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const postUpdateData: Partial<Omit<UpsertPostPayload, 'translation_group_id' | 'author_id'>> = {
|
|
176
|
+
title: rawFormData.title,
|
|
177
|
+
slug: rawFormData.slug,
|
|
178
|
+
language_id: rawFormData.language_id,
|
|
179
|
+
excerpt: rawFormData.excerpt,
|
|
180
|
+
status: rawFormData.status,
|
|
181
|
+
published_at: publishedAtISO,
|
|
182
|
+
meta_title: rawFormData.meta_title,
|
|
183
|
+
meta_description: rawFormData.meta_description,
|
|
184
|
+
feature_image_id: rawFormData.feature_image_id,
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// capture previous full content
|
|
188
|
+
const previousContent = await getFullPostContent(postId);
|
|
189
|
+
|
|
190
|
+
const { error: updateError } = await supabase
|
|
191
|
+
.from("posts")
|
|
192
|
+
.update(postUpdateData)
|
|
193
|
+
.eq("id", postId);
|
|
194
|
+
|
|
195
|
+
if (updateError) {
|
|
196
|
+
console.error("Error updating post:", updateError);
|
|
197
|
+
if (updateError.code === '23505' && updateError.message.includes('posts_language_id_slug_key')) {
|
|
198
|
+
return encodedRedirect("error", postEditPath, `The slug "${postUpdateData.slug}" already exists for the selected language. Please use a unique slug.`);
|
|
199
|
+
}
|
|
200
|
+
return encodedRedirect("error", postEditPath, `Failed to update post: ${updateError.message}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// create revision after update
|
|
204
|
+
if (previousContent && user) {
|
|
205
|
+
const newContent = await getFullPostContent(postId);
|
|
206
|
+
if (newContent) {
|
|
207
|
+
await createPostRevision(postId, user.id, previousContent, newContent);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
revalidatePath("/cms/posts");
|
|
212
|
+
if (existingPost.slug) revalidatePath(`/blog/${existingPost.slug}`);
|
|
213
|
+
if (rawFormData.slug && rawFormData.slug !== existingPost.slug) {
|
|
214
|
+
revalidatePath(`/blog/${rawFormData.slug}`);
|
|
215
|
+
}
|
|
216
|
+
revalidatePath(postEditPath);
|
|
217
|
+
redirect(`${postEditPath}?success=Post updated successfully`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
export async function deletePost(postId: number) {
|
|
222
|
+
const supabase = createClient();
|
|
223
|
+
|
|
224
|
+
// 1. Fetch the Translation Group
|
|
225
|
+
const { data: post, error: fetchError } = await supabase
|
|
226
|
+
.from("posts")
|
|
227
|
+
.select("translation_group_id")
|
|
228
|
+
.eq("id", postId)
|
|
229
|
+
.single();
|
|
230
|
+
|
|
231
|
+
if (fetchError || !post) {
|
|
232
|
+
return encodedRedirect("error", "/cms/posts", "Post not found or error fetching details.");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const { translation_group_id } = post;
|
|
236
|
+
|
|
237
|
+
// 2. Find All Related Posts
|
|
238
|
+
const { data: relatedPosts, error: relatedPostsError } = await supabase
|
|
239
|
+
.from("posts")
|
|
240
|
+
.select("slug")
|
|
241
|
+
.eq("translation_group_id", translation_group_id);
|
|
242
|
+
|
|
243
|
+
if (relatedPostsError) {
|
|
244
|
+
return encodedRedirect("error", "/cms/posts", "Could not fetch related posts for deletion.");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// 3. Delete All Associated Navigation Links
|
|
248
|
+
if (relatedPosts && relatedPosts.length > 0) {
|
|
249
|
+
const urlsToDelete = relatedPosts.map(p => `/blog/${p.slug}`);
|
|
250
|
+
const { error: navError } = await supabase
|
|
251
|
+
.from("navigation_items")
|
|
252
|
+
.delete()
|
|
253
|
+
.in("url", urlsToDelete);
|
|
254
|
+
|
|
255
|
+
if (navError) {
|
|
256
|
+
console.error("Error deleting navigation links:", navError);
|
|
257
|
+
// Not returning an error to the user, but logging it.
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// 4. Delete All Related Posts
|
|
262
|
+
const { error: deletePostsError } = await supabase
|
|
263
|
+
.from("posts")
|
|
264
|
+
.delete()
|
|
265
|
+
.eq("translation_group_id", translation_group_id);
|
|
266
|
+
|
|
267
|
+
if (deletePostsError) {
|
|
268
|
+
return encodedRedirect("error", "/cms/posts", `Failed to delete posts: ${deletePostsError.message}`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Revalidate paths
|
|
272
|
+
revalidatePath("/cms/posts");
|
|
273
|
+
revalidatePath("/cms/navigation");
|
|
274
|
+
if (relatedPosts) {
|
|
275
|
+
relatedPosts.forEach(p => {
|
|
276
|
+
if (p.slug) {
|
|
277
|
+
revalidatePath(`/blog/${p.slug}`);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// 5. Update Redirect Message
|
|
283
|
+
redirect(`/cms/posts?success=${encodeURIComponent("Post and all its translations were deleted successfully.")}`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Helper function (already defined in page actions, ensure consistent or shared)
|
|
287
|
+
function generatePlaceholderSlug(title: string, langCode: string): string {
|
|
288
|
+
const baseSlug = title.toLowerCase()
|
|
289
|
+
.replace(/\s+/g, '-')
|
|
290
|
+
.replace(/[^\w-]+/g, '')
|
|
291
|
+
.replace(/--+/g, '-')
|
|
292
|
+
.replace(/^-+|-+$/g, '')
|
|
293
|
+
.substring(0, 70);
|
|
294
|
+
return `${baseSlug}-${langCode}-${uuidv4().substring(0, 6)}`;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
type UpsertPostPayload = {
|
|
298
|
+
language_id: number;
|
|
299
|
+
author_id: string | null;
|
|
300
|
+
title: string;
|
|
301
|
+
slug: string;
|
|
302
|
+
excerpt?: string | null;
|
|
303
|
+
status: PageStatus;
|
|
304
|
+
published_at?: string | null;
|
|
305
|
+
meta_title?: string | null;
|
|
306
|
+
meta_description?: string | null;
|
|
307
|
+
translation_group_id: string;
|
|
308
|
+
feature_image_id?: string | null;
|
|
309
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// app/cms/posts/components/DeletePostButtonClient.tsx
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
import React, { useState, useRef } from 'react';
|
|
5
|
+
import { DropdownMenuItem } from "@nextblock-cms/ui";
|
|
6
|
+
import { Trash2 } from "lucide-react";
|
|
7
|
+
import { deletePost } from "../actions";
|
|
8
|
+
import { ConfirmationModal } from '@/app/cms/components/ConfirmationModal';
|
|
9
|
+
|
|
10
|
+
interface DeletePostButtonClientProps {
|
|
11
|
+
postId: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default function DeletePostButtonClient({ postId }: DeletePostButtonClientProps) {
|
|
15
|
+
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
16
|
+
const formRef = useRef<HTMLFormElement>(null);
|
|
17
|
+
|
|
18
|
+
const deletePostActionWithId = deletePost.bind(null, postId);
|
|
19
|
+
|
|
20
|
+
const handleSelect = (event: Event) => {
|
|
21
|
+
event.preventDefault();
|
|
22
|
+
setIsModalOpen(true);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const handleConfirmDelete = () => {
|
|
26
|
+
if (formRef.current) {
|
|
27
|
+
formRef.current.requestSubmit();
|
|
28
|
+
}
|
|
29
|
+
setIsModalOpen(false);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<>
|
|
34
|
+
<form action={deletePostActionWithId} ref={formRef} className="w-full">
|
|
35
|
+
{/* The button is now of type button to prevent form submission on click */}
|
|
36
|
+
<button type="button" className="w-full text-left" onClick={(e) => e.preventDefault()}>
|
|
37
|
+
<DropdownMenuItem
|
|
38
|
+
className="text-red-600 hover:!text-red-600 hover:!bg-red-50 dark:hover:!bg-red-700/20 cursor-pointer"
|
|
39
|
+
onSelect={handleSelect}
|
|
40
|
+
>
|
|
41
|
+
<Trash2 className="mr-2 h-4 w-4" />
|
|
42
|
+
Delete
|
|
43
|
+
</DropdownMenuItem>
|
|
44
|
+
</button>
|
|
45
|
+
</form>
|
|
46
|
+
<ConfirmationModal
|
|
47
|
+
isOpen={isModalOpen}
|
|
48
|
+
onClose={() => setIsModalOpen(false)}
|
|
49
|
+
onConfirm={handleConfirmDelete}
|
|
50
|
+
title="Are you sure?"
|
|
51
|
+
description="This will permanently delete the post. This action cannot be undone."
|
|
52
|
+
/>
|
|
53
|
+
</>
|
|
54
|
+
);
|
|
55
|
+
}
|