From 2a41667ef87e7fcfa68dd482fb831e1904442873 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Sat, 12 Apr 2025 16:40:32 +0200 Subject: [PATCH] Format code, rmove unused --- src/ExtStates/NotAuthenticated.tsx | 4 +- src/common/BoundedNumericTextField.tsx | 60 +- src/components/AddressQRCode.tsx | 52 +- src/components/Apps/AppsHomeDesktop.tsx | 3 +- .../Chat/AnnouncementDiscussion.tsx | 414 ++-- src/components/Chat/AnnouncementList.tsx | 88 +- src/components/Chat/ChatDirect.tsx | 1265 +++++----- src/components/Chat/ChatGroup.tsx | 1880 ++++++++------ src/components/Chat/GroupAnnouncements.tsx | 419 ++-- src/components/DesktopSideBar.tsx | 2 +- src/components/GlobalActions/JoinGroup.tsx | 148 +- src/components/Group/Group.tsx | 2206 +++++++++-------- src/components/Minting/Minting.tsx | 314 ++- src/components/QortPayment.tsx | 303 +-- src/components/Theme/ThemeContext.tsx | 29 + src/components/Theme/ThemeSelector.tsx | 77 + 16 files changed, 3969 insertions(+), 3295 deletions(-) create mode 100644 src/components/Theme/ThemeContext.tsx create mode 100644 src/components/Theme/ThemeSelector.tsx diff --git a/src/ExtStates/NotAuthenticated.tsx b/src/ExtStates/NotAuthenticated.tsx index 049c282..f7729de 100644 --- a/src/ExtStates/NotAuthenticated.tsx +++ b/src/ExtStates/NotAuthenticated.tsx @@ -6,7 +6,7 @@ import React, { useState, } from 'react'; import { Spacer } from '../common/Spacer'; -import { CustomButton, TextP, TextSpan } from '../App-styles'; +import { CustomButton, TextP, TextSpan } from '../styles/App-styles'; import { Box, Button, @@ -29,7 +29,7 @@ import { CustomizedSnackbars } from '../components/Snackbar/Snackbar'; import { cleanUrl, gateways } from '../background'; import { GlobalContext } from '../App'; import Tooltip, { TooltipProps, tooltipClasses } from '@mui/material/Tooltip'; -import ThemeSelector from '../styles/ThemeSelector'; +import ThemeSelector from '../components/Theme/ThemeSelector'; const manifestData = { version: '0.5.3', diff --git a/src/common/BoundedNumericTextField.tsx b/src/common/BoundedNumericTextField.tsx index c997f8a..9951d7e 100644 --- a/src/common/BoundedNumericTextField.tsx +++ b/src/common/BoundedNumericTextField.tsx @@ -3,15 +3,15 @@ import { InputAdornment, TextField, TextFieldProps, -} from "@mui/material"; -import React, { useRef, useState } from "react"; -import AddIcon from "@mui/icons-material/Add"; -import RemoveIcon from "@mui/icons-material/Remove"; +} from '@mui/material'; +import React, { useRef, useState } from 'react'; +import AddIcon from '@mui/icons-material/Add'; +import RemoveIcon from '@mui/icons-material/Remove'; import { removeTrailingZeros, setNumberWithinBounds, -} from "./numberFunctions.ts"; -import { CustomInput } from "../App-styles.ts"; +} from './numberFunctions.ts'; +import { CustomInput } from '../styles/App-styles.ts'; type eventType = React.ChangeEvent; type BoundedNumericTextFieldProps = { @@ -37,18 +37,18 @@ export const BoundedNumericTextField = ({ ...props }: BoundedNumericTextFieldProps) => { const [textFieldValue, setTextFieldValue] = useState( - initialValue || "" + initialValue || '' ); const ref = useRef(null); const stringIsEmpty = (value: string) => { - return value === ""; + return value === ''; }; const isAllZerosNum = /^0*\.?0*$/; const isFloatNum = /^-?[0-9]*\.?[0-9]*$/; const isIntegerNum = /^-?[0-9]+$/; const skipMinMaxCheck = (value: string) => { - const lastIndexIsDecimal = value.charAt(value.length - 1) === "."; + const lastIndexIsDecimal = value.charAt(value.length - 1) === '.'; const isEmpty = stringIsEmpty(value); const isAllZeros = isAllZerosNum.test(value); const isInteger = isIntegerNum.test(value); @@ -69,7 +69,7 @@ export const BoundedNumericTextField = ({ const getSigDigits = (number: string) => { if (isIntegerNum.test(number)) return 0; - const decimalSplit = number.split("."); + const decimalSplit = number.split('.'); return decimalSplit[decimalSplit.length - 1].length; }; @@ -78,15 +78,15 @@ export const BoundedNumericTextField = ({ }; const filterTypes = (value: string) => { - if (allowDecimals === false) value = value.replace(".", ""); - if (allowNegatives === false) value = value.replace("-", ""); + if (allowDecimals === false) value = value.replace('.', ''); + if (allowNegatives === false) value = value.replace('-', ''); if (sigDigitsExceeded(value, maxSigDigits)) { value = value.substring(0, value.length - 1); } return value; }; const filterValue = (value: string) => { - if (stringIsEmpty(value)) return ""; + if (stringIsEmpty(value)) return ''; value = filterTypes(value); if (isFloatNum.test(value)) { return setMinMaxValue(value); @@ -109,8 +109,8 @@ export const BoundedNumericTextField = ({ const formatValueOnBlur = (e: eventType) => { let value = e.target.value; - if (stringIsEmpty(value) || value === ".") { - setTextFieldValue(""); + if (stringIsEmpty(value) || value === '.') { + setTextFieldValue(''); return; } @@ -129,23 +129,33 @@ export const BoundedNumericTextField = ({ ...props?.InputProps, endAdornment: addIconButtons ? ( - changeValueWithIncDecButton(1)}> - {" "} + changeValueWithIncDecButton(1)} + > + {' '} - changeValueWithIncDecButton(-1)}> - {" "} + changeValueWithIncDecButton(-1)} + > + {' '} ) : ( <> ), }} - onChange={e => listeners(e as eventType)} - onBlur={e => { + onChange={(e) => listeners(e as eventType)} + onBlur={(e) => { formatValueOnBlur(e as eventType); }} autoComplete="off" diff --git a/src/components/AddressQRCode.tsx b/src/components/AddressQRCode.tsx index 77fa252..ebe2036 100644 --- a/src/components/AddressQRCode.tsx +++ b/src/components/AddressQRCode.tsx @@ -1,57 +1,57 @@ -import React, { useState } from "react"; -import QRCode from "react-qr-code"; -import { TextP } from "../App-styles"; -import { Box, Typography } from "@mui/material"; +import React, { useState } from 'react'; +import QRCode from 'react-qr-code'; +import { TextP } from '../styles/App-styles'; +import { Box, Typography } from '@mui/material'; export const AddressQRCode = ({ targetAddress }) => { const [open, setOpen] = useState(false); return ( { - setOpen((prev)=> !prev); + setOpen((prev) => !prev); }} > - {open ? 'Hide QR code' :'See QR code'} + {open ? 'Hide QR code' : 'See QR code'} {open && ( diff --git a/src/components/Apps/AppsHomeDesktop.tsx b/src/components/Apps/AppsHomeDesktop.tsx index 7d65edb..b8167ca 100644 --- a/src/components/Apps/AppsHomeDesktop.tsx +++ b/src/components/Apps/AppsHomeDesktop.tsx @@ -15,7 +15,8 @@ import { SortablePinnedApps } from './SortablePinnedApps'; import { extractComponents } from '../Chat/MessageDisplay'; import ArrowOutwardIcon from '@mui/icons-material/ArrowOutward'; import { AppsPrivate } from './AppsPrivate'; -import ThemeSelector from '../../styles/ThemeSelector'; +import ThemeSelector from '../Theme/ThemeSelector'; + export const AppsHomeDesktop = ({ setMode, myApp, diff --git a/src/components/Chat/AnnouncementDiscussion.tsx b/src/components/Chat/AnnouncementDiscussion.tsx index 5603de0..5ebe885 100644 --- a/src/components/Chat/AnnouncementDiscussion.tsx +++ b/src/components/Chat/AnnouncementDiscussion.tsx @@ -1,18 +1,32 @@ -import React, { useMemo, useRef, useState } from "react"; -import TipTap from "./TipTap"; -import { AuthenticatedContainerInnerTop, CustomButton } from "../../App-styles"; -import { Box, CircularProgress } from "@mui/material"; -import { objectToBase64 } from "../../qdn/encryption/group-encryption"; -import ShortUniqueId from "short-unique-id"; -import { LoadingSnackbar } from "../Snackbar/LoadingSnackbar"; -import { getBaseApi, getFee } from "../../background"; -import { decryptPublishes, getTempPublish, handleUnencryptedPublishes, saveTempPublish } from "./GroupAnnouncements"; -import { AnnouncementList } from "./AnnouncementList"; -import { Spacer } from "../../common/Spacer"; +import React, { useMemo, useRef, useState } from 'react'; +import TipTap from './TipTap'; +import { + AuthenticatedContainerInnerTop, + CustomButton, +} from '../../styles/App-styles'; +import { Box, CircularProgress } from '@mui/material'; +import { objectToBase64 } from '../../qdn/encryption/group-encryption'; +import ShortUniqueId from 'short-unique-id'; +import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar'; +import { getBaseApi, getFee } from '../../background'; +import { + decryptPublishes, + getTempPublish, + handleUnencryptedPublishes, + saveTempPublish, +} from './GroupAnnouncements'; +import { AnnouncementList } from './AnnouncementList'; +import { Spacer } from '../../common/Spacer'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; -import { getArbitraryEndpointReact, getBaseApiReact, isMobile, pauseAllQueues, resumeAllQueues } from "../../App"; +import { + getArbitraryEndpointReact, + getBaseApiReact, + isMobile, + pauseAllQueues, + resumeAllQueues, +} from '../../App'; -const tempKey = 'accouncement-comment' +const tempKey = 'accouncement-comment'; const uid = new ShortUniqueId({ length: 8 }); export const AnnouncementDiscussion = ({ @@ -23,28 +37,28 @@ export const AnnouncementDiscussion = ({ setSelectedAnnouncement, show, myName, - isPrivate + isPrivate, }) => { const [isSending, setIsSending] = useState(false); const [isLoading, setIsLoading] = useState(false); const [isFocusedParent, setIsFocusedParent] = useState(false); - const [comments, setComments] = useState([]) - const [tempPublishedList, setTempPublishedList] = useState([]) - const firstMountRef = useRef(false) - const [data, setData] = useState({}) + const [comments, setComments] = useState([]); + const [tempPublishedList, setTempPublishedList] = useState([]); + const firstMountRef = useRef(false); + const [data, setData] = useState({}); const editorRef = useRef(null); const setEditorRef = (editorInstance) => { editorRef.current = editorInstance; }; - + const clearEditorContent = () => { if (editorRef.current) { editorRef.current.chain().focus().clearContent().run(); - if(isMobile){ + if (isMobile) { setTimeout(() => { - editorRef.current?.chain().blur().run(); - setIsFocusedParent(false) + editorRef.current?.chain().blur().run(); + setIsFocusedParent(false); }, 200); } } @@ -52,14 +66,16 @@ export const AnnouncementDiscussion = ({ const getData = async ({ identifier, name }, isPrivate) => { try { - const res = await fetch( `${getBaseApiReact()}/arbitrary/DOCUMENT/${name}/${identifier}?encoding=base64` ); - if(!res?.ok) return + if (!res?.ok) return; const data = await res.text(); - const response = isPrivate === false ? handleUnencryptedPublishes([data]) : await decryptPublishes([{ data }], secretKey); - + const response = + isPrivate === false + ? handleUnencryptedPublishes([data]) + : await decryptPublishes([{ data }], secretKey); + const messageData = response[0]; setData((prev) => { return { @@ -67,19 +83,19 @@ export const AnnouncementDiscussion = ({ [`${identifier}-${name}`]: messageData, }; }); - } catch (error) {} }; const publishAnc = async ({ encryptedData, identifier }: any) => { try { if (!selectedAnnouncement) return; - + return new Promise((res, rej) => { - window.sendMessage("publishGroupEncryptedResource", { - encryptedData, - identifier, - }) + window + .sendMessage('publishGroupEncryptedResource', { + encryptedData, + identifier, + }) .then((response) => { if (!response?.error) { res(response); @@ -88,63 +104,60 @@ export const AnnouncementDiscussion = ({ rej(response.error); }) .catch((error) => { - rej(error.message || "An error occurred"); + rej(error.message || 'An error occurred'); }); - }); } catch (error) {} }; - const setTempData = async ()=> { + const setTempData = async () => { try { - const getTempAnnouncements = await getTempPublish() - if(getTempAnnouncements[tempKey]){ - let tempData = [] - Object.keys(getTempAnnouncements[tempKey] || {}).map((key)=> { - const value = getTempAnnouncements[tempKey][key] - if(value.data?.announcementId === selectedAnnouncement.identifier){ - tempData.push(value.data) + const getTempAnnouncements = await getTempPublish(); + if (getTempAnnouncements[tempKey]) { + let tempData = []; + Object.keys(getTempAnnouncements[tempKey] || {}).map((key) => { + const value = getTempAnnouncements[tempKey][key]; + if (value.data?.announcementId === selectedAnnouncement.identifier) { + tempData.push(value.data); + } + }); + setTempPublishedList(tempData); } - }) - setTempPublishedList(tempData) - } - } catch (error) { - - } - - } + } catch (error) {} + }; const publishComment = async () => { try { - pauseAllQueues() - const fee = await getFee('ARBITRARY') + pauseAllQueues(); + const fee = await getFee('ARBITRARY'); await show({ - message: "Would you like to perform a ARBITRARY transaction?" , - publishFee: fee.fee + ' QORT' - }) + message: 'Would you like to perform a ARBITRARY transaction?', + publishFee: fee.fee + ' QORT', + }); if (isSending) return; if (editorRef.current) { const htmlContent = editorRef.current.getHTML(); - - if (!htmlContent?.trim() || htmlContent?.trim() === "

") return; + + if (!htmlContent?.trim() || htmlContent?.trim() === '

') return; setIsSending(true); const message = { version: 1, extra: {}, message: htmlContent, }; - const secretKeyObject = isPrivate === false ? null : await getSecretKey(false, true); - const message64: any = await objectToBase64(message); - - const encryptSingle = isPrivate === false ? message64 : await encryptChatMessage( - message64, - secretKeyObject - ); + const secretKeyObject = + isPrivate === false ? null : await getSecretKey(false, true); + const message64: any = await objectToBase64(message); + + const encryptSingle = + isPrivate === false + ? message64 + : await encryptChatMessage(message64, secretKeyObject); const randomUid = uid.rnd(); const identifier = `cm-${selectedAnnouncement.identifier}-${randomUid}`; const res = await publishAnc({ encryptedData: encryptSingle, - identifier + identifier, }); const dataToSaveToStorage = { @@ -153,18 +166,18 @@ export const AnnouncementDiscussion = ({ service: 'DOCUMENT', tempData: message, created: Date.now(), - announcementId: selectedAnnouncement.identifier - } - await saveTempPublish({data: dataToSaveToStorage, key: tempKey}) - setTempData() - + announcementId: selectedAnnouncement.identifier, + }; + await saveTempPublish({ data: dataToSaveToStorage, key: tempKey }); + setTempData(); + clearEditorContent(); } // send chat message } catch (error) { console.error(error); } finally { - resumeAllQueues() + resumeAllQueues(); setIsSending(false); } }; @@ -172,7 +185,6 @@ export const AnnouncementDiscussion = ({ const getComments = React.useCallback( async (selectedAnnouncement, isPrivate) => { try { - setIsLoading(true); const offset = 0; @@ -181,13 +193,13 @@ export const AnnouncementDiscussion = ({ const identifier = `cm-${selectedAnnouncement.identifier}`; const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=20&includemetadata=false&offset=${offset}&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(); - setTempData() + setTempData(); setComments(responseData); setIsLoading(false); for (const data of responseData) { @@ -203,119 +215,122 @@ export const AnnouncementDiscussion = ({ [secretKey] ); - const loadMore = async()=> { + const loadMore = async () => { try { setIsLoading(true); - const offset = comments.length + const offset = comments.length; const identifier = `cm-${selectedAnnouncement.identifier}`; - const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=20&includemetadata=false&offset=${offset}&reverse=true&prefix=true`; - const response = await fetch(url, { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }); - const responseData = await response.json(); + const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=20&includemetadata=false&offset=${offset}&reverse=true&prefix=true`; + const response = await fetch(url, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + const responseData = await response.json(); - setComments((prev)=> [...prev, ...responseData]); - setIsLoading(false); - for (const data of responseData) { - getData({ name: data.name, identifier: data.identifier }, isPrivate); - } - } catch (error) { - - } - - } + setComments((prev) => [...prev, ...responseData]); + setIsLoading(false); + for (const data of responseData) { + getData({ name: data.name, identifier: data.identifier }, isPrivate); + } + } catch (error) {} + }; const combinedListTempAndReal = useMemo(() => { // Combine the two lists const combined = [...tempPublishedList, ...comments]; - + // Remove duplicates based on the "identifier" const uniqueItems = new Map(); - combined.forEach(item => { - uniqueItems.set(item.identifier, item); // This will overwrite duplicates, keeping the last occurrence + combined.forEach((item) => { + uniqueItems.set(item.identifier, 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) => b.created - a.created); - + const sortedList = Array.from(uniqueItems.values()).sort( + (a, b) => b.created - a.created + ); + return sortedList; }, [tempPublishedList, comments]); React.useEffect(() => { - if(!secretKey && isPrivate) return + if (!secretKey && isPrivate) return; if (selectedAnnouncement && !firstMountRef.current && isPrivate !== null) { getComments(selectedAnnouncement, isPrivate); - firstMountRef.current = true + firstMountRef.current = true; } }, [selectedAnnouncement, secretKey, isPrivate]); return (
-
- - - setSelectedAnnouncement(null)} sx={{ - cursor: 'pointer' - }} /> - +
+ + setSelectedAnnouncement(null)} + sx={{ + cursor: 'pointer', + }} + /> + -
{}} + setSelectedAnnouncement={() => {}} disableComment showLoadMore={comments.length > 0 && comments.length % 20 === 0} loadMore={loadMore} myName={myName} - />
- - {isFocusedParent && ( - { - if(isSending) return - setIsFocusedParent(false) - clearEditorContent() - // Unfocus the editor - }} - style={{ - marginTop: 'auto', - alignSelf: 'center', - cursor: isSending ? 'default' : 'pointer', - flexShrink: 0, - padding: isMobile && '5px', - fontSize: isMobile && '14px', - background: 'red', - }} - > - - {` Close`} - - - )} - { - if (isSending) return; - publishComment(); - }} - style={{ - marginTop: "auto", - alignSelf: "center", - cursor: isSending ? "default" : "pointer", - background: isSending && "rgba(0, 0, 0, 0.8)", + - {isSending && ( - { + if (isSending) return; + setIsFocusedParent(false); + clearEditorContent(); + // Unfocus the editor }} - /> + style={{ + marginTop: 'auto', + alignSelf: 'center', + cursor: isSending ? 'default' : 'pointer', + flexShrink: 0, + padding: isMobile && '5px', + fontSize: isMobile && '14px', + background: 'red', + }} + > + {` Close`} + )} - {` Publish Comment`} - - - + { + if (isSending) return; + publishComment(); + }} + style={{ + marginTop: 'auto', + alignSelf: 'center', + cursor: isSending ? 'default' : 'pointer', + background: isSending && 'rgba(0, 0, 0, 0.8)', + flexShrink: 0, + padding: isMobile && '5px', + fontSize: isMobile && '14px', + }} + > + {isSending && ( + + )} + {` Publish Comment`} + +
- +
diff --git a/src/components/Chat/AnnouncementList.tsx b/src/components/Chat/AnnouncementList.tsx index b55ebb5..efaf1fa 100644 --- a/src/components/Chat/AnnouncementList.tsx +++ b/src/components/Chat/AnnouncementList.tsx @@ -1,13 +1,13 @@ -import React, { useCallback, useState, useEffect, useRef } from "react"; +import React, { useCallback, useState, useEffect, useRef } from 'react'; import { List, AutoSizer, CellMeasurerCache, CellMeasurer, -} from "react-virtualized"; -import { AnnouncementItem } from "./AnnouncementItem"; -import { Box } from "@mui/material"; -import { CustomButton } from "../../App-styles"; +} from 'react-virtualized'; +import { AnnouncementItem } from './AnnouncementItem'; +import { Box } from '@mui/material'; +import { CustomButton } from '../../styles/App-styles'; const cache = new CellMeasurerCache({ fixedWidth: true, @@ -21,9 +21,8 @@ export const AnnouncementList = ({ disableComment, showLoadMore, loadMore, - myName + myName, }) => { - const listRef = useRef(); const [messages, setMessages] = useState(initialMessages); @@ -35,39 +34,44 @@ export const AnnouncementList = ({ setMessages(initialMessages); }, [initialMessages]); - return (
{messages.map((message) => { - const messageData = message?.tempData ? { - decryptedData: message?.tempData - } : announcementData[`${message.identifier}-${message.name}`]; + const messageData = message?.tempData + ? { + decryptedData: message?.tempData, + } + : announcementData[`${message.identifier}-${message.name}`]; return ( - -
- -
- +
+ +
); })} {/* @@ -83,16 +87,20 @@ export const AnnouncementList = ({ /> )} */} - - {showLoadMore && ( - Load older announcements - )} - + + {showLoadMore && ( + + Load older announcements + + )} +
); }; diff --git a/src/components/Chat/ChatDirect.tsx b/src/components/Chat/ChatDirect.tsx index d23f026..1af3f8e 100644 --- a/src/components/Chat/ChatDirect.tsx +++ b/src/components/Chat/ChatDirect.tsx @@ -1,52 +1,78 @@ -import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react' +import React, { + useCallback, + useEffect, + useMemo, + useReducer, + useRef, + 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 '../../App-styles' +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 { LoadingSnackbar } from '../Snackbar/LoadingSnackbar'; import { getNameInfo } from '../Group/Group'; import { Spacer } from '../../common/Spacer'; import { CustomizedSnackbars } from '../Snackbar/Snackbar'; -import { getBaseApiReact, getBaseApiReactSocket, isMobile, pauseAllQueues, resumeAllQueues } from '../../App'; +import { + getBaseApiReact, + getBaseApiReactSocket, + isMobile, + pauseAllQueues, + resumeAllQueues, +} from '../../App'; import { getPublicKey } from '../../background'; import { useMessageQueue } from '../../MessageQueueContext'; -import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from '../../utils/events'; +import { + executeEvent, + subscribeToEvent, + unsubscribeFromEvent, +} from '../../utils/events'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; -import ShortUniqueId from "short-unique-id"; +import ShortUniqueId from 'short-unique-id'; import { ReturnIcon } from '../../assets/Icons/ReturnIcon'; import { ExitIcon } from '../../assets/Icons/ExitIcon'; import { MessageItem, ReplyPreview } from './MessageItem'; - const uid = new ShortUniqueId({ length: 5 }); +export const ChatDirect = ({ + myAddress, + isNewChat, + selectedDirect, + setSelectedDirect, + setNewChat, + getTimestampEnterChat, + myName, + balance, + close, + setMobileViewModeKeepOpen, +}) => { + const { queueChats, addToQueue, processWithNewMessages } = useMessageQueue(); + const [isFocusedParent, setIsFocusedParent] = useState(false); + const [onEditMessage, setOnEditMessage] = useState(null); -export const ChatDirect = ({ myAddress, isNewChat, selectedDirect, setSelectedDirect, setNewChat, getTimestampEnterChat, myName, balance, close, setMobileViewModeKeepOpen}) => { - const { queueChats, addToQueue, processWithNewMessages} = useMessageQueue(); - const [isFocusedParent, setIsFocusedParent] = useState(false); - const [onEditMessage, setOnEditMessage] = useState(null) - - const [messages, setMessages] = useState([]) - const [isSending, setIsSending] = useState(false) - const [directToValue, setDirectToValue] = useState('') - const hasInitialized = useRef(false) - const [isLoading, setIsLoading] = useState(false) + const [messages, setMessages] = useState([]); + const [isSending, setIsSending] = useState(false); + const [directToValue, setDirectToValue] = useState(''); + const hasInitialized = useRef(false); + const [isLoading, setIsLoading] = useState(false); const [openSnack, setOpenSnack] = React.useState(false); const [infoSnack, setInfoSnack] = React.useState(null); - const [publicKeyOfRecipient, setPublicKeyOfRecipient] = React.useState("") - const hasInitializedWebsocket = useRef(false) - const [chatReferences, setChatReferences] = useState({}) + const [publicKeyOfRecipient, setPublicKeyOfRecipient] = React.useState(''); + const hasInitializedWebsocket = useRef(false); + const [chatReferences, setChatReferences] = useState({}); const editorRef = useRef(null); const socketRef = useRef(null); const timeoutIdRef = useRef(null); - const [messageSize, setMessageSize] = useState(0) + const [messageSize, setMessageSize] = useState(0); const groupSocketTimeoutRef = useRef(null); - const [replyMessage, setReplyMessage] = useState(null) + const [replyMessage, setReplyMessage] = useState(null); const setEditorRef = (editorInstance) => { editorRef.current = editorInstance; }; @@ -55,42 +81,47 @@ export const ChatDirect = ({ myAddress, isNewChat, selectedDirect, setSelectedDi const triggerRerender = () => { forceUpdate(); // Trigger re-render by updating the state }; - const publicKeyOfRecipientRef = useRef(null) - const getPublicKeyFunc = async (address)=> { + const publicKeyOfRecipientRef = useRef(null); + const getPublicKeyFunc = async (address) => { try { - const publicKey = await getPublicKey(address) - if(publicKeyOfRecipientRef.current !== selectedDirect?.address) return - setPublicKeyOfRecipient(publicKey) - } catch (error) { - - } - } + const publicKey = await getPublicKey(address); + if (publicKeyOfRecipientRef.current !== selectedDirect?.address) return; + setPublicKeyOfRecipient(publicKey); + } catch (error) {} + }; - const tempMessages = useMemo(()=> { - if(!selectedDirect?.address) return [] - if(queueChats[selectedDirect?.address]){ - return queueChats[selectedDirect?.address]?.filter((item)=> !item?.chatReference) + const tempMessages = useMemo(() => { + if (!selectedDirect?.address) return []; + if (queueChats[selectedDirect?.address]) { + return queueChats[selectedDirect?.address]?.filter( + (item) => !item?.chatReference + ); } - return [] - }, [selectedDirect?.address, queueChats]) + return []; + }, [selectedDirect?.address, queueChats]); - const tempChatReferences = useMemo(()=> { - if(!selectedDirect?.address) return [] - if(queueChats[selectedDirect?.address]){ - return queueChats[selectedDirect?.address]?.filter((item)=> !!item?.chatReference) + const tempChatReferences = useMemo(() => { + if (!selectedDirect?.address) return []; + if (queueChats[selectedDirect?.address]) { + return queueChats[selectedDirect?.address]?.filter( + (item) => !!item?.chatReference + ); } - return [] - }, [selectedDirect?.address, queueChats]) + return []; + }, [selectedDirect?.address, queueChats]); - useEffect(()=> { - if(selectedDirect?.address){ - publicKeyOfRecipientRef.current = selectedDirect?.address - getPublicKeyFunc(publicKeyOfRecipientRef.current) + useEffect(() => { + if (selectedDirect?.address) { + publicKeyOfRecipientRef.current = selectedDirect?.address; + getPublicKeyFunc(publicKeyOfRecipientRef.current); } - }, [selectedDirect?.address]) - + }, [selectedDirect?.address]); - const middletierFunc = async (data: any, selectedDirectAddress: string, myAddress: string) => { + const middletierFunc = async ( + data: any, + selectedDirectAddress: string, + myAddress: string + ) => { try { if (hasInitialized.current) { decryptMessages(data, true); @@ -99,9 +130,9 @@ export const ChatDirect = ({ myAddress, isNewChat, selectedDirect, setSelectedDi hasInitialized.current = true; const url = `${getBaseApiReact()}/chat/messages?involving=${selectedDirectAddress}&involving=${myAddress}&encoding=BASE64&limit=0&reverse=false`; const response = await fetch(url, { - method: "GET", + method: 'GET', headers: { - "Content-Type": "application/json", + 'Content-Type': 'application/json', }, }); const responseData = await response.json(); @@ -109,589 +140,666 @@ export const ChatDirect = ({ myAddress, isNewChat, selectedDirect, setSelectedDi } catch (error) { console.error(error); } - } + }; - const decryptMessages = (encryptedMessages: any[], isInitiated: boolean)=> { - try { - return new Promise((res, rej)=> { - window.sendMessage("decryptDirect", { + const decryptMessages = (encryptedMessages: any[], isInitiated: boolean) => { + try { + return new Promise((res, rej) => { + window + .sendMessage('decryptDirect', { data: encryptedMessages, involvingAddress: selectedDirect?.address, }) - .then((decryptResponse) => { - if (!decryptResponse?.error) { - const response = processWithNewMessages(decryptResponse, selectedDirect?.address); - res(response); - - if (isInitiated) { - const formatted = response.filter((rawItem) => !rawItem?.chatReference).map((item) => ({ + .then((decryptResponse) => { + if (!decryptResponse?.error) { + const response = processWithNewMessages( + decryptResponse, + selectedDirect?.address + ); + res(response); + + if (isInitiated) { + const formatted = response + .filter((rawItem) => !rawItem?.chatReference) + .map((item) => ({ ...item, id: item.signature, text: item.message, unread: item?.sender === myAddress ? false : true, })); - setMessages((prev) => [...prev, ...formatted]); - setChatReferences((prev) => { - const organizedChatReferences = { ...prev }; + setMessages((prev) => [...prev, ...formatted]); + setChatReferences((prev) => { + const organizedChatReferences = { ...prev }; - response.filter((rawItem) => !!rawItem?.chatReference && rawItem?.type === 'edit').forEach((item) => { - try { - organizedChatReferences[item.chatReference] = { - ...(organizedChatReferences[item.chatReference] || {}), - edit: item - }; - } catch(error){ - - } - }) - return organizedChatReferences - }) - } else { - hasInitialized.current = true; - const formatted = response.filter((rawItem) => !rawItem?.chatReference) + response + .filter( + (rawItem) => + !!rawItem?.chatReference && rawItem?.type === 'edit' + ) + .forEach((item) => { + try { + organizedChatReferences[item.chatReference] = { + ...(organizedChatReferences[item.chatReference] || + {}), + edit: item, + }; + } catch (error) {} + }); + return organizedChatReferences; + }); + } else { + hasInitialized.current = true; + const formatted = response + .filter((rawItem) => !rawItem?.chatReference) .map((item) => ({ ...item, id: item.signature, text: item.message, unread: false, })); - setMessages(formatted); + setMessages(formatted); - setChatReferences((prev) => { - const organizedChatReferences = { ...prev }; + setChatReferences((prev) => { + const organizedChatReferences = { ...prev }; - response.filter((rawItem) => !!rawItem?.chatReference && rawItem?.type === 'edit').forEach((item) => { - try { - organizedChatReferences[item.chatReference] = { - ...(organizedChatReferences[item.chatReference] || {}), - edit: item - }; - } catch(error){ - - } - }) - return organizedChatReferences - }) - } - return; + response + .filter( + (rawItem) => + !!rawItem?.chatReference && rawItem?.type === 'edit' + ) + .forEach((item) => { + try { + organizedChatReferences[item.chatReference] = { + ...(organizedChatReferences[item.chatReference] || + {}), + edit: item, + }; + } catch (error) {} + }); + return organizedChatReferences; + }); } - rej(response.error); - }) - .catch((error) => { - rej(error.message || "An error occurred"); - }); - - }) - } catch (error) { - - } - } + return; + } + rej(response.error); + }) + .catch((error) => { + rej(error.message || 'An error occurred'); + }); + }); + } catch (error) {} + }; - const forceCloseWebSocket = () => { + const forceCloseWebSocket = () => { + if (socketRef.current) { + clearTimeout(timeoutIdRef.current); + clearTimeout(groupSocketTimeoutRef.current); + socketRef.current.close(1000, 'forced'); + socketRef.current = null; + } + }; + + const pingWebSocket = () => { + try { + if (socketRef.current?.readyState === WebSocket.OPEN) { + socketRef.current.send('ping'); + timeoutIdRef.current = setTimeout(() => { + if (socketRef.current) { + socketRef.current.close(); + clearTimeout(groupSocketTimeoutRef.current); + } + }, 5000); // Close if no pong in 5 seconds + } + } catch (error) { + console.error('Error during ping:', error); + } + }; + + const initWebsocketMessageGroup = () => { + forceCloseWebSocket(); // Close any existing connection + + if (!selectedDirect?.address || !myAddress) return; + + const socketLink = `${getBaseApiReactSocket()}/websockets/chat/messages?involving=${selectedDirect?.address}&involving=${myAddress}&encoding=BASE64&limit=100`; + socketRef.current = new WebSocket(socketLink); + + socketRef.current.onopen = () => { + setTimeout(pingWebSocket, 50); // Initial ping + }; + + socketRef.current.onmessage = (e) => { + try { + if (e.data === 'pong') { + clearTimeout(timeoutIdRef.current); + groupSocketTimeoutRef.current = setTimeout(pingWebSocket, 45000); // Ping every 45 seconds + } else { + middletierFunc( + JSON.parse(e.data), + selectedDirect?.address, + myAddress + ); + + setIsLoading(false); + } + } catch (error) { + console.error('Error handling WebSocket message:', error); + } + }; + + socketRef.current.onclose = (event) => { + clearTimeout(groupSocketTimeoutRef.current); + clearTimeout(timeoutIdRef.current); + console.warn(`WebSocket closed: ${event.reason || 'unknown reason'}`); + if (event.reason !== 'forced' && event.code !== 1000) { + setTimeout(() => initWebsocketMessageGroup(), 10000); // Retry after 10 seconds + } + }; + + socketRef.current.onerror = (error) => { + console.error('WebSocket error:', error); + clearTimeout(groupSocketTimeoutRef.current); + clearTimeout(timeoutIdRef.current); if (socketRef.current) { - clearTimeout(timeoutIdRef.current); - clearTimeout(groupSocketTimeoutRef.current); - socketRef.current.close(1000, 'forced'); - socketRef.current = null; + socketRef.current.close(); } }; - - const pingWebSocket = () => { - try { - if (socketRef.current?.readyState === WebSocket.OPEN) { - socketRef.current.send('ping'); - timeoutIdRef.current = setTimeout(() => { - if (socketRef.current) { - socketRef.current.close(); - clearTimeout(groupSocketTimeoutRef.current); - } - }, 5000); // Close if no pong in 5 seconds - } - } catch (error) { - console.error('Error during ping:', error); - } + }; + + const setDirectChatValueFunc = async (e) => { + setDirectToValue(e.detail.directToValue); + }; + useEffect(() => { + subscribeToEvent('setDirectToValueNewChat', setDirectChatValueFunc); + + return () => { + unsubscribeFromEvent('setDirectToValueNewChat', setDirectChatValueFunc); }; - + }, []); - const initWebsocketMessageGroup = () => { - forceCloseWebSocket(); // Close any existing connection - - if (!selectedDirect?.address || !myAddress) return; - - const socketLink = `${getBaseApiReactSocket()}/websockets/chat/messages?involving=${selectedDirect?.address}&involving=${myAddress}&encoding=BASE64&limit=100`; - socketRef.current = new WebSocket(socketLink); - - socketRef.current.onopen = () => { - setTimeout(pingWebSocket, 50); // Initial ping - }; - - socketRef.current.onmessage = (e) => { - try { - if (e.data === 'pong') { - clearTimeout(timeoutIdRef.current); - groupSocketTimeoutRef.current = setTimeout(pingWebSocket, 45000); // Ping every 45 seconds - } else { - middletierFunc(JSON.parse(e.data), selectedDirect?.address, myAddress) + useEffect(() => { + if (hasInitializedWebsocket.current || isNewChat) return; + setIsLoading(true); + initWebsocketMessageGroup(); + hasInitializedWebsocket.current = true; - setIsLoading(false); - } - } catch (error) { - console.error('Error handling WebSocket message:', error); - } - }; - - socketRef.current.onclose = (event) => { - clearTimeout(groupSocketTimeoutRef.current); - clearTimeout(timeoutIdRef.current); - console.warn(`WebSocket closed: ${event.reason || 'unknown reason'}`); - if (event.reason !== 'forced' && event.code !== 1000) { - setTimeout(() => initWebsocketMessageGroup(), 10000); // Retry after 10 seconds - } - }; - - socketRef.current.onerror = (error) => { - console.error('WebSocket error:', error); - clearTimeout(groupSocketTimeoutRef.current); - clearTimeout(timeoutIdRef.current); - if (socketRef.current) { - socketRef.current.close(); - } - }; + return () => { + forceCloseWebSocket(); // Clean up WebSocket on component unmount }; + }, [selectedDirect?.address, myAddress, isNewChat]); - const setDirectChatValueFunc = async (e)=> { - setDirectToValue(e.detail.directToValue) - } - useEffect(() => { - subscribeToEvent("setDirectToValueNewChat", setDirectChatValueFunc); - - return () => { - unsubscribeFromEvent("setDirectToValueNewChat", setDirectChatValueFunc); - }; - }, []); - - useEffect(() => { - if (hasInitializedWebsocket.current || isNewChat) return; - setIsLoading(true); - initWebsocketMessageGroup(); - hasInitializedWebsocket.current = true; - - return () => { - forceCloseWebSocket(); // Clean up WebSocket on component unmount - }; - }, [selectedDirect?.address, myAddress, isNewChat]); + const sendChatDirect = async ( + { chatReference = undefined, messageText, otherData }: any, + address, + publicKeyOfRecipient, + isNewChatVar + ) => { + try { + const directTo = isNewChatVar ? directToValue : address; + if (!directTo) return; + return new Promise((res, rej) => { + window + .sendMessage( + 'sendChatDirect', + { + directTo, + chatReference, + messageText, + otherData, + publicKeyOfRecipient, + address: directTo, + }, + 120000 + ) + .then(async (response) => { + if (!response?.error) { + if (isNewChatVar) { + let getRecipientName = null; + try { + getRecipientName = await getNameInfo(response.recipient); + } catch (error) { + console.error('Error fetching recipient name:', error); + } + setSelectedDirect({ + address: response.recipient, + name: getRecipientName, + timestamp: Date.now(), + sender: myAddress, + senderName: myName, + }); + setNewChat(null); + window + .sendMessage('addTimestampEnterChat', { + timestamp: Date.now(), + groupId: response.recipient, + }) + .catch((error) => { + console.error( + 'Failed to add timestamp:', + error.message || 'An error occurred' + ); + }); -const sendChatDirect = async ({ chatReference = undefined, messageText, otherData}: any, address, publicKeyOfRecipient, isNewChatVar)=> { - try { - const directTo = isNewChatVar ? directToValue : address - - if(!directTo) return - return new Promise((res, rej)=> { - window.sendMessage("sendChatDirect", { - directTo, - chatReference, - messageText, - otherData, - publicKeyOfRecipient, - address: directTo, - }, 120000) - .then(async (response) => { - if (!response?.error) { - if (isNewChatVar) { - let getRecipientName = null; - try { - getRecipientName = await getNameInfo(response.recipient); - } catch (error) { - console.error("Error fetching recipient name:", error); + setTimeout(() => { + getTimestampEnterChat(); + }, 400); } - setSelectedDirect({ - address: response.recipient, - name: getRecipientName, - timestamp: Date.now(), - sender: myAddress, - senderName: myName, - }); - setNewChat(null); - - window.sendMessage("addTimestampEnterChat", { - timestamp: Date.now(), - groupId: response.recipient, - }).catch((error) => { - console.error("Failed to add timestamp:", error.message || "An error occurred"); - }); - - setTimeout(() => { - getTimestampEnterChat(); - }, 400); + res(response); + return; } - res(response); - return; - } - rej(response.error); - }) - .catch((error) => { - rej(error.message || "An error occurred"); - }); - - }) - } catch (error) { - throw new Error(error) - } finally { - } -} -const clearEditorContent = () => { - if (editorRef.current) { - setMessageSize(0) - editorRef.current.chain().focus().clearContent().run(); - if(isMobile){ - setTimeout(() => { - editorRef.current?.chain().blur().run(); - setIsFocusedParent(false) - executeEvent("sent-new-message-group", {}) - setTimeout(() => { - triggerRerender(); - }, 300); - }, 200); + rej(response.error); + }) + .catch((error) => { + rej(error.message || 'An error occurred'); + }); + }); + } catch (error) { + throw new Error(error); + } finally { } - } -}; -useEffect(() => { - if (!editorRef?.current) return; - const handleUpdate = () => { - const htmlContent = editorRef?.current.getHTML(); - const stringified = JSON.stringify(htmlContent); - const size = new Blob([stringified]).size; - setMessageSize(size + 200); }; - - // Add a listener for the editorRef?.current's content updates - editorRef?.current.on('update', handleUpdate); - - // Cleanup the listener on unmount - return () => { - editorRef?.current.off('update', handleUpdate); + const clearEditorContent = () => { + if (editorRef.current) { + setMessageSize(0); + editorRef.current.chain().focus().clearContent().run(); + if (isMobile) { + setTimeout(() => { + editorRef.current?.chain().blur().run(); + setIsFocusedParent(false); + executeEvent('sent-new-message-group', {}); + setTimeout(() => { + triggerRerender(); + }, 300); + }, 200); + } + } }; -}, [editorRef?.current]); + useEffect(() => { + if (!editorRef?.current) return; + const handleUpdate = () => { + const htmlContent = editorRef?.current.getHTML(); + const stringified = JSON.stringify(htmlContent); + const size = new Blob([stringified]).size; + setMessageSize(size + 200); + }; - const sendMessage = async ()=> { - try { - if(messageSize > 4000) return + // Add a listener for the editorRef?.current's content updates + editorRef?.current.on('update', handleUpdate); - - if(+balance < 4) throw new Error('You need at least 4 QORT to send a message') - if(isSending) return - if (editorRef.current) { - const htmlContent = editorRef.current.getHTML(); - - if(!htmlContent?.trim() || htmlContent?.trim() === '

') return - setIsSending(true) - pauseAllQueues() - const message = JSON.stringify(htmlContent) - - - if(isNewChat){ - await sendChatDirect({ messageText: htmlContent}, null, null, true) - return + // Cleanup the listener on unmount + return () => { + editorRef?.current.off('update', handleUpdate); + }; + }, [editorRef?.current]); + + const sendMessage = async () => { + try { + if (messageSize > 4000) return; + + if (+balance < 4) + throw new Error('You need at least 4 QORT to send a message'); + if (isSending) return; + if (editorRef.current) { + const htmlContent = editorRef.current.getHTML(); + + if (!htmlContent?.trim() || htmlContent?.trim() === '

') return; + setIsSending(true); + pauseAllQueues(); + const message = JSON.stringify(htmlContent); + + if (isNewChat) { + await sendChatDirect({ messageText: htmlContent }, null, null, true); + return; } - let repliedTo = replyMessage?.signature + let repliedTo = replyMessage?.signature; - if (replyMessage?.chatReference) { - repliedTo = replyMessage?.chatReference - } - let chatReference = onEditMessage?.signature + if (replyMessage?.chatReference) { + repliedTo = replyMessage?.chatReference; + } + let chatReference = onEditMessage?.signature; const otherData = { ...(onEditMessage?.decryptedData || {}), specialId: uid.rnd(), repliedTo: onEditMessage ? onEditMessage?.repliedTo : repliedTo, - type: chatReference ? 'edit' : '' - } + type: chatReference ? 'edit' : '', + }; const sendMessageFunc = async () => { - return await sendChatDirect({ chatReference, messageText: htmlContent, otherData}, selectedDirect?.address, publicKeyOfRecipient, false) + return await sendChatDirect( + { chatReference, messageText: htmlContent, otherData }, + selectedDirect?.address, + publicKeyOfRecipient, + false + ); }; - - // Add the function to the queue const messageObj = { message: { timestamp: Date.now(), - senderName: myName, - sender: myAddress, - ...(otherData || {}), - text: htmlContent, + senderName: myName, + sender: myAddress, + ...(otherData || {}), + text: htmlContent, }, - chatReference - } - addToQueue(sendMessageFunc, messageObj, 'chat-direct', - selectedDirect?.address ); + chatReference, + }; + addToQueue( + sendMessageFunc, + messageObj, + 'chat-direct', + selectedDirect?.address + ); setTimeout(() => { - executeEvent("sent-new-message-group", {}) + executeEvent('sent-new-message-group', {}); }, 150); - clearEditorContent() - setReplyMessage(null) - setOnEditMessage(null) - - } - // send chat message - } catch (error) { - const errorMsg = error?.message || error - setInfoSnack({ - type: "error", - message: errorMsg === 'invalid signature' ? 'You need at least 4 QORT to send a message' : errorMsg, - }); - setOpenSnack(true); - console.error(error) - } finally { - setIsSending(false) - resumeAllQueues() + clearEditorContent(); + setReplyMessage(null); + setOnEditMessage(null); } + // send chat message + } catch (error) { + const errorMsg = error?.message || error; + setInfoSnack({ + type: 'error', + message: + errorMsg === 'invalid signature' + ? 'You need at least 4 QORT to send a message' + : errorMsg, + }); + setOpenSnack(true); + console.error(error); + } finally { + setIsSending(false); + resumeAllQueues(); } + }; - const onReply = useCallback((message)=> { - if(onEditMessage){ - clearEditorContent() + const onReply = useCallback( + (message) => { + if (onEditMessage) { + clearEditorContent(); } - setReplyMessage(message) - setOnEditMessage(null) - editorRef?.current?.chain().focus() - }, [onEditMessage]) - - - const onEdit = useCallback((message)=> { - setOnEditMessage(message) - setReplyMessage(null) - editorRef.current.chain().focus().setContent(message?.text).run(); - - }, []) - + setReplyMessage(message); + setOnEditMessage(null); + editorRef?.current?.chain().focus(); + }, + [onEditMessage] + ); + + const onEdit = useCallback((message) => { + setOnEditMessage(message); + setReplyMessage(null); + editorRef.current.chain().focus().setContent(message?.text).run(); + }, []); + return ( -
+
{!isMobile && ( - - - Close Direct Chat + + + + Close Direct Chat + + + )} + {isMobile && ( + + + + { + close(); + }} + > + + + + + {isNewChat + ? '' + : selectedDirect?.name || + selectedDirect?.address?.slice(0, 10) + '...'} + + + { + setSelectedDirect(null); + setMobileViewModeKeepOpen(''); + setNewChat(false); + }} + > + + + + )} - {isMobile && ( - - - - { - close() - }} - > - - - - - {isNewChat ? '' : selectedDirect?.name || (selectedDirect?.address?.slice(0,10) + '...')} - - - { - setSelectedDirect(null) - setMobileViewModeKeepOpen('') - setNewChat(false) - }} - > - - - - - - )} {isNewChat && ( <> - - setDirectToValue(e.target.value)} /> - + + setDirectToValue(e.target.value)} + /> )} - - - -
-
+ +
+
- {replyMessage && ( - - - - { - setReplyMessage(null) - setOnEditMessage(null) - - }} - > - - - - )} - {onEditMessage && ( - - - - { - setReplyMessage(null) - setOnEditMessage(null) - - clearEditorContent() - - - }} - > - - - - )} - - - {messageSize > 750 && ( - - 4000 ? 'var(--danger)' : 'unset' - }}>{`Your message size is of ${messageSize} bytes out of a maximum of 4000`} - - - )} -
- - - - { - - if(isSending) return - sendMessage() - }} - style={{ - marginTop: 'auto', - alignSelf: 'center', - cursor: isSending ? 'default' : 'pointer', - background: isSending && 'rgba(0, 0, 0, 0.8)', - flexShrink: 0, - padding: '5px', - width: '100px', - minWidth: 'auto' + justifyContent: 'flex-end', + }} + > + {replyMessage && ( + - {isSending && ( - + + { + setReplyMessage(null); + setOnEditMessage(null); + }} + > + + + + )} + {onEditMessage && ( + + + + { + setReplyMessage(null); + setOnEditMessage(null); + + clearEditorContent(); + }} + > + + + + )} + + + {messageSize > 750 && ( + + 4000 ? 'var(--danger)' : 'unset', + }} + >{`Your message size is of ${messageSize} bytes out of a maximum of 4000`} + + )} +
+ + + { + if (isSending) return; + sendMessage(); + }} + style={{ + marginTop: 'auto', + alignSelf: 'center', + cursor: isSending ? 'default' : 'pointer', + background: isSending && 'rgba(0, 0, 0, 0.8)', + flexShrink: 0, + padding: '5px', + width: '100px', + minWidth: 'auto', + }} + > + {isSending && ( + { left: '50%', marginTop: '-12px', marginLeft: '-12px', - color: 'white' + color: 'white', }} /> - )} - {` Send`} - - - + )} + {` Send`} + +
- - + +
- ) -} + ); +}; diff --git a/src/components/Chat/ChatGroup.tsx b/src/components/Chat/ChatGroup.tsx index e054bd9..4cf5a7a 100644 --- a/src/components/Chat/ChatGroup.tsx +++ b/src/components/Chat/ChatGroup.tsx @@ -1,100 +1,140 @@ -import React, { useCallback, useContext, useEffect, useMemo, useReducer, 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' -import { CustomButton } from '../../App-styles' +import React, { + useCallback, + useContext, + useEffect, + useMemo, + useReducer, + 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'; +import { CustomButton } from '../../styles/App-styles'; import CircularProgress from '@mui/material/CircularProgress'; -import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar' -import { getBaseApiReact, getBaseApiReactSocket, isMobile, MyContext, pauseAllQueues, resumeAllQueues } from '../../App' -import { CustomizedSnackbars } from '../Snackbar/Snackbar' -import { PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY } from '../../constants/codes' -import { useMessageQueue } from '../../MessageQueueContext' -import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from '../../utils/events' -import { Box, ButtonBase, Divider, Typography } from '@mui/material' -import ShortUniqueId from "short-unique-id"; -import { ReplyPreview } from './MessageItem' -import { ExitIcon } from '../../assets/Icons/ExitIcon' -import { RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS } from '../../constants/resourceTypes' -import { isExtMsg } from '../../background' -import AppViewerContainer from '../Apps/AppViewerContainer' -import CloseIcon from "@mui/icons-material/Close"; -import { throttle } from 'lodash' +import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar'; +import { + getBaseApiReact, + getBaseApiReactSocket, + isMobile, + MyContext, + pauseAllQueues, + resumeAllQueues, +} from '../../App'; +import { CustomizedSnackbars } from '../Snackbar/Snackbar'; +import { PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY } from '../../constants/codes'; +import { useMessageQueue } from '../../MessageQueueContext'; +import { + executeEvent, + subscribeToEvent, + unsubscribeFromEvent, +} from '../../utils/events'; +import { Box, ButtonBase, Divider, Typography } from '@mui/material'; +import ShortUniqueId from 'short-unique-id'; +import { ReplyPreview } from './MessageItem'; +import { ExitIcon } from '../../assets/Icons/ExitIcon'; +import { RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS } from '../../constants/resourceTypes'; +import { isExtMsg } from '../../background'; +import AppViewerContainer from '../Apps/AppViewerContainer'; +import CloseIcon from '@mui/icons-material/Close'; +import { throttle } from 'lodash'; const uid = new ShortUniqueId({ length: 5 }); -export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey, myAddress, handleNewEncryptionNotification, hide, handleSecretKeyCreationInProgress, triedToFetchSecretKey, myName, balance, getTimestampEnterChatParent, hideView, isPrivate}) => { - const {isUserBlocked} = useContext(MyContext) - const [messages, setMessages] = useState([]) - const [chatReferences, setChatReferences] = useState({}) - const [isSending, setIsSending] = useState(false) - const [isLoading, setIsLoading] = useState(false) +export const ChatGroup = ({ + selectedGroup, + secretKey, + setSecretKey, + getSecretKey, + myAddress, + handleNewEncryptionNotification, + hide, + handleSecretKeyCreationInProgress, + triedToFetchSecretKey, + myName, + balance, + getTimestampEnterChatParent, + hideView, + isPrivate, +}) => { + const { isUserBlocked } = useContext(MyContext); + const [messages, setMessages] = useState([]); + const [chatReferences, setChatReferences] = useState({}); + const [isSending, setIsSending] = useState(false); + const [isLoading, setIsLoading] = useState(false); const [isMoved, setIsMoved] = useState(false); const [openSnack, setOpenSnack] = React.useState(false); const [infoSnack, setInfoSnack] = React.useState(null); - const hasInitialized = useRef(false) + const hasInitialized = useRef(false); const [isFocusedParent, setIsFocusedParent] = useState(false); - const [replyMessage, setReplyMessage] = useState(null) - const [onEditMessage, setOnEditMessage] = useState(null) - const [isOpenQManager, setIsOpenQManager] = useState(null) + const [replyMessage, setReplyMessage] = useState(null); + const [onEditMessage, setOnEditMessage] = useState(null); + const [isOpenQManager, setIsOpenQManager] = useState(null); -const [messageSize, setMessageSize] = useState(0) - const hasInitializedWebsocket = useRef(false) + const [messageSize, setMessageSize] = useState(0); + const hasInitializedWebsocket = useRef(false); const socketRef = useRef(null); // WebSocket reference const timeoutIdRef = useRef(null); // Timeout ID reference const groupSocketTimeoutRef = useRef(null); // Group Socket Timeout reference const editorRef = useRef(null); const { queueChats, addToQueue, processWithNewMessages } = useMessageQueue(); const [, forceUpdate] = useReducer((x) => x + 1, 0); - const lastReadTimestamp = useRef(null) + const lastReadTimestamp = useRef(null); const handleUpdateRef = useRef(null); - const getTimestampEnterChat = async (selectedGroup) => { try { return new Promise((res, rej) => { - window.sendMessage("getTimestampEnterChat") - .then((response) => { - if (!response?.error) { - if(response && selectedGroup){ - lastReadTimestamp.current = response[selectedGroup] || undefined - window.sendMessage("addTimestampEnterChat", { - timestamp: Date.now(), - groupId: selectedGroup - }).catch((error) => { - console.error("Failed to add timestamp:", error.message || "An error occurred"); + window + .sendMessage('getTimestampEnterChat') + .then((response) => { + if (!response?.error) { + if (response && selectedGroup) { + lastReadTimestamp.current = + response[selectedGroup] || undefined; + window + .sendMessage('addTimestampEnterChat', { + timestamp: Date.now(), + groupId: selectedGroup, + }) + .catch((error) => { + console.error( + 'Failed to add timestamp:', + error.message || 'An error occurred' + ); + }); + + setTimeout(() => { + getTimestampEnterChatParent(); + }, 600); + } + + res(response); + return; + } + rej(response.error); + }) + .catch((error) => { + rej(error.message || 'An error occurred'); }); - - - setTimeout(() => { - getTimestampEnterChatParent(); - }, 600); - } - - res(response); - return; - } - rej(response.error); - }) - .catch((error) => { - rej(error.message || "An error occurred"); - }); - }); } catch (error) {} }; - useEffect(()=> { - if(!selectedGroup) return - getTimestampEnterChat(selectedGroup) - }, [selectedGroup]) - - + useEffect(() => { + if (!selectedGroup) return; + getTimestampEnterChat(selectedGroup); + }, [selectedGroup]); const members = useMemo(() => { const uniqueMembers = new Set(); @@ -115,70 +155,77 @@ const [messageSize, setMessageSize] = useState(0) editorRef.current = editorInstance; }; - const tempMessages = useMemo(()=> { - if(!selectedGroup) return [] - if(queueChats[selectedGroup]){ - return queueChats[selectedGroup]?.filter((item)=> !item?.chatReference) + const tempMessages = useMemo(() => { + if (!selectedGroup) return []; + if (queueChats[selectedGroup]) { + return queueChats[selectedGroup]?.filter((item) => !item?.chatReference); } - return [] - }, [selectedGroup, queueChats]) - const tempChatReferences = useMemo(()=> { - if(!selectedGroup) return [] - if(queueChats[selectedGroup]){ - return queueChats[selectedGroup]?.filter((item)=> !!item?.chatReference) + return []; + }, [selectedGroup, queueChats]); + const tempChatReferences = useMemo(() => { + if (!selectedGroup) return []; + if (queueChats[selectedGroup]) { + return queueChats[selectedGroup]?.filter((item) => !!item?.chatReference); } - return [] - }, [selectedGroup, queueChats]) + return []; + }, [selectedGroup, queueChats]); - const secretKeyRef = useRef(null) + const secretKeyRef = useRef(null); - useEffect(()=> { - if(secretKey){ - secretKeyRef.current = secretKey + useEffect(() => { + if (secretKey) { + secretKeyRef.current = secretKey; } - }, [secretKey]) + }, [secretKey]); - // const getEncryptedSecretKey = useCallback(()=> { - // const response = getResource() - // const decryptResponse = decryptResource() - // return - // }, []) + // const getEncryptedSecretKey = useCallback(()=> { + // const response = getResource() + // const decryptResponse = decryptResource() + // return + // }, []) - - const checkForFirstSecretKeyNotification = (messages)=> { - messages?.forEach((message)=> { + const checkForFirstSecretKeyNotification = (messages) => { + messages?.forEach((message) => { try { - const decodeMsg = atob(message.data); - if(decodeMsg === PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY){ - handleSecretKeyCreationInProgress() - return + const decodeMsg = atob(message.data); + if (decodeMsg === PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY) { + handleSecretKeyCreationInProgress(); + return; } - } catch (error) { - - } - }) - } + } catch (error) {} + }); + }; - const updateChatMessagesWithBlocksFunc = (e) => { - if(e.detail){ - setMessages((prev)=> prev?.filter((item)=> { - return !isUserBlocked(item?.sender, item?.senderName) - })) - } - }; - - useEffect(() => { - subscribeToEvent("updateChatMessagesWithBlocks", updateChatMessagesWithBlocksFunc); - - return () => { - unsubscribeFromEvent("updateChatMessagesWithBlocks", updateChatMessagesWithBlocksFunc); - }; - }, []); + const updateChatMessagesWithBlocksFunc = (e) => { + if (e.detail) { + setMessages((prev) => + prev?.filter((item) => { + return !isUserBlocked(item?.sender, item?.senderName); + }) + ); + } + }; - const middletierFunc = async (data: any, groupId: string) => { + useEffect(() => { + subscribeToEvent( + 'updateChatMessagesWithBlocks', + updateChatMessagesWithBlocksFunc + ); + + return () => { + unsubscribeFromEvent( + 'updateChatMessagesWithBlocks', + updateChatMessagesWithBlocksFunc + ); + }; + }, []); + + const middletierFunc = async (data: any, groupId: string) => { try { if (hasInitialized.current) { - const dataRemovedBlock = data?.filter((item)=> !isUserBlocked(item?.sender, item?.senderName)) + const dataRemovedBlock = data?.filter( + (item) => !isUserBlocked(item?.sender, item?.senderName) + ); decryptMessages(dataRemovedBlock, true); return; @@ -186,351 +233,462 @@ const [messageSize, setMessageSize] = useState(0) hasInitialized.current = true; const url = `${getBaseApiReact()}/chat/messages?txGroupId=${groupId}&encoding=BASE64&limit=0&reverse=false`; const response = await fetch(url, { - method: "GET", + method: 'GET', headers: { - "Content-Type": "application/json", + 'Content-Type': 'application/json', }, }); const responseData = await response.json(); - const dataRemovedBlock = responseData?.filter((item)=> { - return !isUserBlocked(item?.sender, item?.senderName) - }) + const dataRemovedBlock = responseData?.filter((item) => { + return !isUserBlocked(item?.sender, item?.senderName); + }); decryptMessages(dataRemovedBlock, false); } catch (error) { console.error(error); } - } - - const decryptMessages = (encryptedMessages: any[], isInitiated: boolean )=> { - try { - if(!secretKeyRef.current){ - checkForFirstSecretKeyNotification(encryptedMessages) - } - return new Promise((res, rej)=> { - window.sendMessage("decryptSingle", { + }; + + const decryptMessages = (encryptedMessages: any[], isInitiated: boolean) => { + try { + if (!secretKeyRef.current) { + checkForFirstSecretKeyNotification(encryptedMessages); + } + return new Promise((res, rej) => { + window + .sendMessage('decryptSingle', { data: encryptedMessages, secretKeyObject: secretKey, }) - .then((response) => { - if (!response?.error) { - const filterUIMessages = encryptedMessages.filter((item) => !isExtMsg(item.data)); - const decodedUIMessages = decodeBase64ForUIChatMessages(filterUIMessages); - - const combineUIAndExtensionMsgsBefore = [...decodedUIMessages, ...response]; - const combineUIAndExtensionMsgs = processWithNewMessages( - combineUIAndExtensionMsgsBefore.map((item) => ({ - ...item, - ...(item?.decryptedData || {}), - })), - selectedGroup - ); - res(combineUIAndExtensionMsgs); - - if (isInitiated) { + .then((response) => { + if (!response?.error) { + const filterUIMessages = encryptedMessages.filter( + (item) => !isExtMsg(item.data) + ); + const decodedUIMessages = + decodeBase64ForUIChatMessages(filterUIMessages); - const formatted = combineUIAndExtensionMsgs - .filter((rawItem) => !rawItem?.chatReference) - .map((item) => { - const additionalFields = item?.data === 'NDAwMQ==' ? { - text: "

First group key created.

" - } : {} - return { - ...item, - id: item.signature, - text: item?.decryptedData?.message || "", - repliedTo: item?.repliedTo || item?.decryptedData?.repliedTo, - unread: item?.sender === myAddress ? false : !!item?.chatReference ? false : true, - isNotEncrypted: !!item?.messageText, - ...additionalFields - } - }); - setMessages((prev) => [...prev, ...formatted]); - - setChatReferences((prev) => { - const organizedChatReferences = { ...prev }; - combineUIAndExtensionMsgs - .filter((rawItem) => rawItem && rawItem.chatReference && (rawItem?.decryptedData?.type === "reaction" || rawItem?.decryptedData?.type === "edit" || rawItem?.type === "edit" || rawItem?.isEdited || rawItem?.type === "reaction")) - .forEach((item) => { - try { - if(item?.decryptedData?.type === "edit"){ - organizedChatReferences[item.chatReference] = { - ...(organizedChatReferences[item.chatReference] || {}), - edit: item.decryptedData, - }; - } else if(item?.type === "edit" || item?.isEdited){ - organizedChatReferences[item.chatReference] = { - ...(organizedChatReferences[item.chatReference] || {}), - edit: item, - }; - } else { - const content = item?.content || item.decryptedData?.content; - const sender = item.sender; - const newTimestamp = item.timestamp; - const contentState = item?.contentState !== undefined ? item?.contentState : item.decryptedData?.contentState; - - if (!content || typeof content !== "string" || !sender || typeof sender !== "string" || !newTimestamp) { - console.warn("Invalid content, sender, or timestamp in reaction data", item); - return; - } - - organizedChatReferences[item.chatReference] = { - ...(organizedChatReferences[item.chatReference] || {}), - reactions: organizedChatReferences[item.chatReference]?.reactions || {}, - }; - - organizedChatReferences[item.chatReference].reactions[content] = - organizedChatReferences[item.chatReference].reactions[content] || []; - - let latestTimestampForSender = null; - - organizedChatReferences[item.chatReference].reactions[content] = - organizedChatReferences[item.chatReference].reactions[content].filter((reaction) => { - if (reaction.sender === sender) { - latestTimestampForSender = Math.max(latestTimestampForSender || 0, reaction.timestamp); - } - return reaction.sender !== sender; - }); - - if (latestTimestampForSender && newTimestamp < latestTimestampForSender) { - return; - } - - if (contentState !== false) { - organizedChatReferences[item.chatReference].reactions[content].push(item); - } - - if (organizedChatReferences[item.chatReference].reactions[content].length === 0) { - delete organizedChatReferences[item.chatReference].reactions[content]; - } + const combineUIAndExtensionMsgsBefore = [ + ...decodedUIMessages, + ...response, + ]; + const combineUIAndExtensionMsgs = processWithNewMessages( + combineUIAndExtensionMsgsBefore.map((item) => ({ + ...item, + ...(item?.decryptedData || {}), + })), + selectedGroup + ); + res(combineUIAndExtensionMsgs); + + if (isInitiated) { + const formatted = combineUIAndExtensionMsgs + .filter((rawItem) => !rawItem?.chatReference) + .map((item) => { + const additionalFields = + item?.data === 'NDAwMQ==' + ? { + text: '

First group key created.

', } - - } catch (error) { - console.error("Error processing reaction/edit item:", error, item); - } - }); - - return organizedChatReferences; + : {}; + return { + ...item, + id: item.signature, + text: item?.decryptedData?.message || '', + repliedTo: + item?.repliedTo || item?.decryptedData?.repliedTo, + unread: + item?.sender === myAddress + ? false + : !!item?.chatReference + ? false + : true, + isNotEncrypted: !!item?.messageText, + ...additionalFields, + }; }); - } else { - let firstUnreadFound = false; - const formatted = combineUIAndExtensionMsgs - .filter((rawItem) => !rawItem?.chatReference) - .map((item) => { - const additionalFields = item?.data === 'NDAwMQ==' ? { - text: "

First group key created.

" - } : {} - const divide = lastReadTimestamp.current && !firstUnreadFound && item.timestamp > lastReadTimestamp.current && myAddress !== item?.sender; - - if(divide){ - firstUnreadFound = true - } - return { - ...item, - id: item.signature, - text: item?.decryptedData?.message || "", - repliedTo: item?.repliedTo || item?.decryptedData?.repliedTo, - isNotEncrypted: !!item?.messageText, - unread: false, - divide, - ...additionalFields - } - }); - setMessages(formatted); - - setChatReferences((prev) => { - const organizedChatReferences = { ...prev }; - - combineUIAndExtensionMsgs - .filter((rawItem) => rawItem && rawItem.chatReference && (rawItem?.decryptedData?.type === "reaction" || rawItem?.decryptedData?.type === "edit" || rawItem?.type === "edit" || rawItem?.isEdited || rawItem?.type === "reaction")) - .forEach((item) => { - try { - if(item?.decryptedData?.type === "edit"){ - organizedChatReferences[item.chatReference] = { - ...(organizedChatReferences[item.chatReference] || {}), - edit: item.decryptedData, - }; - } else if(item?.type === "edit" || item?.isEdited){ - organizedChatReferences[item.chatReference] = { - ...(organizedChatReferences[item.chatReference] || {}), - edit: item, - }; - } else { - const content = item?.content || item.decryptedData?.content; + setMessages((prev) => [...prev, ...formatted]); + + setChatReferences((prev) => { + const organizedChatReferences = { ...prev }; + combineUIAndExtensionMsgs + .filter( + (rawItem) => + rawItem && + rawItem.chatReference && + (rawItem?.decryptedData?.type === 'reaction' || + rawItem?.decryptedData?.type === 'edit' || + rawItem?.type === 'edit' || + rawItem?.isEdited || + rawItem?.type === 'reaction') + ) + .forEach((item) => { + try { + if (item?.decryptedData?.type === 'edit') { + organizedChatReferences[item.chatReference] = { + ...(organizedChatReferences[item.chatReference] || + {}), + edit: item.decryptedData, + }; + } else if (item?.type === 'edit' || item?.isEdited) { + organizedChatReferences[item.chatReference] = { + ...(organizedChatReferences[item.chatReference] || + {}), + edit: item, + }; + } else { + const content = + item?.content || item.decryptedData?.content; const sender = item.sender; const newTimestamp = item.timestamp; - const contentState = item?.contentState !== undefined ? item?.contentState : item.decryptedData?.contentState; - - if (!content || typeof content !== "string" || !sender || typeof sender !== "string" || !newTimestamp) { - console.warn("Invalid content, sender, or timestamp in reaction data", item); + const contentState = + item?.contentState !== undefined + ? item?.contentState + : item.decryptedData?.contentState; + + if ( + !content || + typeof content !== 'string' || + !sender || + typeof sender !== 'string' || + !newTimestamp + ) { + console.warn( + 'Invalid content, sender, or timestamp in reaction data', + item + ); return; } - + organizedChatReferences[item.chatReference] = { - ...(organizedChatReferences[item.chatReference] || {}), - reactions: organizedChatReferences[item.chatReference]?.reactions || {}, + ...(organizedChatReferences[item.chatReference] || + {}), + reactions: + organizedChatReferences[item.chatReference] + ?.reactions || {}, }; - - organizedChatReferences[item.chatReference].reactions[content] = - organizedChatReferences[item.chatReference].reactions[content] || []; - + + organizedChatReferences[item.chatReference].reactions[ + content + ] = + organizedChatReferences[item.chatReference] + .reactions[content] || []; + let latestTimestampForSender = null; - - organizedChatReferences[item.chatReference].reactions[content] = - organizedChatReferences[item.chatReference].reactions[content].filter((reaction) => { - if (reaction.sender === sender) { - latestTimestampForSender = Math.max(latestTimestampForSender || 0, reaction.timestamp); - } - return reaction.sender !== sender; - }); - - if (latestTimestampForSender && newTimestamp < latestTimestampForSender) { + + organizedChatReferences[item.chatReference].reactions[ + content + ] = organizedChatReferences[ + item.chatReference + ].reactions[content].filter((reaction) => { + if (reaction.sender === sender) { + latestTimestampForSender = Math.max( + latestTimestampForSender || 0, + reaction.timestamp + ); + } + return reaction.sender !== sender; + }); + + if ( + latestTimestampForSender && + newTimestamp < latestTimestampForSender + ) { return; } - + if (contentState !== false) { - organizedChatReferences[item.chatReference].reactions[content].push(item); + organizedChatReferences[ + item.chatReference + ].reactions[content].push(item); } - - if (organizedChatReferences[item.chatReference].reactions[content].length === 0) { - delete organizedChatReferences[item.chatReference].reactions[content]; + + if ( + organizedChatReferences[item.chatReference] + .reactions[content].length === 0 + ) { + delete organizedChatReferences[item.chatReference] + .reactions[content]; } } - } catch (error) { - console.error("Error processing reaction item:", error, item); - } - }); - - return organizedChatReferences; + } catch (error) { + console.error( + 'Error processing reaction/edit item:', + error, + item + ); + } + }); + + return organizedChatReferences; + }); + } else { + let firstUnreadFound = false; + const formatted = combineUIAndExtensionMsgs + .filter((rawItem) => !rawItem?.chatReference) + .map((item) => { + const additionalFields = + item?.data === 'NDAwMQ==' + ? { + text: '

First group key created.

', + } + : {}; + const divide = + lastReadTimestamp.current && + !firstUnreadFound && + item.timestamp > lastReadTimestamp.current && + myAddress !== item?.sender; + + if (divide) { + firstUnreadFound = true; + } + return { + ...item, + id: item.signature, + text: item?.decryptedData?.message || '', + repliedTo: + item?.repliedTo || item?.decryptedData?.repliedTo, + isNotEncrypted: !!item?.messageText, + unread: false, + divide, + ...additionalFields, + }; }); - } + setMessages(formatted); + + setChatReferences((prev) => { + const organizedChatReferences = { ...prev }; + + combineUIAndExtensionMsgs + .filter( + (rawItem) => + rawItem && + rawItem.chatReference && + (rawItem?.decryptedData?.type === 'reaction' || + rawItem?.decryptedData?.type === 'edit' || + rawItem?.type === 'edit' || + rawItem?.isEdited || + rawItem?.type === 'reaction') + ) + .forEach((item) => { + try { + if (item?.decryptedData?.type === 'edit') { + organizedChatReferences[item.chatReference] = { + ...(organizedChatReferences[item.chatReference] || + {}), + edit: item.decryptedData, + }; + } else if (item?.type === 'edit' || item?.isEdited) { + organizedChatReferences[item.chatReference] = { + ...(organizedChatReferences[item.chatReference] || + {}), + edit: item, + }; + } else { + const content = + item?.content || item.decryptedData?.content; + const sender = item.sender; + const newTimestamp = item.timestamp; + const contentState = + item?.contentState !== undefined + ? item?.contentState + : item.decryptedData?.contentState; + + if ( + !content || + typeof content !== 'string' || + !sender || + typeof sender !== 'string' || + !newTimestamp + ) { + console.warn( + 'Invalid content, sender, or timestamp in reaction data', + item + ); + return; + } + + organizedChatReferences[item.chatReference] = { + ...(organizedChatReferences[item.chatReference] || + {}), + reactions: + organizedChatReferences[item.chatReference] + ?.reactions || {}, + }; + + organizedChatReferences[item.chatReference].reactions[ + content + ] = + organizedChatReferences[item.chatReference] + .reactions[content] || []; + + let latestTimestampForSender = null; + + organizedChatReferences[item.chatReference].reactions[ + content + ] = organizedChatReferences[ + item.chatReference + ].reactions[content].filter((reaction) => { + if (reaction.sender === sender) { + latestTimestampForSender = Math.max( + latestTimestampForSender || 0, + reaction.timestamp + ); + } + return reaction.sender !== sender; + }); + + if ( + latestTimestampForSender && + newTimestamp < latestTimestampForSender + ) { + return; + } + + if (contentState !== false) { + organizedChatReferences[ + item.chatReference + ].reactions[content].push(item); + } + + if ( + organizedChatReferences[item.chatReference] + .reactions[content].length === 0 + ) { + delete organizedChatReferences[item.chatReference] + .reactions[content]; + } + } + } catch (error) { + console.error( + 'Error processing reaction item:', + error, + item + ); + } + }); + + return organizedChatReferences; + }); } - rej(response.error); - }) - .catch((error) => { - rej(error.message || "An error occurred"); - }); - - }) - } catch (error) { - - } + } + rej(response.error); + }) + .catch((error) => { + rej(error.message || 'An error occurred'); + }); + }); + } catch (error) {} + }; + + const forceCloseWebSocket = () => { + if (socketRef.current) { + clearTimeout(timeoutIdRef.current); + clearTimeout(groupSocketTimeoutRef.current); + socketRef.current.close(1000, 'forced'); + socketRef.current = null; } + }; - + const pingGroupSocket = () => { + try { + if (socketRef.current?.readyState === WebSocket.OPEN) { + socketRef.current.send('ping'); + timeoutIdRef.current = setTimeout(() => { + if (socketRef.current) { + socketRef.current.close(); + clearTimeout(groupSocketTimeoutRef.current); + } + }, 5000); // Close if no pong in 5 seconds + } + } catch (error) { + console.error('Error during ping:', error); + } + }; + const initWebsocketMessageGroup = () => { + let socketLink = `${getBaseApiReactSocket()}/websockets/chat/messages?txGroupId=${selectedGroup}&encoding=BASE64&limit=100`; + socketRef.current = new WebSocket(socketLink); - const forceCloseWebSocket = () => { - if (socketRef.current) { - - clearTimeout(timeoutIdRef.current); - clearTimeout(groupSocketTimeoutRef.current); - socketRef.current.close(1000, 'forced'); - socketRef.current = null; + socketRef.current.onopen = () => { + setTimeout(pingGroupSocket, 50); + }; + socketRef.current.onmessage = (e) => { + try { + if (e.data === 'pong') { + clearTimeout(timeoutIdRef.current); + groupSocketTimeoutRef.current = setTimeout(pingGroupSocket, 45000); // Ping every 45 seconds + } else { + middletierFunc(JSON.parse(e.data), selectedGroup); + setIsLoading(false); + } + } catch (error) {} + }; + socketRef.current.onclose = () => { + clearTimeout(groupSocketTimeoutRef.current); + clearTimeout(timeoutIdRef.current); + console.warn(`WebSocket closed: ${event.reason || 'unknown reason'}`); + if (event.reason !== 'forced' && event.code !== 1000) { + setTimeout(() => initWebsocketMessageGroup(), 1000); // Retry after 10 seconds } }; - - const pingGroupSocket = () => { - try { - if (socketRef.current?.readyState === WebSocket.OPEN) { - socketRef.current.send('ping'); - timeoutIdRef.current = setTimeout(() => { - if (socketRef.current) { - socketRef.current.close(); - clearTimeout(groupSocketTimeoutRef.current); - } - }, 5000); // Close if no pong in 5 seconds - } - } catch (error) { - console.error('Error during ping:', error); + socketRef.current.onerror = (e) => { + clearTimeout(groupSocketTimeoutRef.current); + clearTimeout(timeoutIdRef.current); + if (socketRef.current) { + socketRef.current.close(); + } + }; + }; + + useEffect(() => { + if (hasInitializedWebsocket.current) return; + if (triedToFetchSecretKey && !secretKey) { + forceCloseWebSocket(); + setMessages([]); + setIsLoading(true); + initWebsocketMessageGroup(); } - } - const initWebsocketMessageGroup = () => { - + }, [triedToFetchSecretKey, secretKey, isPrivate]); - let socketLink = `${getBaseApiReactSocket()}/websockets/chat/messages?txGroupId=${selectedGroup}&encoding=BASE64&limit=100` - socketRef.current = new WebSocket(socketLink) + useEffect(() => { + if (isPrivate === null) return; + if (isPrivate === false || !secretKey || hasInitializedWebsocket.current) + return; + forceCloseWebSocket(); + setMessages([]); + setIsLoading(true); + pauseAllQueues(); + setTimeout(() => { + resumeAllQueues(); + }, 6000); + initWebsocketMessageGroup(); + hasInitializedWebsocket.current = true; + }, [secretKey, isPrivate]); - - socketRef.current.onopen = () => { - setTimeout(pingGroupSocket, 50) - } - socketRef.current.onmessage = (e) => { - try { - if (e.data === 'pong') { - clearTimeout(timeoutIdRef.current); - groupSocketTimeoutRef.current = setTimeout(pingGroupSocket, 45000); // Ping every 45 seconds - } else { - middletierFunc(JSON.parse(e.data), selectedGroup) - setIsLoading(false) - } - } catch (error) { - - } - - - } - socketRef.current.onclose = () => { - clearTimeout(groupSocketTimeoutRef.current); - clearTimeout(timeoutIdRef.current); - console.warn(`WebSocket closed: ${event.reason || 'unknown reason'}`); - if (event.reason !== 'forced' && event.code !== 1000) { - setTimeout(() => initWebsocketMessageGroup(), 1000); // Retry after 10 seconds - } - } - socketRef.current.onerror = (e) => { - clearTimeout(groupSocketTimeoutRef.current); - clearTimeout(timeoutIdRef.current); - if (socketRef.current) { - socketRef.current.close(); - } - } - } + useEffect(() => { + const notifications = messages.filter( + (message) => message?.decryptedData?.type === 'notification' + ); + if (notifications.length === 0) return; + const latestNotification = notifications.reduce((latest, current) => { + return current.timestamp > latest.timestamp ? current : latest; + }, notifications[0]); + handleNewEncryptionNotification(latestNotification); + }, [messages]); - useEffect(()=> { - if(hasInitializedWebsocket.current) return - if(triedToFetchSecretKey && !secretKey){ - forceCloseWebSocket() - setMessages([]) - setIsLoading(true) - initWebsocketMessageGroup() - } - }, [triedToFetchSecretKey, secretKey, isPrivate]) - - useEffect(()=> { - if(isPrivate === null) return - if(isPrivate === false || !secretKey || hasInitializedWebsocket.current) return - forceCloseWebSocket() - setMessages([]) - setIsLoading(true) - pauseAllQueues() - setTimeout(() => { - resumeAllQueues() - }, 6000); - initWebsocketMessageGroup() - hasInitializedWebsocket.current = true - }, [secretKey, isPrivate]) - - - useEffect(()=> { - const notifications = messages.filter((message)=> message?.decryptedData - ?.type === 'notification') - if(notifications.length === 0) return - const latestNotification = notifications.reduce((latest, current) => { - return current.timestamp > latest.timestamp ? current : latest; - }, notifications[0]); - handleNewEncryptionNotification(latestNotification) - - }, [messages]) - - - const encryptChatMessage = async (data: string, secretKeyObject: any, reactiontypeNumber?: number)=> { + const encryptChatMessage = async ( + data: string, + secretKeyObject: any, + reactiontypeNumber?: number + ) => { try { - return new Promise((res, rej)=> { - window.sendMessage("encryptSingle", { - data, - secretKeyObject, - typeNumber: reactiontypeNumber, - }) + return new Promise((res, rej) => { + window + .sendMessage('encryptSingle', { + data, + secretKeyObject, + typeNumber: reactiontypeNumber, + }) .then((response) => { if (!response?.error) { res(response); @@ -539,172 +697,189 @@ const [messageSize, setMessageSize] = useState(0) rej(response.error); }) .catch((error) => { - rej(error.message || "An error occurred"); + rej(error.message || 'An error occurred'); }); - - }) + }); + } catch (error) {} + }; + + const sendChatGroup = async ({ + groupId, + typeMessage = undefined, + chatReference = undefined, + messageText, + }: any) => { + try { + return new Promise((res, rej) => { + window + .sendMessage( + 'sendChatGroup', + { + groupId, + typeMessage, + chatReference, + messageText, + }, + 120000 + ) + .then((response) => { + if (!response?.error) { + res(response); + return; + } + rej(response.error); + }) + .catch((error) => { + rej(error.message || 'An error occurred'); + }); + }); } catch (error) { - + throw new Error(error); } -} - -const sendChatGroup = async ({groupId, typeMessage = undefined, chatReference = undefined, messageText}: any)=> { - try { - return new Promise((res, rej)=> { - window.sendMessage("sendChatGroup", { - groupId, - typeMessage, - chatReference, - messageText, - }, 120000) - .then((response) => { - if (!response?.error) { - res(response); - return; - } - rej(response.error); - }) - .catch((error) => { - rej(error.message || "An error occurred"); - }); - - }) - } catch (error) { - throw new Error(error) - } -} -const clearEditorContent = () => { - if (editorRef.current) { - setMessageSize(0) - editorRef.current.chain().focus().clearContent().run(); - if(isMobile){ - setTimeout(() => { - editorRef.current?.chain().blur().run(); - setIsFocusedParent(false) - executeEvent("sent-new-message-group", {}) + }; + const clearEditorContent = () => { + if (editorRef.current) { + setMessageSize(0); + editorRef.current.chain().focus().clearContent().run(); + if (isMobile) { setTimeout(() => { - triggerRerender(); - }, 300); - }, 200); + editorRef.current?.chain().blur().run(); + setIsFocusedParent(false); + executeEvent('sent-new-message-group', {}); + setTimeout(() => { + triggerRerender(); + }, 300); + }, 200); + } } - } -}; + }; + const sendMessage = async () => { + try { + if (messageSize > 4000) return; + if (isPrivate === null) + throw new Error('Unable to determine if group is private'); + if (isSending) return; + if (+balance < 4) + throw new Error('You need at least 4 QORT to send a message'); + pauseAllQueues(); + if (editorRef.current) { + const htmlContent = editorRef.current.getHTML(); - const sendMessage = async ()=> { - try { - if(messageSize > 4000) return - if(isPrivate === null) throw new Error('Unable to determine if group is private') - if(isSending) return - if(+balance < 4) throw new Error('You need at least 4 QORT to send a message') - pauseAllQueues() - if (editorRef.current) { - const htmlContent = editorRef.current.getHTML(); - - if(!htmlContent?.trim() || htmlContent?.trim() === '

') return - + if (!htmlContent?.trim() || htmlContent?.trim() === '

') return; - setIsSending(true) - const message = isPrivate === false ? editorRef.current.getJSON() : htmlContent - const secretKeyObject = await getSecretKey(false, true) + setIsSending(true); + const message = + isPrivate === false ? editorRef.current.getJSON() : htmlContent; + const secretKeyObject = await getSecretKey(false, true); - let repliedTo = replyMessage?.signature + let repliedTo = replyMessage?.signature; - if (replyMessage?.chatReference) { - repliedTo = replyMessage?.chatReference - } - let chatReference = onEditMessage?.signature - - const publicData = isPrivate ? {} : { - isEdited : chatReference ? true : false, + if (replyMessage?.chatReference) { + repliedTo = replyMessage?.chatReference; } + let chatReference = onEditMessage?.signature; + + const publicData = isPrivate + ? {} + : { + isEdited: chatReference ? true : false, + }; const otherData = { repliedTo, ...(onEditMessage?.decryptedData || {}), type: chatReference ? 'edit' : '', specialId: uid.rnd(), - ...publicData - } + ...publicData, + }; const objectMessage = { ...(otherData || {}), [isPrivate ? 'message' : 'messageText']: message, - version: 3 - } - const message64: any = await objectToBase64(objectMessage) - - const encryptSingle = isPrivate === false ? JSON.stringify(objectMessage) : await encryptChatMessage(message64, secretKeyObject) - // const res = await sendChatGroup({groupId: selectedGroup,messageText: encryptSingle}) - - const sendMessageFunc = async () => { - return await sendChatGroup({groupId: selectedGroup,messageText: encryptSingle, chatReference}) + version: 3, }; - + const message64: any = await objectToBase64(objectMessage); + + const encryptSingle = + isPrivate === false + ? JSON.stringify(objectMessage) + : await encryptChatMessage(message64, secretKeyObject); + // const res = await sendChatGroup({groupId: selectedGroup,messageText: encryptSingle}) + + const sendMessageFunc = async () => { + return await sendChatGroup({ + groupId: selectedGroup, + messageText: encryptSingle, + chatReference, + }); + }; + // Add the function to the queue const messageObj = { message: { text: htmlContent, timestamp: Date.now(), - senderName: myName, - sender: myAddress, - ...(otherData || {}) + senderName: myName, + sender: myAddress, + ...(otherData || {}), }, - chatReference - } - addToQueue(sendMessageFunc, messageObj, 'chat', - selectedGroup ); + chatReference, + }; + addToQueue(sendMessageFunc, messageObj, 'chat', selectedGroup); setTimeout(() => { - executeEvent("sent-new-message-group", {}) + executeEvent('sent-new-message-group', {}); }, 150); - clearEditorContent() - setReplyMessage(null) - setOnEditMessage(null) - } - // send chat message - } catch (error) { - const errorMsg = error?.message || error - setInfoSnack({ - type: "error", - message: errorMsg, - }); - setOpenSnack(true); - console.error(error) - } finally { - setIsSending(false) - resumeAllQueues() + clearEditorContent(); + setReplyMessage(null); + setOnEditMessage(null); } + // send chat message + } catch (error) { + const errorMsg = error?.message || error; + setInfoSnack({ + type: 'error', + message: errorMsg, + }); + setOpenSnack(true); + console.error(error); + } finally { + setIsSending(false); + resumeAllQueues(); } + }; - useEffect(() => { - if (!editorRef?.current) return; - - handleUpdateRef.current = throttle(async () => { - try { - if(isPrivate){ + useEffect(() => { + if (!editorRef?.current) return; + + handleUpdateRef.current = throttle(async () => { + try { + if (isPrivate) { const htmlContent = editorRef.current.getHTML(); - const message64 = await objectToBase64(JSON.stringify(htmlContent)) - const secretKeyObject = await getSecretKey(false, true) - const encryptSingle = await encryptChatMessage(message64, secretKeyObject) + const message64 = await objectToBase64(JSON.stringify(htmlContent)); + const secretKeyObject = await getSecretKey(false, true); + const encryptSingle = await encryptChatMessage( + message64, + secretKeyObject + ); setMessageSize((encryptSingle?.length || 0) + 200); } else { const htmlContent = editorRef.current.getJSON(); - const message = JSON.stringify(htmlContent) - const size = new Blob([message]).size + const message = JSON.stringify(htmlContent); + const size = new Blob([message]).size; setMessageSize(size + 300); } - - } catch (error) { + } catch (error) { // calc size error - } - }, 1200); - - const currentEditor = editorRef.current; - - currentEditor.on("update", handleUpdateRef.current); - - return () => { - currentEditor.off("update", handleUpdateRef.current); - }; - }, [editorRef, setMessageSize, isPrivate]); + } + }, 1200); + + const currentEditor = editorRef.current; + + currentEditor.on('update', handleUpdateRef.current); + + return () => { + currentEditor.off('update', handleUpdateRef.current); + }; + }, [editorRef, setMessageSize, isPrivate]); useEffect(() => { if (hide) { @@ -713,210 +888,260 @@ const clearEditorContent = () => { setIsMoved(false); // Reset the position immediately when showing } }, [hide]); - - const onReply = useCallback((message)=> { - if(onEditMessage){ - clearEditorContent() - } - setReplyMessage(message) - setOnEditMessage(null) - editorRef?.current?.chain().focus() - }, [onEditMessage]) - - const onEdit = useCallback((message)=> { - setOnEditMessage(message) - setReplyMessage(null) - editorRef.current.chain().focus().setContent(message?.messageText || message?.text).run(); - - }, []) - const handleReaction = useCallback(async (reaction, chatMessage, reactionState = true)=> { - try { - - if(isSending) return - if(+balance < 4) throw new Error('You need at least 4 QORT to send a message') - pauseAllQueues() - - - setIsSending(true) - const message = '' - const secretKeyObject = await getSecretKey(false, true) - - - const otherData = { - specialId: uid.rnd(), - type: 'reaction', - content: reaction, - contentState: reactionState + const onReply = useCallback( + (message) => { + if (onEditMessage) { + clearEditorContent(); } - const objectMessage = { - message, - ...(otherData || {}) + setReplyMessage(message); + setOnEditMessage(null); + editorRef?.current?.chain().focus(); + }, + [onEditMessage] + ); + + const onEdit = useCallback((message) => { + setOnEditMessage(message); + setReplyMessage(null); + editorRef.current + .chain() + .focus() + .setContent(message?.messageText || message?.text) + .run(); + }, []); + const handleReaction = useCallback( + async (reaction, chatMessage, reactionState = true) => { + try { + if (isSending) return; + if (+balance < 4) + throw new Error('You need at least 4 QORT to send a message'); + pauseAllQueues(); + + setIsSending(true); + const message = ''; + const secretKeyObject = await getSecretKey(false, true); + + const otherData = { + specialId: uid.rnd(), + type: 'reaction', + content: reaction, + contentState: reactionState, + }; + const objectMessage = { + message, + ...(otherData || {}), + }; + const message64: any = await objectToBase64(objectMessage); + const reactiontypeNumber = RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS; + const encryptSingle = + isPrivate === false + ? JSON.stringify(objectMessage) + : await encryptChatMessage( + message64, + secretKeyObject, + reactiontypeNumber + ); + // const res = await sendChatGroup({groupId: selectedGroup,messageText: encryptSingle}) + + const sendMessageFunc = async () => { + return await sendChatGroup({ + groupId: selectedGroup, + messageText: encryptSingle, + chatReference: chatMessage.signature, + }); + }; + + // Add the function to the queue + const messageObj = { + message: { + text: message, + timestamp: Date.now(), + senderName: myName, + sender: myAddress, + ...(otherData || {}), + }, + chatReference: chatMessage.signature, + }; + addToQueue(sendMessageFunc, messageObj, 'chat-reaction', selectedGroup); + // setTimeout(() => { + // executeEvent("sent-new-message-group", {}) + // }, 150); + // clearEditorContent() + // setReplyMessage(null) + + // send chat message + } catch (error) { + const errorMsg = error?.message || error; + setInfoSnack({ + type: 'error', + message: errorMsg, + }); + setOpenSnack(true); + console.error(error); + } finally { + setIsSending(false); + resumeAllQueues(); } - const message64: any = await objectToBase64(objectMessage) - const reactiontypeNumber = RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS - const encryptSingle = isPrivate === false ? JSON.stringify(objectMessage) : await encryptChatMessage(message64, secretKeyObject, reactiontypeNumber) - // const res = await sendChatGroup({groupId: selectedGroup,messageText: encryptSingle}) - - const sendMessageFunc = async () => { - return await sendChatGroup({groupId: selectedGroup,messageText: encryptSingle, chatReference: chatMessage.signature}) - }; + }, + [isPrivate] + ); - // Add the function to the queue - const messageObj = { - message: { - text: message, - timestamp: Date.now(), - senderName: myName, - sender: myAddress, - ...(otherData || {}) - }, - chatReference: chatMessage.signature - } - addToQueue(sendMessageFunc, messageObj, 'chat-reaction', - selectedGroup ); - // setTimeout(() => { - // executeEvent("sent-new-message-group", {}) - // }, 150); - // clearEditorContent() - // setReplyMessage(null) + const openQManager = useCallback(() => { + setIsOpenQManager(true); + }, []); - // send chat message - } catch (error) { - const errorMsg = error?.message || error - setInfoSnack({ - type: "error", - message: errorMsg, - }); - setOpenSnack(true); - console.error(error) - } finally { - setIsSending(false) - resumeAllQueues() - } - }, [isPrivate]) - - const openQManager = useCallback(()=> { - setIsOpenQManager(true) - }, []) - return ( -
- - - - {(!!secretKey || isPrivate === false) && ( -
- -
+ + + {(!!secretKey || isPrivate === false) && ( +
- {replyMessage && ( - - + }} + > +
+ {replyMessage && ( + + - { - setReplyMessage(null) - - setOnEditMessage(null) + { + setReplyMessage(null); - }} - > - - - - )} - {onEditMessage && ( - - + setOnEditMessage(null); + }} + > + + + + )} + {onEditMessage && ( + + - { - setReplyMessage(null) - setOnEditMessage(null) - - clearEditorContent() - - }} - > - - - - )} - - - - {messageSize > 750 && ( - - 4000 ? 'var(--danger)' : 'unset' - }}>{`Your message size is of ${messageSize} bytes out of a maximum of 4000`} + { + setReplyMessage(null); + setOnEditMessage(null); - - )} -
- - + clearEditorContent(); + }} + > + + + + )} - { - - if(isSending) return - sendMessage() + + {messageSize > 750 && ( + + 4000 ? 'var(--danger)' : 'unset', + }} + >{`Your message size is of ${messageSize} bytes out of a maximum of 4000`} + + )} +
+ + + { + if (isSending) return; + sendMessage(); }} style={{ marginTop: 'auto', @@ -926,85 +1151,108 @@ const clearEditorContent = () => { flexShrink: 0, padding: '5px', width: '100px', - minWidth: 'auto' - + minWidth: 'auto', }} > {isSending && ( + size={18} + sx={{ + position: 'absolute', + top: '50%', + left: '50%', + marginTop: '-12px', + marginLeft: '-12px', + color: 'white', + }} + /> )} {` Send`} - - -
- )} - {isOpenQManager !== null && ( - - - - Q-Manager - { - setIsOpenQManager(false) - }}> - - - - - + +
)} - - {/* */} - - + {isOpenQManager !== null && ( + + + + Q-Manager + { + setIsOpenQManager(false); + }} + > + + + + + + + + )} + + {/* */} + +
- ) -} + ); +}; diff --git a/src/components/Chat/GroupAnnouncements.tsx b/src/components/Chat/GroupAnnouncements.tsx index 9ba7668..98dd48b 100644 --- a/src/components/Chat/GroupAnnouncements.tsx +++ b/src/components/Chat/GroupAnnouncements.tsx @@ -4,30 +4,33 @@ import React, { useMemo, useRef, useState, -} from "react"; -import { CreateCommonSecret } from "./CreateCommonSecret"; -import { reusableGet } from "../../qdn/publish/pubish"; -import { uint8ArrayToObject } from "../../backgroundFunctions/encryption"; +} 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 "../../App-styles"; -import CircularProgress from "@mui/material/CircularProgress"; -import { getBaseApi, getFee } from "../../background"; -import { LoadingSnackbar } from "../Snackbar/LoadingSnackbar"; -import { Box, Typography } from "@mui/material"; -import { Spacer } from "../../common/Spacer"; -import ShortUniqueId from "short-unique-id"; -import { AnnouncementList } from "./AnnouncementList"; +} 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 CircularProgress from '@mui/material/CircularProgress'; +import { getBaseApi, getFee } from '../../background'; +import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar'; +import { Box, Typography } 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 CampaignIcon from '@mui/icons-material/Campaign'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import { AnnouncementDiscussion } from './AnnouncementDiscussion'; import { MyContext, getArbitraryEndpointReact, @@ -35,11 +38,11 @@ import { isMobile, pauseAllQueues, resumeAllQueues, -} from "../../App"; -import { RequestQueueWithPromise } from "../../utils/queue/queue"; -import { CustomizedSnackbars } from "../Snackbar/Snackbar"; -import { addDataPublishesFunc, getDataPublishesFunc } from "../Group/Group"; -import { getRootHeight } from "../../utils/mobile/mobileUtils"; +} from '../../App'; +import { RequestQueueWithPromise } from '../../utils/queue/queue'; +import { CustomizedSnackbars } from '../Snackbar/Snackbar'; +import { addDataPublishesFunc, getDataPublishesFunc } from '../Group/Group'; +import { getRootHeight } from '../../utils/mobile/mobileUtils'; export const requestQueueCommentCount = new RequestQueueWithPromise(3); export const requestQueuePublishedAccouncements = new RequestQueueWithPromise( @@ -48,10 +51,11 @@ export const requestQueuePublishedAccouncements = new RequestQueueWithPromise( export const saveTempPublish = async ({ data, key }: any) => { return new Promise((res, rej) => { - window.sendMessage("saveTempPublish", { - data, - key, - }) + window + .sendMessage('saveTempPublish', { + data, + key, + }) .then((response) => { if (!response?.error) { res(response); @@ -60,37 +64,37 @@ export const saveTempPublish = async ({ data, key }: any) => { rej(response.error); }) .catch((error) => { - rej(error.message || "An error occurred"); + rej(error.message || 'An error occurred'); }); - }); }; export const getTempPublish = async () => { return new Promise((res, rej) => { - window.sendMessage("getTempPublish", {}) - .then((response) => { - if (!response?.error) { - res(response); - return; - } - rej(response.error); - }) - .catch((error) => { - rej(error.message || "An error occurred"); - }); - + window + .sendMessage('getTempPublish', {}) + .then((response) => { + if (!response?.error) { + res(response); + return; + } + rej(response.error); + }) + .catch((error) => { + rej(error.message || 'An error occurred'); + }); }); }; export const decryptPublishes = async (encryptedMessages: any[], secretKey) => { try { return await new Promise((res, rej) => { - window.sendMessage("decryptSingleForPublishes", { - data: encryptedMessages, - secretKeyObject: secretKey, - skipDecodeBase64: true, - }) + window + .sendMessage('decryptSingleForPublishes', { + data: encryptedMessages, + secretKeyObject: secretKey, + skipDecodeBase64: true, + }) .then((response) => { if (!response?.error) { res(response); @@ -99,26 +103,23 @@ export const decryptPublishes = async (encryptedMessages: any[], secretKey) => { rej(response.error); }) .catch((error) => { - rej(error.message || "An error occurred"); + rej(error.message || 'An error occurred'); }); - }); } catch (error) {} }; -export const handleUnencryptedPublishes = (publishes) => { - let publishesData = [] - publishes.forEach((pub)=> { +export const handleUnencryptedPublishes = (publishes) => { + let publishesData = []; + publishes.forEach((pub) => { try { const decryptToUnit8Array = base64ToUint8Array(pub); const decodedData = uint8ArrayToObject(decryptToUnit8Array); - if(decodedData){ - publishesData.push({decryptedData: decodedData}) + if (decodedData) { + publishesData.push({ decryptedData: decodedData }); } - } catch (error) { - - } - }) - return publishesData + } catch (error) {} + }); + return publishesData; }; export const GroupAnnouncements = ({ selectedGroup, @@ -130,7 +131,7 @@ export const GroupAnnouncements = ({ isAdmin, hide, myName, - isPrivate + isPrivate, }) => { const [messages, setMessages] = useState([]); const [isSending, setIsSending] = useState(false); @@ -159,12 +160,15 @@ export const GroupAnnouncements = ({ useEffect(() => { if (!selectedGroup) return; (async () => { - const res = await getDataPublishesFunc(selectedGroup, "anc"); + const res = await getDataPublishesFunc(selectedGroup, 'anc'); dataPublishes.current = res || {}; })(); }, [selectedGroup]); - const getAnnouncementData = async ({ identifier, name, resource }, isPrivate) => { + const getAnnouncementData = async ( + { identifier, name, resource }, + isPrivate + ) => { try { let data = dataPublishes.current[`${name}-${identifier}`]; if ( @@ -179,14 +183,17 @@ export const GroupAnnouncements = ({ }); if (!res?.ok) return; data = await res.text(); - await addDataPublishesFunc({ ...resource, data }, selectedGroup, "anc"); + await addDataPublishesFunc({ ...resource, data }, selectedGroup, 'anc'); } else { 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]; - if(!messageData) return + if (!messageData) return; setAnnouncementData((prev) => { return { ...prev, @@ -194,12 +201,17 @@ export const GroupAnnouncements = ({ }; }); } catch (error) { - console.error("error", error); + console.error('error', error); } }; useEffect(() => { - if ((!secretKey && isPrivate) || hasInitializedWebsocket.current || isPrivate === null) return; + if ( + (!secretKey && isPrivate) || + hasInitializedWebsocket.current || + isPrivate === null + ) + return; setIsLoading(true); // initWebsocketMessageGroup() hasInitializedWebsocket.current = true; @@ -208,10 +220,11 @@ export const GroupAnnouncements = ({ const encryptChatMessage = 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); @@ -220,19 +233,19 @@ export const GroupAnnouncements = ({ rej(response.error); }) .catch((error) => { - rej(error.message || "An error occurred"); + rej(error.message || 'An error occurred'); }); - }); } catch (error) {} }; const publishAnc = async ({ encryptedData, identifier }: any) => { return new Promise((res, rej) => { - window.sendMessage("publishGroupEncryptedResource", { - encryptedData, - identifier, - }) + window + .sendMessage('publishGroupEncryptedResource', { + encryptedData, + identifier, + }) .then((response) => { if (!response?.error) { res(response); @@ -241,9 +254,8 @@ export const GroupAnnouncements = ({ rej(response.error); }) .catch((error) => { - rej(error.message || "An error occurred"); + rej(error.message || 'An error occurred'); }); - }); }; const clearEditorContent = () => { @@ -255,7 +267,7 @@ export const GroupAnnouncements = ({ setIsFocusedParent(false); setTimeout(() => { triggerRerender(); - }, 300); + }, 300); }, 200); } } @@ -266,10 +278,12 @@ export const GroupAnnouncements = ({ const getTempAnnouncements = await getTempPublish(); if (getTempAnnouncements?.announcement) { let tempData = []; - Object.keys(getTempAnnouncements?.announcement || {}).filter((annKey)=> annKey?.startsWith(`grp-${selectedGroup}-anc`)).map((key) => { - const value = getTempAnnouncements?.announcement[key]; - tempData.push(value.data); - }); + Object.keys(getTempAnnouncements?.announcement || {}) + .filter((annKey) => annKey?.startsWith(`grp-${selectedGroup}-anc`)) + .map((key) => { + const value = getTempAnnouncements?.announcement[key]; + tempData.push(value.data); + }); setTempPublishedList(tempData); } } catch (error) {} @@ -278,27 +292,28 @@ export const GroupAnnouncements = ({ const publishAnnouncement = async () => { try { pauseAllQueues(); - const fee = await getFee("ARBITRARY"); + const fee = await getFee('ARBITRARY'); await show({ - message: "Would you like to perform a ARBITRARY transaction?", - publishFee: fee.fee + " QORT", + message: 'Would you like to perform a ARBITRARY transaction?', + publishFee: fee.fee + ' QORT', }); if (isSending) return; if (editorRef.current) { const htmlContent = editorRef.current.getHTML(); - if (!htmlContent?.trim() || htmlContent?.trim() === "

