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,321 @@
|
|
|
1
|
+
// app/cms/CmsClientLayout.tsx
|
|
2
|
+
"use client"
|
|
3
|
+
|
|
4
|
+
import React, { type ReactNode, useEffect } from "react"
|
|
5
|
+
import { useAuth } from "@/context/AuthContext"
|
|
6
|
+
import { useRouter, usePathname } from "next/navigation" // Import usePathname
|
|
7
|
+
import Link from "next/link"
|
|
8
|
+
import {
|
|
9
|
+
LayoutDashboard, FileText, PenTool, Users, Settings, ChevronRight, LogOut, Menu, ListTree, Image as ImageIconLucide, X, Languages as LanguagesIconLucide, MessageSquare,
|
|
10
|
+
Copyright as CopyrightIcon,
|
|
11
|
+
} from "lucide-react"
|
|
12
|
+
import { Button } from "@nextblock-cms/ui"
|
|
13
|
+
import { Avatar, AvatarFallback, AvatarImage } from "@nextblock-cms/ui"
|
|
14
|
+
import { cn } from "@nextblock-cms/utils"
|
|
15
|
+
import { signOutAction } from "@/app/actions";
|
|
16
|
+
|
|
17
|
+
const LoadingSpinner = () => (
|
|
18
|
+
<div className="flex justify-center items-center h-full w-full py-20">
|
|
19
|
+
<div className="relative">
|
|
20
|
+
<div className="h-16 w-16 rounded-full border-t-4 border-b-4 border-primary animate-spin"></div>
|
|
21
|
+
<div className="absolute inset-0 flex items-center justify-center">
|
|
22
|
+
<div className="h-10 w-10 rounded-full bg-background"></div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
type NavItemProps = {
|
|
29
|
+
href: string
|
|
30
|
+
icon: React.ElementType
|
|
31
|
+
children: React.ReactNode
|
|
32
|
+
isActive?: boolean
|
|
33
|
+
adminOnly?: boolean
|
|
34
|
+
writerOnly?: boolean
|
|
35
|
+
isAdmin?: boolean
|
|
36
|
+
isWriter?: boolean
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const NavItem = ({ href, icon: Icon, children, isActive, adminOnly, writerOnly, isAdmin, isWriter }: NavItemProps) => {
|
|
40
|
+
if (adminOnly && !isAdmin) return null
|
|
41
|
+
if (writerOnly && !isWriter && !isAdmin) return null
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<li>
|
|
45
|
+
<Link
|
|
46
|
+
href={href}
|
|
47
|
+
className={cn(
|
|
48
|
+
"flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium transition-all",
|
|
49
|
+
isActive
|
|
50
|
+
? "bg-primary/10 text-primary dark:bg-primary/20"
|
|
51
|
+
: "text-slate-600 hover:text-primary hover:bg-primary/5 dark:text-slate-300 dark:hover:bg-primary/10",
|
|
52
|
+
)}
|
|
53
|
+
>
|
|
54
|
+
<Icon className="h-5 w-5" />
|
|
55
|
+
<span>{children}</span>
|
|
56
|
+
{isActive && <ChevronRight className="h-4 w-4 ml-auto" />}
|
|
57
|
+
</Link>
|
|
58
|
+
</li>
|
|
59
|
+
)
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
type CollapsibleNavItemProps = {
|
|
63
|
+
icon: React.ElementType
|
|
64
|
+
title: string
|
|
65
|
+
children: React.ReactNode
|
|
66
|
+
isActive?: boolean
|
|
67
|
+
adminOnly?: boolean
|
|
68
|
+
isAdmin?: boolean
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const CollapsibleNavItem = ({ icon: Icon, title, children, isActive, adminOnly, isAdmin }: CollapsibleNavItemProps) => {
|
|
72
|
+
const [isOpen, setIsOpen] = React.useState(isActive);
|
|
73
|
+
|
|
74
|
+
if (adminOnly && !isAdmin) return null
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<li>
|
|
78
|
+
<button
|
|
79
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
80
|
+
className={cn(
|
|
81
|
+
"flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium transition-all w-full",
|
|
82
|
+
isActive
|
|
83
|
+
? "bg-primary/10 text-primary dark:bg-primary/20"
|
|
84
|
+
: "text-slate-600 hover:text-primary hover:bg-primary/5 dark:text-slate-300 dark:hover:bg-primary/10",
|
|
85
|
+
)}
|
|
86
|
+
>
|
|
87
|
+
<Icon className="h-5 w-5" />
|
|
88
|
+
<span>{title}</span>
|
|
89
|
+
<ChevronRight className={cn("h-4 w-4 ml-auto transition-transform", isOpen && "rotate-90")} />
|
|
90
|
+
</button>
|
|
91
|
+
{isOpen && (
|
|
92
|
+
<ul className="pl-6 space-y-1.5 mt-1.5">
|
|
93
|
+
{children}
|
|
94
|
+
</ul>
|
|
95
|
+
)}
|
|
96
|
+
</li>
|
|
97
|
+
)
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
export default function CmsClientLayout({ children }: { children: ReactNode }) {
|
|
102
|
+
const { user, profile, role, isLoading, isAdmin, isWriter } = useAuth();
|
|
103
|
+
const router = useRouter();
|
|
104
|
+
const pathname = usePathname(); // Use the usePathname hook
|
|
105
|
+
const [cmsSidebarOpen, setCmsSidebarOpen] = React.useState(false);
|
|
106
|
+
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
if (!isLoading) {
|
|
109
|
+
if (!user) {
|
|
110
|
+
router.push("/sign-in?redirect=/cms/dashboard");
|
|
111
|
+
} else if (!isWriter && !isAdmin) {
|
|
112
|
+
router.push("/unauthorized?reason=insufficient_role_in_layout");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}, [user, role, isLoading, router, isAdmin, isWriter]);
|
|
116
|
+
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
const mainLayoutElement = document.querySelector('body > div > main > div.flex-1.w-full.flex.flex-col.items-center');
|
|
119
|
+
if (mainLayoutElement) {
|
|
120
|
+
mainLayoutElement.classList.remove('max-w-7xl');
|
|
121
|
+
(mainLayoutElement as HTMLElement).style.padding = '0';
|
|
122
|
+
}
|
|
123
|
+
const mainScreenChild = document.querySelector('main.min-h-screen > div.flex-1.w-full');
|
|
124
|
+
if (mainScreenChild) {
|
|
125
|
+
mainScreenChild.classList.remove("max-w-7xl");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const handleResize = () => {
|
|
129
|
+
if (window.innerWidth >= 768) {
|
|
130
|
+
setCmsSidebarOpen(true);
|
|
131
|
+
} else {
|
|
132
|
+
// setCmsSidebarOpen(false); // Removed to allow manual toggle on mobile
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
handleResize();
|
|
136
|
+
window.addEventListener('resize', handleResize);
|
|
137
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
138
|
+
}, []);
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
// With server-side auth data, isLoading is initially false.
|
|
142
|
+
// We show a spinner only if something client-side sets it to true (e.g., during logout).
|
|
143
|
+
if (isLoading) {
|
|
144
|
+
return <LoadingSpinner />;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// The useEffect handles redirection if the server-provided user/role is insufficient.
|
|
148
|
+
// Returning null here prevents rendering the layout for unauthorized users.
|
|
149
|
+
if (!user || (!isWriter && !isAdmin)) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const getInitials = () => {
|
|
154
|
+
if (profile && profile.full_name) return profile.full_name.split(' ').map((n: string) => n[0]).join('').substring(0,2).toUpperCase();
|
|
155
|
+
if (profile && profile.username) return profile.username.substring(0,2).toUpperCase();
|
|
156
|
+
if (user && user.email) return user.email.charAt(0).toUpperCase();
|
|
157
|
+
return "U"; // Default fallback
|
|
158
|
+
}
|
|
159
|
+
const getRoleColor = () => {
|
|
160
|
+
if (isAdmin) return "bg-amber-500";
|
|
161
|
+
if (isWriter) return "bg-emerald-500";
|
|
162
|
+
return "bg-sky-500"; // Default color
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// pageTitle logic should now work reliably with usePathname
|
|
166
|
+
let pageTitle = "CMS"; // Default title
|
|
167
|
+
if (pathname === "/cms/dashboard") pageTitle = "Dashboard";
|
|
168
|
+
else if (pathname.startsWith("/cms/pages/new")) pageTitle = "New Page";
|
|
169
|
+
else if (pathname.startsWith("/cms/pages/") && pathname.endsWith("/edit")) pageTitle = "Edit Page";
|
|
170
|
+
else if (pathname.startsWith("/cms/pages")) pageTitle = "Pages";
|
|
171
|
+
else if (pathname.startsWith("/cms/posts/new")) pageTitle = "New Post";
|
|
172
|
+
else if (pathname.startsWith("/cms/posts/") && pathname.endsWith("/edit")) pageTitle = "Edit Post";
|
|
173
|
+
else if (pathname.startsWith("/cms/posts")) pageTitle = "Posts";
|
|
174
|
+
else if (pathname.startsWith("/cms/navigation/new")) pageTitle = "New Navigation Item";
|
|
175
|
+
else if (pathname.startsWith("/cms/navigation/") && pathname.includes("/edit")) pageTitle = "Edit Navigation Item";
|
|
176
|
+
else if (pathname.startsWith("/cms/navigation")) pageTitle = "Navigation";
|
|
177
|
+
else if (pathname.startsWith("/cms/media/") && pathname.endsWith("/edit")) pageTitle = "Edit Media Item";
|
|
178
|
+
else if (pathname.startsWith("/cms/media")) pageTitle = "Media Library";
|
|
179
|
+
else if (pathname.startsWith("/cms/users/") && pathname.endsWith("/edit")) pageTitle = "Edit User Profile";
|
|
180
|
+
else if (pathname.startsWith("/cms/users")) pageTitle = "User Management";
|
|
181
|
+
else if (pathname.startsWith("/cms/settings/languages/new")) pageTitle = "New Language";
|
|
182
|
+
else if (pathname.startsWith("/cms/settings/languages") && pathname.includes("/edit")) pageTitle = "Edit Language";
|
|
183
|
+
else if (pathname.startsWith("/cms/settings/languages")) pageTitle = "Language Settings";
|
|
184
|
+
// Fallback for general /cms/settings if no more specific language path matches
|
|
185
|
+
else if (pathname.startsWith("/cms/settings/logos")) pageTitle = "Logos";
|
|
186
|
+
else if (pathname.startsWith("/cms/settings/copyright")) pageTitle = "Copyright Settings";
|
|
187
|
+
else if (pathname.startsWith("/cms/settings/extra-translations")) pageTitle = "Extra Translations";
|
|
188
|
+
else if (pathname.startsWith("/cms/settings")) pageTitle = "Settings";
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<div className="w-full flex flex-col md:flex-row bg-slate-50 dark:bg-slate-950 min-h-screen">
|
|
193
|
+
<div className="fixed bottom-4 right-4 z-[60] md:hidden">
|
|
194
|
+
<Button
|
|
195
|
+
variant="outline"
|
|
196
|
+
size="icon"
|
|
197
|
+
onClick={() => setCmsSidebarOpen(!cmsSidebarOpen)}
|
|
198
|
+
className="bg-white shadow-lg dark:bg-slate-800 rounded-full h-12 w-12"
|
|
199
|
+
>
|
|
200
|
+
{cmsSidebarOpen ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
|
|
201
|
+
</Button>
|
|
202
|
+
</div>
|
|
203
|
+
|
|
204
|
+
<aside
|
|
205
|
+
className={cn(
|
|
206
|
+
"fixed md:sticky top-0 left-0 h-screen w-64 bg-white shadow-lg transition-transform duration-300 ease-in-out dark:bg-slate-900 dark:border-r dark:border-slate-700/60",
|
|
207
|
+
"md:translate-x-0",
|
|
208
|
+
cmsSidebarOpen ? "translate-x-0" : "-translate-x-full",
|
|
209
|
+
"z-30"
|
|
210
|
+
)}
|
|
211
|
+
>
|
|
212
|
+
<div className="flex flex-col h-full">
|
|
213
|
+
<div className="p-4 border-b dark:border-slate-700/60 h-16 flex items-center shrink-0">
|
|
214
|
+
<Link href="/cms/dashboard" className="flex items-center gap-2 px-2">
|
|
215
|
+
<div className="h-8 w-8 rounded-md bg-gradient-to-br from-primary to-primary/80 flex items-center justify-center text-white font-bold">
|
|
216
|
+
NRH
|
|
217
|
+
</div>
|
|
218
|
+
<h2 className="text-xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-primary to-primary/70 dark:from-primary dark:to-primary/80">
|
|
219
|
+
CMS
|
|
220
|
+
</h2>
|
|
221
|
+
</Link>
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
<nav className="px-3 py-4 flex-1 overflow-y-auto">
|
|
225
|
+
<ul className="space-y-1.5">
|
|
226
|
+
<NavItem href="/cms/dashboard" icon={LayoutDashboard} isActive={pathname === "/cms/dashboard"} isAdmin={isAdmin} isWriter={isWriter}>
|
|
227
|
+
Dashboard
|
|
228
|
+
</NavItem>
|
|
229
|
+
<NavItem href="/cms/pages" icon={FileText} isActive={pathname.startsWith("/cms/pages")} writerOnly isAdmin={isAdmin} isWriter={isWriter}>
|
|
230
|
+
Pages
|
|
231
|
+
</NavItem>
|
|
232
|
+
<NavItem href="/cms/posts" icon={PenTool} isActive={pathname.startsWith("/cms/posts")} writerOnly isAdmin={isAdmin} isWriter={isWriter}>
|
|
233
|
+
Posts
|
|
234
|
+
</NavItem>
|
|
235
|
+
<NavItem href="/cms/media" icon={ImageIconLucide} isActive={pathname.startsWith("/cms/media")} writerOnly isAdmin={isAdmin} isWriter={isWriter}>
|
|
236
|
+
Media
|
|
237
|
+
</NavItem>
|
|
238
|
+
|
|
239
|
+
{isAdmin && (
|
|
240
|
+
<>
|
|
241
|
+
<div className="mt-6 mb-2">
|
|
242
|
+
<p className="px-3 text-xs font-semibold text-slate-500 uppercase tracking-wider dark:text-slate-400">
|
|
243
|
+
Administration
|
|
244
|
+
</p>
|
|
245
|
+
</div>
|
|
246
|
+
<NavItem href="/cms/navigation" icon={ListTree} isActive={pathname.startsWith("/cms/navigation")} adminOnly isAdmin={isAdmin}>
|
|
247
|
+
Navigation
|
|
248
|
+
</NavItem>
|
|
249
|
+
<NavItem href="/cms/users" icon={Users} isActive={pathname.startsWith("/cms/users")} adminOnly isAdmin={isAdmin}>
|
|
250
|
+
Manage Users
|
|
251
|
+
</NavItem>
|
|
252
|
+
<CollapsibleNavItem
|
|
253
|
+
icon={Settings}
|
|
254
|
+
title="Settings"
|
|
255
|
+
isActive={pathname.startsWith("/cms/settings")}
|
|
256
|
+
adminOnly
|
|
257
|
+
isAdmin={isAdmin}
|
|
258
|
+
>
|
|
259
|
+
<NavItem href="/cms/settings/languages" icon={LanguagesIconLucide} isActive={pathname.startsWith("/cms/settings/languages")} adminOnly isAdmin={isAdmin}>
|
|
260
|
+
Languages
|
|
261
|
+
</NavItem>
|
|
262
|
+
<NavItem href="/cms/settings/logos" icon={ImageIconLucide} isActive={pathname.startsWith("/cms/settings/logos")} adminOnly isAdmin={isAdmin}>
|
|
263
|
+
Logos
|
|
264
|
+
</NavItem>
|
|
265
|
+
<NavItem href="/cms/settings/copyright" icon={CopyrightIcon} isActive={pathname.startsWith("/cms/settings/copyright")} adminOnly isAdmin={isAdmin}>
|
|
266
|
+
Copyright
|
|
267
|
+
</NavItem>
|
|
268
|
+
<NavItem href="/cms/settings/extra-translations" icon={MessageSquare} isActive={pathname.startsWith("/cms/settings/extra-translations")} adminOnly isAdmin={isAdmin}>
|
|
269
|
+
Extra Translations
|
|
270
|
+
</NavItem>
|
|
271
|
+
</CollapsibleNavItem>
|
|
272
|
+
</>
|
|
273
|
+
)}
|
|
274
|
+
</ul>
|
|
275
|
+
</nav>
|
|
276
|
+
|
|
277
|
+
<div className="mt-auto p-3 border-t border-slate-200 dark:border-slate-700/60 shrink-0">
|
|
278
|
+
<div className="flex items-center gap-3">
|
|
279
|
+
<Avatar className="h-10 w-10 border">
|
|
280
|
+
<AvatarImage src={profile?.avatar_url || undefined} alt={profile?.username || user?.email} />
|
|
281
|
+
<AvatarFallback className="bg-primary/10 text-primary font-medium">{getInitials()}</AvatarFallback>
|
|
282
|
+
</Avatar>
|
|
283
|
+
<div className="flex-1 min-w-0">
|
|
284
|
+
<p className="text-sm font-medium truncate text-slate-700 dark:text-slate-200">{profile?.full_name || profile?.username || user?.email}</p>
|
|
285
|
+
<div className="flex items-center gap-1.5">
|
|
286
|
+
<div className={cn("h-2 w-2 rounded-full", getRoleColor())}></div>
|
|
287
|
+
<p className="text-xs text-slate-500 dark:text-slate-400 capitalize">{role}</p>
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
<form action={signOutAction}>
|
|
291
|
+
<Button type="submit" variant="ghost" size="icon" className="text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-300" title="Sign Out">
|
|
292
|
+
<LogOut className="h-4 w-4" />
|
|
293
|
+
</Button>
|
|
294
|
+
</form>
|
|
295
|
+
</div>
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
</aside>
|
|
299
|
+
|
|
300
|
+
<div className="flex-1 transition-all duration-300 ease-in-out w-full">
|
|
301
|
+
<header className="bg-background dark:bg-slate-800/30 border-b border-border h-16 flex items-center px-6 sticky top-0 z-20 w-full shrink-0">
|
|
302
|
+
<Button variant="ghost" size="icon" className="md:hidden mr-3 -ml-2" onClick={() => setCmsSidebarOpen(!cmsSidebarOpen)}>
|
|
303
|
+
<Menu className="h-5 w-5" />
|
|
304
|
+
</Button>
|
|
305
|
+
<h1 className="text-lg font-semibold text-foreground">
|
|
306
|
+
{pageTitle}
|
|
307
|
+
</h1>
|
|
308
|
+
</header>
|
|
309
|
+
<main className="p-6 w-full overflow-y-auto h-[calc(100vh-4rem)]">
|
|
310
|
+
{children}
|
|
311
|
+
</main>
|
|
312
|
+
</div>
|
|
313
|
+
{cmsSidebarOpen && ! (typeof window !== 'undefined' && window.innerWidth >= 768) && (
|
|
314
|
+
<div
|
|
315
|
+
className="fixed inset-0 bg-black/30 z-20 md:hidden"
|
|
316
|
+
onClick={() => setCmsSidebarOpen(false)}
|
|
317
|
+
/>
|
|
318
|
+
)}
|
|
319
|
+
</div>
|
|
320
|
+
)
|
|
321
|
+
}
|