started app section

This commit is contained in:
2024-10-17 06:09:20 +03:00
parent e9860ae7be
commit bd170d8481
13 changed files with 451 additions and 4 deletions

View File

@@ -0,0 +1,22 @@
import React, { useEffect, useMemo, useState } from "react";
import {
AppsLibraryContainer,
} from "./Apps-styles";
import { Avatar, Box, ButtonBase, InputBase } from "@mui/material";
import { Add } from "@mui/icons-material";
import { getBaseApiReact } from "../../App";
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
import { Spacer } from "../../common/Spacer";
export const AppInfo = ({ app }) => {
return (
<AppsLibraryContainer>
</AppsLibraryContainer>
);
};

View File

@@ -0,0 +1,108 @@
import {
AppBar,
Button,
Toolbar,
Typography,
Box,
TextField,
InputLabel,
} from "@mui/material";
import { styled } from "@mui/system";
export const AppsParent = styled(Box)(({ theme }) => ({
display: "flex",
width: "100%",
flexDirection: "column",
height: "100%",
alignItems: "center",
overflow: 'auto',
// For WebKit-based browsers (Chrome, Safari, etc.)
"::-webkit-scrollbar": {
width: "0px", // Set the width to 0 to hide the scrollbar
height: "0px", // Set the height to 0 for horizontal scrollbar
},
// For Firefox
scrollbarWidth: "none", // Hides the scrollbar in Firefox
// Optional for better cross-browser consistency
"-ms-overflow-style": "none" // Hides scrollbar in IE and Edge
}));
export const AppsContainer = styled(Box)(({ theme }) => ({
display: "flex",
width: "90%",
justifyContent: 'space-evenly',
gap: '24px',
flexWrap: 'wrap',
alignItems: 'flex-start',
}));
export const AppsLibraryContainer = styled(Box)(({ theme }) => ({
display: "flex",
width: "90%",
flexDirection: 'column',
justifyContent: 'flex-start',
alignItems: 'flex-start',
}));
export const AppsSearchContainer = styled(Box)(({ theme }) => ({
display: "flex",
width: "90%",
justifyContent: 'space-between',
alignItems: 'center',
backgroundColor: '#434343',
borderRadius: '8px',
padding: '0px 10px',
height: '36px'
}));
export const AppsSearchLeft = styled(Box)(({ theme }) => ({
display: "flex",
width: "90%",
justifyContent: 'flex-start',
alignItems: 'center',
gap: '10px'
}));
export const AppsSearchRight = styled(Box)(({ theme }) => ({
display: "flex",
width: "90%",
justifyContent: 'flex-end',
alignItems: 'center',
}));
export const AppCircleContainer = styled(Box)(({ theme }) => ({
display: "flex",
flexDirection: "column",
gap: '5px',
alignItems: 'center',
width: '100%'
}));
export const Add = styled(Typography)(({ theme }) => ({
fontSize: '36px',
fontWeight: 500,
lineHeight: '43.57px',
textAlign: 'left'
}));
export const AppCircleLabel = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 500,
lineHeight: 1.2,
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
width: '100%'
}));
export const AppLibrarySubTitle = styled(Typography)(({ theme }) => ({
fontSize: '16px',
fontWeight: 500,
lineHeight: 1.2,
}));
export const AppCircle = styled(Box)(({ theme }) => ({
display: "flex",
width: "60px",
flexDirection: "column",
height: "60px",
alignItems: 'center',
justifyContent: 'center',
borderRadius: '50%',
backgroundColor: "var(--apps-circle)",
border: '1px solid #FFFFFF'
}));

View File