") return; + if (!htmlContent?.trim() || htmlContent?.trim() === '

') return; setIsSending(true); const message = { version: 1, extra: {}, message: htmlContent, }; - const secretKeyObject = isPrivate === false ? null : await getSecretKey(false, true); - const message64: any = await objectToBase64(message); - const encryptSingle = isPrivate === false ? message64 : await encryptChatMessage( - message64, - secretKeyObject - ); + const secretKeyObject = + isPrivate === false ? null : await getSecretKey(false, true); + const message64: any = await objectToBase64(message); + const encryptSingle = + isPrivate === false + ? message64 + : await encryptChatMessage(message64, secretKeyObject); const randomUid = uid.rnd(); const identifier = `grp-${selectedGroup}-anc-${randomUid}`; const res = await publishAnc({ @@ -309,13 +324,13 @@ export const GroupAnnouncements = ({ const dataToSaveToStorage = { name: myName, identifier, - service: "DOCUMENT", + service: 'DOCUMENT', tempData: message, created: Date.now(), }; await saveTempPublish({ data: dataToSaveToStorage, - key: "announcement", + key: 'announcement', }); setTempData(selectedGroup); clearEditorContent(); @@ -324,7 +339,7 @@ export const GroupAnnouncements = ({ } catch (error) { if (!error) return; setInfoSnack({ - type: "error", + type: 'error', message: error, }); setOpenSnack(true); @@ -343,9 +358,9 @@ export const GroupAnnouncements = ({ const identifier = `grp-${selectedGroup}-anc-`; const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=20&includemetadata=false&offset=${offset}&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(); @@ -354,11 +369,14 @@ export const GroupAnnouncements = ({ setAnnouncements(responseData); setIsLoading(false); for (const data of responseData) { - getAnnouncementData({ - name: data.name, - identifier: data.identifier, - resource: data, - }, isPrivate); + getAnnouncementData( + { + name: data.name, + identifier: data.identifier, + resource: data, + }, + isPrivate + ); } } catch (error) { } finally { @@ -369,8 +387,13 @@ export const GroupAnnouncements = ({ ); React.useEffect(() => { - if(!secretKey && isPrivate) return - if (selectedGroup && !hasInitialized.current && !hide && isPrivate !== null) { + if (!secretKey && isPrivate) return; + if ( + selectedGroup && + !hasInitialized.current && + !hide && + isPrivate !== null + ) { getAnnouncements(selectedGroup, isPrivate); hasInitialized.current = true; } @@ -384,9 +407,9 @@ export const GroupAnnouncements = ({ const identifier = `grp-${selectedGroup}-anc-`; const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=20&includemetadata=false&offset=${offset}&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(); @@ -394,7 +417,10 @@ export const GroupAnnouncements = ({ setAnnouncements((prev) => [...prev, ...responseData]); setIsLoading(false); for (const data of responseData) { - getAnnouncementData({ name: data.name, identifier: data.identifier }, isPrivate); + getAnnouncementData( + { name: data.name, identifier: data.identifier }, + isPrivate + ); } } catch (error) {} }; @@ -406,9 +432,9 @@ export const GroupAnnouncements = ({ const identifier = `grp-${selectedGroup}-anc-`; const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=20&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(); @@ -416,10 +442,13 @@ export const GroupAnnouncements = ({ if (!latestMessage) { for (const data of responseData) { try { - getAnnouncementData({ - name: data.name, - identifier: data.identifier, - }, isPrivate); + getAnnouncementData( + { + name: data.name, + identifier: data.identifier, + }, + isPrivate + ); } catch (error) {} } setAnnouncements(responseData); @@ -434,7 +463,10 @@ export const GroupAnnouncements = ({ for (const data of newArray) { try { - getAnnouncementData({ name: data.name, identifier: data.identifier }, isPrivate); + getAnnouncementData( + { name: data.name, identifier: data.identifier }, + isPrivate + ); } catch (error) {} } setAnnouncements((prev) => [...newArray, ...prev]); @@ -486,13 +518,15 @@ export const GroupAnnouncements = ({
{!isMobile && ( Group Announcements )} - +
{!isLoading && combinedListTempAndReal?.length === 0 && ( No announcements @@ -589,28 +622,28 @@ export const GroupAnnouncements = ({ style={{ // position: 'fixed', // bottom: '0px', - backgroundColor: "#232428", - minHeight: isMobile ? "0px" : "150px", - maxHeight: isMobile ? "auto" : "400px", - 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", + backgroundColor: '#232428', + minHeight: isMobile ? '0px' : '150px', + maxHeight: isMobile ? 'auto' : '400px', + 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, }} >
@@ -625,12 +658,12 @@ export const GroupAnnouncements = ({
{isFocusedParent && ( @@ -639,19 +672,19 @@ export const GroupAnnouncements = ({ if (isSending) return; setIsFocusedParent(false); clearEditorContent(); - setTimeout(() => { - triggerRerender(); - }, 300); + setTimeout(() => { + triggerRerender(); + }, 300); // Unfocus the editor }} style={{ - marginTop: "auto", - alignSelf: "center", - cursor: isSending ? "default" : "pointer", - background: "var(--danger)", + marginTop: 'auto', + alignSelf: 'center', + cursor: isSending ? 'default' : 'pointer', + background: 'var(--danger)', flexShrink: 0, - padding: isMobile && "5px", - fontSize: isMobile && "14px", + padding: isMobile && '5px', + fontSize: isMobile && '14px', }} > {` Close`} @@ -663,25 +696,25 @@ export const GroupAnnouncements = ({ publishAnnouncement(); }} style={{ - marginTop: "auto", - alignSelf: "center", - cursor: isSending ? "default" : "pointer", - background: isSending && "rgba(0, 0, 0, 0.8)", + marginTop: 'auto', + alignSelf: 'center', + cursor: isSending ? 'default' : 'pointer', + background: isSending && 'rgba(0, 0, 0, 0.8)', flexShrink: 0, - padding: isMobile && "5px", - fontSize: isMobile && "14px", + padding: isMobile && '5px', + fontSize: isMobile && '14px', }} > {isSending && ( )} @@ -701,7 +734,7 @@ export const GroupAnnouncements = ({
diff --git a/src/components/DesktopSideBar.tsx b/src/components/DesktopSideBar.tsx index 08336e5..dce577b 100644 --- a/src/components/DesktopSideBar.tsx +++ b/src/components/DesktopSideBar.tsx @@ -6,7 +6,7 @@ import { IconWrapper } from './Desktop/DesktopFooter'; import { useRecoilState } from 'recoil'; import { enabledDevModeAtom } from '../atoms/global'; import { AppsIcon } from '../assets/Icons/AppsIcon'; -import ThemeSelector from '../styles/ThemeSelector'; +import ThemeSelector from './Theme/ThemeSelector'; export const DesktopSideBar = ({ goToHome, diff --git a/src/components/GlobalActions/JoinGroup.tsx b/src/components/GlobalActions/JoinGroup.tsx index f03430b..5085bac 100644 --- a/src/components/GlobalActions/JoinGroup.tsx +++ b/src/components/GlobalActions/JoinGroup.tsx @@ -1,5 +1,5 @@ -import React, { useContext, useEffect, useMemo, useState } from "react"; -import { subscribeToEvent, unsubscribeFromEvent } from "../../utils/events"; +import React, { useContext, useEffect, useMemo, useState } from 'react'; +import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events'; import { Box, Button, @@ -9,12 +9,12 @@ import { DialogActions, DialogContent, Typography, -} from "@mui/material"; -import { CustomButton, CustomButtonAccept } from "../../App-styles"; -import { getBaseApiReact, MyContext } from "../../App"; -import { getFee } from "../../background"; -import { CustomizedSnackbars } from "../Snackbar/Snackbar"; -import { FidgetSpinner } from "react-loader-spinner"; +} from '@mui/material'; +import { CustomButton, CustomButtonAccept } from '../../styles/App-styles'; +import { getBaseApiReact, MyContext } from '../../App'; +import { getFee } from '../../background'; +import { CustomizedSnackbars } from '../Snackbar/Snackbar'; +import { FidgetSpinner } from 'react-loader-spinner'; export const JoinGroup = ({ memberGroups }) => { const { show, setTxList } = useContext(MyContext); @@ -42,43 +42,45 @@ export const JoinGroup = ({ memberGroups }) => { }; useEffect(() => { - subscribeToEvent("globalActionJoinGroup", handleJoinGroup); + subscribeToEvent('globalActionJoinGroup', handleJoinGroup); return () => { - unsubscribeFromEvent("globalActionJoinGroup", handleJoinGroup); + unsubscribeFromEvent('globalActionJoinGroup', handleJoinGroup); }; }, []); - const isInGroup = useMemo(()=> { - return !!memberGroups.find((item)=> +item?.groupId === +groupInfo?.groupId) - }, [memberGroups, groupInfo]) + const isInGroup = useMemo(() => { + return !!memberGroups.find( + (item) => +item?.groupId === +groupInfo?.groupId + ); + }, [memberGroups, groupInfo]); const joinGroup = async (group, isOpen) => { try { const groupId = group.groupId; - const fee = await getFee("JOIN_GROUP"); + 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, @@ -90,7 +92,7 @@ export const JoinGroup = ({ memberGroups }) => { 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, @@ -105,7 +107,7 @@ export const JoinGroup = ({ memberGroups }) => { return; } else { setInfoSnack({ - type: "error", + type: 'error', message: response?.error, }); setOpenSnack(true); @@ -114,8 +116,8 @@ export const JoinGroup = ({ memberGroups }) => { }) .catch((error) => { setInfoSnack({ - type: "error", - message: error.message || "An error occurred", + type: 'error', + message: error.message || 'An error occurred', }); setOpenSnack(true); rej(error); @@ -138,37 +140,37 @@ export const JoinGroup = ({ memberGroups }) => { {!groupInfo && ( - {" "} + {' '} {" "} + />{' '} )} @@ -176,7 +178,7 @@ export const JoinGroup = ({ memberGroups }) => { @@ -185,7 +187,7 @@ export const JoinGroup = ({ memberGroups }) => { {groupInfo?.description && ( @@ -193,19 +195,19 @@ export const JoinGroup = ({ memberGroups }) => { )} {isInGroup && ( - - *You are already in this group! - + + *You are already in this group! + )} {!isInGroup && groupInfo?.isOpen === false && ( @@ -216,32 +218,34 @@ export const JoinGroup = ({ memberGroups }) => { - { + { joinGroup(groupInfo, groupInfo?.isOpen); setIsOpen(false); - }} disabled={isInGroup}> - - Join - + + Join + - + setIsOpen(false)} > @@ -259,14 +263,14 @@ export const JoinGroup = ({ memberGroups }) => { {isLoadingJoinGroup && ( { - 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"); + 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; @@ -111,7 +111,6 @@ export const getPublishesFromAdmins = async (admins: string[], groupId) => { return dateB.getTime() - dateA.getTime(); }); - return sortedData[0]; }; interface GroupProps { @@ -149,7 +148,7 @@ export const getGroupAdminsAddress = async (groupNumber: number) => { export function validateSecretKey(obj) { // Check if the input is an object - if (typeof obj !== "object" || obj === null) { + if (typeof obj !== 'object' || obj === null) { return false; } @@ -164,19 +163,19 @@ export function validateSecretKey(obj) { const value = obj[key]; // Check that value is an object and not null - if (typeof value !== "object" || value === null) { + if (typeof value !== 'object' || value === null) { return false; } - // Check for messageKey - if (!value.hasOwnProperty("messageKey")) { + // Check for messageKey + if (!value.hasOwnProperty('messageKey')) { return false; } // Ensure messageKey and nonce are non-empty strings if ( - typeof value.messageKey !== "string" || - value.messageKey.trim() === "" + typeof value.messageKey !== 'string' || + value.messageKey.trim() === '' ) { return false; } @@ -196,33 +195,34 @@ export const getGroupMembers = async (groupNumber: number) => { return groupData; }; - export const decryptResource = async (data: string, fromQortalRequest) => { try { return new Promise((res, rej) => { - window.sendMessage("decryptGroupEncryption", { - data, - }) + window + .sendMessage('decryptGroupEncryption', { + data, + }) .then((response) => { if (!response?.error) { res(response); return; } - if(fromQortalRequest){ - rej({error: response.error, message: response?.error}); + if (fromQortalRequest) { + rej({ error: response.error, message: response?.error }); } else { rej(response.error); - } }) .catch((error) => { - if(fromQortalRequest){ - rej({message: error.message || "An error occurred", error: error.message || "An error occurred"}); + if (fromQortalRequest) { + rej({ + message: error.message || 'An error occurred', + error: error.message || 'An error occurred', + }); } else { - rej(error.message || "An error occurred",); + rej(error.message || 'An error occurred'); } }); - }); } catch (error) {} }; @@ -230,11 +230,12 @@ export const decryptResource = async (data: string, fromQortalRequest) => { export const addDataPublishesFunc = async (data: string, groupId, type) => { try { return new Promise((res, rej) => { - window.sendMessage("addDataPublishes", { - data, - groupId, - type, - }) + window + .sendMessage('addDataPublishes', { + data, + groupId, + type, + }) .then((response) => { if (!response?.error) { res(response); @@ -243,9 +244,8 @@ export const addDataPublishesFunc = async (data: string, groupId, type) => { rej(response.error); }) .catch((error) => { - rej(error.message || "An error occurred"); + rej(error.message || 'An error occurred'); }); - }); } catch (error) {} }; @@ -253,10 +253,11 @@ export const addDataPublishesFunc = async (data: string, groupId, type) => { export const getDataPublishesFunc = async (groupId, type) => { try { return new Promise((res, rej) => { - window.sendMessage("getDataPublishes", { - groupId, - type, - }) + window + .sendMessage('getDataPublishes', { + groupId, + type, + }) .then((response) => { if (!response?.error) { res(response); @@ -265,9 +266,8 @@ export const getDataPublishesFunc = async (groupId, type) => { rej(response.error); }) .catch((error) => { - rej(error.message || "An error occurred"); + rej(error.message || 'An error occurred'); }); - }); } catch (error) {} }; @@ -279,7 +279,7 @@ export async function getNameInfo(address: string) { if (nameData?.length > 0) { return nameData[0]?.name; } else { - return ""; + return ''; } } @@ -294,7 +294,6 @@ export const getGroupAdmins = async (groupNumber: number) => { let membersAddresses = []; let both = []; - const getMemNames = groupData?.members?.map(async (member) => { if (member?.member) { const name = await requestQueueAdminMemberNames.enqueue(() => { @@ -327,7 +326,7 @@ export const getNames = async (listOfMembers) => { if (name) { members.push({ ...member, name }); } else { - members.push({ ...member, name: "" }); + members.push({ ...member, name: '' }); } } @@ -339,7 +338,6 @@ export const getNames = async (listOfMembers) => { return members; }; export const getNamesForAdmins = async (admins) => { - let members: any = []; const getMemNames = admins?.map(async (admin) => { @@ -379,9 +377,9 @@ export const Group = ({ balance, setIsOpenDrawerProfile, setDesktopViewMode, - desktopViewMode + desktopViewMode, }: GroupProps) => { - const [desktopSideView, setDesktopSideView] = useState('groups') + const [desktopSideView, setDesktopSideView] = useState('groups'); const [secretKey, setSecretKey] = useState(null); const [secretKeyPublishDate, setSecretKeyPublishDate] = useState(null); @@ -405,10 +403,11 @@ export const Group = ({ const [openAddGroup, setOpenAddGroup] = useState(false); const [isInitialGroups, setIsInitialGroups] = useState(false); const [openManageMembers, setOpenManageMembers] = useState(false); - const { setMemberGroups, rootHeight, isRunningPublicNode } = useContext(MyContext); + const { setMemberGroups, rootHeight, isRunningPublicNode } = + useContext(MyContext); const lastGroupNotification = useRef(null); const [timestampEnterData, setTimestampEnterData] = useState({}); - const [chatMode, setChatMode] = useState("groups"); + const [chatMode, setChatMode] = useState('groups'); const [newChat, setNewChat] = useState(false); const [openSnack, setOpenSnack] = React.useState(false); const [infoSnack, setInfoSnack] = React.useState(null); @@ -417,18 +416,18 @@ export const Group = ({ const [isLoadingGroup, setIsLoadingGroup] = React.useState(false); const [firstSecretKeyInCreation, setFirstSecretKeyInCreation] = React.useState(false); - const [groupSection, setGroupSection] = React.useState("home"); + const [groupSection, setGroupSection] = React.useState('home'); const [groupAnnouncements, setGroupAnnouncements] = React.useState({}); const [defaultThread, setDefaultThread] = React.useState(null); const [isOpenDrawer, setIsOpenDrawer] = React.useState(false); - const setIsOpenBlockedUserModal = useSetRecoilState(isOpenBlockedModalAtom) + const setIsOpenBlockedUserModal = useSetRecoilState(isOpenBlockedModalAtom); const [hideCommonKeyPopup, setHideCommonKeyPopup] = React.useState(false); - const [isLoadingGroupMessage, setIsLoadingGroupMessage] = React.useState(""); - const [drawerMode, setDrawerMode] = React.useState("groups"); + const [isLoadingGroupMessage, setIsLoadingGroupMessage] = React.useState(''); + const [drawerMode, setDrawerMode] = React.useState('groups'); const [mutedGroups, setMutedGroups] = useState([]); - const [mobileViewMode, setMobileViewMode] = useState("home"); - const [mobileViewModeKeepOpen, setMobileViewModeKeepOpen] = useState(""); + const [mobileViewMode, setMobileViewMode] = useState('home'); + const [mobileViewModeKeepOpen, setMobileViewModeKeepOpen] = useState(''); const isFocusedRef = useRef(true); const timestampEnterDataRef = useRef({}); const selectedGroupRef = useRef(null); @@ -441,42 +440,42 @@ export const Group = ({ const { clearStatesMessageQueueProvider } = useMessageQueue(); const initiatedGetMembers = useRef(false); const [groupChatTimestamps, setGroupChatTimestamps] = React.useState({}); - const [appsMode, setAppsMode] = useState('home') - const [appsModeDev, setAppsModeDev] = useState('home') - const [isOpenSideViewDirects, setIsOpenSideViewDirects] = useState(false) - const [isOpenSideViewGroups, setIsOpenSideViewGroups] = useState(false) - const [isForceShowCreationKeyPopup, setIsForceShowCreationKeyPopup] = useState(false) + const [appsMode, setAppsMode] = useState('home'); + const [appsModeDev, setAppsModeDev] = useState('home'); + const [isOpenSideViewDirects, setIsOpenSideViewDirects] = useState(false); + const [isOpenSideViewGroups, setIsOpenSideViewGroups] = useState(false); + const [isForceShowCreationKeyPopup, setIsForceShowCreationKeyPopup] = + useState(false); - const [groupsProperties, setGroupsProperties] = useRecoilState(groupsPropertiesAtom) + const [groupsProperties, setGroupsProperties] = + useRecoilState(groupsPropertiesAtom); const setUserInfoForLevels = useSetRecoilState(addressInfoControllerAtom); - const isPrivate = useMemo(()=> { - if(selectedGroup?.groupId === '0') return false - if(!selectedGroup?.groupId || !groupsProperties[selectedGroup?.groupId]) return null - if(groupsProperties[selectedGroup?.groupId]?.isOpen === true) return false - if(groupsProperties[selectedGroup?.groupId]?.isOpen === false) return true - return null - }, [selectedGroup]) + const isPrivate = useMemo(() => { + if (selectedGroup?.groupId === '0') return false; + if (!selectedGroup?.groupId || !groupsProperties[selectedGroup?.groupId]) + return null; + if (groupsProperties[selectedGroup?.groupId]?.isOpen === true) return false; + if (groupsProperties[selectedGroup?.groupId]?.isOpen === false) return true; + return null; + }, [selectedGroup]); - - - - const setSelectedGroupId = useSetRecoilState(selectedGroupIdAtom) - const toggleSideViewDirects = ()=> { - if(isOpenSideViewGroups){ - setIsOpenSideViewGroups(false) + const setSelectedGroupId = useSetRecoilState(selectedGroupIdAtom); + const toggleSideViewDirects = () => { + if (isOpenSideViewGroups) { + setIsOpenSideViewGroups(false); } - setIsOpenSideViewDirects((prev)=> !prev) - } - const toggleSideViewGroups = ()=> { - if(isOpenSideViewDirects){ - setIsOpenSideViewDirects(false) + setIsOpenSideViewDirects((prev) => !prev); + }; + const toggleSideViewGroups = () => { + if (isOpenSideViewDirects) { + setIsOpenSideViewDirects(false); } - setIsOpenSideViewGroups((prev)=> !prev) - } - useEffect(()=> { - timestampEnterDataRef.current = timestampEnterData - }, [timestampEnterData]) + setIsOpenSideViewGroups((prev) => !prev); + }; + useEffect(() => { + timestampEnterDataRef.current = timestampEnterData; + }, [timestampEnterData]); useEffect(() => { isFocusedRef.current = isFocused; @@ -487,21 +486,20 @@ export const Group = ({ useEffect(() => { selectedGroupRef.current = selectedGroup; - setSelectedGroupId(selectedGroup?.groupId) + setSelectedGroupId(selectedGroup?.groupId); }, [selectedGroup]); useEffect(() => { selectedDirectRef.current = selectedDirect; }, [selectedDirect]); - - const getUserSettings = async () => { try { return new Promise((res, rej) => { - window.sendMessage("getUserSettings", { - key: "mutedGroups", - }) + window + .sendMessage('getUserSettings', { + key: 'mutedGroups', + }) .then((response) => { if (!response?.error) { setMutedGroups(response || []); @@ -511,12 +509,11 @@ export const Group = ({ rej(response.error); }) .catch((error) => { - rej(error.message || "An error occurred"); + rej(error.message || 'An error occurred'); }); - }); } catch (error) { - console.log("error", error); + console.log('error', error); } }; @@ -527,56 +524,56 @@ export const Group = ({ const getTimestampEnterChat = async () => { try { return new Promise((res, rej) => { - window.sendMessage("getTimestampEnterChat") - .then((response) => { - if (!response?.error) { - setTimestampEnterData(response); - res(response); - return; - } - rej(response.error); - }) - .catch((error) => { - rej(error.message || "An error occurred"); - }); - + window + .sendMessage('getTimestampEnterChat') + .then((response) => { + if (!response?.error) { + setTimestampEnterData(response); + res(response); + return; + } + rej(response.error); + }) + .catch((error) => { + rej(error.message || 'An error occurred'); + }); }); } catch (error) {} }; const refreshHomeDataFunc = () => { - setGroupSection("default"); + setGroupSection('default'); setTimeout(() => { - setGroupSection("home"); + setGroupSection('home'); }, 300); }; const getGroupAnnouncements = async () => { try { return new Promise((res, rej) => { - window.sendMessage("getGroupNotificationTimestamp") - .then((response) => { - if (!response?.error) { - setGroupAnnouncements(response); - res(response); - return; - } - rej(response.error); - }) - .catch((error) => { - rej(error.message || "An error occurred"); - }); - + window + .sendMessage('getGroupNotificationTimestamp') + .then((response) => { + if (!response?.error) { + setGroupAnnouncements(response); + res(response); + return; + } + rej(response.error); + }) + .catch((error) => { + rej(error.message || 'An error occurred'); + }); }); } catch (error) {} }; - useEffect(()=> { - if(myAddress){ - getGroupAnnouncements() - getTimestampEnterChat() + useEffect(() => { + if (myAddress) { + getGroupAnnouncements(); + getTimestampEnterChat(); } - }, [myAddress]) + }, [myAddress]); const getGroupOwner = async (groupId) => { try { @@ -592,9 +589,6 @@ export const Group = ({ } catch (error) {} }; - - - const directChatHasUnread = useMemo(() => { let hasUnread = false; directs.forEach((direct) => { @@ -615,13 +609,12 @@ export const Group = ({ const groupChatHasUnread = useMemo(() => { let hasUnread = false; groups.forEach((group) => { - - if ( group?.data && group?.sender !== myAddress && - group?.timestamp && groupChatTimestamps[group?.groupId] && - ((!timestampEnterData[group?.groupId] && + group?.timestamp && + groupChatTimestamps[group?.groupId] && + ((!timestampEnterData[group?.groupId] && Date.now() - group?.timestamp < timeDifferenceForNotificationChats) || timestampEnterData[group?.groupId] < group?.timestamp) ) { @@ -644,15 +637,12 @@ export const Group = ({ return hasUnread; }, [groupAnnouncements, groups]); - - - const getSecretKey = async ( loadingGroupParam?: boolean, secretKeyToPublish?: boolean ) => { try { - setIsLoadingGroupMessage("Locating encryption keys"); + setIsLoadingGroupMessage('Locating encryption keys'); pauseAllQueues(); let dataFromStorage; let publishFromStorage; @@ -660,8 +650,7 @@ export const Group = ({ if ( secretKeyToPublish && secretKey && - lastFetchedSecretKey.current - && + lastFetchedSecretKey.current && Date.now() - lastFetchedSecretKey.current < 600000 ) return secretKey; @@ -681,10 +670,11 @@ export const Group = ({ setAdmins(addresses); setAdminsWithNames(both); if (!names.length) { - throw new Error("Network error"); + throw new Error('Network error'); } const publish = - publishFromStorage || (await getPublishesFromAdmins(names, selectedGroup?.groupId)); + publishFromStorage || + (await getPublishesFromAdmins(names, selectedGroup?.groupId)); if (prevGroupId !== selectedGroupRef.current.groupId) { if (settimeoutForRefetchSecretKey.current) { @@ -705,7 +695,7 @@ export const Group = ({ data = dataFromStorage; } else { // const shouldRebuild = !secretKeyPublishDate || (publish?.update && publish?.updated > secretKeyPublishDate) - setIsLoadingGroupMessage("Downloading encryption keys"); + setIsLoadingGroupMessage('Downloading encryption keys'); const res = await fetch( `${getBaseApiReact()}/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${ publish.identifier @@ -717,20 +707,25 @@ export const Group = ({ 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'); setSecretKeyDetails(publish); setSecretKey(decryptedKeyToObject); lastFetchedSecretKey.current = Date.now(); setMemberCountFromSecretKeyData(decryptedKey.count); - window.sendMessage("setGroupData", { - groupId: selectedGroup?.groupId, - secretKeyData: data, - secretKeyResource: publish, - admins: { names, addresses, both }, - }).catch((error) => { - console.error("Failed to set group data:", error.message || "An error occurred"); + window + .sendMessage('setGroupData', { + groupId: selectedGroup?.groupId, + secretKeyData: data, + secretKeyResource: publish, + admins: { names, addresses, both }, + }) + .catch((error) => { + console.error( + 'Failed to set group data:', + error.message || 'An error occurred' + ); }); - + if (decryptedKeyToObject) { setTriedToFetchSecretKey(true); setFirstSecretKeyInCreation(false); @@ -739,7 +734,7 @@ export const Group = ({ setTriedToFetchSecretKey(true); } } catch (error) { - if (error === "Unable to decrypt data") { + if (error === 'Unable to decrypt data') { setTriedToFetchSecretKey(true); settimeoutForRefetchSecretKey.current = setTimeout(() => { getSecretKey(); @@ -747,182 +742,197 @@ export const Group = ({ } } finally { setIsLoadingGroup(false); - setIsLoadingGroupMessage(""); + setIsLoadingGroupMessage(''); resumeAllQueues(); } }; - - const getAdminsForPublic = async(selectedGroup)=> { + const getAdminsForPublic = async (selectedGroup) => { try { - const { names, addresses, both } = - await getGroupAdmins(selectedGroup?.groupId) - setAdmins(addresses); - setAdminsWithNames(both); + const { names, addresses, both } = await getGroupAdmins( + selectedGroup?.groupId + ); + setAdmins(addresses); + setAdminsWithNames(both); } catch (error) { //error } - } + }; useEffect(() => { if (selectedGroup && isPrivate !== null) { - if(isPrivate){ + if (isPrivate) { setTriedToFetchSecretKey(false); getSecretKey(true); } - + getGroupOwner(selectedGroup?.groupId); } - if(isPrivate === false){ + if (isPrivate === false) { setTriedToFetchSecretKey(true); - if(selectedGroup?.groupId !== '0'){ - getAdminsForPublic(selectedGroup) + if (selectedGroup?.groupId !== '0') { + getAdminsForPublic(selectedGroup); } - - } }, [selectedGroup, isPrivate]); - - - - - const getCountNewMesg = async (groupId, after)=> { + const getCountNewMesg = async (groupId, after) => { try { const response = await fetch( `${getBaseApiReact()}/chat/messages?after=${after}&txGroupId=${groupId}&haschatreference=false&encoding=BASE64&limit=1` ); const data = await response.json(); - if(data && data[0]) return data[0].timestamp - } catch (error) { - - } - } + if (data && data[0]) return data[0].timestamp; + } catch (error) {} + }; - const getLatestRegularChat = async (groups)=> { + const getLatestRegularChat = async (groups) => { try { - - const groupData = {} + const groupData = {}; - const getGroupData = groups.map(async(group)=> { - if(!group.groupId || !group?.timestamp) return null - if((!groupData[group.groupId] || groupData[group.groupId] < group.timestamp)){ - const hasMoreRecentMsg = await getCountNewMesg(group.groupId, timestampEnterDataRef.current[group?.groupId] || Date.now() - 24 * 60 * 60 * 1000) - if(hasMoreRecentMsg){ - groupData[group.groupId] = hasMoreRecentMsg + const getGroupData = groups.map(async (group) => { + if (!group.groupId || !group?.timestamp) return null; + if ( + !groupData[group.groupId] || + groupData[group.groupId] < group.timestamp + ) { + const hasMoreRecentMsg = await getCountNewMesg( + group.groupId, + timestampEnterDataRef.current[group?.groupId] || + Date.now() - 24 * 60 * 60 * 1000 + ); + if (hasMoreRecentMsg) { + groupData[group.groupId] = hasMoreRecentMsg; } } else { - return null + return null; } - }) + }); - await Promise.all(getGroupData) - setGroupChatTimestamps(groupData) - } catch (error) { - - } - } + await Promise.all(getGroupData); + setGroupChatTimestamps(groupData); + } catch (error) {} + }; - const getGroupsProperties = useCallback(async(address)=> { + const getGroupsProperties = useCallback(async (address) => { try { const url = `${getBaseApiReact()}/groups/member/${address}`; const response = await fetch(url); - if(!response.ok) throw new Error('Cannot get group properties') + if (!response.ok) throw new Error('Cannot get group properties'); let data = await response.json(); - const transformToObject = data.reduce((result, item) => { - - result[item.groupId] = item - return result; - }, {}); - setGroupsProperties(transformToObject) + const transformToObject = data.reduce((result, item) => { + result[item.groupId] = item; + return result; + }, {}); + setGroupsProperties(transformToObject); } catch (error) { // error } - }, []) + }, []); - - useEffect(()=> { - if(!myAddress) return - if(areKeysEqual(groups?.map((grp)=> grp?.groupId), Object.keys(groupsProperties))){ + useEffect(() => { + if (!myAddress) return; + if ( + areKeysEqual( + groups?.map((grp) => grp?.groupId), + Object.keys(groupsProperties) + ) + ) { } else { - getGroupsProperties(myAddress) + getGroupsProperties(myAddress); } - }, [groups, myAddress]) - - + }, [groups, myAddress]); useEffect(() => { // Handler function for incoming messages const messageHandler = (event) => { if (event.origin !== window.location.origin) { - return; + return; } const message = event.data; - if (message?.action === "SET_GROUPS") { - + if (message?.action === 'SET_GROUPS') { // Update the component state with the received 'sendqort' state setGroups(sortArrayByTimestampAndGroupName(message.payload)); getLatestRegularChat(message.payload); - setMemberGroups(message.payload?.filter((item)=> item?.groupId !== '0')); - - if (selectedGroupRef.current && groupSectionRef.current === "chat") { - window.sendMessage("addTimestampEnterChat", { - timestamp: Date.now(), - groupId: selectedGroupRef.current.groupId, - }).catch((error) => { - console.error("Failed to add timestamp:", error.message || "An error occurred"); - }); + setMemberGroups( + message.payload?.filter((item) => item?.groupId !== '0') + ); + + if (selectedGroupRef.current && groupSectionRef.current === 'chat') { + window + .sendMessage('addTimestampEnterChat', { + timestamp: Date.now(), + groupId: selectedGroupRef.current.groupId, + }) + .catch((error) => { + console.error( + 'Failed to add timestamp:', + error.message || 'An error occurred' + ); + }); } - + if (selectedDirectRef.current) { - window.sendMessage("addTimestampEnterChat", { - timestamp: Date.now(), - groupId: selectedDirectRef.current.address, - }).catch((error) => { - console.error("Failed to add timestamp:", error.message || "An error occurred"); - }); + window + .sendMessage('addTimestampEnterChat', { + timestamp: Date.now(), + groupId: selectedDirectRef.current.address, + }) + .catch((error) => { + console.error( + 'Failed to add timestamp:', + error.message || 'An error occurred' + ); + }); } - + setTimeout(() => { getTimestampEnterChat(); }, 600); } - - if (message?.action === "SET_GROUP_ANNOUNCEMENTS") { + + if (message?.action === 'SET_GROUP_ANNOUNCEMENTS') { // Update the component state with the received 'sendqort' state setGroupAnnouncements(message.payload); - - if (selectedGroupRef.current && groupSectionRef.current === "announcement") { - window.sendMessage("addGroupNotificationTimestamp", { - timestamp: Date.now(), - groupId: selectedGroupRef.current.groupId, - }).catch((error) => { - console.error("Failed to add group notification timestamp:", error.message || "An error occurred"); - }); - + + if ( + selectedGroupRef.current && + groupSectionRef.current === 'announcement' + ) { + window + .sendMessage('addGroupNotificationTimestamp', { + timestamp: Date.now(), + groupId: selectedGroupRef.current.groupId, + }) + .catch((error) => { + console.error( + 'Failed to add group notification timestamp:', + error.message || 'An error occurred' + ); + }); + setTimeout(() => { getGroupAnnouncements(); }, 200); } } - - if (message?.action === "SET_DIRECTS") { + + if (message?.action === 'SET_DIRECTS') { // Update the component state with the received 'sendqort' state setDirects(message.payload); - } else if (message?.action === "PLAY_NOTIFICATION_SOUND") { + } else if (message?.action === 'PLAY_NOTIFICATION_SOUND') { // audio.play(); } }; - + // Attach the event listener - window.addEventListener("message", messageHandler); - + window.addEventListener('message', messageHandler); + // Clean up the event listener on component unmount return () => { - window.removeEventListener("message", messageHandler); + window.removeEventListener('message', messageHandler); }; }, []); - useEffect(() => { if ( @@ -933,11 +943,12 @@ export const Group = ({ ) return; - window.sendMessage("setupGroupWebsocket", {}) - .catch((error) => { - console.error("Failed to setup group websocket:", error.message || "An error occurred"); - }); - + window.sendMessage('setupGroupWebsocket', {}).catch((error) => { + console.error( + 'Failed to setup group websocket:', + error.message || 'An error occurred' + ); + }); hasInitializedWebsocket.current = true; }, [myAddress, groups]); @@ -954,7 +965,8 @@ export const Group = ({ !initiatedGetMembers.current && selectedGroup?.groupId && secretKey && - admins.includes(myAddress) && selectedGroup?.groupId !== '0' + admins.includes(myAddress) && + selectedGroup?.groupId !== '0' ) { // getAdmins(selectedGroup?.groupId); getMembers(selectedGroup?.groupId); @@ -1000,10 +1012,11 @@ export const Group = ({ try { setIsLoadingNotifyAdmin(true); await new Promise((res, rej) => { - window.sendMessage("notifyAdminRegenerateSecretKey", { - adminAddress: admin.address, - groupName: selectedGroup?.groupName, - }) + window + .sendMessage('notifyAdminRegenerateSecretKey', { + adminAddress: admin.address, + groupName: selectedGroup?.groupName, + }) .then((response) => { if (!response?.error) { res(response); @@ -1012,19 +1025,18 @@ export const Group = ({ rej(response.error); }) .catch((error) => { - rej(error.message || "An error occurred"); + rej(error.message || 'An error occurred'); }); - }); setInfoSnack({ - type: "success", - message: "Successfully sent notification.", + type: 'success', + message: 'Successfully sent notification.', }); setOpenSnack(true); } catch (error) { setInfoSnack({ - type: "error", - message: "Unable to send notification", + type: 'error', + message: 'Unable to send notification', }); } finally { setIsLoadingNotifyAdmin(false); @@ -1038,7 +1050,8 @@ export const Group = ({ if (!findGroup) return false; if (!findGroup?.data) return false; return ( - findGroup?.timestamp && groupChatTimestamps[findGroup?.groupId] && + findGroup?.timestamp && + groupChatTimestamps[findGroup?.groupId] && ((!timestampEnterData[selectedGroup?.groupId] && Date.now() - findGroup?.timestamp < timeDifferenceForNotificationChats) || @@ -1066,23 +1079,27 @@ export const Group = ({ return; } if (findDirect) { - if(!isMobile){ - setDesktopSideView("directs"); - setDesktopViewMode('home') + if (!isMobile) { + setDesktopSideView('directs'); + setDesktopViewMode('home'); } else { - setMobileViewModeKeepOpen("messaging"); + setMobileViewModeKeepOpen('messaging'); } setSelectedDirect(null); setNewChat(false); - window.sendMessage("addTimestampEnterChat", { - timestamp: Date.now(), - groupId: findDirect.address, - }).catch((error) => { - console.error("Failed to add timestamp:", error.message || "An error occurred"); + window + .sendMessage('addTimestampEnterChat', { + timestamp: Date.now(), + groupId: findDirect.address, + }) + .catch((error) => { + console.error( + 'Failed to add timestamp:', + error.message || 'An error occurred' + ); }); - setTimeout(() => { setSelectedDirect(findDirect); @@ -1102,36 +1119,40 @@ export const Group = ({ ); if (findDirect) { - if(!isMobile){ - setDesktopSideView("directs"); + if (!isMobile) { + setDesktopSideView('directs'); } else { - setMobileViewModeKeepOpen("messaging"); + setMobileViewModeKeepOpen('messaging'); } setSelectedDirect(null); setNewChat(false); - window.sendMessage("addTimestampEnterChat", { - timestamp: Date.now(), - groupId: findDirect.address, - }).catch((error) => { - console.error("Failed to add timestamp:", error.message || "An error occurred"); + window + .sendMessage('addTimestampEnterChat', { + timestamp: Date.now(), + groupId: findDirect.address, + }) + .catch((error) => { + console.error( + 'Failed to add timestamp:', + error.message || 'An error occurred' + ); }); - setTimeout(() => { setSelectedDirect(findDirect); getTimestampEnterChat(); }, 200); } else { - if(!isMobile){ - setDesktopSideView("directs"); + if (!isMobile) { + setDesktopSideView('directs'); } else { - setMobileViewModeKeepOpen("messaging"); + setMobileViewModeKeepOpen('messaging'); } setNewChat(true); setTimeout(() => { - executeEvent("setDirectToValueNewChat", { + executeEvent('setDirectToValueNewChat', { directToValue: name || directAddress, }); }, 500); @@ -1139,41 +1160,50 @@ export const Group = ({ }; useEffect(() => { - subscribeToEvent("openDirectMessageInternal", openDirectChatFromInternal); + subscribeToEvent('openDirectMessageInternal', openDirectChatFromInternal); return () => { unsubscribeFromEvent( - "openDirectMessageInternal", + 'openDirectMessageInternal', openDirectChatFromInternal ); }; }, [directs, selectedDirect]); useEffect(() => { - subscribeToEvent("openDirectMessage", openDirectChatFromNotification); + subscribeToEvent('openDirectMessage', openDirectChatFromNotification); return () => { - unsubscribeFromEvent("openDirectMessage", openDirectChatFromNotification); + unsubscribeFromEvent('openDirectMessage', openDirectChatFromNotification); }; }, [directs, selectedDirect]); const handleMarkAsRead = (e) => { const { groupId } = e.detail; - window.sendMessage("addTimestampEnterChat", { - timestamp: Date.now(), - groupId, - }).catch((error) => { - console.error("Failed to add timestamp:", error.message || "An error occurred"); - }); - - - window.sendMessage("addGroupNotificationTimestamp", { + window + .sendMessage('addTimestampEnterChat', { timestamp: Date.now(), groupId, - }).catch((error) => { - console.error("Failed to add group notification timestamp:", error.message || "An error occurred"); - }); - + }) + .catch((error) => { + console.error( + 'Failed to add timestamp:', + error.message || 'An error occurred' + ); + }); + + window + .sendMessage('addGroupNotificationTimestamp', { + timestamp: Date.now(), + groupId, + }) + .catch((error) => { + console.error( + 'Failed to add group notification timestamp:', + error.message || 'An error occurred' + ); + }); + setTimeout(() => { getGroupAnnouncements(); getTimestampEnterChat(); @@ -1181,10 +1211,10 @@ export const Group = ({ }; useEffect(() => { - subscribeToEvent("markAsRead", handleMarkAsRead); + subscribeToEvent('markAsRead', handleMarkAsRead); return () => { - unsubscribeFromEvent("markAsRead", handleMarkAsRead); + unsubscribeFromEvent('markAsRead', handleMarkAsRead); }; }, []); @@ -1196,7 +1226,7 @@ export const Group = ({ setSecretKeyDetails(null); setNewEncryptionNotification(null); setMemberCountFromSecretKeyData(null); - setIsForceShowCreationKeyPopup(false) + setIsForceShowCreationKeyPopup(false); setSelectedGroup(null); setSelectedDirect(null); setGroups([]); @@ -1212,7 +1242,7 @@ export const Group = ({ setOpenManageMembers(false); setMemberGroups([]); // Assuming you're clearing the context here as well setTimestampEnterData({}); - setChatMode("groups"); + setChatMode('groups'); setNewChat(false); setOpenSnack(false); setInfoSnack(null); @@ -1220,10 +1250,10 @@ export const Group = ({ setIsLoadingGroups(false); setIsLoadingGroup(false); setFirstSecretKeyInCreation(false); - setGroupSection("home"); + setGroupSection('home'); setGroupAnnouncements({}); setDefaultThread(null); - setMobileViewMode("home"); + setMobileViewMode('home'); // Reset all useRef values to their initial states hasInitialized.current = false; hasInitializedWebsocket.current = false; @@ -1237,8 +1267,8 @@ export const Group = ({ setupGroupWebsocketInterval.current = null; settimeoutForRefetchSecretKey.current = null; initiatedGetMembers.current = false; - if(!isMobile){ - setDesktopViewMode('home') + if (!isMobile) { + setDesktopViewMode('home'); } }; @@ -1248,30 +1278,29 @@ export const Group = ({ }; useEffect(() => { - subscribeToEvent("logout-event", logoutEventFunc); + subscribeToEvent('logout-event', logoutEventFunc); return () => { - unsubscribeFromEvent("logout-event", logoutEventFunc); + unsubscribeFromEvent('logout-event', logoutEventFunc); }; }, []); const openAppsMode = () => { if (isMobile) { - setMobileViewMode("apps"); + setMobileViewMode('apps'); } if (!isMobile) { - setDesktopViewMode('apps') - + setDesktopViewMode('apps'); } - if(isMobile){ - setIsOpenSideViewDirects(false) - setIsOpenSideViewGroups(false) - setGroupSection("default"); + if (isMobile) { + setIsOpenSideViewDirects(false); + setIsOpenSideViewGroups(false); + setGroupSection('default'); setSelectedGroup(null); setNewChat(false); setSelectedDirect(null); setSecretKey(null); - setGroupOwner(null) + setGroupOwner(null); lastFetchedSecretKey.current = null; initiatedGetMembers.current = false; setSecretKeyPublishDate(null); @@ -1282,23 +1311,19 @@ export const Group = ({ setMemberCountFromSecretKeyData(null); setTriedToFetchSecretKey(false); setFirstSecretKeyInCreation(false); - setIsOpenSideViewDirects(false) - setIsOpenSideViewGroups(false) + setIsOpenSideViewDirects(false); + setIsOpenSideViewGroups(false); } - - }; useEffect(() => { - subscribeToEvent("open-apps-mode", openAppsMode); + subscribeToEvent('open-apps-mode', openAppsMode); return () => { - unsubscribeFromEvent("open-apps-mode", openAppsMode); + unsubscribeFromEvent('open-apps-mode', openAppsMode); }; }, []); - - const openGroupChatFromNotification = (e) => { if (isLoadingOpenSectionFromNotification.current) return; @@ -1306,18 +1331,18 @@ export const Group = ({ const findGroup = groups?.find((group) => +group?.groupId === +groupId); if (findGroup?.groupId === selectedGroup?.groupId) { isLoadingOpenSectionFromNotification.current = false; - setChatMode("groups"); - setDesktopViewMode('chat') + setChatMode('groups'); + setDesktopViewMode('chat'); return; } if (findGroup) { - setChatMode("groups"); + setChatMode('groups'); setSelectedGroup(null); setSelectedDirect(null); setNewChat(false); setSecretKey(null); - setGroupOwner(null) + setGroupOwner(null); lastFetchedSecretKey.current = null; initiatedGetMembers.current = false; setSecretKeyPublishDate(null); @@ -1326,26 +1351,30 @@ export const Group = ({ setAdminsWithNames([]); setMembers([]); setMemberCountFromSecretKeyData(null); - setIsForceShowCreationKeyPopup(false) + setIsForceShowCreationKeyPopup(false); setTriedToFetchSecretKey(false); setFirstSecretKeyInCreation(false); - setGroupSection("chat"); - if(!isMobile){ - setDesktopViewMode('chat') + setGroupSection('chat'); + if (!isMobile) { + setDesktopViewMode('chat'); } - window.sendMessage("addTimestampEnterChat", { - timestamp: Date.now(), - groupId: findGroup.groupId, - }).catch((error) => { - console.error("Failed to add timestamp:", error.message || "An error occurred"); + window + .sendMessage('addTimestampEnterChat', { + timestamp: Date.now(), + groupId: findGroup.groupId, + }) + .catch((error) => { + console.error( + 'Failed to add timestamp:', + error.message || 'An error occurred' + ); }); - setTimeout(() => { setSelectedGroup(findGroup); - setMobileViewMode("group"); - setDesktopSideView('groups') + setMobileViewMode('group'); + setDesktopSideView('groups'); getTimestampEnterChat(); isLoadingOpenSectionFromNotification.current = false; }, 350); @@ -1355,10 +1384,10 @@ export const Group = ({ }; useEffect(() => { - subscribeToEvent("openGroupMessage", openGroupChatFromNotification); + subscribeToEvent('openGroupMessage', openGroupChatFromNotification); return () => { - unsubscribeFromEvent("openGroupMessage", openGroupChatFromNotification); + unsubscribeFromEvent('openGroupMessage', openGroupChatFromNotification); }; }, [groups, selectedGroup]); @@ -1368,10 +1397,10 @@ export const Group = ({ const findGroup = groups?.find((group) => +group?.groupId === +groupId); if (findGroup?.groupId === selectedGroup?.groupId) return; if (findGroup) { - setChatMode("groups"); + setChatMode('groups'); setSelectedGroup(null); setSecretKey(null); - setGroupOwner(null) + setGroupOwner(null); lastFetchedSecretKey.current = null; initiatedGetMembers.current = false; setSecretKeyPublishDate(null); @@ -1380,24 +1409,29 @@ export const Group = ({ setAdminsWithNames([]); setMembers([]); setMemberCountFromSecretKeyData(null); - setIsForceShowCreationKeyPopup(false) + setIsForceShowCreationKeyPopup(false); setTriedToFetchSecretKey(false); setFirstSecretKeyInCreation(false); - setGroupSection("announcement"); - if(!isMobile){ - setDesktopViewMode('chat') + setGroupSection('announcement'); + if (!isMobile) { + setDesktopViewMode('chat'); } - window.sendMessage("addGroupNotificationTimestamp", { - timestamp: Date.now(), - groupId: findGroup.groupId, - }).catch((error) => { - console.error("Failed to add group notification timestamp:", error.message || "An error occurred"); + window + .sendMessage('addGroupNotificationTimestamp', { + timestamp: Date.now(), + groupId: findGroup.groupId, + }) + .catch((error) => { + console.error( + 'Failed to add group notification timestamp:', + error.message || 'An error occurred' + ); }); - + setTimeout(() => { setSelectedGroup(findGroup); - setMobileViewMode("group"); - setDesktopSideView('groups') + setMobileViewMode('group'); + setDesktopSideView('groups'); getGroupAnnouncements(); }, 350); } @@ -1405,13 +1439,13 @@ export const Group = ({ useEffect(() => { subscribeToEvent( - "openGroupAnnouncement", + 'openGroupAnnouncement', openGroupAnnouncementFromNotification ); return () => { unsubscribeFromEvent( - "openGroupAnnouncement", + 'openGroupAnnouncement', openGroupAnnouncementFromNotification ); }; @@ -1422,16 +1456,16 @@ export const Group = ({ const { groupId } = data; const findGroup = groups?.find((group) => +group?.groupId === +groupId); if (findGroup?.groupId === selectedGroup?.groupId) { - setGroupSection("forum"); + setGroupSection('forum'); setDefaultThread(data); return; } if (findGroup) { - setChatMode("groups"); + setChatMode('groups'); setSelectedGroup(null); setSecretKey(null); - setGroupOwner(null) + setGroupOwner(null); lastFetchedSecretKey.current = null; initiatedGetMembers.current = false; setSecretKeyPublishDate(null); @@ -1440,28 +1474,28 @@ export const Group = ({ setAdminsWithNames([]); setMembers([]); setMemberCountFromSecretKeyData(null); - setIsForceShowCreationKeyPopup(false) + setIsForceShowCreationKeyPopup(false); setTriedToFetchSecretKey(false); setFirstSecretKeyInCreation(false); - setGroupSection("forum"); + setGroupSection('forum'); setDefaultThread(data); - if(!isMobile){ - setDesktopViewMode('chat') + if (!isMobile) { + setDesktopViewMode('chat'); } setTimeout(() => { setSelectedGroup(findGroup); - setMobileViewMode("group"); - setDesktopSideView('groups') + setMobileViewMode('group'); + setDesktopSideView('groups'); getGroupAnnouncements(); }, 350); } }; useEffect(() => { - subscribeToEvent("openThreadNewPost", openThreadNewPostFunc); + subscribeToEvent('openThreadNewPost', openThreadNewPostFunc); return () => { - unsubscribeFromEvent("openThreadNewPost", openThreadNewPostFunc); + unsubscribeFromEvent('openThreadNewPost', openThreadNewPostFunc); }; }, [groups, selectedGroup]); @@ -1471,23 +1505,21 @@ export const Group = ({ const goToHome = async () => { if (isMobile) { - setMobileViewMode("home"); + setMobileViewMode('home'); } if (!isMobile) { } - setDesktopViewMode('home') - + setDesktopViewMode('home'); await new Promise((res) => { setTimeout(() => { res(null); }, 200); }); - }; const goToAnnouncements = async () => { - setGroupSection("default"); + setGroupSection('default'); await new Promise((res) => { setTimeout(() => { res(null); @@ -1495,14 +1527,19 @@ export const Group = ({ }); setSelectedDirect(null); setNewChat(false); - setGroupSection("announcement"); - window.sendMessage("addGroupNotificationTimestamp", { - timestamp: Date.now(), - groupId: selectedGroupRef.current.groupId, - }).catch((error) => { - console.error("Failed to add group notification timestamp:", error.message || "An error occurred"); + setGroupSection('announcement'); + window + .sendMessage('addGroupNotificationTimestamp', { + timestamp: Date.now(), + groupId: selectedGroupRef.current.groupId, + }) + .catch((error) => { + console.error( + 'Failed to add group notification timestamp:', + error.message || 'An error occurred' + ); }); - + setTimeout(() => { getGroupAnnouncements(); }, 200); @@ -1510,33 +1547,37 @@ export const Group = ({ const openDrawerGroups = () => { setIsOpenDrawer(true); - setDrawerMode("groups"); + setDrawerMode('groups'); }; const goToThreads = () => { setSelectedDirect(null); setNewChat(false); - setGroupSection("forum"); + setGroupSection('forum'); }; const goToChat = async () => { - setGroupSection("default"); + setGroupSection('default'); await new Promise((res) => { setTimeout(() => { res(null); }, 200); }); - setGroupSection("chat"); + setGroupSection('chat'); setNewChat(false); setSelectedDirect(null); if (selectedGroupRef.current) { - window.sendMessage("addTimestampEnterChat", { - timestamp: Date.now(), - groupId: selectedGroupRef.current.groupId, - }).catch((error) => { - console.error("Failed to add timestamp:", error.message || "An error occurred"); + window + .sendMessage('addTimestampEnterChat', { + timestamp: Date.now(), + groupId: selectedGroupRef.current.groupId, + }) + .catch((error) => { + console.error( + 'Failed to add timestamp:', + error.message || 'An error occurred' + ); }); - setTimeout(() => { getTimestampEnterChat(); @@ -1544,39 +1585,42 @@ export const Group = ({ } }; - - const renderDirects = () => { return (
{!isMobile && ( - - + { - setDesktopSideView("groups"); + setDesktopSideView('groups'); }} > { - setDesktopSideView("directs"); + setDesktopSideView('directs'); }} > @@ -1609,31 +1658,31 @@ export const Group = ({ height={24} color={ directChatHasUnread - ? "var(--unread)" + ? 'var(--unread)' : desktopSideView === 'directs' - ? "white" - : "rgba(250, 250, 250, 0.5)" + ? 'white' + : 'rgba(250, 250, 250, 0.5)' } /> - - + + )} - +
{directs.map((direct: any) => ( { - console.error("Failed to add timestamp:", error.message || "An error occurred"); + window + .sendMessage('addTimestampEnterChat', { + timestamp: Date.now(), + groupId: direct.address, + }) + .catch((error) => { + console.error( + 'Failed to add timestamp:', + error.message || 'An error occurred' + ); }); - + setTimeout(() => { setSelectedDirect(direct); @@ -1657,29 +1711,29 @@ export const Group = ({ }, 200); }} sx={{ - display: "flex", - width: "100%", - flexDirection: "column", - cursor: "pointer", - border: "1px #232428 solid", - padding: "2px", - borderRadius: "2px", + display: 'flex', + width: '100%', + flexDirection: 'column', + cursor: 'pointer', + border: '1px #232428 solid', + padding: '2px', + borderRadius: '2px', background: - direct?.address === selectedDirect?.address && "white", + direct?.address === selectedDirect?.address && 'white', }} > @@ -1688,28 +1742,32 @@ export const Group = ({ {direct?.sender !== myAddress && @@ -1721,7 +1779,7 @@ export const Group = ({ direct?.timestamp) && ( )} @@ -1732,10 +1790,10 @@ export const Group = ({
New Chat @@ -1757,142 +1815,150 @@ export const Group = ({ ); }; - const renderGroups = () => { return (
{!isMobile && ( - - { - setDesktopSideView("groups"); + - { + setDesktopSideView('groups'); + }} > - - - - { - setDesktopSideView("directs"); - }} - > - + + + + { + setDesktopSideView('directs'); + }} > - - + label="Messaging" + selected={desktopSideView === 'directs'} + > + + )} - +
{groups.map((group: any) => ( { - setMobileViewMode("group"); - setDesktopSideView('groups') + setMobileViewMode('group'); + setDesktopSideView('groups'); initiatedGetMembers.current = false; clearAllQueues(); setSelectedDirect(null); setTriedToFetchSecretKey(false); setNewChat(false); setSelectedGroup(null); - setUserInfoForLevels({}) + setUserInfoForLevels({}); setSecretKey(null); lastFetchedSecretKey.current = null; setSecretKeyPublishDate(null); setAdmins([]); setSecretKeyDetails(null); setAdminsWithNames([]); - setGroupOwner(null) + setGroupOwner(null); setMembers([]); setMemberCountFromSecretKeyData(null); setHideCommonKeyPopup(false); setFirstSecretKeyInCreation(false); - setGroupSection("chat"); + setGroupSection('chat'); setIsOpenDrawer(false); - setIsForceShowCreationKeyPopup(false) + setIsForceShowCreationKeyPopup(false); setTimeout(() => { setSelectedGroup(group); - }, 200); }} sx={{ - display: "flex", - width: "100%", - flexDirection: "column", - cursor: "pointer", - border: "1px #232428 solid", - padding: "2px", - borderRadius: "2px", + display: 'flex', + width: '100%', + flexDirection: 'column', + cursor: 'pointer', + border: '1px #232428 solid', + padding: '2px', + borderRadius: '2px', background: - group?.groupId === selectedGroup?.groupId && "white", + group?.groupId === selectedGroup?.groupId && 'white', }} > {groupsProperties[group?.groupId]?.isOpen === false ? ( - - + + - ): ( - - + ) : ( + + - // - // {group.groupName?.charAt(0)} - // + // + // {group.groupName?.charAt(0)} + // )} - {groupAnnouncements[group?.groupId] && !groupAnnouncements[group?.groupId]?.seentimestamp && ( )} {group?.data && - groupChatTimestamps[group?.groupId] && + groupChatTimestamps[group?.groupId] && group?.sender !== myAddress && group?.timestamp && ((!timestampEnterData[group?.groupId] && @@ -1992,7 +2071,7 @@ export const Group = ({ group?.timestamp) && ( )} @@ -2004,48 +2083,47 @@ export const Group = ({
- {chatMode === "groups" && ( + {chatMode === 'groups' && ( <> - { - setOpenAddGroup(true); - }} - > - { + setOpenAddGroup(true); }} - /> - Group Mgmt - - {!isRunningPublicNode && ( - { - setIsOpenBlockedUserModal(true); - }} - sx={{ - minWidth: 'unset', - padding: '10px' - }} - > - - - )} - + > + + Group Mgmt + + {!isRunningPublicNode && ( + { + setIsOpenBlockedUserModal(true); + }} + sx={{ + minWidth: 'unset', + padding: '10px', + }} + > + + + )} )} - {chatMode === "directs" && ( + {chatMode === 'directs' && ( { setNewChat(true); @@ -2055,7 +2133,7 @@ export const Group = ({ > New Chat @@ -2065,7 +2143,7 @@ export const Group = ({
); }; - + return ( <> - - -
- {!isMobile && ((desktopViewMode !== 'apps' && desktopViewMode !== 'dev') || isOpenSideViewGroups) && ( - - )} + {!isMobile && + ((desktopViewMode !== 'apps' && desktopViewMode !== 'dev') || + isOpenSideViewGroups) && ( + + )} - {!isMobile && desktopViewMode === 'chat' && desktopSideView !== 'directs' && renderGroups()} - {!isMobile && desktopViewMode === 'chat' && desktopSideView === 'directs' && renderDirects()} + {!isMobile && + desktopViewMode === 'chat' && + desktopSideView !== 'directs' && + renderGroups()} + {!isMobile && + desktopViewMode === 'chat' && + desktopSideView === 'directs' && + renderDirects()} - {newChat && ( <> - {isMobile && ( - { - close() + close(); }} > - + - { - setSelectedDirect(null) - setMobileViewModeKeepOpen('') + setSelectedDirect(null); + setMobileViewModeKeepOpen(''); }} > - + - )} + )} - - No group selected - - - )} - -
- {!isMobile && ( - - - - )} - - - - - {triedToFetchSecretKey && ( - + + )} + +
+ {!isMobile && ( + + )} + + + {triedToFetchSecretKey && ( + + )} + {isPrivate && + firstSecretKeyInCreation && + triedToFetchSecretKey && + !secretKeyPublishDate && ( +
+ {' '} + + The group's first common encryption key is in the process + of creation. Please wait a few minutes for it to be + retrieved by the network. Checking every 2 minutes... + +
+ )} + {isPrivate && + !admins.includes(myAddress) && + !secretKey && + triedToFetchSecretKey ? ( + <> + {secretKeyPublishDate || + (!secretKeyPublishDate && !firstSecretKeyInCreation) ? ( +
+ {' '} + + You are not part of the encrypted group of members. Wait + until an admin re-encrypts the keys. + + + + + Only unencrypted messages will be displayed. + + + + + Try notifying an admin from the list of admins below: + + + {adminsWithNames.map((admin) => { + return ( + + {admin?.name} + notifyAdmin(admin)} + > + Notify + + + ); + })} +
+ ) : null} + + ) : admins.includes(myAddress) && + !secretKey && + isPrivate && + triedToFetchSecretKey ? null : !triedToFetchSecretKey ? null : ( + <> + + + {groupSection === 'adminSpace' && ( + + )} + + )} + + + {((isPrivate && + admins.includes(myAddress) && + shouldReEncrypt && + triedToFetchSecretKey && + !firstSecretKeyInCreation && + !hideCommonKeyPopup) || + isForceShowCreationKeyPopup) && ( + )} - {isPrivate && firstSecretKeyInCreation && - triedToFetchSecretKey && - !secretKeyPublishDate && ( -
- {" "} - - The group's first common encryption key is in the - process of creation. Please wait a few minutes for it to - be retrieved by the network. Checking every 2 minutes... - -
- )} - {isPrivate && !admins.includes(myAddress) && - !secretKey && - triedToFetchSecretKey ? ( - <> - {secretKeyPublishDate || - (!secretKeyPublishDate && !firstSecretKeyInCreation) ? ( -
- {" "} - - You are not part of the encrypted group of members. - Wait until an admin re-encrypts the keys. - - - - Only unencrypted messages will be displayed. - - - - Try notifying an admin from the list of admins below: - - - {adminsWithNames.map((admin) => { - return ( - - {admin?.name} - notifyAdmin(admin)} - > - Notify - - - ); - })} -
- ) : null} - - ) : admins.includes(myAddress) && - (!secretKey && isPrivate) && - triedToFetchSecretKey ? null : !triedToFetchSecretKey ? null : ( - <> - - - {groupSection === "adminSpace" && ( - - )} - - - )} - - - {((isPrivate && admins.includes(myAddress) && - shouldReEncrypt && - triedToFetchSecretKey && - !firstSecretKeyInCreation && - !hideCommonKeyPopup) || isForceShowCreationKeyPopup) && ( - - )} -
- {openManageMembers && ( - - )} -
- - + + {openManageMembers && ( + + )} +
+ {selectedDirect && !newChat && ( <> )} - - {!isMobile && ( - - )} - {!isMobile && ( - - )} - - - {!isMobile && ( - - - - )} + {!isMobile && ( + + )} + {!isMobile && ( + + )} - + {!isMobile && ( + + )} - + - - + > @@ -2583,5 +2719,3 @@ export const Group = ({ ); }; - - diff --git a/src/components/Minting/Minting.tsx b/src/components/Minting/Minting.tsx index af63ece..4a9eeb3 100644 --- a/src/components/Minting/Minting.tsx +++ b/src/components/Minting/Minting.tsx @@ -13,29 +13,29 @@ import { InputLabel, Snackbar, Typography, -} from "@mui/material"; +} from '@mui/material'; import React, { useCallback, useContext, useEffect, useMemo, useState, -} from "react"; -import CloseIcon from "@mui/icons-material/Close"; -import { MyContext, getBaseApiReact } from "../../App"; +} from 'react'; +import CloseIcon from '@mui/icons-material/Close'; +import { MyContext, getBaseApiReact } from '../../App'; import { executeEvent, subscribeToEvent, unsubscribeFromEvent, -} from "../../utils/events"; -import { getFee, getNameOrAddress } from "../../background"; -import CopyToClipboard from "react-copy-to-clipboard"; -import { AddressBox } from "../../App-styles"; -import { Spacer } from "../../common/Spacer"; -import Copy from "../../assets/svgs/Copy.svg"; -import { Loader } from "../Loader"; -import { FidgetSpinner } from "react-loader-spinner"; -import { useModal } from "../../common/useModal"; +} from '../../utils/events'; +import { getFee, getNameOrAddress } from '../../background'; +import CopyToClipboard from 'react-copy-to-clipboard'; +import { AddressBox } from '../../styles/App-styles'; +import { Spacer } from '../../common/Spacer'; +import Copy from '../../assets/svgs/Copy.svg'; +import { Loader } from '../Loader'; +import { FidgetSpinner } from 'react-loader-spinner'; +import { useModal } from '../../common/useModal'; export const Minting = ({ setIsOpenMinting, @@ -47,30 +47,30 @@ export const Minting = ({ }) => { const [mintingAccounts, setMintingAccounts] = useState([]); const [accountInfo, setAccountInfo] = useState(null); - const [rewardSharePublicKey, setRewardSharePublicKey] = useState(""); - const [mintingKey, setMintingKey] = useState(""); - const [rewardsharekey, setRewardsharekey] = useState(""); + const [rewardSharePublicKey, setRewardSharePublicKey] = useState(''); + const [mintingKey, setMintingKey] = useState(''); + const [rewardsharekey, setRewardsharekey] = useState(''); const [rewardShares, setRewardShares] = useState([]); const [nodeInfos, setNodeInfos] = useState({}); const [openSnack, setOpenSnack] = useState(false); const [isLoading, setIsLoading] = useState(false); - const { show: showKey, message } = useModal(); - const { isShow: isShowNext, onOk, show: showNext } = useModal(); + const { show: showKey, message } = useModal(); + const { isShow: isShowNext, onOk, show: showNext } = useModal(); const [info, setInfo] = useState(null); const [names, setNames] = useState({}); const [accountInfos, setAccountInfos] = useState({}); - const [showWaitDialog, setShowWaitDialog] = useState(false) + const [showWaitDialog, setShowWaitDialog] = useState(false); const isPartOfMintingGroup = useMemo(() => { if (groups?.length === 0) return false; - return !!groups?.find((item) => item?.groupId?.toString() === "694"); + return !!groups?.find((item) => item?.groupId?.toString() === '694'); }, [groups]); const getMintingAccounts = useCallback(async () => { try { const url = `${getBaseApiReact()}/admin/mintingaccounts`; const response = await fetch(url); if (!response.ok) { - throw new Error("network error"); + throw new Error('network error'); } const data = await response.json(); setMintingAccounts(data); @@ -117,7 +117,7 @@ export const Minting = ({ const url = `${getBaseApiReact()}/addresses/${address}`; const response = await fetch(url); if (!response.ok) { - throw new Error("network error"); + throw new Error('network error'); } const data = await response.json(); if (others) { @@ -144,10 +144,10 @@ export const Minting = ({ }; useEffect(() => { - subscribeToEvent("refresh-rewardshare-list", refreshRewardShare); + subscribeToEvent('refresh-rewardshare-list', refreshRewardShare); return () => { - unsubscribeFromEvent("refresh-rewardshare-list", refreshRewardShare); + unsubscribeFromEvent('refresh-rewardshare-list', refreshRewardShare); }; }, [myAddress]); @@ -177,15 +177,15 @@ export const Minting = ({ try { const url = `${getBaseApiReact()}/admin/status`; const response = await fetch(url, { - method: "GET", + method: 'GET', headers: { - "Content-Type": "application/json", + 'Content-Type': 'application/json', }, }); const data = await response.json(); setNodeInfos(data); } catch (error) { - console.error("Request failed", error); + console.error('Request failed', error); } }; @@ -194,11 +194,11 @@ export const Minting = ({ const url = `${getBaseApiReact()}/addresses/rewardshares?involving=${address}`; const response = await fetch(url); if (!response.ok) { - throw new Error("network error"); + throw new Error('network error'); } const data = await response.json(); setRewardShares(data); - return data + return data; } catch (error) {} }, []); @@ -208,10 +208,10 @@ export const Minting = ({ return await new Promise((res, rej) => { window .sendMessage( - "ADMIN_ACTION", + 'ADMIN_ACTION', { - type: "addmintingaccount", + type: 'addmintingaccount', value: val, }, 180000, @@ -220,7 +220,7 @@ export const Minting = ({ .then((response) => { if (!response?.error) { res(response); - setMintingKey(""); + setMintingKey(''); setTimeout(() => { getMintingAccounts(); }, 300); @@ -229,13 +229,13 @@ export const Minting = ({ rej({ message: response.error }); }) .catch((error) => { - rej({ message: error.message || "An error occurred" }); + rej({ message: error.message || 'An error occurred' }); }); }); } catch (error) { setInfo({ - type: "error", - message: error?.message || "Unable to add minting account", + type: 'error', + message: error?.message || 'Unable to add minting account', }); setOpenSnack(true); } finally { @@ -249,10 +249,10 @@ export const Minting = ({ return await new Promise((res, rej) => { window .sendMessage( - "ADMIN_ACTION", + 'ADMIN_ACTION', { - type: "removemintingaccount", + type: 'removemintingaccount', value: val, }, 180000, @@ -270,13 +270,13 @@ export const Minting = ({ rej({ message: response.error }); }) .catch((error) => { - rej({ message: error.message || "An error occurred" }); + rej({ message: error.message || 'An error occurred' }); }); }); } catch (error) { setInfo({ - type: "error", - message: error?.message || "Unable to remove minting account", + type: 'error', + message: error?.message || 'Unable to remove minting account', }); setOpenSnack(true); } finally { @@ -285,14 +285,14 @@ export const Minting = ({ }, []); const createRewardShare = useCallback(async (publicKey, recipient) => { - const fee = await getFee("REWARD_SHARE"); + const fee = await getFee('REWARD_SHARE'); await show({ - message: "Would you like to perform an REWARD_SHARE transaction?", - publishFee: fee.fee + " QORT", + message: 'Would you like to perform an REWARD_SHARE transaction?', + publishFee: fee.fee + ' QORT', }); return await new Promise((res, rej) => { window - .sendMessage("createRewardShare", { + .sendMessage('createRewardShare', { recipientPublicKey: publicKey, }) .then((response) => { @@ -301,7 +301,7 @@ export const Minting = ({ { recipient, ...response, - type: "add-rewardShare", + type: 'add-rewardShare', label: `Add rewardshare: awaiting confirmation`, labelDone: `Add rewardshare: success!`, done: false, @@ -314,7 +314,7 @@ export const Minting = ({ rej({ message: response.error }); }) .catch((error) => { - rej({ message: error.message || "An error occurred" }); + rej({ message: error.message || 'An error occurred' }); }); }); }, []); @@ -322,7 +322,7 @@ export const Minting = ({ const getRewardSharePrivateKey = useCallback(async (publicKey) => { return await new Promise((res, rej) => { window - .sendMessage("getRewardSharePrivateKey", { + .sendMessage('getRewardSharePrivateKey', { recipientPublicKey: publicKey, }) .then((response) => { @@ -333,7 +333,7 @@ export const Minting = ({ rej({ message: response.error }); }) .catch((error) => { - rej({ message: error.message || "An error occurred" }); + rej({ message: error.message || 'An error occurred' }); }); }); }, []); @@ -341,26 +341,24 @@ export const Minting = ({ const waitUntilRewardShareIsConfirmed = async (timeoutMs = 600000) => { const pollingInterval = 30000; const startTime = Date.now(); - + const sleep = (ms) => new Promise((res) => setTimeout(res, ms)); - + while (Date.now() - startTime < timeoutMs) { - - const rewardShares = await getRewardShares(myAddress); - const findRewardShare = rewardShares?.find( - (item) => - item?.recipient === myAddress && item?.mintingAccount === myAddress - ); - - if (findRewardShare) { - return true; // Exit early if found - } - - + const rewardShares = await getRewardShares(myAddress); + const findRewardShare = rewardShares?.find( + (item) => + item?.recipient === myAddress && item?.mintingAccount === myAddress + ); + + if (findRewardShare) { + return true; // Exit early if found + } + await sleep(pollingInterval); // Wait before the next poll } - - throw new Error("Timeout waiting for reward share confirmation"); + + throw new Error('Timeout waiting for reward share confirmation'); }; const startMinting = async () => { @@ -377,23 +375,22 @@ export const Minting = ({ addMintingAccount(privateRewardShare); } else { await createRewardShare(accountInfo?.publicKey, myAddress); - setShowWaitDialog(true) - await waitUntilRewardShareIsConfirmed() + setShowWaitDialog(true); + await waitUntilRewardShareIsConfirmed(); await showNext({ - message: '' - }) + message: '', + }); const privateRewardShare = await getRewardSharePrivateKey( accountInfo?.publicKey ); - setShowWaitDialog(false) + setShowWaitDialog(false); addMintingAccount(privateRewardShare); - } } catch (error) { - setShowWaitDialog(false) + setShowWaitDialog(false); setInfo({ - type: "error", - message: error?.message || "Unable to start minting", + type: 'error', + message: error?.message || 'Unable to start minting', }); setOpenSnack(true); } finally { @@ -412,13 +409,13 @@ export const Minting = ({ const url = `${getBaseApiReact()}/groups/member/${address}`; const response = await fetch(url); const data = await response.json(); - return !!data?.find((grp) => grp?.groupId?.toString() === "694"); + return !!data?.find((grp) => grp?.groupId?.toString() === '694'); }; const removeRewardShare = useCallback(async (rewardShare) => { return await new Promise((res, rej) => { window - .sendMessage("removeRewardShare", { + .sendMessage('removeRewardShare', { rewardShareKeyPairPublicKey: rewardShare.rewardSharePublicKey, recipient: rewardShare.recipient, percentageShare: -1, @@ -430,7 +427,7 @@ export const Minting = ({ { ...rewardShare, ...response, - type: "remove-rewardShare", + type: 'remove-rewardShare', label: `Remove rewardshare: awaiting confirmation`, labelDone: `Remove rewardshare: success!`, done: false, @@ -442,7 +439,7 @@ export const Minting = ({ rej({ message: response.error }); }) .catch((error) => { - rej({ message: error.message || "An error occurred" }); + rej({ message: error.message || 'An error occurred' }); }); }); }, []); @@ -454,8 +451,8 @@ export const Minting = ({ const privateRewardShare = await removeRewardShare(rewardShare); } catch (error) { setInfo({ - type: "error", - message: error?.message || "Unable to remove reward share", + type: 'error', + message: error?.message || 'Unable to remove reward share', }); setOpenSnack(true); } finally { @@ -468,9 +465,9 @@ export const Minting = ({ setIsLoading(true); const confirmReceiver = await getNameOrAddress(receiver); if (confirmReceiver.error) - throw new Error("Invalid receiver address or name"); + throw new Error('Invalid receiver address or name'); const isInMinterGroup = await checkIfMinterGroup(confirmReceiver); - if (!isInMinterGroup) throw new Error("Account not in Minter Group"); + if (!isInMinterGroup) throw new Error('Account not in Minter Group'); const publicKey = await getPublicKeyFromAddress(confirmReceiver); const findRewardShare = rewardShares?.find( (item) => @@ -487,8 +484,8 @@ export const Minting = ({ } } catch (error) { setInfo({ - type: "error", - message: error?.message || "Unable to create reward share", + type: 'error', + message: error?.message || 'Unable to create reward share', }); setOpenSnack(true); } finally { @@ -550,11 +547,9 @@ export const Minting = ({ (accountInfo?.blocksMinted + accountInfo?.blocksMintedAdjustment); let countBlocksString = countBlocks.toString(); - return "" + countBlocksString; + return '' + countBlocksString; }; - - return ( - {"Manage your minting"} + {'Manage your minting'} {isLoading && ( Account: {handleNames(accountInfo?.address)} @@ -631,11 +626,11 @@ export const Minting = ({ {isPartOfMintingGroup && !accountIsMinting && ( - - )} @@ -837,7 +835,7 @@ export const Minting = ({ {info?.message} diff --git a/src/components/QortPayment.tsx b/src/components/QortPayment.tsx index 6add9b2..ec68533 100644 --- a/src/components/QortPayment.tsx +++ b/src/components/QortPayment.tsx @@ -1,167 +1,170 @@ import { Box, CircularProgress } from '@mui/material'; -import React, { useEffect, useState } from 'react' -import { CustomButton, CustomInput, CustomLabel, TextP } from '../App-styles'; +import React, { useEffect, useState } from 'react'; +import { + CustomButton, + CustomInput, + CustomLabel, + TextP, +} from '../styles/App-styles'; import { Spacer } from '../common/Spacer'; import BoundedNumericTextField from '../common/BoundedNumericTextField'; import { PasswordField } from './PasswordField/PasswordField'; import { ErrorText } from './ErrorText/ErrorText'; import { getFee } from '../background'; -export const QortPayment = ({balance, show, onSuccess, defaultPaymentTo}) => { - const [paymentTo, setPaymentTo] = useState(defaultPaymentTo); - const [paymentAmount, setPaymentAmount] = useState(0); - const [paymentPassword, setPaymentPassword] = useState(""); - const [sendPaymentError, setSendPaymentError] = useState(""); - const [sendPaymentSuccess, setSendPaymentSuccess] = useState(""); - const [isLoadingSendCoin, setIsLoadingSendCoin] = useState(false); +export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => { + const [paymentTo, setPaymentTo] = useState(defaultPaymentTo); + const [paymentAmount, setPaymentAmount] = useState(0); + const [paymentPassword, setPaymentPassword] = useState(''); + const [sendPaymentError, setSendPaymentError] = useState(''); + const [sendPaymentSuccess, setSendPaymentSuccess] = useState(''); + const [isLoadingSendCoin, setIsLoadingSendCoin] = useState(false); + const sendCoinFunc = async () => { + try { + setSendPaymentError(''); + setSendPaymentSuccess(''); + if (!paymentTo) { + setSendPaymentError('Please enter a recipient'); + return; + } + if (!paymentAmount) { + setSendPaymentError('Please enter an amount greater than 0'); + return; + } + if (!paymentPassword) { + setSendPaymentError('Please enter your wallet password'); + return; + } + const fee = await getFee('PAYMENT'); - - const sendCoinFunc = async() => { - try { - setSendPaymentError(""); - setSendPaymentSuccess(""); - if (!paymentTo) { - setSendPaymentError("Please enter a recipient"); - return; + await show({ + message: `Would you like to transfer ${Number(paymentAmount)} QORT?`, + paymentFee: fee.fee + ' QORT', + }); + setIsLoadingSendCoin(true); + window + .sendMessage('sendCoin', { + amount: Number(paymentAmount), + receiver: paymentTo.trim(), + password: paymentPassword, + }) + .then((response) => { + if (response?.error) { + setSendPaymentError(response.error); + } else { + onSuccess(); } - if (!paymentAmount) { - setSendPaymentError("Please enter an amount greater than 0"); - return; - } - if (!paymentPassword) { - setSendPaymentError("Please enter your wallet password"); - return; - } - const fee = await getFee('PAYMENT') - - await show({ - message: `Would you like to transfer ${Number(paymentAmount)} QORT?` , - paymentFee: fee.fee + ' QORT' - }) - setIsLoadingSendCoin(true); - window - .sendMessage("sendCoin", { - amount: Number(paymentAmount), - receiver: paymentTo.trim(), - password: paymentPassword, - }) - .then((response) => { - if (response?.error) { - setSendPaymentError(response.error); - } else { - onSuccess() - - } - setIsLoadingSendCoin(false); - }) - .catch((error) => { - console.error("Failed to send coin:", error); - setIsLoadingSendCoin(false); - }); - } catch (error) { - // error - } - }; + setIsLoadingSendCoin(false); + }) + .catch((error) => { + console.error('Failed to send coin:', error); + setIsLoadingSendCoin(false); + }); + } catch (error) { + // error + } + }; return ( <> - - Transfer QORT - - - - Balance: - - - {balance?.toFixed(2)} QORT - - - + sx={{ + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + }} + > + + Transfer QORT + + + + Balance: + + + {balance?.toFixed(2)} QORT + + + - - To - - setPaymentTo(e.target.value)} - autoComplete="off" - /> - - - Amount - - - setPaymentAmount(+e)} - /> - - - Confirm Wallet Password - - - setPaymentPassword(e.target.value)} - autoComplete="off" - /> - - - {sendPaymentError} - {/* {sendPaymentSuccess} */} - - + To + + setPaymentTo(e.target.value)} + autoComplete="off" + /> + + Amount + + setPaymentAmount(+e)} + /> + + + Confirm Wallet Password + + + setPaymentPassword(e.target.value)} + autoComplete="off" + /> +
+ + {sendPaymentError} + {/* {sendPaymentSuccess} */} + + { + if (isLoadingSendCoin) return; + sendCoinFunc(); + }} + > + {isLoadingSendCoin && ( + { - if(isLoadingSendCoin) return - sendCoinFunc(); - }} - > - {isLoadingSendCoin && ( - - )} - Send - + /> + )} + Send + - ) -} + ); +}; diff --git a/src/components/Theme/ThemeContext.tsx b/src/components/Theme/ThemeContext.tsx new file mode 100644 index 0000000..e64c183 --- /dev/null +++ b/src/components/Theme/ThemeContext.tsx @@ -0,0 +1,29 @@ +import { createContext, useContext, useState, useMemo } from 'react'; +import { ThemeProvider as MuiThemeProvider } from '@mui/material/styles'; +import { darkTheme, lightTheme } from '../../styles/theme'; + +const ThemeContext = createContext({ + themeMode: 'light', + toggleTheme: () => {}, +}); + +export const ThemeProvider = ({ children }: { children: React.ReactNode }) => { + const [themeMode, setThemeMode] = useState('light'); + + const theme = useMemo( + () => (themeMode === 'light' ? lightTheme : darkTheme), + [themeMode] + ); + + const toggleTheme = () => { + setThemeMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light')); + }; + + return ( + + {children} + + ); +}; + +export const useThemeContext = () => useContext(ThemeContext); diff --git a/src/components/Theme/ThemeSelector.tsx b/src/components/Theme/ThemeSelector.tsx new file mode 100644 index 0000000..5b55f53 --- /dev/null +++ b/src/components/Theme/ThemeSelector.tsx @@ -0,0 +1,77 @@ +import { useThemeContext } from "./ThemeContext"; +import { styled, Switch } from "@mui/material"; + +const ThemeSwitch = styled(Switch)(({ theme }) => ({ + width: 62, + height: 34, + padding: 7, + "& .MuiSwitch-switchBase": { + margin: 1, + padding: 0, + transform: "translateX(6px)", + "&.Mui-checked": { + color: "#fff", + transform: "translateX(22px)", + "& .MuiSwitch-thumb:before": { + backgroundImage: `url('data:image/svg+xml;utf8,')`, + }, + "& + .MuiSwitch-track": { + opacity: 1, + backgroundColor: "#aab4be", + ...theme.applyStyles("dark", { + backgroundColor: "#8796A5", + }), + }, + }, + }, + "& .MuiSwitch-thumb": { + backgroundColor: "#001e3c", + width: 32, + height: 32, + "&::before": { + content: "''", + position: "absolute", + width: "100%", + height: "100%", + left: 0, + top: 0, + backgroundRepeat: "no-repeat", + backgroundPosition: "center", + backgroundImage: `url('data:image/svg+xml;utf8,')`, + }, + ...theme.applyStyles("dark", { + backgroundColor: "#003892", + }), + }, + "& .MuiSwitch-track": { + opacity: 1, + backgroundColor: "#aab4be", + borderRadius: 20 / 2, + ...theme.applyStyles("dark", { + backgroundColor: "#8796A5", + }), + }, +})); + +const ThemeSelector = ({ style }) => { + const { themeMode, toggleTheme } = useThemeContext(); + return ( +
+ +
+ ); +}; + +export default ThemeSelector;