diff --git a/src/App.tsx b/src/App.tsx index f190e0a..de83cfe 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -134,6 +134,7 @@ import BoundedNumericTextField from "./common/BoundedNumericTextField"; import { useHandleUserInfo } from "./components/Group/useHandleUserInfo"; import { Minting } from "./components/Minting/Minting"; import { isRunningGateway } from "./qortalRequests"; +import { GlobalActions } from "./components/GlobalActions/GlobalActions"; type extStates = @@ -1817,6 +1818,7 @@ function App() { > + )} {isOpenSendQort && isMainWindow && ( diff --git a/src/components/Chat/MessageDisplay.tsx b/src/components/Chat/MessageDisplay.tsx index 3418eaf..62529c7 100644 --- a/src/components/Chat/MessageDisplay.tsx +++ b/src/components/Chat/MessageDisplay.tsx @@ -97,6 +97,27 @@ export const MessageDisplay = ({ htmlContent, isReply, setMobileViewModeKeepOpen window.open(href, '_system'); } else if (target.getAttribute('data-url')) { const url = target.getAttribute('data-url'); + let copyUrl = url + + try { + copyUrl = copyUrl.replace(/^(qortal:\/\/)/, '') + if (copyUrl.startsWith('use-')) { + // Handle the new 'use' format + const parts = copyUrl.split('/') + const type = parts[0].split('-')[1] // e.g., 'group' from 'use-group' + parts.shift() + const action = parts.length > 0 ? parts[0].split('-')[1] : null // e.g., 'invite' from 'action-invite' + parts.shift() + const idPrefix = parts.length > 0 ? parts[0].split('-')[0] : null // e.g., 'groupid' from 'groupid-321' + const id = parts.length > 0 ? parts[0].split('-')[1] : null // e.g., '321' from 'groupid-321' + if(action === 'join'){ + executeEvent("globalActionJoinGroup", { groupId: id}); + return + } + } + } catch (error) { + //error + } const res = extractComponents(url); if (res) { const { service, name, identifier, path } = res; diff --git a/src/components/Embeds/VideoPlayer.tsx b/src/components/Embeds/VideoPlayer.tsx index 9f9c84d..9138fcf 100644 --- a/src/components/Embeds/VideoPlayer.tsx +++ b/src/components/Embeds/VideoPlayer.tsx @@ -541,7 +541,7 @@ export const VideoPlayer: React.FC = ({ id={identifier} ref={videoRef} src={!startPlay ? '' : resourceStatus?.status === 'READY' ? src : ''} - poster="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" + poster={poster ? poster : "data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="} onTimeUpdate={updateProgress} autoPlay={autoplay} onClick={togglePlay} diff --git a/src/components/GlobalActions/GlobalActions.tsx b/src/components/GlobalActions/GlobalActions.tsx new file mode 100644 index 0000000..6dd845c --- /dev/null +++ b/src/components/GlobalActions/GlobalActions.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import { JoinGroup } from './JoinGroup' + +export const GlobalActions = ({memberGroups}) => { + return ( + <> + + + ) +} diff --git a/src/components/GlobalActions/JoinGroup.tsx b/src/components/GlobalActions/JoinGroup.tsx new file mode 100644 index 0000000..65da592 --- /dev/null +++ b/src/components/GlobalActions/JoinGroup.tsx @@ -0,0 +1,286 @@ +import React, { useContext, useEffect, useMemo, useState } from "react"; +import { subscribeToEvent, unsubscribeFromEvent } from "../../utils/events"; +import { + Box, + Button, + ButtonBase, + CircularProgress, + Dialog, + DialogActions, + DialogContent, + Typography, +} from "@mui/material"; +import { CustomButton, CustomButtonAccept } from "../../App-styles"; +import { getBaseApiReact, MyContext } from "../../App"; +import { getFee } from "../../background"; +import { CustomizedSnackbars } from "../Snackbar/Snackbar"; +import { FidgetSpinner } from "react-loader-spinner"; + +export const JoinGroup = ({ memberGroups }) => { + const { show, setTxList } = useContext(MyContext); + const [openSnack, setOpenSnack] = useState(false); + const [infoSnack, setInfoSnack] = useState(null); + const [groupInfo, setGroupInfo] = useState(null); + const [isLoadingInfo, setIsLoadingInfo] = useState(false); + const [isOpen, setIsOpen] = useState(false); + const [isLoadingJoinGroup, setIsLoadingJoinGroup] = useState(false); + const handleJoinGroup = async (e) => { + setGroupInfo(null); + const groupId = e?.detail?.groupId; + if (groupId) { + try { + setIsOpen(true); + setIsLoadingInfo(true); + const response = await fetch(`${getBaseApiReact()}/groups/${groupId}`); + const groupData = await response.json(); + setGroupInfo(groupData); + } catch (error) { + } finally { + setIsLoadingInfo(false); + } + } + }; + + useEffect(() => { + subscribeToEvent("globalActionJoinGroup", handleJoinGroup); + + return () => { + unsubscribeFromEvent("globalActionJoinGroup", handleJoinGroup); + }; + }, []); + + const isInGroup = useMemo(()=> { + return !!memberGroups.find((item)=> +item?.groupId === +groupInfo?.groupId) + }, [memberGroups, groupInfo]) + const joinGroup = async (group, isOpen) => { + try { + const groupId = group.groupId; + const fee = await getFee("JOIN_GROUP"); + await show({ + message: "Would you like to perform an JOIN_GROUP transaction?", + publishFee: fee.fee + " QORT", + }); + setIsLoadingJoinGroup(true); + await new Promise((res, rej) => { + window + .sendMessage("joinGroup", { + groupId, + }) + .then((response) => { + if (!response?.error) { + setInfoSnack({ + type: "success", + message: + "Successfully requested to join group. It may take a couple of minutes for the changes to propagate", + }); + + if (isOpen) { + setTxList((prev) => [ + { + ...response, + type: "joined-group", + label: `Joined Group ${group?.groupName}: awaiting confirmation`, + labelDone: `Joined Group ${group?.groupName}: success!`, + done: false, + groupId, + }, + ...prev, + ]); + } else { + setTxList((prev) => [ + { + ...response, + type: "joined-group-request", + label: `Requested to join Group ${group?.groupName}: awaiting confirmation`, + labelDone: `Requested to join Group ${group?.groupName}: success!`, + done: false, + groupId, + }, + ...prev, + ]); + } + + setOpenSnack(true); + res(response); + return; + } else { + 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); + }); + }); + setIsLoadingJoinGroup(false); + } catch (error) { + } finally { + setIsLoadingJoinGroup(false); + } + }; + return ( + <> + + + {!groupInfo && ( + + {" "} + {" "} + + )} + + + Group name: {` ${groupInfo?.groupName}`} + + + Number of members: {` ${groupInfo?.memberCount}`} + + {groupInfo?.description && ( + + {groupInfo?.description} + + )} + {isInGroup && ( + + *You are already in this group! + + )} + {!isInGroup && groupInfo?.isOpen === false && ( + + *This is a closed/private group, so you will need to wait until + an admin accepts your request + + )} + + + + { + joinGroup(groupInfo, groupInfo?.isOpen); + + setIsOpen(false); + }} disabled={isInGroup}> + + Join + + + + setIsOpen(false)} + > + Close + + + + + + {isLoadingJoinGroup && ( + + + + )} + + ); +}; diff --git a/src/components/Tutorials/img/creation.webp b/src/components/Tutorials/img/creation.webp new file mode 100644 index 0000000..3747645 Binary files /dev/null and b/src/components/Tutorials/img/creation.webp differ diff --git a/src/components/Tutorials/img/dashboard.webp b/src/components/Tutorials/img/dashboard.webp new file mode 100644 index 0000000..d120287 Binary files /dev/null and b/src/components/Tutorials/img/dashboard.webp differ diff --git a/src/components/Tutorials/img/groups.webp b/src/components/Tutorials/img/groups.webp new file mode 100644 index 0000000..329336b Binary files /dev/null and b/src/components/Tutorials/img/groups.webp differ diff --git a/src/components/Tutorials/img/important.webp b/src/components/Tutorials/img/important.webp new file mode 100644 index 0000000..82680b5 Binary files /dev/null and b/src/components/Tutorials/img/important.webp differ diff --git a/src/components/Tutorials/img/navigation.webp b/src/components/Tutorials/img/navigation.webp new file mode 100644 index 0000000..8b6d67b Binary files /dev/null and b/src/components/Tutorials/img/navigation.webp differ diff --git a/src/components/Tutorials/img/overview.webp b/src/components/Tutorials/img/overview.webp new file mode 100644 index 0000000..5198fe1 Binary files /dev/null and b/src/components/Tutorials/img/overview.webp differ diff --git a/src/components/Tutorials/img/started.webp b/src/components/Tutorials/img/started.webp new file mode 100644 index 0000000..7162518 Binary files /dev/null and b/src/components/Tutorials/img/started.webp differ diff --git a/src/components/Tutorials/useHandleTutorials.tsx b/src/components/Tutorials/useHandleTutorials.tsx index dc2f19b..1e5fe79 100644 --- a/src/components/Tutorials/useHandleTutorials.tsx +++ b/src/components/Tutorials/useHandleTutorials.tsx @@ -2,6 +2,13 @@ import React, { useCallback, useEffect, useState } from "react"; import { saveToLocalStorage } from "../Apps/AppsNavBar"; import { getData, storeData } from "../../utils/chromeStorage"; +import creationImg from './img/creation.webp' +import dashboardImg from './img/dashboard.webp' +import groupsImg from './img/groups.webp' +import importantImg from './img/important.webp' +import navigationImg from './img/navigation.webp' +import overviewImg from './img/overview.webp' +import startedImg from './img/started.webp' const checkIfGatewayIsOnline = async () => { try { @@ -80,6 +87,7 @@ useEffect(()=> { name: "a-test", service: "VIDEO", identifier: "account-creation-go", + poster: creationImg }, }); } @@ -95,6 +103,7 @@ useEffect(()=> { name: "a-test", service: "VIDEO", identifier: "important-information-go", + poster: importantImg }, }); } @@ -114,6 +123,7 @@ useEffect(()=> { name: "a-test", service: "VIDEO", identifier: "getting-started-go", + poster: startedImg }, }, { @@ -122,6 +132,7 @@ useEffect(()=> { name: "a-test", service: "VIDEO", identifier: "overview-go", + poster: overviewImg }, }, { @@ -130,6 +141,7 @@ useEffect(()=> { name: "a-test", service: "VIDEO", identifier: "groups-go", + poster: groupsImg }, }, ], @@ -149,6 +161,7 @@ useEffect(()=> { name: "a-test", service: "VIDEO", identifier: "apps-dashboard-go", + poster: dashboardImg }, }, { @@ -157,6 +170,7 @@ useEffect(()=> { name: "a-test", service: "VIDEO", identifier: "apps-navigation-go", + poster: navigationImg }, } diff --git a/src/components/WrapperUserAction.tsx b/src/components/WrapperUserAction.tsx index aa0d19a..8758c99 100644 --- a/src/components/WrapperUserAction.tsx +++ b/src/components/WrapperUserAction.tsx @@ -80,7 +80,8 @@ export const WrapperUserAction = ({ children, address, name, disabled }) => { }, 200); }} sx={{ - color: 'white' + color: 'white', + justifyContent: 'flex-start' }} > Message @@ -98,11 +99,26 @@ export const WrapperUserAction = ({ children, address, name, disabled }) => { }} sx={{ - color: 'white' + color: 'white', + justifyContent: 'flex-start' }} > Send QORT +