272 lines
8.3 KiB
React
272 lines
8.3 KiB
React
|
|
import React, { useState, useEffect } from "react";
|
||
|
|
import { useNavigate } from "react-router-dom";
|
||
|
|
import "./LandingPageCMS.scss";
|
||
|
|
import {
|
||
|
|
FaHome,
|
||
|
|
FaHeading,
|
||
|
|
FaImage,
|
||
|
|
FaQuestionCircle,
|
||
|
|
FaArrowLeft,
|
||
|
|
FaSave,
|
||
|
|
FaEye,
|
||
|
|
FaLock,
|
||
|
|
FaMoon,
|
||
|
|
FaSun,
|
||
|
|
FaBars,
|
||
|
|
FaTimes
|
||
|
|
} from "react-icons/fa";
|
||
|
|
import { MdDashboard, MdEdit } from "react-icons/md";
|
||
|
|
import { useDispatch } from "react-redux";
|
||
|
|
import { message } from "antd";
|
||
|
|
import { getAdminPanel, postAdminPanel, putAdminPanel } from "../features/AdminPanel/AdminPanel";
|
||
|
|
import { getSession } from "../Services/others";
|
||
|
|
|
||
|
|
// Section Components
|
||
|
|
import HeroContentEditor from "./Sections/HeroContentEditor";
|
||
|
|
import OfferingsEditor from "./Sections/OfferingsEditor";
|
||
|
|
import FeaturesEditor from "./Sections/FeaturesEditor";
|
||
|
|
import FAQEditor from "./Sections/FAQEditor";
|
||
|
|
|
||
|
|
const subDirectory = import.meta.env.ENV_BASE_URL;
|
||
|
|
|
||
|
|
const LandingPageCMS = () => {
|
||
|
|
const navigate = useNavigate();
|
||
|
|
const dispatch = useDispatch();
|
||
|
|
const [activeSection, setActiveSection] = useState("hero");
|
||
|
|
const [isSaving, setIsSaving] = useState(false);
|
||
|
|
const [hasChanges, setHasChanges] = useState(false);
|
||
|
|
const [isAuthorized, setIsAuthorized] = useState(false);
|
||
|
|
const [isCheckingAuth, setIsCheckingAuth] = useState(true);
|
||
|
|
const [isDarkMode, setIsDarkMode] = useState(true);
|
||
|
|
const [isMobileSidebarOpen, setIsMobileSidebarOpen] = useState(false);
|
||
|
|
|
||
|
|
// Load theme preference from localStorage
|
||
|
|
useEffect(() => {
|
||
|
|
const savedTheme = localStorage.getItem("landing-cms-theme");
|
||
|
|
if (savedTheme) {
|
||
|
|
setIsDarkMode(savedTheme === "dark");
|
||
|
|
}
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
const toggleTheme = () => {
|
||
|
|
const newTheme = !isDarkMode;
|
||
|
|
setIsDarkMode(newTheme);
|
||
|
|
localStorage.setItem("landing-cms-theme", newTheme ? "dark" : "light");
|
||
|
|
};
|
||
|
|
|
||
|
|
// 📱 Prevent body scroll when mobile sidebar is open
|
||
|
|
useEffect(() => {
|
||
|
|
if (isMobileSidebarOpen && window.innerWidth <= 900) {
|
||
|
|
document.body.style.overflow = "hidden";
|
||
|
|
} else {
|
||
|
|
document.body.style.overflow = "";
|
||
|
|
}
|
||
|
|
return () => {
|
||
|
|
document.body.style.overflow = "";
|
||
|
|
};
|
||
|
|
}, [isMobileSidebarOpen]);
|
||
|
|
|
||
|
|
// 🔒 Authentication Check
|
||
|
|
useEffect(() => {
|
||
|
|
const checkAuthentication = () => {
|
||
|
|
const userType = getSession("UserType");
|
||
|
|
const userId = getSession("UserId");
|
||
|
|
const authToken = sessionStorage.getItem("auth");
|
||
|
|
|
||
|
|
// Allow only Marketing, Super Admin, or Admin
|
||
|
|
const allowedUserTypes = ["Marketing", "Super Admin", "Admin", "Super Admin User"];
|
||
|
|
|
||
|
|
if (!authToken || !userId || !allowedUserTypes.includes(userType)) {
|
||
|
|
message.error("Unauthorized access! Please login first.");
|
||
|
|
navigate(`${subDirectory}signin`);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
setIsAuthorized(true);
|
||
|
|
setIsCheckingAuth(false);
|
||
|
|
};
|
||
|
|
|
||
|
|
checkAuthentication();
|
||
|
|
}, [navigate]);
|
||
|
|
|
||
|
|
const sections = [
|
||
|
|
{
|
||
|
|
id: "hero",
|
||
|
|
name: "Hero Section",
|
||
|
|
icon: <FaHeading />,
|
||
|
|
description: "Main headline, subtext, and CTA button",
|
||
|
|
component: HeroContentEditor
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: "offerings",
|
||
|
|
name: "Offerings",
|
||
|
|
icon: <FaImage />,
|
||
|
|
description: "3 key features (Experience, Innovation, Cost)",
|
||
|
|
component: OfferingsEditor
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: "features",
|
||
|
|
name: "Features",
|
||
|
|
icon: <MdDashboard />,
|
||
|
|
description: "Product features and benefits",
|
||
|
|
component: FeaturesEditor
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: "faq",
|
||
|
|
name: "FAQ Section",
|
||
|
|
icon: <FaQuestionCircle />,
|
||
|
|
description: "Frequently asked questions",
|
||
|
|
component: FAQEditor
|
||
|
|
}
|
||
|
|
];
|
||
|
|
|
||
|
|
const currentSection = sections.find(s => s.id === activeSection);
|
||
|
|
const CurrentComponent = currentSection?.component;
|
||
|
|
|
||
|
|
const handlePreview = () => {
|
||
|
|
window.open(`${subDirectory}`, '_blank');
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleSaveAll = async () => {
|
||
|
|
setIsSaving(true);
|
||
|
|
try {
|
||
|
|
// Save logic will be implemented in each section component
|
||
|
|
message.success("All changes saved successfully!");
|
||
|
|
setHasChanges(false);
|
||
|
|
} catch (error) {
|
||
|
|
message.error("Failed to save changes");
|
||
|
|
} finally {
|
||
|
|
setIsSaving(false);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// Show loading while checking auth
|
||
|
|
if (isCheckingAuth) {
|
||
|
|
return (
|
||
|
|
<div className="landing-cms-auth-loading">
|
||
|
|
<div className="auth-loading-spinner"></div>
|
||
|
|
<p>Verifying access...</p>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Don't render if not authorized (will redirect)
|
||
|
|
if (!isAuthorized) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className={`landing-cms-container ${isDarkMode ? 'dark-mode' : 'light-mode'}`}>
|
||
|
|
{/* Top Header */}
|
||
|
|
<div className="landing-cms-header">
|
||
|
|
<div className="landing-cms-header-left">
|
||
|
|
<button
|
||
|
|
className="landing-cms-mobile-menu-btn"
|
||
|
|
onClick={() => setIsMobileSidebarOpen(!isMobileSidebarOpen)}
|
||
|
|
aria-label="Toggle mobile menu"
|
||
|
|
>
|
||
|
|
{isMobileSidebarOpen ? <FaTimes /> : <FaBars />}
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
className="landing-cms-back-btn"
|
||
|
|
onClick={() => navigate(`${subDirectory}`)}
|
||
|
|
>
|
||
|
|
<FaArrowLeft /> <span>Back to Home</span>
|
||
|
|
</button>
|
||
|
|
<div className="landing-cms-title-section">
|
||
|
|
<h1>Landing Page CMS</h1>
|
||
|
|
<p>Manage your homepage content dynamically</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div className="landing-cms-header-right">
|
||
|
|
<button
|
||
|
|
className="landing-cms-theme-toggle"
|
||
|
|
onClick={toggleTheme}
|
||
|
|
aria-label={isDarkMode ? "Switch to Light Mode" : "Switch to Dark Mode"}
|
||
|
|
title={isDarkMode ? "Switch to Light Mode" : "Switch to Dark Mode"}
|
||
|
|
>
|
||
|
|
{isDarkMode ? <FaSun /> : <FaMoon />}
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
className="landing-cms-preview-btn"
|
||
|
|
onClick={handlePreview}
|
||
|
|
>
|
||
|
|
<FaEye /> <span>Preview</span>
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
className={`landing-cms-save-btn ${hasChanges ? 'has-changes' : ''}`}
|
||
|
|
onClick={handleSaveAll}
|
||
|
|
disabled={isSaving || !hasChanges}
|
||
|
|
>
|
||
|
|
<FaSave /> <span>{isSaving ? 'Saving...' : 'Save All Changes'}</span>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="landing-cms-main">
|
||
|
|
{/* Mobile Overlay */}
|
||
|
|
{isMobileSidebarOpen && (
|
||
|
|
<div
|
||
|
|
className="landing-cms-mobile-overlay"
|
||
|
|
onClick={() => setIsMobileSidebarOpen(false)}
|
||
|
|
aria-hidden="true"
|
||
|
|
/>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* Sidebar Navigation */}
|
||
|
|
<div className={`landing-cms-sidebar ${isMobileSidebarOpen ? 'mobile-open' : ''}`}>
|
||
|
|
<div className="landing-cms-sidebar-header">
|
||
|
|
<FaHome />
|
||
|
|
<span>Content Sections</span>
|
||
|
|
</div>
|
||
|
|
<div className="landing-cms-nav-items">
|
||
|
|
{sections.map((section) => (
|
||
|
|
<button
|
||
|
|
key={section.id}
|
||
|
|
className={`landing-cms-nav-item ${activeSection === section.id ? 'active' : ''}`}
|
||
|
|
onClick={() => {
|
||
|
|
setActiveSection(section.id);
|
||
|
|
setIsMobileSidebarOpen(false); // Close on mobile after selection
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<div className="landing-cms-nav-icon">{section.icon}</div>
|
||
|
|
<div className="landing-cms-nav-content">
|
||
|
|
<div className="landing-cms-nav-title">{section.name}</div>
|
||
|
|
<div className="landing-cms-nav-desc">{section.description}</div>
|
||
|
|
</div>
|
||
|
|
</button>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Content Area */}
|
||
|
|
<div className="landing-cms-content">
|
||
|
|
<div className="landing-cms-content-header">
|
||
|
|
<div className="landing-cms-breadcrumb">
|
||
|
|
<span>Landing Page</span>
|
||
|
|
<span>/</span>
|
||
|
|
<span>{currentSection?.name}</span>
|
||
|
|
</div>
|
||
|
|
{hasChanges && (
|
||
|
|
<div className="landing-cms-unsaved-badge">
|
||
|
|
Unsaved changes
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="landing-cms-content-body">
|
||
|
|
{CurrentComponent && (
|
||
|
|
<CurrentComponent
|
||
|
|
onDataChange={() => setHasChanges(true)}
|
||
|
|
/>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
export default LandingPageCMS;
|
||
|
|
|