Files
biblical-guide.com/components/layout/navigation.tsx
Andrei 95070e5369 Add comprehensive page management system to admin dashboard
Features added:
- Database schema for pages and media files with content types (Rich Text, HTML, Markdown)
- Admin API routes for full page CRUD operations
- Image upload functionality with file management
- Rich text editor using TinyMCE with image insertion
- Admin interface for creating/editing pages with SEO options
- Dynamic navigation and footer integration
- Public page display routes with proper SEO metadata
- Support for featured images and content excerpts

Admin features:
- Create/edit/delete pages with rich content editor
- Upload and manage images through media library
- Configure pages to appear in navigation or footer
- Set page status (Draft, Published, Archived)
- SEO title and description management
- Real-time preview of content changes

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-24 07:26:25 +00:00

324 lines
9.3 KiB
TypeScript

'use client'
import React, { useState, useEffect } from 'react'
import {
AppBar,
Box,
Toolbar,
IconButton,
Typography,
Menu,
Container,
Avatar,
Button,
Tooltip,
MenuItem,
Drawer,
List,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText,
useMediaQuery,
useTheme,
} from '@mui/material'
import {
Menu as MenuIcon,
MenuBook,
Favorite as Prayer,
Search,
AccountCircle,
Home,
Settings,
Logout,
Login,
Bookmark,
} from '@mui/icons-material'
import { useRouter } from 'next/navigation'
import { useTranslations, useLocale } from 'next-intl'
import { LanguageSwitcher } from './language-switcher'
import { useAuth } from '@/hooks/use-auth'
interface DynamicPage {
id: string
title: string
slug: string
showInNavigation: boolean
navigationOrder?: number
}
export function Navigation() {
const [anchorElNav, setAnchorElNav] = useState<null | HTMLElement>(null)
const [anchorElUser, setAnchorElUser] = useState<null | HTMLElement>(null)
const [drawerOpen, setDrawerOpen] = useState(false)
const [dynamicPages, setDynamicPages] = useState<DynamicPage[]>([])
const router = useRouter()
const theme = useTheme()
const isMobile = useMediaQuery(theme.breakpoints.down('md'))
const t = useTranslations('navigation')
const locale = useLocale()
const { user, isAuthenticated, logout } = useAuth()
useEffect(() => {
fetchDynamicPages()
}, [])
const fetchDynamicPages = async () => {
try {
const response = await fetch('/api/pages?location=navigation')
if (response.ok) {
const data = await response.json()
setDynamicPages(data.data || [])
}
} catch (error) {
console.error('Failed to fetch dynamic pages:', error)
}
}
const basePages = [
{ name: t('home'), path: '/', icon: <Home /> },
{ name: t('bible'), path: '/bible', icon: <MenuBook /> },
{ name: t('prayers'), path: '/prayers', icon: <Prayer /> },
{ name: t('search'), path: '/search', icon: <Search /> },
]
const dynamicNavPages = dynamicPages.map(page => ({
name: page.title,
path: `/pages/${page.slug}`,
icon: null
}))
const pages = [...basePages, ...dynamicNavPages]
const authenticatedPages = [
...pages,
{ name: t('bookmarks'), path: '/bookmarks', icon: <Bookmark /> },
]
const settings = [
{ name: t('bookmarks'), icon: <Bookmark />, action: 'bookmarks' },
{ name: t('profile'), icon: <AccountCircle />, action: 'profile' },
{ name: t('settings'), icon: <Settings />, action: 'settings' },
{ name: t('logout'), icon: <Logout />, action: 'logout' },
]
const handleOpenNavMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorElNav(event.currentTarget)
}
const handleOpenUserMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorElUser(event.currentTarget)
}
const handleCloseNavMenu = () => {
setAnchorElNav(null)
}
const handleCloseUserMenu = () => {
setAnchorElUser(null)
}
const handleNavigate = (path: string) => {
const localizedPath = `/${locale}${path === '/' ? '' : path}`
router.push(localizedPath)
handleCloseNavMenu()
setDrawerOpen(false)
}
const handleUserMenuAction = (action: string) => {
handleCloseUserMenu()
switch (action) {
case 'bookmarks':
router.push(`/${locale}/bookmarks`)
break
case 'profile':
router.push(`/${locale}/profile`)
break
case 'settings':
router.push(`/${locale}/settings`)
break
case 'logout':
logout()
router.push(`/${locale}`)
break
default:
break
}
}
const handleLogin = () => {
router.push(`/${locale}/auth/login`)
}
const toggleDrawer = (open: boolean) => {
setDrawerOpen(open)
}
const DrawerList = (
<Box sx={{ width: 250 }} role="presentation">
<List>
{(isAuthenticated ? authenticatedPages : pages).map((page) => (
<ListItem key={page.name} disablePadding>
<ListItemButton onClick={() => handleNavigate(page.path)}>
<ListItemIcon sx={{ color: 'primary.main' }}>
{page.icon}
</ListItemIcon>
<ListItemText primary={page.name} />
</ListItemButton>
</ListItem>
))}
</List>
</Box>
)
return (
<>
<AppBar position="static" sx={{ bgcolor: 'primary.main' }}>
<Container maxWidth="xl">
<Toolbar disableGutters>
{/* Desktop Logo */}
<MenuBook sx={{ display: { xs: 'none', md: 'flex' }, mr: 1 }} />
<Typography
variant="h6"
noWrap
component="a"
href={`/${locale}`}
sx={{
mr: 2,
display: { xs: 'none', md: 'flex' },
fontFamily: 'monospace',
fontWeight: 700,
letterSpacing: '.3rem',
color: 'inherit',
textDecoration: 'none',
}}
>
BIBLICAL GUIDE
</Typography>
{/* Mobile Menu */}
<Box sx={{ flexGrow: 1, display: { xs: 'flex', md: 'none' } }}>
<IconButton
size="large"
aria-label="meniu principal"
aria-controls="menu-appbar"
aria-haspopup="true"
onClick={() => toggleDrawer(true)}
color="inherit"
>
<MenuIcon />
</IconButton>
</Box>
{/* Mobile Logo */}
<MenuBook sx={{ display: { xs: 'flex', md: 'none' }, mr: 1 }} />
<Typography
variant="h5"
noWrap
component="a"
href={`/${locale}`}
sx={{
mr: 2,
display: { xs: 'flex', md: 'none' },
flexGrow: 1,
fontFamily: 'monospace',
fontWeight: 700,
letterSpacing: '.3rem',
color: 'inherit',
textDecoration: 'none',
}}
>
BIBLICAL
</Typography>
{/* Desktop Menu */}
<Box sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }}>
{(isAuthenticated ? authenticatedPages : pages).map((page) => (
<Button
key={page.name}
onClick={() => handleNavigate(page.path)}
sx={{
my: 2,
color: 'white',
display: 'flex',
alignItems: 'center',
gap: 1,
mx: 1,
'&:hover': {
bgcolor: 'primary.dark',
},
}}
startIcon={page.icon}
>
{page.name}
</Button>
))}
</Box>
{/* Language Switcher */}
<LanguageSwitcher />
{/* User Menu */}
<Box sx={{ flexGrow: 0 }}>
{isAuthenticated ? (
<>
<Tooltip title={user?.name || user?.email || t('profile')}>
<IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
<Avatar sx={{ bgcolor: 'secondary.main' }}>
{user?.name ? user.name.charAt(0).toUpperCase() : <AccountCircle />}
</Avatar>
</IconButton>
</Tooltip>
<Menu
sx={{ mt: '45px' }}
id="menu-appbar"
anchorEl={anchorElUser}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
keepMounted
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
open={Boolean(anchorElUser)}
onClose={handleCloseUserMenu}
>
<MenuItem disabled>
<Typography variant="body2" color="text.secondary">
{user?.name || user?.email}
</Typography>
</MenuItem>
{settings.map((setting) => (
<MenuItem key={setting.name} onClick={() => handleUserMenuAction(setting.action)}>
<ListItemIcon>
{setting.icon}
</ListItemIcon>
<Typography textAlign="center">{setting.name}</Typography>
</MenuItem>
))}
</Menu>
</>
) : (
<Button
onClick={handleLogin}
variant="outlined"
startIcon={<Login />}
sx={{ color: 'white', borderColor: 'white', '&:hover': { borderColor: 'white', bgcolor: 'primary.dark' } }}
>
Login
</Button>
)}
</Box>
</Toolbar>
</Container>
</AppBar>
{/* Mobile Drawer */}
<Drawer anchor="left" open={drawerOpen} onClose={() => toggleDrawer(false)}>
{DrawerList}
</Drawer>
</>
)
}