added the basic ui structure for now
This commit is contained in:
@@ -14,6 +14,7 @@ import HealthPage from "./components/HealthPage.jsx";
|
||||
//import Header from "./components/Header.jsx";
|
||||
import theme from "./theme";
|
||||
import Apps from "./views/Apps";
|
||||
import Apps2 from "./views/Apps2.jsx";
|
||||
import AppCreator from "./views/AppCreator";
|
||||
import DetectionDashBoard from "./views/DetectionDashboard.jsx";
|
||||
|
||||
@@ -408,6 +409,21 @@ const App = (message, props) => {
|
||||
{...props}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/apps2"
|
||||
element={
|
||||
<Apps2
|
||||
serverside={false}
|
||||
isLoaded={isLoaded}
|
||||
userdata={userdata}
|
||||
globalUrl={globalUrl}
|
||||
surfaceColor={theme.palette.surfaceColor}
|
||||
inputColor={theme.palette.inputColor}
|
||||
{...props}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
|
||||
@@ -0,0 +1,562 @@
|
||||
import React, { useState, useEffect, useContext } 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,
|
||||
} 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, RefinementList } from "react-instantsearch-dom";
|
||||
import { removeQuery } from "../components/ScrollToTop.jsx";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl }) => {
|
||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
||||
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 (
|
||||
<Grid item xs={12} key={index}>
|
||||
<a href={appUrl} rel="noopener noreferrer" style={{ textDecoration: "none", color: "#f85a3e" }}>
|
||||
<Paper elevation={0} style={paperStyle} onMouseOver={() => setMouseHoverIndex(index)} onMouseOut={() => setMouseHoverIndex(-1)}>
|
||||
<ButtonBase style={{ borderRadius: 6, fontSize: 16, overflow: "hidden", display: "flex", alignItems: "flex-start", width: '100%', backgroundColor: mouseHoverIndex === index ? "#2F2F2F" : "#212121" }}>
|
||||
<img alt={data.name} src={data.small_image || data.large_image ? (data.small_image || data.large_image) : "/images/no_image.png"} style={{ width: 100, height: 100, borderRadius: 6, margin: 10, border: "1px solid #212122", boxShadow: "0px 0px 10px 0px rgba(0, 0, 0, 0.1)" }} />
|
||||
<div style={{ display: "flex", flexDirection: "column", alignItems: "flex-start", width: 339, gap: 8, fontWeight: '400', overflow: "hidden", margin: "12px 0 12px 0", fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)" }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", marginLeft: 8, gap: 8 }}>
|
||||
{data.name.replace(/_/g, ' ').replace(/\b\w/g, char => char.toUpperCase())}
|
||||
</div>
|
||||
<div style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", marginLeft: 8, color: "rgba(158, 158, 158, 1)" }}>
|
||||
{data.categories ? data.categories.join(", ") : "NA"}
|
||||
</div>
|
||||
<div style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", width: 230, textAlign: 'start', marginLeft: 8, color: "rgba(158, 158, 158, 1)", display: "flex" }}>
|
||||
<div style={{ minWidth: 120, overflow: "hidden" }}>
|
||||
{data.generated !== true && data.tags && data.tags.slice(0, 2).map((tag, tagIndex) => (
|
||||
<span key={tagIndex}>
|
||||
{tag}
|
||||
{tagIndex < data.tags.length - 1 ? ", " : ""}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
{/* Deactivate button logic */}
|
||||
{data.generated === true ? (
|
||||
<Button style={{ marginLeft: 15, width: 102, height: 35, borderRadius: 200, backgroundColor: "rgba(73, 73, 73, 1)", color: "rgba(241, 241, 241, 1)", textTransform: "none" }}>
|
||||
Deactivate
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</ButtonBase>
|
||||
</Paper>
|
||||
</a>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
const Apps2 = (props) => {
|
||||
const { globalUrl, isLoaded, serverside, userdata } = 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(true);
|
||||
const [userApps, setUserApps] = useState([]);
|
||||
const [orgApps, setOrgApps] = useState([]);
|
||||
const [selectedCategoryForUsersAndOgsApps, setselectedCategoryForUsersAndOgsApps] = useState([]);
|
||||
const [selectedTagsForUserAndOrgApps, setSelectedTagsForUserAndOrgApps] = useState([]);
|
||||
const [appsToShow, setAppsToShow] = useState([]);
|
||||
const [deactivatedIndexes, setDeactivatedIndexes] = React.useState([]);
|
||||
|
||||
|
||||
const [mouseHoverIndex, setMouseHoverIndex] = useState(-1);
|
||||
var counted = 0;
|
||||
const [showNoAppFound, setShowNoAppFound] = useState(false);
|
||||
|
||||
const isCloud =
|
||||
window.location.host === "localhost:3002" ||
|
||||
window.location.host === "shuffler.io";
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setShowNoAppFound(true);
|
||||
}, 1000);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
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(() => {
|
||||
if (currTab === 1) {
|
||||
const baseUrl = globalUrl;
|
||||
const userAppsUrl = `${baseUrl}/api/v1/users/me/apps`;
|
||||
fetch(userAppsUrl, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
setUserApps(data);
|
||||
setIsLoading(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error fetching user apps:", err);
|
||||
});
|
||||
} else if (currTab === 0) {
|
||||
const baseUrl = globalUrl;
|
||||
const appsUrl = `${baseUrl}/api/v1/apps`;
|
||||
fetch(appsUrl, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
console.log("data of orgApps", data)
|
||||
setOrgApps(data);
|
||||
setIsLoading(false)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error fetching apps:", err);
|
||||
});
|
||||
}
|
||||
}, [currTab]);
|
||||
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (serverside) {
|
||||
return null;
|
||||
}
|
||||
}, [serverside]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
const findTopCategories = () => {
|
||||
const categoryCountMap = {};
|
||||
|
||||
// Check if userAndOrgsApp is an array before iterating over it and Find top 10 Category from the apps
|
||||
if (Array.isArray(appsToShow)) {
|
||||
appsToShow.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 = {};
|
||||
|
||||
if (Array.isArray(appsToShow)) {
|
||||
appsToShow.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 categories = findTopCategories();
|
||||
if (categories) {
|
||||
setCategories(categories)
|
||||
}
|
||||
console.log(categories)
|
||||
const tags = findTopTags();
|
||||
if (tags) {
|
||||
setLabels(tags);
|
||||
}
|
||||
console.log(tags)
|
||||
}, [currTab])
|
||||
|
||||
const handleCreateApp = () => {
|
||||
navigate('/create-app');
|
||||
};
|
||||
|
||||
|
||||
//Search app base on app name, category and tag
|
||||
const filteredUserAppdata = Array.isArray(appsToShow) ? appsToShow.filter((app) => {
|
||||
const matchesSearchQuery = (
|
||||
searchQuery === "" ||
|
||||
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 = (
|
||||
selectedCategoryForUsersAndOgsApps.length === 0 ||
|
||||
(app.categories && app.categories.some(category =>
|
||||
selectedCategoryForUsersAndOgsApps.includes(category)
|
||||
))
|
||||
);
|
||||
|
||||
const matchesSelectedTags = (
|
||||
selectedTagsForUserAndOrgApps.length === 0 ||
|
||||
(app.tags && selectedTagsForUserAndOrgApps.some(tag =>
|
||||
app.tags.includes(tag)
|
||||
))
|
||||
);
|
||||
|
||||
|
||||
return matchesSearchQuery && matchesSelectedCategories && matchesSelectedTags;
|
||||
}) : [];
|
||||
|
||||
|
||||
const handleTabChange = (newTab) => {
|
||||
setCurrTab(newTab);
|
||||
if (currTab === 0) {
|
||||
setAppsToShow(orgApps)
|
||||
}
|
||||
if (currTab === 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("Current Tab", currTab)
|
||||
console.log("Apps to show", appsToShow)
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
const boxStyle = {
|
||||
color: "white",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
margin: "auto",
|
||||
maxWidth: "60%",
|
||||
// padding: '20px 380px',
|
||||
};
|
||||
|
||||
const CustomClearRefinements = connectStateResults(({ searchResults, ...rest }) => {
|
||||
const hasFilters = searchResults && searchResults.nbHits !== searchResults.nbSortedHits;
|
||||
return <ClearRefinements translations={{ reset: <span style={{ textDecoration: 'underline' }}>Clear All</span> }} {...rest} disabled={!hasFilters} />;
|
||||
});
|
||||
|
||||
|
||||
const SearchBox = ({ refine, searchQuery, setSearchQuery }) => {
|
||||
var defaultSearch = "";
|
||||
|
||||
|
||||
//useEffect(() => {
|
||||
if (
|
||||
window !== undefined &&
|
||||
window.location !== undefined &&
|
||||
window.location.search !== undefined &&
|
||||
window.location.search !== null
|
||||
) {
|
||||
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||
const params = Object.fromEntries(urlSearchParams.entries());
|
||||
const foundQuery = params["q"];
|
||||
if (foundQuery !== null && foundQuery !== undefined) {
|
||||
console.log("Got query: ", foundQuery);
|
||||
refine(foundQuery);
|
||||
defaultSearch = foundQuery;
|
||||
searchQuery = foundQuery
|
||||
}
|
||||
}
|
||||
//}, [])
|
||||
|
||||
|
||||
const handleSearch = () => {
|
||||
refine(searchQuery.trim());
|
||||
};
|
||||
|
||||
return (
|
||||
<form noValidate action="" role="search">
|
||||
<TextField
|
||||
value={searchQuery}
|
||||
fullWidth
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
marginTop: 20,
|
||||
marginLeft: 10,
|
||||
marginRight: 12,
|
||||
width: 693,
|
||||
borderRadius: 8,
|
||||
fontSize: 16,
|
||||
}}
|
||||
InputProps={{
|
||||
style: {
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
height: 50,
|
||||
width: 693,
|
||||
borderRadius: 8,
|
||||
},
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<Search style={{ marginLeft: 5 }} />
|
||||
</InputAdornment>
|
||||
),
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
{searchQuery.length > 0 && (
|
||||
<ClearIcon
|
||||
style={{
|
||||
color: "white",
|
||||
cursor: "pointer",
|
||||
marginRight: 10
|
||||
}}
|
||||
onClick={() => {
|
||||
setSearchQuery('')
|
||||
removeQuery("q");
|
||||
refine('')
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSearch}
|
||||
style={{
|
||||
backgroundImage:
|
||||
"linear-gradient(to right, rgb(248, 106, 62), rgb(243, 64, 121))",
|
||||
color: "white",
|
||||
border: "none",
|
||||
padding: "10px 20px",
|
||||
width: 100,
|
||||
height: 35,
|
||||
borderRadius: 17.5,
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
Search
|
||||
</button>
|
||||
</InputAdornment>
|
||||
),
|
||||
|
||||
}}
|
||||
autoComplete="off"
|
||||
color="primary"
|
||||
placeholder="Search more than 2500 Apps"
|
||||
id="shuffle_search_field"
|
||||
onChange={(event) => {
|
||||
setSearchQuery(event.currentTarget.value);
|
||||
removeQuery("q");
|
||||
refine(event.currentTarget.value);
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
}
|
||||
}}
|
||||
limit={5}
|
||||
/>
|
||||
{/*isSearchStalled ? 'My search is stalled' : ''*/}
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
const CustomSearchBox = connectSearchBox(SearchBox);
|
||||
// const CustomHits = connectHits(Hits);
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div style={boxStyle}>
|
||||
<Typography variant="h4" style={{ marginBottom: 20, paddingLeft: 15, textTransform: 'none' }}>
|
||||
Apps
|
||||
</Typography>
|
||||
<div style={{ borderBottom: '1px solid gray', marginBottom: 30, marginRight: 10 }}>
|
||||
<Tabs
|
||||
value={currTab}
|
||||
onChange={(event, newTab) => handleTabChange(newTab)}
|
||||
TabIndicatorProps={{ style: { height: '3px', borderRadius: 10 } }}
|
||||
>
|
||||
<Tab label="Organization Apps" style={{ textTransform: 'none', marginRight: 20 }} />
|
||||
<Tab label="My Apps" style={{ textTransform: 'none', marginRight: 20 }} />
|
||||
<Tab label="Discover Apps" style={{ textTransform: 'none' }} />
|
||||
</Tabs>
|
||||
</div>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: 20 }}>
|
||||
<div style={{ flex: 1, marginRight: 10 }}>
|
||||
<TextField
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
placeholder="Search for apps"
|
||||
value={searchQuery}
|
||||
id="shuffle_search_field"
|
||||
onChange={(event) => {
|
||||
setSearchQuery(event.currentTarget.value);
|
||||
removeQuery("q");
|
||||
// refine(event.currentTarget.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: (
|
||||
<InputAdornment position="end">
|
||||
{
|
||||
searchQuery.length === 0 ? <Search /> : <ClearIcon />
|
||||
}
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
{/* <CustomSearchBox searchQuery={searchQuery} setSearchQuery={setSearchQuery} /> */}
|
||||
</div>
|
||||
<div style={{ flex: 1, marginRight: 10 }}>
|
||||
<Select
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={selectedCategory}
|
||||
onChange={(e) => setSelectedCategory(e.target.value)}
|
||||
displayEmpty
|
||||
style={{ width: '100%', borderRadius: '7px', fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)" }}
|
||||
>
|
||||
<MenuItem value="" >
|
||||
All Categories
|
||||
</MenuItem>
|
||||
{categories?.map((category) => (
|
||||
<MenuItem value={category.category}>
|
||||
{category.category}
|
||||
</MenuItem>
|
||||
))}
|
||||
|
||||
</Select>
|
||||
</div>
|
||||
<div style={{ flex: 1, marginRight: 10 }}>
|
||||
<Select
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={selectedLabel}
|
||||
onChange={(e) => setSelectedLabel(e.target.value)}
|
||||
displayEmpty
|
||||
style={{ width: '100%', borderRadius: '7px', fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)" }}
|
||||
>
|
||||
<MenuItem value="">
|
||||
All Labels
|
||||
</MenuItem>
|
||||
{labels?.map((tag) => (
|
||||
<MenuItem value={tag.tag}>
|
||||
{tag.tag}
|
||||
</MenuItem>
|
||||
))}
|
||||
{/* Add label options here */}
|
||||
</Select>
|
||||
</div>
|
||||
<div style={{ flex: 1, marginRight: 10 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={handleCreateApp}
|
||||
style={{ height: "100%", width: '100%', borderRadius: '7px', textTransform: 'none', fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)" }}
|
||||
>
|
||||
<Add style={{ marginRight: 10 }} />
|
||||
Create an App
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ rowGap: 16, columnGap: 16, marginTop: 16, display: "flex", flexWrap: "wrap", justifyContent: "flex-start", overflowY: "auto", overflowX: "hidden", maxHeight: 570, scrollbarWidth: "thin", scrollbarColor: "#494949 #2f2f2f", maxHeight: 570 }}>
|
||||
{orgApps.map((data, index) => (
|
||||
<AppCard key={index} data={data} index={index} mouseHoverIndex={mouseHoverIndex} setMouseHoverIndex={setMouseHoverIndex} globalUrl={globalUrl} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<AppGrid
|
||||
maxRows={4}
|
||||
showSuggestion={true}
|
||||
globalUrl={globalUrl}
|
||||
isMobile={isMobile}
|
||||
userdata={userdata}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Apps2;
|
||||
Reference in New Issue
Block a user