diff --git a/src/App.tsx b/src/App.tsx index 9bb5342..1c6913a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -118,9 +118,7 @@ import { import { useAppFullScreen } from './useAppFullscreen'; import { NotAuthenticated } from './ExtStates/NotAuthenticated'; import { handleGetFileFromIndexedDB } from './utils/indexedDB'; -import { CoreSyncStatus } from './components/CoreSyncStatus'; import { Wallets } from './Wallets'; -import { RandomSentenceGenerator } from './utils/seedPhrase/RandomSentenceGenerator'; import { useFetchResources } from './common/useFetchResources'; import { Tutorials } from './components/Tutorials/Tutorials'; import { useHandleTutorials } from './components/Tutorials/useHandleTutorials'; @@ -182,28 +180,6 @@ const defaultValues: MyContextInterface = { message: '', }, }; -export let isMobile = false; - -const isMobileDevice = () => { - const userAgent = navigator.userAgent || navigator.vendor || window.opera; - - if (/android/i.test(userAgent)) { - return true; // Android device - } - - if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) { - return true; // iOS device - } - - return false; -}; - -if (isMobileDevice()) { - isMobile = true; - console.log('Running on a mobile device'); -} else { - console.log('Running on a desktop'); -} export const allQueues = { requestQueueCommentCount: requestQueueCommentCount, @@ -436,16 +412,20 @@ function App() { const [isOpenMinting, setIsOpenMinting] = useState(false); const { toggleFullScreen } = useAppFullScreen(setFullScreen); const generatorRef = useRef(null); + const exportSeedphrase = () => { const seedPhrase = generatorRef.current.parsedString; saveSeedPhraseToDisk(seedPhrase); }; + const passwordRef = useRef(null); + useEffect(() => { if (extState === 'wallet-dropped' && passwordRef.current) { passwordRef.current.focus(); } }, [extState]); + useEffect(() => { const isDevModeFromStorage = localStorage.getItem('isEnabledDevMode'); if (isDevModeFromStorage) { @@ -491,31 +471,38 @@ function App() { ); }; }, [toggleFullScreen]); + //resets for recoil const resetAtomSortablePinnedAppsAtom = useResetRecoilState( sortablePinnedAppsAtom ); + const resetAtomIsUsingImportExportSettingsAtom = useResetRecoilState( isUsingImportExportSettingsAtom ); const resetAtomCanSaveSettingToQdnAtom = useResetRecoilState( canSaveSettingToQdnAtom ); + const resetAtomSettingsQDNLastUpdatedAtom = useResetRecoilState( settingsQDNLastUpdatedAtom ); + const resetAtomSettingsLocalLastUpdatedAtom = useResetRecoilState( settingsLocalLastUpdatedAtom ); + const resetAtomOldPinnedAppsAtom = useResetRecoilState(oldPinnedAppsAtom); const resetAtomQMailLastEnteredTimestampAtom = useResetRecoilState( qMailLastEnteredTimestampAtom ); + const resetAtomMailsAtom = useResetRecoilState(mailsAtom); const resetGroupPropertiesAtom = useResetRecoilState(groupsPropertiesAtom); const resetLastPaymentSeenTimestampAtom = useResetRecoilState( lastPaymentSeenTimestampAtom ); + const resetAllRecoil = () => { resetAtomSortablePinnedAppsAtom(); resetAtomCanSaveSettingToQdnAtom(); @@ -528,34 +515,11 @@ function App() { resetGroupPropertiesAtom(); resetLastPaymentSeenTimestampAtom(); }; - useEffect(() => { - if (!isMobile) return; - // Function to set the height of the app to the viewport height - const resetHeight = () => { - const height = window.visualViewport - ? window.visualViewport.height - : window.innerHeight; - // Set the height to the root element (usually #root) - document.getElementById('root').style.height = height + 'px'; - setRootHeight(height + 'px'); - }; - // Set the initial height - resetHeight(); - - // Add event listeners for resize and visualViewport changes - window.addEventListener('resize', resetHeight); - window.visualViewport?.addEventListener('resize', resetHeight); - - // Clean up the event listeners when the component unmounts - return () => { - window.removeEventListener('resize', resetHeight); - window.visualViewport?.removeEventListener('resize', resetHeight); - }; - }, []); const handleSetGlobalApikey = (key) => { globalApiKey = key; }; + useEffect(() => { try { setIsLoading(true); @@ -1232,14 +1196,6 @@ function App() { // Handler for when the window gains focus const handleFocus = () => { setIsFocused(true); - if (isMobile) { - window.sendMessage('clearAllNotifications', {}).catch((error) => { - console.error( - 'Failed to clear notifications:', - error.message || 'An error occurred' - ); - }); - } }; // Handler for when the window loses focus @@ -1255,14 +1211,6 @@ function App() { const handleVisibilityChange = () => { if (document.visibilityState === 'visible') { setIsFocused(true); - if (isMobile) { - window.sendMessage('clearAllNotifications', {}).catch((error) => { - console.error( - 'Failed to clear notifications:', - error.message || 'An error occurred' - ); - }); - } } else { setIsFocused(false); } @@ -1315,7 +1263,7 @@ function App() { return ( - {isMobile && ( - - { - setIsOpenDrawerProfile(false); - }} - sx={{ - cursor: 'pointer', - }} - /> - - )} {desktopViewMode !== 'apps' && desktopViewMode !== 'dev' && desktopViewMode !== 'chat' && <>{renderProfileLeft()}} @@ -1609,51 +1539,46 @@ function App() { > - {!isMobile && ( - <> - - - LOG OUT - - } - placement="left" - arrow - sx={{ fontSize: '24' }} - slotProps={{ - tooltip: { - sx: { - color: theme.palette.text.primary, - backgroundColor: theme.palette.background.default, - }, - }, - arrow: { - sx: { - color: theme.palette.text.primary, - }, - }, + - { - logoutFunc(); - setIsOpenDrawerProfile(false); - }} - /> - - - )} + LOG OUT + + } + placement="left" + arrow + sx={{ fontSize: '24' }} + slotProps={{ + tooltip: { + sx: { + color: theme.palette.text.primary, + backgroundColor: theme.palette.background.default, + }, + }, + arrow: { + sx: { + color: theme.palette.text.primary, + }, + }, + }} + > + { + logoutFunc(); + setIsOpenDrawerProfile(false); + }} + /> + @@ -2009,7 +1934,7 @@ function App() { return ( - {!isMobile && renderProfile()} + renderProfile() )} {isOpenSendQort && isMainWindow && ( + + = ({ color, opacity, ...children }) => { + const theme = useTheme(); + + const setColor = color ? color : theme.palette.text.primary; + const setOpacity = opacity ? opacity : 1; + + return ( + + + + ); +}; diff --git a/src/assets/react.svg b/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/background.ts b/src/background.ts index dad784a..3811a42 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1,5 +1,4 @@ // @ts-nocheck - import './qortalRequests'; import { isArray } from 'lodash'; import { @@ -30,7 +29,6 @@ import { RequestQueueWithPromise } from './utils/queue/queue'; import { validateAddress } from './utils/validateAddress'; import { Sha256 } from 'asmcrypto.js'; import { TradeBotRespondMultipleRequest } from './transactions/TradeBotRespondMultipleRequest'; - import { RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS } from './constants/resourceTypes'; import { addDataPublishesCase, @@ -111,6 +109,7 @@ export let groupSecretkeys = {}; export function cleanUrl(url) { return url?.replace(/^(https?:\/\/)?(www\.)?/, ''); } + export function getProtocol(url) { if (url?.startsWith('https://')) { return 'https'; @@ -130,7 +129,6 @@ export const groupApiLocal = 'http://127.0.0.1:12391'; export const groupApiSocketLocal = 'ws://127.0.0.1:12391'; const timeDifferenceForNotificationChatsBackground = 86400000; const requestQueueAnnouncements = new RequestQueueWithPromise(1); -let isMobile = true; function handleNotificationClick(notificationId) { // Decode the notificationId if it was encoded @@ -193,26 +191,6 @@ function handleNotificationClick(notificationId) { } } -const isMobileDevice = () => { - const userAgent = navigator.userAgent || navigator.vendor || window.opera; - - if (/android/i.test(userAgent)) { - return true; // Android device - } - - if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) { - return true; // iOS device - } - - return false; -}; - -if (isMobileDevice()) { - isMobile = true; - console.log('Running on a mobile device'); -} else { - console.log('Running on a desktop'); -} const allQueues = { requestQueueAnnouncements: requestQueueAnnouncements, }; @@ -264,7 +242,9 @@ export const getForeignKey = async (foreignBlockchain) => { }; export const pauseAllQueues = () => controlAllQueues('pause'); + export const resumeAllQueues = () => controlAllQueues('resume'); + export const checkDifference = ( createdTimestamp, diff = timeDifferenceForNotificationChatsBackground @@ -302,6 +282,7 @@ export const getBaseApi = async (customApi?: string) => { return groupApi; } }; + export const isUsingLocal = async () => { const apiKey = await getApiKeyFromStorage(); // Retrieve apiKey asynchronously if (apiKey?.url) { @@ -345,13 +326,13 @@ const proxyAccountAddress = 'QXPejUe5Za1KD3zCMViWCX35AreMQ9H7ku'; const proxyAccountPublicKey = '5hP6stDWybojoDw5t8z9D51nV945oMPX7qBd29rhX1G7'; const pendingResponses = new Map(); let groups = null; - let socket; let timeoutId; let groupSocketTimeout; let socketTimeout: any; let interval; let intervalThreads; + // Function to check each API endpoint export async function findUsableApi() { for (const endpoint of apiEndpoints) { @@ -435,6 +416,7 @@ async function checkWebviewFocus() { window.addEventListener('message', handleMessage); }); } + const worker = new ChatComputePowWorker(); export async function performPowTask(chatBytes, difficulty) { @@ -584,6 +566,7 @@ const handleNotificationDirect = async (directs) => { setChatHeadsDirect(dataDirects); } }; + async function getThreadActivity(): Promise { const wallet = await getSaveWallet(); const address = wallet.address0; @@ -852,6 +835,7 @@ export async function getNameInfoForOthers(address) { return ''; } } + export async function getAddressInfo(address) { const validApi = await getBaseApi(); const response = await fetch(validApi + '/addresses/' + address); @@ -932,6 +916,7 @@ async function getTradeInfo(qortalAtAddress) { const data = await response.json(); return data; } + async function getTradesInfo(qortalAtAddresses) { // Use Promise.all to fetch data for all addresses concurrently const trades = await Promise.all( @@ -951,6 +936,7 @@ export async function getBalanceInfo() { const data = await response.json(); return data; } + export async function getLTCBalance() { const wallet = await getSaveWallet(); let _url = `${buyTradeNodeBaseUrl}/crosschain/ltc/walletbalance`; @@ -1075,6 +1061,7 @@ const transaction = async ( data: res, }; }; + const makeTransactionRequest = async ( receiver, lastRef, @@ -1113,6 +1100,7 @@ export const getLastRef = async () => { const data = await response.text(); return data; }; + export const sendQortFee = async (): Promise => { const validApi = await getBaseApi(); const response = await fetch( @@ -1386,6 +1374,7 @@ export async function signChatFunc( } return response; } + function sbrk(size, heap) { let brk = 512 * 1024; // stack top let old = brk; diff --git a/src/components/Apps/AppInfo.tsx b/src/components/Apps/AppInfo.tsx index b1254d5..3ac6282 100644 --- a/src/components/Apps/AppInfo.tsx +++ b/src/components/Apps/AppInfo.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useMemo, useState } from 'react'; import { AppCircle, AppCircleContainer, - AppCircleLabel, AppDownloadButton, AppDownloadButtonText, AppInfoAppName, @@ -17,14 +16,11 @@ import { AppsCategoryInfoValue, AppsInfoDescription, AppsLibraryContainer, - AppsParent, AppsWidthLimiter, } from './Apps-styles'; -import { Avatar, Box, ButtonBase, InputBase } from '@mui/material'; -import { Add } from '@mui/icons-material'; -import { getBaseApiReact, isMobile } from '../../App'; +import { Avatar, Box } from '@mui/material'; +import { getBaseApiReact } from '../../App'; import LogoSelected from '../../assets/svgs/LogoSelected.svg'; - import { Spacer } from '../../common/Spacer'; import { executeEvent } from '../../utils/events'; import { AppRating } from './AppRating'; @@ -51,9 +47,8 @@ export const AppInfo = ({ app, myName }) => { return ( { width: '90%', }} > - {!isMobile && } + + { }} > - {!isMobile ? ( - <> - {isSelectedAppPinned - ? 'Unpin from dashboard' - : 'Pin to dashboard'} - - ) : ( - <>{isSelectedAppPinned ? 'Unpin' : 'Pin'} - )} + {isSelectedAppPinned + ? 'Unpin from dashboard' + : 'Pin to dashboard'} - {!isMobile && ( - { - setSortablePinnedApps((prev) => { - let updatedApps; + { + setSortablePinnedApps((prev) => { + let updatedApps; - if (isSelectedAppPinned) { - // Remove the selected app if it is pinned - updatedApps = prev.filter( - (item) => - !( - item?.name === app?.name && - item?.service === app?.service - ) - ); - } else { - // Add the selected app if it is not pinned - updatedApps = [ - ...prev, - { - name: app?.name, - service: app?.service, - }, - ]; - } - - saveToLocalStorage( - 'ext_saved_settings', - 'sortablePinnedApps', - updatedApps + if (isSelectedAppPinned) { + // Remove the selected app if it is pinned + updatedApps = prev.filter( + (item) => + !( + item?.name === app?.name && item?.service === app?.service + ) ); - return updatedApps; - }); - setSettingsLocalLastUpdated(Date.now()); - }} - sx={{ - backgroundColor: '#359ff7ff', - opacity: isSelectedAppPinned ? 0.6 : 1, - }} - > - - {' '} - {isSelectedAppPinned ? 'Unpin' : 'Pin'} - - - )} + } else { + // Add the selected app if it is not pinned + updatedApps = [ + ...prev, + { + name: app?.name, + service: app?.service, + }, + ]; + } + + saveToLocalStorage( + 'ext_saved_settings', + 'sortablePinnedApps', + updatedApps + ); + return updatedApps; + }); + setSettingsLocalLastUpdated(Date.now()); + }} + sx={{ + backgroundColor: '#359ff7ff', + opacity: isSelectedAppPinned ? 0.6 : 1, + }} + > + + {' '} + {isSelectedAppPinned ? 'Unpin' : 'Pin'} + + { diff --git a/src/components/Apps/AppPublish.tsx b/src/components/Apps/AppPublish.tsx index 76bde28..62cc098 100644 --- a/src/components/Apps/AppPublish.tsx +++ b/src/components/Apps/AppPublish.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useMemo, useState } from "react"; +import React, { useContext, useEffect, useState } from 'react'; import { AppCircle, AppCircleContainer, @@ -19,90 +19,74 @@ import { PublishQAppCTAButton, PublishQAppChoseFile, PublishQAppInfo, -} from "./Apps-styles"; -import { - Avatar, - Box, - ButtonBase, - InputBase, - InputLabel, - MenuItem, - Select, -} from "@mui/material"; -import { - Select as BaseSelect, - SelectProps, - selectClasses, - SelectRootSlotProps, -} from "@mui/base/Select"; -import { Option as BaseOption, optionClasses } from "@mui/base/Option"; -import { styled } from "@mui/system"; -import UnfoldMoreRoundedIcon from "@mui/icons-material/UnfoldMoreRounded"; -import { Add } from "@mui/icons-material"; -import { MyContext, getBaseApiReact, isMobile } from "../../App"; -import LogoSelected from "../../assets/svgs/LogoSelected.svg"; - -import { Spacer } from "../../common/Spacer"; -import { executeEvent } from "../../utils/events"; -import { useDropzone } from "react-dropzone"; -import { LoadingSnackbar } from "../Snackbar/LoadingSnackbar"; -import { CustomizedSnackbars } from "../Snackbar/Snackbar"; -import { getFee } from "../../background"; -import { fileToBase64 } from "../../utils/fileReading"; +} from './Apps-styles'; +import { InputBase, InputLabel, MenuItem, Select } from '@mui/material'; +import { styled } from '@mui/system'; +import UnfoldMoreRoundedIcon from '@mui/icons-material/UnfoldMoreRounded'; +import { Add } from '@mui/icons-material'; +import { MyContext, getBaseApiReact } from '../../App'; +import LogoSelected from '../../assets/svgs/LogoSelected.svg'; +import { Spacer } from '../../common/Spacer'; +import { executeEvent } from '../../utils/events'; +import { useDropzone } from 'react-dropzone'; +import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar'; +import { CustomizedSnackbars } from '../Snackbar/Snackbar'; +import { getFee } from '../../background'; +import { fileToBase64 } from '../../utils/fileReading'; const CustomSelect = styled(Select)({ - border: "0.5px solid var(--50-white, #FFFFFF80)", - padding: "0px 15px", - borderRadius: "5px", - height: "36px", - width: "100%", - maxWidth: "450px", - "& .MuiSelect-select": { - padding: "0px", + border: '0.5px solid var(--50-white, #FFFFFF80)', + padding: '0px 15px', + borderRadius: '5px', + height: '36px', + width: '100%', + maxWidth: '450px', + '& .MuiSelect-select': { + padding: '0px', }, - "&:hover": { - borderColor: "none", // Border color on hover + '&:hover': { + borderColor: 'none', // Border color on hover }, - "&.Mui-focused .MuiOutlinedInput-notchedOutline": { - borderColor: "none", // Border color when focused + '&.Mui-focused .MuiOutlinedInput-notchedOutline': { + borderColor: 'none', // Border color when focused }, - "&.Mui-disabled": { + '&.Mui-disabled': { opacity: 0.5, // Lower opacity when disabled }, - "& .MuiSvgIcon-root": { - color: "var(--50-white, #FFFFFF80)", + '& .MuiSvgIcon-root': { + color: 'var(--50-white, #FFFFFF80)', }, }); const CustomMenuItem = styled(MenuItem)({ - backgroundColor: "#1f1f1f", // Background for dropdown items - color: "#ccc", - "&:hover": { - backgroundColor: "#333", // Darker background on hover + backgroundColor: '#1f1f1f', // Background for dropdown items + color: '#ccc', + '&:hover': { + backgroundColor: '#333', // Darker background on hover }, }); export const AppPublish = ({ names, categories }) => { - const [name, setName] = useState(""); - const [title, setTitle] = useState(""); - const [description, setDescription] = useState(""); - const [category, setCategory] = useState(""); - const [appType, setAppType] = useState("APP"); + const [name, setName] = useState(''); + const [title, setTitle] = useState(''); + const [description, setDescription] = useState(''); + const [category, setCategory] = useState(''); + const [appType, setAppType] = useState('APP'); const [file, setFile] = useState(null); const { show } = useContext(MyContext); - const [tag1, setTag1] = useState(""); - const [tag2, setTag2] = useState(""); - const [tag3, setTag3] = useState(""); - const [tag4, setTag4] = useState(""); - const [tag5, setTag5] = useState(""); + const [tag1, setTag1] = useState(''); + const [tag2, setTag2] = useState(''); + const [tag3, setTag3] = useState(''); + const [tag4, setTag4] = useState(''); + const [tag5, setTag5] = useState(''); const [openSnack, setOpenSnack] = useState(false); const [infoSnack, setInfoSnack] = useState(null); - const [isLoading, setIsLoading] = useState(""); - const maxFileSize = appType === "APP" ? 50 * 1024 * 1024 : 400 * 1024 * 1024; // 50MB or 400MB + const [isLoading, setIsLoading] = useState(''); + const maxFileSize = appType === 'APP' ? 50 * 1024 * 1024 : 400 * 1024 * 1024; // 50MB or 400MB const { getRootProps, getInputProps } = useDropzone({ accept: { - "application/zip": [".zip"], // Only accept zip files + 'application/zip': ['.zip'], // Only accept zip files }, maxSize: maxFileSize, // Set the max size based on appType multiple: false, // Disable multiple file uploads @@ -114,7 +98,7 @@ export const AppPublish = ({ names, categories }) => { onDropRejected: (fileRejections) => { fileRejections.forEach(({ file, errors }) => { errors.forEach((error) => { - if (error.code === "file-too-large") { + if (error.code === 'file-too-large') { console.error( `File ${file.name} is too large. Max size allowed is ${ maxFileSize / (1024 * 1024) @@ -128,13 +112,13 @@ export const AppPublish = ({ names, categories }) => { const getQapp = React.useCallback(async (name, appType) => { try { - setIsLoading("Loading app information"); + setIsLoading('Loading app information'); const url = `${getBaseApiReact()}/arbitrary/resources/search?service=${appType}&mode=ALL&name=${name}&includemetadata=true`; const response = await fetch(url, { - method: "GET", + method: 'GET', headers: { - "Content-Type": "application/json", + 'Content-Type': 'application/json', }, }); if (!response?.ok) return; @@ -142,18 +126,18 @@ export const AppPublish = ({ names, categories }) => { if (responseData?.length > 0) { const myApp = responseData[0]; - setTitle(myApp?.metadata?.title || ""); - setDescription(myApp?.metadata?.description || ""); - setCategory(myApp?.metadata?.category || ""); - setTag1(myApp?.metadata?.tags[0] || ""); - setTag2(myApp?.metadata?.tags[1] || ""); - setTag3(myApp?.metadata?.tags[2] || ""); - setTag4(myApp?.metadata?.tags[3] || ""); - setTag5(myApp?.metadata?.tags[4] || ""); + setTitle(myApp?.metadata?.title || ''); + setDescription(myApp?.metadata?.description || ''); + setCategory(myApp?.metadata?.category || ''); + setTag1(myApp?.metadata?.tags[0] || ''); + setTag2(myApp?.metadata?.tags[1] || ''); + setTag3(myApp?.metadata?.tags[2] || ''); + setTag4(myApp?.metadata?.tags[3] || ''); + setTag5(myApp?.metadata?.tags[4] || ''); } } catch (error) { } finally { - setIsLoading(""); + setIsLoading(''); } }, []); @@ -173,12 +157,12 @@ export const AppPublish = ({ names, categories }) => { file, }; const requiredFields = [ - "name", - "title", - "description", - "category", - "appType", - "file", + 'name', + 'title', + 'description', + 'category', + 'appType', + 'file', ]; const missingFields: string[] = []; @@ -188,32 +172,33 @@ export const AppPublish = ({ names, categories }) => { } }); if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(", "); + const missingFieldsString = missingFields.join(', '); const errorMsg = `Missing fields: ${missingFieldsString}`; throw new Error(errorMsg); } - const fee = await getFee("ARBITRARY"); + const fee = await getFee('ARBITRARY'); await show({ - message: "Would you like to publish this app?", - publishFee: fee.fee + " QORT", + message: 'Would you like to publish this app?', + publishFee: fee.fee + ' QORT', }); - setIsLoading("Publishing... Please wait."); + setIsLoading('Publishing... Please wait.'); const fileBase64 = await fileToBase64(file); await new Promise((res, rej) => { - window.sendMessage("publishOnQDN", { - data: fileBase64, - service: appType, - title, - description, - category, - tag1, - tag2, - tag3, - tag4, - tag5, - uploadType: "zip", - }) + window + .sendMessage('publishOnQDN', { + data: fileBase64, + service: appType, + title, + description, + category, + tag1, + tag2, + tag3, + tag4, + tag5, + uploadType: 'zip', + }) .then((response) => { if (!response?.error) { res(response); @@ -222,14 +207,13 @@ export const AppPublish = ({ names, categories }) => { rej(response.error); }) .catch((error) => { - rej(error.message || "An error occurred"); + rej(error.message || 'An error occurred'); }); - }); setInfoSnack({ - type: "success", + type: 'success', message: - "Successfully published. Please wait a couple minutes for the network to propogate the changes.", + 'Successfully published. Please wait a couple minutes for the network to propogate the changes.', }); setOpenSnack(true); const dataObj = { @@ -242,35 +226,49 @@ export const AppPublish = ({ names, categories }) => { }, created: Date.now(), }; - executeEvent("addTab", { + executeEvent('addTab', { data: dataObj, }); } catch (error) { setInfoSnack({ - type: "error", - message: error?.message || "Unable to publish app", + type: 'error', + message: error?.message || 'Unable to publish app', }); setOpenSnack(true); } finally { - setIsLoading(""); + setIsLoading(''); } }; + return ( - - + + Create Apps! + + Note: Currently, only one App and Website is allowed per Name. + - Name/App + + + Name/App + + { Select Name/App - {" "} + {' '} {/* This is the placeholder item */} {names.map((name) => { return {name}; })} + - App service type + + + App service type + + { Select App Type - {" "} + {' '} {/* This is the placeholder item */} - App - Website + App + Website + - Title + + + Title + + setTitle(e.target.value)} sx={{ - border: "0.5px solid var(--50-white, #FFFFFF80)", - padding: "0px 15px", - borderRadius: "5px", - height: "36px", - width: "100%", - maxWidth: "450px", + border: '0.5px solid var(--50-white, #FFFFFF80)', + padding: '0px 15px', + borderRadius: '5px', + height: '36px', + width: '100%', + maxWidth: '450px', }} placeholder="Title" inputProps={{ - "aria-label": "Title", - fontSize: "14px", - fontWeight: 400, - }} - /> - - Description - setDescription(e.target.value)} - sx={{ - border: "0.5px solid var(--50-white, #FFFFFF80)", - padding: "0px 15px", - borderRadius: "5px", - height: "36px", - width: "100%", - maxWidth: "450px", - }} - placeholder="Description" - inputProps={{ - "aria-label": "Description", - fontSize: "14px", + 'aria-label': 'Title', + fontSize: '14px', fontWeight: 400, }} /> - Category + + + Description + + + setDescription(e.target.value)} + sx={{ + border: '0.5px solid var(--50-white, #FFFFFF80)', + padding: '0px 15px', + borderRadius: '5px', + height: '36px', + width: '100%', + maxWidth: '450px', + }} + placeholder="Description" + inputProps={{ + 'aria-label': 'Description', + fontSize: '14px', + fontWeight: 400, + }} + /> + + + + + Category + + { Select Category - {" "} + {' '} {/* This is the placeholder item */} {categories?.map((category) => { @@ -379,23 +404,30 @@ export const AppPublish = ({ names, categories }) => { ); })} + - Tags + + + Tags + + setTag1(e.target.value)} sx={{ - border: "0.5px solid var(--50-white, #FFFFFF80)", - padding: "0px 15px", - borderRadius: "5px", - height: "36px", - width: "100px", + border: '0.5px solid var(--50-white, #FFFFFF80)', + padding: '0px 15px', + borderRadius: '5px', + height: '36px', + width: '100px', }} placeholder="Tag 1" inputProps={{ - "aria-label": "Tag 1", - fontSize: "14px", + 'aria-label': 'Tag 1', + fontSize: '14px', fontWeight: 400, }} /> @@ -403,16 +435,16 @@ export const AppPublish = ({ names, categories }) => { value={tag2} onChange={(e) => setTag2(e.target.value)} sx={{ - border: "0.5px solid var(--50-white, #FFFFFF80)", - padding: "0px 15px", - borderRadius: "5px", - height: "36px", - width: "100px", + border: '0.5px solid var(--50-white, #FFFFFF80)', + padding: '0px 15px', + borderRadius: '5px', + height: '36px', + width: '100px', }} placeholder="Tag 2" inputProps={{ - "aria-label": "Tag 2", - fontSize: "14px", + 'aria-label': 'Tag 2', + fontSize: '14px', fontWeight: 400, }} /> @@ -420,16 +452,16 @@ export const AppPublish = ({ names, categories }) => { value={tag3} onChange={(e) => setTag3(e.target.value)} sx={{ - border: "0.5px solid var(--50-white, #FFFFFF80)", - padding: "0px 15px", - borderRadius: "5px", - height: "36px", - width: "100px", + border: '0.5px solid var(--50-white, #FFFFFF80)', + padding: '0px 15px', + borderRadius: '5px', + height: '36px', + width: '100px', }} placeholder="Tag 3" inputProps={{ - "aria-label": "Tag 3", - fontSize: "14px", + 'aria-label': 'Tag 3', + fontSize: '14px', fontWeight: 400, }} /> @@ -437,16 +469,16 @@ export const AppPublish = ({ names, categories }) => { value={tag4} onChange={(e) => setTag4(e.target.value)} sx={{ - border: "0.5px solid var(--50-white, #FFFFFF80)", - padding: "0px 15px", - borderRadius: "5px", - height: "36px", - width: "100px", + border: '0.5px solid var(--50-white, #FFFFFF80)', + padding: '0px 15px', + borderRadius: '5px', + height: '36px', + width: '100px', }} placeholder="Tag 4" inputProps={{ - "aria-label": "Tag 4", - fontSize: "14px", + 'aria-label': 'Tag 4', + fontSize: '14px', fontWeight: 400, }} /> @@ -454,27 +486,31 @@ export const AppPublish = ({ names, categories }) => { value={tag5} onChange={(e) => setTag5(e.target.value)} sx={{ - border: "0.5px solid var(--50-white, #FFFFFF80)", - padding: "0px 15px", - borderRadius: "5px", - height: "36px", - width: "100px", + border: '0.5px solid var(--50-white, #FFFFFF80)', + padding: '0px 15px', + borderRadius: '5px', + height: '36px', + width: '100px', }} placeholder="Tag 5" inputProps={{ - "aria-label": "Tag 5", - fontSize: "14px", + 'aria-label': 'Tag 5', + fontSize: '14px', fontWeight: 400, }} /> + + - Select .zip file containing static content:{" "} + Select .zip file containing static content:{' '} + + {`(${ - appType === "APP" ? "50mb" : "400mb" + appType === 'APP' ? '50mb' : '400mb' } MB maximum)`} {file && ( <> @@ -484,21 +520,25 @@ export const AppPublish = ({ names, categories }) => { )} + - {" "} + {' '} Choose File + + Publish + { info={infoSnack} setInfo={setInfoSnack} /> - ); }; diff --git a/src/components/Apps/AppViewer.tsx b/src/components/Apps/AppViewer.tsx index 9302df7..723db86 100644 --- a/src/components/Apps/AppViewer.tsx +++ b/src/components/Apps/AppViewer.tsx @@ -1,210 +1,249 @@ -import React, { useContext, useEffect, useMemo, useState } from "react"; +import React, { useContext, useEffect, useMemo, useState } from 'react'; +import { Box } from '@mui/material'; +import { MyContext, getBaseApiReact } from '../../App'; +import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events'; +import { useFrame } from 'react-frame-component'; +import { useQortalMessageListener } from './useQortalMessageListener'; +import { useThemeContext } from '../Theme/ThemeContext'; -import { Box, } from "@mui/material"; -import { MyContext, getBaseApiReact, isMobile } from "../../App"; +export const AppViewer = React.forwardRef( + ({ app, hide, isDevMode, skipAuth }, iframeRef) => { + const { rootHeight } = useContext(MyContext); + // const iframeRef = useRef(null); + const { window: frameWindow } = useFrame(); + const { path, history, changeCurrentIndex, resetHistory } = + useQortalMessageListener( + frameWindow, + iframeRef, + app?.tabId, + isDevMode, + app?.name, + app?.service, + skipAuth + ); + const [url, setUrl] = useState(''); + const { themeMode } = useThemeContext(); -import { subscribeToEvent, unsubscribeFromEvent } from "../../utils/events"; -import { useFrame } from "react-frame-component"; -import { useQortalMessageListener } from "./useQortalMessageListener"; -import { useThemeContext } from "../Theme/ThemeContext"; + useEffect(() => { + if (app?.isPreview) return; + if (isDevMode) { + setUrl(app?.url); + return; + } + let hasQueryParam = false; + if (app?.path && app.path.includes('?')) { + hasQueryParam = true; + } + setUrl( + `${getBaseApiReact()}/render/${app?.service}/${app?.name}${app?.path != null ? `/${app?.path}` : ''}${hasQueryParam ? '&' : '?'}theme=${themeMode}&identifier=${app?.identifier != null && app?.identifier != 'null' ? app?.identifier : ''}` + ); + }, [app?.service, app?.name, app?.identifier, app?.path, app?.isPreview]); + useEffect(() => { + if (app?.isPreview && app?.url) { + resetHistory(); + setUrl(app.url); + } + }, [app?.url, app?.isPreview]); + const defaultUrl = useMemo(() => { + return url; + }, [url, isDevMode]); - -export const AppViewer = React.forwardRef(({ app , hide, isDevMode, skipAuth}, iframeRef) => { - const { rootHeight } = useContext(MyContext); - // const iframeRef = useRef(null); - const { window: frameWindow } = useFrame(); - const {path, history, changeCurrentIndex, resetHistory} = useQortalMessageListener(frameWindow, iframeRef, app?.tabId, isDevMode, app?.name, app?.service, skipAuth) - const [url, setUrl] = useState('') - const { themeMode } = useThemeContext(); - - useEffect(()=> { - if(app?.isPreview) return - if(isDevMode){ - setUrl(app?.url) - return - } - let hasQueryParam = false - if(app?.path && app.path.includes('?')){ - hasQueryParam = true - } - - setUrl(`${getBaseApiReact()}/render/${app?.service}/${app?.name}${app?.path != null ? `/${app?.path}` : ''}${hasQueryParam ? "&": "?" }theme=${themeMode}&identifier=${(app?.identifier != null && app?.identifier != 'null') ? app?.identifier : ''}`) - }, [app?.service, app?.name, app?.identifier, app?.path, app?.isPreview]) - - useEffect(()=> { - if(app?.isPreview && app?.url){ - resetHistory() - setUrl(app.url) - } - }, [app?.url, app?.isPreview]) - const defaultUrl = useMemo(()=> { - return url - }, [url, isDevMode]) - - - const refreshAppFunc = (e) => { - const {tabId} = e.detail - if(tabId === app?.tabId){ - if(isDevMode){ - resetHistory() - if(!app?.isPreview || app?.isPrivate){ - setUrl(app?.url + `?time=${Date.now()}`) + const refreshAppFunc = (e) => { + const { tabId } = e.detail; + if (tabId === app?.tabId) { + if (isDevMode) { + resetHistory(); + if (!app?.isPreview || app?.isPrivate) { + setUrl(app?.url + `?time=${Date.now()}`); + } + return; } - return - + const constructUrl = `${getBaseApiReact()}/render/${app?.service}/${app?.name}${path != null ? path : ''}?theme=${themeMode}&identifier=${app?.identifier != null ? app?.identifier : ''}&time=${new Date().getMilliseconds()}`; + setUrl(constructUrl); } - const constructUrl = `${getBaseApiReact()}/render/${app?.service}/${app?.name}${path != null ? path : ''}?theme=${themeMode}&identifier=${app?.identifier != null ? app?.identifier : ''}&time=${new Date().getMilliseconds()}` - setUrl(constructUrl) - } - }; - - useEffect(() => { - subscribeToEvent("refreshApp", refreshAppFunc); - - return () => { - unsubscribeFromEvent("refreshApp", refreshAppFunc); }; - }, [app, path, isDevMode]); - useEffect(()=> { - if(!iframeRef?.current) return - const targetOrigin = iframeRef.current ? new URL(iframeRef.current.src).origin : "*"; - // Send the navigation command after setting up the listener and timeout - iframeRef.current.contentWindow.postMessage( - { action: 'THEME_CHANGED', theme: themeMode, requestedHandler: 'UI' }, targetOrigin - ); - }, [themeMode]) + useEffect(() => { + subscribeToEvent('refreshApp', refreshAppFunc); - const removeTrailingSlash = (str) => str.replace(/\/$/, ''); - const copyLinkFunc = (e) => { - const {tabId} = e.detail - if(tabId === app?.tabId){ - let link = 'qortal://' + app?.service + '/' + app?.name - if(path && path.startsWith('/')){ - link = link + removeTrailingSlash(path) - } - if(path && !path.startsWith('/')){ - link = link + '/' + removeTrailingSlash(path) - } - navigator.clipboard.writeText(link) - .then(() => { - console.log("Path copied to clipboard:", path); - }) - .catch((error) => { - console.error("Failed to copy path:", error); - }); - } - }; + return () => { + unsubscribeFromEvent('refreshApp', refreshAppFunc); + }; + }, [app, path, isDevMode]); - useEffect(() => { - subscribeToEvent("copyLink", copyLinkFunc); - - return () => { - unsubscribeFromEvent("copyLink", copyLinkFunc); - }; - }, [app, path]); - - // Function to navigate back in iframe - const navigateBackInIframe = async () => { - if (iframeRef.current && iframeRef.current.contentWindow && history?.currentIndex > 0) { - // Calculate the previous index and path - const previousPageIndex = history.currentIndex - 1; - const previousPath = history.customQDNHistoryPaths[previousPageIndex]; - const targetOrigin = iframeRef.current ? new URL(iframeRef.current.src).origin : "*"; - // Signal non-manual navigation - iframeRef.current.contentWindow.postMessage( - { action: 'PERFORMING_NON_MANUAL', currentIndex: previousPageIndex },targetOrigin - ); - // Update the current index locally - changeCurrentIndex(previousPageIndex); - - // Create a navigation promise with a 200ms timeout - const navigationPromise = new Promise((resolve, reject) => { - function handleNavigationSuccess(event) { - if (event.data?.action === 'NAVIGATION_SUCCESS' && event.data.path === previousPath) { - frameWindow.removeEventListener('message', handleNavigationSuccess); - resolve(); - } - } - - frameWindow.addEventListener('message', handleNavigationSuccess); - - // Timeout after 200ms if no response - setTimeout(() => { - window.removeEventListener('message', handleNavigationSuccess); - reject(new Error("Navigation timeout")); - }, 200); - const targetOrigin = iframeRef.current ? new URL(iframeRef.current.src).origin : "*"; + useEffect(() => { + if (!iframeRef?.current) return; + const targetOrigin = iframeRef.current + ? new URL(iframeRef.current.src).origin + : '*'; // Send the navigation command after setting up the listener and timeout iframeRef.current.contentWindow.postMessage( - { action: 'NAVIGATE_TO_PATH', path: previousPath, requestedHandler: 'UI' }, targetOrigin + { action: 'THEME_CHANGED', theme: themeMode, requestedHandler: 'UI' }, + targetOrigin ); - }); + }, [themeMode]); - // Execute navigation promise and handle timeout fallback - try { - await navigationPromise; - } catch (error) { - if(isDevMode){ - setUrl(`${url}${previousPath != null ? previousPath : ''}?theme=${themeMode}&time=${new Date().getMilliseconds()}&isManualNavigation=false`) - return - } - setUrl(`${getBaseApiReact()}/render/${app?.service}/${app?.name}${previousPath != null ? previousPath : ''}?theme=${themeMode}&identifier=${(app?.identifier != null && app?.identifier != 'null') ? app?.identifier : ''}&time=${new Date().getMilliseconds()}&isManualNavigation=false`) - // iframeRef.current.contentWindow.location.href = previousPath; // Fallback URL update - } - } else { - console.log('Iframe not accessible or does not have a content window.'); - } -}; + const removeTrailingSlash = (str) => str.replace(/\/$/, ''); - const navigateBackAppFunc = (e) => { - - navigateBackInIframe() - }; - - useEffect(() => { - if(!app?.tabId) return - subscribeToEvent(`navigateBackApp-${app?.tabId}`, navigateBackAppFunc); - - return () => { - unsubscribeFromEvent(`navigateBackApp-${app?.tabId}`, navigateBackAppFunc); + const copyLinkFunc = (e) => { + const { tabId } = e.detail; + if (tabId === app?.tabId) { + let link = 'qortal://' + app?.service + '/' + app?.name; + if (path && path.startsWith('/')) { + link = link + removeTrailingSlash(path); + } + if (path && !path.startsWith('/')) { + link = link + '/' + removeTrailingSlash(path); + } + navigator.clipboard + .writeText(link) + .then(() => { + console.log('Path copied to clipboard:', path); + }) + .catch((error) => { + console.error('Failed to copy path:', error); + }); + } }; - }, [app, history]); + useEffect(() => { + subscribeToEvent('copyLink', copyLinkFunc); - // Function to navigate back in iframe - const navigateForwardInIframe = async () => { + return () => { + unsubscribeFromEvent('copyLink', copyLinkFunc); + }; + }, [app, path]); - - if (iframeRef.current && iframeRef.current.contentWindow) { - const targetOrigin = iframeRef.current ? new URL(iframeRef.current.src).origin : "*"; - iframeRef.current.contentWindow.postMessage( - { action: 'NAVIGATE_FORWARD'}, + // Function to navigate back in iframe + const navigateBackInIframe = async () => { + if ( + iframeRef.current && + iframeRef.current.contentWindow && + history?.currentIndex > 0 + ) { + // Calculate the previous index and path + const previousPageIndex = history.currentIndex - 1; + const previousPath = history.customQDNHistoryPaths[previousPageIndex]; + const targetOrigin = iframeRef.current + ? new URL(iframeRef.current.src).origin + : '*'; + // Signal non-manual navigation + iframeRef.current.contentWindow.postMessage( + { action: 'PERFORMING_NON_MANUAL', currentIndex: previousPageIndex }, targetOrigin - ); - } else { - console.log('Iframe not accessible or does not have a content window.'); + ); + // Update the current index locally + changeCurrentIndex(previousPageIndex); + + // Create a navigation promise with a 200ms timeout + const navigationPromise = new Promise((resolve, reject) => { + function handleNavigationSuccess(event) { + if ( + event.data?.action === 'NAVIGATION_SUCCESS' && + event.data.path === previousPath + ) { + frameWindow.removeEventListener( + 'message', + handleNavigationSuccess + ); + resolve(); + } + } + + frameWindow.addEventListener('message', handleNavigationSuccess); + + // Timeout after 200ms if no response + setTimeout(() => { + window.removeEventListener('message', handleNavigationSuccess); + reject(new Error('Navigation timeout')); + }, 200); + const targetOrigin = iframeRef.current + ? new URL(iframeRef.current.src).origin + : '*'; + // Send the navigation command after setting up the listener and timeout + iframeRef.current.contentWindow.postMessage( + { + action: 'NAVIGATE_TO_PATH', + path: previousPath, + requestedHandler: 'UI', + }, + targetOrigin + ); + }); + + // Execute navigation promise and handle timeout fallback + try { + await navigationPromise; + } catch (error) { + if (isDevMode) { + setUrl( + `${url}${previousPath != null ? previousPath : ''}?theme=${themeMode}&time=${new Date().getMilliseconds()}&isManualNavigation=false` + ); + return; + } + setUrl( + `${getBaseApiReact()}/render/${app?.service}/${app?.name}${previousPath != null ? previousPath : ''}?theme=${themeMode}&identifier=${app?.identifier != null && app?.identifier != 'null' ? app?.identifier : ''}&time=${new Date().getMilliseconds()}&isManualNavigation=false` + ); + // iframeRef.current.contentWindow.location.href = previousPath; // Fallback URL update + } + } else { + console.log('Iframe not accessible or does not have a content window.'); + } + }; + + const navigateBackAppFunc = (e) => { + navigateBackInIframe(); + }; + + useEffect(() => { + if (!app?.tabId) return; + subscribeToEvent(`navigateBackApp-${app?.tabId}`, navigateBackAppFunc); + + return () => { + unsubscribeFromEvent( + `navigateBackApp-${app?.tabId}`, + navigateBackAppFunc + ); + }; + }, [app, history]); + + // Function to navigate back in iframe + const navigateForwardInIframe = async () => { + if (iframeRef.current && iframeRef.current.contentWindow) { + const targetOrigin = iframeRef.current + ? new URL(iframeRef.current.src).origin + : '*'; + iframeRef.current.contentWindow.postMessage( + { action: 'NAVIGATE_FORWARD' }, + targetOrigin + ); + } else { + console.log('Iframe not accessible or does not have a content window.'); + } + }; + + return ( + + + + ); } -}; - - - return ( - - - - - - ); -}); +); diff --git a/src/components/Apps/AppViewerContainer.tsx b/src/components/Apps/AppViewerContainer.tsx index 194ae68..aa60a6c 100644 --- a/src/components/Apps/AppViewerContainer.tsx +++ b/src/components/Apps/AppViewerContainer.tsx @@ -1,20 +1,19 @@ -import React, { useContext, } from 'react'; +import React, { useContext } from 'react'; import { AppViewer } from './AppViewer'; import Frame from 'react-frame-component'; -import { MyContext, isMobile } from '../../App'; +import { MyContext } from '../../App'; -const AppViewerContainer = React.forwardRef(({ app, isSelected, hide, isDevMode, customHeight, skipAuth }, ref) => { - const { rootHeight } = useContext(MyContext); +const AppViewerContainer = React.forwardRef( + ({ app, isSelected, hide, isDevMode, customHeight, skipAuth }, ref) => { + const { rootHeight } = useContext(MyContext); - - return ( - - - - } - style={{ - position: (!isSelected || hide) && 'fixed', - left: (!isSelected || hide) && '-200vw', - height: customHeight ? customHeight : !isMobile ? '100vh' : `calc(${rootHeight} - 60px - 45px)`, - border: 'none', - width: '100%', - overflow: 'hidden', - }} - > - - - ); -}); + + + } + style={{ + border: 'none', + height: '100vh', + left: (!isSelected || hide) && '-200vw', + overflow: 'hidden', + position: (!isSelected || hide) && 'fixed', + width: '100%', + }} + > + + + ); + } +); export default AppViewerContainer; diff --git a/src/components/Apps/Apps-styles.tsx b/src/components/Apps/Apps-styles.tsx index 18e3af1..152c09a 100644 --- a/src/components/Apps/Apps-styles.tsx +++ b/src/components/Apps/Apps-styles.tsx @@ -137,9 +137,9 @@ export const AppCircle = styled(Box)(({ theme }) => ({ theme.palette.mode === 'dark' ? 'rgb(209, 209, 209)' : 'rgba(41, 41, 43, 1)', - borderWidth: '1px', borderRadius: '50%', borderStyle: 'solid', + borderWidth: '1px', color: theme.palette.text.primary, display: 'flex', flexDirection: 'column', diff --git a/src/components/Apps/AppsDevModeHome.tsx b/src/components/Apps/AppsDevModeHome.tsx index 8c346c2..0fc8242 100644 --- a/src/components/Apps/AppsDevModeHome.tsx +++ b/src/components/Apps/AppsDevModeHome.tsx @@ -22,7 +22,7 @@ import { Input, } from '@mui/material'; import { Add } from '@mui/icons-material'; -import { MyContext, getBaseApiReact, isMobile } from '../../App'; +import { MyContext, getBaseApiReact } from '../../App'; import LogoSelected from '../../assets/svgs/LogoSelected.svg'; import { executeEvent } from '../../utils/events'; import { Spacer } from '../../common/Spacer'; @@ -279,7 +279,9 @@ export const AppsDevModeHome = ({ Dev Mode Apps + + @@ -302,6 +304,7 @@ export const AppsDevModeHome = ({ Server + { addPreviewApp(); @@ -309,15 +312,17 @@ export const AppsDevModeHome = ({ > + + Zip + { addPreviewAppWithDirectory(); @@ -325,7 +330,7 @@ export const AppsDevModeHome = ({ > @@ -334,6 +339,7 @@ export const AppsDevModeHome = ({ Directory + { executeEvent('appsDevModeAddTab', { @@ -347,7 +353,7 @@ export const AppsDevModeHome = ({ > @@ -371,9 +377,11 @@ export const AppsDevModeHome = ({ /> + Q-Sandbox + { executeEvent('appsDevModeAddTab', { @@ -387,7 +395,7 @@ export const AppsDevModeHome = ({ > @@ -411,10 +419,12 @@ export const AppsDevModeHome = ({ /> + API + {isShow && ( {'Add custom framework'} + + - + + + {combinedListTempAndReal.map((message, index, list) => { let fullMessage = message; @@ -780,17 +759,17 @@ export const Thread = ({ > @@ -812,6 +791,7 @@ export const Thread = ({ {message?.name?.charAt(0)} + + {formatTimestampForum(message?.created)} + @@ -908,6 +890,7 @@ export const Thread = ({ {message?.name?.charAt(0)} + + {formatTimestampForum(message?.created)} + @@ -952,9 +937,9 @@ export const Thread = ({ + {loading && promotions.length === 0 && ( @@ -589,6 +583,7 @@ export const ListOfGroupPromotions = () => { > Group name: {` ${promotion?.groupName}`} + { Number of members:{' '} {` ${promotion?.memberCount}`} + {promotion?.description && ( { {promotion?.description} )} + {promotion?.isOpen === false && ( { your request )} + + { > Close + { > {promotion?.name?.charAt(0)} + { {promotion?.name} + { {promotion?.groupName} + + { : 'Private group'} + + { > {promotion?.data} + + { + @@ -779,6 +788,7 @@ export const ListOfGroupPromotions = () => { + {isShowModal && ( diff --git a/src/components/Group/ListOfThreadPostsWatched.tsx b/src/components/Group/ListOfThreadPostsWatched.tsx index 6e24ba3..e0a9d00 100644 --- a/src/components/Group/ListOfThreadPostsWatched.tsx +++ b/src/components/Group/ListOfThreadPostsWatched.tsx @@ -1,21 +1,14 @@ -import * as React from "react"; -import List from "@mui/material/List"; -import ListItem from "@mui/material/ListItem"; -import ListItemButton from "@mui/material/ListItemButton"; -import ListItemIcon from "@mui/material/ListItemIcon"; -import ListItemText from "@mui/material/ListItemText"; -import Checkbox from "@mui/material/Checkbox"; -import IconButton from "@mui/material/IconButton"; -import CommentIcon from "@mui/icons-material/Comment"; -import InfoIcon from "@mui/icons-material/Info"; -import GroupAddIcon from "@mui/icons-material/GroupAdd"; -import { executeEvent } from "../../utils/events"; -import { Box, Typography } from "@mui/material"; -import { Spacer } from "../../common/Spacer"; -import { getGroupNames } from "./UserListOfInvites"; -import { CustomLoader } from "../../common/CustomLoader"; -import VisibilityIcon from "@mui/icons-material/Visibility"; -import { isMobile } from "../../App"; +import * as React from 'react'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemText from '@mui/material/ListItemText'; +import IconButton from '@mui/material/IconButton'; +import { executeEvent } from '../../utils/events'; +import { Box, Typography } from '@mui/material'; +import { Spacer } from '../../common/Spacer'; +import { CustomLoader } from '../../common/CustomLoader'; +import VisibilityIcon from '@mui/icons-material/Visibility'; export const ListOfThreadPostsWatched = () => { const [posts, setPosts] = React.useState([]); @@ -24,33 +17,33 @@ export const ListOfThreadPostsWatched = () => { const getPosts = async () => { try { await new Promise((res, rej) => { - window.sendMessage("getThreadActivity", {}) - .then((response) => { - if (!response?.error) { - if (!response) { - res(null); + window + .sendMessage('getThreadActivity', {}) + .then((response) => { + if (!response?.error) { + if (!response) { + res(null); + return; + } + const uniquePosts = response.reduce((acc, current) => { + const x = acc.find( + (item) => item?.thread?.threadId === current?.thread?.threadId + ); + if (!x) { + return acc.concat([current]); + } else { + return acc; + } + }, []); + setPosts(uniquePosts); + res(uniquePosts); return; } - const uniquePosts = response.reduce((acc, current) => { - const x = acc.find( - (item) => item?.thread?.threadId === current?.thread?.threadId - ); - if (!x) { - return acc.concat([current]); - } else { - return acc; - } - }, []); - setPosts(uniquePosts); - res(uniquePosts); - return; - } - rej(response.error); - }) - .catch((error) => { - rej(error.message || "An error occurred"); - }); - + rej(response.error); + }) + .catch((error) => { + rej(error.message || 'An error occurred'); + }); }); } catch (error) { } finally { @@ -63,49 +56,50 @@ export const ListOfThreadPostsWatched = () => { }, []); return ( - + New Thread Posts: - + {loading && posts.length === 0 && ( @@ -114,19 +108,18 @@ export const ListOfThreadPostsWatched = () => { {!loading && posts.length === 0 && ( Nothing to display @@ -134,47 +127,46 @@ export const ListOfThreadPostsWatched = () => { )} {posts?.length > 0 && ( - - {posts?.map((post) => { - return ( - { - executeEvent("openThreadNewPost", { - data: post, - }); - }} - disablePadding - secondaryAction={ - - - - } - > - - - - - ); - })} - + + {posts?.map((post) => { + return ( + { + executeEvent('openThreadNewPost', { + data: post, + }); + }} + disablePadding + secondaryAction={ + + + + } + > + + + + + ); + })} + )} - ); diff --git a/src/components/Group/ManageMembers.tsx b/src/components/Group/ManageMembers.tsx index de361fa..a69dde4 100644 --- a/src/components/Group/ManageMembers.tsx +++ b/src/components/Group/ManageMembers.tsx @@ -1,10 +1,6 @@ import * as React from 'react'; import Button from '@mui/material/Button'; import Dialog from '@mui/material/Dialog'; -import ListItemText from '@mui/material/ListItemText'; -import ListItemButton from '@mui/material/ListItemButton'; -import List from '@mui/material/List'; -import Divider from '@mui/material/Divider'; import AppBar from '@mui/material/AppBar'; import Toolbar from '@mui/material/Toolbar'; import IconButton from '@mui/material/IconButton'; @@ -19,7 +15,7 @@ import { ListOfBans } from './ListOfBans'; import { ListOfJoinRequests } from './ListOfJoinRequests'; import { Box, ButtonBase, Card, Tab, Tabs } from '@mui/material'; import { CustomizedSnackbars } from '../Snackbar/Snackbar'; -import { MyContext, getBaseApiReact, isMobile } from '../../App'; +import { MyContext, getBaseApiReact } from '../../App'; import { getGroupMembers, getNames } from './Group'; import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar'; import { getFee } from '../../background'; @@ -27,6 +23,7 @@ import { LoadingButton } from '@mui/lab'; import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events'; import { Spacer } from '../../common/Spacer'; import InsertLinkIcon from '@mui/icons-material/InsertLink'; + function a11yProps(index: number) { return { id: `simple-tab-${index}`, @@ -193,9 +190,9 @@ export const ManageMembers = ({ @@ -221,9 +218,10 @@ export const ManageMembers = ({ '&.Mui-selected': { color: 'white', }, - fontSize: isMobile ? '0.75rem' : '1rem', // Adjust font size for mobile + fontSize: '1rem', }} /> + + + + + GroupId: {groupInfo?.groupId} + GroupName: {groupInfo?.groupName} + Number of members: {groupInfo?.memberCount} + Join Group Link + + {selectedGroup?.groupId && !isOwner && ( Load members with names + + )} + + { borderRadius: '19px', display: 'flex', flexDirection: 'column', - height: isMobile ? '165px' : '250px', + height: '250px', overflow: 'auto', padding: '20px', width: '322px', diff --git a/src/components/Group/ThingsToDoInitial.tsx b/src/components/Group/ThingsToDoInitial.tsx index 367b3b1..023cfde 100644 --- a/src/components/Group/ThingsToDoInitial.tsx +++ b/src/components/Group/ThingsToDoInitial.tsx @@ -1,28 +1,23 @@ -import * as React from "react"; -import List from "@mui/material/List"; -import ListItem from "@mui/material/ListItem"; -import ListItemButton from "@mui/material/ListItemButton"; -import ListItemIcon from "@mui/material/ListItemIcon"; -import ListItemText from "@mui/material/ListItemText"; -import Checkbox from "@mui/material/Checkbox"; -import IconButton from "@mui/material/IconButton"; -import CommentIcon from "@mui/icons-material/Comment"; -import InfoIcon from "@mui/icons-material/Info"; -import { Box, Typography } from "@mui/material"; -import { Spacer } from "../../common/Spacer"; -import { isMobile } from "../../App"; -import { QMailMessages } from "./QMailMessages"; -import { executeEvent } from "../../utils/events"; +import * as React from 'react'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import { Box, Typography } from '@mui/material'; +import { Spacer } from '../../common/Spacer'; +import { QMailMessages } from './QMailMessages'; +import { executeEvent } from '../../utils/events'; -export const ThingsToDoInitial = ({ myAddress, name, hasGroups, balance, userInfo }) => { +export const ThingsToDoInitial = ({ + myAddress, + name, + hasGroups, + balance, + userInfo, +}) => { const [checked1, setChecked1] = React.useState(false); const [checked2, setChecked2] = React.useState(false); - // const [checked3, setChecked3] = React.useState(false); - - // React.useEffect(() => { - // if (hasGroups) setChecked3(true); - // }, [hasGroups]); - React.useEffect(() => { if (balance && +balance >= 6) { @@ -30,111 +25,114 @@ export const ThingsToDoInitial = ({ myAddress, name, hasGroups, balance, userInf } }, [balance]); - React.useEffect(() => { if (name) setChecked2(true); }, [name]); + const isLoaded = React.useMemo(() => { + if (userInfo !== null) return true; + return false; + }, [userInfo]); - const isLoaded = React.useMemo(()=> { - if(userInfo !== null) return true - return false - }, [ userInfo]) + const hasDoneNameAndBalanceAndIsLoaded = React.useMemo(() => { + if (isLoaded && checked1 && checked2) return true; + return false; + }, [checked1, isLoaded, checked2]); - const hasDoneNameAndBalanceAndIsLoaded = React.useMemo(()=> { - if(isLoaded && checked1 && checked2) return true - return false -}, [checked1, isLoaded, checked2]) - -if(hasDoneNameAndBalanceAndIsLoaded){ - return ( - - ); -} -if(!isLoaded) return null + if (hasDoneNameAndBalanceAndIsLoaded) { + return ( + + ); + } + if (!isLoaded) return null; return ( - {!isLoaded ? 'Loading...' : 'Getting Started' } + {!isLoaded ? 'Loading...' : 'Getting Started'} + {isLoaded && ( - - - { - executeEvent("openBuyQortInfo", {}) - }} - > - - - - {/* + + { + executeEvent('openBuyQortInfo', {}); + }} + > + + + + {/* */} - - - - - // - // - // } - disablePadding - > - - - { - executeEvent('openRegisterName', {}) - }} sx={{ - "& .MuiTypography-root": { - fontSize: "1rem", - fontWeight: 400, - }, - }} primary={`Register a name`} /> - - - - - - {/* + + + + // + // + // } + disablePadding + > + + { + executeEvent('openRegisterName', {}); + }} + sx={{ + '& .MuiTypography-root': { + fontSize: '1rem', + fontWeight: 400, + }, + }} + primary={`Register a name`} + /> + + + + + + {/* */} - + )} - ); diff --git a/src/components/ReactionPicker.tsx b/src/components/ReactionPicker.tsx index 2f5f08e..c95adcf 100644 --- a/src/components/ReactionPicker.tsx +++ b/src/components/ReactionPicker.tsx @@ -1,9 +1,8 @@ -import React, { useState, useRef, useEffect } from 'react'; +import { useState, useRef, useEffect } from 'react'; import ReactDOM from 'react-dom'; import Picker, { EmojiStyle, Theme } from 'emoji-picker-react'; import './ReactionPicker.css'; import { ButtonBase } from '@mui/material'; -import { isMobile } from '../App'; export const ReactionPicker = ({ onReaction }) => { const [showPicker, setShowPicker] = useState(false); @@ -30,7 +29,7 @@ export const ReactionPicker = ({ onReaction }) => { } else { // Get the button's position const buttonRect = buttonRef.current.getBoundingClientRect(); - const pickerWidth = isMobile ? 300 : 350; // Adjust based on picker width + const pickerWidth = 350; // Calculate position to align the right edge of the picker with the button's right edge setPickerPosition({ @@ -90,15 +89,15 @@ export const ReactionPicker = ({ onReaction }) => { }} > , document.body diff --git a/src/components/TaskManager/TaskManager.tsx b/src/components/TaskManager/TaskManager.tsx index f1f075b..673c385 100644 --- a/src/components/TaskManager/TaskManager.tsx +++ b/src/components/TaskManager/TaskManager.tsx @@ -12,7 +12,7 @@ import PendingIcon from '@mui/icons-material/Pending'; import TaskAltIcon from '@mui/icons-material/TaskAlt'; import ExpandLess from '@mui/icons-material/ExpandLess'; import ExpandMore from '@mui/icons-material/ExpandMore'; -import { MyContext, getBaseApiReact, isMobile } from '../../App'; +import { MyContext, getBaseApiReact } from '../../App'; import { executeEvent } from '../../utils/events'; export const TaskManager = ({ getUserInfo }) => { @@ -141,8 +141,7 @@ export const TaskManager = ({ getUserInfo }) => { }); }, [txList]); - if (isMobile || txList?.length === 0 || txList.every((item) => item?.done)) - return null; + if (txList?.length === 0 || txList.every((item) => item?.done)) return null; return ( <> diff --git a/src/components/Tutorials/Tutorials.tsx b/src/components/Tutorials/Tutorials.tsx index 4f9ea49..382774c 100644 --- a/src/components/Tutorials/Tutorials.tsx +++ b/src/components/Tutorials/Tutorials.tsx @@ -1,72 +1,103 @@ -import React, { useContext, useState } from 'react' -import { GlobalContext, MyContext } from '../../App'; -import { Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Tab, Tabs, Typography } from '@mui/material'; +import { useContext, useState } from 'react'; +import { GlobalContext } from '../../App'; +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + IconButton, + Tab, + Tabs, + useTheme, +} from '@mui/material'; import CloseIcon from '@mui/icons-material/Close'; import { VideoPlayer } from '../Embeds/VideoPlayer'; export const Tutorials = () => { - const { openTutorialModal, setOpenTutorialModal } = useContext(GlobalContext); - const [multiNumber, setMultiNumber] = useState(0) - const handleClose = ()=> { - setOpenTutorialModal(null) - setMultiNumber(0) - } - if(!openTutorialModal) return null - if(openTutorialModal?.multi){ - const selectedTutorial = openTutorialModal?.multi[multiNumber] - return ( - { + setOpenTutorialModal(null); + setMultiNumber(0); + }; + + if (!openTutorialModal) return null; + + if (openTutorialModal?.multi) { + const selectedTutorial = openTutorialModal?.multi[multiNumber]; + return ( + - setMultiNumber(value)} aria-label="basic tabs example"> - {openTutorialModal?.multi?.map((item, index)=> { - return ( - - - ) - })} - - + setMultiNumber(value)} + aria-label="basic tabs example" + > + {openTutorialModal?.multi?.map((item, index) => { + return ( + + ); + })} + + + {selectedTutorial?.title} {` Tutorial`} + ({ + sx={{ position: 'absolute', right: 8, top: 8, - color: theme.palette.grey[500], - })} + color: theme.palette.text.primary, + }} > - - + + - ) - } + ); + } + return ( <> { fullWidth={true} maxWidth="xl" > - + {openTutorialModal?.title} {` Tutorial`} + ({ + sx={{ position: 'absolute', right: 8, top: 8, - color: theme.palette.grey[500], - })} + color: theme.palette.text.primary, + }} > - - + + + - ) -} + ); +}; diff --git a/src/useAppFullscreen.tsx b/src/useAppFullscreen.tsx index 3dbe4a1..1ce6842 100644 --- a/src/useAppFullscreen.tsx +++ b/src/useAppFullscreen.tsx @@ -1,67 +1,85 @@ import { useCallback, useEffect } from 'react'; -import { isMobile } from './App'; export const useAppFullScreen = (setFullScreen) => { - const enterFullScreen = useCallback(() => { - const element = document.documentElement; // Target the entire HTML document - if (element.requestFullscreen) { - element.requestFullscreen(); - } else if (element.mozRequestFullScreen) { // Firefox - element.mozRequestFullScreen(); - } else if (element.webkitRequestFullscreen) { // Chrome, Safari and Opera - element.webkitRequestFullscreen(); - } else if (element.msRequestFullscreen) { // IE/Edge - element.msRequestFullscreen(); - } - }, []); + const enterFullScreen = useCallback(() => { + const element = document.documentElement; // Target the entire HTML document + if (element.requestFullscreen) { + element.requestFullscreen(); + } else if (element.mozRequestFullScreen) { + // Firefox + element.mozRequestFullScreen(); + } else if (element.webkitRequestFullscreen) { + // Chrome, Safari and Opera + element.webkitRequestFullscreen(); + } else if (element.msRequestFullscreen) { + // IE/Edge + element.msRequestFullscreen(); + } + }, []); - const exitFullScreen = useCallback(() => { - if (document.fullscreenElement) { - document.exitFullscreen(); - } else if (document.mozFullScreenElement) { - document.mozCancelFullScreen(); - } else if (document.webkitFullscreenElement) { - document.webkitExitFullscreen(); - } else if (document.msFullscreenElement) { - document.msExitFullscreen(); - } - }, []); + const exitFullScreen = useCallback(() => { + if (document.fullscreenElement) { + document.exitFullscreen(); + } else if (document.mozFullScreenElement) { + document.mozCancelFullScreen(); + } else if (document.webkitFullscreenElement) { + document.webkitExitFullscreen(); + } else if (document.msFullscreenElement) { + document.msExitFullscreen(); + } + }, []); - const toggleFullScreen = useCallback(() => { - if(!isMobile || isMobile) return - if (document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement) { - exitFullScreen(); - setFullScreen(false) - } else { - enterFullScreen(); - setFullScreen(true) - } - }, [enterFullScreen, exitFullScreen]); + const toggleFullScreen = useCallback(() => { + if ( + document.fullscreenElement || + document.mozFullScreenElement || + document.webkitFullscreenElement || + document.msFullscreenElement + ) { + exitFullScreen(); + setFullScreen(false); + } else { + enterFullScreen(); + setFullScreen(true); + } + }, [enterFullScreen, exitFullScreen]); - // Listen for changes to fullscreen state - useEffect(() => { - const handleFullScreenChange = () => { - if (document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement) { - - } else { - setFullScreen(false); - } - }; + // Listen for changes to fullscreen state + useEffect(() => { + const handleFullScreenChange = () => { + if ( + document.fullscreenElement || + document.mozFullScreenElement || + document.webkitFullscreenElement || + document.msFullscreenElement + ) { + // TODO check empty block + } else { + setFullScreen(false); + } + }; - document.addEventListener('fullscreenchange', handleFullScreenChange); - document.addEventListener('webkitfullscreenchange', handleFullScreenChange); // Safari - document.addEventListener('mozfullscreenchange', handleFullScreenChange); // Firefox - document.addEventListener('MSFullscreenChange', handleFullScreenChange); // IE/Edge + document.addEventListener('fullscreenchange', handleFullScreenChange); + document.addEventListener('webkitfullscreenchange', handleFullScreenChange); // Safari + document.addEventListener('mozfullscreenchange', handleFullScreenChange); // Firefox + document.addEventListener('MSFullscreenChange', handleFullScreenChange); // IE/Edge - return () => { - document.removeEventListener('fullscreenchange', handleFullScreenChange); - document.removeEventListener('webkitfullscreenchange', handleFullScreenChange); - document.removeEventListener('mozfullscreenchange', handleFullScreenChange); - document.removeEventListener('MSFullscreenChange', handleFullScreenChange); - }; - }, []); + return () => { + document.removeEventListener('fullscreenchange', handleFullScreenChange); + document.removeEventListener( + 'webkitfullscreenchange', + handleFullScreenChange + ); + document.removeEventListener( + 'mozfullscreenchange', + handleFullScreenChange + ); + document.removeEventListener( + 'MSFullscreenChange', + handleFullScreenChange + ); + }; + }, []); - return { enterFullScreen, exitFullScreen, toggleFullScreen }; + return { enterFullScreen, exitFullScreen, toggleFullScreen }; }; - -