mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-07-23 04:36:52 +00:00
started app section
This commit is contained in:
22
src/components/Apps/AppInfo.tsx
Normal file
22
src/components/Apps/AppInfo.tsx
Normal 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>
|
||||
);
|
||||
};
|
108
src/components/Apps/Apps-styles.tsx
Normal file
108
src/components/Apps/Apps-styles.tsx
Normal 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'
|
||||
}));
|
62
src/components/Apps/Apps.tsx
Normal file
62
src/components/Apps/Apps.tsx
Normal 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} />}
|
||||
</>
|
||||
)
|
||||
}
|
56
src/components/Apps/AppsHome.tsx
Normal file
56
src/components/Apps/AppsHome.tsx
Normal 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>
|
||||
)
|
||||
}
|
163
src/components/Apps/AppsLibrary.tsx
Normal file
163
src/components/Apps/AppsLibrary.tsx
Normal 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>
|
||||
);
|
||||
};
|
Reference in New Issue
Block a user