leumas-private-shared
Version:
Private React JSX Package For Leumas Shared Components, Headers, Footers, Asides, Login Pages, API Key Manager and much more. Styles and everything reusable to avoid DRY code across all of our subdomains
433 lines (393 loc) • 21.2 kB
JSX
import React, { useState, useEffect, useRef } from 'react';
import { Link } from "react-router-dom";
import { MdClose } from 'react-icons/md';
import HasIdValidation from "../LeumasID/HasIdValidation";
import axios from 'axios';
import "./Header.css";
import { searchConfigs } from "./searchConfigs";
import { FaUser, FaHome, FaBell, FaUsers, FaKey, FaLevelUpAlt, FaTrophy, FaCog, FaHistory, FaTasks, FaBars, FaTimes, FaClipboard } from 'react-icons/fa';
import NotificationPopup from "./Popups/NotificationPopup";
import AchievementsPopup from "./Popups/AchievementsPopup";
import FriendRequestsPopup from './Popups/FriendRequestsPopup';
import HistoryPopup from "./Popups/HistoryPopup";
import SettingsPopup from "./Popups/SettingsPopup";
import AutomationsPopup from "./Popups/AutomationsPopup";
const Header = ({
ThemeToggleButton,
useTheme,
useAuthUser,
useSignOut,
headerLinksUrl,
megaMenuUrl
}) => {
const [siteHeaderLinks, setSiteHeaderLinks] = useState({ links: [], logo: {} });
const [isDropdownOpen, setIsDropdownOpen] = useState(null);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [mobileDropdownOpen, setMobileDropdownOpen] = useState(null);
const [showProfilePopup, setShowProfilePopup] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const [searchResults, setSearchResults] = useState([]);
const [showSearchResults, setShowSearchResults] = useState(false);
const user = useAuthUser();
const signOut = useSignOut();
const [notifications, setNotifications] = useState(['Notification 1', 'Notification 2']);
const [friendRequests, setFriendRequests] = useState(['Friend Request 1', 'Friend Request 2']);
const [historyData, setHistoryData] = useState([{ site: 'Site 1', ip: '192.168.0.1' }, { site: 'Site 2', ip: '192.168.0.2' }]);
const [settings, setSettings] = useState(['Setting 1', 'Setting 2']);
const [achievements, setAchievements] = useState(['Achievement 1', 'Achievement 2']);
const [automations, setAutomations] = useState(['Automation 1', 'Automation 2']);
const [showNotificationPopup, setShowNotificationPopup] = useState(false);
const [showFriendRequestsPopup, setShowFriendRequestsPopup] = useState(false);
const [showHistoryPopup, setShowHistoryPopup] = useState(false);
const [showSettingsPopup, setShowSettingsPopup] = useState(false);
const [showAchievementsPopup, setShowAchievementsPopup] = useState(false);
const [showAutomationsPopup, setShowAutomationsPopup] = useState(false);
const [megaMenu, setMegaMenu] = useState({});
const { theme } = useTheme(); // Use the theme from the passed-in hook
console.log("Header's Theme:", theme);
const fetchMegaMenu = async () => {
try {
const response = await axios.get(megaMenuUrl);
setMegaMenu(response.data);
} catch (err) {
alert("Failed to fetch Mega Menu. Server must be down.", err);
}
};
useEffect(() => {
fetch(headerLinksUrl)
.then(response => response.json())
.then(data => {
setSiteHeaderLinks(data);
})
.catch(error => console.error('Error fetching site header links:', error));
fetchMegaMenu();
}, [headerLinksUrl, megaMenuUrl]);
const toggleProfilePopup = () => {
setShowProfilePopup(!showProfilePopup);
};
const handleMouseEnter = (index) => {
setIsDropdownOpen(index);
};
const handleDropdownMouseLeave = () => {
setIsDropdownOpen(null);
};
const toggleMobileDropdown = (index) => {
if (mobileDropdownOpen === index) {
setMobileDropdownOpen(null);
} else {
setMobileDropdownOpen(index);
}
};
const handleSignOut = () => {
signOut();
window.location.href = '/';
};
const debounce = (func, delay) => {
let debounceTimer;
return function (...args) {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => func(...args), delay);
};
};
const handleSearch = async () => {
try {
const searchResults = await searchConfigs(searchQuery);
setSearchResults(searchResults);
setShowSearchResults(true);
} catch (error) {
console.error("Search error:", error);
}
};
const debouncedFetchSearchResults = debounce(handleSearch, 300);
const handleSearchChange = (e) => {
const query = e.target.value;
setSearchQuery(query);
debouncedFetchSearchResults(query);
};
const copyToClipboard = (text) => {
navigator.clipboard.writeText(JSON.stringify(text, null, 2))
.then(() => alert("Copied to clipboard"))
.catch((err) => console.error("Could not copy text", err));
};
const scrollContainerRef = useRef(null);
const scrollLeft = () => {
if (scrollContainerRef.current) {
scrollContainerRef.current.scrollBy({
left: -200,
behavior: 'smooth',
});
}
};
const scrollRight = () => {
if (scrollContainerRef.current) {
scrollContainerRef.current.scrollBy({
left: 200,
behavior: 'smooth',
});
}
};
return (
<nav className={`theme-transition theme-${theme} border-b-2 border-blue-400 rounded-lg`} style={{ backgroundColor: 'var(--primary-color)', color: 'var(--text-dark)' }}>
<div className="flex flex-wrap items-center justify-between max-w-screen-xl mx-auto p-4">
<Link to={siteHeaderLinks.logo?.href} className="flex items-center space-x-3">
<img src={siteHeaderLinks.logo?.src} className="h-8" alt={siteHeaderLinks.logo?.alt} onClick={() => window.location.href = 'https://leumas.tech'} />
<span className="self-center text-2xl font-semibold whitespace-nowrap">{siteHeaderLinks.logo?.alt}</span>
</Link>
<div className="hidden md:flex flex-grow items-center justify-center space-x-4">
<ul className="flex justify-center space-x-4">
{siteHeaderLinks.links.map((link, index) => (
<li key={index}
onMouseEnter={() => handleMouseEnter(index)}
className="relative">
{link.dropdown ? (
<button onClick={() => toggleMobileDropdown(index)} className="inline-flex items-center">
{link.name}
<svg className="ml-2 w-4 h-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none"><path stroke="#6B7280" strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 8l4 4 4-4" /></svg>
</button>
) : (
<Link to={link.href} className="inline-flex items-center">
{link.name}
</Link>
)}
{link.dropdown && isDropdownOpen === index && (
<div className={`absolute z-10 left-0 mt-1 w-48 py-2 shadow-lg border border-gray-200 rounded-md theme-transition theme-${theme}`} style={{ backgroundColor: 'var(--primary-color)', color: 'var(--text-dark)' }}
onMouseLeave={handleDropdownMouseLeave}>
{link.dropdown.map((sublink, subIndex) => (
<Link key={subIndex} to={`${sublink.href}`} className="block px-4 py-2 text-sm hover:scale-105">{sublink.name}</Link>
))}
</div>
)}
</li>
))}
</ul>
</div>
<div className="flex items-center space-x-1 md:space-x-2 mx-2">
{/* Use the ThemeToggleButton passed in as a prop */}
<ThemeToggleButton />
{!isMobileMenuOpen && (
<button onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)} className="md:hidden inline-flex items-center p-2 text-sm rounded-lg focus:outline-none focus:ring-2 focus:ring-gray-200">
<span className="sr-only">Open main menu</span>
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16m-7 6h7"></path>
</svg>
</button>
)}
{user ? (
<>
<div className="relative">
<div className="relative inline-block">
<img src={user.profilePicture || "https://res.cloudinary.com/dx25lltre/image/upload/v1707176122/Leumas/2_t6ap9y.svg"} alt="Profile" className="w-10 h-10 rounded-full cursor-pointer" onClick={toggleProfilePopup} />
<span className="absolute bottom-0 left-7 w-3.5 h-3.5 bg-green-400 border-2 border-white dark:border-gray-800 rounded-full"></span>
</div>
{showProfilePopup && (
<div className={`absolute right-0 mt-2 py-2 w-48 shadow-lg rounded-md z-50 theme-transition theme-${theme}`} style={{ backgroundColor: 'var(--primary-color)', color: 'var(--text-dark)' }}>
<div className="px-4 py-2">
<p className="font-semibold">{user.nicename.substring(0, 15)}...</p>
<p className="text-sm">{user.email.substring(0, 18)}...</p>
</div>
<div className={`p-2`}>
<HasIdValidation leumasId={user?.leumasIdRecords[0]} />
</div>
<div className="border rounded-lg p-2 flex flex-col items-center justify-start gap-4 w-full">
<div className="grid grid-cols-3 gap-2 justify-items-center w-full">
<button className="icon-button" title="Profile" onClick={() => window.location.href = 'https://soxial.leumas.tech/my-profile'}>
<FaUser size={24} />
</button>
<button className="icon-button" title="Home" onClick={() => window.location.href = 'https://leumas.tech'}>
<FaHome size={24} />
</button>
<button className="icon-button relative" title="Notifications" onClick={() => setShowNotificationPopup(true)}>
<FaBell size={24} />
<span className="notification-badge">
{notifications.length}
</span>
</button>
<button className="icon-button relative" title="Friend Requests" onClick={() => setShowFriendRequestsPopup(true)}>
<FaUsers size={24} />
<span className="notification-badge">
{friendRequests.length}
</span>
</button>
<button className="icon-button" title="API Keys" onClick={() => window.location.href = '/dashboard'}>
<FaKey size={24} />
</button>
<button className="icon-button" title="Achievements" onClick={() => setShowAchievementsPopup(true)}>
<FaTrophy size={24} />
</button>
<button className="icon-button" title="Settings" onClick={() => setShowSettingsPopup(true)}>
<FaCog size={24} />
</button>
<button className="icon-button" title="History" onClick={() => setShowHistoryPopup(true)}>
<FaHistory size={24} />
</button>
<button className="icon-button" title="Automations" onClick={() => setShowAutomationsPopup(true)}>
<FaTasks size={24} />
</button>
</div>
<div className="flex items-center gap-2">
<FaLevelUpAlt size={24} />
<span>Level 1</span>
</div>
{showNotificationPopup && <NotificationPopup notifications={notifications} onClose={() => setShowNotificationPopup(false)} />}
{showFriendRequestsPopup && <FriendRequestsPopup friendRequests={friendRequests} onClose={() => setShowFriendRequestsPopup(false)} />}
{showHistoryPopup && <HistoryPopup history={historyData} onClose={() => setShowHistoryPopup(false)} />}
{showSettingsPopup && <SettingsPopup settings={settings} onClose={() => setShowSettingsPopup(false)} />}
{showAchievementsPopup && <AchievementsPopup achievements={achievements} onClose={() => setShowAchievementsPopup(false)} />}
{showAutomationsPopup && <AutomationsPopup automations={automations} onClose={() => setShowAutomationsPopup(false)} />}
</div>
<div className="px-4 py-2 border-t border-gray-200">
<button onClick={handleSignOut} className="w-full text-left bg-blue-500 text-white rounded-md py-2 mt-2 hover:bg-red-600 transition-colors duration-150 ease-in-out text-center">Sign Out</button>
</div>
</div>
)}
</div>
</>
) : (
<>
<Link to="/login" className="px-4 py-2 rounded hover:bg-blue-600 dark:hover:bg-gray-800 rounded-lg bg-blue-400 ml-2 w-full text-center">Login</Link>
</>
)}
</div>
{isMobileMenuOpen && (
<div className={`fixed inset-0 z-50 flex flex-col items-center justify-center p-4 gap-2 overflow-y-auto theme-transition theme-${theme}`} style={{ backgroundColor: 'var(--primary-color)', color: 'var(--text-dark)' }}>
<button onClick={() => setIsMobileMenuOpen(false)} className="absolute top-4 right-4 p-2 text-red-500 hover:text-red-700">
<MdClose className="w-6 h-6" />
<span className="sr-only">Close</span>
</button>
{siteHeaderLinks.links.map((link, index) => (
<div key={index} className={`w-full text-center border border-blue-400 rounded-lg`} style={{ animationDelay: `${index * 0.3}s` }}>
{link.dropdown ? (
<button onClick={() => setMobileDropdownOpen(mobileDropdownOpen === index ? null : index)} className={`slide-in p-2 rounded w-full flex justify-between items-center`}>
{link.name}
<svg className="w-4 h-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 011.414 1.414l-4 4a1 1 01-1.414 0l-4-4a1 1 010-1.414z" clipRule="evenodd" />
</svg>
</button>
) : (
<Link to={link.href} className={`slide-in p-2 rounded w-full flex justify-between items-center`} style={{ animationDelay: `${index * 0.1}s` }} onClick={() => setIsMobileMenuOpen(false)}>
{link.name}
</Link>
)}
{link.dropdown && mobileDropdownOpen === index && (
<div className="flex flex-col items-center">
{link.dropdown.map((sublink, subIndex) => (
<Link key={subIndex} to={sublink.href} className={`slide-in block px-4 py-2 text-sm w-full text-center`} style={{ animationDelay: `${(index + subIndex) * 0.1}s` }} onClick={() => setIsMobileMenuOpen(false)}>
{sublink.name}
</Link>
))}
</div>
)}
</div>
))}
</div>
)}
</div>
<div className="relative px-4 py-2 shadow-xl flex items-center justify-start gap-2">
<button
className=""
onClick={() => setShowSearchResults(!showSearchResults)}
>
{showSearchResults ? <FaTimes className="w-6 h-6" /> : <FaBars className="w-6 h-6" />}
</button>
<input
type="text"
value={searchQuery}
onChange={handleSearchChange}
className={`w-full rounded-lg p-2 focus:outline-none focus:ring-2 focus:ring-blue-400 border border-blue-400 border-2 max-w-[150px] theme-transition theme-${theme}`}
placeholder="Search..."
/>
{showSearchResults && (
<div className={`absolute top-full left-0 w-full mt-2 shadow-lg rounded-md z-50 theme-transition theme-${theme}`} style={{ backgroundColor: 'var(--primary-color)', color: 'var(--text-dark)' }}>
<ul>
{searchResults.map((result, index) => (
<li key={index} className="px-4 py-2 border border-black">
<div className="font-bold text-blue-500">{result.config}</div>
{result.matches.map((match, matchIndex) => (
<div key={matchIndex} className="py-2 border border-green-400 p-2 rounded-lg shadow-xl">
<div className="grid grid-cols-2 gap-2">
{Object.entries(match.object).map(([key, value], valueIndex) => (
<div key={valueIndex} className="bg-gray-200 p-2 rounded">
<span className="font-bold">{key}:</span>
{typeof value === 'object' ? (
<pre className="bg-gray-100 p-1 rounded shadow-inner">{JSON.stringify(value, null, 2)}</pre>
) : (
<span className="ml-2">{value}</span>
)}
</div>
))}
</div>
<button
className="flex items-center bg-blue-500 text-white p-2 rounded mt-2 hover:bg-blue-600 transition duration-200 ease-in-out"
onClick={() => copyToClipboard(match.object)}
>
<FaClipboard className="" />
</button>
</div>
))}
</li>
))}
</ul>
</div>
)}
<div className="relative w-full rounded-lg overflow-hidden flex items-center px-4 max-h-[50px] border">
<button
className="absolute left-0 z-10 p-3 bg-blue-400 rounded"
onClick={scrollLeft}
>
<
</button>
<div
className="w-full flex items-center overflow-x-auto no-scrollbar"
ref={scrollContainerRef}
>
{megaMenu && megaMenu.links?.length > 0 ? (
<>
{megaMenu.links.map((link, index) => (
<div key={index} className="mega-menu-item p-2">
{link.dropdown && link.dropdown.length > 0 ? (
<div className="dropdown relative">
<button className="dropbtn p-1 rounded w-32 text-center text-sm">
{link.name}
</button>
<div className="dropdown-content absolute hidden shadow-lg rounded mt-1 w-32 theme-transition theme-${theme}" style={{ backgroundColor: 'var(--primary-color)', color: 'var(--text-dark)' }}>
{link.dropdown.map((sublink, subIndex) => (
<a
href={sublink.href}
key={subIndex}
className="block p-1 hover:bg-gray-200 text-sm"
>
{sublink.name}
</a>
))}
</div>
</div>
) : (
<button
onClick={() => { window.location.href = link.href }}
className="p-1 rounded w-32 text-center text-sm border border-blue-400 rounded-lg hover:scale-105"
>
{link.name}
</button>
)}
</div>
))}
</>
) : (
<>
<p className="text-sm">Error Links Not Found : Server Down</p>
<button onClick={() => console.log("Mega Menu ", megaMenu?.links?.length)} className="text-sm">Click</button>
</>
)}
</div>
<button
className="absolute right-0 z-10 p-3 bg-blue-400 rounded"
onClick={scrollRight}
>
>
</button>
</div>
</div>
</nav>
);
};
export default Header;