Add comprehensive social media management system and improve admin pages
## Social Media Management System - Add SocialMediaLink database model with platform, URL, icon, and ordering - Create complete CRUD API endpoints for admin social media management - Implement admin social media management page with Material-UI DataGrid - Add "Social Media" menu item to admin navigation - Update footer to dynamically load and display enabled social media links - Support multiple platforms: Facebook, Twitter, Instagram, YouTube, LinkedIn, GitHub, TikTok - Include proper icon mapping and fallback handling ## Admin Pages Improvements - Replace broken TinyMCE editor with working WYSIWYG rich text editor - Create SimpleRichEditor component with toolbar for formatting - Fix admin authentication to use cookies instead of localStorage tokens - Update all admin API calls to use credentials: 'include' - Increase content editor height to 800px for better editing experience - Add Lexical editor component as alternative (not currently used) ## Footer Pages System - Create 8 comprehensive footer pages: About, Blog, Support, API Docs, Terms, Privacy, Cookies, GDPR - Implement dynamic footer link management with smart categorization - Separate Quick Links and Legal sections with automatic filtering - Remove duplicate hardcoded links and use database-driven system - All footer pages are fully written with professional content ## Database & Dependencies - Add uuid package for ID generation - Update Prisma schema with new SocialMediaLink model and relations - Seed default social media links for Facebook, Twitter, Instagram, YouTube - Add Lexical rich text editor packages (@lexical/react, etc.) ## Technical Improvements - Fix async params compatibility for Next.js 15 - Update MUI DataGrid deprecated props - Improve admin layout navigation structure - Add proper TypeScript interfaces for all new components - Implement proper error handling and user feedback 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,10 @@ import {
|
||||
Twitter,
|
||||
Instagram,
|
||||
YouTube,
|
||||
LinkedIn,
|
||||
GitHub,
|
||||
MusicNote as TikTok,
|
||||
Share as DefaultIcon
|
||||
} from '@mui/icons-material'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useTranslations, useLocale } from 'next-intl'
|
||||
@@ -27,8 +31,18 @@ interface DynamicPage {
|
||||
footerOrder?: number
|
||||
}
|
||||
|
||||
interface SocialMediaLink {
|
||||
id: string
|
||||
platform: string
|
||||
name: string
|
||||
url: string
|
||||
icon: string
|
||||
order: number
|
||||
}
|
||||
|
||||
export function Footer() {
|
||||
const [dynamicPages, setDynamicPages] = useState<DynamicPage[]>([])
|
||||
const [socialLinks, setSocialLinks] = useState<SocialMediaLink[]>([])
|
||||
const router = useRouter()
|
||||
const t = useTranslations('home')
|
||||
const tSeo = useTranslations('seo')
|
||||
@@ -36,6 +50,7 @@ export function Footer() {
|
||||
|
||||
useEffect(() => {
|
||||
fetchDynamicPages()
|
||||
fetchSocialLinks()
|
||||
}, [])
|
||||
|
||||
const fetchDynamicPages = async () => {
|
||||
@@ -50,10 +65,36 @@ export function Footer() {
|
||||
}
|
||||
}
|
||||
|
||||
const fetchSocialLinks = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/social-media')
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
setSocialLinks(data.data || [])
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch social media links:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const getCurrentYear = () => {
|
||||
return new Date().getFullYear()
|
||||
}
|
||||
|
||||
const renderSocialIcon = (iconName: string) => {
|
||||
const iconMap = {
|
||||
'Facebook': Facebook,
|
||||
'Twitter': Twitter,
|
||||
'Instagram': Instagram,
|
||||
'YouTube': YouTube,
|
||||
'LinkedIn': LinkedIn,
|
||||
'GitHub': GitHub,
|
||||
'TikTok': TikTok
|
||||
}
|
||||
const IconComponent = iconMap[iconName as keyof typeof iconMap] || DefaultIcon
|
||||
return <IconComponent />
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper component="footer" sx={{ bgcolor: 'grey.900', color: 'white', py: 6 }}>
|
||||
<Container maxWidth="lg">
|
||||
@@ -74,12 +115,7 @@ export function Footer() {
|
||||
{t('footer.quickLinks.title')}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Button color="inherit" sx={{ justifyContent: 'flex-start', p: 0 }}>
|
||||
{t('footer.quickLinks.about')}
|
||||
</Button>
|
||||
<Button color="inherit" sx={{ justifyContent: 'flex-start', p: 0 }}>
|
||||
{t('footer.quickLinks.blog')}
|
||||
</Button>
|
||||
{/* Static important links */}
|
||||
<Button
|
||||
color="inherit"
|
||||
sx={{ justifyContent: 'flex-start', p: 0 }}
|
||||
@@ -87,22 +123,21 @@ export function Footer() {
|
||||
>
|
||||
{t('footer.quickLinks.contact')}
|
||||
</Button>
|
||||
<Button color="inherit" sx={{ justifyContent: 'flex-start', p: 0 }}>
|
||||
{t('footer.quickLinks.support')}
|
||||
</Button>
|
||||
<Button color="inherit" sx={{ justifyContent: 'flex-start', p: 0 }}>
|
||||
{t('footer.quickLinks.api')}
|
||||
</Button>
|
||||
{dynamicPages.map((page) => (
|
||||
<Button
|
||||
key={page.id}
|
||||
color="inherit"
|
||||
sx={{ justifyContent: 'flex-start', p: 0 }}
|
||||
onClick={() => router.push(`/${locale}/pages/${page.slug}`)}
|
||||
>
|
||||
{page.title}
|
||||
</Button>
|
||||
))}
|
||||
|
||||
{/* Dynamic pages - filtered for non-legal pages */}
|
||||
{dynamicPages
|
||||
.filter(page => !['terms', 'privacy', 'cookies', 'gdpr'].includes(page.slug))
|
||||
.sort((a, b) => (a.footerOrder || 999) - (b.footerOrder || 999))
|
||||
.map((page) => (
|
||||
<Button
|
||||
key={page.id}
|
||||
color="inherit"
|
||||
sx={{ justifyContent: 'flex-start', p: 0 }}
|
||||
onClick={() => router.push(`/${locale}/pages/${page.slug}`)}
|
||||
>
|
||||
{page.title}
|
||||
</Button>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -112,18 +147,20 @@ export function Footer() {
|
||||
{t('footer.legal.title')}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Button color="inherit" sx={{ justifyContent: 'flex-start', p: 0 }}>
|
||||
{t('footer.legal.terms')}
|
||||
</Button>
|
||||
<Button color="inherit" sx={{ justifyContent: 'flex-start', p: 0 }}>
|
||||
{t('footer.legal.privacy')}
|
||||
</Button>
|
||||
<Button color="inherit" sx={{ justifyContent: 'flex-start', p: 0 }}>
|
||||
{t('footer.legal.cookies')}
|
||||
</Button>
|
||||
<Button color="inherit" sx={{ justifyContent: 'flex-start', p: 0 }}>
|
||||
{t('footer.legal.gdpr')}
|
||||
</Button>
|
||||
{/* Dynamic legal pages */}
|
||||
{dynamicPages
|
||||
.filter(page => ['terms', 'privacy', 'cookies', 'gdpr'].includes(page.slug))
|
||||
.sort((a, b) => (a.footerOrder || 999) - (b.footerOrder || 999))
|
||||
.map((page) => (
|
||||
<Button
|
||||
key={page.id}
|
||||
color="inherit"
|
||||
sx={{ justifyContent: 'flex-start', p: 0 }}
|
||||
onClick={() => router.push(`/${locale}/pages/${page.slug}`)}
|
||||
>
|
||||
{page.title}
|
||||
</Button>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -132,19 +169,25 @@ export function Footer() {
|
||||
<Typography variant="h6" sx={{ mb: 2 }}>
|
||||
{t('footer.social.title')}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<IconButton color="inherit" size="small">
|
||||
<Facebook />
|
||||
</IconButton>
|
||||
<IconButton color="inherit" size="small">
|
||||
<Twitter />
|
||||
</IconButton>
|
||||
<IconButton color="inherit" size="small">
|
||||
<Instagram />
|
||||
</IconButton>
|
||||
<IconButton color="inherit" size="small">
|
||||
<YouTube />
|
||||
</IconButton>
|
||||
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
||||
{socialLinks.map((link) => (
|
||||
<IconButton
|
||||
key={link.id}
|
||||
color="inherit"
|
||||
size="small"
|
||||
href={link.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title={link.name}
|
||||
>
|
||||
{renderSocialIcon(link.icon)}
|
||||
</IconButton>
|
||||
))}
|
||||
{socialLinks.length === 0 && (
|
||||
<Typography variant="caption" color="grey.500">
|
||||
No social media links configured
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
Reference in New Issue
Block a user