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,98 @@
1
+ // app/cms/navigation/components/SortableNavItem.tsx
2
+ "use client";
3
+
4
+ import React from 'react';
5
+ import { useSortable } from '@dnd-kit/sortable';
6
+ import { CSS } from '@dnd-kit/utilities';
7
+ import { TableCell, TableRow } from "@nextblock-cms/ui";
8
+ import { Button } from '@nextblock-cms/ui';
9
+ import { Badge } from "@nextblock-cms/ui";
10
+ import { GripVertical, MoreHorizontal, Edit3 } from 'lucide-react';
11
+ import Link from 'next/link';
12
+ import {
13
+ DropdownMenu,
14
+ DropdownMenuContent,
15
+ DropdownMenuItem,
16
+ DropdownMenuTrigger,
17
+ } from "@nextblock-cms/ui";
18
+ import DeleteNavItemButton from "./DeleteNavItemButton"; // Assuming this exists and works
19
+ import type { Database } from '@nextblock-cms/db'; // Ensure this path is correct
20
+
21
+ type NavigationItem = Database['public']['Tables']['navigation_items']['Row'];
22
+
23
+ // Extended NavItem type for rendering
24
+ export interface HierarchicalNavItem extends NavigationItem {
25
+ id: number; // Ensure id is number if it comes from DB as bigint
26
+ children: HierarchicalNavItem[];
27
+ parentLabel?: string | null;
28
+ pageSlug?: string | null;
29
+ languageCode: string; // Added for consistency if needed
30
+ depth: number;
31
+ parentItem?: HierarchicalNavItem | null; // Direct reference to the parent
32
+ }
33
+
34
+ interface SortableNavItemProps {
35
+ item: HierarchicalNavItem;
36
+ // Add any other props needed for actions, like onDelete, onEdit
37
+ }
38
+
39
+ export function SortableNavItem({ item }: SortableNavItemProps) {
40
+ const {
41
+ attributes,
42
+ listeners,
43
+ setNodeRef,
44
+ transform,
45
+ transition,
46
+ isDragging,
47
+ } = useSortable({ id: item.id });
48
+
49
+ const style: React.CSSProperties = {
50
+ transform: CSS.Transform.toString(transform),
51
+ transition: transition || undefined, // Ensure transition is not null
52
+ opacity: isDragging ? 0.7 : 1,
53
+ zIndex: isDragging ? 100 : 'auto',
54
+ };
55
+
56
+ return (
57
+ <TableRow ref={setNodeRef} style={style} {...attributes} className={isDragging ? "bg-muted" : ""}>
58
+ <TableCell style={{ paddingLeft: `${item.depth * 20 + 16}px` }} className="py-2"> {/* Indentation + original padding */}
59
+ <div className="flex items-center">
60
+ <Button
61
+ variant="ghost"
62
+ size="sm"
63
+ {...listeners} // Drag handle
64
+ className="cursor-grab mr-2 p-1"
65
+ aria-label="Drag to reorder"
66
+ >
67
+ <GripVertical className="h-4 w-4 text-muted-foreground" />
68
+ </Button>
69
+ <span className="font-medium">{item.label}</span>
70
+ </div>
71
+ </TableCell>
72
+ <TableCell className="text-xs text-muted-foreground max-w-[150px] truncate py-2" title={item.url}>
73
+ {item.url}
74
+ </TableCell>
75
+ <TableCell className="py-2"><Badge variant="outline">{item.order}</Badge></TableCell>
76
+ <TableCell className="text-xs text-muted-foreground py-2">{item.parentLabel || 'None'}</TableCell>
77
+ <TableCell className="text-xs text-muted-foreground py-2">{item.pageSlug ? `/${item.pageSlug}` : 'Manual URL'}</TableCell>
78
+ <TableCell className="text-right py-2">
79
+ <DropdownMenu>
80
+ <DropdownMenuTrigger asChild>
81
+ <Button variant="ghost" size="icon">
82
+ <MoreHorizontal className="h-4 w-4" />
83
+ <span className="sr-only">Item actions for {item.label}</span>
84
+ </Button>
85
+ </DropdownMenuTrigger>
86
+ <DropdownMenuContent align="end">
87
+ <DropdownMenuItem asChild>
88
+ <Link href={`/cms/navigation/${item.id}/edit`} className="flex items-center">
89
+ <Edit3 className="mr-2 h-4 w-4" /> Edit
90
+ </Link>
91
+ </DropdownMenuItem>
92
+ <DeleteNavItemButton itemId={item.id} />
93
+ </DropdownMenuContent>
94
+ </DropdownMenu>
95
+ </TableCell>
96
+ </TableRow>
97
+ );
98
+ }
@@ -0,0 +1,26 @@
1
+ // app/cms/navigation/new/page.tsx
2
+ import NavigationItemForm from "../components/NavigationItemForm";
3
+ import { createNavigationItem } from "../actions";
4
+ import { getLanguages, getNavigationItems, getPages } from "../utils";
5
+
6
+ export default async function NewNavigationItemPage() {
7
+ const [languages, navigationItems, pages] = await Promise.all([
8
+ getLanguages(),
9
+ getNavigationItems(),
10
+ getPages(),
11
+ ]);
12
+
13
+ return (
14
+ <div className="max-w-2xl mx-auto">
15
+ <h1 className="text-2xl font-bold mb-6">Create New Navigation Item</h1>
16
+ <NavigationItemForm
17
+ formAction={createNavigationItem}
18
+ actionButtonText="Create Item"
19
+ isEditing={false}
20
+ languages={languages}
21
+ parentItems={navigationItems}
22
+ pages={pages}
23
+ />
24
+ </div>
25
+ );
26
+ }
@@ -0,0 +1,102 @@
1
+ // app/cms/navigation/page.tsx
2
+ import { createClient } from "@nextblock-cms/db/server";
3
+ import Link from "next/link";
4
+ import { Button } from "@nextblock-cms/ui";
5
+ import { PlusCircle, ListTree } from "lucide-react";
6
+ import type { Database } from "@nextblock-cms/db";
7
+ import { getActiveLanguagesServerSide } from "@nextblock-cms/db/server";
8
+
9
+ type NavigationItem = Database['public']['Tables']['navigation_items']['Row'] & { pages?: { slug: string } | null };
10
+ type MenuLocation = Database['public']['Enums']['menu_location'];
11
+ import NavigationMenuDnd from "./components/NavigationMenuDnd"; // Import the new DND component
12
+
13
+
14
+ // This function fetches ALL navigation items.
15
+ // Grouping and hierarchical transformation will happen in the client component.
16
+ async function getAllNavigationItems(): Promise<NavigationItem[]> {
17
+ const supabase = createClient();
18
+ const { data: items, error: itemsError } = await supabase
19
+ .from("navigation_items")
20
+ .select("*, pages (slug)") // Fetch linked page slug directly
21
+ .order("menu_key")
22
+ .order("language_id")
23
+ .order("parent_id", { nullsFirst: true })
24
+ .order("order");
25
+
26
+ if (itemsError) {
27
+ console.error("Error fetching all navigation items:", itemsError);
28
+ return [];
29
+ }
30
+ return items || [];
31
+ }
32
+
33
+ export default async function CmsNavigationListPage() {
34
+ const allNavItemsFlat = await getAllNavigationItems();
35
+ const allLanguages = await getActiveLanguagesServerSide();
36
+
37
+ // Group items by menu_key and then by language_id for passing to NavigationMenuDnd
38
+ const groupedItemsForDnd: Record<string, Record<number, NavigationItem[]>> = {};
39
+ allNavItemsFlat.forEach(item => {
40
+ if (!groupedItemsForDnd[item.menu_key]) {
41
+ groupedItemsForDnd[item.menu_key] = {};
42
+ }
43
+ if (!groupedItemsForDnd[item.menu_key][item.language_id]) {
44
+ groupedItemsForDnd[item.menu_key][item.language_id] = [];
45
+ }
46
+ groupedItemsForDnd[item.menu_key][item.language_id].push(item);
47
+ });
48
+
49
+ return (
50
+ <div className="w-full">
51
+ <div className="flex justify-between items-center mb-6">
52
+ <h1 className="text-2xl font-semibold">Manage Navigation</h1>
53
+ <Button variant="default" asChild>
54
+ <Link href="/cms/navigation/new">
55
+ <PlusCircle className="mr-2 h-4 w-4" /> Create New Item
56
+ </Link>
57
+ </Button>
58
+ </div>
59
+
60
+ {allNavItemsFlat.length === 0 ? (
61
+ <div className="text-center py-10 border rounded-lg">
62
+ <ListTree className="mx-auto h-12 w-12 text-muted-foreground" />
63
+ <h3 className="mt-2 text-sm font-medium text-foreground">No navigation items found</h3>
64
+ <p className="mt-1 text-sm text-muted-foreground">
65
+ Get started by creating a new navigation item.
66
+ </p>
67
+ <div className="mt-6">
68
+ <Button asChild>
69
+ <Link href="/cms/navigation/new">
70
+ <PlusCircle className="mr-2 h-4 w-4" /> Create Item
71
+ </Link>
72
+ </Button>
73
+ </div>
74
+ </div>
75
+ ) : (
76
+ <div className="space-y-8">
77
+ {Object.entries(groupedItemsForDnd).map(([menuKey, langGroups]) => (
78
+ <div key={menuKey}>
79
+ <h2 className="text-xl font-semibold mb-3 capitalize">{menuKey.toLowerCase()} Menus</h2>
80
+ {Object.entries(langGroups).map(([langIdStr, itemsInLang]) => {
81
+ const languageId = parseInt(langIdStr, 10);
82
+ const language = allLanguages.find(l => l.id === languageId);
83
+ if (!language) return null; // Should not happen if data is consistent
84
+
85
+ return (
86
+ <div key={`${menuKey}-${languageId}`} className="mb-6 p-4 border rounded-lg shadow-sm bg-card">
87
+ <h3 className="text-lg font-medium mb-4">Language: {language.name} ({language.code.toUpperCase()})</h3>
88
+ <NavigationMenuDnd
89
+ menuKey={menuKey as MenuLocation}
90
+ languageCode={language.code}
91
+ initialItems={itemsInLang}
92
+ />
93
+ </div>
94
+ );
95
+ })}
96
+ </div>
97
+ ))}
98
+ </div>
99
+ )}
100
+ </div>
101
+ );
102
+ }
@@ -0,0 +1,51 @@
1
+ // app/cms/navigation/utils.ts
2
+ "use server";
3
+
4
+ import { createClient } from "@nextblock-cms/db/server";
5
+
6
+ export async function getLanguages() {
7
+ const supabase = createClient();
8
+ const { data, error } = await supabase
9
+ .from("languages")
10
+ .select("id, code, name, is_default, is_active, created_at, updated_at")
11
+ .eq("is_active", true)
12
+ .order("name");
13
+
14
+ if (error) {
15
+ console.error("Error fetching languages:", error);
16
+ throw new Error("Could not fetch languages.");
17
+ }
18
+
19
+ return data;
20
+ }
21
+
22
+ export async function getNavigationItems() {
23
+ const supabase = createClient();
24
+ const { data, error } = await supabase
25
+ .from("navigation_items")
26
+ .select("id, label, language_id, menu_key, parent_id, translation_group_id")
27
+ .order("order");
28
+
29
+ if (error) {
30
+ console.error("Error fetching navigation items:", error);
31
+ throw new Error("Could not fetch navigation items.");
32
+ }
33
+
34
+ return data;
35
+ }
36
+
37
+ export async function getPages() {
38
+ const supabase = createClient();
39
+ const { data, error } = await supabase
40
+ .from("pages")
41
+ .select("id, title, slug, language_id")
42
+ .eq("status", "published")
43
+ .order("title");
44
+
45
+ if (error) {
46
+ console.error("Error fetching pages:", error);
47
+ throw new Error("Could not fetch pages.");
48
+ }
49
+
50
+ return data;
51
+ }
@@ -0,0 +1,121 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import React from "react";
5
+ import { Separator } from "@nextblock-cms/ui";
6
+ import { Button } from "@nextblock-cms/ui";
7
+ import { Eye, ArrowLeft } from "lucide-react";
8
+ import PageForm from "../../components/PageForm";
9
+ import BlockEditorArea from "@/app/cms/blocks/components/BlockEditorArea";
10
+ import ContentLanguageSwitcher from "@/app/cms/components/ContentLanguageSwitcher";
11
+ import CopyContentFromLanguage from "@/app/cms/components/CopyContentFromLanguage";
12
+ import RevisionHistoryButton from "@/app/cms/revisions/RevisionHistoryButton";
13
+ import { UploadFolderProvider } from "@/app/cms/media/UploadFolderContext";
14
+ import type { Database } from "@nextblock-cms/db";
15
+
16
+ type Page = Database["public"]["Tables"]["pages"]["Row"];
17
+ type Block = Database["public"]["Tables"]["blocks"]["Row"];
18
+ type Language = Database["public"]["Tables"]["languages"]["Row"];
19
+
20
+ interface PageWithBlocks extends Page {
21
+ blocks: Block[];
22
+ language_code?: string;
23
+ translation_group_id: string;
24
+ }
25
+
26
+ interface EditPageClientProps {
27
+ page: PageWithBlocks;
28
+ pageId: number;
29
+ allSiteLanguages: Language[];
30
+ updatePageAction: (formData: FormData) => Promise<{ error?: string } | void>;
31
+ publicPageUrl: string;
32
+ }
33
+
34
+ export default function EditPageClient({
35
+ page,
36
+ pageId,
37
+ allSiteLanguages,
38
+ updatePageAction,
39
+ publicPageUrl,
40
+ }: EditPageClientProps) {
41
+ return (
42
+ <UploadFolderProvider defaultFolder={`pages/${page.slug}/`}>
43
+ <div className="space-y-8 w-full mx-auto px-6">
44
+ <div className="flex justify-between items-center flex-wrap gap-4 w-full">
45
+ <div className="flex items-center gap-3">
46
+ <Button
47
+ variant="outline"
48
+ size="icon"
49
+ aria-label="Back to pages"
50
+ asChild
51
+ >
52
+ <Link href="/cms/pages">
53
+ <ArrowLeft className="h-4 w-4" />
54
+ </Link>
55
+ </Button>
56
+ <div>
57
+ <h1 className="text-2xl font-bold">Edit Page</h1>
58
+ <p
59
+ className="text-sm text-muted-foreground truncate max-w-md"
60
+ title={page.title}
61
+ >
62
+ {page.title}
63
+ </p>
64
+ </div>
65
+ </div>
66
+ <div className="flex items-center gap-3 flex-wrap">
67
+ {allSiteLanguages.length > 0 && (
68
+ <ContentLanguageSwitcher
69
+ currentItem={{
70
+ ...page,
71
+ translation_group_id: page.translation_group_id ?? "",
72
+ }}
73
+ itemType="page"
74
+ allSiteLanguages={allSiteLanguages}
75
+ />
76
+ )}
77
+ {page.translation_group_id && allSiteLanguages.length > 1 && (
78
+ <CopyContentFromLanguage
79
+ parentId={pageId}
80
+ parentType="page"
81
+ currentLanguageId={page.language_id}
82
+ translationGroupId={page.translation_group_id}
83
+ allSiteLanguages={allSiteLanguages}
84
+ />
85
+ )}
86
+ <Button variant="outline" asChild>
87
+ <Link
88
+ href={publicPageUrl}
89
+ target="_blank"
90
+ rel="noopener noreferrer"
91
+ >
92
+ <Eye className="mr-2 h-4 w-4" /> View Live
93
+ </Link>
94
+ </Button>
95
+ <RevisionHistoryButton parentType="page" parentId={pageId} />
96
+ </div>
97
+ </div>
98
+
99
+ <PageForm
100
+ page={page}
101
+ formAction={updatePageAction}
102
+ actionButtonText="Update Page Metadata"
103
+ isEditing={true}
104
+ availableLanguagesProp={allSiteLanguages}
105
+ />
106
+
107
+ <Separator className="my-8" />
108
+
109
+ <div className="w-full mx-auto px-6">
110
+ <h2 className="text-xl font-semibold mb-4">Page Content Blocks</h2>
111
+ <BlockEditorArea
112
+ parentId={page.id}
113
+ parentType="page"
114
+ initialBlocks={page.blocks}
115
+ languageId={page.language_id}
116
+ />
117
+ </div>
118
+ </div>
119
+ </UploadFolderProvider>
120
+ );
121
+ }
@@ -0,0 +1,79 @@
1
+ // app/cms/pages/[id]/edit/page.tsx
2
+ import React from "react";
3
+ import { createClient } from "@nextblock-cms/db/server";
4
+ import { updatePage } from "../../actions";
5
+ import type { Database } from "@nextblock-cms/db";
6
+ import { notFound, redirect } from "next/navigation";
7
+
8
+ type Page = Database['public']['Tables']['pages']['Row'];
9
+ type Block = Database['public']['Tables']['blocks']['Row'];
10
+ type Language = Database['public']['Tables']['languages']['Row'];
11
+ import { getActiveLanguagesServerSide } from "@nextblock-cms/db/server";
12
+ import EditPageClient from "./EditPageClient";
13
+
14
+ // ... (Interface PageWithBlocks and getPageDataWithBlocks remain the same) ...
15
+ interface PageWithBlocks extends Page {
16
+ blocks: Block[];
17
+ language_code?: string;
18
+ translation_group_id: string;
19
+ }
20
+
21
+ async function getPageDataWithBlocks(id: number): Promise<PageWithBlocks | null> {
22
+ const supabase = createClient();
23
+ const { data: pageData, error: pageError } = await supabase
24
+ .from("pages")
25
+ .select(`
26
+ *,
27
+ languages!inner (code),
28
+ blocks (*)
29
+ `)
30
+ .eq("id", id)
31
+ .order('order', { foreignTable: 'blocks', ascending: true })
32
+ .single();
33
+
34
+ if (pageError) {
35
+ console.error("Error fetching page with blocks for edit:", pageError);
36
+ return null;
37
+ }
38
+
39
+ const langCode = Array.isArray(pageData.languages)
40
+ ? pageData.languages[0]?.code
41
+ : (pageData.languages as Language)?.code;
42
+
43
+ return { ...pageData, blocks: pageData.blocks || [], language_code: langCode } as PageWithBlocks;
44
+ }
45
+
46
+
47
+ export default async function EditPage(props: { params: Promise<{ id: string }> }) {
48
+ const params = await props.params;
49
+ const pageId = parseInt(params.id, 10);
50
+ if (isNaN(pageId)) return notFound();
51
+
52
+ const supabase = createClient();
53
+ const { data: { user } } = await supabase.auth.getUser();
54
+ if (!user) return redirect(`/sign-in?redirect=/cms/pages/${pageId}/edit`);
55
+ const { data: profile } = await supabase.from('profiles').select('role').eq('id', user.id).single();
56
+ if (!profile || !['ADMIN', 'WRITER'].includes(profile.role)) {
57
+ return <div className="p-6">Access Denied.</div>;
58
+ }
59
+
60
+ const [pageWithBlocks, allSiteLanguages] = await Promise.all([
61
+ getPageDataWithBlocks(pageId),
62
+ getActiveLanguagesServerSide()
63
+ ]);
64
+
65
+ if (!pageWithBlocks) return notFound();
66
+
67
+ const updatePageWithId = updatePage.bind(null, pageId);
68
+ const publicPageUrl = `/${pageWithBlocks.slug}`;
69
+
70
+ return (
71
+ <EditPageClient
72
+ page={pageWithBlocks}
73
+ pageId={pageId}
74
+ allSiteLanguages={allSiteLanguages}
75
+ updatePageAction={updatePageWithId}
76
+ publicPageUrl={publicPageUrl}
77
+ />
78
+ );
79
+ }