diff --git a/src/components/Apps/useQortalMessageListener.tsx b/src/components/Apps/useQortalMessageListener.tsx index f8bd7a0..ddeb62d 100644 --- a/src/components/Apps/useQortalMessageListener.tsx +++ b/src/components/Apps/useQortalMessageListener.tsx @@ -174,15 +174,52 @@ export function openIndexedDB() { } - -const UIQortalRequests = [ +export const listOfAllQortalRequests = [ 'GET_USER_ACCOUNT', 'DECRYPT_DATA', 'SEND_COIN', 'GET_LIST_ITEMS', 'ADD_LIST_ITEMS', 'DELETE_LIST_ITEM', 'VOTE_ON_POLL', 'CREATE_POLL', 'SEND_CHAT_MESSAGE', 'JOIN_GROUP', 'DEPLOY_AT', 'GET_USER_WALLET', 'GET_WALLET_BALANCE', 'GET_USER_WALLET_INFO', 'GET_CROSSCHAIN_SERVER_INFO', 'GET_TX_ACTIVITY_SUMMARY', 'GET_FOREIGN_FEE', 'UPDATE_FOREIGN_FEE', 'GET_SERVER_CONNECTION_HISTORY', 'SET_CURRENT_FOREIGN_SERVER', - 'ADD_FOREIGN_SERVER', 'REMOVE_FOREIGN_SERVER', 'GET_DAY_SUMMARY', 'CREATE_TRADE_BUY_ORDER', 'CREATE_TRADE_SELL_ORDER', 'CANCEL_TRADE_SELL_ORDER', 'IS_USING_GATEWAY', 'ADMIN_ACTION', 'SIGN_TRANSACTION', 'OPEN_NEW_TAB', 'CREATE_AND_COPY_EMBED_LINK', 'DECRYPT_QORTAL_GROUP_DATA', 'DECRYPT_DATA_WITH_SHARING_KEY', 'DELETE_HOSTED_DATA', 'GET_HOSTED_DATA' + 'ADD_FOREIGN_SERVER', 'REMOVE_FOREIGN_SERVER', 'GET_DAY_SUMMARY', 'CREATE_TRADE_BUY_ORDER', 'CREATE_TRADE_SELL_ORDER', 'CANCEL_TRADE_SELL_ORDER', 'IS_USING_GATEWAY', 'ADMIN_ACTION', 'SIGN_TRANSACTION', 'OPEN_NEW_TAB', 'CREATE_AND_COPY_EMBED_LINK', 'DECRYPT_QORTAL_GROUP_DATA', 'DECRYPT_DATA_WITH_SHARING_KEY', 'DELETE_HOSTED_DATA', 'GET_HOSTED_DATA', 'PUBLISH_MULTIPLE_QDN_RESOURCES', + 'PUBLISH_QDN_RESOURCE', + 'ENCRYPT_DATA', + 'ENCRYPT_DATA_WITH_SHARING_KEY', + 'ENCRYPT_QORTAL_GROUP_DATA', + 'SAVE_FILE', + 'GET_ACCOUNT_DATA', + 'GET_ACCOUNT_NAMES', + 'SEARCH_NAMES', + 'GET_NAME_DATA', + 'GET_QDN_RESOURCE_URL', + 'LINK_TO_QDN_RESOURCE', + 'LIST_QDN_RESOURCES', + 'SEARCH_QDN_RESOURCES', + 'FETCH_QDN_RESOURCE', + 'GET_QDN_RESOURCE_STATUS', + 'GET_QDN_RESOURCE_PROPERTIES', + 'GET_QDN_RESOURCE_METADATA', + 'SEARCH_CHAT_MESSAGES', + 'LIST_GROUPS', + 'GET_BALANCE', + 'GET_AT', + 'GET_AT_DATA', + 'LIST_ATS', + 'FETCH_BLOCK', + 'FETCH_BLOCK_RANGE', + 'SEARCH_TRANSACTIONS', + 'GET_PRICE', + 'SHOW_ACTIONS' +] + +export const UIQortalRequests = [ + 'GET_USER_ACCOUNT', 'DECRYPT_DATA', 'SEND_COIN', 'GET_LIST_ITEMS', + 'ADD_LIST_ITEMS', 'DELETE_LIST_ITEM', 'VOTE_ON_POLL', 'CREATE_POLL', + 'SEND_CHAT_MESSAGE', 'JOIN_GROUP', 'DEPLOY_AT', 'GET_USER_WALLET', + 'GET_WALLET_BALANCE', 'GET_USER_WALLET_INFO', 'GET_CROSSCHAIN_SERVER_INFO', + 'GET_TX_ACTIVITY_SUMMARY', 'GET_FOREIGN_FEE', 'UPDATE_FOREIGN_FEE', + 'GET_SERVER_CONNECTION_HISTORY', 'SET_CURRENT_FOREIGN_SERVER', + 'ADD_FOREIGN_SERVER', 'REMOVE_FOREIGN_SERVER', 'GET_DAY_SUMMARY', 'CREATE_TRADE_BUY_ORDER', 'CREATE_TRADE_SELL_ORDER', 'CANCEL_TRADE_SELL_ORDER', 'IS_USING_GATEWAY', 'ADMIN_ACTION', 'SIGN_TRANSACTION', 'OPEN_NEW_TAB', 'CREATE_AND_COPY_EMBED_LINK', 'DECRYPT_QORTAL_GROUP_DATA', 'DECRYPT_DATA_WITH_SHARING_KEY', 'DELETE_HOSTED_DATA', 'GET_HOSTED_DATA', 'SHOW_ACTIONS' ]; diff --git a/src/components/Group/Group.tsx b/src/components/Group/Group.tsx index 6f9293a..75cd960 100644 --- a/src/components/Group/Group.tsx +++ b/src/components/Group/Group.tsx @@ -195,7 +195,7 @@ export const getGroupMembers = async (groupNumber: number) => { }; -export const decryptResource = async (data: string) => { +export const decryptResource = async (data: string, fromQortalRequest) => { try { return new Promise((res, rej) => { window.sendMessage("decryptGroupEncryption", { @@ -206,10 +206,19 @@ export const decryptResource = async (data: string) => { res(response); return; } - rej(response.error); + if(fromQortalRequest){ + rej({error: response.error, message: response?.error}); + } else { + rej(response.error); + + } }) .catch((error) => { - rej(error.message || "An error occurred"); + if(fromQortalRequest){ + rej({message: error.message || "An error occurred", error: error.message || "An error occurred"}); + } else { + rej(error.message || "An error occurred",); + } }); }); diff --git a/src/components/Group/ListOfGroupPromotions.tsx b/src/components/Group/ListOfGroupPromotions.tsx index 6ed444a..2f474d3 100644 --- a/src/components/Group/ListOfGroupPromotions.tsx +++ b/src/components/Group/ListOfGroupPromotions.tsx @@ -24,12 +24,7 @@ import { TextField, Typography, } from "@mui/material"; -import { - AutoSizer, - CellMeasurer, - CellMeasurerCache, - List, -} from "react-virtualized"; + import { getNameInfo } from "./Group"; import { getBaseApi, getFee } from "../../background"; import { LoadingButton } from "@mui/lab"; @@ -51,6 +46,8 @@ import ShortUniqueId from "short-unique-id"; import { CustomizedSnackbars } from "../Snackbar/Snackbar"; import { getGroupNames } from "./UserListOfInvites"; import { WrapperUserAction } from "../WrapperUserAction"; +import { useVirtualizer } from "@tanstack/react-virtual"; +import ErrorBoundary from "../../common/ErrorBoundary"; export const requestQueuePromos = new RequestQueueWithPromise(20); @@ -68,10 +65,7 @@ export function utf8ToBase64(inputString: string): string { const uid = new ShortUniqueId({ length: 8 }); -const cache = new CellMeasurerCache({ - fixedWidth: true, - defaultHeight: 50, -}); + export function getGroupId(str) { const match = str.match(/group-(\d+)-/); @@ -102,6 +96,16 @@ export const ListOfGroupPromotions = () => { const { show, setTxList } = useContext(MyContext); const listRef = useRef(); + const rowVirtualizer = useVirtualizer({ + count: promotions.length, + getItemKey: React.useCallback( + (index) => promotions[index]?.identifier, + [promotions] + ), + getScrollElement: () => listRef.current, + estimateSize: () => 80, // Provide an estimated height of items, adjust this as needed + overscan: 10, // Number of items to render outside the visible area to improve smoothness + }); useEffect(() => { try { @@ -190,7 +194,7 @@ export const ListOfGroupPromotions = () => { }, initialDelay); return () => clearTimeout(initialTimeout); - }, [getPromotions]); + }, [getPromotions, promotionTimeInterval]); const handlePopoverOpen = (event, index) => { setPopoverAnchor(event.currentTarget); @@ -324,69 +328,165 @@ export const ListOfGroupPromotions = () => { } }; - // const handleCancelInvitation = async (address)=> { - // try { - // const fee = await getFee('CANCEL_GROUP_INVITE') - // await show({ - // message: "Would you like to perform a CANCEL_GROUP_INVITE transaction?" , - // publishFee: fee.fee + ' QORT' - // }) - // setIsLoadingCancelInvite(true) - // await new Promise((res, rej)=> { - // window.sendMessage("cancelInvitationToGroup", { - // groupId, - // qortalAddress: address, - // }) - // .then((response) => { - // if (!response?.error) { - // setInfoSnack({ - // type: "success", - // message: "Successfully canceled invitation. It may take a couple of minutes for the changes to propagate", - // }); - // setOpenSnack(true); - // handlePopoverClose(); - // setIsLoadingCancelInvite(true); - // res(response); - // return; - // } - // setInfoSnack({ - // type: "error", - // message: response?.error, - // }); - // setOpenSnack(true); - // rej(response.error); - // }) - // .catch((error) => { - // setInfoSnack({ - // type: "error", - // message: error.message || "An error occurred", - // }); - // setOpenSnack(true); - // rej(error); - // }); - // }) - // } catch (error) { - // } finally { - // setIsLoadingCancelInvite(false) - // } - // } - - const rowRenderer = ({ index, key, parent, style }) => { - const promotion = promotions[index]; - - return ( - + - {({ measure }) => ( -
- + + Group Promotions + + + + + + + + {loading && promotions.length === 0 && ( + + + + )} + {!loading && promotions.length === 0 && ( + + + Nothing to display + + + )} +
+
+
+
+ {rowVirtualizer.getVirtualItems().map((virtualRow) => { + const index = virtualRow.index; + const promotion = promotions[index]; + return ( + +
+ + Error loading content: Invalid Data + + } + > + { -
- )} - - ); - }; + +
+ + ); + })} +
+
+
+
- - return ( - - - - - Group Promotions - - - - - - - - {loading && promotions.length === 0 && ( - - - - )} - {!loading && promotions.length === 0 && ( - - - Nothing to display - - - )} -
- - {({ height, width }) => ( - - )} - -
diff --git a/src/components/Group/ManageMembers.tsx b/src/components/Group/ManageMembers.tsx index dedc5be..8dab53f 100644 --- a/src/components/Group/ManageMembers.tsx +++ b/src/components/Group/ManageMembers.tsx @@ -17,9 +17,9 @@ import { InviteMember } from "./InviteMember"; import { ListOfInvites } from "./ListOfInvites"; import { ListOfBans } from "./ListOfBans"; import { ListOfJoinRequests } from "./ListOfJoinRequests"; -import { Box, Tab, Tabs } from "@mui/material"; +import { Box, Card, Tab, Tabs } from "@mui/material"; import { CustomizedSnackbars } from "../Snackbar/Snackbar"; -import { MyContext, isMobile } from "../../App"; +import { MyContext, getBaseApiReact, isMobile } from "../../App"; import { getGroupMembers, getNames } from "./Group"; import { LoadingSnackbar } from "../Snackbar/LoadingSnackbar"; import { getFee } from "../../background"; @@ -59,6 +59,7 @@ export const ManageMembers = ({ const [infoSnack, setInfoSnack] = React.useState(null); const [isLoadingMembers, setIsLoadingMembers] = React.useState(false) const [isLoadingLeave, setIsLoadingLeave] = React.useState(false) + const [groupInfo, setGroupInfo] = React.useState(null) const handleChange = (event: React.SyntheticEvent, newValue: number) => { setValue(newValue); }; @@ -68,6 +69,7 @@ export const ManageMembers = ({ setOpen(false); }; + const handleLeaveGroup = async () => { try { setIsLoadingLeave(true) @@ -130,10 +132,20 @@ export const ManageMembers = ({ setMembersWithNames(res?.members || []); } catch (error) {} }; + const getGroupInfo = async (groupId) => { + try { + const response = await fetch( + `${getBaseApiReact()}/groups/${groupId}` + ); + const groupData = await response.json(); + setGroupInfo(groupData) + } catch (error) {} + }; React.useEffect(()=> { if(selectedGroup?.groupId){ getMembers(selectedGroup?.groupId) + getGroupInfo(selectedGroup?.groupId) } }, [selectedGroup?.groupId]) @@ -248,14 +260,23 @@ export const ManageMembers = ({ />
- + + + GroupId: {groupInfo?.groupId} + GroupName: {groupInfo?.groupName} + Number of members: {groupInfo?.memberCount} + + {selectedGroup?.groupId && !isOwner && ( - Leave Group )} - + {value === 0 && ( { } break; } - + case "SHOW_ACTIONS" : { + try { + + event.source.postMessage({ + requestId: request.requestId, + action: request.action, + payload: listOfAllQortalRequests, + type: "backgroundMessageResponse", + }, event.origin); + } catch (error) { + event.source.postMessage({ + requestId: request.requestId, + action: request.action, + error: error?.message, + type: "backgroundMessageResponse", + }, event.origin); + } + break; + } default: break; } diff --git a/src/qortalRequests/get.ts b/src/qortalRequests/get.ts index bf4eee5..b569acd 100644 --- a/src/qortalRequests/get.ts +++ b/src/qortalRequests/get.ts @@ -427,7 +427,7 @@ export const encryptData = async (data, sender) => { }; export const encryptQortalGroupData = async (data, sender) => { - let data64 = data.data64; + let data64 = data?.data64 || data?.base64; let groupId = data?.groupId let isAdmins = data?.isAdmins if(!groupId){ @@ -462,7 +462,8 @@ export const encryptQortalGroupData = async (data, sender) => { url ); const resData = await res.text(); - const decryptedKey: any = await decryptResource(resData); + + const decryptedKey: any = await decryptResource(resData, true); const dataint8Array = base64ToUint8Array(decryptedKey.data); const decryptedKeyToObject = uint8ArrayToObject(dataint8Array); @@ -496,8 +497,7 @@ url url ); const resData = await res.text(); - const decryptedKey: any = await decryptResource(resData); - + const decryptedKey: any = await decryptResource(resData, true); const dataint8Array = base64ToUint8Array(decryptedKey.data); const decryptedKeyToObject = uint8ArrayToObject(dataint8Array); @@ -526,7 +526,7 @@ url }; export const decryptQortalGroupData = async (data, sender) => { - let data64 = data.data64; + let data64 = data?.data64 || data?.base64; let groupId = data?.groupId let isAdmins = data?.isAdmins if(!groupId){ @@ -557,7 +557,7 @@ export const decryptQortalGroupData = async (data, sender) => { url ); const resData = await res.text(); - const decryptedKey: any = await decryptResource(resData); + const decryptedKey: any = await decryptResource(resData, true); const dataint8Array = base64ToUint8Array(decryptedKey.data); const decryptedKeyToObject = uint8ArrayToObject(dataint8Array); @@ -588,7 +588,7 @@ url url ); const resData = await res.text(); - const decryptedKey: any = await decryptResource(resData); + const decryptedKey: any = await decryptResource(resData, true); const dataint8Array = base64ToUint8Array(decryptedKey.data); const decryptedKeyToObject = uint8ArrayToObject(dataint8Array); @@ -615,7 +615,7 @@ url }; export const encryptDataWithSharingKey = async (data, sender) => { - let data64 = data.data64; + let data64 = data?.data64 || data?.base64; let publicKeys = data.publicKeys || []; if (data?.file || data?.blob) { data64 = await fileToBase64(data?.file || data?.blob); @@ -663,7 +663,10 @@ export const decryptDataWithSharingKey = async (data, sender) => { }; export const getHostedData = async (data, isFromExtension) => { - + const isGateway = await isRunningGateway(); + if (isGateway) { + throw new Error("This action cannot be done through a gateway"); + } const resPermission = await getUserPermission( { text1: "Do you give this application permission to", @@ -675,18 +678,19 @@ export const getHostedData = async (data, isFromExtension) => { if(accepted){ const limit = data?.limit ? data?.limit : 20; - const query = data?.query ? data?.query : undefined + const query = data?.query ? data?.query : "" const offset = data?.offset ? data?.offset : 0 - try { - - const url = await createEndpoint(`/arbitrary/hosted/resources/?limit=${limit}&query=${query}&offset=${offset}`); - const response = await fetch(url); - const data = await response.json(); - return data - } catch (error) { - throw error + let urlPath = `/arbitrary/hosted/resources/?limit=${limit}&offset=${offset}` + if(query){ + urlPath = urlPath + `&query=${query}` } + + const url = await createEndpoint(urlPath); + const response = await fetch(url); + const dataResponse = await response.json(); + return dataResponse + } else { throw new Error("User declined to get list of hosted resources"); @@ -695,6 +699,10 @@ export const getHostedData = async (data, isFromExtension) => { }; export const deleteHostedData = async (data, isFromExtension) => { + const isGateway = await isRunningGateway(); + if (isGateway) { + throw new Error("This action cannot be done through a gateway"); + } const requiredFields = ["hostedData"]; const missingFields: string[] = []; requiredFields.forEach((field) => { @@ -716,7 +724,7 @@ export const deleteHostedData = async (data, isFromExtension) => { for (const hostedDataItem of hostedData){ try { - const url = await createEndpoint(`/arbitrary/resource/${hostedDataItem.service}/${hostedDataItem.name}/${hostedDataItem.identifer}`); + const url = await createEndpoint(`/arbitrary/resource/${hostedDataItem.service}/${hostedDataItem.name}/${hostedDataItem.identifier}`); await fetch(url, { method: "DELETE", headers: { @@ -971,7 +979,7 @@ export const publishQDNResource = async ( if (!data.file && !data.data64 && !data.base64) { throw new Error("No data or file was submitted"); } - // Use "default" if user hasn't specified an identifer + // Use "default" if user hasn't specified an identifier const service = data.service; const appFee = data?.appFee ? +data.appFee : undefined const appFeeRecipient = data?.appFeeRecipient