Files
shuffle-cracked/frontend/src/components/Navbar.jsx
T
2026-02-10 19:30:54 +05:30

2784 lines
99 KiB
React

import React, { useContext, useEffect, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import {
AppBar,
Box,
Toolbar,
IconButton,
Typography,
Menu,
MenuItem,
Button,
Container,
useTheme,
useMediaQuery,
Divider,
alpha,
Avatar,
Select,
Tooltip,
DialogTitle,
DialogContent,
Dialog,
Slide,
Collapse
} from "@mui/material";
import MenuIcon from "@mui/icons-material/Menu";
import { Close as CloseIcon, Folder as FolderIcon, Code as CodeIcon, LibraryBooks as LibraryBooksIcon } from '@mui/icons-material'
import SearchBox from "../components/SearchData.jsx";
import ReactGA from "react-ga4";
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
import KeyboardCommandKeyIcon from '@mui/icons-material/KeyboardCommandKey';
import SearchIcon from "@mui/icons-material/Search";
// import SearchField from "../components/Searchfield.jsx";
import { toast } from "react-toastify";
import AddIcon from '@mui/icons-material/Add';
import Mousetrap from "mousetrap";
import LicencePopup from "../components/LicencePopup.jsx";
import { Context } from "../context/ContextApi.jsx";
import { getTheme } from "../theme.jsx";
const curpath = (typeof window !== "undefined" && window.location && typeof window.location.pathname === "string")
? window.location.pathname
: "";
// Menu Data Structure
const menuData = {
Products: [
{
title: "Shuffle",
description:
"The most versatile automation engine. Focused on cybersecurity.",
icon: "/images/icons/shuffleLogo.svg",
path: "/docs/about",
gaData: {
category: "navbar",
action: "products_click",
label: "shuffle_logo_click"
}
},
{
title: "Singul",
description:
"Connect to your favorite services with a singul line of code.",
icon: "/images/logos/singul.svg",
path: "https://singul.io",
gaData: {
category: "navbar",
action: "products_click",
label: "singul_click"
}
},
/*
{
title: "API Explorer",
description:
"Explore, run and automate APIs from over 2500 platforms.",
icon: "/images/icons/API.svg",
path: "/apis",
gaData: {
category: "navbar",
action: "products_click",
label: "api_explorer_click"
}
},
*/
],
Services: [
{
title: "Proof of Concept",
description:
"Register for POC to test the best of Shuffle for free for 30 days.",
icon: "/images/ProfessionalServices.svg",
path: "/poc",
gaData: {
category: "navbar",
action: "services_click",
label: "proof_of_concept_click"
}
},
{
title: "Support",
description:
"Support to help you build automations with confidence.",
icon: "/images/Support.svg",
path: "/support",
gaData: {
category: "navbar",
action: "services_click",
label: "support_click"
}
},
{
title: "Training",
description:
"Tailored to provide hands-on learning of Shuffle for automation mastery.",
icon: "/images/Training.svg",
path: "/training",
gaData: {
category: "navbar",
action: "services_click",
label: "training_click"
}
},
// {
// title: "Security Consultation",
// description:
// "Automate your infrastructure with expert guidance and tailored solutions.",
// icon: "/images/SecurityConsultation.svg",
// path: "/contact?category=security_consultation",
// gaData: {
// category: "navbar",
// action: "services_click",
// label: "security_consultation_click"
// }
// },
],
Resources: {
columns: [
{
title: "Platform",
items: [
{
title: "Usecases",
icon: "/images/icons/usecases.svg",
hoverIcon: "/images/icons/usecases_hover.svg",
link: "/usecases",
gaData: {
category: "navbar",
action: "resources_click",
label: "usecases_click"
}
},
{
title: "Documentation",
icon: "/images/icons/docs.svg",
hoverIcon: "/images/icons/docs_hover.svg",
link: "/docs/about",
gaData: {
category: "navbar",
action: "resources_click",
label: "documentation_click"
}
},
{ title: "FAQ", icon: "/images/icons/faq.svg", hoverIcon: "/images/icons/faq_hover.svg", link: "/faq", gaData: { category: "navbar", action: "resources_click", label: "faq" } },
],
},
{
title: "Company",
items: [
{
title: "About us",
icon: "/images/icons/about_us.svg",
hoverIcon: "/images/icons/about_us_hover.svg",
link: "/docs/about",
gaData: {
category: "navbar",
action: "resources_click",
label: "about_us_click"
}
},
{
title: "Articles",
icon: "/images/icons/articles.svg",
hoverIcon: "/images/icons/articles_hover.svg",
link: "/articles",
gaData: {
category: "navbar",
action: "resources_click",
label: "articles_click"
}
},
{
title: "Contact Us",
icon: "/images/icons/contact_us.svg",
hoverIcon: "/images/icons/contact_us_hover.svg",
link: "/contact?category=contact",
gaData: {
category: "navbar",
action: "resources_click",
label: "contact_us_click"
}
},
],
},
{
title: "Join the community",
social: true,
platforms: [
{ name: "Discord", icon: "/images/icons/discord.svg", link: "https://discord.gg/B2CBzUm", gaData: { category: "navbar", action: "social_click", label: "discord_icon_click" } },
{ name: "GitHub", icon: "/images/icons/github.svg", link: "https://github.com/shuffle/shuffle/blob/main/.github/install-guide.md", gaData: { category: "navbar", action: "social_click", label: "github_icon_click" } },
],
followUs: [
{ name: "LinkedIn", icon: "/images/icons/linkedIn.svg", link: "https://www.linkedin.com/company/shuffleio", gaData: { category: "navbar", action: "social_click", label: "linkedin_icon_click" } },
{ name: "Twitter", icon: "/images/icons/x.svg", link: "https://twitter.com/shuffleio", gaData: { category: "navbar", action: "social_click", label: "twitter_icon_click" } },
],
},
],
},
};
// Add this new component at the top level of the file, right after the imports
const LoadingSkeleton = () => {
const theme = useTheme();
return (
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center' }}>
<Box
sx={{
width: 42,
height: 42,
backgroundColor: alpha(theme.palette.primary.main, 0.1),
borderRadius: '8px',
"@keyframes pulse": {
"0%": { opacity: 1 },
"50%": { opacity: 0.5 },
"100%": { opacity: 1 },
},
animation: 'pulse 1.5s ease-in-out infinite',
}}
/>
<Box
sx={{
width: 120,
height: 42,
backgroundColor: alpha(theme.palette.primary.main, 0.1),
borderRadius: '8px',
"@keyframes pulse": {
"0%": { opacity: 1 },
"50%": { opacity: 0.5 },
"100%": { opacity: 1 },
},
animation: 'pulse 1.5s ease-in-out infinite',
}}
/>
<Box
sx={{
width: 90,
height: 42,
backgroundColor: alpha(theme.palette.primary.main, 0.1),
borderRadius: '8px',
"@keyframes pulse": {
"0%": { opacity: 1 },
"50%": { opacity: 0.5 },
"100%": { opacity: 1 },
},
animation: 'pulse 1.5s ease-in-out infinite',
}}
/>
<Box
sx={{
width: 42,
height: 42,
backgroundColor: alpha(theme.palette.primary.main, 0.1),
borderRadius: '50%',
"@keyframes pulse": {
"0%": { opacity: 1 },
"50%": { opacity: 0.5 },
"100%": { opacity: 1 },
},
animation: 'pulse 1.5s ease-in-out infinite',
}}
/>
</Box>
);
};
// Add this new component for mobile menu
const MobileMenu = ({ anchorEl, handleClose, isLoggedIn, navigate, isCloud }) => {
const [openSection, setOpenSection] = useState(null);
const theme = useTheme();
// Add useEffect to handle body scroll
useEffect(() => {
if (Boolean(anchorEl)) {
// Disable scroll
document.body.style.overflow = 'hidden';
} else {
// Re-enable scroll
document.body.style.overflow = 'auto';
}
// Cleanup function to ensure scroll is re-enabled when component unmounts
return () => {
document.body.style.overflow = 'auto';
};
}, [anchorEl]);
const handleSectionClick = (section) => {
setOpenSection(openSection === section ? null : section);
};
const handleItemClick = (path) => {
handleClose();
navigate(path);
};
const showDesktopToast = () => {
toast.info("Please open on desktop for the full experience");
handleClose();
};
return (
<Dialog
open={Boolean(anchorEl)}
onClose={handleClose}
fullScreen
TransitionComponent={Slide}
TransitionProps={{ direction: "left" }}
disableScrollLock
PaperProps={{
sx: {
backgroundColor: "#1A1A1A",
color: "white",
marginTop: "64px",
height: 'calc(100% - 64px)',
fontFamily: theme.typography.fontFamily,
zIndex: 60000,
boxShadow: 'none',
borderTop: '1px solid rgba(255, 255, 255, 0.1)',
position: 'relative',
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '4px',
background: 'linear-gradient(180deg, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0) 100%)',
pointerEvents: 'none',
},
}
}}
BackdropProps={{
sx: {
backgroundColor: 'transparent',
}
}}
>
<Box sx={{ overflow: 'auto', height: '100%', backgroundColor: "#1A1A1A" }}>
{Object.keys(menuData).map((section) => (
<Box key={section}>
<Button
fullWidth
onClick={() => handleSectionClick(section)}
sx={{
color: 'white',
justifyContent: 'space-between',
textTransform: 'none',
py: 2,
fontSize: '16px',
px: 3,
borderBottom: '1px solid rgba(255,255,255,0.1)',
fontFamily: theme.typography.fontFamily,
borderRadius: 0,
'&:hover': {
backgroundColor: 'rgba(255,255,255,0.05)'
}
}}
endIcon={
<KeyboardArrowDownIcon
sx={{
transform: openSection === section ? 'rotate(180deg)' : 'none',
transition: 'transform 0.3s'
}}
/>
}
>
{section}
</Button>
<Collapse in={openSection === section}>
{section === 'Resources' ? (
<Box sx={{ p: 2, borderBottom: '1px solid rgba(255, 255, 255, 0.1)' }}>
{menuData.Resources.columns.map((column, index) => (
<Box
key={index}
sx={{
mb: 3,
borderBottom: index !== menuData.Resources.columns.length - 1 ?
'1px solid rgba(255, 255, 255, 0.1)' : 'none',
pb: index !== menuData.Resources.columns.length - 1 ? 3 : 0,
}}
>
<Typography sx={{
color: 'white',
mb: 1,
pl: 1,
fontSize: '14px',
fontFamily: theme.typography.fontFamily,
}}>
{column.title}
</Typography>
{column.social ? (
<>
<Box sx={{ display: 'flex', gap: 2, mb: 2, pl: 1 }}>
{column.platforms.map((platform) => (
<IconButton
key={platform.name}
sx={{
padding: 0,
width: 40,
height: 40,
"&:hover": {
backgroundColor: "transparent",
opacity: 0.8,
},
}}
onClick={() => {
if (isCloud) {
ReactGA.event(platform.gaData);
}
window.open(platform.link, '_blank')
return;
}}
// onClick={() => showDesktopToast()}
>
<img
src={platform.icon}
alt={platform.name}
style={{
width: "100%",
height: "100%",
}}
/>
</IconButton>
))}
</Box>
<Typography sx={{
color: 'white',
mb: 1,
pl: 1,
fontSize: '14px',
fontFamily: theme.typography.fontFamily,
}}>
Follow Us
</Typography>
<Box sx={{ display: 'flex', gap: 2, pl: 1 }}>
{column.followUs.map((platform) => (
<IconButton
key={platform.name}
sx={{
padding: 0,
width: 40,
height: 40,
"&:hover": {
backgroundColor: "transparent",
opacity: 0.8,
},
}}
onClick={() => {
if (isCloud) {
ReactGA.event(platform.gaData);
}
window.open(platform.link, '_blank')
return;
}}
>
<img
src={platform.icon}
alt={platform.name}
style={{
width: "100%",
height: "100%",
}}
/>
</IconButton>
))}
</Box>
</>
) : (
column.items.map((item) => (
<Button
key={item.title}
fullWidth
startIcon={
<img
src={item.icon}
alt={item.title}
style={{ width: 20, height: 20 }}
/>
}
onClick={() => {
if (isCloud) {
ReactGA.event(item.gaData);
handleItemClick(item.link)
}else{
if(item.link.includes("usecases") || item.link.includes("docs")){
handleItemClick(item.link)
}else{
window.open("https://shuffler.io" + item.link, '_blank');
return;
}
}
}}
sx={{
color: 'white',
justifyContent: 'flex-start',
textTransform: 'none',
py: 1.5,
fontFamily: theme.typography.fontFamily,
borderRadius: 0,
'&:hover': {
backgroundColor: 'rgba(255,255,255,0.05)'
}
}}
>
{item.title}
</Button>
))
)}
</Box>
))}
</Box>
) : (
<Box>
{menuData[section].map((item) => (
<Button
key={item.title}
fullWidth
onClick={() => {
if (item.title === "Singul") {
window.open(item.path, '_blank');
return;
} else {
handleItemClick(item.path);
}
}}
sx={{
color: 'white',
justifyContent: 'flex-start',
textTransform: 'none',
py: 2,
px: 3,
gap: 2,
fontFamily: theme.typography.fontFamily,
borderBottom: '1px solid #494949',
borderRadius: 0,
position: 'relative',
'&:hover': {
backgroundColor: 'rgba(255,255,255,0.05)'
}
}}
>
<img
src={item.icon}
alt={item.title}
style={{
width: 40,
height: 40,
padding: 8,
borderRadius: 4,
backgroundColor: '#2F2F2F',
minWidth: 40,
minHeight: 40,
}}
/>
<Box sx={{ textAlign: 'left' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography sx={{
fontWeight: 600,
mb: 0.5,
color: 'white',
fontSize: '15px',
fontFamily: theme.typography.fontFamily,
}}>
{item.title}
</Typography>
{item.title === "Singul" && (
<Typography
sx={{
fontSize: '10px',
color: '#FF8544',
border: '1px solid #FF8544',
borderRadius: '4px',
padding: '2px 6px',
lineHeight: 1,
fontWeight: 500,
textTransform: 'uppercase',
letterSpacing: '0.5px',
}}
>
Coming Soon
</Typography>
)}
</Box>
<Typography
variant="body2"
sx={{
color: 'white',
fontSize: '12px',
fontFamily: theme.typography.fontFamily,
}}
>
{item.description}
</Typography>
</Box>
</Button>
))}
</Box>
)}
</Collapse>
</Box>
))}
{/* Static menu items */}
<Button
fullWidth
onClick={() => {
if (isCloud) {
ReactGA.event({
category: "navbar",
action: "pricing_click",
label: "go_to_pricing",
})
handleItemClick('/pricing')
}else{
window.open("https://shuffler.io/pricing", '_blank');
return;
}
}}
sx={{
color: 'white',
justifyContent: 'flex-start',
textTransform: 'none',
py: 2,
px: 3,
borderBottom: '1px solid rgba(255,255,255,0.1)',
fontFamily: theme.typography.fontFamily,
borderRadius: 0,
'&:hover': {
backgroundColor: 'rgba(255,255,255,0.05)'
}
}}
>
Pricing
</Button>
<Button
fullWidth
onClick={() => {
if (isCloud) {
ReactGA.event({
category: "navbar",
action: "partners_click",
label: "go_to_partners",
})
handleItemClick('/become-partner')
}else{
window.open("https://shuffler.io/become-partner", '_blank');
return;
}
}}
sx={{
color: 'white',
justifyContent: 'flex-start',
textTransform: 'none',
py: 2,
px: 3,
borderBottom: '1px solid rgba(255,255,255,0.1)',
fontFamily: theme.typography.fontFamily,
borderRadius: 0,
'&:hover': {
backgroundColor: 'rgba(255,255,255,0.05)'
}
}}
>
Partners
</Button>
{/* Action buttons */}
<Box sx={{ p: 2, mt: 2 }}>
<Button
fullWidth
variant="contained"
onClick={() => {
window.open("https://shuffler.io/contact?category=book_a_demo", '_blank');
return;
}}
sx={{
backgroundColor: '#FF8544',
color: '#1A1A1A',
textTransform: 'none',
py: 1.5,
mb: 2,
fontFamily: theme.typography.fontFamily,
borderRadius: 0,
'&:hover': {
backgroundColor: '#FF8544',
}
}}
>
Book a Demo
</Button>
<Button
fullWidth
variant="outlined"
onClick={() => showDesktopToast()}
sx={{
color: 'white',
borderColor: 'rgba(255,255,255,0.3)',
textTransform: 'none',
py: 1.5,
fontFamily: theme.typography.fontFamily,
borderRadius: 0,
'&:hover': {
borderColor: 'white',
backgroundColor: 'transparent'
}
}}
>
Login
</Button>
</Box>
</Box>
</Dialog>
);
};
const Navbar = (props) => {
const {
globalUrl,
isLoaded,
isLoggedIn,
removeCookie,
homePage,
userdata,
// isMobile,
serverside,
billingInfo,
SHUFFLE_VERSION,
notifications,
} = props;
const topbar_var = "topbar_closed10"
const theme = useTheme();
const {themeMode} = useContext(Context);
const currentTheme = getTheme(themeMode);
const {searchBarModalOpen, setSearchBarModalOpen, isDocSearchModalOpen, setIsDocSearchModalOpen} = useContext(Context)
const [pricingModalOpen, setPricingModalOpen] = useState(false);
const isTabletOrMobile = useMediaQuery(theme.breakpoints.down("lg"));
const isMobile = useMediaQuery(theme.breakpoints.down("md"));
const [anchorElNav, setAnchorElNav] = useState(null);
const [openMenu, setOpenMenu] = useState(null);
const [selectedOrganization, setSelectedOrganization] = useState({});
const navigate = useNavigate();
const [anchorElUser, setAnchorElUser] = useState(null);
const [anchorElOrg, setAnchorElOrg] = useState(null);
const [isOrgChanging, setIsOrgChanging] = useState(false);
const [isHovered, setIsHovered] = useState("");
const [orgSelectOpen, setOrgSelectOpen] = useState(false);
const [showTopbar, setShowTopbar] = useState(true) // Set to true to show top bar
const isCloud =
serverside === true || typeof window === "undefined"
? true
: window.location.host === "localhost:3002" ||
window.location.host === "shuffler.io" ||
window.location.host === "localhost:5002";
const stripeKey = typeof window === 'undefined' || window.location === undefined ? "" : window.location.origin === "https://shuffler.io" ? "pk_live_51PXYYMEJjT17t98N20qEqItyt1fLQjrnn41lPeG2PjnSlZHTDNKHuisAbW00s4KAn86nGuqB9uSVU4ds8MutbnMU00DPXpZ8ZD" : "pk_test_51PXYYMEJjT17t98NbDkojZ3DRvsFUQBs35LGMx3i436BXwEBVFKB9nCvHt0Q3M4MG3dz4mHheuWvfoYvpaL3GmsG00k1Rb2ksO"
useEffect(() => {
// Manually setShowTopbar(true) to show topbar by default
const topbar = localStorage.getItem(topbar_var)
if (topbar === "true") {
setShowTopbar(false)
}
}, [])
const pricingModal =
<Dialog
open={pricingModalOpen}
onClose={() => {
setPricingModalOpen(false);
}}
PaperProps={{
style: {
color: "white",
minWidth: 850,
minHeight: 370,
padding: 20,
backgroundColor: "rgba(0, 0, 0, 1)",
borderRadius: theme.palette?.borderRadius,
},
}}
>
<DialogTitle style={{ display: "flex" }}>
<span style={{ color: "white", fontSize: 24 }}>
Upgrade your plan
</span>
<IconButton
onClick={() => {
if (isCloud) {
ReactGA.event({
category: "navbar",
action: "close_upgrade_modal",
label: "navbar_upgrade_modal",
})
};
setPricingModalOpen(false);
}}
style={{
marginLeft: "auto",
position: "absolute",
top: 20,
right: 20,
}}
>
<CloseIcon />
</IconButton>
</DialogTitle>
<div style={{ paddingLeft: "30px", paddingRight: "30px" }}>
<LicencePopup
serverside={serverside}
removeCookie={removeCookie}
isLoaded={isLoaded}
isLoggedIn={isLoggedIn}
globalUrl={globalUrl}
selectedOrganization={selectedOrganization}
billingInfo={billingInfo}
features={selectedOrganization?.sync_features}
isCloud={isCloud}
userdata={userdata}
stripeKey={stripeKey}
setModalOpen={setPricingModalOpen}
licensePopup={true}
{...props}
/>
</div>
</Dialog>
const modalView = (
<Dialog
open={searchBarModalOpen && !isDocSearchModalOpen}
onClose={() => {
setSearchBarModalOpen(false);
}}
PaperProps={{
sx: {
color: "white",
minWidth: "750px",
height: "785px",
borderRadius: "16px",
border: "1px solid var(--Container-Stroke, #494949)",
background: currentTheme.palette.DialogStyle.backgroundColor,
boxShadow: "0px 16px 24px 8px rgba(0, 0, 0, 0.25)",
},
'& .MuiDialogContent-root': {
backgroundColor: currentTheme?.palette?.DialogStyle?.backgroundColor,
},
'& .MuiDialogTitle-root': {
backgroundColor: currentTheme?.palette?.DialogStyle?.backgroundColor,
},
}}
sx={{
zIndex: 50005,
}}
>
<Box sx={{ display: "flex", justifyContent: "space-between",backgroundColor: currentTheme?.palette?.DialogStyle?.backgroundColor, alignItems: "center", px: 2, pt: 2 }}>
<DialogTitle
sx={{
color: "var(--Paragraph-text, #C8C8C8)",
p: 0,
m: 0,
fontFamily: theme.typography.fontFamily,
}}
>
Search for Docs, Apps, Workflows and more
</DialogTitle>
<IconButton
onClick={() => setSearchBarModalOpen(false)}
sx={{
color: 'white',
'&:hover': {
backgroundColor: 'rgba(255, 255, 255, 0.1)'
}
}}
>
<CloseIcon />
</IconButton>
</Box>
<DialogContent style={{backgroundColor: currentTheme?.palette?.DialogStyle?.backgroundColor}} >
<Box sx={{ pt: 3 }}>
<SearchBox globalUrl={globalUrl} serverside={serverside} userdata={userdata} />
</Box>
</DialogContent>
<Divider sx={{ backgroundColor: 'rgba(255, 255, 255, 0.1)' }}/>
</Dialog>
);
const handleOpenNavMenu = (event) => {
setAnchorElNav(event.currentTarget);
};
const handleCloseNavMenu = () => {
setAnchorElNav(null);
};
const handleMenuOpen = (menu) => {
setOpenMenu(menu);
};
const handleMenuClose = () => {
setOpenMenu(null);
};
const handleOpenUserMenu = (event) => {
setAnchorElUser(event.currentTarget);
};
const handleCloseUserMenu = () => {
setAnchorElUser(null);
};
const handleOpenOrgMenu = (event) => {
setAnchorElOrg(event.currentTarget);
};
const handleCloseOrgMenu = () => {
setAnchorElOrg(null);
};
const menuStyles = {
"& .MuiPaper-root": {
backgroundColor: "#212121",
marginTop: 1,
backdropFilter: "blur(10px)",
fontFamily: theme.typography.fontFamily,
},
};
const menuItemBox = {
display: "flex",
alignItems: "flex-start",
padding: 2,
paddingTop: "20px",
paddingBottom: "20px",
paddingLeft: "20px",
gap: 2,
width: "100%",
backgroundColor: "#212121",
transition: "all 0.2s ease-in-out",
borderBottom: "1px solid #494949",
};
const buttonStyles = {
fontFamily: theme.typography.fontFamily,
color: "white",
textTransform: "none",
fontSize: "16px",
fontWeight: 400,
padding: "6px 12px",
"& .MuiSvgIcon-root": {
transition: "transform 0.2s ease-in-out",
},
"&:hover": {
backgroundColor: "transparent",
color: theme.palette.primary.main,
"& .MuiSvgIcon-root": {
transform: "rotate(180deg)",
},
},
// Add this to hide the ripple effect's container
"& .MuiTouchRipple-root": {
display: "none",
},
};
// Render menu content based on type
const renderMenuContent = (item, isCloud) => {
if (item === "Resources") {
return (
<Box sx={{ display: "flex", gap: 8, p: 1.5 }}>
{menuData.Resources.columns.map((column, index) => (
<Box
key={index}
sx={{
flex: 1,
borderRight:
index !== menuData.Resources.columns.length - 1
? "1px solid rgba(255, 255, 255, 0.1)"
: "none",
pr: index !== menuData.Resources.columns.length - 1 ? 4 : 0,
}}
>
<Typography
sx={{
color: "white",
fontSize: "20px",
fontWeight: 600,
mb: 2.5,
pl:
index !== menuData.Resources.columns.length - 1
? "12px"
: 0,
fontFamily: theme.typography.fontFamily,
}}
>
{column.title}
</Typography>
{column.social ? (
<>
<Box sx={{ display: "flex", gap: 1.5, mb: 4 }}>
{column.platforms.map((platform) => (
<IconButton
key={platform.name}
component="a"
href={platform.link}
target="_blank"
onClick={() => {
if (isCloud) {
ReactGA.event(platform.gaData);
}
handleMenuClose();
}}
sx={{
padding: 0,
width: 48,
height: 48,
"&:hover": {
backgroundColor: "transparent",
opacity: 0.8,
},
}}
>
<img
src={platform.icon}
alt={platform.name}
style={{
width: "100%",
height: "100%",
}}
/>
</IconButton>
))}
</Box>
<Typography
sx={{
color: "white",
fontSize: "20px",
fontWeight: 500,
mb: 2,
fontFamily: theme.typography.fontFamily,
}}
>
Follow Us
</Typography>
<Box sx={{ display: "flex", gap: 1.5 }}>
{column.followUs.map((platform) => (
<IconButton
key={platform.name}
component="a"
href={platform.link}
target="_blank"
onClick={() => {
if (isCloud) {
ReactGA.event(platform.gaData);
}
handleMenuClose();
}}
sx={{
padding: 0,
width: 48,
height: 48,
"&:hover": {
backgroundColor: "transparent",
opacity: 0.8,
},
}}
>
<img
src={platform.icon}
alt={platform.name}
style={{
width: "100%",
height: "100%",
}}
/>
</IconButton>
))}
</Box>
</>
) : (
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: 2,
}}
>
{column.items.map((item) => (
<Button
key={item.title}
component={Link}
to={item.link}
onClick={() => {
if (isCloud) {
ReactGA.event(item.gaData);
handleMenuClose();
} else if (item.link.includes("docs") || item.link.includes("usecases")) {
handleMenuClose();
} else {
window.open("https://shuffler.io" + item.link, '_blank');
return;
}
}}
startIcon={
<img
width={22}
height={22}
src={isHovered === item.title.toLowerCase() ? item.hoverIcon : item.icon}
alt={item.title}
style={{ width: 22, height: 22, paddingLeft: "5px"}}
/>
}
onMouseEnter={() => setIsHovered(item.title.toLowerCase())}
onMouseLeave={() => setIsHovered("")}
sx={{
color: "white",
justifyContent: "flex-start",
textTransform: "none",
padding: "8px 12px",
borderRadius: "8px",
fontSize: "16px",
fontWeight: 500,
fontFamily: theme.typography.fontFamily,
"&:hover": {
backgroundColor: "#292929",
color: theme.palette.primary.main,
},
"& .MuiButton-startIcon": {
marginRight: "12px",
},
}}
>
{item.title}
</Button>
))}
</Box>
)}
</Box>
))}
</Box>
);
}
return menuData[item].map((menuItem, index) => (
<MenuItem
key={menuItem.title}
onClick={() => {
if (menuItem.title === "Singul") {
window.open(menuItem.path, '_blank');
return;
}
if(isCloud) {
ReactGA.event(menuItem.gaData);
}
handleCloseNavMenu();
handleMenuClose();
if(isCloud) {
navigate(menuItem.path);
} else {
if(menuItem.path.includes("docs")){
navigate(menuItem.path);
handleMenuClose();
}else{
window.open("https://shuffler.io" + menuItem.path, '_blank');
return;
}
}
}}
sx={{
padding: 0,
backgroundColor: "#212121",
"&:hover": {
backgroundColor: "#212121",
},
"&:last-child": {
borderBottomLeftRadius: "4px",
borderBottomRightRadius: "4px",
},
"&:first-of-type": {
paddingTop: "4px",
},
}}
>
<Box
sx={{
...menuItemBox,
borderBottom:
index === menuData[item].length - 1
? "none"
: "1px solid rgba(255, 255, 255, 0.1)",
borderBottomLeftRadius:
index === menuData[item].length - 1 ? "4px" : 0,
borderBottomRightRadius:
index === menuData[item].length - 1 ? "4px" : 0,
"&:hover": {
backgroundColor: "#292929",
},
}}
>
<Box
component="img"
src={menuItem.icon}
sx={{
width: isCloud ? 44 : 75,
height: isCloud ? 44 : 75,
padding: "20px",
borderRadius: 1.5,
backgroundColor: "#2F2F2F",
}}
/>
<Box sx={{ paddingTop: "5px"}}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography
sx={{
color: "white",
fontWeight: 600,
mb: 0.5,
fontSize: "16px",
fontFamily: theme.typography.fontFamily,
}}
>
{menuItem.title}
</Typography>
</Box>
<Typography
variant="body2"
sx={{
color: "#C8C8C8",
fontSize: "14px",
paddingRight: "12px",
fontFamily: theme.typography.fontFamily,
}}
>
{menuItem.description}
</Typography>
</Box>
</Box>
</MenuItem>
));
};
const renderDesktopMenu = () => (
<Box
sx={{
flexGrow: 1,
display: "flex",
gap: isTabletOrMobile ? 0 : 1,
marginLeft: isTabletOrMobile ? 2 : 4,
alignItems: "center",
}}
>
{Object.keys(menuData).map((item) => (
<Box
key={item}
sx={{
position: "relative",
}}
>
<Button
onMouseEnter={() => handleMenuOpen(item)}
onMouseLeave={(e) => {
const dropdown = document.querySelector('.dropdown-content'); // Adjust the selector as needed
const rect = e.currentTarget.getBoundingClientRect();
const dropdownRect = dropdown ? dropdown.getBoundingClientRect() : null;
// Check if the cursor is moving down
const isMovingDown = e.clientY > rect.bottom;
// Check if the cursor is within the dropdown area or a buffer zone
const isOverDropdown = dropdownRect && (
(e.clientY >= dropdownRect.top - 20 && e.clientY <= dropdownRect.bottom + 20) && // Buffer zone
(e.clientX >= dropdownRect.left && e.clientX <= dropdownRect.right)
);
if (!isMovingDown && !isOverDropdown) {
handleMenuClose();
}
}}
sx={{
...buttonStyles,
color: openMenu === item ? theme.palette.primary.main : "white",
"& .MuiSvgIcon-root": {
transform: openMenu === item ? "rotate(180deg)" : "rotate(0deg)",
transition: "transform 0.3s ease",
},
}}
endIcon={<KeyboardArrowDownIcon sx={{ fontSize: 20 }} />}
>
{item}
</Button>
{/* Custom Dropdown */}
<Box
className="dropdown-content"
onMouseEnter={() => handleMenuOpen(item)}
onMouseLeave={handleMenuClose}
sx={{
position: "absolute",
top: "130%",
left:
item === "Products"
? 0
: item === "Services"
? -50
: item === "Resources"
? isTabletOrMobile ? -330 : -250
: 0,
backgroundColor: "#212121",
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
borderBottomLeftRadius: "4px",
borderBottomRightRadius: "4px",
boxShadow:
"0 8px 16px rgba(0, 0, 0, 0.25), 0 2px 4px rgba(0, 0, 0, 0.1)",
backdropFilter: "blur(10px)",
zIndex: -20,
opacity: openMenu === item ? 1 : 0,
visibility: openMenu === item ? "visible" : "hidden",
transform: openMenu === item ? "translateY(0)" : "translateY(-8px)",
transition: "transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease",
pointerEvents: openMenu === item ? "auto" : "none",
...(item === "Resources" && {
width: "900px",
padding: 3,
}),
}}
>
{renderMenuContent(item, isCloud)}
</Box>
</Box>
))}
<Button
component={Link}
to="/pricing"
sx={buttonStyles}
onClick={() => {
if(isCloud) {
navigate("/pricing");
ReactGA.event({
category: "navbar",
action: "pricing_click",
label: "go_to_pricing",
})
} else {
window.open("https://shuffler.io/pricing?env=Self-Hosted", '_blank');
return;
}
}}
>
Pricing
</Button>
<Box
className="partners-container"
sx={{
position: "relative",
"&::after": {
// This creates an invisible connection between button and dropdown
content: '""',
position: "absolute",
top: "100%",
left: 0,
width: "100%",
height: "20px", // Buffer zone height
backgroundColor: "transparent",
},
"&:hover .partners-dropdown": {
opacity: 1,
visibility: "visible",
pointerEvents: "auto",
transform: "translateY(0)",
transition: "opacity 0.3s ease, visibility 0s, transform 0.3s ease",
},
}}
>
<Button
component={Link}
to="/become-partner"
sx={{
...buttonStyles,
}}
onClick={() => {
if(isCloud) {
navigate("/become-partner");
ReactGA.event({
category: "navbar",
action: "partners_click",
label: "go_to_become_partner",
})
} else {
window.open("https://shuffler.io/become-partner", '_blank');
return;
}
}}
>
Partners
</Button>
<Box
className="partners-dropdown"
sx={{
position: "absolute",
top: "140%",
left: 0,
backgroundColor: "#212121",
borderRadius: "4px",
boxShadow: "0px 5px 15px rgba(0, 0, 0, 0.2)",
width: "220px",
opacity: 0,
visibility: "hidden",
pointerEvents: "none",
transform: "translateY(-10px)",
transition: "opacity 0.3s ease, visibility 0s linear 0.3s, transform 0.3s ease",
zIndex: 10,
"&:hover": {
opacity: 1,
visibility: "visible",
pointerEvents: "auto",
transform: "translateY(0)",
},
// Add a pseudo-element to create a buffer zone above the dropdown
"&::before": {
content: '""',
position: "absolute",
top: "-20px", // Match the height of the buffer zone
left: 0,
width: "100%",
height: "20px",
backgroundColor: "transparent",
}
}}
>
<Box sx={{}}>
<Button
fullWidth
sx={{
color: "white",
justifyContent: "flex-start",
textTransform: "none",
py: 2,
px: 2,
fontSize: "14px",
fontFamily: theme.typography.fontFamily,
borderRadius: "4px",
"&:hover": {
backgroundColor: "rgba(255,255,255,0.05)"
}
}}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
if(isCloud) {
navigate("/become-partner");
ReactGA.event({
category: "navbar",
action: "partners_click",
label: "become_partner_click",
})
} else {
window.open("https://shuffler.io/become-partner", '_blank');
}
}}
>
Become a Partner
</Button>
<Button
fullWidth
sx={{
color: "white",
justifyContent: "flex-start",
textTransform: "none",
fontSize: "14px",
fontFamily: theme.typography.fontFamily,
py: 2,
px: 2,
borderRadius: "4px",
"&:hover": {
backgroundColor: "rgba(255,255,255,0.05)"
}
}}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
navigate("/partners");
if(isCloud) {
ReactGA.event({
category: "navbar",
action: "partners_click",
label: "discover_partners_click",
})
}
}}
>
Discover Partners
</Button>
</Box>
</Box>
</Box>
</Box>
);
const menuItemStyles = {
fontFamily: theme.typography.fontFamily,
color: "white",
"&:hover": {
backgroundColor: "rgba(255, 133, 68, 0.1)",
},
};
const searchButtonStyles = {
fontFamily: theme.typography.fontFamily,
color: "rgba(255, 255, 255, 0.7)",
border: "1px solid rgba(26, 26, 26, 0.8)",
backgroundColor: "#2F2F2F",
textTransform: "none",
borderRadius: "8px",
fontSize: "14px",
padding: "6px 12px",
display: "flex",
alignItems: "center",
height: "42px",
gap: 1,
minWidth: "110px",
justifyContent: "space-between",
"&:hover": {
borderColor: "rgba(255, 255, 255, 0.1)",
},
};
const searchIconButtonStyles = {
color: "rgba(255, 255, 255, 0.7)",
padding: "8px",
minWidth: "unset",
height: "42px",
"&:hover": {
backgroundColor: "#3F3F3F",
},
};
// Add this shared button style
const sharedButtonStyles = {
fontFamily: theme.typography.fontFamily,
textTransform: "none",
fontSize: "16px",
fontWeight: 600,
padding: "6px 24px",
};
useEffect(() => {
Mousetrap.bind(['command+k', 'ctrl+k'], () => {
setSearchBarModalOpen(true);
setIsDocSearchModalOpen(false);
return false; // Prevent the default action
});
Mousetrap.bind(['esc'], () => {
setSearchBarModalOpen(false);
setIsDocSearchModalOpen(false);
return false; // Prevent the default action
});
return () => {
Mousetrap.unbind(['command+k', 'ctrl+k']);
};
}, []);
useEffect(() => {
if (isLoggedIn && userdata?.active_org?.id?.length > 0) {
handleGetOrg(userdata.active_org.id);
}
}, [isLoggedIn]);
const handleGetOrg = (orgId) => {
fetch(`${globalUrl}/api/v1/orgs/${orgId}`, {
method: "GET",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
})
.then((response) => {
if (response.status === 401) {
}
return response.json();
})
.then((responseJson) => {
if (responseJson["success"] === false) {
console.log("Error getting org: ", responseJson);
} else {
setSelectedOrganization(responseJson);
}
})
.catch((error) => {
console.log("Error getting org: ", error);
});
};
const handleClickLogout = () => {
console.log("SHOULD LOG OUT");
toast.info("Logging out...");
// Don't really care about the logout
fetch(globalUrl + "/api/v1/logout", {
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json",
},
})
.then(() => {
// Log out anyway
removeCookie("session_token", { path: "/" });
removeCookie("session_token", { path: "/" });
removeCookie("session_token", { path: "/" });
removeCookie("__session", { path: "/" });
removeCookie("__session", { path: "/" });
removeCookie("__session", { path: "/" });
removeCookie("__session", { path: "/" });
window.location.pathname = "/";
localStorage.setItem("globalUrl", "")
// Delete userinfo from localstorage
localStorage.removeItem("apps")
localStorage.removeItem("workflows")
localStorage.removeItem("userinfo")
})
.catch((error) => {
console.log(error);
});
};
const topbarHeight = showTopbar ? 40 : 0
const topbar = !isCloud || !showTopbar ? null :
curpath === "/" || curpath.includes("/docs") || curpath === "/pricing" || curpath === "/contact" || curpath === "/search" || curpath === "/usecases" || curpath === "/training" || curpath === "/poc" || curpath === "/professional-services" ?
<span style={{ zIndex: 50001, marginTop: -4}}>
{/* uncommit this to show topbar for release */}
{/* <div style={{ position: "relative", height: topbarHeight, backgroundImage: "linear-gradient(to right, #f86a3e, #f34079)", overflow: "hidden", }}>
<Typography style={{ paddingTop: 7, fontSize:16, margin: "auto", textAlign: "center", color: "white", }}>
// Shuffle 1.4.0 is out! Read more about&nbsp;
Shuffle 2.0.0 is out now!&nbsp;
<u>
<span onClick={() => {
ReactGA.event({
category: "landingpage",
action: "click_header_training",
label: "",
})
navigate("/articles/2.0_release")
}} style={{ cursor: "pointer", textDecoration: "none", fontWeight: 600, color: "rgba(255,255,255,0.9)" }}>
Read about it here.
</span>
</u>
</Typography>
<IconButton color="secondary" style={{ position: "absolute", top: 0, right: 20, }} onClick={(event) => {
setShowTopbar(false)
// Set storage that it's clicked
localStorage.setItem(topbar_var, "true")
}}>
<CloseIcon />
</IconButton>
</div> */}
{/* commit below div if we need to show release stuff */}
<div style={{ position: "relative", height: topbarHeight, backgroundImage: "linear-gradient(to right, #f86a3e, #f34079)", overflow: "hidden", }}>
<Typography style={{ paddingTop: 7, fontSize:16, margin: "auto", textAlign: "center", color: "white", }}>
{/* Shuffle 1.4.0 is out! Read more about&nbsp; */}
New&nbsp;
<u>
<span onClick={() => {
ReactGA.event({
category: "landingpage",
action: "click_header_training",
label: "",
})
navigate("/training")
}} style={{ cursor: "pointer", textDecoration: "none", fontWeight: 600, color: "rgba(255,255,255,0.9)" }}>
Public Training
</span>
</u>
&nbsp;Dates Released!
</Typography>
<IconButton color="secondary" style={{ position: "absolute", top: 0, right: 20, }} onClick={(event) => {
setShowTopbar(false)
// Set storage that it's clicked
localStorage.setItem(topbar_var, "true")
}}>
<CloseIcon />
</IconButton>
</div>
</span>
:
null
const handleClickChangeOrg = (orgId) => {
setIsOrgChanging(true);
const data = { org_id: orgId };
localStorage.setItem("globalUrl", "");
localStorage.setItem("getting_started_sidebar", "open");
toast.info("Changing organization...");
fetch(`${globalUrl}/api/v1/orgs/${orgId}/change`, {
mode: "cors",
credentials: "include",
crossDomain: true,
method: "POST",
body: JSON.stringify(data),
withCredentials: true,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
})
.then(function (response) {
if (response.status !== 200) {
console.log("Error in response");
} else {
localStorage.removeItem("apps");
localStorage.removeItem("workflows");
localStorage.removeItem("userinfo");
}
return response.json();
})
.then(function (responseJson) {
if (responseJson.success === true) {
if (
responseJson.region_url !== undefined &&
responseJson.region_url !== null &&
responseJson.region_url.length > 0
) {
console.log("Region Change: ", responseJson.region_url);
localStorage.setItem("globalUrl", responseJson.region_url);
//globalUrl = responseJson.region_url
}
if (responseJson["reason"] === "SSO_REDIRECT") {
toast.info("Redirecting to SSO login page as SSO is required for this organization.");
setTimeout(() => {
window.location.href = responseJson["url"];
}, 2000);
} else {
toast("Successfully changed active organization - refreshing!");
setTimeout(() => {
window.location.reload();
}, 2000);
}
} else {
setIsOrgChanging(false);
if (
responseJson.reason !== undefined &&
responseJson.reason !== null &&
responseJson.reason.length > 0
) {
toast(responseJson.reason);
} else {
toast("Failed changing org. Try again or contact support@shuffler.io if this persists.");
}
}
})
.catch((error) => {
console.log("error changing: ", error);
setIsOrgChanging(false);
});
};
// Add these helper functions from LeftSideBar
const getRegionTag = (region_url) => {
let regiontag = "EU";
if (region_url !== undefined && region_url !== null && region_url.length > 0) {
const regionsplit = region_url.split(".");
if (regionsplit.length > 2 && !regionsplit[0].includes("shuffler")) {
const namesplit = regionsplit[0].split("/");
regiontag = namesplit[namesplit.length - 1];
if (regiontag === "california") {
regiontag = "US";
} else if (regiontag === "frankfurt") {
regiontag = "EU-2";
} else if (regiontag === "ca") {
regiontag = "CA";
}
}
}
return regiontag;
};
const getRegionFlag = (region_url) => {
const regionTag = getRegionTag(region_url);
const regionMapping = {
"US": "us",
"EU": "eu",
"EU-2": "de",
"CA": "ca",
"UK": "gb"
};
const region = regionMapping[regionTag] || "eu";
return `https://flagcdn.com/48x36/${region}.png`;
};
// Add this useEffect to prevent scroll locking
useEffect(() => {
// Remove modal classes that cause scroll locking
const removeModalClasses = () => {
document.body.style.overflow = 'auto';
document.body.style.paddingRight = '0px';
};
// Create observer to watch for class changes
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.attributeName === 'class') {
if (document.body.classList.contains('MuiModal-open')) {
removeModalClasses();
}
}
});
});
// Start observing
observer.observe(document.body, {
attributes: true,
attributeFilter: ['class'],
});
// Cleanup
return () => {
observer.disconnect();
removeModalClasses();
};
}, []);
const handleOrgSelectClick = (event) => {
// Prevent the default Select behavior
event.preventDefault();
if(isCloud){
ReactGA.event({
category: "navbar",
action: "org_dropdown_click",
label: "org_dropdown_click",
})
}
// Toggle the select open state
setOrgSelectOpen(!orgSelectOpen);
};
return (
<AppBar
position="fixed"
sx={{
transition: serverside === true ? "none": undefined,
backgroundColor: "transparent",
boxShadow: "none",
backgroundImage: "none",
fontFamily: theme.typography.fontFamily,
padding: "5px 0",
zIndex: 12000,
"&::before": {
content: '""',
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "rgba(26, 26, 26, 0.75)",
backdropFilter: "blur(10px)",
zIndex: -1,
},
}}
>
{topbar}
<Container maxWidth="xl" sx={{ zoom: {
md: "0.7",
lg: "0.8",
xl: "1",
} }}>
<Toolbar disableGutters sx={{ justifyContent: "space-between" }}>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Link
to="/"
style={{
textDecoration: "none",
display: "flex",
alignItems: "center",
paddingLeft: "10px",
}}
onClick={() => {
if (isCloud) {
ReactGA.event({
category: "navbar",
action: "home_click",
label: "shuffle_logo",
});
}
}}
>
<img
src={"/images/logos/topleft_logo.svg"}
alt="shuffle logo"
style={{ height: 25 }}
/>
</Link>
</Box>
{isMobile ? (
/* Mobile Navigation Icons */
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
<IconButton
sx={{ color: "white" }}
onClick={() => {
setSearchBarModalOpen(true);
setIsDocSearchModalOpen(false);
}}
>
<SearchIcon sx={{ fontSize: 22 }} />
</IconButton>
<IconButton
onClick={
(e) => {
if(Boolean(anchorElNav)) {
handleCloseNavMenu();
}else{
handleOpenNavMenu(e);
}
}
}
sx={{ color: "white" }}
>
{Boolean(anchorElNav) ? (
<CloseIcon sx={{ fontSize: 24 }} />
) : (
<MenuIcon sx={{ fontSize: 24 }} />
)}
</IconButton>
<MobileMenu
anchorEl={anchorElNav}
handleClose={handleCloseNavMenu}
isLoggedIn={isLoggedIn}
navigate={navigate}
isCloud={isCloud}
/>
</Box>
) : (
<>
{modalView}
{pricingModal}
{renderDesktopMenu()}
{/* Right Side Buttons */}
<Box sx={{ display: "flex", gap: 2, alignItems: "center" }}>
{!isLoaded ? (
<LoadingSkeleton />
) : isLoggedIn ? (
<>
{isOrgChanging ? (
<LoadingSkeleton />
) : (
<>
<Tooltip
title={
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 4, color : "#FF8544" }}>
<KeyboardCommandKeyIcon style={{ width: 15 }} />
+ K
</div>
}
placement="bottom"
arrow
componentsProps={{
tooltip: {
sx: {
backgroundColor: "rgba(33, 33, 33, 1)",
color: "rgba(241, 241, 241, 1)",
fontSize: 12,
border: "1px solid rgba(73, 73, 73, 1)",
fontFamily: theme?.typography?.fontFamily,
}
},
popper: {
sx: {
zIndex: 1000019,
}
}
}}
>
<IconButton
sx={searchIconButtonStyles}
onClick={() => {
if (isCloud) {
ReactGA.event({
category: "navbar",
action: "search_icon_click",
label: "search_icon_click",
})
}
setSearchBarModalOpen(true);
setIsDocSearchModalOpen(false);
}}
>
<SearchIcon sx={{ fontSize: 24 }} />
</IconButton>
</Tooltip>
{isCloud && (userdata.org_status === undefined || userdata.org_status === null || userdata.org_status.length === 0) ?
<Button
variant="contained"
sx={{
...sharedButtonStyles,
backgroundColor: "#FF8544",
border: "1px solid #FF8544",
color: "#1A1A1A",
borderRadius: "8px",
padding: "8px 24px",
"&:hover": {
backgroundColor: "#FF955C",
},
}}
onClick={() => {
if (isCloud) {
ReactGA.event({
category: "navbar",
action: "upgrade_click",
label: "upgrade_button_click",
})
}
setPricingModalOpen(true)
}}
>
Upgrade
</Button>
: null}
<Button
variant="contained"
sx={{
...sharedButtonStyles,
backgroundColor: "#2F2F2F",
border: "1px solid #2F2F2F",
color: "white",
borderRadius: "8px",
padding: "8px 20px",
"&:hover": {
backgroundColor: "#494949",
color: "white",
border: "1px solid white",
},
}}
onClick={() => {
if (isCloud) {
ReactGA.event({
category: "navbar",
action: "go_to_product",
label: "go_to_product_click",
})
}
navigate("/workflows");
}}
>
{isTabletOrMobile ? "Product" : "Go to Product"}
</Button>
{/* Organization Dropdown */}
{/* <Select
open={orgSelectOpen}
value={userdata?.active_org?.id || ''}
onClick={handleOrgSelectClick}
onChange={(e) => {
if (e.target.value === "create_new_suborgs") {
return;
}
handleClickChangeOrg(e.target.value);
setOrgSelectOpen(false);
}}
onClose={() => setOrgSelectOpen(false)}
renderValue={(selected) => {
const selectedOrg = userdata?.orgs?.find(org => org.id === selected);
return (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<img
src={selectedOrg?.image || theme.palette.defaultImage}
alt={selectedOrg?.name}
style={{
width: 22,
height: 22,
borderRadius: '4px',
}}
/>
<span>{selectedOrg?.name?.slice(0, 3) + '...'}</span>
</Box>
);
}}
sx={{
display: {
xs:"none",
xl: "flex",
},
height: 45,
backgroundColor: "#212121",
borderRadius: "8px",
color: "white",
'.MuiSelect-select': {
padding: "9px 14px",
display: 'flex',
alignItems: 'center',
backgroundColor: '#212121',
gap: 1,
},
'.MuiOutlinedInput-notchedOutline': {
border: 'none',
},
'&:hover': {
backgroundColor: "rgba(47, 47, 47, 0.5)",
},
'& .MuiPaper-root': {
backgroundColor: '#212121',
},
'& .MuiMenu-paper': {
backgroundColor: '#212121',
},
}}
MenuProps={{
PaperProps: {
sx: {
backgroundColor: "#212121 !important",
color: "white",
marginTop: 1,
maxHeight: '400px',
boxShadow: "0 8px 16px rgba(0, 0, 0, 0.25)",
'& .MuiMenu-list': {
backgroundColor: "#212121",
},
'& .MuiMenuItem-root': {
backgroundColor: "#212121",
},
zIndex: 50004,
}
},
disableScrollLock: true,
BackdropProps: {
invisible: true,
sx: {
backgroundColor: 'transparent',
}
},
anchorOrigin: {
vertical: 'bottom',
horizontal: 'right',
},
transformOrigin: {
vertical: 'top',
horizontal: 'right',
},
}}
>
{userdata?.orgs?.map((data, index) => {
if (!data.name) return null;
const imagesize = 22;
const skipOrg = data.creator_org && userdata.child_orgs?.some(
childOrg => childOrg.id === data.creator_org
);
if (skipOrg) return null;
const regiontag = getRegionTag(data.region_url);
const flagUrl = getRegionFlag(data.region_url);
return (
<MenuItem
key={index}
value={data.id}
disabled={data.id === userdata?.active_org?.id}
sx={{
height: 40,
backgroundColor: data.id === userdata?.active_org?.id ?
'rgba(255, 133, 68, 0.1) !important' :
'#212121 !important',
'&:hover': {
backgroundColor: 'rgba(255, 255, 255, 0.1) !important',
},
}}
>
<Tooltip
title={data.creator_org ? `Suborg of ${data.creator_org}` : ''}
placement="left"
disableInteractive
enterTouchDelay={0}
leaveTouchDelay={0}
componentsProps={{
tooltip: {
sx: {
backgroundColor: '#2F2F2F',
fontSize: '12px',
padding: '8px 12px',
borderRadius: '4px'
}
}
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
{isCloud && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mr: 2, flexShrink: 0 }}>
<img
src={flagUrl}
alt={regiontag}
style={{
width: 20,
height: 15,
borderRadius: '2px',
}}
/>
</Box>
)}
<img
src={data.image || theme.palette.defaultImage}
alt={data.name}
style={{
width: imagesize,
height: imagesize,
marginLeft: data.creator_org ? 20 : 0,
borderRadius: '4px',
flexShrink: 0,
}}
/>
<Typography
sx={{
fontSize: '16px',
maxWidth: '200px',
overflow: 'auto',
whiteSpace: 'nowrap',
scrollbarWidth: 'thin',
'&::-webkit-scrollbar': {
height: '4px',
},
'&::-webkit-scrollbar-track': {
background: 'transparent',
},
'&::-webkit-scrollbar-thumb': {
background: 'rgba(255, 255, 255, 0.1)',
borderRadius: '4px',
},
'&::-webkit-scrollbar-thumb:hover': {
background: 'rgba(255, 255, 255, 0.2)',
},
}}
>
{data.name}
</Typography>
</Box>
</Tooltip>
</MenuItem>
);
})}
<Divider sx={{ backgroundColor: 'rgba(255,255,255,0.1)' }} />
<MenuItem
value="create_new_suborgs"
component={Link}
to="/admin?tab=tenants"
sx={{
height: 40,
justifyContent: 'center',
'&:hover': {
backgroundColor: 'rgba(255, 255, 255, 0.1)',
},
}}
>
<AddIcon sx={{ mr: 1 }} />
Add suborgs
</MenuItem>
</Select> */}
{/* User Avatar Menu */}
<IconButton
onClick={(e) => {
if (isCloud) {
ReactGA.event({
category: "navbar",
action: "click_user_menu",
label: "open_user_dropdown"
});
}
handleOpenUserMenu(e);
}}
sx={{
padding: 0,
"&:hover": {
backgroundColor: "rgba(47, 47, 47, 0.5)",
},
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Avatar
src={userdata?.avatar}
alt="avatar"
sx={{ width: 32, height: 32 }}
/>
</Box>
</IconButton>
<Menu
disabledPortal={serverside === true ? true : false}
anchorEl={anchorElUser}
open={Boolean(anchorElUser)}
onClose={handleCloseUserMenu}
disableScrollLock
sx={{ zIndex: 50004 }}
MenuListProps={{
sx: {
py: 0,
fontFamily: theme.typography.fontFamily,
backgroundColor: "#212121 !important",
}
}}
PaperProps={{
sx: {
backgroundColor: "#212121 !important",
color: "white",
marginTop: "16px",
minWidth: 200,
maxWidth: 200,
boxShadow: "0 8px 16px rgba(0, 0, 0, 0.25)",
fontFamily: theme.typography.fontFamily,
}
}}
BackdropProps={{
invisible: true,
sx: {
backgroundColor: 'transparent',
}
}}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
>
{/* User Info Section */}
<Box sx={{ px: 2, py: 1, fontFamily: theme.typography.fontFamily }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, py: 0.5 }}>
<Avatar
src={userdata?.avatar}
alt="avatar"
sx={{ width: 40, height: 40 }}
/>
<Box sx={{ width: '100%', minWidth: 0 }}>
<Typography sx={{
fontWeight: 500,
fontSize: '14px',
fontFamily: theme.typography.fontFamily,
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}}>
{userdata?.username}
</Typography>
</Box>
</Box>
</Box>
<Divider sx={{ backgroundColor: "rgba(255,255,255,0.1)" }} />
{/* Menu Items */}
<MenuItem
onClick={() => {
if (isCloud) {
ReactGA.event({
category: "navbar",
action: "user_dropdown_click",
label: "go_to_admin"
});
}
handleCloseUserMenu();
navigate("/admin");
}}
onMouseEnter={() => setIsHovered("organization")}
onMouseLeave={() => setIsHovered("")}
sx={{
py: 1.5,
px: 2,
fontFamily: theme.typography.fontFamily,
transition: "color 0.1s ease",
'&:hover': {
backgroundColor: 'rgba(255, 255, 255, 0.1)',
color: isHovered === "organization" ? "#FF8544" : "white",
},
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
<Box
component="img"
src={isHovered === "organization" ? "/images/icons/Org_hover.svg" : "/images/icons/Org.svg"}
sx={{ width: 20, height: 20 }}
/>
<Typography sx={{
color: "inherit",
fontSize: '14px',
fontFamily: theme.typography.fontFamily
}}>
Organization
</Typography>
</Box>
</MenuItem>
<MenuItem
onClick={() => {
if (isCloud) {
ReactGA.event({
category: "navbar",
action: "user_dropdown_click",
label: "go_to_settings",
})
}
handleCloseUserMenu();
navigate("/settings");
}}
onMouseEnter={() => setIsHovered("settings")}
onMouseLeave={() => setIsHovered("")}
sx={{
py: 1.5,
px: 2,
fontFamily: theme.typography.fontFamily,
transition: "color 0.1s ease",
'&:hover': {
backgroundColor: 'rgba(255, 255, 255, 0.1)',
color: isHovered === "settings" ? "#FF8544" : "white",
},
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
<Box
component="img"
src={isHovered === "settings" ? "/images/icons/settings_hover.svg" : "/images/icons/settings.svg"}
sx={{ width: 20, height: 20 }}
/>
<Typography sx={{
fontSize: '14px',
color: "inherit",
fontFamily: theme.typography.fontFamily
}}>
Settings
</Typography>
</Box>
</MenuItem>
<MenuItem
onClick={() => {
if (isCloud) {
ReactGA.event({
category: "navbar",
action: "user_dropdown_click",
label: "go_to_notifications",
})
}
handleCloseUserMenu();
navigate("/admin?admin_tab=notifications");
}}
onMouseEnter={() => setIsHovered("notifications")}
onMouseLeave={() => setIsHovered("")}
sx={{
py: 1.5,
px: 2,
fontFamily: theme.typography.fontFamily,
transition: "color 0.1s ease",
'&:hover': {
backgroundColor: 'rgba(255, 255, 255, 0.1)',
color: isHovered === "notifications" ? "#FF8544" : "white",
},
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
<Box
component="img"
src={isHovered === "notifications" ? "/images/icons/notifications_hover.svg" : "/images/icons/notifications.svg"}
sx={{ width: 20, height: 20 }}
/>
<Typography sx={{
fontSize: '14px',
color: "inherit",
fontFamily: theme.typography.fontFamily
}}>
Notifications ({notifications === undefined || notifications === null ? 0 :
notifications?.filter((notification) => notification.read === false).length})
</Typography>
</Box>
</MenuItem>
<Divider sx={{ backgroundColor: "rgba(255,255,255,0.1)" }} />
<MenuItem
onClick={() => {
if (isCloud) {
ReactGA.event({
category: "navbar",
action: "user_dropdown_click",
label: "go_to_about",
})
}
handleCloseUserMenu();
navigate("/docs/about");
}}
onMouseEnter={() => setIsHovered("about")}
onMouseLeave={() => setIsHovered("")}
sx={{
py: 1.5,
px: 2,
fontFamily: theme.typography.fontFamily,
transition: "color 0.1s ease",
'&:hover': {
backgroundColor: 'rgba(255, 255, 255, 0.1)',
color: isHovered === "about" ? "#FF8544" : "white",
},
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
<Box
component="img"
src={isHovered === "about" ? "/images/icons/about_hover.svg" : "/images/icons/about.svg"}
sx={{ width: 20, height: 20, marginLeft: -0.2 }}
/>
<Typography sx={{
fontSize: '14px',
color: "inherit",
fontFamily: theme.typography.fontFamily
}}>
About
</Typography>
</Box>
</MenuItem>
<MenuItem
onClick={() => {
if (isCloud) {
ReactGA.event({
category: "navbar",
action: "user_dropdown_click",
label: "logout_click",
})
}
handleCloseUserMenu();
handleClickLogout();
}}
onMouseEnter={() => setIsHovered("logout")}
onMouseLeave={() => setIsHovered("")}
sx={{
py: 1.5,
px: 2,
fontFamily: theme.typography.fontFamily,
'&:hover': {
backgroundColor: 'rgba(255, 255, 255, 0.1)',
color: isHovered === "logout" ? "#FD4C62" : "white",
},
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
<Box
component="img"
src={isHovered === "logout" ? "/images/icons/logout_hover.svg" : "/images/icons/logout.svg"}
sx={{ width: 20, height: 20 }}
/>
<Typography sx={{
fontSize: '14px',
color: "inherit",
fontFamily: theme.typography.fontFamily
}}>
Logout
</Typography>
</Box>
</MenuItem>
<Divider sx={{ backgroundColor: "rgba(255,255,255,0.1)" }} />
<Box
sx={{
py: 1.5,
px: 2,
backgroundColor: 'rgba(255, 255, 255, 0.03)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
mt: -1
}}
>
<Typography
variant="caption"
sx={{
color: 'rgba(255, 255, 255, 0.5)',
fontSize: '12px',
fontFamily: theme.typography.fontFamily,
letterSpacing: '0.2px',
margin: 0
}}
>
Version {SHUFFLE_VERSION}
</Typography>
</Box>
</Menu>
</>
)}
</>
) : (
<>
<Tooltip
title={
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 4, color : "#FF8544" }}>
<KeyboardCommandKeyIcon style={{ width: 15 }} />
+ K
</div>
}
placement="bottom"
arrow
componentsProps={{
tooltip: {
sx: {
backgroundColor: "rgba(33, 33, 33, 1)",
color: "rgba(241, 241, 241, 1)",
fontSize: 12,
border: "1px solid rgba(73, 73, 73, 1)",
fontFamily: theme?.typography?.fontFamily,
}
},
popper: {
sx: {
zIndex: 1000019,
}
}
}}
>
<IconButton
sx={searchIconButtonStyles}
onClick={() => {
if (isCloud) {
ReactGA.event({
category: "navbar",
action: "search_icon_click",
label: "search_icon_click",
})
}
setSearchBarModalOpen(true);
setIsDocSearchModalOpen(false);
}}
>
<SearchIcon sx={{ fontSize: 24 }} />
</IconButton>
</Tooltip>
<Divider orientation="vertical" sx={{ height: 24, bgcolor: '#494949'}} />
<Button
variant="outlined"
disableRipple
sx={{
...sharedButtonStyles,
px: 0,
color: "white",
backgroundColor: "transparent",
border: "none",
"&:hover": {
border: "none",
backgroundColor: "transparent",
color: "#FF8544",
},
"&:active": {
backgroundColor: "transparent",
},
}}
onClick={() => {
if (isCloud) {
ReactGA.event({
category: "navbar",
action: "signin_click",
label: "signin_click",
});
}
navigate("/login");
}}
>
Login
</Button>
{isCloud &&
<Button
variant="contained"
color="secondary"
sx={{
...sharedButtonStyles,
display: {
xs: "none",
lg: "block"
},
backgroundColor: "#2F2F2F",
border: "1px solid #2F2F2F",
borderRadius: "8px",
"&:hover": {
border: "1px solid white",
backgroundColor: "#2F2F2F",
},
}}
onClick={() => {
if (isCloud) {
ReactGA.event({
category: "navbar",
action: "signup_click",
label: "signup_click",
});
}
navigate("/register");
}}
>
Sign Up
</Button>
}
<Button
variant="contained"
sx={{
...sharedButtonStyles,
backgroundColor: "#FF8544",
borderRadius: "6px",
color: "#1A1A1A",
"&:hover": {
backgroundColor: "#FF955C",
},
}}
onClick={() => {
if (isCloud) {
ReactGA.event({
category: "navbar",
action: "Book a Demo",
label: "Book a Demo",
});
navigate("/contact?category=book_a_demo");
}else{
window.open("https://shuffler.io/contact?category=book_a_demo", '_blank');
return;
}
}}
>
{isTabletOrMobile ? "Demo" : "Book a Demo"}
</Button>
</>
)}
</Box>
</>
)}
</Toolbar>
</Container>
</AppBar>
);
};
export default Navbar;