import React, { useState, useEffect } from 'react'; import { Home, Mail, Info, FileText, Search, Save, Globe, X, Plus, Trash2, Image as ImageIcon, Eye, Edit, Upload, Loader2, Check, XCircle, Award, Shield, RefreshCw, Accessibility, HelpCircle } from 'lucide-react'; import { pageContentService, PageContent, PageType, UpdatePageContentData, bannerService, Banner } from '../../services/api'; import { toast } from 'react-toastify'; import Loading from '../../components/common/Loading'; import { ConfirmationDialog } from '../../components/common'; import IconPicker from '../../components/admin/IconPicker'; type ContentTab = 'overview' | 'home' | 'contact' | 'about' | 'footer' | 'seo' | 'privacy' | 'terms' | 'refunds' | 'cancellation' | 'accessibility' | 'faq'; const PageContentDashboard: React.FC = () => { const [activeTab, setActiveTab] = useState('overview'); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [pageContents, setPageContents] = useState>({ home: null, contact: null, about: null, footer: null, seo: null, privacy: null, terms: null, refunds: null, cancellation: null, accessibility: null, faq: null, }); // Form states for each page const [homeData, setHomeData] = useState({}); const [contactData, setContactData] = useState({}); const [aboutData, setAboutData] = useState({}); const [footerData, setFooterData] = useState({}); const [seoData, setSeoData] = useState({}); const [privacyData, setPrivacyData] = useState({ is_active: true }); const [termsData, setTermsData] = useState({ is_active: true }); const [refundsData, setRefundsData] = useState({ is_active: true }); const [cancellationData, setCancellationData] = useState({ is_active: true }); const [accessibilityData, setAccessibilityData] = useState({ is_active: true }); const [faqData, setFaqData] = useState({ is_active: true }); // Banner management state const [banners, setBanners] = useState([]); const [loadingBanners, setLoadingBanners] = useState(false); const [showBannerModal, setShowBannerModal] = useState(false); const [editingBanner, setEditingBanner] = useState(null); const [bannerFormData, setBannerFormData] = useState({ title: '', description: '', image_url: '', link_url: '', position: 'home', display_order: 0, is_active: true, start_date: '', end_date: '', }); const [imageFile, setImageFile] = useState(null); const [imagePreview, setImagePreview] = useState(null); const [uploadingImage, setUploadingImage] = useState(false); const [useFileUpload, setUseFileUpload] = useState(true); const [deleteConfirm, setDeleteConfirm] = useState<{ show: boolean; id: number | null }>({ show: false, id: null }); useEffect(() => { fetchAllPageContents(); if (activeTab === 'home') { fetchBanners(); } }, [activeTab]); const fetchAllPageContents = async () => { try { setLoading(true); const response = await pageContentService.getAllPageContents(); const contents = response.data.page_contents || []; const contentsMap: Record = { home: null, contact: null, about: null, footer: null, seo: null, privacy: null, terms: null, refunds: null, cancellation: null, accessibility: null, faq: null, }; contents.forEach((content) => { if (content.page_type in contentsMap) { contentsMap[content.page_type as PageType] = content; } }); setPageContents(contentsMap); // Initialize form data initializeFormData(contentsMap); } catch (error: any) { toast.error(error.response?.data?.message || 'Unable to load page contents'); } finally { setLoading(false); } }; // Helper function to normalize arrays (handle both arrays and JSON strings) const normalizeArray = (value: any): any[] => { if (!value) return []; if (Array.isArray(value)) return value; if (typeof value === 'string') { try { const parsed = JSON.parse(value); return Array.isArray(parsed) ? parsed : []; } catch { return []; } } return []; }; const initializeFormData = (contents: Record) => { // Home if (contents.home) { setHomeData({ title: contents.home.title || '', subtitle: contents.home.subtitle || '', description: contents.home.description || '', content: contents.home.content || '', hero_title: contents.home.hero_title || '', hero_subtitle: contents.home.hero_subtitle || '', hero_image: contents.home.hero_image || '', meta_title: contents.home.meta_title || '', meta_description: contents.home.meta_description || '', meta_keywords: contents.home.meta_keywords || '', og_title: contents.home.og_title || '', og_description: contents.home.og_description || '', og_image: contents.home.og_image || '', features: normalizeArray(contents.home.features), amenities_section_title: contents.home.amenities_section_title || '', amenities_section_subtitle: contents.home.amenities_section_subtitle || '', amenities: normalizeArray(contents.home.amenities), testimonials_section_title: contents.home.testimonials_section_title || '', testimonials_section_subtitle: contents.home.testimonials_section_subtitle || '', testimonials: normalizeArray(contents.home.testimonials), about_preview_title: contents.home.about_preview_title || '', about_preview_subtitle: contents.home.about_preview_subtitle || '', about_preview_content: contents.home.about_preview_content || '', about_preview_image: contents.home.about_preview_image || '', stats: normalizeArray(contents.home.stats), luxury_section_title: contents.home.luxury_section_title || '', luxury_section_subtitle: contents.home.luxury_section_subtitle || '', luxury_section_image: contents.home.luxury_section_image || '', luxury_features: normalizeArray(contents.home.luxury_features), luxury_gallery_section_title: contents.home.luxury_gallery_section_title || '', luxury_gallery_section_subtitle: contents.home.luxury_gallery_section_subtitle || '', luxury_gallery: normalizeArray(contents.home.luxury_gallery), luxury_testimonials_section_title: contents.home.luxury_testimonials_section_title || '', luxury_testimonials_section_subtitle: contents.home.luxury_testimonials_section_subtitle || '', luxury_testimonials: normalizeArray(contents.home.luxury_testimonials), luxury_services_section_title: contents.home.luxury_services_section_title || '', luxury_services_section_subtitle: contents.home.luxury_services_section_subtitle || '', luxury_services: normalizeArray(contents.home.luxury_services), luxury_experiences_section_title: contents.home.luxury_experiences_section_title || '', luxury_experiences_section_subtitle: contents.home.luxury_experiences_section_subtitle || '', luxury_experiences: normalizeArray(contents.home.luxury_experiences), awards_section_title: contents.home.awards_section_title || '', awards_section_subtitle: contents.home.awards_section_subtitle || '', awards: normalizeArray(contents.home.awards), cta_title: contents.home.cta_title || '', cta_subtitle: contents.home.cta_subtitle || '', cta_button_text: contents.home.cta_button_text || '', cta_button_link: contents.home.cta_button_link || '', cta_image: contents.home.cta_image || '', partners_section_title: contents.home.partners_section_title || '', partners_section_subtitle: contents.home.partners_section_subtitle || '', partners: normalizeArray(contents.home.partners), }); } // Contact if (contents.contact) { setContactData({ title: contents.contact.title || '', subtitle: contents.contact.subtitle || '', description: contents.contact.description || '', content: contents.contact.content || '', map_url: contents.contact.map_url || '', meta_title: contents.contact.meta_title || '', meta_description: contents.contact.meta_description || '', }); } // About if (contents.about) { setAboutData({ title: contents.about.title || '', subtitle: contents.about.subtitle || '', description: contents.about.description || '', content: contents.about.content || '', story_content: contents.about.story_content || '', values: normalizeArray(contents.about.values), features: normalizeArray(contents.about.features), about_hero_image: contents.about.about_hero_image || '', mission: contents.about.mission || '', vision: contents.about.vision || '', team: normalizeArray(contents.about.team), timeline: normalizeArray(contents.about.timeline), achievements: normalizeArray(contents.about.achievements), meta_title: contents.about.meta_title || '', meta_description: contents.about.meta_description || '', }); } // Footer if (contents.footer) { setFooterData({ title: contents.footer.title || '', description: contents.footer.description || '', social_links: contents.footer.social_links || {}, footer_links: contents.footer.footer_links || { quick_links: [], support_links: [] }, badges: contents.footer.badges || [], copyright_text: contents.footer.copyright_text || '', meta_title: contents.footer.meta_title || '', meta_description: contents.footer.meta_description || '', }); } // SEO if (contents.seo) { setSeoData({ meta_title: contents.seo.meta_title || '', meta_description: contents.seo.meta_description || '', meta_keywords: contents.seo.meta_keywords || '', og_title: contents.seo.og_title || '', og_description: contents.seo.og_description || '', og_image: contents.seo.og_image || '', canonical_url: contents.seo.canonical_url || '', }); } // Privacy if (contents.privacy) { setPrivacyData({ title: contents.privacy.title || '', subtitle: contents.privacy.subtitle || '', description: contents.privacy.description || '', content: contents.privacy.content || '', meta_title: contents.privacy.meta_title || '', meta_description: contents.privacy.meta_description || '', is_active: contents.privacy.is_active ?? true, }); } // Terms if (contents.terms) { setTermsData({ title: contents.terms.title || '', subtitle: contents.terms.subtitle || '', description: contents.terms.description || '', content: contents.terms.content || '', meta_title: contents.terms.meta_title || '', meta_description: contents.terms.meta_description || '', is_active: contents.terms.is_active ?? true, }); } // Refunds if (contents.refunds) { setRefundsData({ title: contents.refunds.title || '', subtitle: contents.refunds.subtitle || '', description: contents.refunds.description || '', content: contents.refunds.content || '', meta_title: contents.refunds.meta_title || '', meta_description: contents.refunds.meta_description || '', is_active: contents.refunds.is_active ?? true, }); } // Cancellation if (contents.cancellation) { setCancellationData({ title: contents.cancellation.title || '', subtitle: contents.cancellation.subtitle || '', description: contents.cancellation.description || '', content: contents.cancellation.content || '', meta_title: contents.cancellation.meta_title || '', meta_description: contents.cancellation.meta_description || '', is_active: contents.cancellation.is_active ?? true, }); } // Accessibility if (contents.accessibility) { setAccessibilityData({ title: contents.accessibility.title || '', subtitle: contents.accessibility.subtitle || '', description: contents.accessibility.description || '', content: contents.accessibility.content || '', meta_title: contents.accessibility.meta_title || '', meta_description: contents.accessibility.meta_description || '', is_active: contents.accessibility.is_active ?? true, }); } // FAQ if (contents.faq) { setFaqData({ title: contents.faq.title || '', subtitle: contents.faq.subtitle || '', description: contents.faq.description || '', content: contents.faq.content || '', meta_title: contents.faq.meta_title || '', meta_description: contents.faq.meta_description || '', is_active: contents.faq.is_active ?? true, }); } }; const handleSave = async (pageType: PageType, data: UpdatePageContentData) => { try { setSaving(true); // Remove contact_info for contact and footer pages since it's now managed centrally const { contact_info, ...dataToSave } = data; if (pageType === 'contact' || pageType === 'footer') { await pageContentService.updatePageContent(pageType, dataToSave); } else { await pageContentService.updatePageContent(pageType, data); } toast.success(`${pageType.charAt(0).toUpperCase() + pageType.slice(1)} content saved successfully`); await fetchAllPageContents(); } catch (error: any) { toast.error(error.response?.data?.message || `Failed to save ${pageType} content`); } finally { setSaving(false); } }; // Banner management functions const fetchBanners = async () => { try { setLoadingBanners(true); const response = await bannerService.getAllBanners({ position: 'home' }); if (response.success || response.status === 'success') { setBanners(response.data?.banners || []); } } catch (error: any) { toast.error(error.response?.data?.message || 'Failed to load banners'); } finally { setLoadingBanners(false); } }; const handleImageChange = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; if (file.size > 5 * 1024 * 1024) { toast.error('Image size must be less than 5MB'); return; } setImageFile(file); // Create preview const reader = new FileReader(); reader.onloadend = () => { setImagePreview(reader.result as string); }; reader.readAsDataURL(file); // Upload image immediately try { setUploadingImage(true); const response = await bannerService.uploadBannerImage(file); if (response.success) { setBannerFormData({ ...bannerFormData, image_url: response.data.image_url }); toast.success('Image uploaded successfully'); } } catch (error: any) { toast.error(error.response?.data?.message || 'Failed to upload image'); setImageFile(null); setImagePreview(null); } finally { setUploadingImage(false); } }; // Generic image upload handler for page content images const handlePageContentImageUpload = async ( file: File, onSuccess: (imageUrl: string) => void ) => { if (file.size > 5 * 1024 * 1024) { toast.error('Image size must be less than 5MB'); return; } try { const response = await pageContentService.uploadImage(file); if (response.success) { onSuccess(response.data.image_url); toast.success('Image uploaded successfully'); } } catch (error: any) { toast.error(error.response?.data?.message || 'Failed to upload image'); } }; const handleBannerSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!bannerFormData.image_url && !imageFile) { toast.error('Please upload an image or provide an image URL'); return; } try { let imageUrl = bannerFormData.image_url; if (imageFile && !imageUrl) { setUploadingImage(true); const uploadResponse = await bannerService.uploadBannerImage(imageFile); if (uploadResponse.success) { imageUrl = uploadResponse.data.image_url; } else { throw new Error('Failed to upload image'); } setUploadingImage(false); } const submitData = { ...bannerFormData, image_url: imageUrl, start_date: bannerFormData.start_date || undefined, end_date: bannerFormData.end_date || undefined, }; if (editingBanner) { await bannerService.updateBanner(editingBanner.id, submitData); toast.success('Banner updated successfully'); } else { await bannerService.createBanner(submitData); toast.success('Banner created successfully'); } setShowBannerModal(false); resetBannerForm(); fetchBanners(); } catch (error: any) { toast.error(error.response?.data?.message || 'An error occurred'); setUploadingImage(false); } }; const handleEditBanner = (banner: Banner) => { setEditingBanner(banner); setBannerFormData({ title: banner.title || '', description: banner.description || '', image_url: banner.image_url || '', link_url: banner.link_url || '', position: banner.position || 'home', display_order: banner.display_order || 0, is_active: banner.is_active ?? true, start_date: banner.start_date ? banner.start_date.split('T')[0] : '', end_date: banner.end_date ? banner.end_date.split('T')[0] : '', }); setImageFile(null); const previewUrl = banner.image_url ? (banner.image_url.startsWith('http') ? banner.image_url : `${import.meta.env.VITE_API_URL?.replace('/api', '') || 'http://localhost:8000'}${banner.image_url}`) : null; setImagePreview(previewUrl); setUseFileUpload(false); setShowBannerModal(true); }; const handleDeleteBanner = async () => { if (!deleteConfirm.id) return; try { await bannerService.deleteBanner(deleteConfirm.id); toast.success('Banner deleted successfully'); setDeleteConfirm({ show: false, id: null }); fetchBanners(); } catch (error: any) { toast.error(error.response?.data?.message || 'Failed to delete banner'); } }; const resetBannerForm = () => { setBannerFormData({ title: '', description: '', image_url: '', link_url: '', position: 'home', display_order: 0, is_active: true, start_date: '', end_date: '', }); setImageFile(null); setImagePreview(null); setUseFileUpload(true); setEditingBanner(null); }; const toggleBannerActive = async (banner: Banner) => { try { await bannerService.updateBanner(banner.id, { is_active: !banner.is_active, }); toast.success(`Banner ${!banner.is_active ? 'activated' : 'deactivated'}`); fetchBanners(); } catch (error: any) { toast.error(error.response?.data?.message || 'Failed to update banner'); } }; const tabs = [ { id: 'overview' as ContentTab, label: 'Overview', icon: Home }, { id: 'home' as ContentTab, label: 'Home', icon: Home }, { id: 'contact' as ContentTab, label: 'Contact', icon: Mail }, { id: 'about' as ContentTab, label: 'About', icon: Info }, { id: 'privacy' as ContentTab, label: 'Privacy', icon: Shield }, { id: 'terms' as ContentTab, label: 'Terms', icon: FileText }, { id: 'refunds' as ContentTab, label: 'Refunds', icon: RefreshCw }, { id: 'cancellation' as ContentTab, label: 'Cancellation', icon: XCircle }, { id: 'accessibility' as ContentTab, label: 'Accessibility', icon: Accessibility }, { id: 'faq' as ContentTab, label: 'FAQ', icon: HelpCircle }, { id: 'footer' as ContentTab, label: 'Footer', icon: FileText }, { id: 'seo' as ContentTab, label: 'SEO', icon: Search }, ]; if (loading) { return ; } return (
{/* Luxury Header */}

Page Content Management

Centralized control for all frontend pages and SEO optimization

{/* Premium Tab Navigation */}
{tabs.map((tab) => { const Icon = tab.icon; const isActive = activeTab === tab.id; return ( ); })}
{/* Overview Tab */} {activeTab === 'overview' && (
{[ { id: 'home' as PageType, label: 'Home Page', icon: Home, color: 'blue', description: 'Manage hero section, featured content' }, { id: 'contact' as PageType, label: 'Contact Page', icon: Mail, color: 'green', description: 'Manage contact information and form' }, { id: 'about' as PageType, label: 'About Page', icon: Info, color: 'amber', description: 'Manage story, values, and features' }, { id: 'privacy' as PageType, label: 'Privacy Policy', icon: Shield, color: 'red', description: 'Manage privacy policy content' }, { id: 'terms' as PageType, label: 'Terms & Conditions', icon: FileText, color: 'teal', description: 'Manage terms and conditions' }, { id: 'refunds' as PageType, label: 'Refunds Policy', icon: RefreshCw, color: 'orange', description: 'Manage refunds policy content' }, { id: 'cancellation' as PageType, label: 'Cancellation Policy', icon: XCircle, color: 'pink', description: 'Manage cancellation policy content' }, { id: 'accessibility' as PageType, label: 'Accessibility', icon: Accessibility, color: 'cyan', description: 'Manage accessibility information' }, { id: 'faq' as PageType, label: 'FAQ', icon: HelpCircle, color: 'violet', description: 'Manage frequently asked questions' }, { id: 'footer' as PageType, label: 'Footer', icon: FileText, color: 'purple', description: 'Manage footer links and social media' }, { id: 'seo' as PageType, label: 'SEO Settings', icon: Search, color: 'indigo', description: 'Manage meta tags and SEO optimization' }, ].map((page) => { const Icon = page.icon; const hasContent = pageContents[page.id] !== null; return (
setActiveTab(page.id as ContentTab)} className={`group relative bg-white/90 backdrop-blur-xl rounded-2xl shadow-xl border p-8 cursor-pointer transition-all duration-300 hover:shadow-2xl hover:scale-105 overflow-hidden ${ page.color === 'blue' ? 'border-blue-100/50 hover:border-blue-300/60' : page.color === 'green' ? 'border-green-100/50 hover:border-green-300/60' : page.color === 'amber' ? 'border-amber-100/50 hover:border-amber-300/60' : page.color === 'purple' ? 'border-purple-100/50 hover:border-purple-300/60' : page.color === 'red' ? 'border-red-100/50 hover:border-red-300/60' : page.color === 'teal' ? 'border-teal-100/50 hover:border-teal-300/60' : page.color === 'orange' ? 'border-orange-100/50 hover:border-orange-300/60' : 'border-indigo-100/50 hover:border-indigo-300/60' }`} >

{page.label}

{hasContent && (
Active
)}

{page.description}

{hasContent ? 'Edit Content' : 'Add Content'}
); })}
)} {/* Home Tab */} {activeTab === 'home' && (
{/* Home Page Content Section */}

Home Page Content

setHomeData({ ...homeData, hero_title: e.target.value })} className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200" placeholder="Welcome to Luxury Hotel" />
setHomeData({ ...homeData, hero_subtitle: e.target.value })} className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200" placeholder="Experience unparalleled luxury" />
setHomeData({ ...homeData, hero_image: e.target.value })} className="flex-1 px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200" placeholder="https://example.com/hero-image.jpg or upload" />
{homeData.hero_image && (
Hero preview
)}
setHomeData({ ...homeData, title: e.target.value })} className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200" />