import React, { useState, useEffect, useContext, useCallback, memo, useMemo, useRef } from "react"; import theme from "../theme.jsx"; import { isMobile } from "react-device-detect"; import AppGrid from "../components/AppGrid.jsx"; import { useLocation, useNavigate } from "react-router-dom"; import { TextField, Button, Typography, MenuItem, Select, Tabs, Tab, Zoom, Grid, Paper, ButtonBase, Tooltip, Box, CircularProgress, Checkbox, } from "@mui/material"; import { Context } from "../context/ContextApi.jsx"; import Add from '@mui/icons-material/Add'; import InputAdornment from '@mui/material/InputAdornment'; import Search from '@mui/icons-material/Search'; import ClearIcon from '@mui/icons-material/Clear'; import { ClearRefinements, connectHits, connectSearchBox, connectStateResults, InstantSearch, RefinementList, connectRefinementList, Configure } from "react-instantsearch-dom"; import { removeQuery } from "../components/ScrollToTop.jsx"; import { toast } from "react-toastify"; import algoliasearch from "algoliasearch/lite"; import { debounce } from "lodash"; import AppSelection from "../components/AppSelection.jsx"; const searchClient = algoliasearch( "JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240" ); // AppCard Component const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl, deactivatedIndexes, currTab }) => { 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}`; const paperStyle = { backgroundColor: mouseHoverIndex === index ? "rgba(26, 26, 26, 1)" : "#1A1A1A", color: "rgba(241, 241, 241, 1)", cursor: "pointer", position: "relative", width: 365, height: 96, borderRadius: 8, boxShadow: "0px 0px 10px 0px rgba(0, 0, 0, 0.1)", marginBottom: 20, }; return ( setMouseHoverIndex(index)} onMouseOut={() => setMouseHoverIndex(-1)} > { // if (!event.target.closest('.deactivate-button')) { // window.location.href = appUrl; // } console.log("clicked"); }} > {data.name}
{data.name.replace(/_/g, ' ').replace(/\b\w/g, char => char.toUpperCase())}
{data.categories ? data.categories.join(", ") : "NA"}
{data.generated !== true && data.tags && data.tags.slice(0, 2).map((tag, tagIndex) => ( {tag} {tagIndex < data.tags.length - 1 ? ", " : ""} ))}
{/* Deactivate button */} {currTab === 0 && !deactivatedIndexes.includes(index) && mouseHoverIndex === index && data.generated === true && ( )}
); }; // Component to fetch all public app from the algolia. const Hits = ({ userdata, hits, setIsAnyAppActivated, searchQuery, globalUrl, isLoading, isLoggedIn, }) => { const [hoverEffect, setHoverEffect] = useState(-1); const [allActivatedAppIds, setAllActivatedAppIds] = useState(userdata?.active_apps); const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; const [deactivatedIndexes, setDeactivatedIndexes] = React.useState([]); const normalizedString = (name) => { if (typeof name === 'string') { return name.replace(/_/g, ' '); } else { return name; } }; //Function for activation and deactivation of app const handleActivateButton = (event, data, type) => { //use prevent default so it will stop redirection to the app page event.preventDefault(); if (!isLoggedIn) { toast.error("Please log in to your account to activate the app.") return; } if (type === "activate") { toast.success(`The ${normalizedString(data.name)} app is activating. Please wait...`); } if (type === "deactivate") { toast.success(`The ${normalizedString(data.name)} app is deactivating. Please wait...`); } const baseURL = globalUrl; const url = `${baseURL}/api/v1/apps/${data.objectID}/${type}`; fetch(url, { method: 'GET', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, credentials: "include", }) .then((response) => response.json()) .then((responseJson) => { if (responseJson.success === false) { toast.error(responseJson.reason); } else { //toast.success(`App ${type}d Successfully!`); if (type === 'activate') { setAllActivatedAppIds(prev => [...prev, data.objectID]); setIsAnyAppActivated(true); } if (type === 'deactivate') { const updatedIds = allActivatedAppIds.filter(id => id !== data.objectID); setAllActivatedAppIds(updatedIds); } } }) .catch(error => { console.log("app error: ", error.toString()); }); } let workflowDelay = 0; const isHeader = true; 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); }, 1000); return () => clearTimeout(timer); }, []); return (
{!isLoading ? (
{hits?.length === 0 && searchQuery.length >= 0 && showNoAppFound ? (
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}`; return ( { setHoverEffect(index); }} onMouseLeave={() => { setHoverEffect(-1); }} > {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) ? ( ) : ( )}
)}
); })}
)}
) : (
)}
); } // 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); // Debounced function to refine search const debouncedRefine = useRef( debounce((value) => { setSearchQuery(value); removeQuery("q"); refine(value); }, 300) // Adjust the delay as needed ).current; useEffect(() => { const urlSearchParams = new URLSearchParams(window.location.search); const params = Object.fromEntries(urlSearchParams.entries()); const foundQuery = params["q"]; if (foundQuery) { setLocalQuery(foundQuery); debouncedRefine(foundQuery); } }, [debouncedRefine]); useEffect(() => { if (searchQuery === "") { return; } if (inputRef.current) { inputRef.current.focus(); } }, [searchQuery]); const handleChange = (event) => { const value = event.target.value; setLocalQuery(value); debouncedRefine(value); }; return ( { if (event.key === "Enter") { event.preventDefault(); } }} style={{ width: '100%', borderRadius: '7px', fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)" }} InputProps={{ style: { borderRadius: 8, }, endAdornment: ( {localQuery?.length === 0 ? : ( { setLocalQuery(''); debouncedRefine(''); }} /> )} ), }} /> ); }; const CustomSearchBox = connectSearchBox(SearchBox); const CustomHits = connectHits(Hits); // Custom Label Dropdown Component const LabelDropdown = ({ items, currentRefinement, refine }) => { const handleChange = (event) => { const value = event.target.value; refine(value); }; return ( ); }; const CustomLabelDropdown = connectRefinementList(LabelDropdown); // Main Apps Component const Apps2 = (props) => { const { globalUrl, isLoaded, serverside, userdata, isLoggedIn, checkLogin } = props; let navigate = useNavigate(); const { leftSideBarOpenByClick } = useContext(Context); const location = useLocation(); const [searchQuery, setSearchQuery] = useState(""); const [selectedCategory, setSelectedCategory] = useState([]); const [selectedLabel, setSelectedLabel] = useState([]); const [currTab, setCurrTab] = useState(0); const [categories, setCategories] = useState([]); const [labels, setLabels] = useState([]); const [isLoading, setIsLoading] = useState(false); const [userApps, setUserApps] = useState([]); const [orgApps, setOrgApps] = useState([]); const [appsToShow, setAppsToShow] = useState([]); const [deactivatedIndexes, setDeactivatedIndexes] = React.useState([]); const [IsAnyAppActivated, setIsAnyAppActivated] = useState(false); const [mouseHoverIndex, setMouseHoverIndex] = useState(-1); var counted = 0; const [showNoAppFound, setShowNoAppFound] = useState(false); const [appFramework, setAppFramework] = useState(undefined); const [defaultSearch, setDefaultSearch] = useState(""); // Set the current tab based on the query parameter useEffect(() => { const queryParams = new URLSearchParams(location.search); const tabParam = queryParams.get('tab'); if (tabParam !== null && tabParam !== undefined) { if (tabParam === 'org_apps') { setCurrTab(0); } else if (tabParam === 'my_apps') { setCurrTab(1); } else { setCurrTab(2); } } }, [location.search]); useEffect(() => { const getFramework = () => { fetch(globalUrl + "/api/v1/apps/frameworkConfiguration", { method: "GET", headers: { "Content-Type": "application/json", Accept: "application/json", }, credentials: "include", }) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for framework!"); } return response.json(); }) .then((responseJson) => { if (responseJson.success === false) { setAppFramework({}) if (responseJson.reason !== undefined) { //toast("Failed loading: " + responseJson.reason) } else { //toast("Failed to load framework for your org.") } } else { setAppFramework(responseJson) } }) .catch((error) => { console.log("err in framework: ", error.toString()); }) } getFramework(); }, []); // Fetch apps based on the current tab : 0 -> org_apps, 1 -> my_apps, 2 -> all_apps useEffect(() => { const fetchApps = async () => { const baseUrl = globalUrl; let url; const userId = userdata?.id; if (currTab === 1 && userId) { url = `${baseUrl}/api/v1/users/${userId}/apps`; } else if (currTab === 0) { url = `${baseUrl}/api/v1/apps`; } setIsLoading(true); try { const response = await fetch(url, { method: "GET", credentials: "include", headers: { "Content-Type": "application/json", }, }); const data = await response.json(); if (currTab === 1) { setAppsToShow(data); setUserApps(data); } else if (currTab === 0) { setAppsToShow(data); setOrgApps(data); // For testing the empty state // setAppsToShow([]); // setOrgApps([]); } setIsLoading(false); } catch (err) { console.error("Error fetching apps:", err); setIsLoading(false); } }; // Only fetch if we have required data if (globalUrl && (currTab === 0 || (currTab === 1 && userdata?.id))) { fetchApps(); } }, [currTab, globalUrl, userdata?.id]); // Remove location.search dependency useEffect(() => { setSearchQuery(""); }, [currTab]) // Find top categories and tags based on the current tab useEffect(() => { if (currTab === 0) { setCategories(findTopCategories()); setLabels(findTopTags()); } }, [currTab, appsToShow]) useEffect(() => { if (serverside) { return null; } }, [serverside]); const findTopCategories = () => { const categoryCountMap = {}; const apps = currTab === 1 ? userApps : orgApps; // Check if userAndOrgsApp is an array before iterating over it and Find top 10 Category from the apps if (Array.isArray(apps)) { apps.forEach((app) => { const categories = app.categories; if (categories && categories.length > 0) { categories.forEach((category) => { categoryCountMap[category] = (categoryCountMap[category] || 0) + 1; }); } }); const categoryArray = Object.keys(categoryCountMap).map((category) => ({ category, count: categoryCountMap[category], })); categoryArray.sort((a, b) => b.count - a.count); const topCategories = categoryArray.slice(0, 7); return topCategories; } }; const findTopTags = () => { const tagCountMap = {}; const apps = currTab === 1 ? userApps : orgApps; if (Array.isArray(apps)) { apps.forEach((app) => { const tags = app.tags; if (tags && tags.length > 0) { tags.forEach((tag) => { tagCountMap[tag] = (tagCountMap[tag] || 0) + 1; }); } }); } const tagArray = Object.keys(tagCountMap).map((tag) => ({ tag, count: tagCountMap[tag], })); tagArray.sort((a, b) => b.count - a.count); const topTags = tagArray.slice(0, 8); return topTags; }; const handleCreateApp = (e) => { e.preventDefault(); console.log("Create app clicked") }; 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; }) : []; setAppsToShow(filteredUserAppdata); }, [searchQuery, selectedCategory, selectedLabel]); const handleTabChange = (newTab) => { setCurrTab(newTab); if (newTab === 0) { setAppsToShow(orgApps) const categories = findTopCategories(); const labels = findTopTags(); setCategories(categories); setLabels(labels); } if (newTab === 1) { setAppsToShow(userApps) } const newQueryParam = newTab === 0 ? 'org_apps' : newTab === 1 ? 'my_apps' : 'all_apps'; const queryParams = new URLSearchParams(location.search); queryParams.set('tab', newQueryParam); queryParams.delete('q'); window.history.replaceState({}, '', `${location.pathname}?${queryParams.toString()}`); console.log("Apps to show", appsToShow) }; const boxStyle = { color: "white", display: "flex", flexDirection: "column", width: "100%", margin: "auto", maxWidth: "60%", // padding: '20px 380px', }; const handleCategoryChange = (event) => { const value = event.target.value; setSelectedCategory(value); }; const handleLabelChange = (event) => { const value = event.target.value; setSelectedLabel(value); }; 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 ? : } ), }} /> } { 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 && }
); }; export default Apps2;