Add theme to all chat pages

This commit is contained in:
Nicola Benaglia 2025-04-19 16:00:58 +02:00
parent cf335a6d0a
commit 905cddf29a
9 changed files with 620 additions and 541 deletions

View File

@ -4,7 +4,7 @@ import {
AuthenticatedContainerInnerTop,
CustomButton,
} from '../../styles/App-styles';
import { Box, CircularProgress } from '@mui/material';
import { Box, CircularProgress, useTheme } from '@mui/material';
import { objectToBase64 } from '../../qdn/encryption/group-encryption';
import ShortUniqueId from 'short-unique-id';
import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar';
@ -39,6 +39,7 @@ export const AnnouncementDiscussion = ({
myName,
isPrivate,
}) => {
const theme = useTheme();
const [isSending, setIsSending] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [isFocusedParent, setIsFocusedParent] = useState(false);
@ -212,6 +213,7 @@ export const AnnouncementDiscussion = ({
getData({ name: data.name, identifier: data.identifier }, isPrivate);
}
} catch (error) {
console.log(error);
} finally {
setIsLoading(false);
@ -274,19 +276,19 @@ export const AnnouncementDiscussion = ({
return (
<div
style={{
height: isMobile ? '100%' : '100%',
display: 'flex',
flexDirection: 'column',
height: isMobile ? '100%' : '100%',
width: '100%',
}}
>
<div
style={{
position: 'relative',
width: '100%',
display: 'flex',
flexDirection: 'column',
flexShrink: 0,
position: 'relative',
width: '100%',
}}
>
<AuthenticatedContainerInnerTop
@ -301,6 +303,7 @@ export const AnnouncementDiscussion = ({
}}
/>
</AuthenticatedContainerInnerTop>
<Spacer height="20px" />
</div>
<AnnouncementList
@ -314,30 +317,27 @@ export const AnnouncementDiscussion = ({
/>
<div
style={{
// position: 'fixed',
// bottom: '0px',
backgroundColor: '#232428',
minHeight: isMobile ? '0px' : '150px',
maxHeight: isMobile ? 'auto' : '400px',
backgroundColor: theme.palette.background.default,
bottom: isFocusedParent ? '0px' : 'unset',
boxSizing: 'border-box',
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
width: '100%',
boxSizing: 'border-box',
padding: isMobile ? '10px' : '20px',
position: isFocusedParent ? 'fixed' : 'relative',
bottom: isFocusedParent ? '0px' : 'unset',
top: isFocusedParent ? '0px' : 'unset',
zIndex: isFocusedParent ? 5 : 'unset',
flexShrink: 0,
maxHeight: '400px',
minHeight: '150px',
overflow: 'hidden',
padding: '20px',
position: isFocusedParent ? 'fixed' : 'relative',
top: isFocusedParent ? '0px' : 'unset',
width: '100%',
zIndex: isFocusedParent ? 5 : 'unset',
}}
>
<div
style={{
display: 'flex',
flexDirection: 'column',
// height: '100%',
flexGrow: isMobile && 1,
flexGrow: 1,
overflow: 'auto',
}}
>
@ -353,11 +353,11 @@ export const AnnouncementDiscussion = ({
<Box
sx={{
display: 'flex',
width: '100&',
flexShrink: 0,
gap: '10px',
justifyContent: 'center',
flexShrink: 0,
position: 'relative',
width: '100&',
}}
>
{isFocusedParent && (
@ -369,13 +369,13 @@ export const AnnouncementDiscussion = ({
// Unfocus the editor
}}
style={{
marginTop: 'auto',
alignSelf: 'center',
background: 'red',
cursor: isSending ? 'default' : 'pointer',
flexShrink: 0,
padding: isMobile && '5px',
fontSize: isMobile && '14px',
background: 'red',
fontSize: '14px',
marginTop: 'auto',
padding: '5px',
}}
>
{` Close`}
@ -387,25 +387,25 @@ export const AnnouncementDiscussion = ({
publishComment();
}}
style={{
marginTop: 'auto',
alignSelf: 'center',
background: theme.palette.background.default,
cursor: isSending ? 'default' : 'pointer',
background: isSending && 'rgba(0, 0, 0, 0.8)',
flexShrink: 0,
padding: isMobile && '5px',
fontSize: isMobile && '14px',
fontSize: '14px',
marginTop: 'auto',
padding: '5px',
}}
>
{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',
}}
/>
)}

View File

@ -1,173 +1,206 @@
import { Message } from "@chatscope/chat-ui-kit-react";
import React, { useEffect, useState } from "react";
import { useInView } from "react-intersection-observer";
import { MessageDisplay } from "./MessageDisplay";
import { Avatar, Box, Typography } from "@mui/material";
import { formatTimestamp } from "../../utils/time";
import React, { useEffect, useState } from 'react';
import { MessageDisplay } from './MessageDisplay';
import { Avatar, Box, Typography, useTheme } from '@mui/material';
import { formatTimestamp } from '../../utils/time';
import ChatBubbleIcon from '@mui/icons-material/ChatBubble';
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import { getBaseApi } from "../../background";
import { requestQueueCommentCount } from "./GroupAnnouncements";
import { CustomLoader } from "../../common/CustomLoader";
import { getArbitraryEndpointReact, getBaseApiReact } from "../../App";
import { WrapperUserAction } from "../WrapperUserAction";
export const AnnouncementItem = ({ message, messageData, setSelectedAnnouncement, disableComment, myName }) => {
import { getBaseApi } from '../../background';
import { requestQueueCommentCount } from './GroupAnnouncements';
import { CustomLoader } from '../../common/CustomLoader';
import { getArbitraryEndpointReact, getBaseApiReact } from '../../App';
import { WrapperUserAction } from '../WrapperUserAction';
const [commentLength, setCommentLength] = useState(0)
const getNumberOfComments = React.useCallback(
async () => {
try {
const offset = 0;
export const AnnouncementItem = ({
message,
messageData,
setSelectedAnnouncement,
disableComment,
myName,
}) => {
const theme = useTheme();
const [commentLength, setCommentLength] = useState(0);
const getNumberOfComments = React.useCallback(async () => {
try {
const offset = 0;
// dispatch(setIsLoadingGlobal(true))
const identifier = `cm-${message.identifier}`;
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=0&includemetadata=false&offset=${offset}&reverse=true&prefix=true`;
const response = await requestQueueCommentCount.enqueue(() => {
return fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
})
const responseData = await response.json();
// dispatch(setIsLoadingGlobal(true))
const identifier = `cm-${message.identifier}`;
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=0&includemetadata=false&offset=${offset}&reverse=true&prefix=true`;
const response = await requestQueueCommentCount.enqueue(() => {
return fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
});
const responseData = await response.json();
setCommentLength(responseData?.length);
} catch (error) {
console.log(error);
}
}, []);
useEffect(() => {
if (disableComment) return;
getNumberOfComments();
}, []);
setCommentLength(responseData?.length);
} catch (error) {
} finally {
// dispatch(setIsLoadingGlobal(false))
}
},
[]
);
useEffect(()=> {
if(disableComment) return
getNumberOfComments()
}, [])
return (
<div
style={{
padding: "10px",
backgroundColor: "#232428",
borderRadius: "7px",
width: "95%",
display: "flex",
backgroundColor: theme.palette.background.default,
borderRadius: '7px',
display: 'flex',
flexDirection: 'column',
gap: '7px',
flexDirection: 'column'
padding: '10px',
width: '95%',
}}
>
<Box sx={{
display: "flex",
gap: '7px',
width: '100%',
wordBreak: 'break-word'
}}>
<WrapperUserAction disabled={myName === message?.name} address={undefined} name={message?.name}>
<Avatar
sx={{
backgroundColor: '#27282c',
color: 'white'
}}
alt={message?.name}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${message?.name}/qortal_avatar?async=true`}
>
{message?.name?.charAt(0)}
</Avatar>
</WrapperUserAction>
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: "7px",
width: '100%'
display: 'flex',
gap: '7px',
width: '100%',
wordBreak: 'break-word',
}}
>
<WrapperUserAction disabled={myName === message?.name} address={undefined} name={message?.name}>
<Typography
<WrapperUserAction
disabled={myName === message?.name}
address={undefined}
name={message?.name}
>
<Avatar
sx={{
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}}
alt={message?.name}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${message?.name}/qortal_avatar?async=true`}
>
{message?.name?.charAt(0)}
</Avatar>
</WrapperUserAction>
<Box
sx={{
fontWight: 600,
fontFamily: "Inter",
color: "cadetBlue",
display: 'flex',
flexDirection: 'column',
gap: '7px',
width: '100%',
}}
>
{message?.name}
</Typography>
</WrapperUserAction>
{!messageData?.decryptedData && (
<Box sx={{
width: '100%',
display: 'flex',
justifyContent: 'center'
}}>
<CustomLoader />
</Box>
)}
{messageData?.decryptedData?.message && (
<>
{messageData?.type === "notification" ? (
<MessageDisplay htmlContent={messageData?.decryptedData?.message} />
) : (
<MessageDisplay htmlContent={messageData?.decryptedData?.message} />
)}
</>
)}
<WrapperUserAction
disabled={myName === message?.name}
address={undefined}
name={message?.name}
>
<Typography
sx={{
fontWight: 600,
fontFamily: 'Inter',
color: 'cadetBlue',
}}
>
{message?.name}
</Typography>
</WrapperUserAction>
{!messageData?.decryptedData && (
<Box
sx={{
width: '100%',
display: 'flex',
justifyContent: 'center',
}}
>
<CustomLoader />
</Box>
)}
{messageData?.decryptedData?.message && (
<>
{messageData?.type === 'notification' ? (
<MessageDisplay
htmlContent={messageData?.decryptedData?.message}
/>
) : (
<MessageDisplay
htmlContent={messageData?.decryptedData?.message}
/>
)}
</>
)}
<Box sx={{
display: 'flex',
justifyContent: 'flex-end',
width: '100%'
}}>
<Typography sx={{
fontSize: '14px',
color: 'gray',
fontFamily: 'Inter'
}}>{formatTimestamp(message.created)}</Typography>
<Box
sx={{
display: 'flex',
justifyContent: 'flex-end',
width: '100%',
}}
>
<Typography
sx={{
color: theme.palette.text.secondary,
fontFamily: 'Inter',
fontSize: '14px',
}}
>
{formatTimestamp(message.created)}
</Typography>
</Box>
</Box>
</Box>
</Box>
{!disableComment && (
<Box sx={{
display: 'flex',
width: '100%',
alignItems: 'center',
justifyContent: 'space-between',
padding: '20px',
cursor: 'pointer',
opacity: 0.4,
borderTop: '1px solid white',
}} onClick={()=> setSelectedAnnouncement(message)}>
<Box sx={{
display: 'flex',
width: '100%',
gap: '25px',
alignItems: 'center',
}}>
<ChatBubbleIcon sx={{
fontSize: '20px'
}} />
{commentLength ? (
<Typography sx={{
fontSize: '14px'
}}>{`${commentLength > 1 ? `${commentLength} comments` : `${commentLength} comment`}`}</Typography>
) : (
<Typography sx={{
fontSize: '14px'
}}>Leave comment</Typography>
)}
{!disableComment && (
<Box
sx={{
alignItems: 'center',
borderTop: '1px solid white',
cursor: 'pointer',
display: 'flex',
justifyContent: 'space-between',
opacity: 0.4,
padding: '20px',
width: '100%',
}}
onClick={() => setSelectedAnnouncement(message)}
>
<Box
sx={{
alignItems: 'center',
display: 'flex',
gap: '25px',
width: '100%',
}}
>
<ChatBubbleIcon
sx={{
fontSize: '20px',
}}
/>
{commentLength ? (
<Typography
sx={{
fontSize: '14px',
}}
>{`${commentLength > 1 ? `${commentLength} comments` : `${commentLength} comment`}`}</Typography>
) : (
<Typography
sx={{
fontSize: '14px',
}}
>
Leave comment
</Typography>
)}
</Box>
<ArrowForwardIosIcon
sx={{
fontSize: '20px',
}}
/>
</Box>
<ArrowForwardIosIcon sx={{
fontSize: '20px'
}} />
</Box>
)}
)}
</div>
);
};

View File

@ -1,10 +1,5 @@
import React, { useCallback, useState, useEffect, useRef } from 'react';
import {
List,
AutoSizer,
CellMeasurerCache,
CellMeasurer,
} from 'react-virtualized';
import { useState, useEffect, useRef } from 'react';
import { CellMeasurerCache } from 'react-virtualized';
import { AnnouncementItem } from './AnnouncementItem';
import { Box } from '@mui/material';
import { CustomButton } from '../../styles/App-styles';
@ -37,12 +32,12 @@ export const AnnouncementList = ({
return (
<div
style={{
position: 'relative',
flexGrow: 1,
width: '100%',
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
flexShrink: 1,
position: 'relative',
width: '100%',
overflow: 'auto',
}}
>
@ -57,11 +52,11 @@ export const AnnouncementList = ({
<div
key={message?.identifier}
style={{
marginBottom: '10px',
width: '100%',
alignItems: 'center',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
marginBottom: '10px',
width: '100%',
}}
>
<AnnouncementItem
@ -89,10 +84,10 @@ export const AnnouncementList = ({
</AutoSizer> */}
<Box
sx={{
width: '100%',
marginTop: '25px',
display: 'flex',
justifyContent: 'center',
marginTop: '25px',
width: '100%',
}}
>
{showLoadMore && (

View File

@ -7,15 +7,10 @@ import React, {
useRef,
useState,
} from 'react';
import { CreateCommonSecret } from './CreateCommonSecret';
import { reusableGet } from '../../qdn/publish/pubish';
import { uint8ArrayToObject } from '../../backgroundFunctions/encryption';
import {
base64ToUint8Array,
decodeBase64ForUIChatMessages,
objectToBase64,
} from '../../qdn/encryption/group-encryption';
import { ChatContainerComp } from './ChatContainer';
import { ChatList } from './ChatList';
import '@chatscope/chat-ui-kit-styles/dist/default/styles.min.css';
import Tiptap from './TipTap';
@ -38,7 +33,7 @@ import {
subscribeToEvent,
unsubscribeFromEvent,
} from '../../utils/events';
import { Box, ButtonBase, Divider, Typography } from '@mui/material';
import { Box, ButtonBase, Divider, Typography, useTheme } from '@mui/material';
import ShortUniqueId from 'short-unique-id';
import { ReplyPreview } from './MessageItem';
import { ExitIcon } from '../../assets/Icons/ExitIcon';
@ -1001,16 +996,18 @@ export const ChatGroup = ({
setIsOpenQManager(true);
}, []);
const theme = useTheme();
return (
<div
style={{
height: isMobile ? '100%' : '100%',
display: 'flex',
flexDirection: 'column',
width: '100%',
height: '100%',
left: hide && '-100000px',
opacity: hide ? 0 : 1,
position: hide ? 'absolute' : 'relative',
left: hide && '-100000px',
width: '100%',
}}
>
<ChatList
@ -1035,40 +1032,38 @@ export const ChatGroup = ({
{(!!secretKey || isPrivate === false) && (
<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',
overflow: 'hidden',
width: '100%',
boxSizing: 'border-box',
padding: isMobile ? '10px' : '20px',
position: isFocusedParent ? 'fixed' : 'relative',
bottom: isFocusedParent ? '0px' : 'unset',
top: isFocusedParent ? '0px' : 'unset',
zIndex: isFocusedParent ? 5 : 'unset',
flexShrink: 0,
minHeight: '150px',
overflow: 'hidden',
padding: '20px',
position: isFocusedParent ? 'fixed' : 'relative',
top: isFocusedParent ? '0px' : 'unset',
width: '100%',
zIndex: isFocusedParent ? 5 : 'unset',
}}
>
<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: '100%',
}}
>
@ -1088,9 +1083,9 @@ export const ChatGroup = ({
{onEditMessage && (
<Box
sx={{
alignItems: 'flex-start',
display: 'flex',
gap: '5px',
alignItems: 'flex-start',
width: '100%',
}}
>
@ -1123,9 +1118,9 @@ export const ChatGroup = ({
<Box
sx={{
display: 'flex',
width: '100%',
justifyContent: 'flex-start',
position: 'relative',
width: '100%',
}}
>
<Typography
@ -1141,11 +1136,11 @@ export const ChatGroup = ({
<Box
sx={{
display: 'flex',
width: '100px',
flexShrink: 0,
gap: '10px',
justifyContent: 'center',
flexShrink: 0,
position: 'relative',
width: '100px',
}}
>
<CustomButton
@ -1154,26 +1149,28 @@ export const ChatGroup = ({
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',
}}
/>
)}
@ -1185,25 +1182,24 @@ export const ChatGroup = ({
{isOpenQManager !== null && (
<Box
sx={{
position: 'fixed',
height: '600px',
maxHeight: '100vh',
width: '400px',
maxWidth: '100vw',
backgroundColor: '#27282c',
zIndex: 100,
bottom: 0,
right: 0,
overflow: 'hidden',
backgroundColor: theme.palette.background.default,
borderTopLeftRadius: '10px',
borderTopRightRadius: '10px',
bottom: 0,
boxShadow: 4,
display: hideView
? 'none'
: isOpenQManager === true
? 'block'
: 'none',
boxShadow: 4,
height: '600px',
maxHeight: '100vh',
maxWidth: '100vw',
overflow: 'hidden',
position: 'fixed',
right: 0,
width: '400px',
zIndex: 100,
}}
>
<Box
@ -1214,12 +1210,11 @@ export const ChatGroup = ({
>
<Box
sx={{
height: '40px',
display: 'flex',
alignItems: 'center',
padding: '5px',
display: 'flex',
height: '40px',
justifyContent: 'space-between',
padding: '5px',
}}
>
<Typography>Q-Manager</Typography>
@ -1251,12 +1246,14 @@ export const ChatGroup = ({
)}
{/* <ChatContainerComp messages={formatMessages} /> */}
<LoadingSnackbar
open={isLoading}
info={{
message: 'Loading chat... please wait.',
}}
/>
<CustomizedSnackbars
open={openSnack}
setOpen={setOpenSnack}

View File

@ -1,35 +1,56 @@
import { Box, Button, Typography } from '@mui/material'
import React, { useContext } from 'react'
import React, { useContext } from 'react';
import { Box, Button, Typography, useTheme } from '@mui/material';
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
import { LoadingButton } from '@mui/lab';
import { MyContext, getArbitraryEndpointReact, getBaseApiReact, pauseAllQueues } from '../../App';
import {
MyContext,
getArbitraryEndpointReact,
getBaseApiReact,
pauseAllQueues,
} from '../../App';
import { getFee } from '../../background';
import { decryptResource, getGroupAdmins, validateSecretKey } from '../Group/Group';
import {
decryptResource,
getGroupAdmins,
validateSecretKey,
} from '../Group/Group';
import { base64ToUint8Array } from '../../qdn/encryption/group-encryption';
import { uint8ArrayToObject } from '../../backgroundFunctions/encryption';
export const CreateCommonSecret = ({groupId, secretKey, isOwner, myAddress, secretKeyDetails, userInfo, noSecretKey, setHideCommonKeyPopup, setIsForceShowCreationKeyPopup, isForceShowCreationKeyPopup}) => {
export const CreateCommonSecret = ({
groupId,
secretKey,
isOwner,
myAddress,
secretKeyDetails,
userInfo,
noSecretKey,
setHideCommonKeyPopup,
setIsForceShowCreationKeyPopup,
isForceShowCreationKeyPopup,
}) => {
const { show, setTxList } = useContext(MyContext);
const [openSnack, setOpenSnack] = React.useState(false);
const [infoSnack, setInfoSnack] = React.useState(null);
const [isLoading, setIsLoading] = React.useState(false)
const [isLoading, setIsLoading] = React.useState(false);
const theme = useTheme();
const getPublishesFromAdmins = async (admins: string[]) => {
// const validApi = await findUsableApi();
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=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')
if (!response.ok) {
throw new Error('network error');
}
const adminData = await response.json();
const filterId = adminData.filter(
(data: any) =>
data.identifier === `symmetric-qchat-group-${groupId}`
(data: any) => data.identifier === `symmetric-qchat-group-${groupId}`
);
if (filterId?.length === 0) {
return false;
@ -38,149 +59,182 @@ export const CreateCommonSecret = ({groupId, secretKey, isOwner, myAddress, sec
// Get the most recent date for both a and b
const dateA = a.updated ? new Date(a.updated) : new Date(a.created);
const dateB = b.updated ? new Date(b.updated) : new Date(b.created);
// Sort by most recent
return dateB.getTime() - dateA.getTime();
});
return sortedData[0];
};
const getSecretKey = async (loadingGroupParam?: boolean, secretKeyToPublish?: boolean) => {
const getSecretKey = async (
loadingGroupParam?: boolean,
secretKeyToPublish?: boolean
) => {
try {
pauseAllQueues()
const {names} = await getGroupAdmins(groupId);
if(!names.length){
throw new Error('Network error')
pauseAllQueues();
const { names } = await getGroupAdmins(groupId);
if (!names.length) {
throw new Error('Network error');
}
const publish = await getPublishesFromAdmins(names);
if (publish === false) {
return false;
}
const res = await fetch(
`${getBaseApiReact()}/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${
publish.identifier
}?encoding=base64&rebuild=true`
);
const data = await res.text();
const decryptedKey: any = await decryptResource(data);
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');
if (decryptedKeyToObject) {
return decryptedKeyToObject;
} else {
}
} catch (error) {
} finally {
console.log(error);
}
};
const createCommonSecret = async ()=> {
try {
const fee = await getFee('ARBITRARY')
await show({
message: "Would you like to perform an ARBITRARY transaction?" ,
publishFee: fee.fee + ' QORT'
})
setIsLoading(true)
const createCommonSecret = async () => {
try {
const fee = await getFee('ARBITRARY');
await show({
message: 'Would you like to perform an ARBITRARY transaction?',
publishFee: fee.fee + ' QORT',
});
setIsLoading(true);
const secretKey2 = await getSecretKey()
if((!secretKey2 && secretKey2 !== false)) throw new Error('invalid secret key')
if (secretKey2 && !validateSecretKey(secretKey2)) throw new Error('invalid secret key')
const secretKey2 = await getSecretKey();
if (!secretKey2 && secretKey2 !== false)
throw new Error('invalid secret key');
if (secretKey2 && !validateSecretKey(secretKey2))
throw new Error('invalid secret key');
const secretKeyToSend = !secretKey2 ? null : secretKey2
window.sendMessage("encryptAndPublishSymmetricKeyGroupChat", {
groupId: groupId,
previousData: secretKeyToSend,
})
.then((response) => {
if (!response?.error) {
setInfoSnack({
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.",
});
setOpenSnack(true);
setTxList((prev) => [
{
...response,
type: 'created-common-secret',
label: `Published secret key for group ${groupId}: awaiting confirmation`,
labelDone: `Published secret key for group ${groupId}: success!`,
done: false,
groupId,
},
...prev,
]);
}
setIsLoading(false);
setTimeout(() => {
setIsForceShowCreationKeyPopup(false)
}, 1000);
})
.catch((error) => {
console.error("Failed to encrypt and publish symmetric key for group chat:", error.message || "An error occurred");
setIsLoading(false);
const secretKeyToSend = !secretKey2 ? null : secretKey2;
window
.sendMessage('encryptAndPublishSymmetricKeyGroupChat', {
groupId: groupId,
previousData: secretKeyToSend,
})
.then((response) => {
if (!response?.error) {
setInfoSnack({
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.',
});
} catch (error) {
}
setOpenSnack(true);
setTxList((prev) => [
{
...response,
type: 'created-common-secret',
label: `Published secret key for group ${groupId}: awaiting confirmation`,
labelDone: `Published secret key for group ${groupId}: success!`,
done: false,
groupId,
},
...prev,
]);
}
setIsLoading(false);
setTimeout(() => {
setIsForceShowCreationKeyPopup(false);
}, 1000);
})
.catch((error) => {
console.error(
'Failed to encrypt and publish symmetric key for group chat:',
error.message || 'An error occurred'
);
setIsLoading(false);
});
} catch (error) {
console.log(error);
}
};
return (
<Box sx={{
padding: '25px',
display: 'flex',
flexDirection: 'column',
gap: '25px',
maxWidth: '350px',
background: '#444444'
}}>
<LoadingButton loading={isLoading} loadingPosition="start" color="warning" variant='contained' onClick={createCommonSecret}>Re-encrypt key</LoadingButton>
{noSecretKey ? (
<Box>
<Typography>There is no group secret key. Be the first admin to publish one!</Typography>
</Box>
) : isOwner && secretKeyDetails && userInfo?.name && userInfo.name !== secretKeyDetails?.name ? (
<Box>
<Typography>The latest group secret key was published by a non-owner. As the owner of the group please re-encrypt the key as a safeguard</Typography>
</Box>
): isForceShowCreationKeyPopup ? null : (
<Box>
<Typography>The group member list has changed. Please re-encrypt the secret key.</Typography>
</Box>
)}
<Box sx={{
<Box
sx={{
background: theme.palette.background.default,
display: 'flex',
width: '100%',
justifyContent: 'flex-end'
}}>
<Button onClick={()=> {
setHideCommonKeyPopup(true)
setIsForceShowCreationKeyPopup(false)
}} size='small'>Hide</Button>
flexDirection: 'column',
gap: '25px',
maxWidth: '350px',
padding: '25px',
}}
>
<LoadingButton
loading={isLoading}
loadingPosition="start"
color="warning"
variant="contained"
onClick={createCommonSecret}
>
Re-encrypt key
</LoadingButton>
{noSecretKey ? (
<Box>
<Typography>
There is no group secret key. Be the first admin to publish one!
</Typography>
</Box>
) : isOwner &&
secretKeyDetails &&
userInfo?.name &&
userInfo.name !== secretKeyDetails?.name ? (
<Box>
<Typography>
The latest group secret key was published by a non-owner. As the
owner of the group please re-encrypt the key as a safeguard
</Typography>
</Box>
) : isForceShowCreationKeyPopup ? null : (
<Box>
<Typography>
The group member list has changed. Please re-encrypt the secret key.
</Typography>
</Box>
)}
<Box
sx={{
display: 'flex',
justifyContent: 'flex-end',
width: '100%',
}}
>
<Button
onClick={() => {
setHideCommonKeyPopup(true);
setIsForceShowCreationKeyPopup(false);
}}
size="small"
>
Hide
</Button>
</Box>
<CustomizedSnackbars open={openSnack} setOpen={setOpenSnack} info={infoSnack} setInfo={setInfoSnack} />
<CustomizedSnackbars
open={openSnack}
setOpen={setOpenSnack}
info={infoSnack}
setInfo={setInfoSnack}
/>
</Box>
)
}
);
};

View File

@ -5,31 +5,22 @@ import React, {
useRef,
useState,
} from 'react';
import { CreateCommonSecret } from './CreateCommonSecret';
import { reusableGet } from '../../qdn/publish/pubish';
import { uint8ArrayToObject } from '../../backgroundFunctions/encryption';
import {
base64ToUint8Array,
objectToBase64,
} from '../../qdn/encryption/group-encryption';
import { ChatContainerComp } from './ChatContainer';
import { ChatList } from './ChatList';
import '@chatscope/chat-ui-kit-styles/dist/default/styles.min.css';
import Tiptap from './TipTap';
import {
AuthenticatedContainerInnerTop,
CustomButton,
} from '../../styles/App-styles';
import { CustomButton } from '../../styles/App-styles';
import CircularProgress from '@mui/material/CircularProgress';
import { getBaseApi, getFee } from '../../background';
import { getFee } from '../../background';
import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar';
import { Box, Typography } from '@mui/material';
import { Box, Typography, useTheme } from '@mui/material';
import { Spacer } from '../../common/Spacer';
import ShortUniqueId from 'short-unique-id';
import { AnnouncementList } from './AnnouncementList';
const uid = new ShortUniqueId({ length: 8 });
import CampaignIcon from '@mui/icons-material/Campaign';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import { AnnouncementDiscussion } from './AnnouncementDiscussion';
import {
MyContext,
@ -42,9 +33,11 @@ import {
import { RequestQueueWithPromise } from '../../utils/queue/queue';
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
import { addDataPublishesFunc, getDataPublishesFunc } from '../Group/Group';
import { getRootHeight } from '../../utils/mobile/mobileUtils';
const uid = new ShortUniqueId({ length: 8 });
export const requestQueueCommentCount = new RequestQueueWithPromise(3);
export const requestQueuePublishedAccouncements = new RequestQueueWithPromise(
3
);
@ -125,6 +118,7 @@ export const handleUnencryptedPublishes = (publishes) => {
});
return publishesData;
};
export const GroupAnnouncements = ({
selectedGroup,
secretKey,
@ -264,6 +258,7 @@ export const GroupAnnouncements = ({
});
});
};
const clearEditorContent = () => {
if (editorRef.current) {
editorRef.current.chain().focus().clearContent().run();
@ -301,10 +296,12 @@ export const GroupAnnouncements = ({
try {
pauseAllQueues();
const fee = await getFee('ARBITRARY');
await show({
message: 'Would you like to perform a ARBITRARY transaction?',
publishFee: fee.fee + ' QORT',
});
if (isSending) return;
if (editorRef.current) {
const htmlContent = editorRef.current.getHTML();
@ -387,8 +384,7 @@ export const GroupAnnouncements = ({
);
}
} catch (error) {
} finally {
// dispatch(setIsLoadingGlobal(false))
console.log(error);
}
},
[secretKey]
@ -437,6 +433,8 @@ export const GroupAnnouncements = ({
const interval = useRef<any>(null);
const theme = useTheme();
const checkNewMessages = React.useCallback(async () => {
try {
const identifier = `grp-${selectedGroup}-anc-`;
@ -485,7 +483,7 @@ export const GroupAnnouncements = ({
}
setAnnouncements((prev) => [...newArray, ...prev]);
} catch (error) {
} finally {
console.log(error);
}
}, [announcements, secretKey, selectedGroup]);
@ -537,10 +535,10 @@ export const GroupAnnouncements = ({
: 'calc(100vh - 70px)',
display: 'flex',
flexDirection: 'column',
width: '100%',
visibility: hide && 'hidden',
position: hide && 'fixed',
left: hide && '-1000px',
position: hide && 'fixed',
visibility: hide && 'hidden',
width: '100%',
}}
>
<AnnouncementDiscussion
@ -560,54 +558,54 @@ export const GroupAnnouncements = ({
return (
<div
style={{
// reference to change height
height: isMobile ? `calc(${rootHeight} - 127px` : 'calc(100vh - 70px)',
display: 'flex',
flexDirection: 'column',
width: '100%',
visibility: hide && 'hidden',
position: hide && 'fixed',
height: 'calc(100vh - 70px)',
left: hide && '-1000px',
position: hide && 'fixed',
visibility: hide && 'hidden',
width: '100%',
}}
>
<div
style={{
position: 'relative',
width: '100%',
display: 'flex',
flexDirection: 'column',
flexShrink: 0,
position: 'relative',
width: '100%',
}}
>
{!isMobile && (
<Box
sx={{
width: '100%',
display: 'flex',
justifyContent: 'center',
padding: isMobile ? '8px' : '25px',
fontSize: isMobile ? '16px' : '20px',
gap: '20px',
alignItems: 'center',
display: 'flex',
fontSize: '20px',
gap: '20px',
justifyContent: 'center',
padding: '25px',
width: '100%',
}}
>
<CampaignIcon
sx={{
fontSize: isMobile ? '16px' : '30px',
fontSize: '30px',
}}
/>
Group Announcements
</Box>
)}
<Spacer height={isMobile ? '0px' : '25px'} />
<Spacer height={'25px'} />
</div>
{!isLoading && combinedListTempAndReal?.length === 0 && (
<Box
sx={{
width: '100%',
display: 'flex',
justifyContent: 'center',
width: '100%',
}}
>
<Typography
@ -634,31 +632,28 @@ export const GroupAnnouncements = ({
{isAdmin && (
<div
style={{
// position: 'fixed',
// bottom: '0px',
backgroundColor: '#232428',
minHeight: isMobile ? '0px' : '150px',
maxHeight: isMobile ? 'auto' : '400px',
backgroundColor: theme.palette.background.default,
bottom: isFocusedParent ? '0px' : 'unset',
boxSizing: 'border-box',
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
width: '100%',
boxSizing: 'border-box',
padding: isMobile ? '10px' : '20px',
position: isFocusedParent ? 'fixed' : 'relative',
bottom: isFocusedParent ? '0px' : 'unset',
top: isFocusedParent ? '0px' : 'unset',
zIndex: isFocusedParent ? 5 : 'unset',
flexShrink: 0,
maxHeight: '400px',
minHeight: '150px',
overflow: 'hidden',
padding: '20px',
position: isFocusedParent ? 'fixed' : 'relative',
top: isFocusedParent ? '0px' : 'unset',
width: '100%',
zIndex: isFocusedParent ? 5 : 'unset',
}}
>
<div
style={{
display: 'flex',
flexDirection: 'column',
flexGrow: isMobile && 1,
flexGrow: 1,
overflow: 'auto',
// height: '100%',
}}
>
<Tiptap
@ -670,14 +665,15 @@ export const GroupAnnouncements = ({
setIsFocusedParent={setIsFocusedParent}
/>
</div>
<Box
sx={{
display: 'flex',
width: '100&',
flexShrink: 0,
gap: '10px',
justifyContent: 'center',
flexShrink: 0,
position: 'relative',
width: '100&',
}}
>
{isFocusedParent && (
@ -692,43 +688,46 @@ export const GroupAnnouncements = ({
// Unfocus the editor
}}
style={{
marginTop: 'auto',
alignSelf: 'center',
cursor: isSending ? 'default' : 'pointer',
background: 'var(--danger)',
cursor: isSending ? 'default' : 'pointer',
flexShrink: 0,
padding: isMobile && '5px',
fontSize: isMobile && '14px',
fontSize: '14px',
marginTop: 'auto',
padding: '5px',
}}
>
{` Close`}
</CustomButton>
)}
<CustomButton
onClick={() => {
if (isSending) return;
publishAnnouncement();
}}
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,
padding: isMobile && '5px',
fontSize: isMobile && '14px',
fontSize: '14px',
marginTop: 'auto',
padding: '5px',
}}
>
{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',
}}
/>
)}

View File

@ -1,19 +1,6 @@
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 { useContext, useEffect, useState } from 'react';
import { GroupMail } from '../Group/Forum/GroupMail';
import { MyContext, isMobile } from '../../App';
export const GroupForum = ({
selectedGroup,
@ -23,12 +10,13 @@ export const GroupForum = ({
isAdmin,
myAddress,
hide,
defaultThread,
defaultThread,
setDefaultThread,
isPrivate
isPrivate,
}) => {
const { rootHeight } = useContext(MyContext);
const { rootHeight } = useContext(MyContext);
const [isMoved, setIsMoved] = useState(false);
useEffect(() => {
if (hide) {
setTimeout(() => setIsMoved(true), 300); // Wait for the fade-out to complete before moving
@ -39,20 +27,27 @@ export const GroupForum = ({
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'
}}
>
<GroupMail isPrivate={isPrivate} hide={hide} getSecretKey={getSecretKey} selectedGroup={selectedGroup} userInfo={userInfo} secretKey={secretKey} defaultThread={defaultThread} setDefaultThread={setDefaultThread} />
</div>
style={{
display: 'flex',
flexDirection: 'column',
height: 'calc(100vh - 70px)',
left: hide && '-1000px',
opacity: hide ? 0 : 1,
position: hide ? 'fixed' : 'relative',
visibility: hide && 'hidden',
width: '100%',
}}
>
<GroupMail
isPrivate={isPrivate}
hide={hide}
getSecretKey={getSecretKey}
selectedGroup={selectedGroup}
userInfo={userInfo}
secretKey={secretKey}
defaultThread={defaultThread}
setDefaultThread={setDefaultThread}
/>
</div>
);
};

View File

@ -1,69 +1,68 @@
import React, {
forwardRef, useEffect, useImperativeHandle,
useState,
} from 'react'
export default forwardRef((props, ref) => {
const [selectedIndex, setSelectedIndex] = useState(0)
const selectItem = index => {
const item = props.items[index]
if (item) {
props.command(item)
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
export default forwardRef((props, ref) => {
const [selectedIndex, setSelectedIndex] = useState(0);
const selectItem = (index) => {
const item = props.items[index];
if (item) {
props.command(item);
}
};
const upHandler = () => {
setSelectedIndex(
(selectedIndex + props.items.length - 1) % props.items.length
);
};
const downHandler = () => {
setSelectedIndex((selectedIndex + 1) % props.items.length);
};
const enterHandler = () => {
selectItem(selectedIndex);
};
useEffect(() => setSelectedIndex(0), [props.items]);
useImperativeHandle(ref, () => ({
onKeyDown: ({ event }) => {
if (event.key === 'ArrowUp') {
upHandler();
return true;
}
}
const upHandler = () => {
setSelectedIndex((selectedIndex + props.items.length - 1) % props.items.length)
}
const downHandler = () => {
setSelectedIndex((selectedIndex + 1) % props.items.length)
}
const enterHandler = () => {
selectItem(selectedIndex)
}
useEffect(() => setSelectedIndex(0), [props.items])
useImperativeHandle(ref, () => ({
onKeyDown: ({ event }) => {
if (event.key === 'ArrowUp') {
upHandler()
return true
}
if (event.key === 'ArrowDown') {
downHandler()
return true
}
if (event.key === 'Enter') {
enterHandler()
return true
}
return false
},
}))
return (
<div className="dropdown-menu">
{props.items.length
? props.items.map((item, index) => (
<button
className={index === selectedIndex ? 'is-selected' : ''}
key={item.id || index}
onClick={() => selectItem(index)}
>
{item.label}
</button>
))
: <div className="item">No result</div>
}
</div>
)
})
if (event.key === 'ArrowDown') {
downHandler();
return true;
}
if (event.key === 'Enter') {
enterHandler();
return true;
}
return false;
},
}));
return (
<div className="dropdown-menu">
{props.items.length ? (
props.items.map((item, index) => (
<button
className={index === selectedIndex ? 'is-selected' : ''}
key={item.id || index}
onClick={() => selectItem(index)}
>
{item.label}
</button>
))
) : (
<div className="item">No result</div>
)}
</div>
);
});

View File

@ -1,8 +1,10 @@
import React, { useRef } from 'react';
import { useRef } from 'react';
import { NodeViewWrapper } from '@tiptap/react';
import { useTheme } from '@mui/material';
const ResizableImage = ({ node, updateAttributes, selected }) => {
const imgRef = useRef(null);
const theme = useTheme();
const startResizing = (e) => {
e.preventDefault();
@ -40,18 +42,23 @@ const ResizableImage = ({ node, updateAttributes, selected }) => {
src={node.attrs.src}
alt={node.attrs.alt || ''}
title={node.attrs.title || ''}
style={{ width: node.attrs.width || 'auto', display: 'block', margin: '0 auto' }}
style={{
width: node.attrs.width || 'auto',
display: 'block',
margin: '0 auto',
}}
draggable={false} // Prevent image dragging
/>
<div
style={{
backgroundColor: theme.palette.background.paper,
bottom: 0,
cursor: 'nwse-resize',
height: '10px',
position: 'absolute',
right: 0,
bottom: 0,
width: '10px',
height: '10px',
backgroundColor: 'gray',
cursor: 'nwse-resize',
zIndex: 1, // Ensure the resize handle is above other content
}}
onMouseDown={startResizing}