+
{({ height, width }) => (
{
+export const CreateCommonSecret = ({groupId, secretKey, isOwner, myAddress, secretKeyDetails, userInfo, noSecretKey, setHideCommonKeyPopup}) => {
const { show, setTxList } = useContext(MyContext);
const [openSnack, setOpenSnack] = React.useState(false);
@@ -18,9 +18,9 @@ export const CreateCommonSecret = ({groupId, secretKey, isOwner, myAddress, sec
const getPublishesFromAdmins = async (admins: string[]) => {
// const validApi = await findUsableApi();
const queryString = admins.map((name) => `name=${name}`).join("&");
- const url = `${getBaseApiReact()}/arbitrary/resources/search?mode=ALL&service=DOCUMENT_PRIVATE&identifier=symmetric-qchat-group-${
+ const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT_PRIVATE&identifier=symmetric-qchat-group-${
groupId
- }&exactmatchnames=true&limit=0&reverse=true&${queryString}`;
+ }&exactmatchnames=true&limit=0&reverse=true&${queryString}&prefix=true`;
const response = await fetch(url);
if(!response.ok){
throw new Error('network error')
@@ -51,11 +51,11 @@ export const CreateCommonSecret = ({groupId, secretKey, isOwner, myAddress, sec
- const groupAdmins = await getGroupAdimns(groupId);
- if(!groupAdmins.length){
+ const {names} = await getGroupAdimns(groupId);
+ if(!names.length){
throw new Error('Network error')
}
- const publish = await getPublishesFromAdmins(groupAdmins);
+ const publish = await getPublishesFromAdmins(names);
if (publish === false) {
@@ -107,7 +107,7 @@ export const CreateCommonSecret = ({groupId, secretKey, isOwner, myAddress, sec
const secretKeyToSend = !secretKey2 ? null : secretKey2
- chrome.runtime.sendMessage({ action: "encryptAndPublishSymmetricKeyGroupChat", payload: {
+ chrome?.runtime?.sendMessage({ action: "encryptAndPublishSymmetricKeyGroupChat", payload: {
groupId: groupId,
previousData: secretKeyToSend
} }, (response) => {
@@ -142,7 +142,7 @@ export const CreateCommonSecret = ({groupId, secretKey, isOwner, myAddress, sec
flexDirection: 'column',
gap: '25px',
maxWidth: '350px',
- background: '#4444'
+ background: '#444444'
}}>
Re-encyrpt key
{noSecretKey ? (
@@ -158,6 +158,15 @@ export const CreateCommonSecret = ({groupId, secretKey, isOwner, myAddress, sec
The group member list has changed. Please re-encrypt the secret key.
)}
+
+ {
+ setHideCommonKeyPopup(true)
+ }} size='small'>Hide
+
diff --git a/src/components/Chat/GroupAnnouncements.tsx b/src/components/Chat/GroupAnnouncements.tsx
index 9f8b91f..dee3494 100644
--- a/src/components/Chat/GroupAnnouncements.tsx
+++ b/src/components/Chat/GroupAnnouncements.tsx
@@ -25,21 +25,30 @@ 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 CampaignIcon from "@mui/icons-material/Campaign";
+import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import { AnnouncementDiscussion } from "./AnnouncementDiscussion";
-import { MyContext, getBaseApiReact, pauseAllQueues, resumeAllQueues } from "../../App";
+import {
+ MyContext,
+ getArbitraryEndpointReact,
+ getBaseApiReact,
+ 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";
-export const requestQueueCommentCount = new RequestQueueWithPromise(3)
-export const requestQueuePublishedAccouncements = new RequestQueueWithPromise(3)
+export const requestQueueCommentCount = new RequestQueueWithPromise(3);
+export const requestQueuePublishedAccouncements = new RequestQueueWithPromise(
+ 3
+);
export const saveTempPublish = async ({ data, key }: any) => {
-
-
return new Promise((res, rej) => {
- chrome.runtime.sendMessage(
+ chrome?.runtime?.sendMessage(
{
action: "saveTempPublish",
payload: {
@@ -48,9 +57,9 @@ export const saveTempPublish = async ({ data, key }: any) => {
},
},
(response) => {
-
if (!response?.error) {
res(response);
+ return;
}
rej(response.error);
}
@@ -59,18 +68,16 @@ export const saveTempPublish = async ({ data, key }: any) => {
};
export const getTempPublish = async () => {
-
-
return new Promise((res, rej) => {
- chrome.runtime.sendMessage(
+ chrome?.runtime?.sendMessage(
{
action: "getTempPublish",
- payload: {
- },
+ payload: {},
},
(response) => {
if (!response?.error) {
res(response);
+ return;
}
rej(response.error);
}
@@ -81,7 +88,7 @@ export const getTempPublish = async () => {
export const decryptPublishes = async (encryptedMessages: any[], secretKey) => {
try {
return await new Promise((res, rej) => {
- chrome.runtime.sendMessage(
+ chrome?.runtime?.sendMessage(
{
action: "decryptSingleForPublishes",
payload: {
@@ -91,7 +98,6 @@ export const decryptPublishes = async (encryptedMessages: any[], secretKey) => {
},
},
(response) => {
-
if (!response?.error) {
res(response);
// if(hasInitialized.current){
@@ -126,37 +132,62 @@ export const GroupAnnouncements = ({
handleNewEncryptionNotification,
isAdmin,
hide,
- myName
+ myName,
}) => {
const [messages, setMessages] = useState([]);
const [isSending, setIsSending] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [announcements, setAnnouncements] = useState([]);
- const [tempPublishedList, setTempPublishedList] = useState([])
+ const [tempPublishedList, setTempPublishedList] = useState([]);
const [announcementData, setAnnouncementData] = useState({});
const [selectedAnnouncement, setSelectedAnnouncement] = useState(null);
- const { show } = React.useContext(MyContext);
+ const [isFocusedParent, setIsFocusedParent] = useState(false);
+
+ const { show, rootHeight } = React.useContext(MyContext);
const [openSnack, setOpenSnack] = React.useState(false);
const [infoSnack, setInfoSnack] = React.useState(null);
const hasInitialized = useRef(false);
const hasInitializedWebsocket = useRef(false);
const editorRef = useRef(null);
-
+ const dataPublishes = useRef({});
const setEditorRef = (editorInstance) => {
editorRef.current = editorInstance;
};
+ const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
- const getAnnouncementData = async ({ identifier, name }) => {
+ const triggerRerender = () => {
+ forceUpdate(); // Trigger re-render by updating the state
+ };
+ useEffect(() => {
+ if (!selectedGroup) return;
+ (async () => {
+ const res = await getDataPublishesFunc(selectedGroup, "anc");
+ dataPublishes.current = res || {};
+ })();
+ }, [selectedGroup]);
+
+ const getAnnouncementData = async ({ identifier, name, resource }) => {
try {
-
- const res = await requestQueuePublishedAccouncements.enqueue(()=> {
- return fetch(
- `${getBaseApiReact()}/arbitrary/DOCUMENT/${name}/${identifier}?encoding=base64`
- );
- })
- const data = await res.text();
+ let data = dataPublishes.current[`${name}-${identifier}`];
+ if (
+ !data ||
+ data?.update ||
+ data?.created !== (resource?.updated || resource?.created)
+ ) {
+ const res = await requestQueuePublishedAccouncements.enqueue(() => {
+ return fetch(
+ `${getBaseApiReact()}/arbitrary/DOCUMENT/${name}/${identifier}?encoding=base64`
+ );
+ });
+ if (!res?.ok) return;
+ data = await res.text();
+ await addDataPublishesFunc({ ...resource, data }, selectedGroup, "anc");
+ } else {
+ data = data.data;
+ }
+
const response = await decryptPublishes([{ data }], secretKey);
-
+
const messageData = response[0];
setAnnouncementData((prev) => {
return {
@@ -164,14 +195,11 @@ export const GroupAnnouncements = ({
[`${identifier}-${name}`]: messageData,
};
});
-
- } catch (error) {}
+ } catch (error) {
+ console.log("error", error);
+ }
};
-
-
-
-
useEffect(() => {
if (!secretKey || hasInitializedWebsocket.current) return;
setIsLoading(true);
@@ -182,7 +210,7 @@ export const GroupAnnouncements = ({
const encryptChatMessage = async (data: string, secretKeyObject: any) => {
try {
return new Promise((res, rej) => {
- chrome.runtime.sendMessage(
+ chrome?.runtime?.sendMessage(
{
action: "encryptSingle",
payload: {
@@ -203,58 +231,61 @@ export const GroupAnnouncements = ({
};
const publishAnc = async ({ encryptedData, identifier }: any) => {
-
-
- return new Promise((res, rej) => {
- chrome.runtime.sendMessage(
- {
- action: "publishGroupEncryptedResource",
- payload: {
- encryptedData,
- identifier,
- },
+ return new Promise((res, rej) => {
+ chrome?.runtime?.sendMessage(
+ {
+ action: "publishGroupEncryptedResource",
+ payload: {
+ encryptedData,
+ identifier,
},
- (response) => {
- if (!response?.error) {
- res(response);
- }
- rej(response.error);
+ },
+ (response) => {
+ if (!response?.error) {
+ res(response);
}
- );
- });
+ rej(response.error);
+ }
+ );
+ });
};
const clearEditorContent = () => {
if (editorRef.current) {
editorRef.current.chain().focus().clearContent().run();
+ if (isMobile) {
+ setTimeout(() => {
+ editorRef.current?.chain().blur().run();
+ setIsFocusedParent(false);
+ setTimeout(() => {
+ triggerRerender();
+ }, 300);
+ }, 200);
+ }
}
};
- const setTempData = async ()=> {
+ const setTempData = async () => {
try {
- const getTempAnnouncements = await getTempPublish()
- if(getTempAnnouncements?.announcement){
- let tempData = []
- Object.keys(getTempAnnouncements?.announcement || {}).map((key)=> {
- const value = getTempAnnouncements?.announcement[key]
- tempData.push(value.data)
- })
- setTempPublishedList(tempData)
- }
- } catch (error) {
-
- }
-
- }
+ const getTempAnnouncements = await getTempPublish();
+ if (getTempAnnouncements?.announcement) {
+ let tempData = [];
+ Object.keys(getTempAnnouncements?.announcement || {}).map((key) => {
+ const value = getTempAnnouncements?.announcement[key];
+ tempData.push(value.data);
+ });
+ setTempPublishedList(tempData);
+ }
+ } catch (error) {}
+ };
const publishAnnouncement = 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();
@@ -263,8 +294,8 @@ export const GroupAnnouncements = ({
const message = {
version: 1,
extra: {},
- message: htmlContent
- }
+ message: htmlContent,
+ };
const secretKeyObject = await getSecretKey(false, true);
const message64: any = await objectToBase64(message);
const encryptSingle = await encryptChatMessage(
@@ -272,38 +303,40 @@ export const GroupAnnouncements = ({
secretKeyObject
);
const randomUid = uid.rnd();
- const identifier = `grp-${selectedGroup}-anc-${randomUid}`;
+ const identifier = `grp-${selectedGroup}-anc-${randomUid}`;
const res = await publishAnc({
encryptedData: encryptSingle,
- identifier
+ identifier,
});
const dataToSaveToStorage = {
name: myName,
identifier,
- service: 'DOCUMENT',
+ service: "DOCUMENT",
tempData: message,
- created: Date.now()
- }
- await saveTempPublish({data: dataToSaveToStorage, key: 'announcement'})
- setTempData()
+ created: Date.now(),
+ };
+ await saveTempPublish({
+ data: dataToSaveToStorage,
+ key: "announcement",
+ });
+ setTempData();
clearEditorContent();
}
// send chat message
} catch (error) {
+ if (!error) return;
setInfoSnack({
type: "error",
message: error,
});
- setOpenSnack(true)
+ setOpenSnack(true);
} finally {
- resumeAllQueues()
+ resumeAllQueues();
setIsSending(false);
}
};
-
-
const getAnnouncements = React.useCallback(
async (selectedGroup) => {
try {
@@ -311,7 +344,7 @@ export const GroupAnnouncements = ({
// dispatch(setIsLoadingGlobal(true))
const identifier = `grp-${selectedGroup}-anc-`;
- const url = `${getBaseApiReact()}/arbitrary/resources/search?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=20&includemetadata=false&offset=${offset}&reverse=true&prefix=true`;
+ 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: {
@@ -319,12 +352,16 @@ export const GroupAnnouncements = ({
},
});
const responseData = await response.json();
-
- setTempData()
+
+ setTempData();
setAnnouncements(responseData);
setIsLoading(false);
for (const data of responseData) {
- getAnnouncementData({ name: data.name, identifier: data.identifier });
+ getAnnouncementData({
+ name: data.name,
+ identifier: data.identifier,
+ resource: data,
+ });
}
} catch (error) {
} finally {
@@ -333,196 +370,206 @@ export const GroupAnnouncements = ({
},
[secretKey]
);
-
+
React.useEffect(() => {
- if (selectedGroup && secretKey && !hasInitialized.current) {
+ if (selectedGroup && secretKey && !hasInitialized.current && !hide) {
getAnnouncements(selectedGroup);
- hasInitialized.current = true
+ hasInitialized.current = true;
}
- }, [selectedGroup, secretKey]);
+ }, [selectedGroup, secretKey, hide]);
-
- const loadMore = async()=> {
+ const loadMore = async () => {
try {
setIsLoading(true);
- const offset = announcements.length
- const identifier = `grp-${selectedGroup}-anc-`;
- const url = `${getBaseApiReact()}/arbitrary/resources/search?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 offset = announcements.length;
+ 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",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+ const responseData = await response.json();
- setAnnouncements((prev)=> [...prev, ...responseData]);
- setIsLoading(false);
+ setAnnouncements((prev) => [...prev, ...responseData]);
+ setIsLoading(false);
+ for (const data of responseData) {
+ getAnnouncementData({ name: data.name, identifier: data.identifier });
+ }
+ } catch (error) {}
+ };
+
+ const interval = useRef(null);
+
+ const checkNewMessages = React.useCallback(async () => {
+ try {
+ 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",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+ const responseData = await response.json();
+ const latestMessage = announcements[0];
+ if (!latestMessage) {
for (const data of responseData) {
- getAnnouncementData({ name: data.name, identifier: data.identifier });
- }
- } catch (error) {
-
- }
-
- }
-
- const interval = useRef(null)
-
- const checkNewMessages = React.useCallback(
- async () => {
- try {
-
- const identifier = `grp-${selectedGroup}-anc-`;
- const url = `${getBaseApiReact()}/arbitrary/resources/search?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=20&includemetadata=false&offset=${0}&reverse=true&prefix=true`;
- const response = await fetch(url, {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json'
- }
- })
- const responseData = await response.json()
- const latestMessage = announcements[0]
- if (!latestMessage) {
- for (const data of responseData) {
- try {
-
- getAnnouncementData({ name: data.name, identifier: data.identifier });
-
- } catch (error) {}
- }
- setAnnouncements(responseData)
- return
- }
- const findMessage = responseData?.findIndex(
- (item: any) => item?.identifier === latestMessage?.identifier
- )
-
- if(findMessage === -1) return
- const newArray = responseData.slice(0, findMessage)
-
- for (const data of newArray) {
try {
-
- getAnnouncementData({ name: data.name, identifier: data.identifier });
-
+ getAnnouncementData({
+ name: data.name,
+ identifier: data.identifier,
+ });
} catch (error) {}
}
- setAnnouncements((prev)=> [...newArray, ...prev])
- } catch (error) {
- } finally {
+ setAnnouncements(responseData);
+ return;
}
- },
- [announcements, secretKey, selectedGroup]
- )
+ const findMessage = responseData?.findIndex(
+ (item: any) => item?.identifier === latestMessage?.identifier
+ );
+
+ if (findMessage === -1) return;
+ const newArray = responseData.slice(0, findMessage);
+
+ for (const data of newArray) {
+ try {
+ getAnnouncementData({ name: data.name, identifier: data.identifier });
+ } catch (error) {}
+ }
+ setAnnouncements((prev) => [...newArray, ...prev]);
+ } catch (error) {
+ } finally {
+ }
+ }, [announcements, secretKey, selectedGroup]);
const checkNewMessagesFunc = useCallback(() => {
- let isCalling = false
+ let isCalling = false;
interval.current = setInterval(async () => {
- if (isCalling) return
- isCalling = true
- const res = await checkNewMessages()
- isCalling = false
- }, 20000)
- }, [checkNewMessages])
+ if (isCalling) return;
+ isCalling = true;
+ const res = await checkNewMessages();
+ isCalling = false;
+ }, 20000);
+ }, [checkNewMessages]);
useEffect(() => {
- if(!secretKey) return
- checkNewMessagesFunc()
+ if (!secretKey || hide) return;
+ checkNewMessagesFunc();
return () => {
if (interval?.current) {
- clearInterval(interval.current)
+ clearInterval(interval.current);
}
- }
- }, [checkNewMessagesFunc])
-
-
+ };
+ }, [checkNewMessagesFunc, hide]);
const combinedListTempAndReal = useMemo(() => {
// Combine the two lists
const combined = [...tempPublishedList, ...announcements];
-
+
// 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, announcements]);
-
- if(selectedAnnouncement){
+ if (selectedAnnouncement) {
return (
-
+ style={{
+ // reference to change height
+ height: isMobile ? `calc(${rootHeight} - 127px` : "calc(100vh - 70px)",
+ display: "flex",
+ flexDirection: "column",
+ width: "100%",
+ visibility: hide && "hidden",
+ position: hide && "fixed",
+ left: hide && "-1000px",
+ }}
+ >
+
- )
+ );
}
-
-
return (
-
-
-
- Group Announcements
-
-
+ {!isMobile && (
+
+
+ Group Announcements
+
+ )}
+
{!isLoading && combinedListTempAndReal?.length === 0 && (
-
- No announcements
+
+
+ No announcements
+
)}
0 && announcements.length % 20 === 0}
+ showLoadMore={
+ announcements.length > 0 && announcements.length % 20 === 0
+ }
loadMore={loadMore}
+ myName={myName}
/>
-
-
-{isAdmin && (
-
-
-
-
-
{
- if (isSending) return;
- publishAnnouncement();
- }}
- style={{
- marginTop: "auto",
- alignSelf: "center",
- cursor: isSending ? "default" : "pointer",
- background: isSending && "rgba(0, 0, 0, 0.8)",
- flexShrink: 0,
- }}
- >
- {isSending && (
-
- )}
- {` Publish Announcement`}
-
-
-)}
-
+ {isAdmin && (
+
+
+
+
+
+ {isFocusedParent && (
+ {
+ if (isSending) return;
+ setIsFocusedParent(false);
+ clearEditorContent();
+ setTimeout(() => {
+ triggerRerender();
+ }, 300);
+ // Unfocus the editor
+ }}
+ style={{
+ marginTop: "auto",
+ alignSelf: "center",
+ cursor: isSending ? "default" : "pointer",
+ background: "red",
+ flexShrink: 0,
+ padding: isMobile && "5px",
+ fontSize: isMobile && "14px",
+ }}
+ >
+ {` Close`}
+
+ )}
+ {
+ if (isSending) return;
+ publishAnnouncement();
+ }}
+ 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 Announcement`}
+
+
+
+ )}
+
+
{
+ const { rootHeight } = useContext(MyContext);
const [isMoved, setIsMoved] = useState(false);
useEffect(() => {
if (hide) {
@@ -35,7 +39,8 @@ export const GroupForum = ({
return (
-
+
);
diff --git a/src/components/Chat/MessageDisplay.tsx b/src/components/Chat/MessageDisplay.tsx
index 885f986..f4cdce7 100644
--- a/src/components/Chat/MessageDisplay.tsx
+++ b/src/components/Chat/MessageDisplay.tsx
@@ -2,7 +2,7 @@ import React, { useEffect } from 'react';
import DOMPurify from 'dompurify';
import './styles.css'; // Ensure this CSS file is imported
-export const MessageDisplay = ({ htmlContent }) => {
+export const MessageDisplay = ({ htmlContent , isReply}) => {
const linkify = (text) => {
// Regular expression to find URLs starting with https://, http://, or www.
@@ -53,7 +53,7 @@ export const MessageDisplay = ({ htmlContent }) => {
};
return (
{
// Delegate click handling to the parent div
diff --git a/src/components/Chat/MessageItem.tsx b/src/components/Chat/MessageItem.tsx
index b53ce75..7df771c 100644
--- a/src/components/Chat/MessageItem.tsx
+++ b/src/components/Chat/MessageItem.tsx
@@ -2,13 +2,30 @@ import { Message } from "@chatscope/chat-ui-kit-react";
import React, { useEffect } from "react";
import { useInView } from "react-intersection-observer";
import { MessageDisplay } from "./MessageDisplay";
-import { Avatar, Box, Typography } from "@mui/material";
+import { Avatar, Box, ButtonBase, Typography } from "@mui/material";
import { formatTimestamp } from "../../utils/time";
import { getBaseApi } from "../../background";
import { getBaseApiReact } from "../../App";
+import { generateHTML } from "@tiptap/react";
+import Highlight from "@tiptap/extension-highlight";
+import StarterKit from "@tiptap/starter-kit";
+import Underline from "@tiptap/extension-underline";
+import { executeEvent } from "../../utils/events";
+import { WrapperUserAction } from "../WrapperUserAction";
+import ReplyIcon from "@mui/icons-material/Reply";
-export const MessageItem = ({ message, onSeen, isLast, isTemp }) => {
-
+export const MessageItem = ({
+ message,
+ onSeen,
+ isLast,
+ isTemp,
+ myAddress,
+ onReply,
+ isShowingAsReply,
+ reply,
+ replyIndex,
+ scrollToItem
+}) => {
const { ref, inView } = useInView({
threshold: 0.7, // Fully visible
triggerOnce: true, // Only trigger once when it becomes visible
@@ -29,62 +46,169 @@ export const MessageItem = ({ message, onSeen, isLast, isTemp }) => {
borderRadius: "7px",
width: "95%",
display: "flex",
- gap: '7px',
- opacity: isTemp ? 0.5 : 1
+ gap: "7px",
+ opacity: isTemp ? 0.5 : 1,
}}
+ id={message?.signature}
>
-
- {message?.senderName?.charAt(0)}
-
+ {isShowingAsReply ? (
+
+ ) : (
+
+
+ {message?.senderName?.charAt(0)}
+
+
+ )}
+
-
- {message?.senderName || message?.sender}
-
+
+
+ {message?.senderName || message?.sender}
+
+
+ {!isShowingAsReply && (
+ {
+ onReply(message);
+ }}
+ >
+
+
+ )}
+
+ {reply && (
+
{
+ scrollToItem(replyIndex)
+
+
+ }}
+ >
+
+
+ Replied to {reply?.senderName || reply?.senderAddress}
+ {reply?.messageText && (
+
+ )}
+ {reply?.text?.type === "notification" ? (
+
+ ) : (
+
+ )}
+
+
+ )}
+ {message?.messageText && (
+
+ )}
{message?.text?.type === "notification" ? (
) : (
)}
-
+
{isTemp ? (
- Sending...
- ): (
- {formatTimestamp(message.timestamp)}
- ) }
-
+
+ Sending...
+
+ ) : (
+
+ {formatTimestamp(message.timestamp)}
+
+ )}
@@ -102,3 +226,51 @@ export const MessageItem = ({ message, onSeen, isLast, isTemp }) => {
);
};
+
+
+export const ReplyPreview = ({message})=> {
+
+ return (
+
+
+
+ Replied to {message?.senderName || message?.senderAddress}
+ {message?.messageText && (
+
+ )}
+ {message?.text?.type === "notification" ? (
+
+ ) : (
+
+ )}
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/Chat/TipTap.tsx b/src/components/Chat/TipTap.tsx
index 4bdf3c6..be6abff 100644
--- a/src/components/Chat/TipTap.tsx
+++ b/src/components/Chat/TipTap.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useRef } from 'react';
+import React, { useEffect, useRef, useState } from 'react';
import { EditorProvider, useCurrentEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { Color } from '@tiptap/extension-color';
@@ -25,6 +25,7 @@ import CustomImage from './CustomImage';
import Compressor from 'compressorjs'
import ImageResize from 'tiptap-extension-resize-image'; // Import the ResizeImage extension
+import { isMobile } from '../../App';
const MenuBar = ({ setEditorRef, isChat }) => {
const { editor } = useCurrentEditor();
const fileInputRef = useRef(null);
@@ -88,10 +89,11 @@ const MenuBar = ({ setEditorRef, isChat }) => {
}
// color={editor.isActive('bold') ? 'white' : 'gray'}
sx={{
- color: editor.isActive('bold') ? 'white' : 'gray'
+ color: editor.isActive('bold') ? 'white' : 'gray',
+ padding: isMobile ? '5px' : 'revert'
}}
>
-
+
editor.chain().focus().toggleItalic().run()}
@@ -104,7 +106,8 @@ const MenuBar = ({ setEditorRef, isChat }) => {
}
// color={editor.isActive('italic') ? 'white' : 'gray'}
sx={{
- color: editor.isActive('italic') ? 'white' : 'gray'
+ color: editor.isActive('italic') ? 'white' : 'gray',
+ padding: isMobile ? '5px' : 'revert'
}}
>
@@ -120,7 +123,8 @@ const MenuBar = ({ setEditorRef, isChat }) => {
}
// color={editor.isActive('strike') ? 'white' : 'gray'}
sx={{
- color: editor.isActive('strike') ? 'white' : 'gray'
+ color: editor.isActive('strike') ? 'white' : 'gray',
+ padding: isMobile ? '5px' : 'revert'
}}
>
@@ -136,19 +140,23 @@ const MenuBar = ({ setEditorRef, isChat }) => {
}
// color={editor.isActive('code') ? 'white' : 'gray'}
sx={{
- color: editor.isActive('code') ? 'white' : 'gray'
+ color: editor.isActive('code') ? 'white' : 'gray',
+ padding: isMobile ? '5px' : 'revert'
}}
>
- editor.chain().focus().unsetAllMarks().run()}>
+ editor.chain().focus().unsetAllMarks().run()}>
editor.chain().focus().toggleBulletList().run()}
// color={editor.isActive('bulletList') ? 'white' : 'gray'}
sx={{
- color: editor.isActive('bulletList') ? 'white' : 'gray'
+ color: editor.isActive('bulletList') ? 'white' : 'gray',
+ padding: isMobile ? '5px' : 'revert'
}}
>
@@ -157,7 +165,8 @@ const MenuBar = ({ setEditorRef, isChat }) => {
onClick={() => editor.chain().focus().toggleOrderedList().run()}
// color={editor.isActive('orderedList') ? 'white' : 'gray'}
sx={{
- color: editor.isActive('orderedList') ? 'white' : 'gray'
+ color: editor.isActive('orderedList') ? 'white' : 'gray',
+ padding: isMobile ? '5px' : 'revert'
}}
>
@@ -166,7 +175,8 @@ const MenuBar = ({ setEditorRef, isChat }) => {
onClick={() => editor.chain().focus().toggleCodeBlock().run()}
// color={editor.isActive('codeBlock') ? 'white' : 'gray'}
sx={{
- color: editor.isActive('codeBlock') ? 'white' : 'gray'
+ color: editor.isActive('codeBlock') ? 'white' : 'gray',
+ padding: isMobile ? '5px' : 'revert'
}}
>
@@ -175,7 +185,8 @@ const MenuBar = ({ setEditorRef, isChat }) => {
onClick={() => editor.chain().focus().toggleBlockquote().run()}
// color={editor.isActive('blockquote') ? 'white' : 'gray'}
sx={{
- color: editor.isActive('blockquote') ? 'white' : 'gray'
+ color: editor.isActive('blockquote') ? 'white' : 'gray',
+ padding: isMobile ? '5px' : 'revert'
}}
>
@@ -187,7 +198,8 @@ const MenuBar = ({ setEditorRef, isChat }) => {
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
// color={editor.isActive('heading', { level: 1 }) ? 'white' : 'gray'}
sx={{
- color: editor.isActive('heading', { level: 1 }) ? 'white' : 'gray'
+ color: editor.isActive('heading', { level: 1 }) ? 'white' : 'gray',
+ padding: isMobile ? '5px' : 'revert'
}}
>
@@ -202,7 +214,8 @@ const MenuBar = ({ setEditorRef, isChat }) => {
.run()
}
sx={{
- color: 'gray'
+ color: 'gray',
+ padding: isMobile ? '5px' : 'revert'
}}
>
@@ -227,7 +240,8 @@ const MenuBar = ({ setEditorRef, isChat }) => {
@@ -268,32 +282,66 @@ const extensions = [
const content = ``;
-export default ({ setEditorRef, onEnter, disableEnter, isChat }) => {
-
+export default ({ setEditorRef, onEnter, disableEnter, isChat, maxHeightOffset, setIsFocusedParent, isFocusedParent, overrideMobile, customEditorHeight }) => {
+ const [isFocused, setIsFocused] = useState(false);
const extensionsFiltered = isChat ? extensions.filter((item)=> item?.name !== 'image') : extensions
+ const editorRef = useRef(null);
+ const setEditorRefFunc = (editorInstance) => {
+ editorRef.current = editorInstance;
+ setEditorRef(editorInstance)
+ };
+ const handleFocus = () => {
+ if(!isMobile) return
+ // setIsFocused(true);
+ setIsFocusedParent(true)
+ };
+
+ const handleBlur = () => {
+ const htmlContent = editorRef.current.getHTML();
+
+ if (!htmlContent?.trim() || htmlContent?.trim() === "
"){
+ // setIsFocused(false);
+ // setIsFocusedParent(false)
+ };
+
+ };
+ // useEffect(()=> {
+ // setIsFocused(isFocusedParent)
+ // },[isFocusedParent])
+
return (
}
- extensions={extensionsFiltered}
- content={content}
- editorProps={{
- handleKeyDown(view, event) {
- if (!disableEnter && event.key === 'Enter') {
- if (event.shiftKey) {
- // Shift+Enter: Insert a hard break
- view.dispatch(view.state.tr.replaceSelectionWith(view.state.schema.nodes.hardBreak.create()));
- return true;
- } else {
- // Enter: Call the callback function
- if (typeof onEnter === 'function') {
- onEnter();
- }
- return true; // Prevent the default action of adding a new line
+ slotBefore={(isFocusedParent || !isMobile || overrideMobile) && }
+ extensions={extensionsFiltered}
+ content={content}
+ onCreate={({ editor }) => {
+ editor.on('focus', handleFocus); // Listen for focus event
+ editor.on('blur', handleBlur); // Listen for blur event
+ }}
+ onUpdate={({ editor }) => {
+ editor.on('focus', handleFocus); // Ensure focus is updated
+ editor.on('blur', handleBlur); // Ensure blur is updated
+ }}
+ editorProps={{
+ attributes: {
+ class: 'tiptap-prosemirror',
+ style: isMobile && `overflow: auto; min-height: ${customEditorHeight ? '200px' : '0px'}; 200px; max-height:calc(100svh - ${ customEditorHeight ? customEditorHeight : '140px'})`,
+ },
+ handleKeyDown(view, event) {
+ if (!disableEnter && event.key === 'Enter') {
+ if (event.shiftKey) {
+ view.dispatch(view.state.tr.replaceSelectionWith(view.state.schema.nodes.hardBreak.create()));
+ return true;
+ } else {
+ if (typeof onEnter === 'function') {
+ onEnter();
}
+ return true;
}
- return false; // Allow default handling for other keys
- },
- }}
- />
- );
+ }
+ return false;
+ },
+ }}
+ />
+ )
};
diff --git a/src/components/Chat/styles.css b/src/components/Chat/styles.css
index f7b28ea..7c65eb0 100644
--- a/src/components/Chat/styles.css
+++ b/src/components/Chat/styles.css
@@ -118,4 +118,8 @@
.tiptap img {
display: block;
max-width: 100%;
-}
\ No newline at end of file
+}
+
+.isReply p {
+ font-size: 12px !important;
+}
diff --git a/src/components/ContextMenu.tsx b/src/components/ContextMenu.tsx
new file mode 100644
index 0000000..6779bb0
--- /dev/null
+++ b/src/components/ContextMenu.tsx
@@ -0,0 +1,165 @@
+import React, { useState, useRef, useMemo, useEffect } from 'react';
+import { ListItemIcon, Menu, MenuItem, Typography, styled } from '@mui/material';
+import MailOutlineIcon from '@mui/icons-material/MailOutline';
+import NotificationsOffIcon from '@mui/icons-material/NotificationsOff';
+import { executeEvent } from '../utils/events';
+
+const CustomStyledMenu = styled(Menu)(({ theme }) => ({
+ '& .MuiPaper-root': {
+ backgroundColor: '#f9f9f9',
+ borderRadius: '12px',
+ padding: theme.spacing(1),
+ boxShadow: '0 5px 15px rgba(0, 0, 0, 0.2)',
+ },
+ '& .MuiMenuItem-root': {
+ fontSize: '14px', // Smaller font size for the menu item text
+ color: '#444',
+ transition: '0.3s background-color',
+ '&:hover': {
+ backgroundColor: '#f0f0f0', // Explicit hover state
+ },
+
+ },
+ }));
+
+export const ContextMenu = ({ children, groupId, getUserSettings, mutedGroups }) => {
+ const [menuPosition, setMenuPosition] = useState(null);
+ const longPressTimeout = useRef(null);
+ const preventClick = useRef(false); // Flag to prevent click after long-press or right-click
+
+ const isMuted = useMemo(()=> {
+ return mutedGroups.includes(groupId)
+ }, [mutedGroups, groupId])
+
+ // Handle right-click (context menu) for desktop
+ const handleContextMenu = (event) => {
+ event.preventDefault();
+ event.stopPropagation(); // Prevent parent click
+
+ // Set flag to prevent any click event after right-click
+ preventClick.current = true;
+
+ setMenuPosition({
+ mouseX: event.clientX,
+ mouseY: event.clientY,
+ });
+ };
+
+ // Handle long-press for mobile
+ const handleTouchStart = (event) => {
+ longPressTimeout.current = setTimeout(() => {
+ preventClick.current = true; // Prevent the next click after long-press
+ event.stopPropagation(); // Prevent parent click
+ setMenuPosition({
+ mouseX: event.touches[0].clientX,
+ mouseY: event.touches[0].clientY,
+ });
+ }, 500); // Long press duration
+ };
+
+ const handleTouchEnd = (event) => {
+ clearTimeout(longPressTimeout.current);
+
+ if (preventClick.current) {
+ event.preventDefault();
+ event.stopPropagation(); // Prevent synthetic click after long-press
+ preventClick.current = false; // Reset the flag
+ }
+ };
+
+
+
+ const handleSetGroupMute = ()=> {
+ try {
+ let value = [...mutedGroups]
+ if(isMuted){
+ value = value.filter((group)=> group !== groupId)
+ } else {
+ value.push(groupId)
+ }
+ chrome?.runtime?.sendMessage(
+ {
+ action: "addUserSettings",
+ payload: {
+ keyValue: {
+ key: 'mutedGroups',
+ value
+ },
+ },
+ }
+ );
+ setTimeout(() => {
+ getUserSettings()
+ }, 400);
+
+ } catch (error) {
+
+ }
+ }
+
+
+
+ const handleClose = (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ setMenuPosition(null);
+ };
+
+ return (
+
+ {children}
+
+ {
+ e.stopPropagation();
+ }}
+ >
+ {
+ handleClose(e)
+ executeEvent("markAsRead", {
+ groupId
+ });
+ }}>
+
+
+
+
+ Mark As Read
+
+
+ {
+
+ handleClose(e)
+ handleSetGroupMute()
+
+ }}>
+
+
+
+
+ {isMuted ? 'Unmute ' : 'Mute '}Push Notifications
+
+
+
+
+ );
+};
+
+
diff --git a/src/components/Desktop/DesktopFooter.tsx b/src/components/Desktop/DesktopFooter.tsx
new file mode 100644
index 0000000..4eafe8b
--- /dev/null
+++ b/src/components/Desktop/DesktopFooter.tsx
@@ -0,0 +1,118 @@
+import * as React from "react";
+import {
+ BottomNavigation,
+ BottomNavigationAction,
+ ButtonBase,
+ Typography,
+} from "@mui/material";
+import { Home, Groups, Message, ShowChart } from "@mui/icons-material";
+import Box from "@mui/material/Box";
+import BottomLogo from "../../assets/svgs/BottomLogo5.svg";
+import { CustomSvg } from "../../common/CustomSvg";
+import { WalletIcon } from "../../assets/Icons/WalletIcon";
+import { HubsIcon } from "../../assets/Icons/HubsIcon";
+import { TradingIcon } from "../../assets/Icons/TradingIcon";
+import { MessagingIcon } from "../../assets/Icons/MessagingIcon";
+import { HomeIcon } from "../../assets/Icons/HomeIcon";
+
+const IconWrapper = ({ children, label, color, selected }) => {
+ return (
+
+ {children}
+
+ {label}
+
+
+ );
+};
+
+export const DesktopFooter = ({
+ selectedGroup,
+ groupSection,
+ isUnread,
+ goToAnnouncements,
+ isUnreadChat,
+ goToChat,
+ goToThreads,
+ setOpenManageMembers,
+ groupChatHasUnread,
+ groupsAnnHasUnread,
+ directChatHasUnread,
+ chatMode,
+ openDrawerGroups,
+ goToHome,
+ setIsOpenDrawerProfile,
+ mobileViewMode,
+ setMobileViewMode,
+ setMobileViewModeKeepOpen,
+ hasUnreadGroups,
+ hasUnreadDirects,
+ isHome,
+ isGroups,
+ isDirects,
+ setDesktopSideView
+}) => {
+ const [value, setValue] = React.useState(0);
+ return (
+
+
+ {
+ goToHome()
+ }}>
+
+
+
+
+ {
+ setDesktopSideView('groups')
+ }}>
+
+
+
+
+ {
+ setDesktopSideView('directs')
+ }}>
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/Desktop/DesktopHeader.tsx b/src/components/Desktop/DesktopHeader.tsx
new file mode 100644
index 0000000..84dab09
--- /dev/null
+++ b/src/components/Desktop/DesktopHeader.tsx
@@ -0,0 +1,280 @@
+import * as React from "react";
+import {
+ BottomNavigation,
+ BottomNavigationAction,
+ ButtonBase,
+ Typography,
+} from "@mui/material";
+import { Home, Groups, Message, ShowChart } from "@mui/icons-material";
+import Box from "@mui/material/Box";
+import BottomLogo from "../../assets/svgs/BottomLogo5.svg";
+import { CustomSvg } from "../../common/CustomSvg";
+import { WalletIcon } from "../../assets/Icons/WalletIcon";
+import { HubsIcon } from "../../assets/Icons/HubsIcon";
+import { TradingIcon } from "../../assets/Icons/TradingIcon";
+import { MessagingIcon } from "../../assets/Icons/MessagingIcon";
+import { HomeIcon } from "../../assets/Icons/HomeIcon";
+import { NotificationIcon2 } from "../../assets/Icons/NotificationIcon2";
+import { ChatIcon } from "../../assets/Icons/ChatIcon";
+import { ThreadsIcon } from "../../assets/Icons/ThreadsIcon";
+import { MembersIcon } from "../../assets/Icons/MembersIcon";
+
+const IconWrapper = ({ children, label, color, selected, selectColor }) => {
+ return (
+
+ {children}
+
+ {label}
+
+
+ );
+};
+
+export const DesktopHeader = ({
+ selectedGroup,
+ groupSection,
+ isUnread,
+ goToAnnouncements,
+ isUnreadChat,
+ goToChat,
+ goToThreads,
+ setOpenManageMembers,
+ groupChatHasUnread,
+ groupsAnnHasUnread,
+ directChatHasUnread,
+ chatMode,
+ openDrawerGroups,
+ goToHome,
+ setIsOpenDrawerProfile,
+ mobileViewMode,
+ setMobileViewMode,
+ setMobileViewModeKeepOpen,
+ hasUnreadGroups,
+ hasUnreadDirects,
+ isHome,
+ isGroups,
+ isDirects,
+ setDesktopSideView,
+ hasUnreadAnnouncements,
+ isAnnouncement,
+ hasUnreadChat,
+ isChat,
+ isForum,
+ setGroupSection
+}) => {
+ const [value, setValue] = React.useState(0);
+ return (
+
+
+
+ {selectedGroup?.groupName}
+
+
+
+ {
+ goToHome();
+ }}
+ >
+
+
+
+
+ {
+ setDesktopSideView("groups");
+ }}
+ >
+
+
+
+
+ {
+ setDesktopSideView("directs");
+ }}
+ >
+
+
+
+
+
+ {
+ goToAnnouncements()
+ }}
+ >
+
+
+
+
+
+ {
+ goToChat()
+ }}
+ >
+
+
+
+
+
+ {
+ setGroupSection("forum");
+
+ }}
+ >
+
+
+
+
+ {
+ setOpenManageMembers(true)
+
+ }}
+ >
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/Drawer/Drawer.tsx b/src/components/Drawer/Drawer.tsx
new file mode 100644
index 0000000..2e545cd
--- /dev/null
+++ b/src/components/Drawer/Drawer.tsx
@@ -0,0 +1,31 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import Drawer from '@mui/material/Drawer';
+import Button from '@mui/material/Button';
+import List from '@mui/material/List';
+import Divider from '@mui/material/Divider';
+import ListItem from '@mui/material/ListItem';
+import ListItemButton from '@mui/material/ListItemButton';
+import ListItemIcon from '@mui/material/ListItemIcon';
+import ListItemText from '@mui/material/ListItemText';
+import InboxIcon from '@mui/icons-material/MoveToInbox';
+import MailIcon from '@mui/icons-material/Mail';
+import CloseIcon from '@mui/icons-material/Close';
+export const DrawerComponent = ({open, setOpen, children}) => {
+
+ const toggleDrawer = (newOpen: boolean) => () => {
+ setOpen(newOpen);
+ };
+
+
+ return (
+
+
+
+
+ {children}
+
+
+
+ );
+}
diff --git a/src/components/Group/AddGroup.tsx b/src/components/Group/AddGroup.tsx
index 47bbf0e..288e6f3 100644
--- a/src/components/Group/AddGroup.tsx
+++ b/src/components/Group/AddGroup.tsx
@@ -29,7 +29,7 @@ import { AddGroupList } from "./AddGroupList";
import { UserListOfInvites } from "./UserListOfInvites";
import { CustomizedSnackbars } from "../Snackbar/Snackbar";
import { getFee } from "../../background";
-import { MyContext } from "../../App";
+import { MyContext, isMobile } from "../../App";
import { subscribeToEvent, unsubscribeFromEvent } from "../../utils/events";
export const Label = styled("label")(
@@ -103,7 +103,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
})
await new Promise((res, rej) => {
- chrome.runtime.sendMessage(
+ chrome?.runtime?.sendMessage(
{
action: "createGroup",
payload: {
@@ -220,44 +220,50 @@ export const AddGroup = ({ address, open, setOpen }) => {
}}
>
-
-
-
-
-
+
+
+
+
+
{value === 0 && (
diff --git a/src/components/Group/AddGroupList.tsx b/src/components/Group/AddGroupList.tsx
index d0b8772..4f459e0 100644
--- a/src/components/Group/AddGroupList.tsx
+++ b/src/components/Group/AddGroupList.tsx
@@ -108,7 +108,7 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
})
setIsLoading(true);
await new Promise((res, rej) => {
- chrome.runtime.sendMessage(
+ chrome?.runtime?.sendMessage(
{
action: "joinGroup",
payload: {
@@ -247,7 +247,7 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
style={{
position: "relative",
height: "500px",
- width: "600px",
+ width: "100%",
display: "flex",
flexDirection: "column",
flexShrink: 1,
diff --git a/src/components/Group/Forum/GroupMail.tsx b/src/components/Group/Forum/GroupMail.tsx
index d962347..6d26848 100644
--- a/src/components/Group/Forum/GroupMail.tsx
+++ b/src/components/Group/Forum/GroupMail.tsx
@@ -53,7 +53,9 @@ import ArrowDownSVG from "../../../assets/svgs/ArrowDown.svg";
import { LoadingSnackbar } from "../../Snackbar/LoadingSnackbar";
import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from "../../../utils/events";
import RefreshIcon from '@mui/icons-material/Refresh';
-import { getBaseApiReact } from "../../../App";
+import { getArbitraryEndpointReact, getBaseApiReact } from "../../../App";
+import { WrapperUserAction } from "../../WrapperUserAction";
+import { addDataPublishesFunc, getDataPublishesFunc } from "../Group";
const filterOptions = ["Recently active", "Newest", "Oldest"];
export const threadIdentifier = "DOCUMENT";
@@ -63,7 +65,8 @@ export const GroupMail = ({
getSecretKey,
secretKey,
defaultThread,
- setDefaultThread
+ setDefaultThread,
+ hide
}) => {
const [viewedThreads, setViewedThreads] = React.useState({});
const [filterMode, setFilterMode] = useState("Recently active");
@@ -74,6 +77,7 @@ export const GroupMail = ({
const [isOpenFilterList, setIsOpenFilterList] = useState(false);
const anchorElInstanceFilter = useRef(null);
const [tempPublishedList, setTempPublishedList] = useState([])
+ const dataPublishes = useRef({})
const [isLoading, setIsLoading] = useState(false)
const groupIdRef = useRef(null);
@@ -81,6 +85,14 @@ export const GroupMail = ({
return selectedGroup?.groupId;
}, [selectedGroup]);
+ useEffect(()=> {
+ if(!groupId) return
+ (async ()=> {
+ const res = await getDataPublishesFunc(groupId, 'thread')
+ dataPublishes.current = res || {}
+ })()
+ }, [groupId])
+
useEffect(() => {
if (groupId !== groupIdRef?.current) {
setCurrentThread(null);
@@ -108,12 +120,19 @@ export const GroupMail = ({
}
- const getEncryptedResource = async ({ name, identifier }) => {
-
+ const getEncryptedResource = async ({ name, identifier, resource }) => {
+ let data = dataPublishes.current[`${name}-${identifier}`]
+ if(!data || (data?.update || data?.created !== (resource?.updated || resource?.created))){
const res = await fetch(
`${getBaseApiReact()}/arbitrary/DOCUMENT/${name}/${identifier}?encoding=base64`
);
- const data = await res.text();
+ if(!res?.ok) return
+ data = await res.text();
+ await addDataPublishesFunc({...resource, data}, groupId, 'thread')
+
+ } else {
+ data = data.data
+ }
const response = await decryptPublishes([{ data }], secretKey);
const messageData = response[0];
@@ -123,7 +142,7 @@ export const GroupMail = ({
const updateThreadActivity = async ({threadId, qortalName, groupId, thread}) => {
try {
await new Promise((res, rej) => {
- chrome.runtime.sendMessage(
+ chrome?.runtime?.sendMessage(
{
action: "updateThreadActivity",
payload: {
@@ -158,7 +177,7 @@ export const GroupMail = ({
}
const identifier = `grp-${groupId}-thread-`;
- const url = `${getBaseApiReact()}/arbitrary/resources/search?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=${20}&includemetadata=false&offset=${offset}&reverse=${isReverse}&prefix=true`;
+ const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=${20}&includemetadata=false&offset=${offset}&reverse=${isReverse}&prefix=true`;
const response = await fetch(url, {
method: "GET",
headers: {
@@ -188,6 +207,7 @@ export const GroupMail = ({
getEncryptedResource({
name: message.name,
identifier: message.identifier,
+ resource: message
}),
delay(5000),
]);
@@ -244,7 +264,7 @@ export const GroupMail = ({
// dispatch(setIsLoadingCustom("Loading recent threads"));
const identifier = `thmsg-grp-${groupId}-thread-`;
- const url = `${getBaseApiReact()}/arbitrary/resources/search?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=100&includemetadata=false&offset=${0}&reverse=true&prefix=true`;
+ const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=100&includemetadata=false&offset=${0}&reverse=true&prefix=true`;
const response = await fetch(url, {
method: "GET",
headers: {
@@ -280,7 +300,7 @@ export const GroupMail = ({
const getMessageForThreads = newArray.map(async (message: any) => {
try {
const identifierQuery = message.threadId;
- const url = `${getBaseApiReact()}/arbitrary/resources/search?mode=ALL&service=${threadIdentifier}&identifier=${identifierQuery}&limit=1&includemetadata=false&offset=${0}&reverse=true&prefix=true`;
+ const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=${threadIdentifier}&identifier=${identifierQuery}&limit=1&includemetadata=false&offset=${0}&reverse=true&prefix=true`;
const response = await fetch(url, {
method: "GET",
headers: {
@@ -307,6 +327,7 @@ export const GroupMail = ({
getEncryptedResource({
name: thread.name,
identifier: message.threadId,
+ resource: thread
}),
delay(10000),
]);
@@ -353,6 +374,7 @@ export const GroupMail = ({
const filterModeRef = useRef("");
useEffect(() => {
+ if(hide) return
if (filterModeRef.current !== filterMode) {
firstMount.current = false;
}
@@ -368,7 +390,7 @@ export const GroupMail = ({
setTempData()
firstMount.current = true;
}
- }, [groupId, members, filterMode]);
+ }, [groupId, members, filterMode, hide]);
const closeThread = useCallback(() => {
setCurrentThread(null);
@@ -656,6 +678,11 @@ export const GroupMail = ({
thread?.threadData?.createdAt < hasViewedRecent?.timestamp;
return (
{
setCurrentThread(thread);
if(thread?.threadId && thread?.threadData?.name){
@@ -665,6 +692,7 @@ export const GroupMail = ({
}
}}
>
+
{thread?.threadData?.name?.charAt(0)}
+
+
by
{thread?.threadData?.name}
+
{formatTimestamp(thread?.threadData?.createdAt)}
diff --git a/src/components/Group/Forum/NewThread.tsx b/src/components/Group/Forum/NewThread.tsx
index f4a088b..231c860 100644
--- a/src/components/Group/Forum/NewThread.tsx
+++ b/src/components/Group/Forum/NewThread.tsx
@@ -30,7 +30,7 @@ import { formatBytes } from "../../../utils/Size";
import { CreateThreadIcon } from "../../../assets/svgs/CreateThreadIcon";
import { SendNewMessage } from "../../../assets/svgs/SendNewMessage";
import { TextEditor } from "./TextEditor";
-import { MyContext, pauseAllQueues, resumeAllQueues } from "../../../App";
+import { MyContext, isMobile, pauseAllQueues, resumeAllQueues } from "../../../App";
import { getFee } from "../../../background";
import TipTap from "../../Chat/TipTap";
import { MessageDisplay } from "../../Chat/MessageDisplay";
@@ -94,7 +94,7 @@ export const publishGroupEncryptedResource = async ({
identifier,
}) => {
return new Promise((res, rej) => {
- chrome.runtime.sendMessage(
+ chrome?.runtime?.sendMessage(
{
action: "publishGroupEncryptedResource",
payload: {
@@ -117,7 +117,7 @@ export const publishGroupEncryptedResource = async ({
export const encryptSingleFunc = async (data: string, secretKeyObject: any) => {
try {
return new Promise((res, rej) => {
- chrome.runtime.sendMessage(
+ chrome?.runtime?.sendMessage(
{
action: "encryptSingle",
payload: {
@@ -147,7 +147,8 @@ export const NewThread = ({
getSecretKey,
closeCallback,
postReply,
- myName
+ myName,
+ setPostReply
}: NewMessageProps) => {
const { show } = React.useContext(MyContext);
@@ -171,6 +172,7 @@ export const NewThread = ({
const closeModal = () => {
setIsOpen(false);
setValue("");
+ setPostReply(null)
};
async function publishQDNResource() {
@@ -399,7 +401,8 @@ export const NewThread = ({
>
setIsOpen(true)}
>
@@ -410,7 +413,7 @@ export const NewThread = ({
{isMessage ? "Post Message" : "New Thread"}
-
+
@@ -463,7 +468,7 @@ export const NewThread = ({
color: "white",
"& .MuiInput-input::placeholder": {
color: "rgba(255,255,255, 0.70) !important",
- fontSize: "20px",
+ fontSize: isMobile ? '14px' : "20px",
fontStyle: "normal",
fontWeight: 400,
lineHeight: "120%", // 24px
@@ -491,7 +496,10 @@ export const NewThread = ({
)}
-
+ {!isMobile && (
+
+
+ )}
{/*
diff --git a/src/components/Group/Forum/ReusableModal.tsx b/src/components/Group/Forum/ReusableModal.tsx
index b61e83e..d081293 100644
--- a/src/components/Group/Forum/ReusableModal.tsx
+++ b/src/components/Group/Forum/ReusableModal.tsx
@@ -1,5 +1,6 @@
import React from 'react'
import { Box, Modal, useTheme } from '@mui/material'
+import { isMobile } from '../../../App'
interface MyModalProps {
open: boolean
@@ -40,7 +41,7 @@ export const ReusableModal: React.FC = ({
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
- width: '75%',
+ width: isMobile ? '95%' : '75%',
bgcolor: theme.palette.primary.main,
boxShadow: 24,
p: 4,
diff --git a/src/components/Group/Forum/ShowMessageWithoutModal.tsx b/src/components/Group/Forum/ShowMessageWithoutModal.tsx
index c8fb5cb..9a079d6 100644
--- a/src/components/Group/Forum/ShowMessageWithoutModal.tsx
+++ b/src/components/Group/Forum/ShowMessageWithoutModal.tsx
@@ -19,8 +19,9 @@ import ReadOnlySlate from "./ReadOnlySlate";
import { MessageDisplay } from "../../Chat/MessageDisplay";
import { getBaseApi } from "../../../background";
import { getBaseApiReact } from "../../../App";
+import { WrapperUserAction } from "../../WrapperUserAction";
-export const ShowMessage = ({ message, openNewPostWithQuote }: any) => {
+export const ShowMessage = ({ message, openNewPostWithQuote, myName }: any) => {
const [expandAttachments, setExpandAttachments] = useState(false);
let cleanHTML = "";
@@ -53,13 +54,17 @@ export const ShowMessage = ({ message, openNewPostWithQuote }: any) => {
}}
>
-
+
{message?.name?.charAt(0)}
+
+
+
{message?.name}
+
{formatTimestampForum(message?.created)}
diff --git a/src/components/Group/Forum/Thread copy.tsx b/src/components/Group/Forum/Thread copy.tsx
index 7aadabd..9091a2c 100644
--- a/src/components/Group/Forum/Thread copy.tsx
+++ b/src/components/Group/Forum/Thread copy.tsx
@@ -24,7 +24,7 @@ import ReturnSVG from '../../../assets/svgs/Return.svg'
import { NewThread } from './NewThread'
import { decryptPublishes } from '../../Chat/GroupAnnouncements'
import { getBaseApi } from '../../../background'
-import { getBaseApiReact } from '../../../App'
+import { getArbitraryEndpointReact, getBaseApiReact } from '../../../App'
interface ThreadProps {
currentThread: any
groupInfo: any
@@ -91,7 +91,7 @@ export const Thread = ({
const offset = messages.length
const identifier = `thmsg-${threadId}`
- const url = `${getBaseApiReact()}/arbitrary/resources/search?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=20&includemetadata=false&offset=${offset}&reverse=true&prefix=true`
+ const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=20&includemetadata=false&offset=${offset}&reverse=true&prefix=true`
const response = await fetch(url, {
method: 'GET',
headers: {
@@ -180,7 +180,7 @@ export const Thread = ({
let threadId = groupInfo.threadId
const identifier = `thmsg-${threadId}`
- const url = `${getBaseApiReact()}/arbitrary/resources/search?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=20&includemetadata=false&offset=${0}&reverse=true&prefix=true`
+ const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=20&includemetadata=false&offset=${0}&reverse=true&prefix=true`
const response = await fetch(url, {
method: 'GET',
headers: {
diff --git a/src/components/Group/Forum/Thread.tsx b/src/components/Group/Forum/Thread.tsx
index 6dd0dbb..f393d5d 100644
--- a/src/components/Group/Forum/Thread.tsx
+++ b/src/components/Group/Forum/Thread.tsx
@@ -1,10 +1,21 @@
-import React, { FC, useCallback, useEffect, useMemo, useRef, useState } from "react";
-
-import { Box, Button, IconButton, Skeleton } from "@mui/material";
+import React, {
+ FC,
+ useCallback,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from "react";
+import {
+ Avatar,
+ Box,
+ Button,
+ ButtonBase,
+ IconButton,
+ Skeleton,
+ Typography,
+} from "@mui/material";
import { ShowMessage } from "./ShowMessageWithoutModal";
-// import {
-// setIsLoadingCustom,
-// } from '../../state/features/globalSlice'
import {
ComposeP,
GroupContainer,
@@ -14,19 +25,38 @@ import {
SingleThreadParent,
ThreadContainer,
ThreadContainerFullWidth,
+ ThreadInfoColumn,
+ ThreadInfoColumnNameP,
+ ThreadInfoColumnTime,
} from "./Mail-styles";
import { Spacer } from "../../../common/Spacer";
import { threadIdentifier } from "./GroupMail";
import LazyLoad from "../../../common/LazyLoad";
import ReturnSVG from "../../../assets/svgs/Return.svg";
import { NewThread } from "./NewThread";
-import { decryptPublishes, getTempPublish } from "../../Chat/GroupAnnouncements";
+import {
+ decryptPublishes,
+ getTempPublish,
+} from "../../Chat/GroupAnnouncements";
import { LoadingSnackbar } from "../../Snackbar/LoadingSnackbar";
import { subscribeToEvent, unsubscribeFromEvent } from "../../../utils/events";
import RefreshIcon from "@mui/icons-material/Refresh";
-import { getBaseApi } from "../../../background";
-import { getBaseApiReact } from "../../../App";
-
+import {
+ getArbitraryEndpointReact,
+ getBaseApiReact,
+ isMobile,
+} from "../../../App";
+import {
+ ArrowDownward as ArrowDownwardIcon,
+ ArrowUpward as ArrowUpwardIcon,
+} from "@mui/icons-material";
+import { addDataPublishesFunc, getDataPublishesFunc } from "../Group";
+import { RequestQueueWithPromise } from "../../../utils/queue/queue";
+import { CustomLoader } from "../../../common/CustomLoader";
+import { WrapperUserAction } from "../../WrapperUserAction";
+import { formatTimestampForum } from "../../../utils/time";
+const requestQueueSaveToLocal = new RequestQueueWithPromise(1);
+const requestQueueDownloadPost = new RequestQueueWithPromise(3);
interface ThreadProps {
currentThread: any;
groupInfo: any;
@@ -34,11 +64,41 @@ interface ThreadProps {
members: any;
}
-const getEncryptedResource = async ({ name, identifier, secretKey }) => {
- const res = await fetch(
- `${getBaseApiReact()}/arbitrary/DOCUMENT/${name}/${identifier}?encoding=base64`
- );
- const data = await res.text();
+const getEncryptedResource = async ({
+ name,
+ identifier,
+ secretKey,
+ resource,
+ groupId,
+ dataPublishes,
+}) => {
+ let data = dataPublishes[`${name}-${identifier}`];
+ if (
+ !data ||
+ data?.update ||
+ data?.created !== (resource?.updated || resource?.created)
+ ) {
+ const res = await requestQueueDownloadPost.enqueue(() => {
+ return fetch(
+ `${getBaseApiReact()}/arbitrary/DOCUMENT/${name}/${identifier}?encoding=base64`
+ );
+ });
+ if (!res.ok) {
+ const errorData = await res.json();
+
+ return {
+ error: errorData?.message,
+ };
+ }
+ data = await res.text();
+
+ if (data?.error || typeof data !== "string") return;
+ await requestQueueSaveToLocal.enqueue(() => {
+ return addDataPublishesFunc({ ...resource, data }, groupId, "thmsg");
+ });
+ } else {
+ data = data.data;
+ }
const response = await decryptPublishes([{ data }], secretKey);
const messageData = response[0];
@@ -53,10 +113,9 @@ export const Thread = ({
userInfo,
secretKey,
getSecretKey,
- updateThreadActivityCurrentThread
+ updateThreadActivityCurrentThread,
}: ThreadProps) => {
- const [tempPublishedList, setTempPublishedList] = useState([])
-
+ const [tempPublishedList, setTempPublishedList] = useState([]);
const [messages, setMessages] = useState([]);
const [hashMapMailMessages, setHashMapMailMessages] = useState({});
const [hasFirstPage, setHasFirstPage] = useState(false);
@@ -66,9 +125,28 @@ export const Thread = ({
const [postReply, setPostReply] = useState(null);
const [hasLastPage, setHasLastPage] = useState(false);
+ // Update: Use a new ref for the scrollable container
+ const threadContainerRef = useRef(null);
+ const threadBeginningRef = useRef(null)
+ // New state variables
+ const [showScrollButton, setShowScrollButton] = useState(false);
+ const [isAtBottom, setIsAtBottom] = useState(false);
+
const secretKeyRef = useRef(null);
const currentThreadRef = useRef(null);
const containerRef = useRef(null);
+ const dataPublishes = useRef({});
+
+ const getSavedData = useCallback(async (groupId) => {
+ const res = await getDataPublishesFunc(groupId, "thmsg");
+ dataPublishes.current = res || {};
+ }, []);
+
+ useEffect(() => {
+ if (!groupInfo?.groupId) return;
+ getSavedData(groupInfo?.groupId);
+ }, [groupInfo?.groupId]);
+
useEffect(() => {
currentThreadRef.current = currentThread;
}, [currentThread]);
@@ -83,8 +161,25 @@ export const Thread = ({
identifier: message.identifier,
name: message.name,
secretKey,
+ resource: message,
+ groupId: groupInfo?.groupId,
+ dataPublishes: dataPublishes.current,
});
-
+
+ if (responseDataMessage?.error) {
+ const fullObject = {
+ ...message,
+ error: responseDataMessage?.error,
+ id: message.identifier,
+ };
+ setHashMapMailMessages((prev) => {
+ return {
+ ...prev,
+ [message.identifier]: fullObject,
+ };
+ });
+ return;
+ }
const fullObject = {
...message,
@@ -100,46 +195,40 @@ export const Thread = ({
} catch (error) {}
};
- const setTempData = async ()=> {
+ const setTempData = async () => {
try {
let threadId = currentThread.threadId;
-
- const keyTemp = 'thread-post'
- const getTempAnnouncements = await getTempPublish()
-
- if(getTempAnnouncements?.[keyTemp]){
-
- let tempData = []
- Object.keys(getTempAnnouncements?.[keyTemp] || {}).map((key)=> {
- const value = getTempAnnouncements?.[keyTemp][key]
-
- if(value.data?.threadId === threadId){
- tempData.push(value.data)
- }
-
- })
- setTempPublishedList(tempData)
- }
- } catch (error) {
-
- }
-
- }
+ const keyTemp = "thread-post";
+ const getTempAnnouncements = await getTempPublish();
+
+ if (getTempAnnouncements?.[keyTemp]) {
+ let tempData = [];
+ Object.keys(getTempAnnouncements?.[keyTemp] || {}).map((key) => {
+ const value = getTempAnnouncements?.[keyTemp][key];
+
+ if (value.data?.threadId === threadId) {
+ tempData.push(value.data);
+ }
+ });
+ setTempPublishedList(tempData);
+ }
+ } catch (error) {}
+ };
const getMailMessages = React.useCallback(
- async (groupInfo: any, before, after, isReverse) => {
+ async (groupInfo: any, before, after, isReverse, groupId) => {
try {
- setTempPublishedList([])
+ setTempPublishedList([]);
setIsLoading(true);
setHasFirstPage(false);
setHasPreviousPage(false);
setHasLastPage(false);
setHasNextPage(false);
let threadId = groupInfo.threadId;
-
+
const identifier = `thmsg-${threadId}`;
- let url = `${getBaseApiReact()}/arbitrary/resources/search?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=20&includemetadata=false&prefix=true`;
+ let url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=20&includemetadata=false&prefix=true`;
if (!isReverse) {
url = url + "&reverse=false";
}
@@ -160,7 +249,6 @@ export const Thread = ({
},
});
const responseData = await response.json();
-
let fullArrayMsg = [...responseData];
if (isReverse) {
@@ -175,15 +263,22 @@ export const Thread = ({
setTimeout(() => {
containerRef.current.scrollIntoView({ behavior: "smooth" });
}, 300);
+ }
+ if(after || before === null && after === null && !isReverse){
+ setTimeout(() => {
+ threadBeginningRef.current.scrollIntoView();
+ }, 100);
}
-
- if (fullArrayMsg.length === 0){
- setTempData()
+
+ if (fullArrayMsg.length === 0) {
+ setTempData();
return;
- }
+ }
// check if there are newer posts
- const urlNewer = `${getBaseApiReact()}/arbitrary/resources/search?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=1&includemetadata=false&reverse=false&prefix=true&before=${fullArrayMsg[0].created}`;
+ const urlNewer = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=1&includemetadata=false&reverse=false&prefix=true&before=${
+ fullArrayMsg[0].created
+ }`;
const responseNewer = await fetch(urlNewer, {
method: "GET",
headers: {
@@ -199,7 +294,7 @@ export const Thread = ({
setHasPreviousPage(false);
}
// check if there are older posts
- const urlOlder = `${getBaseApiReact()}/arbitrary/resources/search?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=1&includemetadata=false&reverse=false&prefix=true&after=${
+ const urlOlder = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=1&includemetadata=false&reverse=false&prefix=true&after=${
fullArrayMsg[fullArrayMsg.length - 1].created
}`;
const responseOlder = await fetch(urlOlder, {
@@ -215,22 +310,22 @@ export const Thread = ({
} else {
setHasLastPage(false);
setHasNextPage(false);
- setTempData()
- updateThreadActivityCurrentThread()
+ setTempData();
+ updateThreadActivityCurrentThread();
}
} catch (error) {
+ console.log("error", error);
} finally {
setIsLoading(false);
-
+ getSavedData(groupId);
}
},
[messages, secretKey]
);
const getMessages = React.useCallback(async () => {
-
- if (!currentThread || !secretKey) return;
- await getMailMessages(currentThread, null, null, false);
- }, [getMailMessages, currentThread, secretKey]);
+ if (!currentThread || !secretKey || !groupInfo?.groupId) return;
+ await getMailMessages(currentThread, null, null, false, groupInfo?.groupId);
+ }, [getMailMessages, currentThread, secretKey, groupInfo?.groupId]);
const firstMount = useRef(false);
const saveTimestamp = useCallback((currentThread: any, username?: string) => {
@@ -287,8 +382,6 @@ export const Thread = ({
}
if (currentThread && secretKey && !firstMount.current) {
getMessagesMiddleware();
-
- // saveTimestamp(currentThread, user.name)
}
}, [currentThread, secretKey]);
const messageCallback = useCallback((msg: any) => {
@@ -304,7 +397,7 @@ export const Thread = ({
let threadId = groupInfo.threadId;
const identifier = `thmsg-${threadId}`;
- const url = `${getBaseApiReact()}/arbitrary/resources/search?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=20&includemetadata=false&offset=${0}&reverse=true&prefix=true`;
+ const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=20&includemetadata=false&offset=${0}&reverse=true&prefix=true`;
const response = await fetch(url, {
method: "GET",
headers: {
@@ -329,6 +422,9 @@ export const Thread = ({
identifier: message.identifier,
name: message.name,
secretKey: secretKeyRef.current,
+ resource: message,
+ groupId: groupInfo?.groupId,
+ dataPublishes: dataPublishes.current,
});
const fullObject = {
@@ -360,25 +456,6 @@ export const Thread = ({
[messages]
);
- // const checkNewMessagesFunc = useCallback(() => {
- // let isCalling = false
- // interval.current = setInterval(async () => {
- // if (isCalling) return
- // isCalling = true
- // const res = await checkNewMessages(currentThread)
- // isCalling = false
- // }, 8000)
- // }, [checkNewMessages, currentThrefirstMount.current = truead])
-
- // useEffect(() => {
- // checkNewMessagesFunc()
- // return () => {
- // if (interval?.current) {
- // clearInterval(interval.current)
- // }
- // }
- // }, [checkNewMessagesFunc])
-
const openNewPostWithQuote = useCallback((reply) => {
setPostReply(reply);
}, []);
@@ -390,7 +467,7 @@ export const Thread = ({
const threadFetchModeFunc = (e) => {
const mode = e.detail?.mode;
if (mode === "last-page") {
- getMailMessages(currentThread, null, null, true);
+ getMailMessages(currentThread, null, null, true, groupInfo?.groupId);
}
firstMount.current = true;
};
@@ -406,44 +483,157 @@ export const Thread = ({
const combinedListTempAndReal = useMemo(() => {
// Combine the two lists
const combined = [...tempPublishedList, ...messages];
-
+
// 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) => a.created - b.created);
-
+ const sortedList = Array.from(uniqueItems.values()).sort(
+ (a, b) => a.created - b.created
+ );
+
return sortedList;
}, [tempPublishedList, messages]);
+ // Updated useEffect to handle scroll and overflow
+ useEffect(() => {
+ const container = threadContainerRef.current; // Updated reference
+ if (!container) return;
+
+ const handleScroll = () => {
+ const { scrollTop, scrollHeight, clientHeight } = container;
+ // Check if user is at the bottom
+ if (scrollTop + clientHeight >= scrollHeight - 5) {
+ setIsAtBottom(true);
+ } else {
+ setIsAtBottom(false);
+ }
+
+ // Initial check if content overflows
+ if (container.scrollHeight > container.clientHeight) {
+ setShowScrollButton(true);
+ } else {
+ setShowScrollButton(false);
+ }
+ };
+ setTimeout(() => {
+ handleScroll();
+ }, 400);
+
+ container.addEventListener("scroll", handleScroll);
+
+ // Cleanup
+ return () => {
+ container.removeEventListener("scroll", handleScroll);
+ };
+ }, [messages]);
+
+ // Function to scroll to the top or bottom of the container
+ const scrollToPosition = () => {
+ const container = threadContainerRef.current; // Updated reference
+ if (!container) return;
+
+ if (isAtBottom) {
+ container.scrollTo({ top: 0, behavior: "smooth" }); // Scroll to top
+ } else {
+ container.scrollTo({ top: container.scrollHeight, behavior: "smooth" }); // Scroll to bottom
+ }
+ };
+
if (!currentThread) return null;
return (
-
-
-
-
+
+
+
+ {
+ setMessages([]);
+ closeThread();
+ }}
+ >
+
+ {!isMobile && Return to Threads }
+
+ {/* Conditionally render the scroll buttons */}
+ {showScrollButton &&
+ (isAtBottom ? (
+
+
+
+ ) : (
+
+
+
+ ))}
+
+
+
+
+
+
+
- {currentThread?.threadData?.title}
-
- {
- setMessages([]);
- closeThread();
+
-
- Return to Threads
-
+ {currentThread?.threadData?.title}
+
+
+
{
- getMailMessages(currentThread, null, null, false);
+ getMailMessages(
+ currentThread,
+ null,
+ null,
+ false,
+ groupInfo?.groupId
+ );
}}
disabled={!hasFirstPage}
variant="contained"
>
- First Page
+ First
{
getMailMessages(
currentThread,
messages[0].created,
null,
- false
+ false,
+ groupInfo?.groupId
);
}}
disabled={!hasPreviousPage}
variant="contained"
>
- Previous Page
+ Previous
{
getMailMessages(
currentThread,
null,
messages[messages.length - 1].created,
- false
+ false,
+ groupInfo?.groupId
);
}}
disabled={!hasNextPage}
variant="contained"
>
- Next page
+ Next
{
- getMailMessages(currentThread, null, null, true);
+ getMailMessages(
+ currentThread,
+ null,
+ null,
+ true,
+ groupInfo?.groupId
+ );
}}
disabled={!hasLastPage}
variant="contained"
>
- Last page
+ Last
-
- {combinedListTempAndReal.map((message) => {
- let fullMessage = message;
+
+ {combinedListTempAndReal.map((message, index, list) => {
+ let fullMessage = message;
if (hashMapMailMessages[message?.identifier]) {
fullMessage = hashMapMailMessages[message.identifier];
+
+
+ if (fullMessage?.error) {
+ return (
+
+
+
+
+
+ {message?.name?.charAt(0)}
+
+
+
+
+
+ {message?.name}
+
+
+
+ {formatTimestampForum(message?.created)}
+
+
+
+
+
+ {fullMessage?.error}
+
+
+
+
+
+ );
+ }
return (
);
- } else if(message?.tempData){
+ } else if (message?.tempData) {
return (
);
}
return (
-
-
+
+ >
+
+
+
+ {message?.name?.charAt(0)}
+
+
+
+
+
+ {message?.name}
+
+
+
+ {formatTimestampForum(message?.created)}
+
+
+
+
+
+
+ Downloading from QDN
+
+
+
+
);
})}
-
+
{!hasLastPage && !isLoading && (
<>
@@ -572,7 +942,13 @@ export const Thread = ({
variant="outlined"
startIcon={ }
onClick={() => {
- getMailMessages(currentThread, null, null, true);
+ getMailMessages(
+ currentThread,
+ null,
+ null,
+ true,
+ groupInfo?.groupId
+ );
}}
sx={{
color: "white",
@@ -584,8 +960,11 @@ export const Thread = ({
>
)}
- {messages?.length > 4 && (
- <>
+
+ 4 ? 'visible' : 'hidden'
+ }}>
{
- getMailMessages(currentThread, null, null, false);
+ getMailMessages(
+ currentThread,
+ null,
+ null,
+ false,
+ groupInfo?.groupId
+ );
}}
disabled={!hasFirstPage}
variant="contained"
>
- First Page
+ First
{
getMailMessages(
currentThread,
messages[0].created,
null,
- false
+ false,
+ groupInfo?.groupId
);
}}
disabled={!hasPreviousPage}
variant="contained"
>
- Previous Page
+ Previous
{
getMailMessages(
currentThread,
null,
messages[messages.length - 1].created,
- false
+ false,
+ groupInfo?.groupId
);
}}
disabled={!hasNextPage}
variant="contained"
>
- Next page
+ Next
{
- getMailMessages(currentThread, null, null, true);
+ getMailMessages(
+ currentThread,
+ null,
+ null,
+ true,
+ groupInfo?.groupId
+ );
}}
disabled={!hasLastPage}
variant="contained"
>
- Last page
+ Last
- >
- )}
+
+
+
- {/* {messages.length >= 20 && (
-
getMailMessages(currentThread, false, true)}>
-
- )} */}
touchStartY) {
+// event.preventDefault();
+// }
+// });
interface GroupProps {
myAddress: string;
@@ -75,7 +123,7 @@ const timeDifferenceForNotificationChats = 900000;
export const requestQueueMemberNames = new RequestQueueWithPromise(5);
export const requestQueueAdminMemberNames = new RequestQueueWithPromise(5);
-const audio = new Audio(chrome.runtime.getURL("msg-not1.wav"));
+const audio = new Audio(chrome.runtime?.getURL("msg-not1.wav"));
export const getGroupAdimnsAddress = async (groupNumber: number) => {
// const validApi = await findUsableApi();
@@ -151,7 +199,7 @@ export const getGroupMembers = async (groupNumber: number) => {
export const decryptResource = async (data: string) => {
try {
return new Promise((res, rej) => {
- chrome.runtime.sendMessage(
+ chrome?.runtime?.sendMessage(
{
action: "decryptGroupEncryption",
payload: {
@@ -159,7 +207,52 @@ export const decryptResource = async (data: string) => {
},
},
(response) => {
-
+ if (!response?.error) {
+ res(response);
+ return;
+ }
+ rej(response.error);
+ }
+ );
+ });
+ } catch (error) {}
+};
+
+export const addDataPublishesFunc = async (data: string, groupId, type) => {
+ try {
+ return new Promise((res, rej) => {
+ chrome?.runtime?.sendMessage(
+ {
+ action: "addDataPublishes",
+ payload: {
+ data,
+ groupId,
+ type,
+ },
+ },
+ (response) => {
+ if (!response?.error) {
+ res(response);
+ }
+ rej(response.error);
+ }
+ );
+ });
+ } catch (error) {}
+};
+
+export const getDataPublishesFunc = async (groupId, type) => {
+ try {
+ return new Promise((res, rej) => {
+ chrome?.runtime?.sendMessage(
+ {
+ action: "getDataPublishes",
+ payload: {
+ groupId,
+ type,
+ },
+ },
+ (response) => {
if (!response?.error) {
res(response);
}
@@ -189,6 +282,8 @@ export const getGroupAdimns = async (groupNumber: number) => {
);
const groupData = await response.json();
let members: any = [];
+ let membersAddresses = [];
+ let both = [];
// if (groupData && Array.isArray(groupData?.members)) {
// for (const member of groupData.members) {
// if (member.member) {
@@ -207,14 +302,16 @@ export const getGroupAdimns = async (groupNumber: number) => {
});
if (name) {
members.push(name);
+ both.push({ name, address: member.member });
}
+ membersAddresses.push(member.member);
}
return true;
});
await Promise.all(getMemNames);
- return members;
+ return { names: members, addresses: membersAddresses, both };
};
export const getNames = async (listOfMembers) => {
@@ -276,10 +373,13 @@ export const Group = ({
isMain,
userInfo,
balance,
+ isOpenDrawerProfile,
+ setIsOpenDrawerProfile,
+ logoutFunc,
}: GroupProps) => {
const [secretKey, setSecretKey] = useState(null);
const [secretKeyPublishDate, setSecretKeyPublishDate] = useState(null);
- const lastFetchedSecretKey = useRef(null)
+ const lastFetchedSecretKey = useRef(null);
const [secretKeyDetails, setSecretKeyDetails] = useState(null);
const [newEncryptionNotification, setNewEncryptionNotification] =
useState(null);
@@ -293,14 +393,13 @@ export const Group = ({
const [directs, setDirects] = useState([]);
const [admins, setAdmins] = useState([]);
const [adminsWithNames, setAdminsWithNames] = useState([]);
-
const [members, setMembers] = useState([]);
const [groupOwner, setGroupOwner] = useState(null);
const [triedToFetchSecretKey, setTriedToFetchSecretKey] = useState(false);
const [openAddGroup, setOpenAddGroup] = useState(false);
const [isInitialGroups, setIsInitialGroups] = useState(false);
const [openManageMembers, setOpenManageMembers] = useState(false);
- const { setMemberGroups, memberGroups } = useContext(MyContext);
+ const { setMemberGroups, memberGroups, rootHeight } = useContext(MyContext);
const lastGroupNotification = useRef(null);
const [timestampEnterData, setTimestampEnterData] = useState({});
const [chatMode, setChatMode] = useState("groups");
@@ -308,13 +407,21 @@ export const Group = ({
const [openSnack, setOpenSnack] = React.useState(false);
const [infoSnack, setInfoSnack] = React.useState(null);
const [isLoadingNotifyAdmin, setIsLoadingNotifyAdmin] = React.useState(false);
- const [isLoadingGroups, setIsLoadingGroups] = React.useState(false);
+ const [isLoadingGroups, setIsLoadingGroups] = React.useState(true);
const [isLoadingGroup, setIsLoadingGroup] = React.useState(false);
- const [firstSecretKeyInCreation, setFirstSecretKeyInCreation] = React.useState(false)
+ const [firstSecretKeyInCreation, setFirstSecretKeyInCreation] =
+ React.useState(false);
const [groupSection, setGroupSection] = React.useState("home");
const [groupAnnouncements, setGroupAnnouncements] = React.useState({});
const [defaultThread, setDefaultThread] = React.useState(null);
-
+ const [isOpenDrawer, setIsOpenDrawer] = React.useState(false);
+ const [hideCommonKeyPopup, setHideCommonKeyPopup] = React.useState(false);
+ 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 [desktopSideView, setDesktopSideView] = useState('groups')
const isFocusedRef = useRef(true);
const selectedGroupRef = useRef(null);
const selectedDirectRef = useRef(null);
@@ -324,7 +431,10 @@ export const Group = ({
const setupGroupWebsocketInterval = useRef(null);
const settimeoutForRefetchSecretKey = useRef(null);
const { clearStatesMessageQueueProvider } = useMessageQueue();
-
+ const initiatedGetMembers = useRef(false);
+ // useEffect(()=> {
+ // setFullHeight()
+ // }, [])
useEffect(() => {
isFocusedRef.current = isFocused;
@@ -341,12 +451,39 @@ export const Group = ({
selectedDirectRef.current = selectedDirect;
}, [selectedDirect]);
+ const getUserSettings = async () => {
+ try {
+ return new Promise((res, rej) => {
+ chrome?.runtime?.sendMessage(
+ {
+ action: "getUserSettings",
+ payload: {
+ key: "mutedGroups",
+ },
+ },
+ (response) => {
+ if (!response?.error) {
+ setMutedGroups(response || []);
+ res(response);
+ return;
+ }
+ rej(response.error);
+ }
+ );
+ });
+ } catch (error) {
+ console.log("error", error);
+ }
+ };
+ useEffect(() => {
+ getUserSettings();
+ }, []);
const getTimestampEnterChat = async () => {
try {
return new Promise((res, rej) => {
- chrome.runtime.sendMessage(
+ chrome?.runtime?.sendMessage(
{
action: "getTimestampEnterChat",
},
@@ -361,6 +498,29 @@ export const Group = ({
});
} catch (error) {}
};
+ const getGroupDataSingle = async (groupId) => {
+ try {
+ return new Promise((res, rej) => {
+ chrome?.runtime?.sendMessage(
+ {
+ action: "getGroupDataSingle",
+ payload: {
+ groupId,
+ },
+ },
+ (response) => {
+ if (!response?.error) {
+ res(response);
+ return;
+ }
+ rej(response.error);
+ }
+ );
+ });
+ } catch (error) {
+ return {};
+ }
+ };
const refreshHomeDataFunc = () => {
setGroupSection("default");
setTimeout(() => {
@@ -371,12 +531,11 @@ export const Group = ({
const getGroupAnnouncements = async () => {
try {
return new Promise((res, rej) => {
- chrome.runtime.sendMessage(
+ chrome?.runtime?.sendMessage(
{
action: "getGroupNotificationTimestamp",
},
(response) => {
-
if (!response?.error) {
setGroupAnnouncements(response);
res(response);
@@ -390,11 +549,10 @@ export const Group = ({
const getGroupOwner = async (groupId) => {
try {
-
const url = `${getBaseApiReact()}/groups/${groupId}`;
const response = await fetch(url);
let data = await response.json();
-
+
const name = await getNameInfo(data?.owner);
if (name) {
data.name = name;
@@ -458,6 +616,37 @@ export const Group = ({
return hasUnread;
}, [timestampEnterData, directs, myAddress]);
+ const groupChatHasUnread = useMemo(() => {
+ let hasUnread = false;
+ groups.forEach((group) => {
+ if (
+ group?.data &&
+ isExtMsg(group?.data) &&
+ group?.sender !== myAddress &&
+ group?.timestamp &&
+ ((!timestampEnterData[group?.groupId] &&
+ Date.now() - group?.timestamp < timeDifferenceForNotificationChats) ||
+ timestampEnterData[group?.groupId] < group?.timestamp)
+ ) {
+ hasUnread = true;
+ }
+ });
+ return hasUnread;
+ }, [timestampEnterData, groups, myAddress]);
+
+ const groupsAnnHasUnread = useMemo(() => {
+ let hasUnread = false;
+ groups.forEach((group) => {
+ if (
+ groupAnnouncements[group?.groupId] &&
+ !groupAnnouncements[group?.groupId]?.seentimestamp
+ ) {
+ hasUnread = true;
+ }
+ });
+ return hasUnread;
+ }, [groupAnnouncements, groups]);
+
// useEffect(() => {
// if (!myAddress) return;
// checkGroupListFunc(myAddress);
@@ -471,15 +660,15 @@ export const Group = ({
const getPublishesFromAdmins = async (admins: string[]) => {
// const validApi = await findUsableApi();
const queryString = admins.map((name) => `name=${name}`).join("&");
- const url = `${getBaseApiReact()}/arbitrary/resources/search?mode=ALL&service=DOCUMENT_PRIVATE&identifier=symmetric-qchat-group-${
+ const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT_PRIVATE&identifier=symmetric-qchat-group-${
selectedGroup?.groupId
- }&exactmatchnames=true&limit=0&reverse=true&${queryString}`;
+ }&exactmatchnames=true&limit=0&reverse=true&${queryString}&prefix=true`;
const response = await fetch(url);
- if(!response.ok){
- throw new Error('network error')
+ if (!response.ok) {
+ throw new Error("network error");
}
const adminData = await response.json();
-
+
const filterId = adminData.filter(
(data: any) =>
data.identifier === `symmetric-qchat-group-${selectedGroup?.groupId}`
@@ -491,18 +680,42 @@ export const Group = ({
// Get the most recent date for both a and b
const dateA = a.updated ? new Date(a.updated) : new Date(a.created);
const dateB = b.updated ? new Date(b.updated) : new Date(b.created);
-
+
// Sort by most recent
return dateB.getTime() - dateA.getTime();
});
-
+
return sortedData[0];
};
- const getSecretKey = async (loadingGroupParam?: boolean, secretKeyToPublish?: boolean) => {
+ const getSecretKey = async (
+ loadingGroupParam?: boolean,
+ secretKeyToPublish?: boolean
+ ) => {
try {
- pauseAllQueues()
-
- if(secretKeyToPublish && secretKey && lastFetchedSecretKey.current && Date.now() - lastFetchedSecretKey.current < 1800000) return secretKey
+ setIsLoadingGroupMessage("Locating encryption keys");
+ // setGroupDataLastSet(null)
+ pauseAllQueues();
+ let dataFromStorage;
+ let publishFromStorage;
+ let adminsFromStorage;
+ // const groupData = await getGroupDataSingle(selectedGroup?.groupId);
+ // if (
+ // groupData?.secretKeyData &&
+ // Date.now() - groupData?.timestampLastSet < 3600000
+ // ) {
+ // dataFromStorage = groupData.secretKeyData;
+ // publishFromStorage = groupData.secretKeyResource;
+ // adminsFromStorage = groupData.admins;
+ // // setGroupDataLastSet(groupData.timestampLastSet)
+ // }
+
+ if (
+ secretKeyToPublish &&
+ secretKey &&
+ lastFetchedSecretKey.current &&
+ Date.now() - lastFetchedSecretKey.current < 1800000
+ )
+ return secretKey;
if (loadingGroupParam) {
setIsLoadingGroup(true);
}
@@ -514,13 +727,16 @@ export const Group = ({
}
const prevGroupId = selectedGroupRef.current.groupId;
// const validApi = await findUsableApi();
- const groupAdmins = await getGroupAdimns(selectedGroup?.groupId);
- setAdmins(groupAdmins)
- if(!groupAdmins.length){
- throw new Error('Network error')
+ const { names, addresses, both } =
+ adminsFromStorage || (await getGroupAdimns(selectedGroup?.groupId));
+ setAdmins(addresses);
+ setAdminsWithNames(both);
+ if (!names.length) {
+ throw new Error("Network error");
}
- const publish = await getPublishesFromAdmins(groupAdmins);
-
+ const publish =
+ publishFromStorage || (await getPublishesFromAdmins(names));
+
if (prevGroupId !== selectedGroupRef.current.groupId) {
if (settimeoutForRefetchSecretKey.current) {
clearTimeout(settimeoutForRefetchSecretKey.current);
@@ -535,59 +751,76 @@ export const Group = ({
return false;
}
setSecretKeyPublishDate(publish?.updated || publish?.created);
-
- const res = await fetch(
- `${getBaseApiReact()}/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${
- publish.identifier
- }?encoding=base64`
- );
- const data = await res.text();
-
+ let data;
+ if (dataFromStorage) {
+ data = dataFromStorage;
+ } else {
+ setIsLoadingGroupMessage("Downloading encryption keys");
+ const res = await fetch(
+ `${getBaseApiReact()}/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${
+ publish.identifier
+ }?encoding=base64`
+ );
+ data = await res.text();
+ }
+
const decryptedKey: any = await decryptResource(data);
-
+
const dataint8Array = base64ToUint8Array(decryptedKey.data);
const decryptedKeyToObject = uint8ArrayToObject(dataint8Array);
-
+
if (!validateSecretKey(decryptedKeyToObject))
throw new Error("SecretKey is not valid");
setSecretKeyDetails(publish);
setSecretKey(decryptedKeyToObject);
- lastFetchedSecretKey.current = Date.now()
+ lastFetchedSecretKey.current = Date.now();
setMemberCountFromSecretKeyData(decryptedKey.count);
+ chrome?.runtime?.sendMessage({
+ action: "setGroupData",
+ payload: {
+ groupId: selectedGroup?.groupId,
+ secretKeyData: data,
+ secretKeyResource: publish,
+ admins: { names, addresses, both },
+ },
+ });
if (decryptedKeyToObject) {
setTriedToFetchSecretKey(true);
- setFirstSecretKeyInCreation(false)
+ setFirstSecretKeyInCreation(false);
return decryptedKeyToObject;
} else {
setTriedToFetchSecretKey(true);
}
-
} catch (error) {
- if(error === 'Unable to decrypt data'){
+ if (error === "Unable to decrypt data") {
setTriedToFetchSecretKey(true);
settimeoutForRefetchSecretKey.current = setTimeout(() => {
getSecretKey();
}, 120000);
}
-
} finally {
setIsLoadingGroup(false);
- if(!secretKeyToPublish){
- await getAdmins(selectedGroup?.groupId);
-
+ setIsLoadingGroupMessage("");
+ if (!secretKeyToPublish) {
+ // await getAdmins(selectedGroup?.groupId);
}
- resumeAllQueues()
-
+ resumeAllQueues();
}
};
+ useEffect(()=> {
+ if(!selectedGroup) return
+ getGroupOwner(selectedGroup?.groupId);
+ }, [selectedGroup])
+
+
useEffect(() => {
- if (selectedGroup) {
+ if (selectedGroup && groupOwner && groupOwner?.isOpen === false) {
setTriedToFetchSecretKey(false);
getSecretKey(true);
- getGroupOwner(selectedGroup?.groupId);
+ // getGroupOwner(selectedGroup?.groupId);
}
- }, [selectedGroup]);
+ }, [selectedGroup, groupOwner]);
// const handleNotification = async (data)=> {
// try {
@@ -596,7 +829,7 @@ export const Group = ({
// }
// const newActiveChats= data
// const oldActiveChats = await new Promise((res, rej) => {
- // chrome.runtime.sendMessage(
+ // chrome?.runtime?.sendMessage(
// {
// action: "getChatHeads",
// },
@@ -627,7 +860,7 @@ export const Group = ({
// if(results?.length > 0){
// if (!lastGroupNotification.current || (Date.now() - lastGroupNotification.current >= 60000)) {
// console.log((Date.now() - lastGroupNotification.current >= 60000), lastGroupNotification.current)
- // chrome.runtime.sendMessage(
+ // chrome?.runtime?.sendMessage(
// {
// action: "notification",
// payload: {
@@ -650,7 +883,7 @@ export const Group = ({
// } catch (error) {
// console.log('error not', error)
// if(!isFocusedRef.current){
- // chrome.runtime.sendMessage(
+ // chrome?.runtime?.sendMessage(
// {
// action: "notification",
// payload: {
@@ -670,7 +903,7 @@ export const Group = ({
// } finally {
- // chrome.runtime.sendMessage(
+ // chrome?.runtime?.sendMessage(
// {
// action: "setChatHeads",
// payload: {
@@ -693,16 +926,15 @@ export const Group = ({
useEffect(() => {
// Listen for messages from the background script
- chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
-
+ chrome?.runtime?.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === "SET_GROUPS") {
// Update the component state with the received 'sendqort' state
setGroups(message.payload);
-
+
setMemberGroups(message.payload);
if (selectedGroupRef.current && groupSectionRef.current === "chat") {
- chrome.runtime.sendMessage({
+ chrome?.runtime?.sendMessage({
action: "addTimestampEnterChat",
payload: {
timestamp: Date.now(),
@@ -711,7 +943,7 @@ export const Group = ({
});
}
if (selectedDirectRef.current) {
- chrome.runtime.sendMessage({
+ chrome?.runtime?.sendMessage({
action: "addTimestampEnterChat",
payload: {
timestamp: Date.now(),
@@ -731,7 +963,7 @@ export const Group = ({
selectedGroupRef.current &&
groupSectionRef.current === "announcement"
) {
- chrome.runtime.sendMessage({
+ chrome?.runtime?.sendMessage({
action: "addGroupNotificationTimestamp",
payload: {
timestamp: Date.now(),
@@ -748,7 +980,7 @@ export const Group = ({
setDirects(message.payload);
// if (selectedGroupRef.current) {
- // chrome.runtime.sendMessage({
+ // chrome?.runtime?.sendMessage({
// action: "addTimestampEnterChat",
// payload: {
// timestamp: Date.now(),
@@ -775,27 +1007,30 @@ export const Group = ({
)
return;
- chrome.runtime.sendMessage({ action: "setupGroupWebsocket" });
+ chrome?.runtime?.sendMessage({ action: "setupGroupWebsocket" });
hasInitializedWebsocket.current = true;
}, [myAddress, groups]);
-
const getMembers = async (groupId) => {
try {
const res = await getGroupMembers(groupId);
- if(groupId !== selectedGroupRef.current?.groupId) return
+ if (groupId !== selectedGroupRef.current?.groupId) return;
setMembers(res);
} catch (error) {}
};
useEffect(() => {
- if (selectedGroup?.groupId) {
+ if (
+ !initiatedGetMembers.current &&
+ selectedGroup?.groupId &&
+ secretKey &&
+ admins.includes(myAddress)
+ ) {
// getAdmins(selectedGroup?.groupId);
getMembers(selectedGroup?.groupId);
+ initiatedGetMembers.current = true;
}
- }, [selectedGroup?.groupId]);
-
-
+ }, [selectedGroup?.groupId, secretKey, myAddress, admins]);
const shouldReEncrypt = useMemo(() => {
if (triedToFetchSecretKey && !secretKeyPublishDate) return true;
@@ -809,13 +1044,13 @@ export const Group = ({
memberCountFromSecretKeyData !== members?.memberCount &&
newEncryptionNotification?.text?.data?.numberOfMembers !==
members?.memberCount;
-
+
if (isDiffMemberNumber) return true;
const latestJoined = members?.members.reduce((maxJoined, current) => {
return current.joined > maxJoined ? current.joined : maxJoined;
}, members?.members[0].joined);
-
+
if (
secretKeyPublishDate < latestJoined &&
newEncryptionNotification?.data?.timestamp < latestJoined
@@ -835,7 +1070,7 @@ export const Group = ({
try {
setIsLoadingNotifyAdmin(true);
await new Promise((res, rej) => {
- chrome.runtime.sendMessage(
+ chrome?.runtime?.sendMessage(
{
action: "notifyAdminRegenerateSecretKey",
payload: {
@@ -844,7 +1079,6 @@ export const Group = ({
},
},
(response) => {
-
if (!response?.error) {
res(response);
}
@@ -872,6 +1106,7 @@ export const Group = ({
.filter((group) => group?.sender !== myAddress)
.find((gr) => gr?.groupId === selectedGroup?.groupId);
if (!findGroup) return false;
+ if (!findGroup?.data || !isExtMsg(findGroup?.data)) return false;
return (
findGroup?.timestamp &&
((!timestampEnterData[selectedGroup?.groupId] &&
@@ -882,7 +1117,6 @@ export const Group = ({
}, [timestampEnterData, selectedGroup]);
const isUnread = useMemo(() => {
-
if (!selectedGroup) return false;
return (
groupAnnouncements?.[selectedGroup?.groupId]?.seentimestamp === false
@@ -893,7 +1127,7 @@ export const Group = ({
if (isLoadingOpenSectionFromNotification.current) return;
isLoadingOpenSectionFromNotification.current = true;
const directAddress = e.detail?.from;
-
+
const findDirect = directs?.find(
(direct) => direct?.address === directAddress
);
@@ -904,11 +1138,11 @@ export const Group = ({
if (findDirect) {
setChatMode("directs");
setSelectedDirect(null);
- setSelectedGroup(null);
+ // setSelectedGroup(null);
setNewChat(false);
- chrome.runtime.sendMessage({
+ chrome?.runtime?.sendMessage({
action: "addTimestampEnterChat",
payload: {
timestamp: Date.now(),
@@ -926,6 +1160,54 @@ export const Group = ({
}
};
+ const openDirectChatFromInternal = (e) => {
+ const directAddress = e.detail?.address;
+ const name = e.detail?.name;
+ const findDirect = directs?.find(
+ (direct) => direct?.address === directAddress || direct?.name === name
+ );
+
+ if (findDirect) {
+ setChatMode("directs");
+ setSelectedDirect(null);
+ // setSelectedGroup(null);
+
+ setNewChat(false);
+
+ chrome?.runtime?.sendMessage({
+ action: "addTimestampEnterChat",
+ payload: {
+ timestamp: Date.now(),
+ groupId: findDirect.address,
+ },
+ });
+
+ setTimeout(() => {
+ setSelectedDirect(findDirect);
+ getTimestampEnterChat();
+ }, 200);
+ } else {
+ setChatMode("directs");
+ setNewChat(true);
+ setTimeout(() => {
+ executeEvent("setDirectToValueNewChat", {
+ directToValue: name || directAddress,
+ });
+ }, 500);
+ }
+ };
+
+ useEffect(() => {
+ subscribeToEvent("openDirectMessageInternal", openDirectChatFromInternal);
+
+ return () => {
+ unsubscribeFromEvent(
+ "openDirectMessageInternal",
+ openDirectChatFromInternal
+ );
+ };
+ }, [directs, selectedDirect]);
+
useEffect(() => {
subscribeToEvent("openDirectMessage", openDirectChatFromNotification);
@@ -934,10 +1216,41 @@ export const Group = ({
};
}, [directs, selectedDirect]);
+ const handleMarkAsRead = (e) => {
+ const { groupId } = e.detail;
+ chrome?.runtime?.sendMessage({
+ action: "addTimestampEnterChat",
+ payload: {
+ timestamp: Date.now(),
+ groupId,
+ },
+ });
+
+ chrome?.runtime?.sendMessage({
+ action: "addGroupNotificationTimestamp",
+ payload: {
+ timestamp: Date.now(),
+ groupId,
+ },
+ });
+ setTimeout(() => {
+ getGroupAnnouncements();
+ getTimestampEnterChat();
+ }, 200);
+ };
+
+ useEffect(() => {
+ subscribeToEvent("markAsRead", handleMarkAsRead);
+
+ return () => {
+ unsubscribeFromEvent("markAsRead", handleMarkAsRead);
+ };
+ }, []);
+
const resetAllStatesAndRefs = () => {
// Reset all useState values to their initial states
setSecretKey(null);
- lastFetchedSecretKey.current = null
+ lastFetchedSecretKey.current = null;
setSecretKeyPublishDate(null);
setSecretKeyDetails(null);
setNewEncryptionNotification(null);
@@ -951,10 +1264,11 @@ export const Group = ({
setMembers([]);
setGroupOwner(null);
setTriedToFetchSecretKey(false);
+ setHideCommonKeyPopup(false);
setOpenAddGroup(false);
setIsInitialGroups(false);
setOpenManageMembers(false);
- setMemberGroups([]); // Assuming you're clearing the context here as well
+ setMemberGroups([]); // Assuming you're clearing the context here as well
setTimestampEnterData({});
setChatMode("groups");
setNewChat(false);
@@ -967,7 +1281,7 @@ export const Group = ({
setGroupSection("home");
setGroupAnnouncements({});
setDefaultThread(null);
-
+ setMobileViewMode("home");
// Reset all useRef values to their initial states
hasInitialized.current = false;
hasInitializedWebsocket.current = false;
@@ -980,13 +1294,13 @@ export const Group = ({
isLoadingOpenSectionFromNotification.current = false;
setupGroupWebsocketInterval.current = null;
settimeoutForRefetchSecretKey.current = null;
+ initiatedGetMembers.current = false;
};
-
- const logoutEventFunc = ()=> {
- resetAllStatesAndRefs()
- clearStatesMessageQueueProvider()
- }
+ const logoutEventFunc = () => {
+ resetAllStatesAndRefs();
+ clearStatesMessageQueueProvider();
+ };
useEffect(() => {
subscribeToEvent("logout-event", logoutEventFunc);
@@ -1013,7 +1327,8 @@ export const Group = ({
setNewChat(false);
setSecretKey(null);
- lastFetchedSecretKey.current = null
+ setGroupOwner(null)
+ lastFetchedSecretKey.current = null;
setSecretKeyPublishDate(null);
setAdmins([]);
setSecretKeyDetails(null);
@@ -1021,10 +1336,10 @@ export const Group = ({
setMembers([]);
setMemberCountFromSecretKeyData(null);
setTriedToFetchSecretKey(false);
- setFirstSecretKeyInCreation(false)
+ setFirstSecretKeyInCreation(false);
setGroupSection("chat");
- chrome.runtime.sendMessage({
+ chrome?.runtime?.sendMessage({
action: "addTimestampEnterChat",
payload: {
timestamp: Date.now(),
@@ -1034,7 +1349,7 @@ export const Group = ({
setTimeout(() => {
setSelectedGroup(findGroup);
-
+ setMobileViewMode("group");
getTimestampEnterChat();
isLoadingOpenSectionFromNotification.current = false;
}, 200);
@@ -1052,7 +1367,6 @@ export const Group = ({
}, [groups, selectedGroup]);
const openGroupAnnouncementFromNotification = (e) => {
-
const groupId = e.detail?.from;
const findGroup = groups?.find((group) => +group?.groupId === +groupId);
@@ -1061,7 +1375,8 @@ export const Group = ({
setChatMode("groups");
setSelectedGroup(null);
setSecretKey(null);
- lastFetchedSecretKey.current = null
+ setGroupOwner(null)
+ lastFetchedSecretKey.current = null;
setSecretKeyPublishDate(null);
setAdmins([]);
setSecretKeyDetails(null);
@@ -1069,9 +1384,9 @@ export const Group = ({
setMembers([]);
setMemberCountFromSecretKeyData(null);
setTriedToFetchSecretKey(false);
- setFirstSecretKeyInCreation(false)
+ setFirstSecretKeyInCreation(false);
setGroupSection("announcement");
- chrome.runtime.sendMessage({
+ chrome?.runtime?.sendMessage({
action: "addGroupNotificationTimestamp",
payload: {
timestamp: Date.now(),
@@ -1080,6 +1395,7 @@ export const Group = ({
});
setTimeout(() => {
setSelectedGroup(findGroup);
+ setMobileViewMode("group");
getGroupAnnouncements();
}, 200);
@@ -1118,7 +1434,8 @@ export const Group = ({
setChatMode("groups");
setSelectedGroup(null);
setSecretKey(null);
- lastFetchedSecretKey.current = null
+ setGroupOwner(null)
+ lastFetchedSecretKey.current = null;
setSecretKeyPublishDate(null);
setAdmins([]);
setSecretKeyDetails(null);
@@ -1126,13 +1443,13 @@ export const Group = ({
setMembers([]);
setMemberCountFromSecretKeyData(null);
setTriedToFetchSecretKey(false);
- setFirstSecretKeyInCreation(false)
+ setFirstSecretKeyInCreation(false);
setGroupSection("forum");
setDefaultThread(data);
setTimeout(() => {
setSelectedGroup(findGroup);
-
+ setMobileViewMode("group");
getGroupAnnouncements();
}, 200);
}
@@ -1146,257 +1463,586 @@ export const Group = ({
};
}, [groups, selectedGroup]);
- const handleSecretKeyCreationInProgress = ()=> {
- setFirstSecretKeyInCreation(true)
- }
+ const handleSecretKeyCreationInProgress = () => {
+ setFirstSecretKeyInCreation(true);
+ };
+ const goToHome = async () => {
+ if (isMobile) {
+ setMobileViewMode("home");
+ }
+ if (!isMobile) {
+ }
+ setGroupSection("default");
+ clearAllQueues();
+ await new Promise((res) => {
+ setTimeout(() => {
+ res(null);
+ }, 200);
+ });
+ setGroupSection("home");
+ setSelectedGroup(null);
+ setNewChat(false);
+ setSelectedDirect(null);
+ setSecretKey(null);
+ setGroupOwner(null)
+ lastFetchedSecretKey.current = null;
+ setSecretKeyPublishDate(null);
+ setAdmins([]);
+ setSecretKeyDetails(null);
+ setAdminsWithNames([]);
+ setMembers([]);
+ setMemberCountFromSecretKeyData(null);
+ setTriedToFetchSecretKey(false);
+ setFirstSecretKeyInCreation(false);
+ };
+ const goToAnnouncements = async () => {
+ setGroupSection("default");
+ await new Promise((res) => {
+ setTimeout(() => {
+ res(null);
+ }, 200);
+ });
+ setSelectedDirect(null);
+ setNewChat(false);
+ setGroupSection("announcement");
+ chrome?.runtime?.sendMessage({
+ action: "addGroupNotificationTimestamp",
+ payload: {
+ timestamp: Date.now(),
+ groupId: selectedGroupRef.current.groupId,
+ },
+ });
+ setTimeout(() => {
+ getGroupAnnouncements();
+ }, 200);
+ };
- return (
- <>
-
-
+ const openDrawerGroups = () => {
+ setIsOpenDrawer(true);
+ setDrawerMode("groups");
+ };
+ const goToThreads = () => {
+ setSelectedDirect(null);
+ setNewChat(false);
+ setGroupSection("forum");
+ };
+
+ const goToChat = async () => {
+ setGroupSection("default");
+ await new Promise((res) => {
+ setTimeout(() => {
+ res(null);
+ }, 200);
+ });
+ setGroupSection("chat");
+ setNewChat(false);
+ setSelectedDirect(null);
+ if (selectedGroupRef.current) {
+ chrome?.runtime?.sendMessage({
+ action: "addTimestampEnterChat",
+ payload: {
+ timestamp: Date.now(),
+ groupId: selectedGroupRef.current.groupId,
+ },
+ });
+
+ setTimeout(() => {
+ getTimestampEnterChat();
+ }, 200);
+ }
+ };
+
+ const renderDirects = () => {
+ return (
+ {isMobile && (
+
+
+
+
+
+
+
+ {
+ setMobileViewModeKeepOpen('')
+ }}
+ >
+
+
+
+
+
+ )}
-
-
{
- setChatMode((prev) =>
- prev === "directs" ? "groups" : "directs"
- );
- setNewChat(false);
- setSelectedDirect(null);
- setSelectedGroup(null);
- setGroupSection("default");
+ {directs.map((direct: any) => (
+
- {chatMode === "groups" && (
- <>
-
+ //
+ //
+ // }
+ onClick={() => {
+ setSelectedDirect(null);
+ setNewChat(false);
+ // setSelectedGroup(null);
+ setIsOpenDrawer(false);
+ chrome?.runtime?.sendMessage({
+ action: "addTimestampEnterChat",
+ payload: {
+ timestamp: Date.now(),
+ groupId: direct.address,
+ },
+ });
+ setTimeout(() => {
+ setSelectedDirect(direct);
+
+ getTimestampEnterChat();
+ }, 200);
+ }}
+ sx={{
+ display: "flex",
+ width: "100%",
+ flexDirection: "column",
+ cursor: "pointer",
+ border: "1px #232428 solid",
+ padding: "2px",
+ borderRadius: "2px",
+ background:
+ direct?.address === selectedDirect?.address && "white",
+ }}
+ >
+
+
+
+ {(direct?.name || direct?.address)?.charAt(0)}
+
+
+
- >
- )}
- {chatMode === "directs" ? "Switch to groups" : "Direct msgs"}
-
-
-
- {directs.map((direct: any) => (
-
-
- //
- //
- // }
- onClick={() => {
- setSelectedDirect(null);
- setNewChat(false);
- setSelectedGroup(null);
- chrome.runtime.sendMessage({
- action: "addTimestampEnterChat",
- payload: {
- timestamp: Date.now(),
- groupId: direct.address,
- },
- });
- setTimeout(() => {
- setSelectedDirect(direct);
-
- getTimestampEnterChat();
- }, 200);
- }}
- sx={{
- display: "flex",
- width: "100%",
- flexDirection: "column",
- cursor: "pointer",
- border: "1px #232428 solid",
- padding: "2px",
- borderRadius: "2px",
- background:
- direct?.address === selectedDirect?.address && "white",
- }}
- >
-
-
-
- {(direct?.name || direct?.address)?.charAt(0)}
-
-
-
- {direct?.sender !== myAddress &&
- direct?.timestamp &&
- ((!timestampEnterData[direct?.address] &&
- Date.now() - direct?.timestamp <
- timeDifferenceForNotificationChats) ||
- timestampEnterData[direct?.address] <
- direct?.timestamp) && (
-
- )}
-
-
-
- ))}
-
-
+ )}
+
+
+
+ ))}
+
+
+ {
+ setNewChat(true);
+ setSelectedDirect(null);
+ // setSelectedGroup(null);
+ setIsOpenDrawer(false);
}}
>
- {groups.map((group: any) => (
-
-
- //
- //
- // }
- onClick={() => {
- clearAllQueues()
- setSelectedDirect(null);
+
+ New Chat
+
+
+
+ );
+ };
- setNewChat(false);
- setSelectedGroup(null);
- setSecretKey(null);
- lastFetchedSecretKey.current = null
- setSecretKeyPublishDate(null);
- setAdmins([]);
- setSecretKeyDetails(null);
- setAdminsWithNames([]);
- setMembers([]);
- setMemberCountFromSecretKeyData(null);
- setTriedToFetchSecretKey(false);
- setFirstSecretKeyInCreation(false)
- setGroupSection("announcement");
-
- setTimeout(() => {
- setSelectedGroup(group);
-
- getTimestampEnterChat();
- }, 200);
-
- if (groupSectionRef.current === "announcement") {
- chrome.runtime.sendMessage({
- action: "addGroupNotificationTimestamp",
- payload: {
- timestamp: Date.now(),
- groupId: group.groupId,
- },
- });
- }
-
- setTimeout(() => {
- getGroupAnnouncements();
- }, 600);
+ const renderGroups = () => {
+ return (
+
+ {/*
+ {isMobile && (
+
+ {
+ setIsOpenDrawer(false);
+ }}
+ sx={{
+ cursor: "pointer",
+ color: "white",
+ }}
+ />
+
+ )}
+ {
+ setChatMode((prev) =>
+ prev === "directs" ? "groups" : "directs"
+ );
+
+ }}
+ sx={{
+ backgroundColor: chatMode === 'directs' && ( groupChatHasUnread || groupsAnnHasUnread) ? 'red' : 'revert'
+ }}
+ >
+ {chatMode === "groups" && (
+ <>
+
+ >
+ )}
+
+ {chatMode === "directs" ? "Switch to groups" : "Direct msgs"}
+
+
*/}
+ {/*
+ {directs.map((direct: any) => (
+
+
+ //
+ //
+ // }
+ onClick={() => {
+ setSelectedDirect(null);
+ setNewChat(false);
+ // setSelectedGroup(null);
+ setIsOpenDrawer(false);
+ chrome?.runtime?.sendMessage({
+ action: "addTimestampEnterChat",
+ payload: {
+ timestamp: Date.now(),
+ groupId: direct.address,
+ },
+ });
+ setTimeout(() => {
+ setSelectedDirect(direct);
+
+ getTimestampEnterChat();
+ }, 200);
+ }}
+ sx={{
+ display: "flex",
+ width: "100%",
+ flexDirection: "column",
+ cursor: "pointer",
+ border: "1px #232428 solid",
+ padding: "2px",
+ borderRadius: "2px",
+ background:
+ direct?.address === selectedDirect?.address && "white",
+ }}
+ >
+
+
+
+ {(direct?.name || direct?.address)?.charAt(0)}
+
+
+
+ {direct?.sender !== myAddress &&
+ direct?.timestamp &&
+ ((!timestampEnterData[direct?.address] &&
+ Date.now() - direct?.timestamp <
+ timeDifferenceForNotificationChats) ||
+ timestampEnterData[direct?.address] <
+ direct?.timestamp) && (
+
+ )}
+
+
+
+ ))}
+
*/}
+
+ {groups.map((group: any) => (
+
+
+ //
+ //
+ // }
+ onClick={() => {
+ setMobileViewMode("group");
+ clearAllQueues();
+ setSelectedDirect(null);
+ setTriedToFetchSecretKey(false);
+ setNewChat(false);
+ setSelectedGroup(null);
+ setSecretKey(null);
+ lastFetchedSecretKey.current = null;
+ setSecretKeyPublishDate(null);
+ setAdmins([]);
+ setSecretKeyDetails(null);
+ setAdminsWithNames([]);
+ setGroupOwner(null)
+ setMembers([]);
+ setMemberCountFromSecretKeyData(null);
+ setHideCommonKeyPopup(false);
+ setFirstSecretKeyInCreation(false);
+ // setGroupSection("announcement");
+ setGroupSection("chat");
+ setIsOpenDrawer(false);
+ setTimeout(() => {
+ setSelectedGroup(group);
+
+ // getTimestampEnterChat();
+ }, 200);
+
+ chrome?.runtime?.sendMessage({
+ action: "addTimestampEnterChat",
+ payload: {
+ timestamp: Date.now(),
+ groupId: group.groupId,
+ },
+ });
+
+ setTimeout(() => {
+ getTimestampEnterChat();
+ }, 200);
+
+ // if (groupSectionRef.current === "announcement") {
+ // chrome?.runtime?.sendMessage({
+ // action: "addGroupNotificationTimestamp",
+ // payload: {
+ // timestamp: Date.now(),
+ // groupId: group.groupId,
+ // },
+ // });
+ // }
+
+ // setTimeout(() => {
+ // getGroupAnnouncements();
+ // }, 600);
+ }}
+ sx={{
+ display: "flex",
+ width: "100%",
+ flexDirection: "column",
+ cursor: "pointer",
+ border: "1px #232428 solid",
+ padding: "2px",
+ borderRadius: "2px",
+ background:
+ group?.groupId === selectedGroup?.groupId && "white",
+ }}
+ >
+
@@ -1442,7 +2088,9 @@ export const Group = ({
}}
/>
)}
- {group?.sender !== myAddress &&
+ {group?.data &&
+ isExtMsg(group?.data) &&
+ group?.sender !== myAddress &&
group?.timestamp &&
((!timestampEnterData[group?.groupId] &&
Date.now() - group?.timestamp <
@@ -1456,330 +2104,632 @@ export const Group = ({
/>
)}
-
-
- ))}
-
-
- {chatMode === "groups" && (
-
{
- setOpenAddGroup(true);
- }}
- >
-
- Add Group
-
- )}
- {chatMode === "directs" && (
-
{
- setNewChat(true);
- setSelectedDirect(null);
- setSelectedGroup(null);
- }}
- >
-
- New Chat
-
- )}
-
+
+
+
+ ))}
-
- {newChat && (
- <>
-
- >
- )}
- {selectedGroup && !newChat && (
- <>
-
+ {chatMode === "groups" && (
+ {
+ setOpenAddGroup(true);
}}
>
- {triedToFetchSecretKey && (
-
- )}
- {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...
-
-
- )}
- {!admins.includes(myAddress) &&
- !secretKey &&
- triedToFetchSecretKey ? (
- <>
- {(secretKeyPublishDate || !secretKeyPublishDate && !firstSecretKeyInCreation) ? (
-
+ Add Group
+
+ )}
+ {chatMode === "directs" && (
+ {
+ setNewChat(true);
+ setSelectedDirect(null);
+ // setSelectedGroup(null);
+ setIsOpenDrawer(false);
+ }}
+ >
+
+ New Chat
+
+ )}
+
+
+ );
+ };
+
+ return (
+ <>
+
+
+
+ {isMobile && (
+
+ )}
+
+
+
+ {!isMobile && desktopSideView === 'groups' && renderGroups()}
+ {!isMobile && desktopSideView === 'directs' && renderDirects()}
+
+
+
+
+ {mobileViewMode === "groups" && !mobileViewModeKeepOpen && renderGroups()}
+
+ {mobileViewModeKeepOpen === "messaging" && renderDirects()}
+ {newChat && (
+ <>
+ {isMobile && (
+
+
- {" "}
-
- You are not part of the encrypted group of members. Wait
- until an admin re-encrypts the keys.
-
-
-
- Try notifying an admin from the list of admins below:
-
-
- {adminsWithNames.map((admin) => {
-
- return (
-
- {admin?.name}
- notifyAdmin(admin)}
- >
- Notify
-
-
- );
- })}
-
- ) : null}
- >
-
- ) : admins.includes(myAddress) &&
- !secretKey &&
- triedToFetchSecretKey ? null : !triedToFetchSecretKey ? null : (
- <>
-
+
+ {
+ close()
+ }}
+ >
+
+
+
+
+
+ {
+ setSelectedDirect(null)
+ setMobileViewModeKeepOpen('')
+ }}
+ >
+
+
+
+
+
+ )}
+
+ {
+ setSelectedDirect(null);
-
+
+ >
+ )}
+ {selectedGroup && (
+ <>
+ {!isMobile && selectedGroup && (
+
+
+
+ )}
+ {isMobile && (
+
+
+
+ {
+ setMobileViewMode("groups");
+ }}
+ >
+
+
+
+
+ {selectedGroup?.groupName}
+
+
+ {/* */}
+
+
+
+ )}
+
+ {isMobile && mobileViewMode === "group" && (
+ <>
+
+ >
+ )}
+
+ {triedToFetchSecretKey && (
+
-
- >
- )}
-
-
- {admins.includes(myAddress) &&
- shouldReEncrypt &&
- triedToFetchSecretKey && !firstSecretKeyInCreation && (
-
+ )}
+ {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...
+
+
)}
-
-
- {openManageMembers && (
-
- )}
+ {!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.
+
+
+
+ Try notifying an admin from the list of admins below:
+
+
+ {adminsWithNames.map((admin) => {
+ return (
+
+ {admin?.name}
+ notifyAdmin(admin)}
+ >
+ Notify
+
+
+ );
+ })}
+
+ ) : null}
+ >
+ ) : admins.includes(myAddress) &&
+ !secretKey &&
+ triedToFetchSecretKey ? null : !triedToFetchSecretKey ? null : (
+ <>
+
+
+ >
+ )}
-
- >
- )}
-
- {selectedDirect && !newChat && (
- <>
-
-
-
- >
- )}
- {!selectedDirect &&
- !selectedGroup &&
- !newChat &&
- groupSection === "home" && (
-
-
- }
- onClick={refreshHomeDataFunc}
+
- Refresh home data
-
+ {admins.includes(myAddress) &&
+ shouldReEncrypt &&
+ triedToFetchSecretKey &&
+ !firstSecretKeyInCreation &&
+ !hideCommonKeyPopup && (
+
+ )}
+
+ {openManageMembers && (
+
+ )}
+ >
+ )}
+ {selectedDirect && !newChat && (
+ <>
-
-
-
-
+
+ {
+ setSelectedDirect(null);
+
+ setNewChat(false);
+ }}
+ setMobileViewModeKeepOpen={setMobileViewModeKeepOpen}
+ />
+
-
+ >
)}
-
+ )}
+ {isMobile && mobileViewMode === "home" && (
+
+ )}
+ {
+ !isMobile && !selectedGroup &&
+ groupSection === "home" && (
+
+
+ )}
+
+
+ {/*
+
+
+
+ Home
+
+
+ {selectedGroup && (
+ <>
{
- setGroupSection("default");
- clearAllQueues()
- await new Promise((res) => {
- setTimeout(() => {
- res(null);
- }, 200);
- });
- setGroupSection("home");
- setSelectedGroup(null);
- setNewChat(false);
- setSelectedDirect(null);
- setSecretKey(null);
- lastFetchedSecretKey.current = null
- setSecretKeyPublishDate(null);
- setAdmins([]);
- setSecretKeyDetails(null);
- setAdminsWithNames([]);
- setMembers([]);
- setMemberCountFromSecretKeyData(null);
- setTriedToFetchSecretKey(false);
- setFirstSecretKeyInCreation(false)
- }}
- >
-
-
- Home
-
-
- {selectedGroup && (
- <>
-
- {
- setGroupSection("default");
- await new Promise((res) => {
- setTimeout(() => {
- res(null);
- }, 200);
- });
- setGroupSection("announcement");
- chrome.runtime.sendMessage({
- action: "addGroupNotificationTimestamp",
- payload: {
- timestamp: Date.now(),
- groupId: selectedGroupRef.current.groupId,
- },
- });
- setTimeout(() => {
- getGroupAnnouncements();
- }, 200);
+ cursor: "pointer",
}}
+ onClick={goToAnnouncements}
>
Announcements
@@ -1899,33 +2776,11 @@ export const Group = ({
alignItems: "center",
justifyContent: "flex-start",
width: "100%",
- cursor: 'pointer'
- }}
- onClick={async () => {
- setGroupSection("default");
- await new Promise((res) => {
- setTimeout(() => {
- res(null);
- }, 200);
- });
- setGroupSection("chat");
- if (selectedGroupRef.current) {
- chrome.runtime.sendMessage({
- action: "addTimestampEnterChat",
- payload: {
- timestamp: Date.now(),
- groupId: selectedGroupRef.current.groupId,
- },
- });
-
- setTimeout(() => {
- getTimestampEnterChat();
- }, 200);
- }
+ cursor: "pointer",
}}
+ onClick={goToChat}
>
Chat
@@ -1959,14 +2814,15 @@ export const Group = ({
alignItems: "center",
justifyContent: "flex-start",
width: "100%",
- cursor: 'pointer'
+ cursor: "pointer",
}}
onClick={() => {
setGroupSection("forum");
+ setSelectedDirect(null);
+ setNewChat(false);
}}
>
setOpenManageMembers(true)}
+ onClick={() => setOpenManageMembers(true)}
sx={{
display: "flex",
gap: "3px",
alignItems: "center",
justifyContent: "flex-start",
width: "100%",
- cursor: 'pointer'
+ cursor: "pointer",
}}
>
-
-
+
Members
- >
- )}
-
- {/*
+ )} */}
+
+ {/* */}
-
+
-
+
+ {isMobile && mobileViewMode === "home" && !mobileViewModeKeepOpen && (
+ <>
+
+ {/*
+ {renderGroups()}
+ */}
+ {isMobile && (
+
+ )}
+ >
+ )}
>
);
};
+
+// {isMobile && (
+//
+//
+// {selectedGroup && (
+// <>
+//
+// }
+// sx={{
+// padding: "4px 6px",
+// color:
+// groupSection === "announcement" ? "black" : "white",
+// backgroundColor: isUnread
+// ? "red"
+// : groupSection === "announcement"
+// ? "white"
+// : "black",
+// "&:hover": {
+// backgroundColor: isUnread
+// ? "red"
+// : groupSection === "announcement"
+// ? "white"
+// : "black",
+// },
+// "&:active": {
+// backgroundColor: isUnread
+// ? "red"
+// : groupSection === "announcement"
+// ? "white"
+// : "black",
+// },
+// "&:focus": {
+// backgroundColor: isUnread
+// ? "red"
+// : groupSection === "announcement"
+// ? "white"
+// : "black",
+// },
+// }}
+// onClick={goToAnnouncements}
+// >
+// ANN
+//
+//
+//
+// }
+// sx={{
+// padding: "4px 6px",
+// color: groupSection === "chat" ? "black" : "white",
+// backgroundColor: isUnreadChat
+// ? "red"
+// : groupSection === "chat"
+// ? "white"
+// : "black",
+// "&:hover": {
+// backgroundColor: isUnreadChat
+// ? "red"
+// : groupSection === "chat"
+// ? "white"
+// : "black", // Same logic for hover
+// },
+// "&:active": {
+// backgroundColor: isUnreadChat
+// ? "red"
+// : groupSection === "chat"
+// ? "white"
+// : "black", // Same logic for active
+// },
+// "&:focus": {
+// backgroundColor: isUnreadChat
+// ? "red"
+// : groupSection === "chat"
+// ? "white"
+// : "black", // Same logic for focus
+// },
+// }}
+// onClick={goToChat}
+// >
+// Chat
+//
+//
+//
+// }
+// sx={{
+// padding: "4px 6px",
+// color: groupSection === "forum" ? "black" : "white",
+// backgroundColor:
+// groupSection === "forum" ? "white" : "black",
+// "&:hover": {
+// backgroundColor: groupSection === "forum" ? "white" : "black", // Hover state
+// },
+// "&:active": {
+// backgroundColor: groupSection === "forum" ? "white" : "black", // Active state
+// },
+// "&:focus": {
+// backgroundColor: groupSection === "forum" ? "white" : "black", // Focus state
+// },
+// }}
+// onClick={() => {
+// setSelectedDirect(null);
+// setNewChat(false)
+// setGroupSection("forum")
+// } }
+// >
+// Forum
+//
+//
+//
+// }
+// sx={{ padding: "4px 6px", backgroundColor: "black", "&:hover": {
+// backgroundColor: "black", // Hover state
+// },
+// "&:active": {
+// backgroundColor: "black", // Active state
+// },
+// "&:focus": {
+// backgroundColor: "black", // Focus state
+// }, }}
+// onClick={() => setOpenManageMembers(true)}
+// >
+// Members
+//
+//
+// >
+// )}
+
+// {/* Second row: Groups, Home, Profile */}
+//
+// }
+// sx={{
+// padding: "2px 4px",
+// backgroundColor:
+// groupChatHasUnread ||
+// groupsAnnHasUnread ||
+// directChatHasUnread
+// ? "red"
+// : "black",
+// "&:hover": {
+// backgroundColor:
+// groupChatHasUnread || groupsAnnHasUnread || directChatHasUnread
+// ? "red"
+// : "black", // Hover state follows the same logic
+// },
+// "&:active": {
+// backgroundColor:
+// groupChatHasUnread || groupsAnnHasUnread || directChatHasUnread
+// ? "red"
+// : "black", // Active state follows the same logic
+// },
+// "&:focus": {
+// backgroundColor:
+// groupChatHasUnread || groupsAnnHasUnread || directChatHasUnread
+// ? "red"
+// : "black", // Focus state follows the same logic
+// },
+// }}
+// onClick={() => {
+// setIsOpenDrawer(true);
+// setDrawerMode("groups");
+// }}
+// >
+// {chatMode === "groups" ? "Groups" : "Direct"}
+//
+//
+//
+//
+//
+//
+//
+//
+// setIsOpenDrawerProfile(true)}
+// >
+//
+//
+//
+//
+//
+// )}
diff --git a/src/components/Group/GroupInvites.tsx b/src/components/Group/GroupInvites.tsx
index 06e176d..0c4b6f6 100644
--- a/src/components/Group/GroupInvites.tsx
+++ b/src/components/Group/GroupInvites.tsx
@@ -8,107 +8,165 @@ import Checkbox from "@mui/material/Checkbox";
import IconButton from "@mui/material/IconButton";
import CommentIcon from "@mui/icons-material/Comment";
import InfoIcon from "@mui/icons-material/Info";
-import GroupAddIcon from '@mui/icons-material/GroupAdd';
+import GroupAddIcon from "@mui/icons-material/GroupAdd";
import { executeEvent } from "../../utils/events";
import { Box, Typography } from "@mui/material";
import { Spacer } from "../../common/Spacer";
import { getGroupNames } from "./UserListOfInvites";
import { CustomLoader } from "../../common/CustomLoader";
-import { getBaseApiReact } from "../../App";
+import { getBaseApiReact, isMobile } from "../../App";
export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
- const [groupsWithJoinRequests, setGroupsWithJoinRequests] = React.useState([])
- const [loading, setLoading] = React.useState(true)
+ const [groupsWithJoinRequests, setGroupsWithJoinRequests] = React.useState(
+ []
+ );
+ const [loading, setLoading] = React.useState(true);
- const getJoinRequests = async ()=> {
+ const getJoinRequests = async () => {
try {
- setLoading(true)
- const response = await fetch(`${getBaseApiReact()}/groups/invites/${myAddress}/?limit=0`);
+ setLoading(true);
+ const response = await fetch(
+ `${getBaseApiReact()}/groups/invites/${myAddress}/?limit=0`
+ );
const data = await response.json();
- const resMoreData = await getGroupNames(data)
+ const resMoreData = await getGroupNames(data);
- setGroupsWithJoinRequests(resMoreData)
+ setGroupsWithJoinRequests(resMoreData);
} catch (error) {
-
} finally {
- setLoading(false)
+ setLoading(false);
}
- }
+ };
React.useEffect(() => {
if (myAddress) {
- getJoinRequests()
+ getJoinRequests();
}
}, [myAddress]);
-
return (
-
- Group Invites
-
- {loading && groupsWithJoinRequests.length === 0 && (
-
-
-
- )}
- {!loading && groupsWithJoinRequests.length === 0 && (
-
- No invites
-
- )}
-
- {groupsWithJoinRequests?.map((group)=> {
- return (
- {
- setOpenAddGroup(true)
- setTimeout(() => {
- executeEvent("openGroupInvitesRequest", {});
-
- }, 300);
+
+
+
-
-
- }
>
-
-
-
-
-
- )
+ Hub Invites:
+
+
+
- })}
-
-
-
-
+
+ {loading && groupsWithJoinRequests.length === 0 && (
+
+
+
+ )}
+ {!loading && groupsWithJoinRequests.length === 0 && (
+
+
+ Nothing to display
+
+
+ )}
+
+ {groupsWithJoinRequests?.map((group) => {
+ return (
+ {
+ setOpenAddGroup(true);
+ setTimeout(() => {
+ executeEvent("openGroupInvitesRequest", {});
+ }, 300);
+ }}
+ disablePadding
+ secondaryAction={
+
+
+
+ }
+ >
+
+
+
+
+ );
+ })}
+
+
);
};
diff --git a/src/components/Group/GroupJoinRequests.tsx b/src/components/Group/GroupJoinRequests.tsx
index 1123d57..1f9d2ca 100644
--- a/src/components/Group/GroupJoinRequests.tsx
+++ b/src/components/Group/GroupJoinRequests.tsx
@@ -15,10 +15,10 @@ import { Box, Typography } from "@mui/material";
import { Spacer } from "../../common/Spacer";
import { CustomLoader } from "../../common/CustomLoader";
import { getBaseApi } from "../../background";
-import { getBaseApiReact } from "../../App";
-export const requestQueueGroupJoinRequests = new RequestQueueWithPromise(3)
+import { getBaseApiReact, isMobile } from "../../App";
+export const requestQueueGroupJoinRequests = new RequestQueueWithPromise(2)
-export const GroupJoinRequests = ({ myAddress, groups, setOpenManageMembers, getTimestampEnterChat, setSelectedGroup, setGroupSection }) => {
+export const GroupJoinRequests = ({ myAddress, groups, setOpenManageMembers, getTimestampEnterChat, setSelectedGroup, setGroupSection, setMobileViewMode }) => {
const [groupsWithJoinRequests, setGroupsWithJoinRequests] = React.useState([])
const [loading, setLoading] = React.useState(true)
@@ -93,19 +93,45 @@ export const GroupJoinRequests = ({ myAddress, groups, setOpenManageMembers, get
}, [myAddress, groups]);
-
return (
-
- Join Requests
-
+
+
+ Join Requests:
+
+
+
+
+
{loading && groupsWithJoinRequests.length === 0 && (
)}
- {!loading && groupsWithJoinRequests.length === 0 && (
-
- No join requests
-
+ {!loading && (groupsWithJoinRequests.length === 0 || groupsWithJoinRequests?.filter((group)=> group?.data?.length > 0).length === 0) && (
+
+
+ Nothing to display
+
+
)}
{groupsWithJoinRequests?.map((group)=> {
@@ -134,6 +171,7 @@ export const GroupJoinRequests = ({ myAddress, groups, setOpenManageMembers, get
key={group?.groupId}
onClick={()=> {
setSelectedGroup(group?.group)
+ setMobileViewMode('group')
getTimestampEnterChat()
setGroupSection("announcement")
setOpenManageMembers(true)
@@ -142,20 +180,31 @@ export const GroupJoinRequests = ({ myAddress, groups, setOpenManageMembers, get
}, 300);
}}
+ sx={{
+ marginBottom: '20px'
+ }}
disablePadding
secondaryAction={
}
>
-
+
-
+
)
@@ -166,5 +215,6 @@ export const GroupJoinRequests = ({ myAddress, groups, setOpenManageMembers, get
+
);
};
diff --git a/src/components/Group/GroupMenu.tsx b/src/components/Group/GroupMenu.tsx
new file mode 100644
index 0000000..5bc99c6
--- /dev/null
+++ b/src/components/Group/GroupMenu.tsx
@@ -0,0 +1,200 @@
+import React, { useState } from "react";
+import {
+ Button,
+ Menu,
+ MenuItem,
+ ListItemIcon,
+ ListItemText,
+ Badge,
+ Box,
+} from "@mui/material";
+import ForumIcon from "@mui/icons-material/Forum";
+import GroupIcon from "@mui/icons-material/Group";
+import { ArrowDownIcon } from "../../assets/Icons/ArrowDownIcon";
+import { NotificationIcon2 } from "../../assets/Icons/NotificationIcon2";
+import { ChatIcon } from "../../assets/Icons/ChatIcon";
+import { ThreadsIcon } from "../../assets/Icons/ThreadsIcon";
+import { MembersIcon } from "../../assets/Icons/MembersIcon";
+
+export const GroupMenu = ({ setGroupSection, groupSection, setOpenManageMembers }) => {
+ const [anchorEl, setAnchorEl] = useState(null);
+ const open = Boolean(anchorEl);
+
+ const handleClick = (event) => {
+ setAnchorEl(event.currentTarget);
+ };
+
+ const handleClose = () => {
+ setAnchorEl(null);
+ };
+
+ return (
+
+
+
+
+ {groupSection === "announcement" &&(
+ <> {" Announcements"}>
+ )}
+ {groupSection === "chat" &&(
+ <> {" Hub Chats"}>
+ )}
+ {groupSection === "forum" &&(
+ <> {" Threads"}>
+ )}
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/Group/Home.tsx b/src/components/Group/Home.tsx
new file mode 100644
index 0000000..4db465d
--- /dev/null
+++ b/src/components/Group/Home.tsx
@@ -0,0 +1,110 @@
+import { Box, Button, Typography } from "@mui/material";
+import React from "react";
+import { Spacer } from "../../common/Spacer";
+import { ListOfThreadPostsWatched } from "./ListOfThreadPostsWatched";
+import { ThingsToDoInitial } from "./ThingsToDoInitial";
+import { GroupJoinRequests } from "./GroupJoinRequests";
+import { GroupInvites } from "./GroupInvites";
+import RefreshIcon from "@mui/icons-material/Refresh";
+
+export const Home = ({
+ refreshHomeDataFunc,
+ myAddress,
+ isLoadingGroups,
+ balance,
+ userInfo,
+ groups,
+ setGroupSection,
+ setSelectedGroup,
+ getTimestampEnterChat,
+ setOpenManageMembers,
+ setOpenAddGroup,
+ setMobileViewMode,
+}) => {
+ return (
+
+
+ 15 ? "16px" : "20px",
+ padding: '10px'
+ }}
+ >
+ Welcome{" "}
+ {userInfo?.name ? (
+ {`, ${userInfo?.name}`}
+ ) : null}
+
+
+
+ {/*
+ }
+ onClick={refreshHomeDataFunc}
+ sx={{
+ color: "white",
+ }}
+ >
+ Refresh home data
+
+ */}
+ {!isLoadingGroups && (
+
+
+
+
+
+
+
+ )}
+
+
+ );
+};
diff --git a/src/components/Group/HomeDesktop.tsx b/src/components/Group/HomeDesktop.tsx
new file mode 100644
index 0000000..2162a02
--- /dev/null
+++ b/src/components/Group/HomeDesktop.tsx
@@ -0,0 +1,150 @@
+import { Box, Button, Typography } from "@mui/material";
+import React from "react";
+import { Spacer } from "../../common/Spacer";
+import { ListOfThreadPostsWatched } from "./ListOfThreadPostsWatched";
+import { ThingsToDoInitial } from "./ThingsToDoInitial";
+import { GroupJoinRequests } from "./GroupJoinRequests";
+import { GroupInvites } from "./GroupInvites";
+import RefreshIcon from "@mui/icons-material/Refresh";
+
+export const HomeDesktop = ({
+ refreshHomeDataFunc,
+ myAddress,
+ isLoadingGroups,
+ balance,
+ userInfo,
+ groups,
+ setGroupSection,
+ setSelectedGroup,
+ getTimestampEnterChat,
+ setOpenManageMembers,
+ setOpenAddGroup,
+ setMobileViewMode,
+}) => {
+ return (
+
+
+
+ 15 ? "16px" : "20px",
+ padding: '10px'
+ }}
+ >
+ Welcome{" "}
+ {userInfo?.name ? (
+ {`, ${userInfo?.name}`}
+ ) : null}
+
+
+ {!isLoadingGroups && (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+
+ {/*
+ }
+ onClick={refreshHomeDataFunc}
+ sx={{
+ color: "white",
+ }}
+ >
+ Refresh home data
+
+ */}
+
+
+
+ );
+};
diff --git a/src/components/Group/InviteMember.tsx b/src/components/Group/InviteMember.tsx
index 5a22678..12efdda 100644
--- a/src/components/Group/InviteMember.tsx
+++ b/src/components/Group/InviteMember.tsx
@@ -26,7 +26,7 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
setIsLoadingInvite(true)
if (!expiryTime || !value) return;
new Promise((res, rej) => {
- chrome.runtime.sendMessage(
+ chrome?.runtime?.sendMessage(
{
action: "inviteToGroup",
payload: {
diff --git a/src/components/Group/ListOfBans.tsx b/src/components/Group/ListOfBans.tsx
index 6cd1236..41ef9f4 100644
--- a/src/components/Group/ListOfBans.tsx
+++ b/src/components/Group/ListOfBans.tsx
@@ -74,7 +74,7 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
})
setIsLoadingUnban(true)
new Promise((res, rej)=> {
- chrome.runtime.sendMessage({ action: "cancelBan", payload: {
+ chrome?.runtime?.sendMessage({ action: "cancelBan", payload: {
groupId,
qortalAddress: address,
}}, (response) => {
@@ -168,7 +168,7 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
return (
Ban list
-
+
{({ height, width }) => (
})
setIsLoadingCancelInvite(true)
await new Promise((res, rej)=> {
- chrome.runtime.sendMessage({ action: "cancelInvitationToGroup", payload: {
+ chrome?.runtime?.sendMessage({ action: "cancelInvitationToGroup", payload: {
groupId,
qortalAddress: address,
}}, (response) => {
@@ -169,7 +169,7 @@ export const ListOfInvites = ({ groupId, setInfoSnack, setOpenSnack, show }) =>
return (
Invitees list
-
+
{({ height, width }) => (
{
- chrome.runtime.sendMessage({ action: "inviteToGroup", payload: {
+ chrome?.runtime?.sendMessage({ action: "inviteToGroup", payload: {
groupId,
qortalAddress: address,
inviteTime: 10800,
@@ -169,7 +169,7 @@ export const ListOfJoinRequests = ({ groupId, setInfoSnack, setOpenSnack, show }
return (
Join request list
-
+
{({ height, width }) => (
{
- chrome.runtime.sendMessage(
+ chrome?.runtime?.sendMessage(
{
action: "kickFromGroup",
payload: {
@@ -107,7 +107,7 @@ const ListOfMembers = ({
});
setIsLoadingBan(true);
await new Promise((res, rej) => {
- chrome.runtime.sendMessage(
+ chrome?.runtime?.sendMessage(
{
action: "banFromGroup",
payload: {
@@ -153,7 +153,7 @@ const ListOfMembers = ({
});
setIsLoadingMakeAdmin(true);
await new Promise((res, rej) => {
- chrome.runtime.sendMessage(
+ chrome?.runtime?.sendMessage(
{
action: "makeAdmin",
payload: {
@@ -198,7 +198,7 @@ const ListOfMembers = ({
});
setIsLoadingRemoveAdmin(true);
await new Promise((res, rej) => {
- chrome.runtime.sendMessage(
+ chrome?.runtime?.sendMessage(
{
action: "removeAdmin",
payload: {
@@ -357,7 +357,7 @@ const ListOfMembers = ({
style={{
position: "relative",
height: "500px",
- width: "600px",
+ width: "100%",
display: "flex",
flexDirection: "column",
flexShrink: 1,
diff --git a/src/components/Group/ListOfThreadPostsWatched.tsx b/src/components/Group/ListOfThreadPostsWatched.tsx
index ed8e7d9..4817805 100644
--- a/src/components/Group/ListOfThreadPostsWatched.tsx
+++ b/src/components/Group/ListOfThreadPostsWatched.tsx
@@ -8,131 +8,174 @@ import Checkbox from "@mui/material/Checkbox";
import IconButton from "@mui/material/IconButton";
import CommentIcon from "@mui/icons-material/Comment";
import InfoIcon from "@mui/icons-material/Info";
-import GroupAddIcon from '@mui/icons-material/GroupAdd';
+import GroupAddIcon from "@mui/icons-material/GroupAdd";
import { executeEvent } from "../../utils/events";
import { Box, Typography } from "@mui/material";
import { Spacer } from "../../common/Spacer";
import { getGroupNames } from "./UserListOfInvites";
import { CustomLoader } from "../../common/CustomLoader";
-import VisibilityIcon from '@mui/icons-material/Visibility';
+import VisibilityIcon from "@mui/icons-material/Visibility";
+import { isMobile } from "../../App";
export const ListOfThreadPostsWatched = () => {
- const [posts, setPosts] = React.useState([])
- const [loading, setLoading] = React.useState(true)
+ const [posts, setPosts] = React.useState([]);
+ const [loading, setLoading] = React.useState(true);
- const getPosts = async ()=> {
+ const getPosts = async () => {
try {
await new Promise((res, rej) => {
- chrome.runtime.sendMessage(
+ chrome?.runtime?.sendMessage(
{
action: "getThreadActivity",
- payload: {
-
- },
+ payload: {},
},
(response) => {
-
if (!response?.error) {
- if(!response) {
- res(null)
- return
+ if (!response) {
+ res(null);
+ return;
}
const uniquePosts = response.reduce((acc, current) => {
- const x = acc.find(item => item?.thread?.threadId === current?.thread?.threadId);
+ const x = acc.find(
+ (item) => item?.thread?.threadId === current?.thread?.threadId
+ );
if (!x) {
return acc.concat([current]);
} else {
return acc;
}
}, []);
- setPosts(uniquePosts)
+ setPosts(uniquePosts);
res(uniquePosts);
- return
+ return;
}
rej(response.error);
}
);
});
} catch (error) {
-
} finally {
- setLoading(false)
+ setLoading(false);
}
- }
+ };
React.useEffect(() => {
-
- getPosts()
-
+ getPosts();
}, []);
-
-
return (
-
- New Thread Posts
-
- {loading && posts.length === 0 && (
-
-
-
- )}
- {!loading && posts.length === 0 && (
-
- No thread post notifications
-
- )}
-
- {posts?.map((post)=> {
- return (
- {
- executeEvent("openThreadNewPost", {
- data: post
- });
- }}
- disablePadding
- secondaryAction={
-
-
-
- }
- >
-
-
-
-
-
- )
+
+ }}
+ >
+
+ New Thread Posts:
+
+
+
+
+
+ {loading && posts.length === 0 && (
+
+
+
+ )}
+ {!loading && posts.length === 0 && (
+
+
+ Nothing to display
+
+
+ )}
+ {posts?.length > 0 && (
+
+ {posts?.map((post) => {
+ return (
+ {
+ executeEvent("openThreadNewPost", {
+ data: post,
+ });
+ }}
+ disablePadding
+ secondaryAction={
+
+
+
+ }
+ >
+
+
+
+
+ );
+ })}
+
+ )}
+
+
);
};
diff --git a/src/components/Group/ManageMembers.tsx b/src/components/Group/ManageMembers.tsx
index aebb3fb..96a46ba 100644
--- a/src/components/Group/ManageMembers.tsx
+++ b/src/components/Group/ManageMembers.tsx
@@ -19,7 +19,7 @@ import { ListOfBans } from "./ListOfBans";
import { ListOfJoinRequests } from "./ListOfJoinRequests";
import { Box, Tab, Tabs } from "@mui/material";
import { CustomizedSnackbars } from "../Snackbar/Snackbar";
-import { MyContext } from "../../App";
+import { MyContext, isMobile } from "../../App";
import { getGroupMembers, getNames } from "./Group";
import { LoadingSnackbar } from "../Snackbar/LoadingSnackbar";
import { getFee } from "../../background";
@@ -77,7 +77,7 @@ export const ManageMembers = ({
})
await new Promise((res, rej) => {
- chrome.runtime.sendMessage(
+ chrome?.runtime?.sendMessage(
{
action: "leaveGroup",
payload: {
@@ -173,63 +173,72 @@ export const ManageMembers = ({
}}
>
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
{selectedGroup?.groupId && !isOwner && (
@@ -244,6 +253,7 @@ export const ManageMembers = ({
sx={{
width: "100%",
padding: "25px",
+ maxWidth: '750px'
}}
>
@@ -272,7 +283,8 @@ export const ManageMembers = ({
@@ -284,7 +296,8 @@ export const ManageMembers = ({
@@ -295,7 +308,8 @@ export const ManageMembers = ({
diff --git a/src/components/Group/ThingsToDoInitial.tsx b/src/components/Group/ThingsToDoInitial.tsx
index 18f75b5..28c3889 100644
--- a/src/components/Group/ThingsToDoInitial.tsx
+++ b/src/components/Group/ThingsToDoInitial.tsx
@@ -10,157 +10,226 @@ import CommentIcon from "@mui/icons-material/Comment";
import InfoIcon from "@mui/icons-material/Info";
import { Box, Typography } from "@mui/material";
import { Spacer } from "../../common/Spacer";
+import { isMobile } from "../../App";
export const ThingsToDoInitial = ({ myAddress, name, hasGroups, balance }) => {
const [checked1, setChecked1] = React.useState(false);
const [checked2, setChecked2] = React.useState(false);
const [checked3, setChecked3] = React.useState(false);
-// const getAddressInfo = async (address) => {
-// const response = await fetch(getBaseApiReact() + "/addresses/" + address);
-// const data = await response.json();
-// if (data.error && data.error === 124) {
-// setChecked1(false);
-// } else if (data.address) {
-// setChecked1(true);
-// }
-// };
+ // const getAddressInfo = async (address) => {
+ // const response = await fetch(getBaseApiReact() + "/addresses/" + address);
+ // const data = await response.json();
+ // if (data.error && data.error === 124) {
+ // setChecked1(false);
+ // } else if (data.address) {
+ // setChecked1(true);
+ // }
+ // };
-// const checkInfo = async () => {
-// try {
-// getAddressInfo(myAddress);
-// } catch (error) {}
-// };
-
-
+ // const checkInfo = async () => {
+ // try {
+ // getAddressInfo(myAddress);
+ // } catch (error) {}
+ // };
React.useEffect(() => {
if (balance && +balance >= 6) {
- setChecked1(true)
+ setChecked1(true);
}
}, [balance]);
- React.useEffect(()=> {
- if(hasGroups) setChecked3(true)
- }, [hasGroups])
+ React.useEffect(() => {
+ if (hasGroups) setChecked3(true);
+ }, [hasGroups]);
- React.useEffect(()=> {
- if(name) setChecked2(true)
- }, [name])
+ React.useEffect(() => {
+ if (name) setChecked2(true);
+ }, [name]);
return (
-
- Suggestion: Complete the following
-
-
-
- //
- //
- // }
- disablePadding
+
+
-
-
-
-
-
-
-
-
- //
- //
- // }
- disablePadding
+
+ Getting Started:
+
+
+
+
+
-
-
-
+
+ //
+ //
+ // }
+ disablePadding
+ sx={{
+ marginBottom: '20px'
+ }}
+ >
+
-
-
-
-
-
- //
- //
- // }
- disablePadding
- >
-
-
-
-
-
-
-
-
+ role={undefined}
+ dense
+ >
+
+
+
+ {/* */}
+
+
+
+
+ //
+ //
+ // }
+ disablePadding
+ >
+
+
+
+
+
+
+
+
+
+ //
+ //
+ // }
+ disablePadding
+ >
+
+
+
+
+
+
+
+
+
+
);
};
diff --git a/src/components/Group/UserListOfInvites.tsx b/src/components/Group/UserListOfInvites.tsx
index d126024..20aa6fb 100644
--- a/src/components/Group/UserListOfInvites.tsx
+++ b/src/components/Group/UserListOfInvites.tsx
@@ -85,7 +85,7 @@ export const UserListOfInvites = ({myAddress, setInfoSnack, setOpenSnack}) => {
setIsLoading(true);
await new Promise((res, rej)=> {
- chrome.runtime.sendMessage({ action: "joinGroup", payload: {
+ chrome?.runtime?.sendMessage({ action: "joinGroup", payload: {
groupId,
}}, (response) => {
@@ -186,7 +186,7 @@ export const UserListOfInvites = ({myAddress, setInfoSnack, setOpenSnack}) => {
return (
Invite list
-
+
{({ height, width }) => (
{
).sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
- chrome.runtime.sendMessage({
+ chrome?.runtime?.sendMessage({
action: 'handleActiveGroupDataFromSocket',
payload: {
groups: sortedGroups,
diff --git a/src/components/Loader.tsx b/src/components/Loader.tsx
index fe0a05e..8db908e 100644
--- a/src/components/Loader.tsx
+++ b/src/components/Loader.tsx
@@ -14,7 +14,7 @@ export const Loader = () => {
left:'0px',
right: '0px',
bottom: '0px',
- zIndex: 2,
+ zIndex: 10,
background: 'rgba(0, 0, 0, 0.4)'
}}>
diff --git a/src/components/Mobile/MobileFooter.tsx b/src/components/Mobile/MobileFooter.tsx
new file mode 100644
index 0000000..ca31331
--- /dev/null
+++ b/src/components/Mobile/MobileFooter.tsx
@@ -0,0 +1,186 @@
+import * as React from "react";
+import {
+ BottomNavigation,
+ BottomNavigationAction,
+ Typography,
+} from "@mui/material";
+import { Home, Groups, Message, ShowChart } from "@mui/icons-material";
+import Box from "@mui/material/Box";
+import BottomLogo from "../../assets/svgs/BottomLogo5.svg";
+import { CustomSvg } from "../../common/CustomSvg";
+import { WalletIcon } from "../../assets/Icons/WalletIcon";
+import { HubsIcon } from "../../assets/Icons/HubsIcon";
+import { TradingIcon } from "../../assets/Icons/TradingIcon";
+import { MessagingIcon } from "../../assets/Icons/MessagingIcon";
+
+const IconWrapper = ({ children, label, color }) => {
+ return (
+
+ {children}
+
+ {label}
+
+
+ );
+};
+
+export const MobileFooter = ({
+ selectedGroup,
+ groupSection,
+ isUnread,
+ goToAnnouncements,
+ isUnreadChat,
+ goToChat,
+ goToThreads,
+ setOpenManageMembers,
+ groupChatHasUnread,
+ groupsAnnHasUnread,
+ directChatHasUnread,
+ chatMode,
+ openDrawerGroups,
+ goToHome,
+ setIsOpenDrawerProfile,
+ mobileViewMode,
+ setMobileViewMode,
+ setMobileViewModeKeepOpen,
+ hasUnreadGroups,
+ hasUnreadDirects
+}) => {
+ const [value, setValue] = React.useState(0);
+ return (
+
+ setValue(newValue)}
+ sx={{ backgroundColor: "transparent", flexGrow: 1 }}
+ >
+ {
+ // setMobileViewMode('wallet')
+ setIsOpenDrawerProfile(true);
+ }}
+ icon={
+
+
+
+ }
+ sx={{ color: value === 0 ? "white" : "gray", padding: "0px 10px" }}
+ />
+ {
+ setMobileViewMode("groups");
+ }}
+ icon={
+
+
+
+ }
+ sx={{
+ color: value === 0 ? "white" : "gray",
+ paddingLeft: "10px",
+ paddingRight: "42px",
+ }}
+ />
+
+
+ {/* Floating Center Button */}
+
+
+ {/* Custom Center Icon */}
+
+
+
+
+ setValue(newValue)}
+ sx={{ backgroundColor: "transparent", flexGrow: 1 }}
+ >
+ {
+ setMobileViewModeKeepOpen("messaging");
+ }}
+ icon={
+
+
+
+ }
+ sx={{
+ color: value === 2 ? "white" : "gray",
+ paddingLeft: "55px",
+ paddingRight: "10px",
+ }}
+ />
+ {
+ chrome.tabs.create({ url: "https://www.qort.trade"});
+ }}
+ icon={
+
+
+
+ }
+ sx={{ color: value === 3 ? "white" : "gray", padding: "0px 10px" }}
+ />
+
+
+ );
+};
diff --git a/src/components/Mobile/MobileHeader.tsx b/src/components/Mobile/MobileHeader.tsx
new file mode 100644
index 0000000..f49291e
--- /dev/null
+++ b/src/components/Mobile/MobileHeader.tsx
@@ -0,0 +1,444 @@
+import React, { useState } from "react";
+import {
+ AppBar,
+ Toolbar,
+ IconButton,
+ Typography,
+ Box,
+ MenuItem,
+ Select,
+ ButtonBase,
+ Menu,
+ ListItemIcon,
+ ListItemText,
+} from "@mui/material";
+import { HomeIcon } from "../../assets/Icons/HomeIcon";
+import { LogoutIcon } from "../../assets/Icons/LogoutIcon";
+import { NotificationIcon } from "../../assets/Icons/NotificationIcon";
+import { ArrowDownIcon } from "../../assets/Icons/ArrowDownIcon";
+import { MessagingIcon } from "../../assets/Icons/MessagingIcon";
+import { MessagingIcon2 } from "../../assets/Icons/MessagingIcon2";
+import { HubsIcon } from "../../assets/Icons/HubsIcon";
+
+const Header = ({
+ logoutFunc,
+ goToHome,
+ setIsOpenDrawerProfile,
+ isThin,
+ setMobileViewModeKeepOpen,
+ hasUnreadGroups,
+ hasUnreadDirects,
+ setMobileViewMode,
+ myName
+ // selectedGroup,
+ // onHomeClick,
+ // onLogoutClick,
+ // onGroupChange,
+ // onWalletClick,
+ // onNotificationClick,
+}) => {
+ const [anchorEl, setAnchorEl] = useState(null);
+ const open = Boolean(anchorEl);
+
+ const handleClick = (event) => {
+ setAnchorEl(event.currentTarget);
+ };
+
+ const handleClose = () => {
+ setAnchorEl(null);
+ };
+
+ if (isThin) {
+ return (
+
+
+ {/* Left Home Icon */}
+
+ {
+ setMobileViewModeKeepOpen("");
+ goToHome();
+ }}
+ // onClick={onHomeClick}
+ >
+
+
+
+
+
+
+
+ {/* Center Title */}
+
+ QORTAL
+
+
+ {/* Right Logout Icon */}
+
+ {
+ setMobileViewModeKeepOpen("messaging");
+ }}
+ edge="end"
+ color="inherit"
+ aria-label="logout"
+
+ // onClick={onLogoutClick}
+ >
+
+
+
+
+
+
+
+
+
+ );
+ }
+ return (
+ <>
+ {/* Main Header */}
+
+
+ {/* Left Home Icon */}
+
+
+
+
+ {/* Center Title */}
+
+ QORTAL
+
+
+ {/* Right Logout Icon */}
+
+
+
+
+
+
+ {/* Secondary Section */}
+
+
+
+ {myName}
+
+ {/*
+ */}
+
+
+
+
+
+
+
+
+ {/* Right Dropdown */}
+ {/* {
+ setIsOpenDrawerProfile(true);
+ }}
+ >
+
+
+ View Wallet
+
+
+
+
+ */}
+
+
+ >
+ );
+};
+
+export default Header;
diff --git a/src/components/WrapperUserAction.tsx b/src/components/WrapperUserAction.tsx
new file mode 100644
index 0000000..2d1f04a
--- /dev/null
+++ b/src/components/WrapperUserAction.tsx
@@ -0,0 +1,108 @@
+import React, { useState } from 'react';
+import { Popover, Button, Box } from '@mui/material';
+import { executeEvent } from '../utils/events';
+
+export const WrapperUserAction = ({ children, address, name, disabled }) => {
+ const [anchorEl, setAnchorEl] = useState(null);
+
+ // Handle child element click to open Popover
+ const handleChildClick = (event) => {
+ event.stopPropagation(); // Prevent parent onClick from firing
+ setAnchorEl(event.currentTarget);
+ };
+
+ // Handle closing the Popover
+ const handleClose = () => {
+ setAnchorEl(null);
+ };
+
+ // Determine if the popover is open
+ const open = Boolean(anchorEl);
+ const id = open ? address || name : undefined;
+
+ if(disabled){
+ return children
+ }
+
+ return (
+ <>
+
+ {/* Render the child without altering dimensions */}
+ {children}
+
+
+ {/* Popover */}
+ event.stopPropagation(), // Stop propagation inside popover
+ },
+ }}
+ >
+
+ {/* Option 1: Message */}
+ {
+ executeEvent('openDirectMessageInternal', {
+ address,
+ name,
+ });
+ handleClose();
+
+ }}
+ sx={{
+ color: 'white'
+ }}
+ >
+ Message
+
+
+ {/* Option 2: Send QORT */}
+ {
+ executeEvent('openPaymentInternal', {
+ address,
+ name,
+ });
+ handleClose();
+
+ }}
+ sx={{
+ color: 'white'
+ }}
+ >
+ Send QORT
+
+
+
+ >
+ );
+};
diff --git a/src/index.css b/src/index.css
index 853afa4..9d6fa90 100644
--- a/src/index.css
+++ b/src/index.css
@@ -25,10 +25,16 @@
padding: 0px;
margin: 0px;
box-sizing: border-box !important;
+ word-break: break-word;
--color-instance : #1E1E20;
--color-instance-popover-bg: #222222;
--Mail-Background: rgba(49, 51, 56, 1);
--new-message-text: black;
+
+ --bg-primary : rgba(31, 32, 35, 1);
+ --bg-2: #27282c;
+ --bg-3: rgba(0, 0, 0, 0.1);
+ --unread: rgba(255, 0, 0, 1);
}
body {
@@ -78,6 +84,23 @@ body {
border: 4px solid transparent;
}
+/* Mobile-specific scrollbar styles */
+@media only screen and (max-width: 600px) {
+ ::-webkit-scrollbar {
+ width: 8px; /* Narrower scrollbar width on mobile */
+ height: 6px; /* Narrower scrollbar height on mobile */
+ }
+
+ ::-webkit-scrollbar-thumb {
+ border-radius: 4px; /* Adjust the radius for a narrower thumb */
+ border: 2px solid transparent; /* Narrower thumb border */
+ }
+}
+
.group-list::-webkit-scrollbar-thumb:hover {
background-color: whitesmoke;
+}
+
+html, body {
+ overscroll-behavior:none !important;
}
\ No newline at end of file
diff --git a/src/transactions/TradeBotRespondMultipleRequest.ts b/src/transactions/TradeBotRespondMultipleRequest.ts
new file mode 100644
index 0000000..a7eb4d8
--- /dev/null
+++ b/src/transactions/TradeBotRespondMultipleRequest.ts
@@ -0,0 +1,42 @@
+// @ts-nocheck
+
+/**
+ * CrossChain - TradeBot Respond Multiple Request (Buy Action)
+ *
+ * These are special types of transactions (JSON ENCODED)
+ */
+
+export class TradeBotRespondMultipleRequest {
+ constructor() {
+ // ...
+ }
+
+ createTransaction(txnReq) {
+ this.addresses(txnReq.addresses)
+ this.foreignKey(txnReq.foreignKey)
+ this.receivingAddress(txnReq.receivingAddress)
+
+ return this.txnRequest()
+ }
+
+ addresses(addresses) {
+ this._addresses = addresses
+ }
+
+ foreignKey(foreignKey) {
+ this._foreignKey = foreignKey
+ }
+
+ receivingAddress(receivingAddress) {
+ this._receivingAddress = receivingAddress
+ }
+
+ txnRequest() {
+ return {
+ addresses: this._addresses,
+ foreignKey: this._foreignKey,
+ receivingAddress: this._receivingAddress
+ }
+ }
+}
+
diff --git a/src/utils/mobile/mobileUtils.ts b/src/utils/mobile/mobileUtils.ts
new file mode 100644
index 0000000..144c8c1
--- /dev/null
+++ b/src/utils/mobile/mobileUtils.ts
@@ -0,0 +1,5 @@
+
+export const getRootHeight = ()=> {
+
+ return document?.getElementById('root')?.style?.height || '100%'
+}
\ No newline at end of file