From 675707cb4992bc6ea2e2446ae7f59eab4a6107ea Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Mon, 2 Dec 2024 12:08:21 +0530 Subject: [PATCH] Added the UI for download from github and added two CTAs --- frontend/src/components/AppModal.jsx | 4 +- frontend/src/views/Apps2.jsx | 624 +++++++++++++++++++++++++-- 2 files changed, 596 insertions(+), 32 deletions(-) diff --git a/frontend/src/components/AppModal.jsx b/frontend/src/components/AppModal.jsx index 17cf2918..17189887 100644 --- a/frontend/src/components/AppModal.jsx +++ b/frontend/src/components/AppModal.jsx @@ -323,7 +323,7 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { fontFamily: "Inter" }} > - + About {app?.name.replace(/_/g, ' ').replace(/\b\w/g, char => char.toUpperCase())} { display: "flex", flexDirection: "row", }}> - + {newAppname} { diff --git a/frontend/src/views/Apps2.jsx b/frontend/src/views/Apps2.jsx index 3b8fd0e8..59037947 100644 --- a/frontend/src/views/Apps2.jsx +++ b/frontend/src/views/Apps2.jsx @@ -13,13 +13,21 @@ import { 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"; @@ -147,7 +155,7 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl, }}> { canEditApp && ( - ) @@ -158,7 +166,7 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl, // marginLeft: 15, width: 102, height: 35, - borderRadius: 6, + borderRadius: 3, backgroundColor: "rgba(73, 73, 73, 1)", color: "rgba(241, 241, 241, 1)", textTransform: "none", @@ -391,7 +399,7 @@ const Hits = ({ flexDirection: "row", overflow: "hidden", gap: 8, - fontWeight:600, + fontWeight: 600, textOverflow: "ellipsis", whiteSpace: "nowrap", color: '#F1F1F1' @@ -765,14 +773,14 @@ const AppSkeleton = () => { width: "100%", height: "100%" }}> -
{ flex: 1, gap: 6 }}> - - -
@@ -827,7 +835,7 @@ const LoadingGrid = () => { // 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(); @@ -850,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); @@ -965,6 +987,40 @@ const Apps2 = (props) => { } }, [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) { @@ -972,6 +1028,426 @@ 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 { + 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) + 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 { + toast("Failed hotload: ", responseJson.reason); + //(responseJson.reason !== undefined && responseJson.reason.length > 0) { + } + }) + .catch((error) => { + toast(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 = {}; @@ -1043,7 +1519,7 @@ const Apps2 = (props) => { const handleTabChange = (event, newTab) => { setCurrTab(newTab); - + // Apply filters immediately when changing tabs if (newTab === 0) { const filteredOrgApps = filterApps(orgApps, searchQuery, selectedCategory, selectedLabel); @@ -1059,17 +1535,17 @@ const Apps2 = (props) => { 1: 'my_apps', 2: 'all_apps' }; - + const queryParams = new URLSearchParams(location.search); 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()}`); }; @@ -1148,10 +1624,98 @@ const Apps2 = (props) => { userdata={userdata} globalUrl={globalUrl} /> + {appsModalLoad}
- - Apps - +
+ + Apps + + {isCloud ? null : ( + + {userdata === undefined || userdata === null || isLoading ? null : ( + + + + )} + + {userdata === undefined || userdata === null || userdata.admin === "false" ? null : + + + + } + + )} +