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.
Files changed (206) hide show
  1. package/bin/create-nextblock.js +997 -0
  2. package/package.json +25 -0
  3. package/scripts/sync-template.js +284 -0
  4. package/templates/nextblock-template/.env.example +37 -0
  5. package/templates/nextblock-template/.swcrc +30 -0
  6. package/templates/nextblock-template/README.md +194 -0
  7. package/templates/nextblock-template/app/(auth-pages)/forgot-password/page.tsx +57 -0
  8. package/templates/nextblock-template/app/(auth-pages)/layout.tsx +9 -0
  9. package/templates/nextblock-template/app/(auth-pages)/post-sign-in/page.tsx +28 -0
  10. package/templates/nextblock-template/app/(auth-pages)/sign-in/page.tsx +67 -0
  11. package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +70 -0
  12. package/templates/nextblock-template/app/ToasterProvider.tsx +17 -0
  13. package/templates/nextblock-template/app/[slug]/PageClientContent.tsx +147 -0
  14. package/templates/nextblock-template/app/[slug]/page.tsx +145 -0
  15. package/templates/nextblock-template/app/[slug]/page.utils.ts +183 -0
  16. package/templates/nextblock-template/app/actions/email.ts +31 -0
  17. package/templates/nextblock-template/app/actions/formActions.ts +65 -0
  18. package/templates/nextblock-template/app/actions/languageActions.ts +130 -0
  19. package/templates/nextblock-template/app/actions/postActions.ts +80 -0
  20. package/templates/nextblock-template/app/actions.ts +146 -0
  21. package/templates/nextblock-template/app/api/process-image/route.ts +210 -0
  22. package/templates/nextblock-template/app/api/revalidate/route.ts +86 -0
  23. package/templates/nextblock-template/app/api/revalidate-log/route.ts +23 -0
  24. package/templates/nextblock-template/app/api/upload/presigned-url/route.ts +106 -0
  25. package/templates/nextblock-template/app/api/upload/proxy/route.ts +84 -0
  26. package/templates/nextblock-template/app/auth/callback/route.ts +58 -0
  27. package/templates/nextblock-template/app/blog/[slug]/PostClientContent.tsx +169 -0
  28. package/templates/nextblock-template/app/blog/[slug]/page.tsx +177 -0
  29. package/templates/nextblock-template/app/blog/[slug]/page.utils.ts +136 -0
  30. package/templates/nextblock-template/app/blog/page.tsx +77 -0
  31. package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +321 -0
  32. package/templates/nextblock-template/app/cms/blocks/actions.ts +434 -0
  33. package/templates/nextblock-template/app/cms/blocks/components/BackgroundSelector.tsx +348 -0
  34. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorArea.tsx +567 -0
  35. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +98 -0
  36. package/templates/nextblock-template/app/cms/blocks/components/BlockTypeCard.tsx +58 -0
  37. package/templates/nextblock-template/app/cms/blocks/components/BlockTypeSelector.tsx +62 -0
  38. package/templates/nextblock-template/app/cms/blocks/components/ColumnEditor.tsx +276 -0
  39. package/templates/nextblock-template/app/cms/blocks/components/DeleteBlockButtonClient.tsx +47 -0
  40. package/templates/nextblock-template/app/cms/blocks/components/EditableBlock.tsx +182 -0
  41. package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +120 -0
  42. package/templates/nextblock-template/app/cms/blocks/components/SectionConfigPanel.tsx +133 -0
  43. package/templates/nextblock-template/app/cms/blocks/components/SortableBlockItem.tsx +46 -0
  44. package/templates/nextblock-template/app/cms/blocks/editors/ButtonBlockEditor.tsx +85 -0
  45. package/templates/nextblock-template/app/cms/blocks/editors/FormBlockEditor.tsx +182 -0
  46. package/templates/nextblock-template/app/cms/blocks/editors/HeadingBlockEditor.tsx +111 -0
  47. package/templates/nextblock-template/app/cms/blocks/editors/ImageBlockEditor.tsx +150 -0
  48. package/templates/nextblock-template/app/cms/blocks/editors/PostsGridBlockEditor.tsx +79 -0
  49. package/templates/nextblock-template/app/cms/blocks/editors/SectionBlockEditor.tsx +337 -0
  50. package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +81 -0
  51. package/templates/nextblock-template/app/cms/blocks/editors/VideoEmbedBlockEditor.tsx +64 -0
  52. package/templates/nextblock-template/app/cms/components/ConfirmationModal.tsx +51 -0
  53. package/templates/nextblock-template/app/cms/components/ContentLanguageSwitcher.tsx +145 -0
  54. package/templates/nextblock-template/app/cms/components/CopyContentFromLanguage.tsx +203 -0
  55. package/templates/nextblock-template/app/cms/components/LanguageFilterSelect.tsx +69 -0
  56. package/templates/nextblock-template/app/cms/dashboard/page.tsx +247 -0
  57. package/templates/nextblock-template/app/cms/layout.tsx +10 -0
  58. package/templates/nextblock-template/app/cms/media/UploadFolderContext.tsx +22 -0
  59. package/templates/nextblock-template/app/cms/media/[id]/edit/page.tsx +80 -0
  60. package/templates/nextblock-template/app/cms/media/actions.ts +577 -0
  61. package/templates/nextblock-template/app/cms/media/components/DeleteMediaButtonClient.tsx +53 -0
  62. package/templates/nextblock-template/app/cms/media/components/FolderNavigator.tsx +273 -0
  63. package/templates/nextblock-template/app/cms/media/components/FolderTree.tsx +122 -0
  64. package/templates/nextblock-template/app/cms/media/components/MediaEditForm.tsx +157 -0
  65. package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +275 -0
  66. package/templates/nextblock-template/app/cms/media/components/MediaImage.tsx +70 -0
  67. package/templates/nextblock-template/app/cms/media/components/MediaPickerDialog.tsx +195 -0
  68. package/templates/nextblock-template/app/cms/media/components/MediaUploadForm.tsx +362 -0
  69. package/templates/nextblock-template/app/cms/media/page.tsx +120 -0
  70. package/templates/nextblock-template/app/cms/navigation/[id]/edit/page.tsx +101 -0
  71. package/templates/nextblock-template/app/cms/navigation/actions.ts +358 -0
  72. package/templates/nextblock-template/app/cms/navigation/components/DeleteNavItemButton.tsx +52 -0
  73. package/templates/nextblock-template/app/cms/navigation/components/NavigationItemForm.tsx +248 -0
  74. package/templates/nextblock-template/app/cms/navigation/components/NavigationLanguageSwitcher.tsx +132 -0
  75. package/templates/nextblock-template/app/cms/navigation/components/NavigationMenuDnd.tsx +701 -0
  76. package/templates/nextblock-template/app/cms/navigation/components/SortableNavItem.tsx +98 -0
  77. package/templates/nextblock-template/app/cms/navigation/new/page.tsx +26 -0
  78. package/templates/nextblock-template/app/cms/navigation/page.tsx +102 -0
  79. package/templates/nextblock-template/app/cms/navigation/utils.ts +51 -0
  80. package/templates/nextblock-template/app/cms/pages/[id]/edit/EditPageClient.tsx +121 -0
  81. package/templates/nextblock-template/app/cms/pages/[id]/edit/page.tsx +79 -0
  82. package/templates/nextblock-template/app/cms/pages/actions.ts +241 -0
  83. package/templates/nextblock-template/app/cms/pages/components/DeletePageButtonClient.tsx +47 -0
  84. package/templates/nextblock-template/app/cms/pages/components/PageForm.tsx +253 -0
  85. package/templates/nextblock-template/app/cms/pages/new/page.tsx +52 -0
  86. package/templates/nextblock-template/app/cms/pages/page.tsx +232 -0
  87. package/templates/nextblock-template/app/cms/posts/[id]/edit/page.tsx +183 -0
  88. package/templates/nextblock-template/app/cms/posts/actions.ts +309 -0
  89. package/templates/nextblock-template/app/cms/posts/components/DeletePostButtonClient.tsx +55 -0
  90. package/templates/nextblock-template/app/cms/posts/components/PostForm.tsx +419 -0
  91. package/templates/nextblock-template/app/cms/posts/new/page.tsx +21 -0
  92. package/templates/nextblock-template/app/cms/posts/page.tsx +192 -0
  93. package/templates/nextblock-template/app/cms/revisions/JsonDiffView.tsx +86 -0
  94. package/templates/nextblock-template/app/cms/revisions/RevisionHistoryButton.tsx +201 -0
  95. package/templates/nextblock-template/app/cms/revisions/actions.ts +84 -0
  96. package/templates/nextblock-template/app/cms/revisions/service.ts +344 -0
  97. package/templates/nextblock-template/app/cms/revisions/utils.ts +127 -0
  98. package/templates/nextblock-template/app/cms/settings/copyright/actions.ts +68 -0
  99. package/templates/nextblock-template/app/cms/settings/copyright/components/CopyrightForm.tsx +78 -0
  100. package/templates/nextblock-template/app/cms/settings/copyright/page.tsx +32 -0
  101. package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +117 -0
  102. package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +216 -0
  103. package/templates/nextblock-template/app/cms/settings/languages/[id]/edit/page.tsx +77 -0
  104. package/templates/nextblock-template/app/cms/settings/languages/actions.ts +261 -0
  105. package/templates/nextblock-template/app/cms/settings/languages/components/DeleteLanguageButton.tsx +76 -0
  106. package/templates/nextblock-template/app/cms/settings/languages/components/LanguageForm.tsx +167 -0
  107. package/templates/nextblock-template/app/cms/settings/languages/new/page.tsx +34 -0
  108. package/templates/nextblock-template/app/cms/settings/languages/page.tsx +156 -0
  109. package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +19 -0
  110. package/templates/nextblock-template/app/cms/settings/logos/actions.ts +114 -0
  111. package/templates/nextblock-template/app/cms/settings/logos/components/LogoForm.tsx +177 -0
  112. package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +11 -0
  113. package/templates/nextblock-template/app/cms/settings/logos/page.tsx +118 -0
  114. package/templates/nextblock-template/app/cms/settings/logos/types.ts +8 -0
  115. package/templates/nextblock-template/app/cms/users/[id]/edit/page.tsx +91 -0
  116. package/templates/nextblock-template/app/cms/users/actions.ts +156 -0
  117. package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +71 -0
  118. package/templates/nextblock-template/app/cms/users/components/UserForm.tsx +138 -0
  119. package/templates/nextblock-template/app/cms/users/page.tsx +183 -0
  120. package/templates/nextblock-template/app/favicon.ico +0 -0
  121. package/templates/nextblock-template/app/globals.css +401 -0
  122. package/templates/nextblock-template/app/layout.tsx +191 -0
  123. package/templates/nextblock-template/app/lib/sitemap-utils.ts +68 -0
  124. package/templates/nextblock-template/app/page.tsx +109 -0
  125. package/templates/nextblock-template/app/providers.tsx +43 -0
  126. package/templates/nextblock-template/app/robots.txt/route.ts +19 -0
  127. package/templates/nextblock-template/app/sitemap.xml/route.ts +63 -0
  128. package/templates/nextblock-template/app/unauthorized/page.tsx +27 -0
  129. package/templates/nextblock-template/backup/backup_2025-06-19.sql +8057 -0
  130. package/templates/nextblock-template/backup/backup_2025-06-20.sql +8159 -0
  131. package/templates/nextblock-template/backup/backup_2025-07-08.sql +8411 -0
  132. package/templates/nextblock-template/backup/backup_2025-07-09.sql +8442 -0
  133. package/templates/nextblock-template/backup/backup_2025-07-10.sql +8442 -0
  134. package/templates/nextblock-template/backup/backup_2025-10-01.sql +8803 -0
  135. package/templates/nextblock-template/backup/backup_2025-10-02.sql +9749 -0
  136. package/templates/nextblock-template/components/BlockRenderer.tsx +119 -0
  137. package/templates/nextblock-template/components/FooterNavigation.tsx +33 -0
  138. package/templates/nextblock-template/components/Header.tsx +42 -0
  139. package/templates/nextblock-template/components/HtmlScriptExecutor.tsx +47 -0
  140. package/templates/nextblock-template/components/LanguageSwitcher.tsx +103 -0
  141. package/templates/nextblock-template/components/ResponsiveNav.tsx +372 -0
  142. package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +17 -0
  143. package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +93 -0
  144. package/templates/nextblock-template/components/blocks/PostsGridClient.tsx +180 -0
  145. package/templates/nextblock-template/components/blocks/renderers/ButtonBlockRenderer.tsx +92 -0
  146. package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +69 -0
  147. package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +98 -0
  148. package/templates/nextblock-template/components/blocks/renderers/HeadingBlockRenderer.tsx +41 -0
  149. package/templates/nextblock-template/components/blocks/renderers/HeroBlockRenderer.tsx +240 -0
  150. package/templates/nextblock-template/components/blocks/renderers/ImageBlockRenderer.tsx +79 -0
  151. package/templates/nextblock-template/components/blocks/renderers/PostsGridBlockRenderer.tsx +33 -0
  152. package/templates/nextblock-template/components/blocks/renderers/SectionBlockRenderer.tsx +189 -0
  153. package/templates/nextblock-template/components/blocks/renderers/TextBlockRenderer.tsx +31 -0
  154. package/templates/nextblock-template/components/blocks/renderers/VideoEmbedBlockRenderer.tsx +59 -0
  155. package/templates/nextblock-template/components/blocks/renderers/inline/AlertWidgetRenderer.tsx +51 -0
  156. package/templates/nextblock-template/components/blocks/renderers/inline/CtaWidgetRenderer.tsx +40 -0
  157. package/templates/nextblock-template/components/blocks/types.ts +8 -0
  158. package/templates/nextblock-template/components/env-var-warning.tsx +33 -0
  159. package/templates/nextblock-template/components/form-message.tsx +26 -0
  160. package/templates/nextblock-template/components/header-auth.tsx +71 -0
  161. package/templates/nextblock-template/components/submit-button.tsx +23 -0
  162. package/templates/nextblock-template/components/theme-switcher.tsx +78 -0
  163. package/templates/nextblock-template/context/AuthContext.tsx +138 -0
  164. package/templates/nextblock-template/context/CurrentContentContext.tsx +42 -0
  165. package/templates/nextblock-template/context/LanguageContext.tsx +206 -0
  166. package/templates/nextblock-template/docs/cms-application-overview.md +56 -0
  167. package/templates/nextblock-template/docs/cms-architecture-overview.md +73 -0
  168. package/templates/nextblock-template/docs/files-structure.md +426 -0
  169. package/templates/nextblock-template/docs/tiptap-bundle-optimization-summary.md +174 -0
  170. package/templates/nextblock-template/eslint.config.mjs +28 -0
  171. package/templates/nextblock-template/index.d.ts +5 -0
  172. package/templates/nextblock-template/lib/blocks/README.md +670 -0
  173. package/templates/nextblock-template/lib/blocks/blockRegistry.ts +1001 -0
  174. package/templates/nextblock-template/lib/ui/ColorPicker.ts +1 -0
  175. package/templates/nextblock-template/lib/ui/ConfirmationDialog.ts +1 -0
  176. package/templates/nextblock-template/lib/ui/CustomSelectWithInput.ts +1 -0
  177. package/templates/nextblock-template/lib/ui/Skeleton.ts +1 -0
  178. package/templates/nextblock-template/lib/ui/avatar.ts +1 -0
  179. package/templates/nextblock-template/lib/ui/badge.ts +1 -0
  180. package/templates/nextblock-template/lib/ui/button.ts +1 -0
  181. package/templates/nextblock-template/lib/ui/card.ts +1 -0
  182. package/templates/nextblock-template/lib/ui/checkbox.ts +1 -0
  183. package/templates/nextblock-template/lib/ui/dialog.ts +1 -0
  184. package/templates/nextblock-template/lib/ui/dropdown-menu.ts +1 -0
  185. package/templates/nextblock-template/lib/ui/input.ts +1 -0
  186. package/templates/nextblock-template/lib/ui/label.ts +1 -0
  187. package/templates/nextblock-template/lib/ui/popover.ts +1 -0
  188. package/templates/nextblock-template/lib/ui/progress.ts +1 -0
  189. package/templates/nextblock-template/lib/ui/select.ts +1 -0
  190. package/templates/nextblock-template/lib/ui/separator.ts +1 -0
  191. package/templates/nextblock-template/lib/ui/table.ts +1 -0
  192. package/templates/nextblock-template/lib/ui/textarea.ts +1 -0
  193. package/templates/nextblock-template/lib/ui/tooltip.ts +1 -0
  194. package/templates/nextblock-template/lib/ui/ui.ts +1 -0
  195. package/templates/nextblock-template/middleware.ts +206 -0
  196. package/templates/nextblock-template/next-env.d.ts +6 -0
  197. package/templates/nextblock-template/next.config.js +99 -0
  198. package/templates/nextblock-template/package.json +52 -0
  199. package/templates/nextblock-template/postcss.config.js +6 -0
  200. package/templates/nextblock-template/project.json +7 -0
  201. package/templates/nextblock-template/public/.gitkeep +0 -0
  202. package/templates/nextblock-template/scripts/backfill-image-meta.ts +149 -0
  203. package/templates/nextblock-template/scripts/backup.js +53 -0
  204. package/templates/nextblock-template/scripts/test-bundle-optimization.js +114 -0
  205. package/templates/nextblock-template/tailwind.config.ts +19 -0
  206. 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
+ }