catch error with console

This commit is contained in:
Nicola Benaglia 2025-04-19 15:30:56 +02:00
parent f9f9895cb1
commit cf335a6d0a
24 changed files with 2670 additions and 2344 deletions

View File

@ -1,13 +1,20 @@
import React from 'react';
import { useTheme } from '@mui/material';
export const ExitIcon= ({ color = 'white', height, width }) => {
return (
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 0L0 2L4 6L0 10L2 12L6 8L10 12L12 10L8 6L12 2L10 0L6 4L2 0Z" fill={color}/>
</svg>
export const ExitIcon = () => {
const theme = useTheme();
);
};
return (
<svg
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2 0L0 2L4 6L0 10L2 12L6 8L10 12L12 10L8 6L12 2L10 0L6 4L2 0Z"
fill={theme.palette.text.primary}
/>
</svg>
);
};

View File

@ -1,14 +1,20 @@
import React from 'react';
import { useTheme } from '@mui/material';
export const ReturnIcon= ({ color = 'white', height, width }) => {
return (
<svg width="20" height="13" viewBox="0 0 20 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.0937 3.73451H3.6243L5.84808 1.67047C6.04153 1.48456 6.14857 1.23558 6.14615 0.97713C6.14373 0.718684 6.03205 0.471459 5.83515 0.288703C5.63825 0.105948 5.37189 0.00228309 5.09344 3.72619e-05C4.815 -0.00220856 4.54674 0.0971438 4.34645 0.276696L0.310933 4.02233C0.111843 4.20718 0 4.45785 0 4.71922C0 4.98059 0.111843 5.23126 0.310933 5.41611L4.34645 9.16175C4.54674 9.3413 4.815 9.44065 5.09344 9.43841C5.37189 9.43616 5.63825 9.33249 5.83515 9.14974C6.03205 8.96698 6.14373 8.71976 6.14615 8.46131C6.14857 8.20287 6.04153 7.95388 5.84808 7.76797L3.6243 5.7059H15.0937C15.8316 5.7059 16.5393 5.97799 17.0611 6.4623C17.5829 6.94662 17.876 7.60349 17.876 8.28842C17.876 8.97335 17.5829 9.63022 17.0611 10.1145C16.5393 10.5989 15.8316 10.8709 15.0937 10.8709V12.8423C16.3949 12.8423 17.6429 12.3625 18.563 11.5085C19.4831 10.6545 20 9.49619 20 8.28842C20 7.08065 19.4831 5.92234 18.563 5.06832C17.6429 4.2143 16.3949 3.73451 15.0937 3.73451Z" fill={color}/>
</svg>
export const ReturnIcon = () => {
const theme = useTheme();
);
};
return (
<svg
fill="none"
height="13"
viewBox="0 0 20 13"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M15.0937 3.73451H3.6243L5.84808 1.67047C6.04153 1.48456 6.14857 1.23558 6.14615 0.97713C6.14373 0.718684 6.03205 0.471459 5.83515 0.288703C5.63825 0.105948 5.37189 0.00228309 5.09344 3.72619e-05C4.815 -0.00220856 4.54674 0.0971438 4.34645 0.276696L0.310933 4.02233C0.111843 4.20718 0 4.45785 0 4.71922C0 4.98059 0.111843 5.23126 0.310933 5.41611L4.34645 9.16175C4.54674 9.3413 4.815 9.44065 5.09344 9.43841C5.37189 9.43616 5.63825 9.33249 5.83515 9.14974C6.03205 8.96698 6.14373 8.71976 6.14615 8.46131C6.14857 8.20287 6.04153 7.95388 5.84808 7.76797L3.6243 5.7059H15.0937C15.8316 5.7059 16.5393 5.97799 17.0611 6.4623C17.5829 6.94662 17.876 7.60349 17.876 8.28842C17.876 8.97335 17.5829 9.63022 17.0611 10.1145C16.5393 10.5989 15.8316 10.8709 15.0937 10.8709V12.8423C16.3949 12.8423 17.6429 12.3625 18.563 11.5085C19.4831 10.6545 20 9.49619 20 8.28842C20 7.08065 19.4831 5.92234 18.563 5.06832C17.6429 4.2143 16.3949 3.73451 15.0937 3.73451Z"
fill={theme.palette.text.primary}
/>
</svg>
);
};

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
import React, { useContext, useMemo, useState } from "react";
import React, { useContext, useMemo, useState } from 'react';
import {
AppCircle,
AppCircleContainer,
@ -6,8 +6,8 @@ import {
AppLibrarySubTitle,
AppsContainer,
AppsParent,
} from "./Apps-styles";
import { Buffer } from "buffer";
} from './Apps-styles';
import { Buffer } from 'buffer';
import {
Avatar,
@ -20,17 +20,17 @@ import {
DialogContentText,
DialogTitle,
Input,
} from "@mui/material";
import { Add } from "@mui/icons-material";
import { MyContext, getBaseApiReact, isMobile } from "../../App";
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
import { executeEvent } from "../../utils/events";
import { Spacer } from "../../common/Spacer";
import { useModal } from "../../common/useModal";
import { createEndpoint, isUsingLocal } from "../../background";
import { Label } from "../Group/AddGroup";
import ShortUniqueId from "short-unique-id";
import swaggerSVG from '../../assets/svgs/swagger.svg'
} from '@mui/material';
import { Add } from '@mui/icons-material';
import { MyContext, getBaseApiReact, isMobile } from '../../App';
import LogoSelected from '../../assets/svgs/LogoSelected.svg';
import { executeEvent } from '../../utils/events';
import { Spacer } from '../../common/Spacer';
import { useModal } from '../../common/useModal';
import { createEndpoint, isUsingLocal } from '../../background';
import { Label } from '../Group/AddGroup';
import ShortUniqueId from 'short-unique-id';
import swaggerSVG from '../../assets/svgs/swagger.svg';
const uid = new ShortUniqueId({ length: 8 });
export const AppsDevModeHome = ({
@ -40,8 +40,8 @@ export const AppsDevModeHome = ({
availableQapps,
myName,
}) => {
const [domain, setDomain] = useState("127.0.0.1");
const [port, setPort] = useState("");
const [domain, setDomain] = useState('127.0.0.1');
const [port, setPort] = useState('');
const [selectedPreviewFile, setSelectedPreviewFile] = useState(null);
const { isShow, onCancel, onOk, show, message } = useModal();
@ -58,7 +58,7 @@ export const AppsDevModeHome = ({
const content = await window.electron.readFile(filePath);
return { buffer: content, filePath };
} else {
console.log("No file selected.");
console.log('No file selected.');
}
};
const handleSelectDirectry = async (existingDirectoryPath) => {
@ -67,7 +67,7 @@ export const AppsDevModeHome = ({
if (buffer) {
return { buffer, directoryPath };
} else {
console.log("No file selected.");
console.log('No file selected.');
}
};
@ -78,34 +78,36 @@ export const AppsDevModeHome = ({
setOpenSnackGlobal(true);
setInfoSnackCustom({
type: "error",
type: 'error',
message:
"Please use your local node for dev mode! Logout and use Local node.",
'Please use your local node for dev mode! Logout and use Local node.',
});
return;
}
const { portVal, domainVal } = await show({
message: "",
publishFee: "",
message: '',
publishFee: '',
});
const framework = domainVal + ":" + portVal;
const framework = domainVal + ':' + portVal;
const response = await fetch(
`${getBaseApiReact()}/developer/proxy/start`,
{
method: "POST",
method: 'POST',
headers: {
"Content-Type": "text/plain",
'Content-Type': 'text/plain',
},
body: framework,
}
);
const responseData = await response.text();
executeEvent("appsDevModeAddTab", {
executeEvent('appsDevModeAddTab', {
data: {
url: "http://127.0.0.1:" + responseData,
url: 'http://127.0.0.1:' + responseData,
},
});
} catch (error) {}
} catch (error) {
console.log(error);
}
};
const addPreviewApp = async (isRefresh, existingFilePath, tabId) => {
@ -115,9 +117,9 @@ export const AppsDevModeHome = ({
setOpenSnackGlobal(true);
setInfoSnackCustom({
type: "error",
type: 'error',
message:
"Please use your local node for dev mode! Logout and use Local node.",
'Please use your local node for dev mode! Logout and use Local node.',
});
return;
}
@ -125,8 +127,8 @@ export const AppsDevModeHome = ({
setOpenSnackGlobal(true);
setInfoSnackCustom({
type: "error",
message: "You need a name to use preview",
type: 'error',
message: 'You need a name to use preview',
});
return;
}
@ -137,29 +139,29 @@ export const AppsDevModeHome = ({
setOpenSnackGlobal(true);
setInfoSnackCustom({
type: "error",
message: "Please select a file",
type: 'error',
message: 'Please select a file',
});
return;
}
const postBody = Buffer.from(buffer).toString("base64");
const postBody = Buffer.from(buffer).toString('base64');
const endpoint = await createEndpoint(
`/arbitrary/APP/${myName}/zip?preview=true`
);
const response = await fetch(endpoint, {
method: "POST",
method: 'POST',
headers: {
"Content-Type": "text/plain",
'Content-Type': 'text/plain',
},
body: postBody,
});
if (!response?.ok) throw new Error("Invalid zip");
if (!response?.ok) throw new Error('Invalid zip');
const previewPath = await response.text();
if (tabId) {
executeEvent("appsDevModeUpdateTab", {
executeEvent('appsDevModeUpdateTab', {
data: {
url: "http://127.0.0.1:12391" + previewPath,
url: 'http://127.0.0.1:12391' + previewPath,
isPreview: true,
filePath,
refreshFunc: (tabId) => {
@ -170,9 +172,9 @@ export const AppsDevModeHome = ({
});
return;
}
executeEvent("appsDevModeAddTab", {
executeEvent('appsDevModeAddTab', {
data: {
url: "http://127.0.0.1:12391" + previewPath,
url: 'http://127.0.0.1:12391' + previewPath,
isPreview: true,
filePath,
refreshFunc: (tabId) => {
@ -192,9 +194,9 @@ export const AppsDevModeHome = ({
setOpenSnackGlobal(true);
setInfoSnackCustom({
type: "error",
type: 'error',
message:
"Please use your local node for dev mode! Logout and use Local node.",
'Please use your local node for dev mode! Logout and use Local node.',
});
return;
}
@ -202,8 +204,8 @@ export const AppsDevModeHome = ({
setOpenSnackGlobal(true);
setInfoSnackCustom({
type: "error",
message: "You need a name to use preview",
type: 'error',
message: 'You need a name to use preview',
});
return;
}
@ -214,29 +216,29 @@ export const AppsDevModeHome = ({
setOpenSnackGlobal(true);
setInfoSnackCustom({
type: "error",
message: "Please select a file",
type: 'error',
message: 'Please select a file',
});
return;
}
const postBody = Buffer.from(buffer).toString("base64");
const postBody = Buffer.from(buffer).toString('base64');
const endpoint = await createEndpoint(
`/arbitrary/APP/${myName}/zip?preview=true`
);
const response = await fetch(endpoint, {
method: "POST",
method: 'POST',
headers: {
"Content-Type": "text/plain",
'Content-Type': 'text/plain',
},
body: postBody,
});
if (!response?.ok) throw new Error("Invalid zip");
if (!response?.ok) throw new Error('Invalid zip');
const previewPath = await response.text();
if (tabId) {
executeEvent("appsDevModeUpdateTab", {
executeEvent('appsDevModeUpdateTab', {
data: {
url: "http://127.0.0.1:12391" + previewPath,
url: 'http://127.0.0.1:12391' + previewPath,
isPreview: true,
directoryPath,
refreshFunc: (tabId) => {
@ -247,9 +249,9 @@ export const AppsDevModeHome = ({
});
return;
}
executeEvent("appsDevModeAddTab", {
executeEvent('appsDevModeAddTab', {
data: {
url: "http://127.0.0.1:12391" + previewPath,
url: 'http://127.0.0.1:12391' + previewPath,
isPreview: true,
directoryPath,
refreshFunc: (tabId) => {
@ -266,12 +268,12 @@ export const AppsDevModeHome = ({
<>
<AppsContainer
sx={{
justifyContent: "flex-start",
justifyContent: 'flex-start',
}}
>
<AppLibrarySubTitle
sx={{
fontSize: "30px",
fontSize: '30px',
}}
>
Dev Mode Apps
@ -280,8 +282,8 @@ export const AppsDevModeHome = ({
<Spacer height="45px" />
<AppsContainer
sx={{
gap: "75px",
justifyContent: "flex-start",
gap: '75px',
justifyContent: 'flex-start',
}}
>
<ButtonBase
@ -291,7 +293,7 @@ export const AppsDevModeHome = ({
>
<AppCircleContainer
sx={{
gap: !isMobile ? "10px" : "5px",
gap: !isMobile ? '10px' : '5px',
}}
>
<AppCircle>
@ -307,7 +309,7 @@ export const AppsDevModeHome = ({
>
<AppCircleContainer
sx={{
gap: !isMobile ? "10px" : "5px",
gap: !isMobile ? '10px' : '5px',
}}
>
<AppCircle>
@ -323,7 +325,7 @@ export const AppsDevModeHome = ({
>
<AppCircleContainer
sx={{
gap: !isMobile ? "10px" : "5px",
gap: !isMobile ? '10px' : '5px',
}}
>
<AppCircle>
@ -334,10 +336,10 @@ export const AppsDevModeHome = ({
</ButtonBase>
<ButtonBase
onClick={() => {
executeEvent("appsDevModeAddTab", {
executeEvent('appsDevModeAddTab', {
data: {
service: "APP",
name: "Q-Sandbox",
service: 'APP',
name: 'Q-Sandbox',
tabId: uid.rnd(),
},
});
@ -345,16 +347,16 @@ export const AppsDevModeHome = ({
>
<AppCircleContainer
sx={{
gap: !isMobile ? "10px" : "5px",
gap: !isMobile ? '10px' : '5px',
}}
>
<AppCircle>
<Avatar
sx={{
height: "42px",
width: "42px",
"& img": {
objectFit: "fill",
height: '42px',
width: '42px',
'& img': {
objectFit: 'fill',
},
}}
alt="Q-Sandbox"
@ -362,8 +364,8 @@ export const AppsDevModeHome = ({
>
<img
style={{
width: "31px",
height: "auto",
width: '31px',
height: 'auto',
}}
alt="center-icon"
/>
@ -374,27 +376,27 @@ export const AppsDevModeHome = ({
</ButtonBase>
<ButtonBase
onClick={() => {
executeEvent("appsDevModeAddTab", {
executeEvent('appsDevModeAddTab', {
data: {
url: "http://127.0.0.1:12391",
url: 'http://127.0.0.1:12391',
isPreview: false,
customIcon: swaggerSVG
customIcon: swaggerSVG,
},
});
}}
>
<AppCircleContainer
sx={{
gap: !isMobile ? "10px" : "5px",
gap: !isMobile ? '10px' : '5px',
}}
>
<AppCircle>
<Avatar
sx={{
height: "42px",
width: "42px",
"& img": {
objectFit: "fill",
height: '42px',
width: '42px',
'& img': {
objectFit: 'fill',
},
}}
alt="API"
@ -402,8 +404,8 @@ export const AppsDevModeHome = ({
>
<img
style={{
width: "31px",
height: "auto",
width: '31px',
height: 'auto',
}}
alt="center-icon"
/>
@ -419,20 +421,20 @@ export const AppsDevModeHome = ({
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
onKeyDown={(e) => {
if (e.key === "Enter" && domain && port) {
if (e.key === 'Enter' && domain && port) {
onOk({ portVal: port, domainVal: domain });
}
}}
>
<DialogTitle id="alert-dialog-title">
{"Add custom framework"}
{'Add custom framework'}
</DialogTitle>
<DialogContent>
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: "5px",
display: 'flex',
flexDirection: 'column',
gap: '5px',
}}
>
<Label>Domain</Label>
@ -444,10 +446,10 @@ export const AppsDevModeHome = ({
</Box>
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: "5px",
marginTop: "15px",
display: 'flex',
flexDirection: 'column',
gap: '5px',
marginTop: '15px',
}}
>
<Label>Port</Label>

View File

@ -1,21 +1,7 @@
import React, {
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { GroupMail } from "../Group/Forum/GroupMail";
import { MyContext, isMobile } from "../../App";
import { getRootHeight } from "../../utils/mobile/mobileUtils";
import { Box, Typography } from "@mui/material";
import { AdminSpaceInner } from "./AdminSpaceInner";
import { useContext, useEffect, useState } from 'react';
import { MyContext, isMobile } from '../../App';
import { Box, Typography } from '@mui/material';
import { AdminSpaceInner } from './AdminSpaceInner';
export const AdminSpace = ({
selectedGroup,
@ -26,11 +12,11 @@ export const AdminSpace = ({
isAdmin,
myAddress,
hide,
defaultThread,
defaultThread,
setDefaultThread,
setIsForceShowCreationKeyPopup
setIsForceShowCreationKeyPopup,
}) => {
const { rootHeight } = useContext(MyContext);
const { rootHeight } = useContext(MyContext);
const [isMoved, setIsMoved] = useState(false);
useEffect(() => {
if (hide) {
@ -42,26 +28,37 @@ export const AdminSpace = ({
return (
<div
style={{
// reference to change height
height: isMobile ? `calc(${rootHeight} - 127px` : "calc(100vh - 70px)",
display: "flex",
flexDirection: "column",
width: "100%",
opacity: hide ? 0 : 1,
visibility: hide && 'hidden',
position: hide ? 'fixed' : 'relative',
left: hide && '-1000px'
}}
>
{!isAdmin && <Box sx={{
width: '100%',
display: 'flex',
justifyContent: 'center',
paddingTop: '25px'
}}><Typography>Sorry, this space is only for Admins.</Typography></Box>}
{isAdmin && <AdminSpaceInner setIsForceShowCreationKeyPopup={setIsForceShowCreationKeyPopup} adminsWithNames={adminsWithNames} selectedGroup={selectedGroup} />}
</div>
style={{
// reference to change height
display: 'flex',
flexDirection: 'column',
height: isMobile ? `calc(${rootHeight} - 127px` : 'calc(100vh - 70px)',
left: hide && '-1000px',
opacity: hide ? 0 : 1,
position: hide ? 'fixed' : 'relative',
visibility: hide && 'hidden',
width: '100%',
}}
>
{!isAdmin && (
<Box
sx={{
display: 'flex',
justifyContent: 'center',
paddingTop: '25px',
width: '100%',
}}
>
<Typography>Sorry, this space is only for Admins.</Typography>
</Box>
)}
{isAdmin && (
<AdminSpaceInner
setIsForceShowCreationKeyPopup={setIsForceShowCreationKeyPopup}
adminsWithNames={adminsWithNames}
selectedGroup={selectedGroup}
/>
)}
</div>
);
};

View File

@ -1,39 +1,42 @@
import React, { useCallback, useContext, useEffect, useState } from "react";
import { useCallback, useContext, useEffect, useState } from 'react';
import {
MyContext,
getArbitraryEndpointReact,
getBaseApiReact,
} from "../../App";
import { Box, Button, Typography } from "@mui/material";
} from '../../App';
import { Box, Button, Typography } from '@mui/material';
import {
decryptResource,
getPublishesFromAdmins,
validateSecretKey,
} from "../Group/Group";
import { getFee } from "../../background";
import { base64ToUint8Array } from "../../qdn/encryption/group-encryption";
import { uint8ArrayToObject } from "../../backgroundFunctions/encryption";
import { formatTimestampForum } from "../../utils/time";
import { Spacer } from "../../common/Spacer";
} from '../Group/Group';
import { getFee } from '../../background';
import { base64ToUint8Array } from '../../qdn/encryption/group-encryption';
import { uint8ArrayToObject } from '../../backgroundFunctions/encryption';
import { formatTimestampForum } from '../../utils/time';
import { Spacer } from '../../common/Spacer';
export const getPublishesFromAdminsAdminSpace = async (
admins: string[],
groupId
) => {
const queryString = admins.map((name) => `name=${name}`).join("&");
const queryString = admins.map((name) => `name=${name}`).join('&');
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT_PRIVATE&identifier=admins-symmetric-qchat-group-${groupId}&exactmatchnames=true&limit=0&reverse=true&${queryString}&prefix=true`;
const response = await fetch(url);
if (!response.ok) {
throw new Error("network error");
throw new Error('network error');
}
const adminData = await response.json();
const filterId = adminData.filter(
(data: any) => data.identifier === `admins-symmetric-qchat-group-${groupId}`
);
if (filterId?.length === 0) {
return false;
}
const sortedData = filterId.sort((a: any, b: any) => {
// Get the most recent date for both a and b
const dateA = a.updated ? new Date(a.updated) : new Date(a.created);
@ -87,10 +90,11 @@ export const AdminSpaceInner = ({
const dataint8Array = base64ToUint8Array(decryptedKey.data);
const decryptedKeyToObject = uint8ArrayToObject(dataint8Array);
if (!validateSecretKey(decryptedKeyToObject))
throw new Error("SecretKey is not valid");
throw new Error('SecretKey is not valid');
setAdminGroupSecretKey(decryptedKeyToObject);
setAdminGroupSecretKeyPublishDetails(getLatestPublish);
} catch (error) {
console.log(error);
} finally {
setIsFetchingAdminGroupSecretKey(false);
}
@ -106,6 +110,7 @@ export const AdminSpaceInner = ({
if (getLatestPublish === false) setGroupSecretKeyPublishDetails(false);
setGroupSecretKeyPublishDetails(getLatestPublish);
} catch (error) {
console.log(error);
} finally {
setIsFetchingGroupSecretKey(false);
}
@ -113,15 +118,17 @@ export const AdminSpaceInner = ({
const createCommonSecretForAdmins = async () => {
try {
const fee = await getFee("ARBITRARY");
const fee = await getFee('ARBITRARY');
await show({
message: "Would you like to perform an ARBITRARY transaction?",
publishFee: fee.fee + " QORT",
message: 'Would you like to perform an ARBITRARY transaction?',
publishFee: fee.fee + ' QORT',
});
setIsLoadingPublishKey(true);
window
.sendMessage("encryptAndPublishSymmetricKeyGroupChatForAdmins", {
.sendMessage('encryptAndPublishSymmetricKeyGroupChatForAdmins', {
groupId: selectedGroup,
previousData: adminGroupSecretKey,
admins: adminsWithNames,
@ -129,27 +136,29 @@ export const AdminSpaceInner = ({
.then((response) => {
if (!response?.error) {
setInfoSnackCustom({
type: "success",
type: 'success',
message:
"Successfully re-encrypted secret key. It may take a couple of minutes for the changes to propagate. Refresh the group in 5 mins.",
'Successfully re-encrypted secret key. It may take a couple of minutes for the changes to propagate. Refresh the group in 5 mins.',
});
setOpenSnackGlobal(true);
return;
}
setInfoSnackCustom({
type: "error",
message: response?.error || "unable to re-encrypt secret key",
type: 'error',
message: response?.error || 'unable to re-encrypt secret key',
});
setOpenSnackGlobal(true);
})
.catch((error) => {
setInfoSnackCustom({
type: "error",
message: error?.message || "unable to re-encrypt secret key",
type: 'error',
message: error?.message || 'unable to re-encrypt secret key',
});
setOpenSnackGlobal(true);
});
} catch (error) {}
} catch (error) {
console.log(error);
}
};
useEffect(() => {
@ -159,27 +168,32 @@ export const AdminSpaceInner = ({
return (
<Box
sx={{
width: "100%",
display: "flex",
flexDirection: "column",
padding: "10px",
alignItems: 'center'
alignItems: 'center',
display: 'flex',
flexDirection: 'column',
padding: '10px',
width: '100%',
}}
>
<Typography sx={{
fontSize: '14px'
}}>Reminder: After publishing the key, it will take a couple of minutes for it to appear. Please just wait.</Typography>
<Typography
sx={{
fontSize: '14px',
}}
>
Reminder: After publishing the key, it will take a couple of minutes for
it to appear. Please just wait.
</Typography>
<Spacer height="25px" />
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: "20px",
width: "300px",
maxWidth: "90%",
padding: '10px',
border: '1px solid gray',
borderRadius: '6px'
borderRadius: '6px',
display: 'flex',
flexDirection: 'column',
gap: '20px',
maxWidth: '90%',
padding: '10px',
width: '300px',
}}
>
{isFetchingGroupSecretKey && (
@ -191,33 +205,47 @@ export const AdminSpaceInner = ({
)}
{groupSecretKeyPublishDetails && (
<Typography>
Last encryption date:{" "}
Last encryption date:{' '}
{formatTimestampForum(
groupSecretKeyPublishDetails?.updated ||
groupSecretKeyPublishDetails?.created
)}{" "}
)}{' '}
{` by ${groupSecretKeyPublishDetails?.name}`}
</Typography>
)}
<Button disabled={isFetchingGroupSecretKey} onClick={()=> setIsForceShowCreationKeyPopup(true)} variant="contained">
<Button
disabled={isFetchingGroupSecretKey}
onClick={() => setIsForceShowCreationKeyPopup(true)}
variant="contained"
>
Publish group secret key
</Button>
<Spacer height="20px" />
<Typography sx={{
fontSize: '14px'
}}>This key is to encrypt GROUP related content. This is the only one used in this UI as of now. All group members will be able to see content encrypted with this key.</Typography>
<Typography
sx={{
fontSize: '14px',
}}
>
This key is to encrypt GROUP related content. This is the only one
used in this UI as of now. All group members will be able to see
content encrypted with this key.
</Typography>
</Box>
<Spacer height="25px" />
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: "20px",
width: "300px",
maxWidth: "90%",
padding: '10px',
border: '1px solid gray',
borderRadius: '6px'
borderRadius: '6px',
display: 'flex',
flexDirection: 'column',
gap: '20px',
maxWidth: '90%',
padding: '10px',
width: '300px',
}}
>
{isFetchingAdminGroupSecretKey && (
@ -228,20 +256,31 @@ export const AdminSpaceInner = ({
)}
{adminGroupSecretKeyPublishDetails && (
<Typography>
Last encryption date:{" "}
Last encryption date:{' '}
{formatTimestampForum(
adminGroupSecretKeyPublishDetails?.updated ||
adminGroupSecretKeyPublishDetails?.created
)}
</Typography>
)}
<Button disabled={isFetchingAdminGroupSecretKey} onClick={createCommonSecretForAdmins} variant="contained">
<Button
disabled={isFetchingAdminGroupSecretKey}
onClick={createCommonSecretForAdmins}
variant="contained"
>
Publish admin secret key
</Button>
<Spacer height="20px" />
<Typography sx={{
fontSize: '14px'
}}>This key is to encrypt ADMIN related content. Only admins would see content encrypted with it.</Typography>
<Typography
sx={{
fontSize: '14px',
}}
>
This key is to encrypt ADMIN related content. Only admins would see
content encrypted with it.
</Typography>
</Box>
</Box>
);

View File

@ -83,7 +83,9 @@ export const AnnouncementDiscussion = ({
[`${identifier}-${name}`]: messageData,
};
});
} catch (error) {}
} catch (error) {
console.log(error);
}
};
const publishAnc = async ({ encryptedData, identifier }: any) => {
@ -107,7 +109,9 @@ export const AnnouncementDiscussion = ({
rej(error.message || 'An error occurred');
});
});
} catch (error) {}
} catch (error) {
console.log(error);
}
};
const setTempData = async () => {
@ -123,7 +127,9 @@ export const AnnouncementDiscussion = ({
});
setTempPublishedList(tempData);
}
} catch (error) {}
} catch (error) {
console.log(error);
}
};
const publishComment = async () => {
@ -235,7 +241,9 @@ export const AnnouncementDiscussion = ({
for (const data of responseData) {
getData({ name: data.name, identifier: data.identifier }, isPrivate);
}
} catch (error) {}
} catch (error) {
console.log(error);
}
};
const combinedListTempAndReal = useMemo(() => {

View File

@ -7,13 +7,12 @@ import React, {
useState,
} from 'react';
import { objectToBase64 } from '../../qdn/encryption/group-encryption';
import { ChatList } from './ChatList';
import '@chatscope/chat-ui-kit-styles/dist/default/styles.min.css';
import Tiptap from './TipTap';
import { CustomButton } from '../../styles/App-styles';
import CircularProgress from '@mui/material/CircularProgress';
import { Box, ButtonBase, Input, Typography } from '@mui/material';
import { Box, ButtonBase, Input, Typography, useTheme } from '@mui/material';
import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar';
import { getNameInfo } from '../Group/Group';
import { Spacer } from '../../common/Spacer';
@ -36,7 +35,7 @@ import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import ShortUniqueId from 'short-unique-id';
import { ReturnIcon } from '../../assets/Icons/ReturnIcon';
import { ExitIcon } from '../../assets/Icons/ExitIcon';
import { MessageItem, ReplyPreview } from './MessageItem';
import { ReplyPreview } from './MessageItem';
const uid = new ShortUniqueId({ length: 5 });
@ -52,6 +51,7 @@ export const ChatDirect = ({
close,
setMobileViewModeKeepOpen,
}) => {
const theme = useTheme();
const { queueChats, addToQueue, processWithNewMessages } = useMessageQueue();
const [isFocusedParent, setIsFocusedParent] = useState(false);
const [onEditMessage, setOnEditMessage] = useState(null);
@ -87,7 +87,9 @@ export const ChatDirect = ({
const publicKey = await getPublicKey(address);
if (publicKeyOfRecipientRef.current !== selectedDirect?.address) return;
setPublicKeyOfRecipient(publicKey);
} catch (error) {}
} catch (error) {
console.log(error);
}
};
const tempMessages = useMemo(() => {
@ -167,6 +169,7 @@ export const ChatDirect = ({
text: item.message,
unread: item?.sender === myAddress ? false : true,
}));
setMessages((prev) => [...prev, ...formatted]);
setChatReferences((prev) => {
const organizedChatReferences = { ...prev };
@ -183,7 +186,9 @@ export const ChatDirect = ({
{}),
edit: item,
};
} catch (error) {}
} catch (error) {
console.log(error);
}
});
return organizedChatReferences;
});
@ -214,7 +219,9 @@ export const ChatDirect = ({
{}),
edit: item,
};
} catch (error) {}
} catch (error) {
console.log(error);
}
});
return organizedChatReferences;
});
@ -227,7 +234,9 @@ export const ChatDirect = ({
rej(error.message || 'An error occurred');
});
});
} catch (error) {}
} catch (error) {
console.log(error);
}
};
const forceCloseWebSocket = () => {
@ -368,7 +377,6 @@ export const ChatDirect = ({
senderName: myName,
});
setNewChat(null);
window
.sendMessage('addTimestampEnterChat', {
timestamp: Date.now(),
@ -396,7 +404,6 @@ export const ChatDirect = ({
});
} catch (error) {
throw new Error(error);
} finally {
}
};
const clearEditorContent = () => {
@ -537,39 +544,39 @@ export const ChatDirect = ({
return (
<div
style={{
height: isMobile ? '100%' : '100vh',
background: theme.palette.background.default,
display: 'flex',
flexDirection: 'column',
height: isMobile ? '100%' : '100vh',
width: '100%',
background: !isMobile && 'var(--bg-2)',
}}
>
{!isMobile && (
<Box
onClick={close}
sx={{
display: 'flex',
alignItems: 'center',
gap: '5px',
alignSelf: 'center',
background: theme.palette.background.default,
borderRadius: '3px',
cursor: 'pointer',
display: 'flex',
gap: '5px',
margin: '10px 0px',
padding: '4px 6px',
width: 'fit-content',
borderRadius: '3px',
background: 'rgb(35, 36, 40)',
margin: '10px 0px',
alignSelf: 'center',
}}
>
<ArrowBackIcon
sx={{
color: 'white',
fontSize: isMobile ? '20px' : '20px',
color: theme.palette.text.primary,
fontSize: '20px',
}}
/>
<Typography
sx={{
color: 'white',
fontSize: isMobile ? '14px' : '14px',
color: theme.palette.text.primary,
fontSize: '14px',
}}
>
Close Direct Chat
@ -579,26 +586,26 @@ export const ChatDirect = ({
{isMobile && (
<Box
sx={{
display: 'flex',
alignItems: 'center',
width: '100%',
marginTop: '14px',
justifyContent: 'center',
display: 'flex',
height: '15px',
justifyContent: 'center',
marginTop: '14px',
width: '100%',
}}
>
<Box
sx={{
display: 'flex',
alignItems: 'center',
display: 'flex',
justifyContent: 'space-between',
width: '320px',
}}
>
<Box
sx={{
display: 'flex',
alignItems: 'center',
display: 'flex',
width: '50px',
}}
>
@ -623,10 +630,10 @@ export const ChatDirect = ({
</Typography>
<Box
sx={{
display: 'flex',
alignItems: 'center',
width: '50px',
display: 'flex',
justifyContent: 'flex-end',
width: '50px',
}}
>
<ButtonBase
@ -670,42 +677,40 @@ export const ChatDirect = ({
<div
style={{
// position: 'fixed',
// bottom: '0px',
backgroundColor: '#232428',
minHeight: isMobile ? '0px' : '150px',
backgroundColor: theme.palette.background.default,
bottom: isFocusedParent ? '0px' : 'unset',
boxSizing: 'border-box',
display: 'flex',
flexDirection: 'row',
flexShrink: 0,
minHeight: '150px',
overflow: 'hidden',
width: '100%',
boxSizing: 'border-box',
padding: isMobile ? '10px' : '20px',
position: isFocusedParent ? 'fixed' : 'relative',
bottom: isFocusedParent ? '0px' : 'unset',
top: isFocusedParent ? '0px' : 'unset',
width: '100%',
zIndex: isFocusedParent ? 5 : 'unset',
flexShrink: 0,
}}
>
<div
style={{
display: 'flex',
flexDirection: 'column',
flexGrow: isMobile && 1,
overflow: !isMobile && 'auto',
flexGrow: 1,
flexShrink: 0,
width: 'calc(100% - 100px)',
justifyContent: 'flex-end',
overflow: 'auto',
width: 'calc(100% - 100px)',
}}
>
{replyMessage && (
<Box
sx={{
alignItems: 'flex-start',
display: 'flex',
gap: '5px',
alignItems: 'flex-start',
width: 'calc(100% - 100px)',
justifyContent: 'flex-end',
width: 'calc(100% - 100px)',
}}
>
<ReplyPreview message={replyMessage} />
@ -723,9 +728,9 @@ export const ChatDirect = ({
{onEditMessage && (
<Box
sx={{
alignItems: 'flex-start',
display: 'flex',
gap: '5px',
alignItems: 'flex-start',
width: '100%',
}}
>
@ -735,7 +740,6 @@ export const ChatDirect = ({
onClick={() => {
setReplyMessage(null);
setOnEditMessage(null);
clearEditorContent();
}}
>
@ -756,9 +760,9 @@ export const ChatDirect = ({
<Box
sx={{
display: 'flex',
width: '100%',
justifyContent: 'flex-start',
position: 'relative',
width: '100%',
}}
>
<Typography
@ -774,12 +778,11 @@ export const ChatDirect = ({
<Box
sx={{
display: 'flex',
width: '100px',
flexShrink: 0,
gap: '10px',
justifyContent: 'center',
flexShrink: 0,
position: 'relative',
width: '100px',
}}
>
<CustomButton
@ -788,26 +791,28 @@ export const ChatDirect = ({
sendMessage();
}}
style={{
marginTop: 'auto',
alignSelf: 'center',
background: isSending
? theme.palette.background.default
: theme.palette.background.paper,
cursor: isSending ? 'default' : 'pointer',
background: isSending && 'rgba(0, 0, 0, 0.8)',
flexShrink: 0,
marginTop: 'auto',
minWidth: 'auto',
padding: '5px',
width: '100px',
minWidth: 'auto',
}}
>
{isSending && (
<CircularProgress
size={18}
sx={{
color: theme.palette.text.primary,
left: '50%',
marginLeft: '-12px',
marginTop: '-12px',
position: 'absolute',
top: '50%',
left: '50%',
marginTop: '-12px',
marginLeft: '-12px',
color: 'white',
}}
/>
)}
@ -815,12 +820,14 @@ export const ChatDirect = ({
</CustomButton>
</Box>
</div>
<LoadingSnackbar
open={isLoading}
info={{
message: 'Loading chat... please wait.',
}}
/>
<CustomizedSnackbars
open={openSnack}
setOpen={setOpenSnack}

View File

@ -128,7 +128,9 @@ export const ChatGroup = ({
rej(error.message || 'An error occurred');
});
});
} catch (error) {}
} catch (error) {
console.log(error);
}
};
useEffect(() => {
@ -192,7 +194,9 @@ export const ChatGroup = ({
handleSecretKeyCreationInProgress();
return;
}
} catch (error) {}
} catch (error) {
console.log(error);
}
});
};
@ -578,7 +582,9 @@ export const ChatGroup = ({
rej(error.message || 'An error occurred');
});
});
} catch (error) {}
} catch (error) {
console.log(error);
}
};
const forceCloseWebSocket = () => {
@ -621,7 +627,9 @@ export const ChatGroup = ({
middletierFunc(JSON.parse(e.data), selectedGroup);
setIsLoading(false);
}
} catch (error) {}
} catch (error) {
console.log(error);
}
};
socketRef.current.onclose = () => {
clearTimeout(groupSocketTimeoutRef.current);
@ -700,7 +708,9 @@ export const ChatGroup = ({
rej(error.message || 'An error occurred');
});
});
} catch (error) {}
} catch (error) {
console.log(error);
}
};
const sendChatGroup = async ({

View File

@ -106,7 +106,9 @@ export const decryptPublishes = async (encryptedMessages: any[], secretKey) => {
rej(error.message || 'An error occurred');
});
});
} catch (error) {}
} catch (error) {
console.log(error);
}
};
export const handleUnencryptedPublishes = (publishes) => {
let publishesData = [];
@ -117,7 +119,9 @@ export const handleUnencryptedPublishes = (publishes) => {
if (decodedData) {
publishesData.push({ decryptedData: decodedData });
}
} catch (error) {}
} catch (error) {
console.log(error);
}
});
return publishesData;
};
@ -236,7 +240,9 @@ export const GroupAnnouncements = ({
rej(error.message || 'An error occurred');
});
});
} catch (error) {}
} catch (error) {
console.log(error);
}
};
const publishAnc = async ({ encryptedData, identifier }: any) => {
@ -286,7 +292,9 @@ export const GroupAnnouncements = ({
});
setTempPublishedList(tempData);
}
} catch (error) {}
} catch (error) {
console.log(error);
}
};
const publishAnnouncement = async () => {
@ -422,7 +430,9 @@ export const GroupAnnouncements = ({
isPrivate
);
}
} catch (error) {}
} catch (error) {
console.log(error);
}
};
const interval = useRef<any>(null);
@ -449,7 +459,9 @@ export const GroupAnnouncements = ({
},
isPrivate
);
} catch (error) {}
} catch (error) {
console.log(error);
}
}
setAnnouncements(responseData);
return;
@ -467,7 +479,9 @@ export const GroupAnnouncements = ({
{ name: data.name, identifier: data.identifier },
isPrivate
);
} catch (error) {}
} catch (error) {
console.log(error);
}
}
setAnnouncements((prev) => [...newArray, ...prev]);
} catch (error) {

View File

@ -1,42 +1,46 @@
import React, { useEffect, useMemo, useRef, useState } from "react";
import { getBaseApiReact } from "../../App";
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { getBaseApiReact } from '../../App';
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
import { CustomizedSnackbars } from "../Snackbar/Snackbar";
import { extractComponents } from '../Chat/MessageDisplay';
import { executeEvent } from '../../utils/events';
import { extractComponents } from "../Chat/MessageDisplay";
import { executeEvent } from "../../utils/events";
import { base64ToBlobUrl } from "../../utils/fileReading";
import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil";
import { blobControllerAtom, blobKeySelector, resourceKeySelector, selectedGroupIdAtom } from "../../atoms/global";
import { parseQortalLink } from "./embed-utils";
import { PollCard } from "./PollEmbed";
import { ImageCard } from "./ImageEmbed";
import { AttachmentCard } from "./AttachmentEmbed";
import { decodeIfEncoded } from "../../utils/decode";
import { base64ToBlobUrl } from '../../utils/fileReading';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import {
blobControllerAtom,
blobKeySelector,
resourceKeySelector,
selectedGroupIdAtom,
} from '../../atoms/global';
import { parseQortalLink } from './embed-utils';
import { PollCard } from './PollEmbed';
import { ImageCard } from './ImageEmbed';
import { AttachmentCard } from './AttachmentEmbed';
import { decodeIfEncoded } from '../../utils/decode';
const getPoll = async (name) => {
const pollName = name;
const url = `${getBaseApiReact()}/polls/${pollName}`;
const response = await fetch(url, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
const responseData = await response.json();
if (responseData?.message?.includes("POLL_NO_EXISTS")) {
throw new Error("POLL_NO_EXISTS");
if (responseData?.message?.includes('POLL_NO_EXISTS')) {
throw new Error('POLL_NO_EXISTS');
} else if (responseData?.pollName) {
const urlVotes = `${getBaseApiReact()}/polls/votes/${pollName}`;
const responseVotes = await fetch(urlVotes, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
@ -49,56 +53,64 @@ const getPoll = async (name) => {
};
export const Embed = ({ embedLink }) => {
const [errorMsg, setErrorMsg] = useState("");
const [errorMsg, setErrorMsg] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [poll, setPoll] = useState(null);
const [type, setType] = useState("");
const [type, setType] = useState('');
const hasFetched = useRef(false);
const [openSnack, setOpenSnack] = useState(false);
const [infoSnack, setInfoSnack] = useState(null);
const [external, setExternal] = useState(null);
const [imageUrl, setImageUrl] = useState("");
const [imageUrl, setImageUrl] = useState('');
const [parsedData, setParsedData] = useState(null);
const setBlobs = useSetRecoilState(blobControllerAtom);
const [selectedGroupId] = useRecoilState(selectedGroupIdAtom)
const resourceData = useMemo(()=> {
const [selectedGroupId] = useRecoilState(selectedGroupIdAtom);
const resourceData = useMemo(() => {
const parsedDataOnTheFly = parseQortalLink(embedLink);
if(parsedDataOnTheFly?.service && parsedDataOnTheFly?.name && parsedDataOnTheFly?.identifier){
if (
parsedDataOnTheFly?.service &&
parsedDataOnTheFly?.name &&
parsedDataOnTheFly?.identifier
) {
return {
service : parsedDataOnTheFly?.service,
service: parsedDataOnTheFly?.service,
name: parsedDataOnTheFly?.name,
identifier: parsedDataOnTheFly?.identifier,
fileName: parsedDataOnTheFly?.fileName ? decodeURIComponent(parsedDataOnTheFly?.fileName) : null,
mimeType: parsedDataOnTheFly?.mimeType ? decodeURIComponent(parsedDataOnTheFly?.mimeType) : null,
key: parsedDataOnTheFly?.key ? decodeURIComponent(parsedDataOnTheFly?.key) : null,
}
fileName: parsedDataOnTheFly?.fileName
? decodeURIComponent(parsedDataOnTheFly?.fileName)
: null,
mimeType: parsedDataOnTheFly?.mimeType
? decodeURIComponent(parsedDataOnTheFly?.mimeType)
: null,
key: parsedDataOnTheFly?.key
? decodeURIComponent(parsedDataOnTheFly?.key)
: null,
};
} else {
return null
return null;
}
}, [embedLink])
}, [embedLink]);
const keyIdentifier = useMemo(()=> {
if(resourceData){
return `${resourceData.service}-${resourceData.name}-${resourceData.identifier}`
const keyIdentifier = useMemo(() => {
if (resourceData) {
return `${resourceData.service}-${resourceData.name}-${resourceData.identifier}`;
} else {
return undefined
return undefined;
}
}, [resourceData])
}, [resourceData]);
const blobUrl = useRecoilValue(blobKeySelector(keyIdentifier));
const handlePoll = async (parsedData) => {
try {
setIsLoading(true);
setErrorMsg("");
setType("POLL");
setErrorMsg('');
setType('POLL');
if (!parsedData?.name)
throw new Error("Invalid poll embed link. Missing name.");
throw new Error('Invalid poll embed link. Missing name.');
const pollRes = await getPoll(parsedData.name);
setPoll(pollRes);
} catch (error) {
setErrorMsg(error?.message || "Invalid embed link");
setErrorMsg(error?.message || 'Invalid embed link');
} finally {
setIsLoading(false);
}
@ -106,8 +118,8 @@ export const Embed = ({ embedLink }) => {
const getImage = async ({ identifier, name, service }, key, parsedData) => {
try {
if(blobUrl?.blobUrl){
return blobUrl?.blobUrl
if (blobUrl?.blobUrl) {
return blobUrl?.blobUrl;
}
let numberOfTries = 0;
let imageFinalUrl = null;
@ -116,76 +128,76 @@ export const Embed = ({ embedLink }) => {
const urlStatus = `${getBaseApiReact()}/arbitrary/resource/status/${service}/${name}/${identifier}?build=true`;
const responseStatus = await fetch(urlStatus, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
const responseData = await responseStatus.json();
if (responseData?.status === "READY") {
if (responseData?.status === 'READY') {
if (parsedData?.encryptionType) {
const urlData = `${getBaseApiReact()}/arbitrary/${service}/${name}/${identifier}?encoding=base64`;
const responseData = await fetch(urlData, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
const data = await responseData.text();
if (data) {
let decryptedData
let decryptedData;
try {
if(key && encryptionType === 'private'){
if (key && encryptionType === 'private') {
decryptedData = await window.sendMessage(
"DECRYPT_DATA_WITH_SHARING_KEY",
{
encryptedData: data,
'DECRYPT_DATA_WITH_SHARING_KEY',
{
encryptedData: data,
key: decodeURIComponent(key),
}
}
);
}
if(encryptionType === 'group'){
if (encryptionType === 'group') {
decryptedData = await window.sendMessage(
"DECRYPT_QORTAL_GROUP_DATA",
{
data64: data,
groupId: selectedGroupId,
}
);
'DECRYPT_QORTAL_GROUP_DATA',
}
{
data64: data,
groupId: selectedGroupId,
}
);
}
} catch (error) {
throw new Error('Unable to decrypt')
throw new Error('Unable to decrypt');
}
if (!decryptedData || decryptedData?.error) throw new Error("Could not decrypt data");
imageFinalUrl = base64ToBlobUrl(decryptedData, parsedData?.mimeType ? decodeURIComponent(parsedData?.mimeType) : undefined)
setBlobs((prev=> {
if (!decryptedData || decryptedData?.error)
throw new Error('Could not decrypt data');
imageFinalUrl = base64ToBlobUrl(
decryptedData,
parsedData?.mimeType
? decodeURIComponent(parsedData?.mimeType)
: undefined
);
setBlobs((prev) => {
return {
...prev,
[`${service}-${name}-${identifier}`]: {
blobUrl: imageFinalUrl,
timestamp: Date.now()
}
}
}))
timestamp: Date.now(),
},
};
});
} else {
throw new Error('No data for image')
throw new Error('No data for image');
}
} else {
imageFinalUrl = `${getBaseApiReact()}/arbitrary/${service}/${name}/${identifier}?async=true`;
// If parsedData is used here, it must be defined somewhere
}
imageFinalUrl = `${getBaseApiReact()}/arbitrary/${service}/${name}/${identifier}?async=true`;
// If parsedData is used here, it must be defined somewhere
}
}
};
@ -203,18 +215,19 @@ export const Embed = ({ embedLink }) => {
}
if (imageFinalUrl) {
return imageFinalUrl;
} else {
setErrorMsg(
"Unable to download IMAGE. Please try again later by clicking the refresh button"
'Unable to download IMAGE. Please try again later by clicking the refresh button'
);
return null;
}
} catch (error) {
console.error("Error fetching image:", error);
console.error('Error fetching image:', error);
setErrorMsg(
error?.error || error?.message || "An unexpected error occurred while trying to download the image"
error?.error ||
error?.message ||
'An unexpected error occurred while trying to download the image'
);
return null;
}
@ -223,25 +236,27 @@ export const Embed = ({ embedLink }) => {
const handleImage = async (parsedData) => {
try {
setIsLoading(true);
setErrorMsg("");
setErrorMsg('');
if (!parsedData?.name || !parsedData?.service || !parsedData?.identifier)
throw new Error("Invalid image embed link. Missing param.");
let image = await getImage({
name: parsedData.name,
service: parsedData.service,
identifier: parsedData?.identifier,
}, parsedData?.key, parsedData);
setImageUrl(image);
throw new Error('Invalid image embed link. Missing param.');
let image = await getImage(
{
name: parsedData.name,
service: parsedData.service,
identifier: parsedData?.identifier,
},
parsedData?.key,
parsedData
);
setImageUrl(image);
} catch (error) {
setErrorMsg(error?.message || "Invalid embed link");
setErrorMsg(error?.message || 'Invalid embed link');
} finally {
setIsLoading(false);
}
};
const handleLink = () => {
try {
const parsedData = parseQortalLink(embedLink);
@ -254,28 +269,26 @@ export const Embed = ({ embedLink }) => {
setExternal(res);
}
}
} catch (error) {
}
} catch (error) {}
switch (type) {
case "POLL":
case 'POLL':
{
handlePoll(parsedData);
}
break;
case "IMAGE":
setType("IMAGE");
case 'IMAGE':
setType('IMAGE');
break;
case 'ATTACHMENT':
setType('ATTACHMENT');
break;
case "ATTACHMENT":
setType("ATTACHMENT");
break;
default:
break;
}
} catch (error) {
setErrorMsg(error?.message || "Invalid embed link");
setErrorMsg(error?.message || 'Invalid embed link');
}
};
@ -284,13 +297,13 @@ export const Embed = ({ embedLink }) => {
const parsedData = parseQortalLink(embedLink);
handleImage(parsedData);
} catch (error) {
setErrorMsg(error?.message || "Invalid embed link");
setErrorMsg(error?.message || 'Invalid embed link');
}
};
const openExternal = () => {
executeEvent("addTab", { data: external });
executeEvent("open-apps-mode", {});
executeEvent('addTab', { data: external });
executeEvent('open-apps-mode', {});
};
useEffect(() => {
@ -299,8 +312,6 @@ export const Embed = ({ embedLink }) => {
hasFetched.current = true;
}, [embedLink]);
const resourceDetails = useRecoilValue(resourceKeySelector(keyIdentifier));
const { parsedType, encryptionType } = useMemo(() => {
@ -312,15 +323,17 @@ export const Embed = ({ embedLink }) => {
parsedType = parsedDataOnTheFly.type;
}
if (parsedDataOnTheFly?.encryptionType) {
encryptionType = parsedDataOnTheFly?.encryptionType
encryptionType = parsedDataOnTheFly?.encryptionType;
}
} catch (error) {}
} catch (error) {
console.log(error);
}
return { parsedType, encryptionType };
}, [embedLink]);
return (
<div>
{parsedType === "POLL" && (
{parsedType === 'POLL' && (
<PollCard
poll={poll}
refresh={handleLink}
@ -332,7 +345,7 @@ export const Embed = ({ embedLink }) => {
errorMsg={errorMsg}
/>
)}
{parsedType === "IMAGE" && (
{parsedType === 'IMAGE' && (
<ImageCard
image={imageUrl}
owner={parsedData?.name}
@ -349,8 +362,8 @@ export const Embed = ({ embedLink }) => {
)}
{parsedType === 'ATTACHMENT' && (
<AttachmentCard
resourceData={resourceData}
resourceDetails={resourceDetails}
resourceData={resourceData}
resourceDetails={resourceDetails}
owner={parsedData?.name}
refresh={fetchImage}
setInfoSnack={setInfoSnack}
@ -373,11 +386,3 @@ export const Embed = ({ embedLink }) => {
</div>
);
};

View File

@ -1,5 +1,5 @@
import React, { useContext, useEffect, useState } from "react";
import { MyContext } from "../../App";
import React, { useContext, useEffect, useState } from 'react';
import { MyContext } from '../../App';
import {
Card,
CardContent,
@ -12,384 +12,389 @@ import {
Box,
ButtonBase,
Divider,
} from "@mui/material";
import { getNameInfo } from "../Group/Group";
import PollIcon from "@mui/icons-material/Poll";
import { getFee } from "../../background";
import RefreshIcon from "@mui/icons-material/Refresh";
import { Spacer } from "../../common/Spacer";
import OpenInNewIcon from "@mui/icons-material/OpenInNew";
import { CustomLoader } from "../../common/CustomLoader";
} from '@mui/material';
import { getNameInfo } from '../Group/Group';
import PollIcon from '@mui/icons-material/Poll';
import { getFee } from '../../background';
import RefreshIcon from '@mui/icons-material/Refresh';
import { Spacer } from '../../common/Spacer';
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
import { CustomLoader } from '../../common/CustomLoader';
export const PollCard = ({
poll,
setInfoSnack,
setOpenSnack,
refresh,
openExternal,
external,
isLoadingParent,
errorMsg,
}) => {
const [selectedOption, setSelectedOption] = useState("");
const [ownerName, setOwnerName] = useState("");
const [showResults, setShowResults] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const { show, userInfo } = useContext(MyContext);
const [isLoadingSubmit, setIsLoadingSubmit] = useState(false);
const handleVote = async () => {
const fee = await getFee("VOTE_ON_POLL");
await show({
message: `Do you accept this VOTE_ON_POLL transaction? POLLS are public!`,
publishFee: fee.fee + " QORT",
});
setIsLoadingSubmit(true);
window
.sendMessage(
"voteOnPoll",
{
pollName: poll?.info?.pollName,
optionIndex: +selectedOption,
},
60000
)
.then((response) => {
setIsLoadingSubmit(false);
if (response.error) {
setInfoSnack({
type: "error",
message: response?.error || "Unable to vote.",
});
setOpenSnack(true);
return;
} else {
setInfoSnack({
type: "success",
message:
"Successfully voted. Please wait a couple minutes for the network to propogate the changes.",
});
setOpenSnack(true);
}
})
.catch((error) => {
setIsLoadingSubmit(false);
poll,
setInfoSnack,
setOpenSnack,
refresh,
openExternal,
external,
isLoadingParent,
errorMsg,
}) => {
const [selectedOption, setSelectedOption] = useState('');
const [ownerName, setOwnerName] = useState('');
const [showResults, setShowResults] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const { show, userInfo } = useContext(MyContext);
const [isLoadingSubmit, setIsLoadingSubmit] = useState(false);
const handleVote = async () => {
const fee = await getFee('VOTE_ON_POLL');
await show({
message: `Do you accept this VOTE_ON_POLL transaction? POLLS are public!`,
publishFee: fee.fee + ' QORT',
});
setIsLoadingSubmit(true);
window
.sendMessage(
'voteOnPoll',
{
pollName: poll?.info?.pollName,
optionIndex: +selectedOption,
},
60000
)
.then((response) => {
setIsLoadingSubmit(false);
if (response.error) {
setInfoSnack({
type: "error",
message: error?.message || "Unable to vote.",
type: 'error',
message: response?.error || 'Unable to vote.',
});
setOpenSnack(true);
return;
} else {
setInfoSnack({
type: 'success',
message:
'Successfully voted. Please wait a couple minutes for the network to propogate the changes.',
});
setOpenSnack(true);
});
};
const getName = async (owner) => {
try {
const res = await getNameInfo(owner);
if (res) {
setOwnerName(res);
}
} catch (error) {}
};
useEffect(() => {
if (poll?.info?.owner) {
getName(poll.info.owner);
})
.catch((error) => {
setIsLoadingSubmit(false);
setInfoSnack({
type: 'error',
message: error?.message || 'Unable to vote.',
});
setOpenSnack(true);
});
};
const getName = async (owner) => {
try {
const res = await getNameInfo(owner);
if (res) {
setOwnerName(res);
}
}, [poll?.info?.owner]);
return (
<Card
} catch (error) {
console.log(error);
}
};
useEffect(() => {
if (poll?.info?.owner) {
getName(poll.info.owner);
}
}, [poll?.info?.owner]);
return (
<Card
sx={{
backgroundColor: '#1F2023',
height: isOpen ? 'auto' : '150px',
}}
>
<Box
sx={{
backgroundColor: "#1F2023",
height: isOpen ? "auto" : "150px",
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '16px 16px 0px 16px',
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: "16px 16px 0px 16px",
display: 'flex',
alignItems: 'center',
gap: '10px',
}}
>
<Box
<PollIcon
sx={{
display: "flex",
alignItems: "center",
gap: "10px",
color: 'white',
}}
>
<PollIcon
/>
<Typography>POLL embed</Typography>
</Box>
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: '10px',
}}
>
<ButtonBase>
<RefreshIcon
onClick={refresh}
sx={{
color: "white",
fontSize: '24px',
color: 'white',
}}
/>
<Typography>POLL embed</Typography>
</Box>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "10px",
}}
>
</ButtonBase>
{external && (
<ButtonBase>
<RefreshIcon
onClick={refresh}
<OpenInNewIcon
onClick={openExternal}
sx={{
fontSize: "24px",
color: "white",
fontSize: '24px',
color: 'white',
}}
/>
</ButtonBase>
{external && (
<ButtonBase>
<OpenInNewIcon
onClick={openExternal}
sx={{
fontSize: "24px",
color: "white",
}}
/>
</ButtonBase>
)}
</Box>
)}
</Box>
<Box
</Box>
<Box
sx={{
padding: '8px 16px 8px 16px',
}}
>
<Typography
sx={{
padding: "8px 16px 8px 16px",
fontSize: '12px',
}}
>
<Typography
Created by {ownerName || poll?.info?.owner}
</Typography>
</Box>
<Divider sx={{ borderColor: 'rgb(255 255 255 / 10%)' }} />
<Box
sx={{
display: 'flex',
flexDirection: 'column',
width: '100%',
alignItems: 'center',
}}
>
{!isOpen && !errorMsg && (
<>
<Spacer height="5px" />
<Button
size="small"
variant="contained"
sx={{
backgroundColor: 'var(--green)',
}}
onClick={() => {
setIsOpen(true);
}}
>
Show poll
</Button>
</>
)}
{isLoadingParent && isOpen && (
<Box
sx={{
fontSize: "12px",
width: '100%',
display: 'flex',
justifyContent: 'center',
}}
>
Created by {ownerName || poll?.info?.owner}
</Typography>
</Box>
<Divider sx={{ borderColor: "rgb(255 255 255 / 10%)" }} />
<Box
sx={{
display: "flex",
flexDirection: "column",
width: "100%",
alignItems: "center",
}}
>
{!isOpen && !errorMsg && (
<>
<Spacer height="5px" />
<Button
size="small"
variant="contained"
sx={{
backgroundColor: "var(--green)",
}}
onClick={() => {
setIsOpen(true);
}}
>
Show poll
</Button>
</>
)}
{isLoadingParent && isOpen && (
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "center",
}}
>
{" "}
<CustomLoader />{" "}
</Box>
)}
{errorMsg && (
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "center",
}}
>
{" "}
<Typography
sx={{
fontSize: "14px",
color: "var(--danger)",
}}
>
{errorMsg}
</Typography>{" "}
</Box>
)}
</Box>
<Box
sx={{
display: isOpen ? "block" : "none",
}}
>
<CardHeader
title={poll?.info?.pollName}
subheader={poll?.info?.description}
{' '}
<CustomLoader />{' '}
</Box>
)}
{errorMsg && (
<Box
sx={{
"& .MuiCardHeader-title": {
fontSize: "18px", // Custom font size for title
},
width: '100%',
display: 'flex',
justifyContent: 'center',
}}
/>
<CardContent>
>
{' '}
<Typography
sx={{
fontSize: "18px",
fontSize: '14px',
color: 'var(--danger)',
}}
>
Options
</Typography>
<RadioGroup
value={selectedOption}
onChange={(e) => setSelectedOption(e.target.value)}
>
{poll?.info?.pollOptions?.map((option, index) => (
<FormControlLabel
key={index}
value={index}
control={
<Radio
sx={{
color: "white", // Unchecked color
"&.Mui-checked": {
color: "var(--green)", // Checked color
},
}}
/>
}
label={option?.optionName}
sx={{
"& .MuiFormControlLabel-label": {
fontSize: "14px",
},
}}
/>
))}
</RadioGroup>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "20px",
}}
>
<Button
variant="contained"
color="primary"
disabled={!selectedOption || isLoadingSubmit}
onClick={handleVote}
>
Vote
</Button>
<Typography
{errorMsg}
</Typography>{' '}
</Box>
)}
</Box>
<Box
sx={{
display: isOpen ? 'block' : 'none',
}}
>
<CardHeader
title={poll?.info?.pollName}
subheader={poll?.info?.description}
sx={{
'& .MuiCardHeader-title': {
fontSize: '18px', // Custom font size for title
},
}}
/>
<CardContent>
<Typography
sx={{
fontSize: '18px',
}}
>
Options
</Typography>
<RadioGroup
value={selectedOption}
onChange={(e) => setSelectedOption(e.target.value)}
>
{poll?.info?.pollOptions?.map((option, index) => (
<FormControlLabel
key={index}
value={index}
control={
<Radio
sx={{
color: 'white', // Unchecked color
'&.Mui-checked': {
color: 'var(--green)', // Checked color
},
}}
/>
}
label={option?.optionName}
sx={{
fontSize: "14px",
fontStyle: "italic",
'& .MuiFormControlLabel-label': {
fontSize: '14px',
},
}}
/>
))}
</RadioGroup>
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: '20px',
}}
>
<Button
variant="contained"
color="primary"
disabled={!selectedOption || isLoadingSubmit}
onClick={handleVote}
>
Vote
</Button>
<Typography
sx={{
fontSize: '14px',
fontStyle: 'italic',
}}
>
{' '}
{`${poll?.votes?.totalVotes} ${
poll?.votes?.totalVotes === 1 ? ' vote' : ' votes'
}`}
</Typography>
</Box>
<Spacer height="10px" />
<Typography
sx={{
fontSize: '14px',
visibility: poll?.votes?.votes?.find(
(item) => item?.voterPublicKey === userInfo?.publicKey
)
? 'visible'
: 'hidden',
}}
>
You've already voted.
</Typography>
<Spacer height="10px" />
{isLoadingSubmit && (
<Typography
sx={{
fontSize: '12px',
}}
>
Is processing transaction, please wait...
</Typography>
)}
<ButtonBase
onClick={() => {
setShowResults((prev) => !prev);
}}
>
{showResults ? 'hide ' : 'show '} results
</ButtonBase>
</CardContent>
{showResults && <PollResults votes={poll?.votes} />}
</Box>
</Card>
);
};
const PollResults = ({ votes }) => {
const maxVotes = Math.max(
...votes?.voteCounts?.map((option) => option.voteCount)
);
const options = votes?.voteCounts;
return (
<Box sx={{ width: '100%', p: 2 }}>
{options
.sort((a, b) => b.voteCount - a.voteCount) // Sort options by votes (highest first)
.map((option, index) => (
<Box key={index} sx={{ mb: 2 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography
variant="body1"
sx={{
fontWeight: index === 0 ? 'bold' : 'normal',
fontSize: '14px',
}}
>
{" "}
{`${poll?.votes?.totalVotes} ${
poll?.votes?.totalVotes === 1 ? " vote" : " votes"
}`}
{`${index + 1}. ${option.optionName}`}
</Typography>
<Typography
variant="body1"
sx={{
fontWeight: index === 0 ? 'bold' : 'normal',
fontSize: '14px',
}}
>
{option.voteCount} votes
</Typography>
</Box>
<Spacer height="10px" />
<Typography
<Box
sx={{
fontSize: "14px",
visibility: poll?.votes?.votes?.find(
(item) => item?.voterPublicKey === userInfo?.publicKey
)
? "visible"
: "hidden",
mt: 1,
height: 10,
backgroundColor: '#e0e0e0',
borderRadius: 5,
overflow: 'hidden',
}}
>
You've already voted.
</Typography>
<Spacer height="10px" />
{isLoadingSubmit && (
<Typography
sx={{
fontSize: "12px",
}}
>
Is processing transaction, please wait...
</Typography>
)}
<ButtonBase
onClick={() => {
setShowResults((prev) => !prev);
}}
>
{showResults ? "hide " : "show "} results
</ButtonBase>
</CardContent>
{showResults && <PollResults votes={poll?.votes} />}
</Box>
</Card>
);
};
const PollResults = ({ votes }) => {
const maxVotes = Math.max(
...votes?.voteCounts?.map((option) => option.voteCount)
);
const options = votes?.voteCounts;
return (
<Box sx={{ width: "100%", p: 2 }}>
{options
.sort((a, b) => b.voteCount - a.voteCount) // Sort options by votes (highest first)
.map((option, index) => (
<Box key={index} sx={{ mb: 2 }}>
<Box sx={{ display: "flex", justifyContent: "space-between" }}>
<Typography
variant="body1"
sx={{ fontWeight: index === 0 ? "bold" : "normal" , fontSize: "14px"}}
>
{`${index + 1}. ${option.optionName}`}
</Typography>
<Typography
variant="body1"
sx={{ fontWeight: index === 0 ? "bold" : "normal" , fontSize: "14px"}}
>
{option.voteCount} votes
</Typography>
</Box>
<Box
sx={{
mt: 1,
height: 10,
backgroundColor: "#e0e0e0",
borderRadius: 5,
overflow: "hidden",
width: `${(option.voteCount / maxVotes) * 100}%`,
height: '100%',
backgroundColor: index === 0 ? '#3f51b5' : '#f50057',
transition: 'width 0.3s ease-in-out',
}}
>
<Box
sx={{
width: `${(option.voteCount / maxVotes) * 100}%`,
height: "100%",
backgroundColor: index === 0 ? "#3f51b5" : "#f50057",
transition: "width 0.3s ease-in-out",
}}
/>
</Box>
/>
</Box>
))}
</Box>
);
};
</Box>
))}
</Box>
);
};

View File

@ -7,7 +7,7 @@ import {
Popover,
TextField,
Typography,
} from "@mui/material";
} from '@mui/material';
import React, {
useCallback,
useContext,
@ -15,20 +15,20 @@ import React, {
useMemo,
useRef,
useState,
} from "react";
} from 'react';
import {
AutoSizer,
CellMeasurer,
CellMeasurerCache,
List,
} from "react-virtualized";
import _ from "lodash";
import { MyContext, getBaseApiReact } from "../../App";
import { LoadingButton } from "@mui/lab";
import { getBaseApi, getFee } from "../../background";
} from 'react-virtualized';
import _ from 'lodash';
import { MyContext, getBaseApiReact } from '../../App';
import { LoadingButton } from '@mui/lab';
import { getBaseApi, getFee } from '../../background';
import LockIcon from '@mui/icons-material/Lock';
import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmailerrorred';
import { Spacer } from "../../common/Spacer";
import { Spacer } from '../../common/Spacer';
const cache = new CellMeasurerCache({
fixedWidth: true,
defaultHeight: 50,
@ -41,7 +41,7 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to
const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open
const listRef = useRef();
const [inputValue, setInputValue] = useState("");
const [inputValue, setInputValue] = useState('');
const [filteredItems, setFilteredItems] = useState(groups);
const [isLoading, setIsLoading] = useState(false);
@ -72,9 +72,7 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
const getGroups = async () => {
try {
const response = await fetch(
`${getBaseApiReact()}/groups/?limit=0`
);
const response = await fetch(`${getBaseApiReact()}/groups/?limit=0`);
const groupData = await response.json();
const filteredGroup = groupData.filter(
(item) => !memberGroups.find((group) => group.groupId === item.groupId)
@ -103,23 +101,25 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
const handleJoinGroup = 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'
})
const fee = await getFee('JOIN_GROUP');
await show({
message: 'Would you like to perform an JOIN_GROUP transaction?',
publishFee: fee.fee + ' QORT',
});
setIsLoading(true);
await new Promise((res, rej) => {
window.sendMessage("joinGroup", {
groupId,
})
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",
type: 'success',
message:
'Successfully requested to join group. It may take a couple of minutes for the changes to propagate',
});
if (isOpen) {
setTxList((prev) => [
{
@ -145,14 +145,14 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
...prev,
]);
}
setOpenSnack(true);
handlePopoverClose();
res(response);
return;
} else {
setInfoSnack({
type: "error",
type: 'error',
message: response?.error,
});
setOpenSnack(true);
@ -161,18 +161,18 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
})
.catch((error) => {
setInfoSnack({
type: "error",
message: error.message || "An error occurred",
type: 'error',
message: error.message || 'An error occurred',
});
setOpenSnack(true);
rej(error);
});
});
setIsLoading(false);
} catch (error) {} finally {
} catch (error) {
console.log(error);
} finally {
setIsLoading(false);
}
};
@ -195,30 +195,30 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
anchorEl={popoverAnchor}
onClose={handlePopoverClose}
anchorOrigin={{
vertical: "bottom",
horizontal: "center",
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: "top",
horizontal: "center",
vertical: 'top',
horizontal: 'center',
}}
style={{ marginTop: "8px" }}
style={{ marginTop: '8px' }}
>
<Box
sx={{
width: "325px",
height: "250px",
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "10px",
padding: "10px",
width: '325px',
height: '250px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '10px',
padding: '10px',
}}
>
<Typography>Join {group?.groupName}</Typography>
<Typography>
{group?.isOpen === false &&
"This is a closed/private group, so you will need to wait until an admin accepts your request"}
'This is a closed/private group, so you will need to wait until an admin accepts your request'}
</Typography>
<LoadingButton
loading={isLoading}
@ -234,16 +234,20 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
onClick={(event) => handlePopoverOpen(event, index)}
>
{group?.isOpen === false && (
<LockIcon sx={{
color: 'var(--green)'
}} />
)}
{group?.isOpen === true && (
<NoEncryptionGmailerrorredIcon sx={{
color: 'var(--danger)'
}} />
)}
<Spacer width="15px" />
<LockIcon
sx={{
color: 'var(--green)',
}}
/>
)}
{group?.isOpen === true && (
<NoEncryptionGmailerrorredIcon
sx={{
color: 'var(--danger)',
}}
/>
)}
<Spacer width="15px" />
<ListItemText
primary={group?.groupName}
secondary={group?.description}
@ -257,11 +261,13 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
};
return (
<Box sx={{
display: 'flex',
flexDirection: 'column',
flexGrow: 1
}}>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
}}
>
<p>Groups list</p>
<TextField
label="Search for Groups"
@ -272,10 +278,10 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
/>
<div
style={{
position: "relative",
width: "100%",
display: "flex",
flexDirection: "column",
position: 'relative',
width: '100%',
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
}}
>

View File

@ -5,10 +5,10 @@ import React, {
useMemo,
useRef,
useState,
} from "react";
import { Avatar, Box, Popover, Typography } from "@mui/material";
} from 'react';
import { Avatar, Box, Popover, Typography } from '@mui/material';
// import { MAIL_SERVICE_TYPE, THREAD_SERVICE_TYPE } from "../../constants/mail";
import { Thread } from "./Thread";
import { Thread } from './Thread';
import {
AllThreadP,
ArrowDownIcon,
@ -38,61 +38,73 @@ import {
ThreadSingleLastMessageP,
ThreadSingleLastMessageSpanP,
ThreadSingleTitle,
} from "./Mail-styles";
} from './Mail-styles';
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import { Spacer } from "../../../common/Spacer";
import { formatDate, formatTimestamp } from "../../../utils/time";
import LazyLoad from "../../../common/LazyLoad";
import { delay } from "../../../utils/helpers";
import { NewThread } from "./NewThread";
import { getBaseApi } from "../../../background";
import { decryptPublishes, getTempPublish, handleUnencryptedPublishes } from "../../Chat/GroupAnnouncements";
import CheckSVG from "../../../assets/svgs/Check.svg";
import SortSVG from "../../../assets/svgs/Sort.svg";
import ArrowDownSVG from "../../../assets/svgs/ArrowDown.svg";
import { LoadingSnackbar } from "../../Snackbar/LoadingSnackbar";
import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from "../../../utils/events";
import { Spacer } from '../../../common/Spacer';
import { formatDate, formatTimestamp } from '../../../utils/time';
import LazyLoad from '../../../common/LazyLoad';
import { delay } from '../../../utils/helpers';
import { NewThread } from './NewThread';
import { getBaseApi } from '../../../background';
import {
decryptPublishes,
getTempPublish,
handleUnencryptedPublishes,
} from '../../Chat/GroupAnnouncements';
import CheckSVG from '../../../assets/svgs/Check.svg';
import SortSVG from '../../../assets/svgs/Sort.svg';
import ArrowDownSVG from '../../../assets/svgs/ArrowDown.svg';
import { LoadingSnackbar } from '../../Snackbar/LoadingSnackbar';
import {
executeEvent,
subscribeToEvent,
unsubscribeFromEvent,
} from '../../../utils/events';
import RefreshIcon from '@mui/icons-material/Refresh';
import { getArbitraryEndpointReact, getBaseApiReact, isMobile } from "../../../App";
import { WrapperUserAction } from "../../WrapperUserAction";
import { addDataPublishesFunc, getDataPublishesFunc } from "../Group";
const filterOptions = ["Recently active", "Newest", "Oldest"];
import {
getArbitraryEndpointReact,
getBaseApiReact,
isMobile,
} from '../../../App';
import { WrapperUserAction } from '../../WrapperUserAction';
import { addDataPublishesFunc, getDataPublishesFunc } from '../Group';
const filterOptions = ['Recently active', 'Newest', 'Oldest'];
export const threadIdentifier = "DOCUMENT";
export const threadIdentifier = 'DOCUMENT';
export const GroupMail = ({
selectedGroup,
userInfo,
getSecretKey,
secretKey,
defaultThread,
defaultThread,
setDefaultThread,
hide,
isPrivate
isPrivate,
}) => {
const [viewedThreads, setViewedThreads] = React.useState<any>({});
const [filterMode, setFilterMode] = useState<string>("Recently active");
const [filterMode, setFilterMode] = useState<string>('Recently active');
const [currentThread, setCurrentThread] = React.useState(null);
const [recentThreads, setRecentThreads] = useState<any[]>([]);
const [allThreads, setAllThreads] = useState<any[]>([]);
const [members, setMembers] = useState<any>(null);
const [isOpenFilterList, setIsOpenFilterList] = useState<boolean>(false);
const anchorElInstanceFilter = useRef<any>(null);
const [tempPublishedList, setTempPublishedList] = useState([])
const dataPublishes = useRef({})
const [tempPublishedList, setTempPublishedList] = useState([]);
const dataPublishes = useRef({});
const [isLoading, setIsLoading] = useState(false)
const [isLoading, setIsLoading] = useState(false);
const groupIdRef = useRef<any>(null);
const groupId = useMemo(() => {
return selectedGroup?.groupId;
}, [selectedGroup]);
useEffect(()=> {
if(!groupId) return
(async ()=> {
const res = await getDataPublishesFunc(groupId, 'thread')
dataPublishes.current = res || {}
})()
}, [groupId])
useEffect(() => {
if (!groupId) return;
(async () => {
const res = await getDataPublishesFunc(groupId, 'thread');
dataPublishes.current = res || {};
})();
}, [groupId]);
useEffect(() => {
if (groupId !== groupIdRef?.current) {
@ -103,55 +115,66 @@ export const GroupMail = ({
}
}, [groupId]);
const setTempData = async ()=> {
const setTempData = async () => {
try {
const getTempAnnouncements = await getTempPublish()
if(getTempAnnouncements?.thread){
let tempData = []
Object.keys(getTempAnnouncements?.thread || {}).map((key)=> {
const value = getTempAnnouncements?.thread[key]
if(value?.data?.groupId === groupIdRef?.current){
tempData.push(value.data)
const getTempAnnouncements = await getTempPublish();
if (getTempAnnouncements?.thread) {
let tempData = [];
Object.keys(getTempAnnouncements?.thread || {}).map((key) => {
const value = getTempAnnouncements?.thread[key];
if (value?.data?.groupId === groupIdRef?.current) {
tempData.push(value.data);
}
});
setTempPublishedList(tempData);
}
})
setTempPublishedList(tempData)
}
} catch (error) {
}
}
const getEncryptedResource = async ({ name, identifier, resource }, isPrivate) => {
let data = dataPublishes.current[`${name}-${identifier}`]
if(!data || (data?.update || data?.created !== (resource?.updated || resource?.created))){
const res = await fetch(
`${getBaseApiReact()}/arbitrary/DOCUMENT/${name}/${identifier}?encoding=base64`
);
if(!res?.ok) return
data = await res.text();
await addDataPublishesFunc({...resource, data}, groupId, 'thread')
} catch (error) {}
};
const getEncryptedResource = async (
{ name, identifier, resource },
isPrivate
) => {
let data = dataPublishes.current[`${name}-${identifier}`];
if (
!data ||
data?.update ||
data?.created !== (resource?.updated || resource?.created)
) {
const res = await fetch(
`${getBaseApiReact()}/arbitrary/DOCUMENT/${name}/${identifier}?encoding=base64`
);
if (!res?.ok) return;
data = await res.text();
await addDataPublishesFunc({ ...resource, data }, groupId, 'thread');
} else {
data = data.data
data = data.data;
}
const response = isPrivate === false ? handleUnencryptedPublishes([data]) : await decryptPublishes([{ data }], secretKey);
const response =
isPrivate === false
? handleUnencryptedPublishes([data])
: await decryptPublishes([{ data }], secretKey);
const messageData = response[0];
return messageData.decryptedData;
};
const updateThreadActivity = async ({threadId, qortalName, groupId, thread}) => {
const updateThreadActivity = async ({
threadId,
qortalName,
groupId,
thread,
}) => {
try {
await new Promise((res, rej) => {
window.sendMessage("updateThreadActivity", {
threadId,
qortalName,
groupId,
thread,
})
window
.sendMessage('updateThreadActivity', {
threadId,
qortalName,
groupId,
thread,
})
.then((response) => {
if (!response?.error) {
res(response);
@ -160,13 +183,10 @@ export const GroupMail = ({
rej(response.error);
})
.catch((error) => {
rej(error.message || "An error occurred");
rej(error.message || 'An error occurred');
});
});
} catch (error) {
} finally {
}
};
@ -174,9 +194,9 @@ export const GroupMail = ({
const getAllThreads = React.useCallback(
async (groupId: string, mode: string, isInitial?: boolean) => {
try {
setIsLoading(true)
setIsLoading(true);
const offset = isInitial ? 0 : allThreads.length;
const isReverse = mode === "Newest" ? true : false;
const isReverse = mode === 'Newest' ? true : false;
if (isInitial) {
// dispatch(setIsLoadingCustom("Loading threads"));
}
@ -184,9 +204,9 @@ export const GroupMail = ({
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=${20}&includemetadata=false&offset=${offset}&reverse=${isReverse}&prefix=true`;
const response = await fetch(url, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
const responseData = await response.json();
@ -209,21 +229,26 @@ export const GroupMail = ({
let threadRes = null;
try {
threadRes = await Promise.race([
getEncryptedResource({
name: message.name,
identifier: message.identifier,
resource: message
}, isPrivate),
getEncryptedResource(
{
name: message.name,
identifier: message.identifier,
resource: message,
},
isPrivate
),
delay(5000),
]);
} catch (error) {}
} catch (error) {
console.log(error);
}
if (threadRes?.title) {
fullObject = {
...message,
threadData: threadRes,
threadOwner: message?.name,
threadId: message.identifier
threadId: message.identifier,
};
}
}
@ -251,7 +276,7 @@ export const GroupMail = ({
console.log({ error });
} finally {
if (isInitial) {
setIsLoading(false)
setIsLoading(false);
// dispatch(setIsLoadingCustom(null));
}
}
@ -261,21 +286,21 @@ export const GroupMail = ({
const getMailMessages = React.useCallback(
async (groupId: string, members: any) => {
try {
setIsLoading(true)
setIsLoading(true);
const identifier = `thmsg-grp-${groupId}-thread-`;
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=100&includemetadata=false&offset=${0}&reverse=true&prefix=true`;
const response = await fetch(url, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
const responseData = await response.json();
const messagesForThread: any = {};
for (const message of responseData) {
let str = message.identifier;
const parts = str.split("-");
const parts = str.split('-');
// Get the second last element
const secondLastId = parts[parts.length - 2];
@ -295,16 +320,16 @@ export const GroupMail = ({
})
.sort((a, b) => b.created - a.created)
.slice(0, 10);
let fullThreadArray: any = [];
const getMessageForThreads = newArray.map(async (message: any) => {
try {
const identifierQuery = message.threadId;
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=${threadIdentifier}&identifier=${identifierQuery}&limit=1&includemetadata=false&offset=${0}&reverse=true&prefix=true`;
const response = await fetch(url, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
const responseData = await response.json();
@ -324,11 +349,14 @@ export const GroupMail = ({
fullThreadArray.push(fullObject);
} else {
let threadRes = await Promise.race([
getEncryptedResource({
name: thread.name,
identifier: message.threadId,
resource: thread
}, isPrivate),
getEncryptedResource(
{
name: thread.name,
identifier: message.threadId,
resource: thread,
},
isPrivate
),
delay(10000),
]);
if (threadRes?.title) {
@ -353,7 +381,7 @@ export const GroupMail = ({
setRecentThreads(sorted);
} catch (error) {
} finally {
setIsLoading(false)
setIsLoading(false);
// dispatch(setIsLoadingCustom(null));
}
},
@ -361,7 +389,6 @@ export const GroupMail = ({
);
const getMessages = React.useCallback(async () => {
// if ( !groupId || members?.length === 0) return;
if (!groupId || isPrivate === null) return;
@ -371,23 +398,23 @@ export const GroupMail = ({
const interval = useRef<any>(null);
const firstMount = useRef(false);
const filterModeRef = useRef("");
const filterModeRef = useRef('');
useEffect(() => {
if(hide) return
if (hide) return;
if (filterModeRef.current !== filterMode) {
firstMount.current = false;
}
// if (groupId && !firstMount.current && members.length > 0) {
if (groupId && !firstMount.current && isPrivate !== null) {
if (filterMode === "Recently active") {
if (filterMode === 'Recently active') {
getMessages();
} else if (filterMode === "Newest") {
getAllThreads(groupId, "Newest", true);
} else if (filterMode === "Oldest") {
getAllThreads(groupId, "Oldest", true);
} else if (filterMode === 'Newest') {
getAllThreads(groupId, 'Newest', true);
} else if (filterMode === 'Oldest') {
getAllThreads(groupId, 'Oldest', true);
}
setTempData()
setTempData();
firstMount.current = true;
}
}, [groupId, members, filterMode, hide, isPrivate]);
@ -428,19 +455,16 @@ export const GroupMail = ({
}
}, []);
let listOfThreadsToDisplay = recentThreads;
if (filterMode === "Newest" || filterMode === "Oldest") {
if (filterMode === 'Newest' || filterMode === 'Oldest') {
listOfThreadsToDisplay = allThreads;
}
const onSubmitNewThread = useCallback(
(val: any) => {
if (filterMode === "Recently active") {
if (filterMode === 'Recently active') {
setRecentThreads((prev) => [val, ...prev]);
} else if (filterMode === "Newest") {
} else if (filterMode === 'Newest') {
setAllThreads((prev) => [val, ...prev]);
}
},
@ -461,72 +485,77 @@ export const GroupMail = ({
setIsOpenFilterList(false);
};
const refetchThreadsLists = useCallback(()=> {
if (filterMode === "Recently active") {
const refetchThreadsLists = useCallback(() => {
if (filterMode === 'Recently active') {
getMessages();
} else if (filterMode === "Newest") {
getAllThreads(groupId, "Newest", true);
} else if (filterMode === "Oldest") {
getAllThreads(groupId, "Oldest", true);
} else if (filterMode === 'Newest') {
getAllThreads(groupId, 'Newest', true);
} else if (filterMode === 'Oldest') {
getAllThreads(groupId, 'Oldest', true);
}
}, [filterMode, isPrivate])
}, [filterMode, isPrivate]);
const updateThreadActivityCurrentThread = ()=> {
if(!currentThread) return
const thread = currentThread
const updateThreadActivityCurrentThread = () => {
if (!currentThread) return;
const thread = currentThread;
updateThreadActivity({
threadId: thread?.threadId, qortalName: thread?.threadData?.name, groupId: groupId, thread: thread
})
}
threadId: thread?.threadId,
qortalName: thread?.threadData?.name,
groupId: groupId,
thread: thread,
});
};
const setThreadFunc = (data)=> {
const thread = data
const setThreadFunc = (data) => {
const thread = data;
setCurrentThread(thread);
if(thread?.threadId && thread?.threadData?.name){
updateThreadActivity({
threadId: thread?.threadId, qortalName: thread?.threadData?.name, groupId: groupId, thread: thread
})
}
if (thread?.threadId && thread?.threadData?.name) {
updateThreadActivity({
threadId: thread?.threadId,
qortalName: thread?.threadData?.name,
groupId: groupId,
thread: thread,
});
}
setTimeout(() => {
executeEvent("threadFetchMode", {
mode: "last-page"
executeEvent('threadFetchMode', {
mode: 'last-page',
});
}, 300);
}
};
useEffect(()=> {
if(defaultThread){
setThreadFunc(defaultThread)
setDefaultThread(null)
useEffect(() => {
if (defaultThread) {
setThreadFunc(defaultThread);
setDefaultThread(null);
}
}, [defaultThread])
}, [defaultThread]);
const combinedListTempAndReal = useMemo(() => {
// Combine the two lists
const transformTempPublishedList = tempPublishedList.map((item)=> {
const transformTempPublishedList = tempPublishedList.map((item) => {
return {
...item,
threadData: item.tempData,
threadOwner: item?.name,
threadId: item.identifier
}
})
threadId: item.identifier,
};
});
const combined = [...transformTempPublishedList, ...listOfThreadsToDisplay];
// Remove duplicates based on the "identifier"
const uniqueItems = new Map();
combined.forEach(item => {
uniqueItems.set(item.threadId, item); // This will overwrite duplicates, keeping the last occurrence
combined.forEach((item) => {
uniqueItems.set(item.threadId, item); // This will overwrite duplicates, keeping the last occurrence
});
// Convert the map back to an array and sort by "created" timestamp in descending order
const sortedList = Array.from(uniqueItems.values()).sort((a, b) =>
filterMode === 'Oldest'
? a.threadData?.createdAt - b.threadData?.createdAt
: b.threadData?.createdAt - a.threadData?.createdAt
);
filterMode === 'Oldest'
? a.threadData?.createdAt - b.threadData?.createdAt
: b.threadData?.createdAt - a.threadData?.createdAt
);
return sortedList;
}, [tempPublishedList, listOfThreadsToDisplay, filterMode]);
@ -548,9 +577,9 @@ export const GroupMail = ({
return (
<GroupContainer
sx={{
position: "relative",
overflow: "auto",
width: "100%",
position: 'relative',
overflow: 'auto',
width: '100%',
}}
>
<Popover
@ -558,19 +587,19 @@ export const GroupMail = ({
anchorEl={anchorElInstanceFilter.current}
onClose={handleCloseThreadFilterList}
anchorOrigin={{
vertical: "bottom",
horizontal: "right",
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: "top",
horizontal: "right",
vertical: 'top',
horizontal: 'right',
}}
>
<InstanceListParent
sx={{
minHeight: "unset",
width: "auto",
padding: "0px",
minHeight: 'unset',
width: 'auto',
padding: '0px',
}}
>
<InstanceListHeader></InstanceListHeader>
@ -583,7 +612,7 @@ export const GroupMail = ({
}}
sx={{
backgroundColor:
filterMode === filter ? "rgba(74, 158, 244, 1)" : "unset",
filterMode === filter ? 'rgba(74, 158, 244, 1)' : 'unset',
}}
key={filter}
>
@ -606,12 +635,11 @@ export const GroupMail = ({
</Popover>
<ThreadContainerFullWidth>
<ThreadContainer>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<NewThread
@ -626,7 +654,7 @@ export const GroupMail = ({
/>
<ComposeContainerBlank
sx={{
height: "auto",
height: 'auto',
}}
>
{selectedGroup && !currentThread && (
@ -647,17 +675,22 @@ export const GroupMail = ({
</ComposeContainerBlank>
</Box>
<Spacer height="30px" />
<Box sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between'
}}>
<AllThreadP>{filterMode}</AllThreadP>
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<AllThreadP>{filterMode}</AllThreadP>
<RefreshIcon onClick={refetchThreadsLists} sx={{
color: 'white',
cursor: 'pointer'
}} />
<RefreshIcon
onClick={refetchThreadsLists}
sx={{
color: 'white',
cursor: 'pointer',
}}
/>
</Box>
<Spacer height="30px" />
@ -668,112 +701,119 @@ export const GroupMail = ({
];
const shouldAppearLighter =
hasViewedRecent &&
filterMode === "Recently active" &&
filterMode === 'Recently active' &&
thread?.threadData?.createdAt < hasViewedRecent?.timestamp;
return (
<SingleThreadParent
sx={{
flexWrap: 'wrap',
gap: '15px',
height: 'auto'
}}
sx={{
flexWrap: 'wrap',
gap: '15px',
height: 'auto',
}}
onClick={() => {
setCurrentThread(thread);
if(thread?.threadId && thread?.threadData?.name){
if (thread?.threadId && thread?.threadData?.name) {
updateThreadActivity({
threadId: thread?.threadId, qortalName: thread?.threadData?.name, groupId: groupId, thread: thread
})
threadId: thread?.threadId,
qortalName: thread?.threadData?.name,
groupId: groupId,
thread: thread,
});
}
}}
>
<Avatar
sx={{
height: "50px",
width: "50px",
height: '50px',
width: '50px',
}}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${thread?.threadData?.name}/qortal_avatar?async=true`}
alt={thread?.threadData?.name}
>
{thread?.threadData?.name?.charAt(0)}
</Avatar>
<ThreadInfoColumn>
<ThreadInfoColumnNameP>
<ThreadInfoColumnbyP>by </ThreadInfoColumnbyP>
{thread?.threadData?.name}
</ThreadInfoColumnNameP>
<ThreadInfoColumnTime>
{formatTimestamp(thread?.threadData?.createdAt)}
</ThreadInfoColumnTime>
</ThreadInfoColumn>
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
width: '100%'
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
width: '100%',
}}
>
<ThreadSingleTitle
sx={{
fontWeight: shouldAppearLighter && 300,
fontSize: isMobile && '18px'
fontSize: isMobile && '18px',
}}
>
{thread?.threadData?.title}
</ThreadSingleTitle>
<Spacer height="10px" />
{filterMode === "Recently active" && (
{filterMode === 'Recently active' && (
<div
style={{
display: "flex",
alignItems: "center",
display: 'flex',
alignItems: 'center',
}}
>
<ThreadSingleLastMessageP>
<ThreadSingleLastMessageSpanP>
last message:{" "}
last message:{' '}
</ThreadSingleLastMessageSpanP>
{formatDate(thread?.created)}
</ThreadSingleLastMessageP>
</div>
)}
</div>
<Box onClick={()=> {
setTimeout(() => {
executeEvent("threadFetchMode", {
mode: "last-page"
});
}, 300);
}} sx={{
position: 'absolute',
bottom: '2px',
right: '2px',
borderRadius: '5px',
backgroundColor: '#27282c',
display: 'flex',
gap: '10px',
alignItems: 'center',
padding: '5px',
cursor: 'pointer',
'&:hover': {
background: 'rgba(255, 255, 255, 0.60)'
}
}}>
<Typography sx={{
color: 'white',
fontSize: '12px'
}}>Last page</Typography>
<ArrowForwardIosIcon sx={{
color: 'white',
fontSize: '12px'
}} />
<Box
onClick={() => {
setTimeout(() => {
executeEvent('threadFetchMode', {
mode: 'last-page',
});
}, 300);
}}
sx={{
position: 'absolute',
bottom: '2px',
right: '2px',
borderRadius: '5px',
backgroundColor: '#27282c',
display: 'flex',
gap: '10px',
alignItems: 'center',
padding: '5px',
cursor: 'pointer',
'&:hover': {
background: 'rgba(255, 255, 255, 0.60)',
},
}}
>
<Typography
sx={{
color: 'white',
fontSize: '12px',
}}
>
Last page
</Typography>
<ArrowForwardIosIcon
sx={{
color: 'white',
fontSize: '12px',
}}
/>
</Box>
</SingleThreadParent>
);
@ -781,12 +821,12 @@ export const GroupMail = ({
<Box
sx={{
width: "100%",
justifyContent: "center",
width: '100%',
justifyContent: 'center',
}}
>
{listOfThreadsToDisplay.length >= 20 &&
filterMode !== "Recently active" && (
filterMode !== 'Recently active' && (
<LazyLoad
onLoadMore={() => getAllThreads(groupId, filterMode, false)}
></LazyLoad>
@ -797,7 +837,7 @@ export const GroupMail = ({
<LoadingSnackbar
open={isLoading}
info={{
message: "Loading threads... please wait.",
message: 'Loading threads... please wait.',
}}
/>
</GroupContainer>

View File

@ -1,11 +1,17 @@
import React, { useEffect, useRef, useState } from "react";
import { Box, Button, CircularProgress, Input, Typography } from "@mui/material";
import ShortUniqueId from "short-unique-id";
import CloseIcon from "@mui/icons-material/Close";
import React, { useEffect, useRef, useState } from 'react';
import {
Box,
Button,
CircularProgress,
Input,
Typography,
} from '@mui/material';
import ShortUniqueId from 'short-unique-id';
import CloseIcon from '@mui/icons-material/Close';
import ModalCloseSVG from "../../../assets/svgs/ModalClose.svg";
import ModalCloseSVG from '../../../assets/svgs/ModalClose.svg';
import ComposeIconSVG from "../../../assets/svgs/ComposeIcon.svg";
import ComposeIconSVG from '../../../assets/svgs/ComposeIcon.svg';
import {
AttachmentContainer,
@ -22,20 +28,25 @@ import {
NewMessageInputRow,
NewMessageSendButton,
NewMessageSendP,
} from "./Mail-styles";
} from './Mail-styles';
import { ReusableModal } from "./ReusableModal";
import { Spacer } from "../../../common/Spacer";
import { formatBytes } from "../../../utils/Size";
import { CreateThreadIcon } from "../../../assets/svgs/CreateThreadIcon";
import { SendNewMessage } from "../../../assets/svgs/SendNewMessage";
import { TextEditor } from "./TextEditor";
import { MyContext, isMobile, pauseAllQueues, resumeAllQueues } from "../../../App";
import { getFee } from "../../../background";
import TipTap from "../../Chat/TipTap";
import { MessageDisplay } from "../../Chat/MessageDisplay";
import { CustomizedSnackbars } from "../../Snackbar/Snackbar";
import { saveTempPublish } from "../../Chat/GroupAnnouncements";
import { ReusableModal } from './ReusableModal';
import { Spacer } from '../../../common/Spacer';
import { formatBytes } from '../../../utils/Size';
import { CreateThreadIcon } from '../../../assets/svgs/CreateThreadIcon';
import { SendNewMessage } from '../../../assets/svgs/SendNewMessage';
import { TextEditor } from './TextEditor';
import {
MyContext,
isMobile,
pauseAllQueues,
resumeAllQueues,
} from '../../../App';
import { getFee } from '../../../background';
import TipTap from '../../Chat/TipTap';
import { MessageDisplay } from '../../Chat/MessageDisplay';
import { CustomizedSnackbars } from '../../Snackbar/Snackbar';
import { saveTempPublish } from '../../Chat/GroupAnnouncements';
const uid = new ShortUniqueId({ length: 8 });
@ -54,21 +65,21 @@ export function objectToBase64(obj: any) {
const jsonString = JSON.stringify(obj);
// Step 2: Create a Blob from the JSON string
const blob = new Blob([jsonString], { type: "application/json" });
const blob = new Blob([jsonString], { type: 'application/json' });
// Step 3: Create a FileReader to read the Blob as a base64-encoded string
return new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => {
if (typeof reader.result === "string") {
if (typeof reader.result === 'string') {
// Remove 'data:application/json;base64,' prefix
const base64 = reader.result.replace(
"data:application/json;base64,",
""
'data:application/json;base64,',
''
);
resolve(base64);
} else {
reject(new Error("Failed to read the Blob as a base64-encoded string"));
reject(new Error('Failed to read the Blob as a base64-encoded string'));
}
};
reader.onerror = () => {
@ -94,10 +105,11 @@ export const publishGroupEncryptedResource = async ({
identifier,
}) => {
return new Promise((res, rej) => {
window.sendMessage("publishGroupEncryptedResource", {
encryptedData,
identifier,
})
window
.sendMessage('publishGroupEncryptedResource', {
encryptedData,
identifier,
})
.then((response) => {
if (!response?.error) {
res(response);
@ -106,19 +118,19 @@ export const publishGroupEncryptedResource = async ({
rej(response.error);
})
.catch((error) => {
rej(error.message || "An error occurred");
rej(error.message || 'An error occurred');
});
});
};
export const encryptSingleFunc = async (data: string, secretKeyObject: any) => {
try {
return new Promise((res, rej) => {
window.sendMessage("encryptSingle", {
data,
secretKeyObject,
})
window
.sendMessage('encryptSingle', {
data,
secretKeyObject,
})
.then((response) => {
if (!response?.error) {
res(response);
@ -127,11 +139,12 @@ export const encryptSingleFunc = async (data: string, secretKeyObject: any) => {
rej(response.error);
})
.catch((error) => {
rej(error.message || "An error occurred");
rej(error.message || 'An error occurred');
});
});
} catch (error) {}
} catch (error) {
console.log(error);
}
};
export const NewThread = ({
groupInfo,
@ -145,14 +158,14 @@ export const NewThread = ({
postReply,
myName,
setPostReply,
isPrivate
isPrivate,
}: NewMessageProps) => {
const { show } = React.useContext(MyContext);
const [isOpen, setIsOpen] = useState<boolean>(false);
const [value, setValue] = useState("");
const [value, setValue] = useState('');
const [isSending, setIsSending] = useState(false);
const [threadTitle, setThreadTitle] = useState<string>("");
const [threadTitle, setThreadTitle] = useState<string>('');
const [openSnack, setOpenSnack] = React.useState(false);
const [infoSnack, setInfoSnack] = React.useState(null);
const editorRef = useRef(null);
@ -168,44 +181,42 @@ export const NewThread = ({
const closeModal = () => {
setIsOpen(false);
setValue("");
if(setPostReply){
setPostReply(null)
setValue('');
if (setPostReply) {
setPostReply(null);
}
};
async function publishQDNResource() {
try {
pauseAllQueues()
if(isSending) return
setIsSending(true)
let name: string = "";
let errorMsg = "";
pauseAllQueues();
if (isSending) return;
setIsSending(true);
let name: string = '';
let errorMsg = '';
name = userInfo?.name || "";
name = userInfo?.name || '';
const missingFields: string[] = [];
if (!isMessage && !threadTitle) {
errorMsg = "Please provide a thread title";
errorMsg = 'Please provide a thread title';
}
if (!name) {
errorMsg = "Cannot send a message without a access to your name";
errorMsg = 'Cannot send a message without a access to your name';
}
if (!groupInfo) {
errorMsg = "Cannot access group information";
errorMsg = 'Cannot access group information';
}
// if (!description) missingFields.push('subject')
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(", ");
const missingFieldsString = missingFields.join(', ');
const errMsg = `Missing: ${missingFieldsString}`;
errorMsg = errMsg;
}
if (errorMsg) {
// dispatch(
// setNotification({
@ -217,17 +228,17 @@ export const NewThread = ({
}
const htmlContent = editorRef.current.getHTML();
if (!htmlContent?.trim() || htmlContent?.trim() === "<p></p>")
throw new Error("Please provide a first message to the thread");
const fee = await getFee("ARBITRARY");
if (!htmlContent?.trim() || htmlContent?.trim() === '<p></p>')
throw new Error('Please provide a first message to the thread');
const fee = await getFee('ARBITRARY');
let feeToShow = fee.fee;
if (!isMessage) {
feeToShow = +feeToShow * 2;
}
await show({
message: "Would you like to perform a ARBITRARY transaction?",
publishFee: feeToShow + " QORT",
message: 'Would you like to perform a ARBITRARY transaction?',
publishFee: feeToShow + ' QORT',
});
let reply = null;
@ -245,20 +256,21 @@ export const NewThread = ({
threadOwner: currentThread?.threadData?.name || name,
reply,
};
const secretKey = isPrivate === false ? null : await getSecretKey(false, true);
const secretKey =
isPrivate === false ? null : await getSecretKey(false, true);
if (!secretKey && isPrivate) {
throw new Error("Cannot get group secret key");
throw new Error('Cannot get group secret key');
}
if (!isMessage) {
const idThread = uid.rnd();
const idMsg = uid.rnd();
const messageToBase64 = await objectToBase64(mailObject);
const encryptSingleFirstPost = isPrivate === false ? messageToBase64 : await encryptSingleFunc(
messageToBase64,
secretKey
);
const encryptSingleFirstPost =
isPrivate === false
? messageToBase64
: await encryptSingleFunc(messageToBase64, secretKey);
const threadObject = {
title: threadTitle,
groupId: groupInfo.id,
@ -267,10 +279,10 @@ export const NewThread = ({
};
const threadToBase64 = await objectToBase64(threadObject);
const encryptSingleThread = isPrivate === false ? threadToBase64 : await encryptSingleFunc(
threadToBase64,
secretKey
);
const encryptSingleThread =
isPrivate === false
? threadToBase64
: await encryptSingleFunc(threadToBase64, secretKey);
let identifierThread = `grp-${groupInfo.groupId}-thread-${idThread}`;
await publishGroupEncryptedResource({
identifier: identifierThread,
@ -288,23 +300,27 @@ export const NewThread = ({
service: 'DOCUMENT',
tempData: threadObject,
created: Date.now(),
groupId: groupInfo.groupId
}
groupId: groupInfo.groupId,
};
const dataToSaveToStoragePost = {
name: myName,
identifier: identifierPost,
service: 'DOCUMENT',
tempData: mailObject,
created: Date.now(),
threadId: identifierThread
}
await saveTempPublish({data: dataToSaveToStorage, key: 'thread'})
await saveTempPublish({data: dataToSaveToStoragePost, key: 'thread-post'})
setInfoSnack({
type: "success",
message: "Successfully created thread. It may take some time for the publish to propagate",
threadId: identifierThread,
};
await saveTempPublish({ data: dataToSaveToStorage, key: 'thread' });
await saveTempPublish({
data: dataToSaveToStoragePost,
key: 'thread-post',
});
setOpenSnack(true)
setInfoSnack({
type: 'success',
message:
'Successfully created thread. It may take some time for the publish to propagate',
});
setOpenSnack(true);
// dispatch(
// setNotification({
@ -313,35 +329,36 @@ export const NewThread = ({
// })
// );
if (publishCallback) {
publishCallback()
publishCallback();
}
closeModal();
} else {
if (!currentThread) throw new Error("unable to locate thread Id");
if (!currentThread) throw new Error('unable to locate thread Id');
const idThread = currentThread.threadId;
const messageToBase64 = await objectToBase64(mailObject);
const encryptSinglePost = isPrivate === false ? messageToBase64 : await encryptSingleFunc(
messageToBase64,
secretKey
);
const encryptSinglePost =
isPrivate === false
? messageToBase64
: await encryptSingleFunc(messageToBase64, secretKey);
const idMsg = uid.rnd();
let identifier = `thmsg-${idThread}-${idMsg}`;
const res = await publishGroupEncryptedResource({
identifier: identifier,
encryptedData: encryptSinglePost,
});
const dataToSaveToStoragePost = {
threadId: idThread,
name: myName,
identifier: identifier,
service: 'DOCUMENT',
tempData: mailObject,
created: Date.now()
}
await saveTempPublish({data: dataToSaveToStoragePost, key: 'thread-post'})
created: Date.now(),
};
await saveTempPublish({
data: dataToSaveToStoragePost,
key: 'thread-post',
});
// await qortalRequest(multiplePublishMsg);
// dispatch(
// setNotification({
@ -350,12 +367,13 @@ export const NewThread = ({
// })
// );
setInfoSnack({
type: "success",
message: "Successfully created post. It may take some time for the publish to propagate",
type: 'success',
message:
'Successfully created post. It may take some time for the publish to propagate',
});
setOpenSnack(true)
if(publishCallback){
publishCallback()
setOpenSnack(true);
if (publishCallback) {
publishCallback();
}
// messageCallback({
// identifier,
@ -369,17 +387,16 @@ export const NewThread = ({
closeModal();
} catch (error: any) {
if(error?.message){
if (error?.message) {
setInfoSnack({
type: "error",
type: 'error',
message: error?.message,
});
setOpenSnack(true)
setOpenSnack(true);
}
} finally {
setIsSending(false);
resumeAllQueues()
resumeAllQueues();
}
}
@ -389,56 +406,59 @@ export const NewThread = ({
return (
<Box
sx={{
display: "flex",
display: 'flex',
}}
>
<ComposeContainer
sx={{
padding: isMobile ? '5px' : "15px",
justifyContent: isMobile ? 'flex-start' : 'revert'
padding: isMobile ? '5px' : '15px',
justifyContent: isMobile ? 'flex-start' : 'revert',
}}
onClick={() => setIsOpen(true)}
>
<ComposeIcon src={ComposeIconSVG} />
<ComposeP>{currentThread ? "New Post" : "New Thread"}</ComposeP>
<ComposeP>{currentThread ? 'New Post' : 'New Thread'}</ComposeP>
</ComposeContainer>
<ReusableModal
open={isOpen}
customStyles={{
maxHeight: isMobile ? '95svh' : "95vh",
maxWidth: "950px",
height: "700px",
borderRadius: "12px 12px 0px 0px",
background: "#434448",
padding: "0px",
gap: "0px",
maxHeight: isMobile ? '95svh' : '95vh',
maxWidth: '950px',
height: '700px',
borderRadius: '12px 12px 0px 0px',
background: '#434448',
padding: '0px',
gap: '0px',
}}
>
<InstanceListHeader
sx={{
height: isMobile ? 'auto' : "50px",
padding: isMobile ? '5px' : "20px 42px",
flexDirection: "row",
height: isMobile ? 'auto' : '50px',
padding: isMobile ? '5px' : '20px 42px',
flexDirection: 'row',
alignItems: 'center',
justifyContent: "space-between",
backgroundColor: "#434448",
justifyContent: 'space-between',
backgroundColor: '#434448',
}}
>
<NewMessageHeaderP>
{isMessage ? "Post Message" : "New Thread"}
{isMessage ? 'Post Message' : 'New Thread'}
</NewMessageHeaderP>
<CloseContainer sx={{
height: '40px'
}} onClick={closeModal}>
<CloseContainer
sx={{
height: '40px',
}}
onClick={closeModal}
>
<NewMessageCloseImg src={ModalCloseSVG} />
</CloseContainer>
</InstanceListHeader>
<InstanceListContainer
sx={{
backgroundColor: "#434448",
padding: isMobile ? '5px' : "20px 42px",
height: "calc(100% - 165px)",
backgroundColor: '#434448',
padding: isMobile ? '5px' : '20px 42px',
height: 'calc(100% - 165px)',
flexShrink: 0,
}}
>
@ -457,19 +477,19 @@ export const NewThread = ({
autoComplete="off"
autoCorrect="off"
sx={{
width: "100%",
color: "white",
"& .MuiInput-input::placeholder": {
color: "rgba(255,255,255, 0.70) !important",
fontSize: isMobile ? '14px' : "20px",
fontStyle: "normal",
width: '100%',
color: 'white',
'& .MuiInput-input::placeholder': {
color: 'rgba(255,255,255, 0.70) !important',
fontSize: isMobile ? '14px' : '20px',
fontStyle: 'normal',
fontWeight: 400,
lineHeight: "120%", // 24px
letterSpacing: "0.15px",
lineHeight: '120%', // 24px
letterSpacing: '0.15px',
opacity: 1,
},
"&:focus": {
outline: "none",
'&:focus': {
outline: 'none',
},
// Add any additional styles for the input here
}}
@ -481,21 +501,18 @@ export const NewThread = ({
{postReply && postReply.textContentV2 && (
<Box
sx={{
width: "100%",
maxHeight: "120px",
overflow: "auto",
width: '100%',
maxHeight: '120px',
overflow: 'auto',
}}
>
<MessageDisplay htmlContent={postReply?.textContentV2} />
</Box>
)}
{!isMobile && (
<Spacer height="30px" />
)}
{!isMobile && <Spacer height="30px" />}
<Box
sx={{
maxHeight: "40vh",
maxHeight: '40vh',
}}
>
<TipTap
@ -515,41 +532,44 @@ export const NewThread = ({
</InstanceListContainer>
<InstanceFooter
sx={{
backgroundColor: "#434448",
padding: isMobile ? '5px' : "20px 42px",
alignItems: "center",
height: isMobile ? 'auto' : "90px",
backgroundColor: '#434448',
padding: isMobile ? '5px' : '20px 42px',
alignItems: 'center',
height: isMobile ? 'auto' : '90px',
}}
>
<NewMessageSendButton onClick={sendMail}>
{isSending && (
<Box sx={{height: '100%', position: 'absolute', width: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center'}}>
<CircularProgress sx={{
}} size={'12px'} />
<Box
sx={{
height: '100%',
position: 'absolute',
width: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<CircularProgress sx={{}} size={'12px'} />
</Box>
)}
<NewMessageSendP>
{isMessage ? "Post" : "Create Thread"}
{isMessage ? 'Post' : 'Create Thread'}
</NewMessageSendP>
{isMessage ? (
<SendNewMessage
opacity={1}
height="25px"
width="25px"
/>
<SendNewMessage opacity={1} height="25px" width="25px" />
) : (
<CreateThreadIcon
opacity={1}
height="25px"
width="25px"
/>
<CreateThreadIcon opacity={1} height="25px" width="25px" />
)}
</NewMessageSendButton>
</InstanceFooter>
</ReusableModal>
<CustomizedSnackbars open={openSnack} setOpen={setOpenSnack} info={infoSnack} setInfo={setInfoSnack} />
<CustomizedSnackbars
open={openSnack}
setOpen={setOpenSnack}
info={infoSnack}
setInfo={setInfoSnack}
/>
</Box>
);
};

View File

@ -195,7 +195,9 @@ export const Thread = ({
[message.identifier]: fullObject,
};
});
} catch (error) {}
} catch (error) {
console.log(error);
}
};
const setTempData = async () => {
@ -216,7 +218,9 @@ export const Thread = ({
});
setTempPublishedList(tempData);
}
} catch (error) {}
} catch (error) {
console.log(error);
}
};
const getMailMessages = React.useCallback(
@ -461,7 +465,9 @@ export const Thread = ({
} else {
fullArrayMsg.unshift(fullObject);
}
} catch (error) {}
} catch (error) {
console.log(error);
}
}
setMessages(fullArrayMsg);
} catch (error) {

View File

@ -1,4 +1,4 @@
import { LoadingButton } from "@mui/lab";
import { LoadingButton } from '@mui/lab';
import {
Box,
Button,
@ -6,45 +6,46 @@ import {
MenuItem,
Select,
SelectChangeEvent,
} from "@mui/material";
import React, { useState } from "react";
import { Spacer } from "../../common/Spacer";
import { Label } from "./AddGroup";
import { getFee } from "../../background";
} from '@mui/material';
import React, { useState } from 'react';
import { Spacer } from '../../common/Spacer';
import { Label } from './AddGroup';
import { getFee } from '../../background';
export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
const [value, setValue] = useState("");
const [value, setValue] = useState('');
const [expiryTime, setExpiryTime] = useState<string>('259200');
const [isLoadingInvite, setIsLoadingInvite] = useState(false)
const [isLoadingInvite, setIsLoadingInvite] = useState(false);
const inviteMember = async () => {
try {
const fee = await getFee('GROUP_INVITE')
const fee = await getFee('GROUP_INVITE');
await show({
message: "Would you like to perform a GROUP_INVITE transaction?" ,
publishFee: fee.fee + ' QORT'
})
setIsLoadingInvite(true)
message: 'Would you like to perform a GROUP_INVITE transaction?',
publishFee: fee.fee + ' QORT',
});
setIsLoadingInvite(true);
if (!expiryTime || !value) return;
new Promise((res, rej) => {
window.sendMessage("inviteToGroup", {
groupId,
qortalAddress: value,
inviteTime: +expiryTime,
})
window
.sendMessage('inviteToGroup', {
groupId,
qortalAddress: value,
inviteTime: +expiryTime,
})
.then((response) => {
if (!response?.error) {
setInfoSnack({
type: "success",
type: 'success',
message: `Successfully invited ${value}. It may take a couple of minutes for the changes to propagate`,
});
setOpenSnack(true);
res(response);
setValue("");
setValue('');
return;
}
setInfoSnack({
type: "error",
type: 'error',
message: response?.error,
});
setOpenSnack(true);
@ -52,16 +53,17 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
})
.catch((error) => {
setInfoSnack({
type: "error",
message: error?.message || "An error occurred",
type: 'error',
message: error?.message || 'An error occurred',
});
setOpenSnack(true);
rej(error);
});
});
} catch (error) {} finally {
setIsLoadingInvite(false)
} catch (error) {
console.log(error);
} finally {
setIsLoadingInvite(false);
}
};
@ -72,8 +74,8 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
return (
<Box
sx={{
display: "flex",
flexDirection: "column",
display: 'flex',
flexDirection: 'column',
}}
>
Invite member
@ -83,8 +85,7 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
placeholder="Name or address"
onChange={(e) => setValue(e.target.value)}
/>
<Spacer height="20px" />
<Spacer height="20px" />
<Label>Invitation Expiry Time</Label>
<Select
labelId="demo-simple-select-label"
@ -105,7 +106,14 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
<MenuItem value={2592000}>30 days</MenuItem>
</Select>
<Spacer height="20px" />
<LoadingButton variant="contained" loadingPosition="start" loading={isLoadingInvite} onClick={inviteMember}>Invite</LoadingButton>
<LoadingButton
variant="contained"
loadingPosition="start"
loading={isLoadingInvite}
onClick={inviteMember}
>
Invite
</LoadingButton>
</Box>
);
};

View File

@ -4,7 +4,7 @@ import React, {
useEffect,
useRef,
useState,
} from "react";
} from 'react';
import {
Avatar,
Box,
@ -25,44 +25,44 @@ import {
Select,
TextField,
Typography,
} from "@mui/material";
} from '@mui/material';
import { getNameInfo } from "./Group";
import { getBaseApi, getFee } from "../../background";
import { LoadingButton } from "@mui/lab";
import LockIcon from "@mui/icons-material/Lock";
import NoEncryptionGmailerrorredIcon from "@mui/icons-material/NoEncryptionGmailerrorred";
import { getNameInfo } from './Group';
import { getBaseApi, getFee } from '../../background';
import { LoadingButton } from '@mui/lab';
import LockIcon from '@mui/icons-material/Lock';
import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmailerrorred';
import {
MyContext,
getArbitraryEndpointReact,
getBaseApiReact,
isMobile,
} from "../../App";
import { Spacer } from "../../common/Spacer";
import { CustomLoader } from "../../common/CustomLoader";
import { RequestQueueWithPromise } from "../../utils/queue/queue";
import { useRecoilState } from "recoil";
} from '../../App';
import { Spacer } from '../../common/Spacer';
import { CustomLoader } from '../../common/CustomLoader';
import { RequestQueueWithPromise } from '../../utils/queue/queue';
import { useRecoilState } from 'recoil';
import {
myGroupsWhereIAmAdminAtom,
promotionTimeIntervalAtom,
promotionsAtom,
} from "../../atoms/global";
import { Label } from "./AddGroup";
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";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
} from '../../atoms/global';
import { Label } from './AddGroup';
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';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
export const requestQueuePromos = new RequestQueueWithPromise(20);
export function utf8ToBase64(inputString: string): string {
// Encode the string as UTF-8
const utf8String = encodeURIComponent(inputString).replace(
/%([0-9A-F]{2})/g,
(match, p1) => String.fromCharCode(Number("0x" + p1))
(match, p1) => String.fromCharCode(Number('0x' + p1))
);
// Convert the UTF-8 encoded string to base64
@ -83,7 +83,7 @@ export const ListOfGroupPromotions = () => {
const [selectedGroup, setSelectedGroup] = useState(null);
const [loading, setLoading] = useState(false);
const [isShowModal, setIsShowModal] = useState(false);
const [text, setText] = useState("");
const [text, setText] = useState('');
const [myGroupsWhereIAmAdmin, setMyGroupsWhereIAmAdmin] = useRecoilState(
myGroupsWhereIAmAdminAtom
);
@ -115,10 +115,12 @@ export const ListOfGroupPromotions = () => {
useEffect(() => {
try {
(async () => {
const feeRes = await getFee("ARBITRARY");
const feeRes = await getFee('ARBITRARY');
setFee(feeRes?.fee);
})();
} catch (error) {}
} catch (error) {
console.log(error);
}
}, []);
const getPromotions = useCallback(async () => {
try {
@ -126,9 +128,9 @@ export const ListOfGroupPromotions = () => {
const identifier = `group-promotions-ui24-`;
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=100&includemetadata=false&reverse=true&prefix=true`;
const response = await fetch(url, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
const responseData = await response.json();
@ -142,7 +144,7 @@ export const ListOfGroupPromotions = () => {
promo.name
}/${promo.identifier}`;
const response = await fetch(url, {
method: "GET",
method: 'GET',
});
try {
@ -164,7 +166,7 @@ export const ListOfGroupPromotions = () => {
}
}
} catch (error) {
console.error("Error fetching promo:", error);
console.error('Error fetching promo:', error);
}
});
}
@ -222,10 +224,10 @@ export const ListOfGroupPromotions = () => {
await new Promise((res, rej) => {
window
.sendMessage("publishOnQDN", {
.sendMessage('publishOnQDN', {
data: data,
identifier: identifier,
service: "DOCUMENT",
service: 'DOCUMENT',
})
.then((response) => {
if (!response?.error) {
@ -235,23 +237,23 @@ export const ListOfGroupPromotions = () => {
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 promotion. It may take a couple of minutes for the promotion to appear",
'Successfully published promotion. It may take a couple of minutes for the promotion to appear',
});
setOpenSnack(true);
setText("");
setText('');
setSelectedGroup(null);
setIsShowModal(false);
} catch (error) {
setInfoSnack({
type: "error",
type: 'error',
message:
error?.message || "Error publishing the promotion. Please try again",
error?.message || 'Error publishing the promotion. Please try again',
});
setOpenSnack(true);
} finally {
@ -262,30 +264,30 @@ export const ListOfGroupPromotions = () => {
const handleJoinGroup = async (group, isOpen) => {
try {
const groupId = group.groupId;
const fee = await getFee("JOIN_GROUP");
const fee = await getFee('JOIN_GROUP');
await show({
message: "Would you like to perform an JOIN_GROUP transaction?",
publishFee: fee.fee + " QORT",
message: 'Would you like to perform an JOIN_GROUP transaction?',
publishFee: fee.fee + ' QORT',
});
setIsLoadingJoinGroup(true);
await new Promise((res, rej) => {
window
.sendMessage("joinGroup", {
.sendMessage('joinGroup', {
groupId,
})
.then((response) => {
if (!response?.error) {
setInfoSnack({
type: "success",
type: 'success',
message:
"Successfully requested to join group. It may take a couple of minutes for the changes to propagate",
'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",
type: 'joined-group',
label: `Joined Group ${group?.groupName}: awaiting confirmation`,
labelDone: `Joined Group ${group?.groupName}: success!`,
done: false,
@ -297,7 +299,7 @@ export const ListOfGroupPromotions = () => {
setTxList((prev) => [
{
...response,
type: "joined-group-request",
type: 'joined-group-request',
label: `Requested to join Group ${group?.groupName}: awaiting confirmation`,
labelDone: `Requested to join Group ${group?.groupName}: success!`,
done: false,
@ -313,7 +315,7 @@ export const ListOfGroupPromotions = () => {
return;
} else {
setInfoSnack({
type: "error",
type: 'error',
message: response?.error,
});
setOpenSnack(true);
@ -322,8 +324,8 @@ export const ListOfGroupPromotions = () => {
})
.catch((error) => {
setInfoSnack({
type: "error",
message: error.message || "An error occurred",
type: 'error',
message: error.message || 'An error occurred',
});
setOpenSnack(true);
rej(error);
@ -339,55 +341,58 @@ export const ListOfGroupPromotions = () => {
return (
<Box
sx={{
width: "100%",
display: "flex",
marginTop: "20px",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
width: '100%',
display: 'flex',
marginTop: '20px',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Box sx={{
display: 'flex',
gap: '20px',
width: '100%',
justifyContent: 'space-between'
}}>
<Box
sx={{
display: 'flex',
gap: '20px',
width: '100%',
justifyContent: 'space-between',
}}
>
<ButtonBase
sx={{
display: "flex",
flexDirection: "row",
padding: `0px ${isExpanded ? "24px" : "20px"}`,
gap: "10px",
justifyContent: "flex-start",
alignSelf: isExpanded && "flex-start",
display: 'flex',
flexDirection: 'row',
padding: `0px ${isExpanded ? '24px' : '20px'}`,
gap: '10px',
justifyContent: 'flex-start',
alignSelf: isExpanded && 'flex-start',
}}
onClick={() => setIsExpanded((prev) => !prev)}
>
<Typography
sx={{
fontSize: "1rem",
fontSize: '1rem',
}}
>
Group promotions {promotions.length > 0 && ` (${promotions.length})`}
Group promotions{' '}
{promotions.length > 0 && ` (${promotions.length})`}
</Typography>
{isExpanded ? (
<ExpandLessIcon
sx={{
marginLeft: "auto",
marginLeft: 'auto',
}}
/>
) : (
<ExpandMoreIcon
sx={{
marginLeft: "auto",
marginLeft: 'auto',
}}
/>
)}
</ButtonBase>
<Box
style={{
width: "330px",
width: '330px',
}}
/>
</Box>
@ -396,24 +401,24 @@ export const ListOfGroupPromotions = () => {
<>
<Box
sx={{
width: isMobile ? "320px" : "750px",
maxWidth: "90%",
display: "flex",
flexDirection: "column",
padding: "0px 20px",
width: isMobile ? '320px' : '750px',
maxWidth: '90%',
display: 'flex',
flexDirection: 'column',
padding: '0px 20px',
}}
>
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
width: '100%',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<Typography
sx={{
fontSize: "13px",
fontSize: '13px',
fontWeight: 600,
}}
></Typography>
@ -421,7 +426,7 @@ export const ListOfGroupPromotions = () => {
variant="contained"
onClick={() => setIsShowModal(true)}
sx={{
fontSize: "12px",
fontSize: '12px',
}}
>
Add Promotion
@ -431,22 +436,22 @@ export const ListOfGroupPromotions = () => {
</Box>
<Box
sx={{
width: isMobile ? "320px" : "750px",
maxWidth: "90%",
maxHeight: "700px",
display: "flex",
flexDirection: "column",
bgcolor: "background.paper",
padding: "20px 0px",
borderRadius: "19px",
width: isMobile ? '320px' : '750px',
maxWidth: '90%',
maxHeight: '700px',
display: 'flex',
flexDirection: 'column',
bgcolor: 'background.paper',
padding: '20px 0px',
borderRadius: '19px',
}}
>
{loading && promotions.length === 0 && (
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "center",
width: '100%',
display: 'flex',
justifyContent: 'center',
}}
>
<CustomLoader />
@ -455,18 +460,18 @@ export const ListOfGroupPromotions = () => {
{!loading && promotions.length === 0 && (
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "center",
alignItems: "center",
height: "100%",
width: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100%',
}}
>
<Typography
sx={{
fontSize: "11px",
fontSize: '11px',
fontWeight: 400,
color: "rgba(255, 255, 255, 0.2)",
color: 'rgba(255, 255, 255, 0.2)',
}}
>
Nothing to display
@ -475,11 +480,11 @@ export const ListOfGroupPromotions = () => {
)}
<div
style={{
height: "600px",
position: "relative",
display: "flex",
flexDirection: "column",
width: "100%",
height: '600px',
position: 'relative',
display: 'flex',
flexDirection: 'column',
width: '100%',
}}
>
<div
@ -487,24 +492,24 @@ export const ListOfGroupPromotions = () => {
className="scrollable-container"
style={{
flexGrow: 1,
overflow: "auto",
position: "relative",
display: "flex",
height: "0px",
overflow: 'auto',
position: 'relative',
display: 'flex',
height: '0px',
}}
>
<div
style={{
height: rowVirtualizer.getTotalSize(),
width: "100%",
width: '100%',
}}
>
<div
style={{
position: "absolute",
position: 'absolute',
top: 0,
left: 0,
width: "100%",
width: '100%',
}}
>
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
@ -516,17 +521,17 @@ export const ListOfGroupPromotions = () => {
ref={rowVirtualizer.measureElement} //measure dynamic row height
key={promotion?.identifier}
style={{
position: "absolute",
position: 'absolute',
top: 0,
left: "50%", // Move to the center horizontally
left: '50%', // Move to the center horizontally
transform: `translateY(${virtualRow.start}px) translateX(-50%)`, // Adjust for centering
width: "100%", // Control width (90% of the parent)
padding: "10px 0",
display: "flex",
alignItems: "center",
overscrollBehavior: "none",
flexDirection: "column",
gap: "5px",
width: '100%', // Control width (90% of the parent)
padding: '10px 0',
display: 'flex',
alignItems: 'center',
overscrollBehavior: 'none',
flexDirection: 'column',
gap: '5px',
}}
>
<ErrorBoundary
@ -538,47 +543,47 @@ export const ListOfGroupPromotions = () => {
>
<Box
sx={{
display: "flex",
flexDirection: "column",
width: "100%",
padding: "0px 20px",
display: 'flex',
flexDirection: 'column',
width: '100%',
padding: '0px 20px',
}}
>
<Popover
open={openPopoverIndex === promotion?.groupId}
anchorEl={popoverAnchor}
onClose={(event, reason) => {
if (reason === "backdropClick") {
if (reason === 'backdropClick') {
// Prevent closing on backdrop click
return;
}
handlePopoverClose(); // Close only on other events like Esc key press
}}
anchorOrigin={{
vertical: "top",
horizontal: "center",
vertical: 'top',
horizontal: 'center',
}}
transformOrigin={{
vertical: "bottom",
horizontal: "center",
vertical: 'bottom',
horizontal: 'center',
}}
style={{ marginTop: "8px" }}
style={{ marginTop: '8px' }}
>
<Box
sx={{
width: "325px",
height: "auto",
maxHeight: "400px",
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "10px",
padding: "10px",
width: '325px',
height: 'auto',
maxHeight: '400px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '10px',
padding: '10px',
}}
>
<Typography
sx={{
fontSize: "13px",
fontSize: '13px',
fontWeight: 600,
}}
>
@ -586,17 +591,17 @@ export const ListOfGroupPromotions = () => {
</Typography>
<Typography
sx={{
fontSize: "13px",
fontSize: '13px',
fontWeight: 600,
}}
>
Number of members:{" "}
Number of members:{' '}
{` ${promotion?.memberCount}`}
</Typography>
{promotion?.description && (
<Typography
sx={{
fontSize: "13px",
fontSize: '13px',
fontWeight: 600,
}}
>
@ -606,7 +611,7 @@ export const ListOfGroupPromotions = () => {
{promotion?.isOpen === false && (
<Typography
sx={{
fontSize: "13px",
fontSize: '13px',
fontWeight: 600,
}}
>
@ -618,11 +623,11 @@ export const ListOfGroupPromotions = () => {
<Spacer height="5px" />
<Box
sx={{
display: "flex",
gap: "20px",
alignItems: "center",
width: "100%",
justifyContent: "center",
display: 'flex',
gap: '20px',
alignItems: 'center',
width: '100%',
justifyContent: 'center',
}}
>
<LoadingButton
@ -652,23 +657,23 @@ export const ListOfGroupPromotions = () => {
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
width: "100%",
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%',
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "15px",
display: 'flex',
alignItems: 'center',
gap: '15px',
}}
>
<Avatar
sx={{
backgroundColor: "#27282c",
color: "white",
backgroundColor: '#27282c',
color: 'white',
}}
alt={promotion?.name}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
@ -680,8 +685,8 @@ export const ListOfGroupPromotions = () => {
<Typography
sx={{
fontWight: 600,
fontFamily: "Inter",
color: "cadetBlue",
fontFamily: 'Inter',
color: 'cadetBlue',
}}
>
{promotion?.name}
@ -690,8 +695,8 @@ export const ListOfGroupPromotions = () => {
<Typography
sx={{
fontWight: 600,
fontFamily: "Inter",
color: "cadetBlue",
fontFamily: 'Inter',
color: 'cadetBlue',
}}
>
{promotion?.groupName}
@ -700,42 +705,42 @@ export const ListOfGroupPromotions = () => {
<Spacer height="20px" />
<Box
sx={{
display: "flex",
gap: "20px",
alignItems: "center",
display: 'flex',
gap: '20px',
alignItems: 'center',
}}
>
{promotion?.isOpen === false && (
<LockIcon
sx={{
color: "var(--green)",
color: 'var(--green)',
}}
/>
)}
{promotion?.isOpen === true && (
<NoEncryptionGmailerrorredIcon
sx={{
color: "var(--danger)",
color: 'var(--danger)',
}}
/>
)}
<Typography
sx={{
fontSize: "15px",
fontSize: '15px',
fontWeight: 600,
}}
>
{promotion?.isOpen
? "Public group"
: "Private group"}
? 'Public group'
: 'Private group'}
</Typography>
</Box>
<Spacer height="20px" />
<Typography
sx={{
fontWight: 600,
fontFamily: "Inter",
color: "cadetBlue",
fontFamily: 'Inter',
color: 'cadetBlue',
}}
>
{promotion?.data}
@ -743,9 +748,9 @@ export const ListOfGroupPromotions = () => {
<Spacer height="20px" />
<Box
sx={{
display: "flex",
justifyContent: "center",
width: "100%",
display: 'flex',
justifyContent: 'center',
width: '100%',
}}
>
<Button
@ -754,8 +759,8 @@ export const ListOfGroupPromotions = () => {
handlePopoverOpen(event, promotion?.groupId)
}
sx={{
fontSize: "12px",
color: "white",
fontSize: '12px',
color: 'white',
}}
>
Join Group: {` ${promotion?.groupName}`}
@ -783,7 +788,7 @@ export const ListOfGroupPromotions = () => {
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{"Promote your group to non-members"}
{'Promote your group to non-members'}
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
@ -791,14 +796,14 @@ export const ListOfGroupPromotions = () => {
group.
</DialogContentText>
<DialogContentText id="alert-dialog-description2">
Max 200 characters. Publish Fee: {fee && fee} {" QORT"}
Max 200 characters. Publish Fee: {fee && fee} {' QORT'}
</DialogContentText>
<Spacer height="20px" />
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: "5px",
display: 'flex',
flexDirection: 'column',
gap: '5px',
}}
>
<Label>Select a group</Label>
@ -832,11 +837,11 @@ export const ListOfGroupPromotions = () => {
}}
multiline={true}
sx={{
"& .MuiFormLabel-root": {
color: "white",
'& .MuiFormLabel-root': {
color: 'white',
},
"& .MuiFormLabel-root.Mui-focused": {
color: "white",
'& .MuiFormLabel-root.Mui-focused': {
color: 'white',
},
}}
/>

View File

@ -1,36 +1,36 @@
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";
import Typography from "@mui/material/Typography";
import CloseIcon from "@mui/icons-material/Close";
import Slide from "@mui/material/Slide";
import { TransitionProps } from "@mui/material/transitions";
import ListOfMembers from "./ListOfMembers";
import { InviteMember } from "./InviteMember";
import { ListOfInvites } from "./ListOfInvites";
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 { getGroupMembers, getNames } from "./Group";
import { LoadingSnackbar } from "../Snackbar/LoadingSnackbar";
import { getFee } from "../../background";
import { LoadingButton } from "@mui/lab";
import { subscribeToEvent, unsubscribeFromEvent } from "../../utils/events";
import { Spacer } from "../../common/Spacer";
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';
import Typography from '@mui/material/Typography';
import CloseIcon from '@mui/icons-material/Close';
import Slide from '@mui/material/Slide';
import { TransitionProps } from '@mui/material/transitions';
import ListOfMembers from './ListOfMembers';
import { InviteMember } from './InviteMember';
import { ListOfInvites } from './ListOfInvites';
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 { getGroupMembers, getNames } from './Group';
import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar';
import { getFee } from '../../background';
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}`,
"aria-controls": `simple-tabpanel-${index}`,
'aria-controls': `simple-tabpanel-${index}`,
};
}
@ -50,16 +50,16 @@ export const ManageMembers = ({
selectedGroup,
isAdmin,
isOwner
isOwner,
}) => {
const [membersWithNames, setMembersWithNames] = React.useState([]);
const [tab, setTab] = React.useState("create");
const [tab, setTab] = React.useState('create');
const [value, setValue] = React.useState(0);
const [openSnack, setOpenSnack] = React.useState(false);
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 [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);
};
@ -69,20 +69,20 @@ export const ManageMembers = ({
setOpen(false);
};
const handleLeaveGroup = async () => {
try {
setIsLoadingLeave(true)
const fee = await getFee('LEAVE_GROUP')
setIsLoadingLeave(true);
const fee = await getFee('LEAVE_GROUP');
await show({
message: "Would you like to perform an LEAVE_GROUP transaction?" ,
publishFee: fee.fee + ' QORT'
})
message: 'Would you like to perform an LEAVE_GROUP transaction?',
publishFee: fee.fee + ' QORT',
});
await new Promise((res, rej) => {
window.sendMessage("leaveGroup", {
groupId: selectedGroup?.groupId,
})
window
.sendMessage('leaveGroup', {
groupId: selectedGroup?.groupId,
})
.then((response) => {
if (!response?.error) {
setTxList((prev) => [
@ -98,8 +98,9 @@ export const ManageMembers = ({
]);
res(response);
setInfoSnack({
type: "success",
message: "Successfully requested to leave group. It may take a couple of minutes for the changes to propagate",
type: 'success',
message:
'Successfully requested to leave group. It may take a couple of minutes for the changes to propagate',
});
setOpenSnack(true);
return;
@ -107,57 +108,62 @@ export const ManageMembers = ({
rej(response.error);
})
.catch((error) => {
rej(error.message || "An error occurred");
rej(error.message || 'An error occurred');
});
});
} catch (error) {} finally {
setIsLoadingLeave(false)
} catch (error) {
console.log(error);
} finally {
setIsLoadingLeave(false);
}
};
const getMembersWithNames = React.useCallback(async (groupId) => {
const getMembersWithNames = React.useCallback(async (groupId) => {
try {
setIsLoadingMembers(true)
setIsLoadingMembers(true);
const res = await getGroupMembers(groupId);
const resWithNames = await getNames(res.members);
setMembersWithNames(resWithNames);
setIsLoadingMembers(false)
} catch (error) {}
setIsLoadingMembers(false);
} catch (error) {
console.log(error);
}
}, []);
const getMembers = async (groupId) => {
try {
const res = await getGroupMembers(groupId);
setMembersWithNames(res?.members || []);
} catch (error) {}
} catch (error) {
console.log(error);
}
};
const getGroupInfo = async (groupId) => {
try {
const response = await fetch(
`${getBaseApiReact()}/groups/${groupId}`
);
const groupData = await response.json();
setGroupInfo(groupData)
} catch (error) {}
const response = await fetch(`${getBaseApiReact()}/groups/${groupId}`);
const groupData = await response.json();
setGroupInfo(groupData);
} catch (error) {
console.log(error);
}
};
React.useEffect(()=> {
if(selectedGroup?.groupId){
getMembers(selectedGroup?.groupId)
getGroupInfo(selectedGroup?.groupId)
React.useEffect(() => {
if (selectedGroup?.groupId) {
getMembers(selectedGroup?.groupId);
getGroupInfo(selectedGroup?.groupId);
}
}, [selectedGroup?.groupId])
}, [selectedGroup?.groupId]);
const openGroupJoinRequestFunc = ()=> {
setValue(4)
}
const openGroupJoinRequestFunc = () => {
setValue(4);
};
React.useEffect(() => {
subscribeToEvent("openGroupJoinRequest", openGroupJoinRequestFunc);
subscribeToEvent('openGroupJoinRequest', openGroupJoinRequestFunc);
return () => {
unsubscribeFromEvent("openGroupJoinRequest", openGroupJoinRequestFunc);
unsubscribeFromEvent('openGroupJoinRequest', openGroupJoinRequestFunc);
};
}, []);
@ -169,7 +175,7 @@ export const ManageMembers = ({
onClose={handleClose}
TransitionComponent={Transition}
>
<AppBar sx={{ position: "relative", bgcolor: "#232428" }}>
<AppBar sx={{ position: 'relative', bgcolor: '#232428' }}>
<Toolbar>
<Typography sx={{ ml: 2, flex: 1 }} variant="h6" component="div">
Manage Members
@ -186,117 +192,136 @@ export const ManageMembers = ({
</AppBar>
<Box
sx={{
bgcolor: "#27282c",
bgcolor: '#27282c',
flexGrow: 1,
overflowY: "auto",
color: "white",
overflowY: 'auto',
color: 'white',
}}
>
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<Tabs
value={value}
onChange={handleChange}
aria-label="basic tabs example"
variant="scrollable" // Make tabs scrollable
scrollButtons="auto" // Show scroll buttons automatically
allowScrollButtonsMobile // Show scroll buttons on mobile as well
sx={{
"& .MuiTabs-indicator": {
backgroundColor: "white",
},
maxWidth: '100%', // Ensure the tabs container fits within the available space
overflow: 'hidden', // Prevents overflow on small screens
}}
>
<Tab
label="List of members"
{...a11yProps(0)}
sx={{
"&.Mui-selected": {
color: "white",
},
fontSize: isMobile ? '0.75rem' : '1rem', // Adjust font size for mobile
}}
/>
<Tab
label="Invite new member"
{...a11yProps(1)}
sx={{
"&.Mui-selected": {
color: "white",
},
fontSize: isMobile ? '0.75rem' : '1rem',
}}
/>
<Tab
label="List of invites"
{...a11yProps(2)}
sx={{
"&.Mui-selected": {
color: "white",
},
fontSize: isMobile ? '0.75rem' : '1rem',
}}
/>
<Tab
label="List of bans"
{...a11yProps(3)}
sx={{
"&.Mui-selected": {
color: "white",
},
fontSize: isMobile ? '0.75rem' : '1rem',
}}
/>
<Tab
label="Join requests"
{...a11yProps(4)}
sx={{
"&.Mui-selected": {
color: "white",
},
fontSize: isMobile ? '0.75rem' : '1rem',
}}
/>
</Tabs>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs
value={value}
onChange={handleChange}
aria-label="basic tabs example"
variant="scrollable" // Make tabs scrollable
scrollButtons="auto" // Show scroll buttons automatically
allowScrollButtonsMobile // Show scroll buttons on mobile as well
sx={{
'& .MuiTabs-indicator': {
backgroundColor: 'white',
},
maxWidth: '100%', // Ensure the tabs container fits within the available space
overflow: 'hidden', // Prevents overflow on small screens
}}
>
<Tab
label="List of members"
{...a11yProps(0)}
sx={{
'&.Mui-selected': {
color: 'white',
},
fontSize: isMobile ? '0.75rem' : '1rem', // Adjust font size for mobile
}}
/>
<Tab
label="Invite new member"
{...a11yProps(1)}
sx={{
'&.Mui-selected': {
color: 'white',
},
fontSize: isMobile ? '0.75rem' : '1rem',
}}
/>
<Tab
label="List of invites"
{...a11yProps(2)}
sx={{
'&.Mui-selected': {
color: 'white',
},
fontSize: isMobile ? '0.75rem' : '1rem',
}}
/>
<Tab
label="List of bans"
{...a11yProps(3)}
sx={{
'&.Mui-selected': {
color: 'white',
},
fontSize: isMobile ? '0.75rem' : '1rem',
}}
/>
<Tab
label="Join requests"
{...a11yProps(4)}
sx={{
'&.Mui-selected': {
color: 'white',
},
fontSize: isMobile ? '0.75rem' : '1rem',
}}
/>
</Tabs>
</Box>
<Card sx={{
padding: '10px',
cursor: 'default',
}}>
<Card
sx={{
padding: '10px',
cursor: 'default',
}}
>
<Box>
<Typography>GroupId: {groupInfo?.groupId}</Typography>
<Typography>GroupName: {groupInfo?.groupName}</Typography>
<Typography>Number of members: {groupInfo?.memberCount}</Typography>
<ButtonBase sx={{
gap: '10px'
}} onClick={async ()=> {
const link = `qortal://use-group/action-join/groupid-${groupInfo?.groupId}`
await navigator.clipboard.writeText(link);
}}><InsertLinkIcon /> <Typography>Join Group Link</Typography></ButtonBase>
<Typography>GroupId: {groupInfo?.groupId}</Typography>
<Typography>GroupName: {groupInfo?.groupName}</Typography>
<Typography>
Number of members: {groupInfo?.memberCount}
</Typography>
<ButtonBase
sx={{
gap: '10px',
}}
onClick={async () => {
const link = `qortal://use-group/action-join/groupid-${groupInfo?.groupId}`;
await navigator.clipboard.writeText(link);
}}
>
<InsertLinkIcon /> <Typography>Join Group Link</Typography>
</ButtonBase>
</Box>
<Spacer height="20px" />
{selectedGroup?.groupId && !isOwner && (
<LoadingButton size="small" loading={isLoadingLeave} loadingPosition="start"
variant="contained" onClick={handleLeaveGroup}>
Leave Group
</LoadingButton>
)}
<Spacer height="20px" />
{selectedGroup?.groupId && !isOwner && (
<LoadingButton
size="small"
loading={isLoadingLeave}
loadingPosition="start"
variant="contained"
onClick={handleLeaveGroup}
>
Leave Group
</LoadingButton>
)}
</Card>
{value === 0 && (
<Box
sx={{
width: "100%",
padding: "25px",
maxWidth: '750px'
width: '100%',
padding: '25px',
maxWidth: '750px',
}}
>
<Button variant="contained" onClick={()=> getMembersWithNames(selectedGroup?.groupId)}>Load members with names</Button>
<Button
variant="contained"
onClick={() => getMembersWithNames(selectedGroup?.groupId)}
>
Load members with names
</Button>
<Spacer height="10px" />
<ListOfMembers
members={membersWithNames || []}
groupId={selectedGroup?.groupId}
setOpenSnack={setOpenSnack}
setOpenSnack={setOpenSnack}
setInfoSnack={setInfoSnack}
isAdmin={isAdmin}
isOwner={isOwner}
@ -304,64 +329,87 @@ export const ManageMembers = ({
/>
</Box>
)}
{value === 1 && (
{value === 1 && (
<Box
sx={{
width: "100%",
padding: "25px",
maxWidth: '750px'
width: '100%',
padding: '25px',
maxWidth: '750px',
}}
>
<InviteMember show={show} groupId={selectedGroup?.groupId} setOpenSnack={setOpenSnack} setInfoSnack={setInfoSnack} />
<InviteMember
show={show}
groupId={selectedGroup?.groupId}
setOpenSnack={setOpenSnack}
setInfoSnack={setInfoSnack}
/>
</Box>
)}
{value === 2 && (
<Box
sx={{
width: "100%",
padding: "25px",
maxWidth: '750px'
width: '100%',
padding: '25px',
maxWidth: '750px',
}}
>
<ListOfInvites show={show} groupId={selectedGroup?.groupId} setOpenSnack={setOpenSnack} setInfoSnack={setInfoSnack} />
<ListOfInvites
show={show}
groupId={selectedGroup?.groupId}
setOpenSnack={setOpenSnack}
setInfoSnack={setInfoSnack}
/>
</Box>
)}
{value === 3 && (
<Box
sx={{
width: "100%",
padding: "25px",
maxWidth: '750px'
width: '100%',
padding: '25px',
maxWidth: '750px',
}}
>
<ListOfBans show={show} groupId={selectedGroup?.groupId} setOpenSnack={setOpenSnack} setInfoSnack={setInfoSnack} />
<ListOfBans
show={show}
groupId={selectedGroup?.groupId}
setOpenSnack={setOpenSnack}
setInfoSnack={setInfoSnack}
/>
</Box>
)}
{value === 4 && (
<Box
sx={{
width: "100%",
padding: "25px",
maxWidth: '750px'
width: '100%',
padding: '25px',
maxWidth: '750px',
}}
>
<ListOfJoinRequests show={show} setOpenSnack={setOpenSnack} setInfoSnack={setInfoSnack} groupId={selectedGroup?.groupId} />
<ListOfJoinRequests
show={show}
setOpenSnack={setOpenSnack}
setInfoSnack={setInfoSnack}
groupId={selectedGroup?.groupId}
/>
</Box>
)}
</Box>
<CustomizedSnackbars open={openSnack} setOpen={setOpenSnack} info={infoSnack} setInfo={setInfoSnack} />
<CustomizedSnackbars
open={openSnack}
setOpen={setOpenSnack}
info={infoSnack}
setInfo={setInfoSnack}
/>
<LoadingSnackbar
open={isLoadingMembers}
info={{
message: "Loading member list with names... please wait.",
message: 'Loading member list with names... please wait.',
}}
/>
</Dialog>
</React.Fragment>
);
};

View File

@ -1,22 +1,29 @@
import React, { useContext, useEffect, useState } from "react";
import Logo2 from "../assets/svgs/Logo2.svg";
import { MyContext, getArbitraryEndpointReact, getBaseApiReact } from "../App";
import { Avatar, Box, Button, ButtonBase, Popover, Typography } from "@mui/material";
import { Spacer } from "../common/Spacer";
import ImageUploader from "../common/ImageUploader";
import { getFee } from "../background";
import { fileToBase64 } from "../utils/fileReading";
import { LoadingButton } from "@mui/lab";
import React, { useContext, useEffect, useState } from 'react';
import Logo2 from '../assets/svgs/Logo2.svg';
import { MyContext, getArbitraryEndpointReact, getBaseApiReact } from '../App';
import {
Avatar,
Box,
Button,
ButtonBase,
Popover,
Typography,
} from '@mui/material';
import { Spacer } from '../common/Spacer';
import ImageUploader from '../common/ImageUploader';
import { getFee } from '../background';
import { fileToBase64 } from '../utils/fileReading';
import { LoadingButton } from '@mui/lab';
import ErrorIcon from '@mui/icons-material/Error';
export const MainAvatar = ({ myName, balance, setOpenSnack, setInfoSnack }) => {
const [hasAvatar, setHasAvatar] = useState(false);
const [avatarFile, setAvatarFile] = useState(null);
const [tempAvatar, setTempAvatar] = useState(null)
const [tempAvatar, setTempAvatar] = useState(null);
const { show } = useContext(MyContext);
const [anchorEl, setAnchorEl] = useState(null);
const [isLoading, setIsLoading] = useState(false)
const [isLoading, setIsLoading] = useState(false);
// Handle child element click to open Popover
const handleChildClick = (event) => {
event.stopPropagation(); // Prevent parent onClick from firing
@ -37,93 +44,105 @@ const [isLoading, setIsLoading] = useState(false)
const identifier = `qortal_avatar`;
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=THUMBNAIL&identifier=${identifier}&limit=1&name=${myName}&includemetadata=false&prefix=true`;
const response = await fetch(url, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
const responseData = await response.json();
if (responseData?.length > 0) {
setHasAvatar(true);
}
} catch (error) {}
} catch (error) {
console.log(error);
}
};
useEffect(() => {
if (!myName) return;
checkIfAvatarExists();
}, [myName]);
const publishAvatar = async ()=> {
const publishAvatar = async () => {
try {
const fee = await getFee('ARBITRARY')
if(+balance < +fee.fee) throw new Error(`Publishing an Avatar requires ${fee.fee}`)
await show({
message: "Would you like to publish an avatar?" ,
publishFee: fee.fee + ' QORT'
})
setIsLoading(true);
const avatarBase64 = await fileToBase64(avatarFile)
await new Promise((res, rej) => {
window.sendMessage("publishOnQDN", {
data: avatarBase64,
identifier: "qortal_avatar",
service: "THUMBNAIL",
})
.then((response) => {
if (!response?.error) {
res(response);
return;
}
rej(response.error);
})
.catch((error) => {
rej(error.message || "An error occurred");
});
});
setAvatarFile(null);
setTempAvatar(`data:image/webp;base64,${avatarBase64}`)
handleClose()
const fee = await getFee('ARBITRARY');
if (+balance < +fee.fee)
throw new Error(`Publishing an Avatar requires ${fee.fee}`);
await show({
message: 'Would you like to publish an avatar?',
publishFee: fee.fee + ' QORT',
});
setIsLoading(true);
const avatarBase64 = await fileToBase64(avatarFile);
await new Promise((res, rej) => {
window
.sendMessage('publishOnQDN', {
data: avatarBase64,
identifier: 'qortal_avatar',
service: 'THUMBNAIL',
})
.then((response) => {
if (!response?.error) {
res(response);
return;
}
rej(response.error);
})
.catch((error) => {
rej(error.message || 'An error occurred');
});
});
setAvatarFile(null);
setTempAvatar(`data:image/webp;base64,${avatarBase64}`);
handleClose();
} catch (error) {
if (error?.message) {
setOpenSnack(true)
setInfoSnack({
type: "error",
message: error?.message,
});
}
setOpenSnack(true);
setInfoSnack({
type: 'error',
message: error?.message,
});
}
} finally {
setIsLoading(false);
setIsLoading(false);
}
}
};
if(tempAvatar){
if (tempAvatar) {
return (
<>
<Avatar
<>
<Avatar
sx={{
height: '138px',
width: '138px',
}}
src={tempAvatar}
alt={myName}
>
{myName?.charAt(0)}
</Avatar>
<ButtonBase onClick={handleChildClick}>
<Typography
sx={{
height: "138px",
width: "138px",
fontSize: '12px',
opacity: 0.5,
}}
src={tempAvatar}
alt={myName}
>
{myName?.charAt(0)}
</Avatar>
<ButtonBase onClick={handleChildClick}>
<Typography
sx={{
fontSize: "12px",
opacity: 0.5,
}}
>
change avatar
</Typography>
</ButtonBase>
<PopoverComp myName={myName} avatarFile={avatarFile} setAvatarFile={setAvatarFile} id={id} open={open} anchorEl={anchorEl} handleClose={handleClose} publishAvatar={publishAvatar} isLoading={isLoading} />
</>
);
change avatar
</Typography>
</ButtonBase>
<PopoverComp
myName={myName}
avatarFile={avatarFile}
setAvatarFile={setAvatarFile}
id={id}
open={open}
anchorEl={anchorEl}
handleClose={handleClose}
publishAvatar={publishAvatar}
isLoading={isLoading}
/>
</>
);
}
if (hasAvatar) {
@ -131,8 +150,8 @@ const [isLoading, setIsLoading] = useState(false)
<>
<Avatar
sx={{
height: "138px",
width: "138px",
height: '138px',
width: '138px',
}}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${myName}/qortal_avatar?async=true`}
alt={myName}
@ -142,14 +161,24 @@ const [isLoading, setIsLoading] = useState(false)
<ButtonBase onClick={handleChildClick}>
<Typography
sx={{
fontSize: "12px",
fontSize: '12px',
opacity: 0.5,
}}
>
change avatar
</Typography>
</ButtonBase>
<PopoverComp myName={myName} avatarFile={avatarFile} setAvatarFile={setAvatarFile} id={id} open={open} anchorEl={anchorEl} handleClose={handleClose} publishAvatar={publishAvatar} isLoading={isLoading} />
<PopoverComp
myName={myName}
avatarFile={avatarFile}
setAvatarFile={setAvatarFile}
id={id}
open={open}
anchorEl={anchorEl}
handleClose={handleClose}
publishAvatar={publishAvatar}
isLoading={isLoading}
/>
</>
);
}
@ -160,42 +189,61 @@ const [isLoading, setIsLoading] = useState(false)
<ButtonBase onClick={handleChildClick}>
<Typography
sx={{
fontSize: "12px",
fontSize: '12px',
opacity: 0.5,
}}
>
set avatar
</Typography>
</ButtonBase>
<PopoverComp myName={myName} avatarFile={avatarFile} setAvatarFile={setAvatarFile} id={id} open={open} anchorEl={anchorEl} handleClose={handleClose} publishAvatar={publishAvatar} isLoading={isLoading} />
<PopoverComp
myName={myName}
avatarFile={avatarFile}
setAvatarFile={setAvatarFile}
id={id}
open={open}
anchorEl={anchorEl}
handleClose={handleClose}
publishAvatar={publishAvatar}
isLoading={isLoading}
/>
</>
);
};
const PopoverComp = ({avatarFile, setAvatarFile, id, open, anchorEl, handleClose, publishAvatar, isLoading, myName}) => {
return (
<Popover
const PopoverComp = ({
avatarFile,
setAvatarFile,
id,
open,
anchorEl,
handleClose,
publishAvatar,
isLoading,
myName,
}) => {
return (
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={handleClose} // Close popover on click outside
anchorOrigin={{
vertical: "bottom",
horizontal: "center",
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: "top",
horizontal: "center",
vertical: 'top',
horizontal: 'center',
}}
>
<Box sx={{ p: 2, display: "flex", flexDirection: "column", gap: 1 }}>
<Box sx={{ p: 2, display: 'flex', flexDirection: 'column', gap: 1 }}>
<Typography
sx={{
fontSize: "12px",
fontSize: '12px',
}}
>
(500 KB max. for GIFS){" "}
(500 KB max. for GIFS){' '}
</Typography>
<ImageUploader onPick={(file) => setAvatarFile(file)}>
<Button variant="contained">Choose Image</Button>
@ -203,23 +251,34 @@ const PopoverComp = ({avatarFile, setAvatarFile, id, open, anchorEl, handleClose
{avatarFile?.name}
<Spacer height="25px" />
{!myName && (
<Box sx={{
<Box
sx={{
display: 'flex',
gap: '5px',
alignItems: 'center'
}}>
<ErrorIcon sx={{
color: 'white'
}} />
<Typography>A registered name is required to set an avatar</Typography>
alignItems: 'center',
}}
>
<ErrorIcon
sx={{
color: 'white',
}}
/>
<Typography>
A registered name is required to set an avatar
</Typography>
</Box>
)}
<Spacer height="25px" />
<LoadingButton loading={isLoading} disabled={!avatarFile || !myName} onClick={publishAvatar} variant="contained">
<Spacer height="25px" />
<LoadingButton
loading={isLoading}
disabled={!avatarFile || !myName}
onClick={publishAvatar}
variant="contained"
>
Publish avatar
</LoadingButton>
</Box>
</Popover>
)
};
);
};

View File

@ -74,7 +74,9 @@ export const Minting = ({
}
const data = await response.json();
setMintingAccounts(data);
} catch (error) {}
} catch (error) {
console.log(error);
}
}, []);
const accountIsMinting = useMemo(() => {
@ -199,7 +201,9 @@ export const Minting = ({
const data = await response.json();
setRewardShares(data);
return data;
} catch (error) {}
} catch (error) {
console.log(error);
}
}, []);
const addMintingAccount = useCallback(async (val) => {

View File

@ -60,7 +60,9 @@ export const TaskManager = ({ getUserInfo }) => {
}
clearInterval(intervals.current[signature]);
}
} catch (error) {}
} catch (error) {
console.log(error);
}
stop = false;
}
};

View File

@ -187,7 +187,7 @@ export const CustomInput = styled(TextField)(({ theme }) => ({
outline: 'none',
width: '183px', // Adjust the width as needed
input: {
fontSize: 10,
fontSize: '12px',
fontFamily: 'Inter',
fontWeight: 400,
color: theme.palette.text.primary,
@ -230,7 +230,7 @@ export const CustomLabel = styled(InputLabel)(({ theme }) => ({
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
fontFamily: 'Inter',
fontSize: '10px',
fontSize: '15px',
fontWeight: 400,
lineHeight: '12px',
}));

View File

@ -106,7 +106,9 @@ export const useQortalGetSaveSettings = (myName, isAuthenticated) => {
setSettingsQDNLastUpdated(0);
}
setCanSave(true);
} catch (error) {}
} catch (error) {
console.log(error);
}
},
[]
);