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,232 @@
|
|
|
1
|
+
// app/cms/pages/page.tsx
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { createClient } from "@nextblock-cms/db/server";
|
|
4
|
+
import Link from "next/link";
|
|
5
|
+
import { Button } from "@nextblock-cms/ui";
|
|
6
|
+
import {
|
|
7
|
+
Table,
|
|
8
|
+
TableBody,
|
|
9
|
+
TableCell,
|
|
10
|
+
TableHead,
|
|
11
|
+
TableHeader,
|
|
12
|
+
TableRow,
|
|
13
|
+
} from "@nextblock-cms/ui";
|
|
14
|
+
import { Badge } from "@nextblock-cms/ui";
|
|
15
|
+
import {
|
|
16
|
+
MoreHorizontal,
|
|
17
|
+
PlusCircle,
|
|
18
|
+
Edit3,
|
|
19
|
+
FileText,
|
|
20
|
+
} from "lucide-react"; // Trash2 removed from here
|
|
21
|
+
import {
|
|
22
|
+
DropdownMenu,
|
|
23
|
+
DropdownMenuContent,
|
|
24
|
+
DropdownMenuItem,
|
|
25
|
+
DropdownMenuTrigger,
|
|
26
|
+
DropdownMenuSeparator,
|
|
27
|
+
} from "@nextblock-cms/ui";
|
|
28
|
+
// Server action `deletePage` is used by DeletePageButtonClient
|
|
29
|
+
import type { Database } from "@nextblock-cms/db";
|
|
30
|
+
import { getActiveLanguagesServerSide } from "@nextblock-cms/db/server";
|
|
31
|
+
|
|
32
|
+
type Page = Database["public"]["Tables"]["pages"]["Row"];
|
|
33
|
+
import LanguageFilterSelect from "@/app/cms/components/LanguageFilterSelect";
|
|
34
|
+
import DeletePageButtonClient from "./components/DeletePageButtonClient"; // Import the client component
|
|
35
|
+
|
|
36
|
+
async function getPagesWithDetails(
|
|
37
|
+
filterLanguageId?: number
|
|
38
|
+
): Promise<{ page: Page; languageCode: string }[]> {
|
|
39
|
+
const supabase = createClient();
|
|
40
|
+
const languages = await getActiveLanguagesServerSide();
|
|
41
|
+
const langMap = new Map(languages.map((l) => [l.id, l.code]));
|
|
42
|
+
|
|
43
|
+
let query = supabase
|
|
44
|
+
.from("pages")
|
|
45
|
+
.select("*, languages!inner(code)")
|
|
46
|
+
.order("created_at", { ascending: false });
|
|
47
|
+
|
|
48
|
+
if (filterLanguageId) {
|
|
49
|
+
query = query.eq("language_id", filterLanguageId);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const { data: pagesData, error } = await query;
|
|
53
|
+
|
|
54
|
+
if (error) {
|
|
55
|
+
console.error("Error fetching pages:", error);
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
if (!pagesData) return [];
|
|
59
|
+
|
|
60
|
+
return pagesData.map((p) => {
|
|
61
|
+
const langInfo = p.languages as unknown as { code: string } | null;
|
|
62
|
+
return {
|
|
63
|
+
page: p as Page,
|
|
64
|
+
languageCode:
|
|
65
|
+
langInfo?.code?.toUpperCase() ||
|
|
66
|
+
langMap.get(p.language_id)?.toUpperCase() ||
|
|
67
|
+
"N/A",
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface CmsPagesListPageProps {
|
|
73
|
+
searchParams?: Promise<{
|
|
74
|
+
lang?: string;
|
|
75
|
+
success?: string;
|
|
76
|
+
}>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export default async function CmsPagesListPage(props: CmsPagesListPageProps) {
|
|
80
|
+
const searchParams = await props.searchParams;
|
|
81
|
+
const allLanguages = await getActiveLanguagesServerSide();
|
|
82
|
+
const selectedLangId = searchParams?.lang
|
|
83
|
+
? parseInt(searchParams.lang, 10)
|
|
84
|
+
: undefined;
|
|
85
|
+
|
|
86
|
+
const isValidLangId = selectedLangId
|
|
87
|
+
? allLanguages.some((l) => l.id === selectedLangId)
|
|
88
|
+
: true;
|
|
89
|
+
const filterLangId = isValidLangId ? selectedLangId : undefined;
|
|
90
|
+
|
|
91
|
+
const pagesWithDetails = await getPagesWithDetails(filterLangId);
|
|
92
|
+
const successMessage = searchParams?.success;
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div className="w-full">
|
|
96
|
+
<div className="flex justify-between items-center mb-6 flex-wrap gap-4">
|
|
97
|
+
<h1 className="text-2xl font-semibold">Manage Pages</h1>
|
|
98
|
+
<div className="flex items-center gap-3">
|
|
99
|
+
<LanguageFilterSelect
|
|
100
|
+
allLanguages={allLanguages}
|
|
101
|
+
currentFilterLangId={filterLangId}
|
|
102
|
+
basePath="/cms/pages"
|
|
103
|
+
/>
|
|
104
|
+
<Button variant="default" asChild>
|
|
105
|
+
<Link href="/cms/pages/new">
|
|
106
|
+
<PlusCircle className="mr-2 h-4 w-4" /> Create New Page
|
|
107
|
+
</Link>
|
|
108
|
+
</Button>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
{successMessage && (
|
|
113
|
+
<div className="mb-4 p-3 rounded-md text-sm bg-green-100 text-green-700 border border-green-200 dark:bg-green-900/30 dark:text-green-300 dark:border-green-700">
|
|
114
|
+
{decodeURIComponent(successMessage)}
|
|
115
|
+
</div>
|
|
116
|
+
)}
|
|
117
|
+
|
|
118
|
+
{pagesWithDetails.length === 0 ? (
|
|
119
|
+
<div className="text-center py-10 border rounded-lg dark:border-slate-700">
|
|
120
|
+
<FileText className="mx-auto h-12 w-12 text-muted-foreground" />
|
|
121
|
+
<h3 className="mt-2 text-sm font-medium text-foreground">
|
|
122
|
+
{filterLangId
|
|
123
|
+
? "No pages found for the selected language."
|
|
124
|
+
: "No pages found."}
|
|
125
|
+
</h3>
|
|
126
|
+
<p className="mt-1 text-sm text-muted-foreground">
|
|
127
|
+
Get started by creating a new page.
|
|
128
|
+
</p>
|
|
129
|
+
<div className="mt-6">
|
|
130
|
+
<Button asChild>
|
|
131
|
+
<Link href="/cms/pages/new">
|
|
132
|
+
<PlusCircle className="mr-2 h-4 w-4" /> Create Page
|
|
133
|
+
</Link>
|
|
134
|
+
</Button>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
) : (
|
|
138
|
+
<div className="rounded-lg border overflow-hidden dark:border-slate-700">
|
|
139
|
+
<Table>
|
|
140
|
+
<TableHeader>
|
|
141
|
+
<TableRow className="dark:border-slate-700">
|
|
142
|
+
<TableHead className="w-[300px] sm:w-[400px]">Title</TableHead>
|
|
143
|
+
<TableHead>Status</TableHead>
|
|
144
|
+
<TableHead>Language</TableHead>
|
|
145
|
+
<TableHead className="hidden md:table-cell">Slug</TableHead>
|
|
146
|
+
<TableHead className="hidden lg:table-cell">
|
|
147
|
+
Last Updated
|
|
148
|
+
</TableHead>
|
|
149
|
+
<TableHead className="text-right w-[80px]">Actions</TableHead>
|
|
150
|
+
</TableRow>
|
|
151
|
+
</TableHeader>
|
|
152
|
+
<TableBody>
|
|
153
|
+
{pagesWithDetails.map(({ page, languageCode }) => (
|
|
154
|
+
<TableRow key={page.id} className="dark:border-slate-700">
|
|
155
|
+
<TableCell className="font-medium">
|
|
156
|
+
<Link
|
|
157
|
+
href={`/cms/pages/${page.id}/edit`}
|
|
158
|
+
className="flex items-center cursor-pointer"
|
|
159
|
+
>
|
|
160
|
+
<Edit3 className="mr-2 h-4 w-4" />
|
|
161
|
+
{page.title}
|
|
162
|
+
</Link>
|
|
163
|
+
</TableCell>
|
|
164
|
+
<TableCell>
|
|
165
|
+
<Badge
|
|
166
|
+
variant={
|
|
167
|
+
page.status === "published"
|
|
168
|
+
? "default"
|
|
169
|
+
: page.status === "draft"
|
|
170
|
+
? "secondary"
|
|
171
|
+
: "destructive"
|
|
172
|
+
}
|
|
173
|
+
className={
|
|
174
|
+
page.status === "published"
|
|
175
|
+
? "bg-green-100 text-green-700 dark:bg-green-700/30 dark:text-green-300 dark:border-green-700/50"
|
|
176
|
+
: page.status === "draft"
|
|
177
|
+
? "bg-yellow-100 text-yellow-700 dark:bg-yellow-700/30 dark:text-yellow-300 dark:border-yellow-700/50"
|
|
178
|
+
: "bg-slate-100 text-slate-700 dark:bg-slate-700/30 dark:text-slate-300 dark:border-slate-600"
|
|
179
|
+
}
|
|
180
|
+
>
|
|
181
|
+
{page.status.charAt(0).toUpperCase() +
|
|
182
|
+
page.status.slice(1)}
|
|
183
|
+
</Badge>
|
|
184
|
+
</TableCell>
|
|
185
|
+
<TableCell>
|
|
186
|
+
<Badge variant="outline" className="dark:border-slate-600">
|
|
187
|
+
{languageCode}
|
|
188
|
+
</Badge>
|
|
189
|
+
</TableCell>
|
|
190
|
+
<TableCell className="text-muted-foreground text-xs hidden md:table-cell">
|
|
191
|
+
/{page.slug}
|
|
192
|
+
</TableCell>
|
|
193
|
+
<TableCell className="hidden lg:table-cell text-xs text-muted-foreground">
|
|
194
|
+
{new Date(page.updated_at).toLocaleDateString()}
|
|
195
|
+
</TableCell>
|
|
196
|
+
<TableCell className="text-right">
|
|
197
|
+
<DropdownMenu>
|
|
198
|
+
<DropdownMenuTrigger asChild>
|
|
199
|
+
<Button variant="ghost" size="icon">
|
|
200
|
+
<MoreHorizontal className="h-4 w-4" />
|
|
201
|
+
<span className="sr-only">
|
|
202
|
+
Page actions for {page.title}
|
|
203
|
+
</span>
|
|
204
|
+
</Button>
|
|
205
|
+
</DropdownMenuTrigger>
|
|
206
|
+
<DropdownMenuContent align="end">
|
|
207
|
+
<DropdownMenuItem asChild>
|
|
208
|
+
<Link
|
|
209
|
+
href={`/cms/pages/${page.id}/edit`}
|
|
210
|
+
className="flex items-center cursor-pointer"
|
|
211
|
+
>
|
|
212
|
+
<Edit3 className="mr-2 h-4 w-4" /> Edit
|
|
213
|
+
</Link>
|
|
214
|
+
</DropdownMenuItem>
|
|
215
|
+
<DropdownMenuSeparator />
|
|
216
|
+
{/* Use the Client Component for the delete button */}
|
|
217
|
+
<DeletePageButtonClient
|
|
218
|
+
pageId={page.id}
|
|
219
|
+
pageTitle={page.title}
|
|
220
|
+
/>
|
|
221
|
+
</DropdownMenuContent>
|
|
222
|
+
</DropdownMenu>
|
|
223
|
+
</TableCell>
|
|
224
|
+
</TableRow>
|
|
225
|
+
))}
|
|
226
|
+
</TableBody>
|
|
227
|
+
</Table>
|
|
228
|
+
</div>
|
|
229
|
+
)}
|
|
230
|
+
</div>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// app/cms/posts/[id]/edit/page.tsx
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Separator } from "@nextblock-cms/ui";
|
|
4
|
+
import { createClient } from "@nextblock-cms/db/server";
|
|
5
|
+
import PostForm from "../../components/PostForm"; // Adjusted path
|
|
6
|
+
import { updatePost } from "../../actions";
|
|
7
|
+
import type { Database } from "@nextblock-cms/db"; // Ensure Language and Media are imported
|
|
8
|
+
import { notFound, redirect } from "next/navigation";
|
|
9
|
+
|
|
10
|
+
type PostType = Database['public']['Tables']['posts']['Row'];
|
|
11
|
+
type BlockType = Database['public']['Tables']['blocks']['Row'];
|
|
12
|
+
type Language = Database['public']['Tables']['languages']['Row'];
|
|
13
|
+
import BlockEditorArea from "@/app/cms/blocks/components/BlockEditorArea";
|
|
14
|
+
import Link from "next/link";
|
|
15
|
+
import { Button } from "@nextblock-cms/ui";
|
|
16
|
+
import { Eye, ArrowLeft } from "lucide-react"; // Removed SeparatorVertical, use <Separator />
|
|
17
|
+
import ContentLanguageSwitcher from "@/app/cms/components/ContentLanguageSwitcher";
|
|
18
|
+
import { getActiveLanguagesServerSide } from "@nextblock-cms/db/server"; // Correct server-side fetch
|
|
19
|
+
import CopyContentFromLanguage from "@/app/cms/components/CopyContentFromLanguage";
|
|
20
|
+
import { UploadFolderProvider } from "@/app/cms/media/UploadFolderContext";
|
|
21
|
+
import RevisionHistoryButton from "@/app/cms/revisions/RevisionHistoryButton";
|
|
22
|
+
|
|
23
|
+
interface PostWithBlocks extends PostType {
|
|
24
|
+
blocks: BlockType[];
|
|
25
|
+
language_code?: string; // From joined languages table
|
|
26
|
+
translation_group_id: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function getPostDataWithBlocks(id: number): Promise<PostWithBlocks | null> {
|
|
30
|
+
const supabase = createClient();
|
|
31
|
+
const { data: postData, error: postError } = await supabase
|
|
32
|
+
.from("posts")
|
|
33
|
+
.select(`
|
|
34
|
+
*,
|
|
35
|
+
languages!inner (code),
|
|
36
|
+
blocks (*)
|
|
37
|
+
`)
|
|
38
|
+
.eq("id", id)
|
|
39
|
+
.order('order', { foreignTable: 'blocks', ascending: true })
|
|
40
|
+
.single();
|
|
41
|
+
|
|
42
|
+
if (postError) {
|
|
43
|
+
console.error("Error fetching post with blocks for edit:", postError);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const langCode = (postData.languages as unknown as Language)?.code;
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
...postData,
|
|
51
|
+
blocks: postData.blocks || [],
|
|
52
|
+
language_code: langCode,
|
|
53
|
+
translation_group_id: postData.translation_group_id,
|
|
54
|
+
} as PostWithBlocks;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default async function EditPostPage(props: { params: Promise<{ id: string }> }) {
|
|
58
|
+
const params = await props.params;
|
|
59
|
+
const postId = parseInt(params.id, 10);
|
|
60
|
+
if (isNaN(postId)) {
|
|
61
|
+
return notFound();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const supabase = createClient();
|
|
65
|
+
const { data: { user } } = await supabase.auth.getUser();
|
|
66
|
+
if (!user) return redirect(`/sign-in?redirect=/cms/posts/${postId}/edit`);
|
|
67
|
+
|
|
68
|
+
const { data: profile } = await supabase.from('profiles').select('role').eq('id', user.id).single();
|
|
69
|
+
if (!profile || !['ADMIN', 'WRITER'].includes(profile.role)) {
|
|
70
|
+
return <div className="p-6 text-center text-red-600">Access Denied. You do not have permission to edit posts.</div>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Fetch post data and all site languages concurrently
|
|
74
|
+
const [postWithBlocks, allSiteLanguages] = await Promise.all([
|
|
75
|
+
getPostDataWithBlocks(postId),
|
|
76
|
+
getActiveLanguagesServerSide() // Fetch languages on the server
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
if (!postWithBlocks) {
|
|
80
|
+
return notFound();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let initialFeatureImageUrl: string | null = null;
|
|
84
|
+
let initialFeatureImageIdProp: string | null = null;
|
|
85
|
+
|
|
86
|
+
// The PostType defines feature_image_id as number | null.
|
|
87
|
+
// However, Media.id is a string (uuid), and PostForm expects a string UUID.
|
|
88
|
+
// This assumes that postWithBlocks.feature_image_id, despite its 'number' typing,
|
|
89
|
+
// actually holds a value that can be used to identify a media item by its UUID,
|
|
90
|
+
// or that the type definition for Post.feature_image_id is outdated.
|
|
91
|
+
// Casting to `unknown` then `string` for the query.
|
|
92
|
+
const featureImageIdFromDb = postWithBlocks.feature_image_id as unknown as (string | number | null);
|
|
93
|
+
|
|
94
|
+
if (featureImageIdFromDb) {
|
|
95
|
+
const { data: mediaItem, error: mediaError } = await supabase
|
|
96
|
+
.from("media")
|
|
97
|
+
.select("id, object_key")
|
|
98
|
+
.eq("id", String(featureImageIdFromDb)) // Query using the ID as string
|
|
99
|
+
.single();
|
|
100
|
+
|
|
101
|
+
if (mediaError) {
|
|
102
|
+
console.error(`Error fetching media item for feature_image_id '${featureImageIdFromDb}':`, mediaError.message);
|
|
103
|
+
// Not critical enough to notFound(), form will just not have initial image.
|
|
104
|
+
} else if (mediaItem) {
|
|
105
|
+
initialFeatureImageIdProp = mediaItem.id; // string UUID from media table
|
|
106
|
+
const r2BaseUrl = process.env.NEXT_PUBLIC_R2_BASE_URL;
|
|
107
|
+
if (r2BaseUrl && mediaItem.object_key) {
|
|
108
|
+
initialFeatureImageUrl = `${r2BaseUrl}/${mediaItem.object_key}`;
|
|
109
|
+
} else if (!r2BaseUrl) {
|
|
110
|
+
console.warn("NEXT_PUBLIC_R2_PUBLIC_URL is not set. Cannot construct feature image URL for edit page.");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const updatePostWithId = updatePost.bind(null, postId);
|
|
116
|
+
const publicPostUrl = `/blog/${postWithBlocks.slug}`;
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<UploadFolderProvider defaultFolder={`posts/${postWithBlocks.slug}/`}>
|
|
120
|
+
<div className="space-y-8 w-full mx-auto px-6">
|
|
121
|
+
<div className="flex justify-between items-center flex-wrap gap-4 w-full">
|
|
122
|
+
<div className="flex items-center gap-3">
|
|
123
|
+
<Button variant="outline" size="icon" aria-label="Back to posts" asChild>
|
|
124
|
+
<Link href="/cms/posts">
|
|
125
|
+
<ArrowLeft className="h-4 w-4" />
|
|
126
|
+
</Link>
|
|
127
|
+
</Button>
|
|
128
|
+
<div>
|
|
129
|
+
<h1 className="text-2xl font-bold">Edit Post</h1>
|
|
130
|
+
<p className="text-sm text-muted-foreground truncate max-w-md" title={postWithBlocks.title}>{postWithBlocks.title}</p>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
<div className="flex items-center gap-3 flex-wrap"> {/* Added flex-wrap for responsiveness */}
|
|
134
|
+
{allSiteLanguages.length > 0 && (
|
|
135
|
+
<ContentLanguageSwitcher
|
|
136
|
+
currentItem={postWithBlocks}
|
|
137
|
+
itemType="post"
|
|
138
|
+
allSiteLanguages={allSiteLanguages}
|
|
139
|
+
/>
|
|
140
|
+
)}
|
|
141
|
+
{postWithBlocks.translation_group_id && allSiteLanguages.length > 1 && (
|
|
142
|
+
<CopyContentFromLanguage
|
|
143
|
+
parentId={postId}
|
|
144
|
+
parentType="post"
|
|
145
|
+
currentLanguageId={postWithBlocks.language_id}
|
|
146
|
+
translationGroupId={postWithBlocks.translation_group_id}
|
|
147
|
+
allSiteLanguages={allSiteLanguages}
|
|
148
|
+
/>
|
|
149
|
+
)}
|
|
150
|
+
<Button variant="outline" asChild>
|
|
151
|
+
<Link href={publicPostUrl} target="_blank" rel="noopener noreferrer">
|
|
152
|
+
<Eye className="mr-2 h-4 w-4" /> View Live Post
|
|
153
|
+
</Link>
|
|
154
|
+
</Button>
|
|
155
|
+
<RevisionHistoryButton parentType="post" parentId={postId} />
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<PostForm
|
|
160
|
+
post={postWithBlocks as PostType & { feature_image_id?: string | null }} // Asserting feature_image_id might be string for PostForm
|
|
161
|
+
formAction={updatePostWithId}
|
|
162
|
+
actionButtonText="Update Post Metadata"
|
|
163
|
+
isEditing={true}
|
|
164
|
+
availableLanguagesProp={allSiteLanguages}
|
|
165
|
+
initialFeatureImageUrl={initialFeatureImageUrl}
|
|
166
|
+
initialFeatureImageId={initialFeatureImageIdProp}
|
|
167
|
+
/>
|
|
168
|
+
|
|
169
|
+
<Separator className="my-8" />
|
|
170
|
+
|
|
171
|
+
<div className="w-full mx-auto px-6">
|
|
172
|
+
<h2 className="text-xl font-semibold mb-4">Post Content Blocks</h2>
|
|
173
|
+
<BlockEditorArea
|
|
174
|
+
parentId={postWithBlocks.id}
|
|
175
|
+
parentType="post"
|
|
176
|
+
initialBlocks={postWithBlocks.blocks}
|
|
177
|
+
languageId={postWithBlocks.language_id}
|
|
178
|
+
/>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
</UploadFolderProvider>
|
|
182
|
+
);
|
|
183
|
+
}
|