diff --git a/public/manifest.json b/public/manifest.json
index 5d88b27..b37d461 100644
--- a/public/manifest.json
+++ b/public/manifest.json
@@ -46,6 +46,6 @@
],
"content_security_policy": {
- "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'; connect-src 'self' https://api.qortal.org https://api2.qortal.org https://appnode.qortal.org https://apinode.qortalnodes.live https://apinode1.qortalnodes.live https://apinode2.qortalnodes.live https://apinode3.qortalnodes.live https://apinode4.qortalnodes.live https://ext-node.qortal.link wss://appnode.qortal.org wss://ext-node.qortal.link ws://127.0.0.1:12391 http://127.0.0.1:12391 https://ext-node.qortal.link; "
+ "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'; connect-src 'self' https://*:* http://*:* wss://*:* ws://*:*;"
}
}
diff --git a/src/App.tsx b/src/App.tsx
index 00cb3a0..53d88e8 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -141,7 +141,7 @@ const defaultValues: MyContextInterface = {
message: "",
},
};
-export let isMobile = false;
+export let isMobile = true;
const isMobileDevice = () => {
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
diff --git a/src/assets/svgs/ClearInput.svg b/src/assets/svgs/ClearInput.svg
new file mode 100644
index 0000000..a4595df
--- /dev/null
+++ b/src/assets/svgs/ClearInput.svg
@@ -0,0 +1,6 @@
+
diff --git a/src/assets/svgs/LogoSelected.svg b/src/assets/svgs/LogoSelected.svg
new file mode 100644
index 0000000..fec7e1e
--- /dev/null
+++ b/src/assets/svgs/LogoSelected.svg
@@ -0,0 +1,9 @@
+
diff --git a/src/assets/svgs/Search.svg b/src/assets/svgs/Search.svg
new file mode 100644
index 0000000..b6cb06b
--- /dev/null
+++ b/src/assets/svgs/Search.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/components/Apps/AppInfo.tsx b/src/components/Apps/AppInfo.tsx
new file mode 100644
index 0000000..0e3be5a
--- /dev/null
+++ b/src/components/Apps/AppInfo.tsx
@@ -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 (
+
+
+
+ );
+};
diff --git a/src/components/Apps/Apps-styles.tsx b/src/components/Apps/Apps-styles.tsx
new file mode 100644
index 0000000..1549596
--- /dev/null
+++ b/src/components/Apps/Apps-styles.tsx
@@ -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'
+ }));
\ No newline at end of file
diff --git a/src/components/Apps/Apps.tsx b/src/components/Apps/Apps.tsx
new file mode 100644
index 0000000..9ffe412
--- /dev/null
+++ b/src/components/Apps/Apps.tsx
@@ -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 (
+ <>
+
+ {mode === 'home' && }
+ {mode === 'library' && }
+ >
+ )
+}
diff --git a/src/components/Apps/AppsHome.tsx b/src/components/Apps/AppsHome.tsx
new file mode 100644
index 0000000..775d7e0
--- /dev/null
+++ b/src/components/Apps/AppsHome.tsx
@@ -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 (
+
+
+ {
+ setMode('library')
+ }}>
+
+
+ +
+
+ Add
+
+
+ {downloadedQapps?.map((qapp)=> {
+ return (
+
+
+
+
+
+
+
+ {qapp?.metadata?.title || qapp?.name}
+
+
+ )
+ })}
+
+
+ )
+}
diff --git a/src/components/Apps/AppsLibrary.tsx b/src/components/Apps/AppsLibrary.tsx
new file mode 100644
index 0000000..d801bc0
--- /dev/null
+++ b/src/components/Apps/AppsLibrary.tsx
@@ -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 (
+
+
+
+
+
+
+ setSearchValue(e.target.value)}
+ sx={{ ml: 1, flex: 1 }}
+ placeholder="Search for apps"
+ inputProps={{ 'aria-label': 'Search for apps', fontSize: '16px', fontWeight: 400 }}
+ />
+
+
+ {searchValue && (
+ {
+ setSearchValue('')
+ }}>
+
+
+ )}
+
+
+
+
+
+ {searchedList?.length > 0 ? (
+ <>
+ {searchedList.map((app)=> {
+
+ return (
+
+ )
+ })}
+ >
+ ) : (
+ <>
+ Official Apps
+
+
+ {officialApps?.map((qapp) => {
+ return (
+
+
+
+
+
+
+
+
+ {qapp?.metadata?.title || qapp?.name}
+
+
+
+ );
+ })}
+
+
+ Featured
+
+
+ Categories
+ >
+ )}
+
+
+
+ );
+};
diff --git a/src/components/Group/Group.tsx b/src/components/Group/Group.tsx
index 45d9c57..3e09a3a 100644
--- a/src/components/Group/Group.tsx
+++ b/src/components/Group/Group.tsx
@@ -88,6 +88,7 @@ import { ExitIcon } from "../../assets/Icons/ExitIcon";
import { HomeDesktop } from "./HomeDesktop";
import { DesktopFooter } from "../Desktop/DesktopFooter";
import { DesktopHeader } from "../Desktop/DesktopHeader";
+import { Apps } from "../Apps/Apps";
// let touchStartY = 0;
// let disablePullToRefresh = false;
@@ -2733,6 +2734,9 @@ export const Group = ({
setMobileViewMode={setMobileViewMode}
/>
)}
+ {isMobile && mobileViewMode === "apps" && (
+
+ )}
{
!isMobile && !selectedGroup &&
groupSection === "home" && (
@@ -2958,7 +2962,7 @@ export const Group = ({
/>
- {isMobile && mobileViewMode === "home" && !mobileViewModeKeepOpen && (
+ {(isMobile && mobileViewMode === "home" || isMobile && mobileViewMode === "apps") && !mobileViewModeKeepOpen && (
<>
+
{
+ if(mobileViewMode === 'home'){
+ setMobileViewMode('apps')
+
+ } else {
+ setMobileViewMode('home')
+
+ }
+ }}>
{/* Custom Center Icon */}
-
+
+