import React, { useEffect, useRef, useState, useContext, useCallback, useMemo, memo } from "react"; import { ExpandLess as ExpandLessIcon, ExpandMore as ExpandMoreIcon, GridView as GridViewIcon, ShieldOutlined as ShieldOutlinedIcon, Add as AddIcon, BorderColor, Close as CloseIcon, ConstructionOutlined as ConstructionOutlinedIcon, Toc as TocIcon, Settings as SettingsIcon } from "@mui/icons-material"; import LightModeIcon from '@mui/icons-material/LightMode'; import DarkModeIcon from '@mui/icons-material/DarkMode'; import MonitorIcon from '@mui/icons-material/Monitor'; import SearchBox from "./SearchData.jsx"; import { Button, Typography, Autocomplete, TextField, Popper, Avatar, Menu, IconButton, MenuItem, Divider, Box, Dialog, DialogContent, Fade, Portal, Collapse, Tooltip, ToggleButtonGroup, ToggleButton, } from "@mui/material"; import RecentWorkflow from "../components/RecentWorkflow.jsx"; import { useNavigate } from "react-router"; import { getTheme } from "../theme.jsx"; import { Link } from "react-router-dom"; import { Business as BusinessIcon, Notifications as NotificationsIcon, HelpOutline as HelpOutlineIcon, MeetingRoom as MeetingRoomIcon, Lightbulb as LightbulbIcon, Search as SearchIcon } from "@mui/icons-material"; import { toast } from "react-toastify"; import { Context } from "../context/ContextApi.jsx"; const ShuffleLogo = "/images/Shuffle_logo.png"; const detectionIcon = "/icons/detection.svg"; const documentationIcon = "/icons/documentation.svg"; const ExpandMoreAndLessIcon = "/icons/expandMoreIcon.svg"; const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { const navigate = useNavigate(); const {setLeftSideBarOpenByClick, leftSideBarOpenByClick, setSearchBarModalOpen, searchBarModalOpen, logoutUrl, themeMode, handleThemeChange, isDocSearchModalOpen, setIsDocSearchModalOpen, supportEmail} = useContext(Context); const [expandLeftNav, setExpandLeftNav] = useState(false); const [activeOrgName, setActiveOrgName] = useState( userdata?.active_org?.name || "Select Organziation" ); const [openAutocomplete, setOpenAutocomplete] = useState(false); const [autocompleteValue, setAutocompleteValue] = useState(""); const [openautomatetab, setOpenautomateTab] = useState(false); const [openSecurityTab, setOpenSecurityTab] = useState(false); const [selectedOrg, setSelectedOrg] = useState(activeOrgName); const [anchorElAvatar, setAnchorElAvatar] = React.useState(null); const [anchorEl, setAnchorEl] = React.useState(null); const [recentworkflows, setRecentworkflows] = useState(null); const [usersWorkFlows, setUsersWorkFlows] = useState([]); const [currentOpenTab, setCurrentOpenTab] = useState(""); const params = new URLSearchParams(window.location.search); const [currentSelectedTheme, setCurrentSelectedTheme] = useState(themeMode); const tab = params.get("tab"); const currentPath = tab ? `${window.location.pathname}?tab=${tab}` : window.location.pathname; const [hoverOnAvatar, setHoverOnAvatar] = useState(false); const [orgOptions, setOrgOptions] = useState( userdata?.orgs?.map((org) => ({ id: org.id, name: org.name, image: org.image, region_url: org.region_url, })) || [] ); const userOrgs = React.useMemo(() => { return orgOptions.find((option) => option.name === selectedOrg); }, [selectedOrg, orgOptions]); const theme = getTheme(themeMode); const darkText = "#0F1419" const lightText = "#CDCDCD" const lightHoverColor = "#D6D6D6" const darkHoverColor = "#2F2F2F" //With this code it is opening search bar on google chrome search bar as well which is not required useEffect(() => { const handleKeyDown = (event) => { if ((event.ctrlKey || event.metaKey) && event.key === "k") { event.preventDefault(); setSearchBarModalOpen((prev)=> !prev); } }; window.addEventListener("keydown", handleKeyDown); return () => { window.removeEventListener("keydown", handleKeyDown); }; }, [setSearchBarModalOpen]); useEffect(() => { if (userdata && userdata?.active_org?.branding?.theme?.length > 0) { setCurrentSelectedTheme(userdata?.active_org?.branding?.theme); }else if (userdata && userdata?.theme?.length > 0) { setCurrentSelectedTheme(userdata?.theme); } }, [userdata]); const CustomPopper = (props) => { return ( {props.children} { setSelectedOrg(userdata?.active_org?.name); setOpenAutocomplete(false) }} > Add suborg ); }; const iconRef = useRef(null); const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); const parsedAvatar = userdata?.avatar !== undefined && userdata?.avatar !== null && userdata?.avatar.length > 0 ? userdata?.avatar : ""; const autocompleteRef = useRef(null); const hoverOutColor = "#e8eaf6"; const handleClose = () => { setAnchorEl(null); setAnchorElAvatar(null); }; const hrefStyle = { color: hoverOutColor, textDecoration: "none", textTransform: "none", fontStyle: "normal", width: "100%", fontSize: "16px", }; const isCloud = serverside === true || typeof window === "undefined" ? true : window.location.host === "localhost:3002" || window.location.host === "shuffler.io" || window.location.host === "localhost:5002"; const UpdateTabStatus = useCallback(() => { const lastTabOpenByUser = localStorage.getItem("lastTabOpenByUser"); if ((lastTabOpenByUser === "automate" && currentPath.includes("/dashboards/automate")) || currentPath.includes("/dashboards/automate")) { setOpenautomateTab(true); setCurrentOpenTab("automate"); setOpenSecurityTab(false); } else if ((lastTabOpenByUser === "security" && currentPath.includes("/dashboards/security")) || currentPath.includes("/dashboards/security")) { setOpenautomateTab(false); setOpenSecurityTab(true); setCurrentOpenTab("security"); } else if ((lastTabOpenByUser === "usecases" && currentPath.includes("/usecases")) || currentPath.includes("/usecases")) { setOpenautomateTab(true); setOpenSecurityTab(false); setCurrentOpenTab("usecases"); } else if ((lastTabOpenByUser === "workflows" && currentPath === "/workflows") || currentPath === "/workflows") { setOpenautomateTab(true); setOpenSecurityTab(false); setCurrentOpenTab("workflows"); } else if ((lastTabOpenByUser === "apps" && currentPath.includes("/apps")) || currentPath.includes("/apps")) { setOpenautomateTab(true); setOpenSecurityTab(false); setCurrentOpenTab("apps"); } else if ((lastTabOpenByUser === "forms" && currentPath.includes("/forms")) || currentPath.includes("/forms")) { setOpenautomateTab(false); setOpenSecurityTab(true); setCurrentOpenTab("forms"); } else if ((lastTabOpenByUser === "datastore" && currentPath.includes("/admin?tab=datastore")) || currentPath.includes("/admin?tab=datastore")) { setOpenautomateTab(false); setOpenSecurityTab(true); setCurrentOpenTab("datastore"); } else if ((lastTabOpenByUser === "files" && currentPath.includes("/admin?tab=files")) || currentPath.includes("/admin?tab=files")) { setOpenautomateTab(false); setOpenSecurityTab(true); setCurrentOpenTab("files"); } else if ((lastTabOpenByUser === "locations" && currentPath.includes("/admin?tab=locations")) || currentPath.includes("/admin?tab=locations")) { setOpenautomateTab(false); setOpenSecurityTab(true); setCurrentOpenTab("locations"); } else if ((lastTabOpenByUser === "docs" || currentPath.includes("/docs")) || currentPath.includes("/docs")) { setOpenautomateTab(false); setOpenSecurityTab(false); setCurrentOpenTab("docs"); } else { setOpenautomateTab(true); setOpenSecurityTab(false); setCurrentOpenTab(""); } },[currentPath]); useEffect(() => { UpdateTabStatus() const expandLeftNav1 = localStorage.getItem("expandLeftNav") if (expandLeftNav1 === "false") { setLeftSideBarOpenByClick(false) } else { const currentLocation = window?.location?.pathname if (currentLocation?.includes('/workflows/')) { } else { setLeftSideBarOpenByClick(true) setExpandLeftNav(true) } } }, []) const getAvailableWorkflows = useCallback((amount) => { var url = `${globalUrl}/api/v1/workflows` if (amount !== undefined && amount !== null) { url += `?top=${amount}` } fetch(url, { method: "GET", headers: { "Content-Type": "application/json", Accept: "application/json", }, credentials: "include", }) .then((response) => { if (response.status !== 200) { toast("Failed getting workflows. Are you logged in?"); return } return response.json(); }) .then((responseJson) => { if (responseJson !== undefined) { var newarray = [] for (var wfkey in responseJson) { const wf = responseJson[wfkey] if (wf.public === true || wf.hidden === true) { continue } newarray.push(wf) } var actionnamelist = []; var parsedactionlist = []; for (var key in newarray) { for (var actionkey in newarray[key].actions) { const action = newarray[key].actions[actionkey]; if (actionnamelist.includes(action.app_name)) { continue; } actionnamelist.push(action.app_name); parsedactionlist.push(action); } } if (newarray.length > 0) { try { localStorage.setItem("workflows", JSON.stringify(newarray)) } catch (e) { console.log("Failed to set workflows in localstorage: ", e) } } setTimeout(() => { setUsersWorkFlows(newarray); if (newarray.length === 0) { setUsersWorkFlows([]); } }, 250) } }) .catch((error) => { toast(error.toString()); }); }, [usersWorkFlows]); useEffect(() => { const fetchData = async () => { if (recentworkflows === null || recentworkflows === undefined || recentworkflows?.length === 0) { await delay(2000); } const storagewf = localStorage.getItem("workflows"); const storageWorkflows = JSON.parse(storagewf); if (storageWorkflows) { setUsersWorkFlows(storageWorkflows); } else { getAvailableWorkflows(); } }; fetchData(); }, []); useEffect(() => { if (usersWorkFlows) { const recentWorkflow = HandleGetUsersRecentWorkflows(usersWorkFlows); setRecentworkflows(recentWorkflow); } }, [usersWorkFlows]); const handleChangeTheme = (newTheme) => { const data = { "org_id": userdata?.active_org?.id, }; data["branding"] = { "theme": newTheme, "enable_chat": userdata?.active_org?.branding?.enable_chat || false, "home_url": userdata.active_org?.branding?.home_url || "", "brand_color": userdata?.active_org?.branding?.brand_color || theme.palette.primary.main, "brand_name": userdata?.active_org?.branding?.brand_name || "", "logout_url": userdata?.active_org?.branding?.logout_url || "", "support_email": userdata?.active_org?.branding?.support_email || "", } data["editing_branding"] = true; const url = globalUrl + `/api/v1/orgs/${userdata?.active_org?.id}`; fetch(url, { mode: "cors", method: "POST", body: JSON.stringify(data), credentials: "include", crossDomain: true, withCredentials: true, headers: { "Content-Type": "application/json; charset=utf-8", }, }) .then((response) => response.json().then((responseJson) => { if (responseJson["success"] === false) { toast("Failed updating org: ", responseJson.reason); } }) ) .catch((error) => { console.log("Error changing theme: ", error); }); }; const handleUpdateTheme = (newTheme) => { handleThemeChange(newTheme); setCurrentSelectedTheme(newTheme) const data = { "user_id": userdata?.id, "theme": newTheme, } const url = globalUrl + `/api/v1/users/updateuser`; fetch(url, { mode: "cors", method: "PUT", body: JSON.stringify(data), credentials: "include", crossDomain: true, withCredentials: true, headers: { "Content-Type": "application/json; charset=utf-8", }, }).then((response) => response.json().then((responseJson) => { if (responseJson["success"] === false) { toast("Failed saving your theme: ", responseJson.reason); } }) ).catch((error) => { console.log("Error saving your theme: ", error); }); }; const removeCookie = (name, path = "/") => { document.cookie = `${name}=; path=${path}; expires=Thu, 01 Jan 1970 00:00:00 GMT;`; }; const handleClickLogout = () => { // Logout API call fetch(`${globalUrl}/api/v1/logout`, { credentials: "include", method: "POST", headers: { "Content-Type": "application/json", }, }) .then((response) => { if (response.status !== 200) { toast.error("Failed to logout. Please try again."); return; } // Remove cookies removeCookie("session_token", { path: "/" }); removeCookie("__session", { path: "/" }); // Clear localStorage localStorage.clear(); if(userdata?.org_status?.includes("integration_partner") && logoutUrl?.length > 0){ window.location.href = logoutUrl } else { window.location.pathname = "/"; } }) .catch((error) => { console.error("Logout error:", error); }); }; const avatarMenu = ( { setAnchorElAvatar(event.currentTarget); }} disableRipple disableElevation > {userdata && (userdata?.org_status?.includes("integration_partner") && userdata?.org_status?.includes("sub_org")) ? null : ( <> { if (newTheme === null) { return; } if (newTheme === currentSelectedTheme) { return; } handleThemeChange(newTheme) setCurrentSelectedTheme(newTheme); if (userdata?.org_status?.includes("integration_partner")){ handleChangeTheme(newTheme); } else { handleUpdateTheme(newTheme); } }} aria-label="theme" style={{display: 'flex', justifyContent: "center", marginBottom: 10, }} > )} { handleClose(); }} style={{fontSize: 18}} > Organization { handleClose(); }} style={{fontSize: 18}} > Account { handleClose(); }} style={{fontSize: 18}} > Notifications ({ notifications === undefined || notifications === null ? 0 : notifications?.filter((notification) => notification.read === false).length }) 0 ? userdata?.active_org?.branding?.documentation_link : "/docs" } target={userdata?.active_org?.branding?.documentation_link?.length > 0 && userdata?.org_status?.includes("integration_partner") ? "_blank" : "_self" } style={hrefStyle}> { handleClose(); }} style={{fontSize: 18}} > About { handleClickLogout(); event.preventDefault(); handleClose(); }} >  Logout Version: 2.1.0-rc1 ); const getWorkflowAppgroup = (data) => { if (!data.actions) { return []; } let appsFound = []; Object.keys(data.actions).forEach((key) => { const parsedAction = data.actions[key]; if (!parsedAction.large_image) { return; } if ( appsFound.findIndex((app) => app.app_name === parsedAction.app_name) < 0 ) { appsFound.push(parsedAction); } }); return appsFound; }; const HandleGetUsersRecentWorkflows = useCallback(() => { if (!usersWorkFlows) { return []; } let groupedWorkflows = []; usersWorkFlows.forEach((workflow) => { const apps = getWorkflowAppgroup(workflow); if (apps.length > 0) { groupedWorkflows.push({ name: workflow.name, id: workflow.id, apps: apps, }); } }); return groupedWorkflows; },[usersWorkFlows]); const handleClickChangeOrg = (orgId) => { toast.info("Changing active organization - please wait!"); const data = { org_id: orgId, }; if (userdata?.active_org?.id === orgId) { return } localStorage.setItem("globalUrl", ""); localStorage.setItem("getting_started_sidebar", "open"); 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"); localStorage.removeItem("lastTabOpenByUser"); } return response.json(); }) .then(function (responseJson) { if (responseJson.success === true) { if ( responseJson?.region_url !== undefined && responseJson?.region_url !== null && responseJson?.region_url.length > 0 ) { localStorage.setItem("globalUrl", responseJson.region_url); } if (responseJson["reason"] === "SSO_REDIRECT") { setTimeout(() => { toast.info( "Redirecting to SSO login page as SSO is required for this organization." ); window.location.href = responseJson["url"]; return; }, 2000); } else { setTimeout(() => { window.location.reload(); }, 2000); } toast.success("Successfully changed active organization - refreshing!"); } else { if ( responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason.length > 0 ) { toast(responseJson.reason); } else { toast( `Failed changing org. Try again or contact ${supportEmail} if this persists.` ); } } }) .catch((error) => { console.log("error changing: ", error); }); }; const getRegionTag = (region_url) => { let regiontag = "UK"; //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; }; useEffect(() => { if(activeOrgName !== userdata?.active_org?.name){ setActiveOrgName(userdata?.active_org?.name || "Select Organization"); } }, [userdata]); const CheckOrgStates = useCallback(() => { setOrgOptions( userdata?.orgs?.map((org) => { let skipOrg = false; if ( org.creator_org !== undefined && org.creator_org !== null && org.creator_org.length > 0 ) { // Finds the parent org for (let key in userdata.child_orgs) { if (userdata.child_orgs[key].id === org.creator_org) { skipOrg = true; break; } } if (skipOrg) { return null; // Skip this org } } return { id: org.id, name: org.name, image: org.image, region_url: getRegionTag(org.region_url), margin_left: org.creator_org !== undefined && org.creator_org !== null && org.creator_org.length > 0 ? 20 : 0, }; }) || [] ); setActiveOrgName(userdata?.active_org?.name || "Select Organization"); setSelectedOrg(userdata?.active_org?.name || "Select Organization"); }, [userdata]); useEffect(() => { if (typeof userdata?.id === "string" && userdata?.id?.length > 0) { CheckOrgStates(); setAutocompleteValue(userdata?.active_org?.name || ""); } }, []); const ButtonStyle = { width: expandLeftNav ? "100%" : 30, justifyContent: expandLeftNav ? "flex-start" : "center", height: 35, color: openautomatetab ? "#F1F1F1" : themeMode === "dark" ? lightText : darkText, textTransform: "none", "& .MuiButton-root ": { width: expandLeftNav ? "100%" : 30, padding: 0, }, fontSize: 18 }; const getRegionFlag = (region_url) => { var region = "UK"; const regionMapping = { "US": "us", "EU": "eu", "CA": "ca", "UK": "gb" }; region = regionMapping[region_url] || "eu"; return `https://flagcdn.com/48x36/${region}.png`; }; useEffect(() => { if (window?.location?.pathname?.includes("/workflows/")) { setExpandLeftNav(false); } }, [window?.location?.pathname]); const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); const showPartnerLogo = userdata?.org_status?.includes("integration_partner") && userdata?.active_org?.image !== undefined && userdata?.active_org?.image !== null && userdata?.active_org?.image.length > 0 return (
{searchBarModalOpen && !isDocSearchModalOpen ? : null} { if(window?.location?.pathname?.includes("/workflows/")) { setExpandLeftNav(true) } }} onMouseLeave={()=>{ if(window?.location?.pathname?.includes("/workflows/")) { setExpandLeftNav(false) } }} > Shuffle Logo {(!leftSideBarOpenByClick || window?.location?.pathname?.includes("/workflows/")) && setExpandLeftNav(true)}} onMouseLeave={()=>{(!leftSideBarOpenByClick || window?.location?.pathname?.includes("/workflows/")) && setExpandLeftNav(false);setOpenAutocomplete(false)}}> {expandLeftNav ? ( ), endAdornment: ( Ctrl+K ), disableUnderline: true, }} onClick={() => { setSearchBarModalOpen(true); setIsDocSearchModalOpen(false); }} onChange={() => { setSearchBarModalOpen(true); setIsDocSearchModalOpen(false); }} /> ):( <> )} { setOpenautomateTab((prev) => !prev); setOpenSecurityTab(false); }} style={{ color: themeMode === "dark" ? lightText : darkText, marginLeft: 0.625, }} onMouseOver={(event)=>{ event.currentTarget.style.backgroundColor = themeMode === "dark" ? darkHoverColor : lightHoverColor; }} onMouseOut={(event)=>{ event.currentTarget.style.backgroundColor = "transparent"; }} > {openautomatetab ? ( ) : ( )} { setOpenSecurityTab((prev) => !prev); setOpenautomateTab(false); }} style={{ marginLeft: 0.625, color: themeMode === "dark" ? lightText : darkText, }} onMouseOver={(event)=>{ event.currentTarget.style.backgroundColor = themeMode === "dark" ? darkHoverColor : lightHoverColor; }} onMouseOut={(event)=>{ event.currentTarget.style.backgroundColor = "transparent"; }} > {openSecurityTab ? ( ) : ( )} {userdata && (userdata?.support || userdata?.active_org?.role === "admin") ? ( <> ): null} {recentworkflows?.length > 0 ? Recent Workflows {recentworkflows?.slice(0, 2).map((workflow, index) => { return ( ) }) } : null } {userdata?.licensed !== true && !userdata?.org_status?.includes("integration_partner") && expandLeftNav &&
} orgOptions, [orgOptions])} getOptionLabel={(option) => option.name} PopperComponent={CustomPopper} open={openAutocomplete} onOpen={() => setOpenAutocomplete(true)} renderOption={(props, option, { selected, index }) => (
  • { e.currentTarget.style.backgroundColor = theme.palette.hoverColor; }} onMouseOut={(e) => { e.currentTarget.style.backgroundColor = option.name === selectedOrg ? theme.palette.hoverColor : "transparent"; }} onClick={(e) => { if (option.id !== userdata?.active_org?.id) { setSelectedOrg(option.name); handleClickChangeOrg(option.id); } else { setSelectedOrg(userdata?.active_org?.name); } setOpenAutocomplete(false); }} > { isCloud ? ( {option.region_url} {option.region_url} ) : null } {option.name} {option.name}
  • )} onChange={(event, newValue) => { if (newValue) { if (userdata?.active_org?.id !== newValue.id) { setSelectedOrg(newValue.name); handleClickChangeOrg(newValue.id); } else { setSelectedOrg(userdata?.active_org?.name); } setOpenAutocomplete(false); } }} onInputChange={(event, newInputValue) => { setAutocompleteValue(newInputValue); }} filterOptions={(options, params) => { const normalize = (str) => str.toLowerCase().replace(/[\s-]+/g, ""); const input = normalize(params.inputValue); return options.filter((option) => normalize(option.name).includes(input) || normalize(option.region_url).includes(input) ); }} value={userOrgs} renderInput={(params) => ( {expandLeftNav ? ( ) : ( setOpenAutocomplete((prev) => !prev)} style={{ cursor: "pointer", fontSize: "24px", opacity: expandLeftNav ? 0 : 1, transition: "opacity 0.3s ease", color: themeMode === "dark" ? lightText : darkText, }} /> )} )} />
    { event.currentTarget.style.backgroundColor = themeMode === "dark" ? darkHoverColor : lightHoverColor; setHoverOnAvatar(true); }} onMouseLeave={(event) => { event.currentTarget.style.backgroundColor = "transparent"; setHoverOnAvatar(false); }} > {expandLeftNav ? ( <> {avatarMenu} ) : ( )}
    ); }; export default LeftSideBar; const ModalView = memo(({searchBarModalOpen, setSearchBarModalOpen, globalUrl, serverside, userdata, isDocSearchModalOpen}) => { const {themeMode} = useContext(Context); const theme = getTheme(themeMode); return ( ( { setSearchBarModalOpen(false); }} PaperProps={{ sx: { color: theme.palette.text.primary, minWidth: "750px", height: "785px", borderRadius: "16px", border: "1px solid var(--Container-Stroke, #494949)", background: theme?.palette?.DialogStyle?.backgroundColor, boxShadow: "0px 16px 24px 8px rgba(0, 0, 0, 0.25)", zIndex: 13000, paddingTop: "20px", '& .MuiDialogContent-root': { backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, }, }, }} > ) ) });