@@ -0,0 +1,62 @@
import React, { useEffect, useState } from 'react'
import { AppsHome } from './AppsHome'
import { Spacer } from '../../common/Spacer'
import { getBaseApiReact } from '../../App'
import { AppsLibrary } from './AppsLibrary'
export const Apps = () => {
const [mode, setMode] = useState('home')
const [availableQapps, setAvailableQapps] = useState([])
const [downloadedQapps, setDownloadedQapps] = useState([])
const getQapps = React.useCallback(
async () => {
try {
let apps = []
let websites = []
// dispatch(setIsLoadingGlobal(true))
const url = `${getBaseApiReact()}/arbitrary/resources/search?service=APP&mode=ALL&includestatus=true&limit=0&includemetadata=true`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if(!response?.ok) return
const responseData = await response.json();
const urlWebsites = `${getBaseApiReact()}/arbitrary/resources/search?service=WEBSITE&mode=ALL&includestatus=true&limit=0&includemetadata=true`;
const responseWebsites = await fetch(urlWebsites, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if(!responseWebsites?.ok) return
const responseDataWebsites = await responseWebsites.json();
apps = responseData
websites = responseDataWebsites
const combine = [...apps, ...websites]
setAvailableQapps(combine)
setDownloadedQapps(combine.filter((qapp)=> qapp?.status?.status === 'READY'))
} catch (error) {
} finally {
// dispatch(setIsLoadingGlobal(false))
}
},
[]
);
useEffect(()=> {
getQapps()
}, [getQapps])
return (
<>
<Spacer height="30px" />
{mode === 'home' && <AppsHome downloadedQapps={downloadedQapps} setMode={setMode} />}
{mode === 'library' && <AppsLibrary downloadedQapps={downloadedQapps} availableQapps={availableQapps} />}
</>
)
}

View File

@@ -0,0 +1,56 @@
import React, { useMemo, useState } from 'react'
import { AppCircle, AppCircleContainer, AppCircleLabel, AppsContainer, AppsParent } from './Apps-styles'
import { Avatar, ButtonBase } from '@mui/material'
import { Add } from '@mui/icons-material'
import { getBaseApiReact } from '../../App'
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
export const AppsHome = ({downloadedQapps, setMode}) => {
return (
<AppsParent>
<AppsContainer>
<ButtonBase onClick={()=> {
setMode('library')
}}>
<AppCircleContainer>
<AppCircle>
<Add>+</Add>
</AppCircle>
<AppCircleLabel>Add</AppCircleLabel>
</AppCircleContainer>
</ButtonBase>
{downloadedQapps?.map((qapp)=> {
return (
<ButtonBase sx={{
height: '80px',
width: '60px'
}}>
<AppCircleContainer>
<AppCircle sx={{
border: 'none'
}}>
<Avatar
sx={{
height: "31px",
width: "31px",
}}
alt={qapp?.name}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${qapp?.name}/qortal_avatar?async=true`}
>
<img style={{
width: '31px',
height: 'auto'
}} src={LogoSelected} alt="center-icon" />
</Avatar>
</AppCircle>
<AppCircleLabel>{qapp?.metadata?.title || qapp?.name}</AppCircleLabel>
</AppCircleContainer>
</ButtonBase>
)
})}
</AppsContainer>
</AppsParent>
)
}

View File

@@ -0,0 +1,163 @@
import React, { useEffect, useMemo, useState } from "react";
import {
AppCircle,
AppCircleContainer,
AppCircleLabel,
AppLibrarySubTitle,
AppsContainer,
AppsLibraryContainer,
AppsParent,
AppsSearchContainer,
AppsSearchLeft,
AppsSearchRight,
} from "./Apps-styles";
import { Avatar, Box, ButtonBase, InputBase } from "@mui/material";
import { Add } from "@mui/icons-material";
import { getBaseApiReact } from "../../App";
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
import IconSearch from "../../assets/svgs/Search.svg";
import IconClearInput from "../../assets/svgs/ClearInput.svg";
import { Spacer } from "../../common/Spacer";
const officialAppList = [
"q-tube",
"q-blog",
"q-share",
"q-support",
"q-mail",
"qombo",
"q-fund",
"q-shop",
];
export const AppsLibrary = ({ downloadedQapps, availableQapps }) => {
const [searchValue, setSearchValue] = useState('')
const officialApps = useMemo(() => {
return availableQapps.filter((app) => app.service === 'APP' &&
officialAppList.includes(app?.name?.toLowerCase())
);
}, [availableQapps]);
const [debouncedValue, setDebouncedValue] = useState(''); // Debounced value
// Debounce logic
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(searchValue); // Update debounced value after delay
}, 500); // 500ms debounce time (adjustable)
// Cleanup timeout if searchValue changes before the timeout completes
return () => {
clearTimeout(handler);
};
}, [searchValue]); // Runs effect when searchValue changes
// Example: Perform search or other actions based on debouncedValue
const searchedList = useMemo(()=> {
if(!debouncedValue) return []
return availableQapps.filter((app)=> app.name.toLowerCase().includes(debouncedValue.toLowerCase()))
}, [debouncedValue])
console.log('officialApps', searchedList)
return (
<AppsParent>
<AppsLibraryContainer>
<Box sx={{
display: 'flex',
width: '100%',
justifyContent: 'center'
}}>
<AppsSearchContainer>
<AppsSearchLeft>
<img src={IconSearch} />
<InputBase
value={searchValue}
onChange={(e)=> setSearchValue(e.target.value)}
sx={{ ml: 1, flex: 1 }}
placeholder="Search for apps"
inputProps={{ 'aria-label': 'Search for apps', fontSize: '16px', fontWeight: 400 }}
/>
</AppsSearchLeft>
<AppsSearchRight>
{searchValue && (
<ButtonBase onClick={()=> {
setSearchValue('')
}}>
<img src={IconClearInput} />
</ButtonBase>
)}
</AppsSearchRight>
</AppsSearchContainer>
</Box>
<Spacer height="25px" />
{searchedList?.length > 0 ? (
<>
{searchedList.map((app)=> {
return (
<AppInfo app={app} />
)
})}
</>
) : (
<>
<AppLibrarySubTitle>Official Apps</AppLibrarySubTitle>
<Spacer height="18px" />
<AppsContainer>
{officialApps?.map((qapp) => {
return (
<ButtonBase
sx={{
height: "80px",
width: "60px",
}}
>
<AppCircleContainer>
<AppCircle
sx={{
border: "none",
}}
>
<Avatar
sx={{
height: "31px",
width: "31px",
}}
alt={qapp?.name}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
qapp?.name
}/qortal_avatar?async=true`}
>
<img
style={{
width: "31px",
height: "auto",
}}
src={LogoSelected}
alt="center-icon"
/>
</Avatar>
</AppCircle>
<AppCircleLabel>
{qapp?.metadata?.title || qapp?.name}
</AppCircleLabel>
</AppCircleContainer>
</ButtonBase>
);
})}
</AppsContainer>
<Spacer height="18px" />
<AppLibrarySubTitle>Featured</AppLibrarySubTitle>
<Spacer height="18px" />
<AppLibrarySubTitle>Categories</AppLibrarySubTitle>
</>
)}
</AppsLibraryContainer>
</AppsParent>
);
};