diff --git a/frontend/src/components/AppModal.jsx b/frontend/src/components/AppModal.jsx index a4bd272f..17189887 100644 --- a/frontend/src/components/AppModal.jsx +++ b/frontend/src/components/AppModal.jsx @@ -291,11 +291,23 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { fullWidth PaperProps={{ sx: { - borderRadius: 3, - border: "1px solid var(--Container-Stroke, #494949)", - backgroundColor: "var(--Container, #212121)", + borderRadius: 2, + border: "1px solid #494949", minWidth: '440px', - fontFamily: "Inter" + fontFamily: "Inter", + backgroundColor: "#212121", + '& .MuiDialogContent-root': { + backgroundColor: "#212121", + }, + '& .MuiDialogTitle-root': { + backgroundColor: "#212121", + }, + '& .MuiTypography-root': { + fontFamily: 'Inter, sans-serif', + }, + '& .MuiButton-root': { + fontFamily: 'Inter, sans-serif', + }, } }} > @@ -304,27 +316,29 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { display: 'flex', justifyContent: 'space-between', alignItems: 'center', - pb: 1, + pb: 2, pt: 2, - px: 3 + pl: 3, + pr: 2, + fontFamily: "Inter" }} > - - About Gmail + + About {app?.name.replace(/_/g, ' ').replace(/\b\w/g, char => char.toUpperCase())} - - + +
{ boxShadow: "0px 0px 10px 0px rgba(0, 0, 0, 0.2)" }} /> -
+
- + {newAppname} { @@ -380,13 +394,16 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { @@ -421,14 +438,26 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { textAlign: "start", flex: 1, }}> - + 20 - + Public Workflow
@@ -439,8 +468,9 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { paddingLeft: "10px", height: "100%", }}> - @@ -456,7 +486,7 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { paddingLeft: "10px", paddingTop: "5px" }}> -
+
{ app?.collection ? ( <> @@ -474,7 +504,7 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { }
- + Part of a collection
@@ -489,9 +519,9 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => {
{ (foundAppUsecase?.srcapp !== undefined && foundAppUsecase?.dstapp !== undefined) ? ( @@ -503,9 +533,9 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => {
{ ) } - + {foundAppUsecase?.name || "Search for a Usecase"} @@ -560,24 +590,24 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => {
- + ); }; diff --git a/frontend/src/components/AppSelection.jsx b/frontend/src/components/AppSelection.jsx index df78af8e..76d38d88 100644 --- a/frontend/src/components/AppSelection.jsx +++ b/frontend/src/components/AppSelection.jsx @@ -428,30 +428,56 @@ const AppSelection = props => { />
) : null} - - Find your apps - - - Select the apps you work with and we will connect them for you. - + { + !isAppPage && ( + <> + + Find your apps + + + Select the apps you work with and we will connect them for you. + + + ) + } + { + isAppPage && ( +
+ + Your organization has no apps yet, select your starting apps here + or discover more apps using the { + navigate("/apps2?tab=all_apps") + }} + style={{ color: "#FF8444", fontWeight: "medium", fontSize: 16, cursor:"pointer" }}>App Library + +
+ ) + } {appButtons.map((appData, index) => { // This is here due to a memory issue with setting apps properly diff --git a/frontend/src/views/Apps2.jsx b/frontend/src/views/Apps2.jsx index 7011be79..011df1ad 100644 --- a/frontend/src/views/Apps2.jsx +++ b/frontend/src/views/Apps2.jsx @@ -12,12 +12,22 @@ import { Box, CircularProgress, Checkbox, + Skeleton, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + IconButton, } from "@mui/material"; import { Context } from "../context/ContextApi.jsx"; import Add from '@mui/icons-material/Add'; +import CachedIcon from '@mui/icons-material/Cached'; +import CloudDownloadIcon from '@mui/icons-material/CloudDownload'; +import EditIcon from '@mui/icons-material/Edit'; import InputAdornment from '@mui/material/InputAdornment'; import Search from '@mui/icons-material/Search'; import ClearIcon from '@mui/icons-material/Clear'; +import CloseIcon from '@mui/icons-material/Close'; import { ClearRefinements, connectHits, connectSearchBox, connectStateResults, InstantSearch, RefinementList, connectRefinementList, Configure } from "react-instantsearch-dom"; import { removeQuery } from "../components/ScrollToTop.jsx"; @@ -34,20 +44,23 @@ const searchClient = algoliasearch( ); // AppCard Component -const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl, deactivatedIndexes, currTab, handleAppClick }) => { +const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl, deactivatedIndexes, currTab, handleAppClick, leftSideBarOpenByClick, userdata }) => { const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" || window.location.host === "localhost:3000"; const appUrl = isCloud ? `/apps/${data.id}` : `https://shuffler.io/apps/${data.id}`; + var canEditApp = userdata.admin === "true" || userdata.id === data?.owner || data?.owner === "" || (userdata.admin === "true" && userdata.active_org.id === data?.reference_org) || !data?.generated const paperStyle = { - backgroundColor: mouseHoverIndex === index ? "rgba(26, 26, 26, 1)" : "#1A1A1A", + backgroundColor: mouseHoverIndex === index ? "rgba(26, 26, 26, 1)" : "#212121", color: "rgba(241, 241, 241, 1)", cursor: "pointer", - position: "relative", - width: 365, + fontFamily: "Inter", + // position: "relative", + width: "100%", height: 96, borderRadius: 8, boxShadow: "0px 0px 10px 0px rgba(0, 0, 0, 0.1)", marginBottom: 20, + transition: "width 0.3s ease", }; return ( @@ -63,7 +76,7 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl, fontSize: 16, overflow: "hidden", display: "flex", - alignItems: "flex-start", + fontFamily: "Inter", width: '100%', backgroundColor: mouseHoverIndex === index ? "#2F2F2F" : "#212121" }} @@ -92,13 +105,14 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl, fontWeight: '400', overflow: "hidden", margin: "12px 0", - fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)" + fontFamily: "Inter" }}>
-
+
{data.generated !== true && data.tags && data.tags.slice(0, 2).map((tag, tagIndex) => ( {tag} @@ -134,44 +149,57 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl,
{/* Deactivate button */} {currTab === 0 && !deactivatedIndexes.includes(index) && mouseHoverIndex === index && data.generated === true && ( - + ) + } + + .then((response) => response.json()) + .then((responseJson) => { + if (responseJson.success === false) { + toast.error(responseJson.reason); + } else { + toast.success("App Deactivated Successfully. Reload UI to see updated changes."); + } + }) + .catch(error => { + console.log("app error: ", error.toString()); + }); + }} + > + Deactivate + +
)}
@@ -192,6 +220,7 @@ const Hits = ({ globalUrl, isLoading, isLoggedIn, + leftSideBarOpenByClick }) => { const [hoverEffect, setHoverEffect] = useState(-1); const [allActivatedAppIds, setAllActivatedAppIds] = useState(userdata?.active_apps); @@ -260,7 +289,6 @@ const Hits = ({ const [showNoAppFound, setShowNoAppFound] = useState(false); - //show some delay to show the "App Not Found." so it doesn't not show while changing tab. useEffect(() => { const timer = setTimeout(() => { setShowNoAppFound(true); @@ -279,287 +307,243 @@ const Hits = ({ No Apps Found ) : ( - -
-
- {hits?.map((data, index) => { - const appUrl = - isCloud - ? `/apps/${data.objectID}?queryID=${data.__queryID}` - : `https://shuffler.io/apps/${data.objectID}?queryID=${data.__queryID}`; +
+ {hits?.map((data, index) => { + const appUrl = + isCloud + ? `/apps/${data.objectID}?queryID=${data.__queryID}` + : `https://shuffler.io/apps/${data.objectID}?queryID=${data.__queryID}`; - return ( - + { + setHoverEffect(index); + }} + onMouseLeave={() => { + setHoverEffect(-1); + }} + > + { + handleAppClick(data); }} > -
{ - handleAppClick(data); - } - } + {data.name} +
- { - setHoverEffect(index); - }} - onMouseLeave={() => { - setHoverEffect(-1); + display: 'flex', + flexDirection: "row", + overflow: "hidden", + gap: 8, + fontWeight: 600, + textOverflow: "ellipsis", + whiteSpace: "nowrap", + color: '#F1F1F1' }} > - - {data.name} -
-
- {(allActivatedAppIds && allActivatedAppIds.includes(data.objectID)) && } - {normalizedString(data.name)} -
+ {(allActivatedAppIds && allActivatedAppIds.includes(data.objectID)) && } + {normalizedString(data.name)} +
-
- {data.categories !== null - ? normalizedString(data.categories).join(", ") - : "NA"} -
-
-
- {hoverEffect === index && isCloud ? ( -
- {data.tags && ( - - - {data.tags.slice(0, 1).map((tag, tagIndex) => ( - - {normalizedString(tag)} - {tagIndex < 1 ? ", " : ""} - - ))} - - - )} -
- ) : ( -
- {data.tags && - data.tags.map((tag, tagIndex) => ( - - {normalizedString(tag)} - {tagIndex < data.tags.length - 1 ? ", " : ""} - - ))} -
- )} -
-
- {hoverEffect === index && isCloud && ( -
- {allActivatedAppIds && allActivatedAppIds.includes(data.objectID) ? ( -
+
+
+ {hoverEffect === index && isCloud ? ( +
+ {data.tags && ( + { - handleActivateButton(event, data, "deactivate"); - }}> - Deactivate - - ) : ( - - )} -
- )} -
+ width: "auto", + height: "auto", + fontSize: 16, + border: "1px solid rgba(73, 73, 73, 1)", + } + } + }} + > + + {data.tags.slice(0, 1).map((tag, tagIndex) => ( + + {normalizedString(tag)} + {tagIndex < 1 ? ", " : ""} + + ))} + + + )}
-
- - + ) : ( +
+ {data.tags && + data.tags.map((tag, tagIndex) => ( + + {normalizedString(tag)} + {tagIndex < data.tags.length - 1 ? ", " : ""} + + ))} +
+ )} +
+
+ {hoverEffect === index && isCloud && ( +
+ {allActivatedAppIds && allActivatedAppIds.includes(data.objectID) ? ( + + ) : ( + + )} +
+ )} +
+
-
- ); - })} -
-
- + + + + ); + })} +
)} ) : ( -
- )} - + + ) + } + ); } -// Custom Category Dropdown Component -const CategoryDropdown = ({ items, currentRefinement, refine }) => { - const handleChange = (event) => { - const value = event.target.value; - refine(value); - }; - return ( - - ); -}; - -const CustomCategoryDropdown = connectRefinementList(CategoryDropdown); // Custom SearchBox Component const SearchBox = ({ refine, searchQuery, setSearchQuery }) => { const inputRef = useRef(null); const [localQuery, setLocalQuery] = useState(searchQuery); + // Initialize search when component mounts or when switching to Discover tab + useEffect(() => { + if (searchQuery) { + setLocalQuery(searchQuery); + refine(searchQuery); // This will trigger the Algolia search + } + }, [searchQuery, refine]); + // Debounced function to refine search const debouncedRefine = useRef( debounce((value) => { @@ -608,10 +592,11 @@ const SearchBox = ({ refine, searchQuery, setSearchQuery }) => { event.preventDefault(); } }} - style={{ width: '100%', borderRadius: '7px', fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)" }} + style={{ borderRadius: 8, height: 45, fontFamily: "Inter", flex: 1 }} InputProps={{ style: { borderRadius: 8, + height: 45 }, endAdornment: ( @@ -619,7 +604,6 @@ const SearchBox = ({ refine, searchQuery, setSearchQuery }) => { { setLocalQuery(''); @@ -637,6 +621,55 @@ const SearchBox = ({ refine, searchQuery, setSearchQuery }) => { const CustomSearchBox = connectSearchBox(SearchBox); const CustomHits = connectHits(Hits); +// Custom Category Dropdown Component +const CategoryDropdown = ({ items, currentRefinement, refine }) => { + const handleChange = (event) => { + const value = event.target.value; + refine(value); + }; + + return ( +
+ + {currentRefinement.length > 0 && ( + refine([])} + /> + )} +
+ ); +}; +const CustomCategoryDropdown = connectRefinementList(CategoryDropdown); + // Custom Label Dropdown Component const LabelDropdown = ({ items, currentRefinement, refine }) => { const handleChange = (event) => { @@ -645,42 +678,164 @@ const LabelDropdown = ({ items, currentRefinement, refine }) => { }; return ( - selected.length ? selected.join(', ') : 'All Labels'} + > + + All Labels - ))} - + {items.map(item => ( + + + {item.label} ({item.count}) + + ))} + + {currentRefinement.length > 0 && ( + refine([])} + /> + )} + + ); +}; +const CustomLabelDropdown = connectRefinementList(LabelDropdown); + + + +// New filter function +const filterApps = (apps, searchQuery, selectedCategory, selectedLabel) => { + if (!Array.isArray(apps)) return []; + + return apps.filter((app) => { + const matchesSearchQuery = ( + searchQuery === "" || // If searchQuery is empty, match all apps + app.name.toLowerCase().includes(searchQuery.toLowerCase()) || + (app.tags && app.tags.some(tag => + tag.toLowerCase().includes(searchQuery.toLowerCase()) + )) || + (app.categories && app.categories.some((category) => + category.toLowerCase().includes(searchQuery.toLowerCase()) + )) + ); + + const matchesSelectedCategories = ( + selectedCategory.length === 0 || // If no category is selected, match all apps + (app.categories && app.categories.some(category => + selectedCategory.includes(category) + )) + ); + + const matchesSelectedTags = ( + selectedLabel.length === 0 || // If no label is selected, match all apps + (app.tags && app.tags.some(tag => + selectedLabel.includes(tag) + )) + ); + + return matchesSearchQuery && matchesSelectedCategories && matchesSelectedTags; + }); +}; + +// Add this new component for the app skeleton +const AppSkeleton = () => { + return ( + +
+ +
+ + + +
+
+
); }; -const CustomLabelDropdown = connectRefinementList(LabelDropdown); +// Replace the loading sections in the main component with this +const LoadingGrid = () => { + return ( +
+ {[...Array(7)].map((_, index) => ( + + ))} +
+ ); +}; // Main Apps Component const Apps2 = (props) => { - const { globalUrl, isLoaded, serverside, userdata, isLoggedIn, checkLogin } = props; + const { globalUrl, isLoaded, serverside, userdata, isLoggedIn, checkLogin, isCloud } = props; let navigate = useNavigate(); const { leftSideBarOpenByClick } = useContext(Context); const location = useLocation(); @@ -703,6 +858,20 @@ const Apps2 = (props) => { const [selectedApp, setSelectedApp] = useState(null); const [appFramework, setAppFramework] = useState(undefined); const [defaultSearch, setDefaultSearch] = useState(""); + + const [apps, setApps] = useState([]); + const [filteredApps, setFilteredApps] = useState([]); + const [appSearchLoading, setAppSearchLoading] = useState(false); + const [creatorProfile, setCreatorProfile] = useState({}); + const [openApi, setOpenApi] = React.useState(""); + const [loadAppsModalOpen, setLoadAppsModalOpen] = useState(false); + const [downloadBranch, setDownloadBranch] = useState("master"); + const [field1, setField1] = useState(""); + const [field2, setField2] = useState(""); + const [validation, setValidation] = useState(null); + + const baseRepository = "https://github.com/frikky/shuffle-apps"; + // Set the current tab based on the query parameter useEffect(() => { const queryParams = new URLSearchParams(location.search); @@ -803,19 +972,55 @@ const Apps2 = (props) => { } }, [currTab, globalUrl, userdata?.id]); // Remove location.search dependency - useEffect(() => { - setSearchQuery(""); - }, [currTab]) + // useEffect(() => { + // // setSearchQuery(""); + // setSelectedCategory([]); + // setSelectedLabel([]); + // }, [currTab]) // Find top categories and tags based on the current tab useEffect(() => { - if (currTab === 0) { + if (currTab === 0 || currTab === 1) { setCategories(findTopCategories()); setLabels(findTopTags()); } }, [currTab, appsToShow]) + const getUserProfile = (username) => { + if (serverside === true || !isCloud) { + setCreatorProfile({}) + return; + } + + fetch(`${globalUrl}/api/v1/users/creators/${username}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for WORKFLOW EXECUTION :O!"); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success !== false) { + console.log("creator profile: ", responseJson) + setCreatorProfile(responseJson); + } else { + setCreatorProfile({}) + } + }) + .catch((error) => { + console.log(error); + setCreatorProfile({}) + }); + }; useEffect(() => { if (serverside) { @@ -823,6 +1028,430 @@ const Apps2 = (props) => { } }, [serverside]); + const getApps = () => { + // Get apps from localstorage + var storageApps = [] + try { + const appstorage = localStorage.getItem("apps") + storageApps = JSON.parse(appstorage) + if (storageApps === null || storageApps === undefined || storageApps.length === 0) { + storageApps = [] + } else { + setAppsToShow(storageApps) + setOrgApps(storageApps) + setApps(storageApps) + // setFilteredApps(storageApps) + // setAppSearchLoading(false) + } + } catch (e) { + //console.log("Failed to get apps from localstorage: ", e) + } + + fetch(globalUrl + "/api/v1/apps", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + setIsLoading(false); + if (response.status !== 200) { + console.log("Status not 200 for apps :O!"); + + //if (isCloud) { + // window.location.pathname = "/search"; + //} + } + + return response.json(); + }) + .then((responseJson) => { + //responseJson = sortByKey(responseJson, "large_image") + //responseJson = sortByKey(responseJson, "is_valid") + //setFilteredApps(responseJson.filter(app => !internalIds.includes(app.name) && !(!app.activated && app.generated))) + console.log("responseJson: from getApps ", responseJson) + var privateapps = []; + var valid = []; + var invalid = []; + for (var key in responseJson) { + const app = responseJson[key]; + if (app.is_valid && !(!app.activated && app.generated)) { + privateapps.push(app); + } else if ( + app.private_id !== undefined && + app.private_id.length > 0 + ) { + valid.push(app); + } else { + invalid.push(app); + } + } + + //console.log(privateapps) + //console.log(valid) + //console.log(invalid) + //console.log(privateapps) + //privateapps.reverse() + privateapps.push(...valid); + privateapps.push(...invalid); + console.log("privateapps: setting apps ", privateapps) + setAppsToShow(privateapps); + setOrgApps(privateapps); + setApps(privateapps); + // setCursearch(""); + + //handleSearchChange(event.target.value) + //setCursearch(event.target.value) + // setFilteredApps(privateapps); + if (privateapps.length > 0) { + if (selectedApp.id === undefined || selectedApp.id === null) { + if (privateapps[0].owner !== undefined && privateapps[0].owner !== null) { + getUserProfile(privateapps[0].owner); + } + + // setContact(privateapps[0].contact_info) + + // setSelectedApp(privateapps[0]); + // setSharingConfiguration(privateapps[0].sharing === true ? "public" : "you") + } + + // if ( + // privateapps[0].actions !== null && + // privateapps[0].actions.length > 0 + // ) { + // setSelectedAction(privateapps[0].actions[0]); + // } else { + // setSelectedAction({}); + // } + } + + if (privateapps.length > 0 && storageApps.length === 0) { + try { + localStorage.setItem("apps", JSON.stringify(privateapps)) + } catch (e) { + console.log("Failed to set apps in localstorage: ", e) + } + } + + //setTimeout(() => { + // setFirstLoad(false) + //}, 5000) + }) + .catch((error) => { + toast(error.toString()); + setIsLoading(false); + }); + }; + + // Locally hotloads app from folder + const hotloadApps = () => { + toast("Hotloading apps from location in .env"); + setIsLoading(true); + fetch(globalUrl + "/api/v1/apps/run_hotload", { + method: "POST", + mode: "cors", + headers: { + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + setIsLoading(false); + if (response.status === 200) { + //toast("Hotloaded apps!") + getApps(); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === true) { + toast("Successfully finished hotload"); + } else { + console.log("failed hotload: ", responseJson) + // toast(`Failed hotload: ${responseJson.reason}`); + //(responseJson.reason !== undefined && responseJson.reason.length > 0) { + } + }) + .catch((error) => { + toast(`Failed hotload: ${error.toString()}`); + }); + }; + + + // Load data e.g. from github + const getSpecificApps = (url, forceUpdate) => { + setValidation(true); + + setIsLoading(true); + //start() + + const parsedData = { + url: url, + branch: downloadBranch || "master", + }; + + if (field1.length > 0) { + parsedData["field_1"] = field1; + } + + if (field2.length > 0) { + parsedData["field_2"] = field2; + } + + parsedData["force_update"] = forceUpdate; + + toast("Getting specific apps from your URL."); + var cors = "cors"; + fetch(globalUrl + "/api/v1/apps/get_existing", { + method: "POST", + mode: "cors", + headers: { + Accept: "application/json", + }, + body: JSON.stringify(parsedData), + credentials: "include", + }) + .then((response) => { + if (response.status === 200) { + toast("Loaded existing apps!"); + } + + //stop() + setIsLoading(false); + setValidation(false); + return response.json(); + }) + .then((responseJson) => { + console.log("DATA: ", responseJson); + if (responseJson.reason !== undefined) { + toast("Failed loading: " + responseJson.reason); + } + }) + .catch((error) => { + console.log("ERROR: ", error.toString()); + //toast(error.toString()); + //stop() + + setIsLoading(false); + setValidation(false); + }); + }; + + const handleGithubValidation = (forceUpdate) => { + getSpecificApps(openApi, forceUpdate); + setLoadAppsModalOpen(false); + }; + + + const appsModalLoad = loadAppsModalOpen ? ( + { + setOpenApi(""); + setLoadAppsModalOpen(false); + setField1(""); + setField2(""); + }} + maxWidth="md" + fullWidth + PaperProps={{ + sx: { + borderRadius: 2, + border: "1px solid #494949", + minWidth: '440px', + fontFamily: "Inter", + backgroundColor: "#212121", + zIndex: 1000, + '& .MuiDialogContent-root': { + backgroundColor: "#212121", + }, + '& .MuiDialogTitle-root': { + backgroundColor: "#212121", + }, + '& .MuiTypography-root': { + fontFamily: 'Inter, sans-serif', + }, + '& .MuiButton-root': { + fontFamily: 'Inter, sans-serif', + }, + } + }} + > + + + Load from Github Repository + + { + setOpenApi(""); + setLoadAppsModalOpen(false); + setField1(""); + setField2(""); + }} + sx={{ + color: 'rgba(255, 255, 255, 0.7)', + '&:hover': { bgcolor: 'rgba(255, 255, 255, 0.1)' } + }} + > + + + + + + Repository (supported: github, gitlab, bitbucket) + + setOpenApi(e.target.value)} + sx={{ + mb: 3, + '& .MuiOutlinedInput-root': { + color: 'white', + height: '50px', + '& fieldset': { + borderColor: 'rgba(255, 255, 255, 0.23)', + }, + }, + }} + /> + + + Branch (default value is "master") + + setDownloadBranch(e.target.value)} + placeholder="master" + sx={{ + mb: 3, + '& .MuiOutlinedInput-root': { + color: 'white', + height: '50px', + '& fieldset': { + borderColor: 'rgba(255, 255, 255, 0.23)', + }, + }, + }} + /> + + + Authentication (optional - private repos etc) + +
+ setField1(e.target.value)} + sx={{ + '& .MuiOutlinedInput-root': { + color: 'white', + height: '50px', + '& fieldset': { + borderColor: 'rgba(255, 255, 255, 0.23)', + }, + }, + }} + /> + setField2(e.target.value)} + sx={{ + '& .MuiOutlinedInput-root': { + color: 'white', + height: '50px', + '& fieldset': { + borderColor: 'rgba(255, 255, 255, 0.23)', + }, + }, + }} + /> +
+
+ + + {!isCloud && ( + + )} + + +
+ ) : null; + const findTopCategories = () => { const categoryCountMap = {}; @@ -881,76 +1510,91 @@ const Apps2 = (props) => { const handleCreateApp = (e) => { e.preventDefault(); - setOpenModal(true); + // setOpenModal(true); }; useEffect(() => { const apps = currTab === 1 ? userApps : orgApps; - // Search app based on app name, category, and tag - const filteredUserAppdata = Array.isArray(apps) ? apps.filter((app) => { - - const matchesSearchQuery = ( - searchQuery === "" || // If searchQuery is empty, match all apps - app.name.toLowerCase().includes(searchQuery.toLowerCase()) || - (app.tags && app.tags.some(tag => - tag.toLowerCase().includes(searchQuery.toLowerCase()) - )) || - (app.categories && app.categories.some((category) => - category.toLowerCase().includes(searchQuery.toLowerCase()) - )) - ); - - const matchesSelectedCategories = ( - selectedCategory.length === 0 || // If no category is selected, match all apps - (app.categories && app.categories.some(category => - selectedCategory.includes(category) - )) - ); - - const matchesSelectedTags = ( - selectedLabel.length === 0 || // If no label is selected, match all apps - (app.tags && app.tags.some(tag => - selectedLabel.includes(tag) - )) - ); - - return matchesSearchQuery && matchesSelectedCategories && matchesSelectedTags; - }) : []; - + const filteredUserAppdata = filterApps(apps, searchQuery, selectedCategory, selectedLabel); setAppsToShow(filteredUserAppdata); - }, [searchQuery, selectedCategory, selectedLabel]); + }, [searchQuery, selectedCategory, selectedLabel, currTab]); - const handleTabChange = (newTab) => { + const handleTabChange = (event, newTab) => { setCurrTab(newTab); + + // Apply filters immediately when changing tabs if (newTab === 0) { - setAppsToShow(orgApps) - const categories = findTopCategories(); - const labels = findTopTags(); - setCategories(categories); - setLabels(labels); + const filteredOrgApps = filterApps(orgApps, searchQuery, selectedCategory, selectedLabel); + setAppsToShow(filteredOrgApps); + } else if (newTab === 1) { + const filteredUserApps = filterApps(userApps, searchQuery, selectedCategory, selectedLabel); + setAppsToShow(filteredUserApps); } - if (newTab === 1) { - setAppsToShow(userApps) - } - const newQueryParam = newTab === 0 ? 'org_apps' : newTab === 1 ? 'my_apps' : 'all_apps'; + + // Update URL query params based on tab index + const tabMapping = { + 0: 'org_apps', + 1: 'my_apps', + 2: 'all_apps' + }; + const queryParams = new URLSearchParams(location.search); - queryParams.set('tab', newQueryParam); - queryParams.delete('q'); - window.history.replaceState({}, '', `${location.pathname}?${queryParams.toString()}`); + queryParams.set('tab', tabMapping[newTab]); + + // Maintain search query in URL regardless of tab + if (searchQuery) { + queryParams.set('q', searchQuery); + } else { + queryParams.delete('q'); + } + + navigate(`${location.pathname}?${queryParams.toString()}`); }; + // Update useEffect to handle initial load and URL search params + useEffect(() => { + const queryParams = new URLSearchParams(location.search); + const searchParam = queryParams.get('q'); + if (searchParam) { + setSearchQuery(searchParam); + } + }, []); + // Update useEffect for filtering to handle both tabs + useEffect(() => { + if (currTab === 2) return; // Skip for "Discover Apps" tab as it uses Algolia + const apps = currTab === 1 ? userApps : orgApps; + const filteredApps = filterApps(apps, searchQuery, selectedCategory, selectedLabel); + setAppsToShow(filteredApps); + + // Update URL with search query + const queryParams = new URLSearchParams(location.search); + if (searchQuery) { + queryParams.set('q', searchQuery); + } else { + queryParams.delete('q'); + } + window.history.replaceState({}, '', `${location.pathname}?${queryParams.toString()}`); + }, [searchQuery, selectedCategory, selectedLabel, currTab, userApps, orgApps]); + + // Update search input handler to maintain state across tabs + const handleSearchChange = (event) => { + const newSearchQuery = event.target.value; + setSearchQuery(newSearchQuery); + }; const boxStyle = { color: "white", display: "flex", flexDirection: "column", + height: "100%", width: "100%", margin: "auto", - maxWidth: "60%", + maxWidth: "70%", + fontFamily: "Inter", // padding: '20px 380px', }; @@ -975,214 +1619,371 @@ const Apps2 = (props) => { } return ( - - -
- - Apps - -
- handleTabChange(newTab)} - TabIndicatorProps={{ style: { height: '3px', borderRadius: 10 } }} - > - - - - -
-
-
- { - (currTab === 0 || currTab === 1) && - { - setSearchQuery(event.target.value); - }} - onKeyDown={(event) => { - if (event.key === "Enter") { - event.preventDefault(); - } - }} - limit={5} - style={{ width: '100%', borderRadius: '7px', fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)" }} - InputProps={{ - style: { - borderRadius: 8, - }, - endAdornment: ( - - { - searchQuery.length === 0 ? : +
+ + + {appsModalLoad} +
+
+ + Apps + + {isCloud ? null : ( + + {userdata === undefined || userdata === null || isLoading ? null : ( + - ), - }} - /> + }} + > + + + )} + + {userdata === undefined || userdata === null || userdata.admin === "false" ? null : + + + + } + + )} +
+
+ handleTabChange(event, newTab)} + TabIndicatorProps={{ style: { height: '3px', borderRadius: 10 } }} + style={{ fontFamily: "Inter" }} + > + + + + +
+
+
+ { + (currTab === 0 || currTab === 1) && + { + if (event.key === "Enter") { + event.preventDefault(); + } + }} + limit={5} + InputProps={{ + style: { + borderRadius: 8, + height: 45 + }, + endAdornment: ( + + {searchQuery.length === 0 ? ( + + ) : ( + { + setSearchQuery(""); + }} + /> + )} + + ), + }} + /> + } + { + currTab === 2 && + + } +
+
+ {currTab === 2 ? ( + + ) : ( + <> + + {selectedCategory.length > 0 && ( + setSelectedCategory([])} + /> + )} + + )} +
+
+ {currTab === 2 ? ( + + ) : ( + <> + + {selectedLabel.length > 0 && ( + setSelectedLabel([])} + /> + )} + + )} +
+
+ +
+
+
+ + { + currTab === 0 && ( +
+ {isLoading ? ( + + ) : ( + <> + {appsToShow?.length > 0 && appsToShow !== undefined && !isLoading ? ( +
+ {appsToShow.map((data, index) => ( + + ))} +
+ ) : ( +
+ +
+ )} + + )} +
+ ) } + { + currTab === 1 && ( +
+ {isLoading ? ( + + ) : ( + <> + {appsToShow?.length > 0 && appsToShow !== undefined ? ( +
+ {appsToShow.map((data, index) => ( + + ))} +
+ ) : ( +
+ No Apps Found +
+ )} + + )} +
+ ) + } + { currTab === 2 && - + }
-
- {currTab === 2 ? ( - - ) : ( - - )} -
-
- {currTab === 2 ? ( - - ) : ( - - )} -
-
- -
-
-
- - { - currTab === 0 && ( -
- {isLoading ? ( -
- -
- ) : ( - <> - {appsToShow?.length > 0 && appsToShow !== undefined && !isLoading ? ( -
- {appsToShow.map((data, index) => ( - - ))} -
- ) : ( -
- -
- )} - - )} -
- ) - } - { - currTab === 1 && ( -
- {isLoading ? ( -
- -
- ) : ( - <> - {appsToShow?.length > 0 && appsToShow !== undefined ? ( -
- {appsToShow.map((data, index) => ( - - ))} -
- ) : ( -
- No Apps Found -
- )} - - )} -
- ) - } - - { - currTab === 2 && - - } -
-
- - +
+ + +
); }; diff --git a/frontend/src/views/Workflows2.jsx b/frontend/src/views/Workflows2.jsx index 3937345e..b0dded3c 100644 --- a/frontend/src/views/Workflows2.jsx +++ b/frontend/src/views/Workflows2.jsx @@ -724,7 +724,15 @@ const Workflows2 = (props) => { }; const handleCreateWorkflow = () => { - setDialogModalOpen(true) + setModalOpen(true) + setIsEditing(false) + setNewWorkflowName("") + setNewWorkflowDescription("") + setDefaultReturnValue("") + setEditingWorkflow({}) + setNewWorkflowTags([]) + setSelectedUsecases([]) + }; @@ -2086,9 +2094,9 @@ const Workflows2 = (props) => { continue } - if (parsedAction.name === "Shuffle Tools" || parsedAction.id === "bc78f35c6c6351b07a09b7aed5d29652") { - continue - } + // if (parsedAction.name === "Shuffle Tools" || parsedAction.id === "bc78f35c6c6351b07a09b7aed5d29652") { + // continue + // } if (appsFound.findIndex(data => data.name === parsedAction.name) < 0) { appsFound.push(parsedAction) @@ -3232,7 +3240,7 @@ const Workflows2 = (props) => { { - setDialogModalOpen(true) + setModalOpen(true) setIsEditing(false) }} > @@ -3287,11 +3295,11 @@ const Workflows2 = (props) => { {editingWorkflow.id !== undefined ? "Edit Workflow" : "Create New Workflow"} -
+
upload.click()} style={{ backgroundColor: "rgba(255,255,255,0.08)" }} > @@ -3299,7 +3307,7 @@ const Workflows2 = (props) => { setDialogModalOpen(false)} style={{ backgroundColor: "rgba(255,255,255,0.08)" }} > @@ -3532,7 +3540,7 @@ const Workflows2 = (props) => { setDefaultReturnValue(""); setEditingWorkflow({}); setNewWorkflowTags([]); - setModalOpen(false); + setDialogModalOpen(false); setSelectedUsecases([]) }} > @@ -4035,6 +4043,7 @@ const Workflows2 = (props) => {
+ { setFilters(chips); findWorkflow(chips); }} - style={{ flex: 0.9, borderRadius: 8 }} - // style={searchStyle} - //onAdd={(chip) => { - // console.log("ADd: ", chip); - // addFilter(chip); - //}} - //onDelete={(_, index) => { - // console.log("Remove: ", index); - // removeFilter(index); - //}} + style={{ flex: 0.9 }} + InputProps={{ + style: { + color: "white", + borderRadius: 8, + }, + }} + sx={{ + '& .MuiOutlinedInput-root': { + '& fieldset': { + borderColor: 'rgba(255, 255, 255, 0.2)', + }, + '&:hover fieldset': { + borderColor: 'rgba(255, 255, 255, 0.3)', + }, + '&.Mui-focused fieldset': { + borderColor: '#f85a3e', + }, + }, + '& .MuiChip-root': { + backgroundColor: 'rgba(255, 255, 255, 0.1)', + color: 'white', + '& .MuiChip-deleteIcon': { + color: 'rgba(255, 255, 255, 0.7)', + '&:hover': { + color: 'white', + }, + }, + }, + }} /> - - @@ -4632,7 +4698,7 @@ const Workflows2 = (props) => { */} - {modalView} + {/* {modalView} */} {deleteModal} {exportVerifyModal} {publishModal}