Merge branch 'feature/public'

This commit is contained in:
PhilReact 2024-09-21 22:23:24 +03:00
commit 273ebb4d18
77 changed files with 9078 additions and 3483 deletions

26
package-lock.json generated
View File

@ -18,9 +18,11 @@
"@testing-library/jest-dom": "^6.4.6",
"@testing-library/user-event": "^14.5.2",
"@tiptap/extension-color": "^2.5.9",
"@tiptap/extension-highlight": "^2.6.6",
"@tiptap/extension-image": "^2.6.6",
"@tiptap/extension-placeholder": "^2.6.2",
"@tiptap/extension-text-style": "^2.5.9",
"@tiptap/extension-underline": "^2.6.6",
"@tiptap/pm": "^2.5.9",
"@tiptap/react": "^2.5.9",
"@tiptap/starter-kit": "^2.5.9",
@ -2300,6 +2302,18 @@
"@tiptap/core": "^2.5.9"
}
},
"node_modules/@tiptap/extension-highlight": {
"version": "2.6.6",
"resolved": "https://registry.npmjs.org/@tiptap/extension-highlight/-/extension-highlight-2.6.6.tgz",
"integrity": "sha512-Z02AYWm1AJAfhmfT4fGCI3YitijF4uNu+eiuq7OxhCiVf9IYaq8xlH2YMxa09QvMUo70ovklxk97+vQUUHeqfQ==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.6.6"
}
},
"node_modules/@tiptap/extension-history": {
"version": "2.5.9",
"resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.5.9.tgz",
@ -2435,6 +2449,18 @@
"@tiptap/core": "^2.5.9"
}
},
"node_modules/@tiptap/extension-underline": {
"version": "2.6.6",
"resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-2.6.6.tgz",
"integrity": "sha512-3A4HqsDM/AFb2VaeWACpGexjgI257kz0yU4jNV8uyydDR2KhqeinuEnoSoOmx9T3pL006TWfPg4vaQYPO3qvrQ==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.6.6"
}
},
"node_modules/@tiptap/pm": {
"version": "2.6.6",
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.6.6.tgz",

View File

@ -22,9 +22,11 @@
"@testing-library/jest-dom": "^6.4.6",
"@testing-library/user-event": "^14.5.2",
"@tiptap/extension-color": "^2.5.9",
"@tiptap/extension-highlight": "^2.6.6",
"@tiptap/extension-image": "^2.6.6",
"@tiptap/extension-placeholder": "^2.6.2",
"@tiptap/extension-text-style": "^2.5.9",
"@tiptap/extension-underline": "^2.6.6",
"@tiptap/pm": "^2.5.9",
"@tiptap/react": "^2.5.9",
"@tiptap/starter-kit": "^2.5.9",

View File

@ -23,7 +23,7 @@ document.addEventListener('qortalExtensionRequests', async (event) => {
}));
return
}
chrome.runtime.sendMessage({ action: "userInfo" }, (response) => {
chrome?.runtime?.sendMessage({ action: "userInfo" }, (response) => {
if (response.error) {
document.dispatchEvent(new CustomEvent('qortalExtensionResponses', {
detail: { type: "USER_INFO", data: {
@ -38,7 +38,7 @@ document.addEventListener('qortalExtensionRequests', async (event) => {
}
});
} else if (type === 'REQUEST_IS_INSTALLED') {
chrome.runtime.sendMessage({ action: "version" }, (response) => {
chrome?.runtime?.sendMessage({ action: "version" }, (response) => {
if (response.error) {
console.error("Error:", response.error);
} else {
@ -49,9 +49,8 @@ document.addEventListener('qortalExtensionRequests', async (event) => {
}
});
} else if (type === 'REQUEST_CONNECTION') {
console.log('REQUEST_CONNECTION')
const hostname = window.location.hostname
chrome.runtime.sendMessage({ action: "connection", payload: {
chrome?.runtime?.sendMessage({ action: "connection", payload: {
hostname
}, timeout }, (response) => {
if (response.error) {
@ -75,7 +74,7 @@ document.addEventListener('qortalExtensionRequests', async (event) => {
return
}
chrome.runtime.sendMessage({ action: "oauth", payload: {
chrome?.runtime?.sendMessage({ action: "oauth", payload: {
nodeBaseUrl: payload.nodeBaseUrl,
senderAddress: payload.senderAddress,
senderPublicKey: payload.senderPublicKey, timestamp: payload.timestamp
@ -105,9 +104,10 @@ document.addEventListener('qortalExtensionRequests', async (event) => {
return
}
chrome.runtime.sendMessage({ action: "buyOrder", payload: {
qortalAtAddress: payload.qortalAtAddress,
hostname
chrome?.runtime?.sendMessage({ action: "buyOrder", payload: {
qortalAtAddresses: payload.qortalAtAddresses,
hostname,
useLocal: payload?.useLocal
}, timeout}, (response) => {
if (response.error) {
@ -136,7 +136,7 @@ document.addEventListener('qortalExtensionRequests', async (event) => {
}));
return
}
chrome.runtime.sendMessage({ action: "ltcBalance", payload: {
chrome?.runtime?.sendMessage({ action: "ltcBalance", payload: {
hostname
}, timeout }, (response) => {
@ -153,6 +153,36 @@ document.addEventListener('qortalExtensionRequests', async (event) => {
}));
}
});
} else if(type === 'CHECK_IF_LOCAL'){
const hostname = window.location.hostname
const res = await connection(hostname)
if(!res){
document.dispatchEvent(new CustomEvent('qortalExtensionResponses', {
detail: { type: "USER_INFO", data: {
error: "Not authorized"
}, requestId }
}));
return
}
chrome?.runtime?.sendMessage({ action: "checkLocal", payload: {
hostname
}, timeout }, (response) => {
if (response.error) {
document.dispatchEvent(new CustomEvent('qortalExtensionResponses', {
detail: { type: "CHECK_IF_LOCAL", data: {
error: response.error
}, requestId }
}));
} else {
// Include the requestId in the detail when dispatching the response
document.dispatchEvent(new CustomEvent('qortalExtensionResponses', {
detail: { type: "CHECK_IF_LOCAL", data: response, requestId }
}));
}
});
} else if (type === 'REQUEST_AUTHENTICATION') {
const hostname = window.location.hostname
const res = await connection(hostname)
@ -164,7 +194,7 @@ document.addEventListener('qortalExtensionRequests', async (event) => {
}));
return
}
chrome.runtime.sendMessage({ action: "authentication", payload: {
chrome?.runtime?.sendMessage({ action: "authentication", payload: {
hostname
}, timeout }, (response) => {
if (response.error) {
@ -191,7 +221,7 @@ document.addEventListener('qortalExtensionRequests', async (event) => {
}));
return
}
chrome.runtime.sendMessage({ action: "sendQort", payload: {
chrome?.runtime?.sendMessage({ action: "sendQort", payload: {
hostname,
amount: payload.amount,
description: payload.description,
@ -221,7 +251,7 @@ document.addEventListener('qortalExtensionRequests', async (event) => {
}));
return
}
chrome.runtime.sendMessage({ action: "closePopup" }, (response) => {
chrome?.runtime?.sendMessage({ action: "closePopup" }, (response) => {
if (response.error) {
document.dispatchEvent(new CustomEvent('qortalExtensionResponses', {
@ -241,7 +271,7 @@ document.addEventListener('qortalExtensionRequests', async (event) => {
});
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
chrome.runtime?.onMessage.addListener(function(message, sender, sendResponse) {
if (message.type === "LOGOUT") {
// Notify the web page
window.postMessage({

View File

@ -77,7 +77,7 @@ display: flex;
border: 1px solid var(--50-white, rgba(255, 255, 255, 0.5));
justify-content: space-between;
align-items: center;
width: 132px;
width: 140px;
height: 25px;
padding: 5px 15px 5px 15px;
gap: 5px;

File diff suppressed because it is too large Load Diff

View File

@ -12,12 +12,11 @@ export const MessageQueueProvider = ({ children }) => {
const [queueChats, setQueueChats] = useState({}); // Stores chats and status for display
const isProcessingRef = useRef(false); // To track if the queue is being processed
const maxRetries = 4;
const clearStatesMessageQueueProvider = useCallback(() => {
setQueueChats({})
messageQueue = []
isProcessingRef.current = false
}, [])
setQueueChats({});
messageQueue = [];
isProcessingRef.current = false;
}, []);
// Function to add a message to the queue
const addToQueue = useCallback((sendMessageFunc, messageObj, type, groupDirectId) => {
@ -40,16 +39,43 @@ export const MessageQueueProvider = ({ children }) => {
// Add the message to the global messageQueue
messageQueue = [
...messageQueue,
{ func: sendMessageFunc, identifier: tempId, groupDirectId }
{ func: sendMessageFunc, identifier: tempId, groupDirectId, specialId: messageObj?.message?.specialId }
];
// Start processing the queue if not already processing
processQueue();
}, []);
// Function to process the messageQueue
// Function to process the messageQueue
const processQueue = useCallback(async () => {
// Method to process with new messages and groupDirectId
const processWithNewMessages = (newMessages, groupDirectId) => {
processQueue(newMessages, groupDirectId);
};
// Function to process the messageQueue and handle new messages
const processQueue = useCallback(async (newMessages = [], groupDirectId) => {
// Filter out any message in the queue that matches the specialId from newMessages
messageQueue = messageQueue.filter((msg) => {
return !newMessages.some(newMsg => newMsg?.specialId === msg?.specialId);
});
// Remove any corresponding entries in queueChats for the provided groupDirectId
setQueueChats((prev) => {
const updatedChats = { ...prev };
if (updatedChats[groupDirectId]) {
// Remove any message in queueChats that has a matching specialId
updatedChats[groupDirectId] = updatedChats[groupDirectId].filter((chat) => {
return !newMessages.some(newMsg => newMsg?.specialId === chat?.message?.specialId);
});
// If no more chats for this group, delete the groupDirectId entry
if (updatedChats[groupDirectId].length === 0) {
delete updatedChats[groupDirectId];
}
}
return updatedChats;
});
// If currently processing or the queue is empty, return
if (isProcessingRef.current || messageQueue.length === 0) return;
@ -77,18 +103,17 @@ const processQueue = useCallback(async () => {
// Execute the function stored in the messageQueue
await currentMessage.func();
// Remove the message from the messageQueue after successful sending
messageQueue = messageQueue.slice(1);
messageQueue = messageQueue.slice(1); // Slice here remains for successful messages
// Remove the message from queueChats after success
setQueueChats((prev) => {
const updatedChats = { ...prev };
updatedChats[groupDirectId] = updatedChats[groupDirectId].filter(
(item) => item.identifier !== identifier
);
return updatedChats;
});
// setQueueChats((prev) => {
// const updatedChats = { ...prev };
// updatedChats[groupDirectId] = updatedChats[groupDirectId].filter(
// (item) => item.identifier !== identifier
// );
// return updatedChats;
// });
} catch (error) {
console.error('Message sending failed', error);
@ -109,7 +134,7 @@ const processQueue = useCallback(async () => {
updatedChats[groupDirectId][chatIndex].status = 'failed-permanent';
// Remove the message from the messageQueue after max retries
messageQueue = messageQueue.slice(1);
messageQueue = messageQueue.slice(1); // Slice for failed messages after max retries
// Remove the message from queueChats after failure
updatedChats[groupDirectId] = updatedChats[groupDirectId].filter(
@ -129,9 +154,8 @@ const processQueue = useCallback(async () => {
isProcessingRef.current = false;
}, []);
return (
<MessageQueueContext.Provider value={{ addToQueue, queueChats, clearStatesMessageQueueProvider }}>
<MessageQueueContext.Provider value={{ addToQueue, queueChats, clearStatesMessageQueueProvider, processWithNewMessages }}>
{children}
</MessageQueueContext.Provider>
);

View File

@ -0,0 +1,14 @@
import React from 'react';
export const ArrowDownIcon= ({ color = 'white', height, width }) => {
return (
<svg width="10" height="9" viewBox="0 0 10 9" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.7" d="M4.944 8.12L-1.86823e-07 0.992L0.648 0.536L4.944 6.776L9.24 0.536L9.888 0.992L4.944 8.12Z" fill={color}/>
</svg>
);
};

View File

@ -0,0 +1,12 @@
import React from 'react';
export const ChatIcon= ({ color = 'white', height = 15, width = 15 }) => {
return (
<svg width={width} height={height} viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.5 0C3.35915 0 0 3.35915 0 7.5V13.8169C0 14.4718 0.528169 15 1.1831 15H7.5C11.6408 15 15 11.6408 15 7.5C15 3.35915 11.6408 0 7.5 0ZM11.0915 10.669H3.90845C3.67606 10.669 3.48592 10.4789 3.48592 10.2465C3.48592 10.0141 3.67606 9.82394 3.90845 9.82394H11.0915C11.3239 9.82394 11.5141 10.0141 11.5141 10.2465C11.5141 10.4789 11.3239 10.669 11.0915 10.669ZM11.0915 8.34507H3.90845C3.67606 8.34507 3.48592 8.15493 3.48592 7.92254C3.48592 7.69014 3.67606 7.5 3.90845 7.5H11.0915C11.3239 7.5 11.5141 7.69014 11.5141 7.92254C11.5141 8.15493 11.3239 8.34507 11.0915 8.34507ZM11.0915 6.02113H3.90845C3.67606 6.02113 3.48592 5.83099 3.48592 5.59859C3.48592 5.3662 3.67606 5.17606 3.90845 5.17606H11.0915C11.3239 5.17606 11.5141 5.3662 11.5141 5.59859C11.5141 5.83099 11.3239 6.02113 11.0915 6.02113Z" fill={color}/>
</svg>
);
};

View File

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

View File

@ -0,0 +1,12 @@
import React from 'react';
export const HomeIcon= ({ color, height = 20, width = 23 }) => {
return (
<svg width={width} height={height} viewBox="0 0 23 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22.936 11.9842C22.8122 12.2972 22.5127 12.502 22.1801 12.5008H19.7155V19.1668C19.714 19.6263 19.3471 19.9985 18.8939 20H14.7862V14.1673C14.7862 12.3265 13.3149 10.8343 11.5 10.8343C9.68509 10.8343 8.21381 12.3265 8.21381 14.1673V20H4.10607C3.65294 19.9985 3.28596 19.6263 3.28452 19.1668V12.5008H0.819874C0.487346 12.502 0.187777 12.2972 0.0640491 11.9842C-0.0642812 11.6739 0.00375033 11.3157 0.236574 11.076L10.9167 0.243778C11.2395 -0.0812593 11.7605 -0.0812593 12.0833 0.243778L22.7634 11.076C22.9963 11.3157 23.0643 11.6739 22.936 11.9842Z" fill={color}/>
</svg>
);
};

View File

@ -0,0 +1,12 @@
import React from 'react';
export const HubsIcon= ({ color, height = 31, width = 32 }) => {
return (
<svg width={width} height={height} viewBox="0 0 32 31" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M24.5333 30H22.4C22.3987 28.1694 20.9667 26.6856 19.2 26.6842H12.8C11.0333 26.6856 9.60133 28.1694 9.6 30H7.46667C7.468 28.5355 8.03065 27.1305 9.03066 26.0943C10.0307 25.0581 11.3866 24.4751 12.8 24.4737H19.2C20.6133 24.4751 21.9693 25.0581 22.9693 26.0943C23.9693 27.1305 24.532 28.5355 24.5333 30ZM11.7333 17.8422C11.7333 16.0545 12.7733 14.4422 14.3667 13.7583C15.9613 13.073 17.7974 13.4516 19.0173 14.7157C20.2373 15.9798 20.6026 17.8822 19.9413 19.5346C19.2813 21.1856 17.7253 22.2632 15.9999 22.2632C13.6439 22.2604 11.7361 20.2834 11.7333 17.8422ZM13.8667 17.8422C13.8667 18.7361 14.3867 19.5429 15.184 19.8842C15.9813 20.2268 16.8987 20.0375 17.508 19.4048C18.1187 18.7734 18.3013 17.8228 17.9707 16.9967C17.6414 16.1705 16.8627 15.6317 16 15.6317C14.8227 15.6331 13.868 16.6223 13.8667 17.8422ZM26.6667 20.0527H22.4V22.2632H26.6667C28.4333 22.2646 29.8653 23.7484 29.8667 25.579H32C31.9987 24.1145 31.436 22.7095 30.436 21.6733C29.436 20.6371 28.08 20.0541 26.6667 20.0527ZM21.3333 13.4212C21.3333 11.6335 22.3733 10.0212 23.9667 9.33727C25.5613 8.65201 27.3974 9.03056 28.6173 10.2947C29.8373 11.5588 30.2026 13.4612 29.5413 15.1136C28.8813 16.7646 27.3253 17.8422 25.5999 17.8422C23.2439 17.8394 21.3361 15.8624 21.3333 13.4212ZM23.4667 13.4212C23.4667 14.3151 23.9867 15.1219 24.784 15.4632C25.5813 15.8058 26.4987 15.6165 27.108 14.9837C27.7187 14.3524 27.9013 13.4018 27.5707 12.5757C27.2414 11.7495 26.4627 11.2107 25.6 11.2107C24.4227 11.2121 23.468 12.2013 23.4667 13.4212ZM9.6 20.0527H5.33333C3.92001 20.0541 2.56399 20.6371 1.56399 21.6733C0.563985 22.7095 0.0013312 24.1145 0 25.579H2.13333C2.13467 23.7484 3.56666 22.2646 5.33333 22.2632H9.6V20.0527ZM2.13333 13.4212C2.13333 11.6335 3.17334 10.0212 4.76665 9.33727C6.36133 8.65201 8.19739 9.03056 9.41732 10.2947C10.6373 11.5588 11.0026 13.4612 10.3413 15.1136C9.6813 16.7646 8.1253 17.8422 6.39993 17.8422C4.04395 17.8394 2.13606 15.8624 2.13333 13.4212ZM4.26667 13.4212C4.26667 14.3151 4.78665 15.1219 5.58401 15.4632C6.38133 15.8058 7.29866 15.6165 7.90801 14.9837C8.51869 14.3524 8.70134 13.4018 8.37069 12.5757C8.04136 11.7495 7.26269 11.2107 6.40003 11.2107C5.22271 11.2121 4.268 12.2013 4.26667 13.4212Z" fill={color}/>
</svg>
);
};

View File

@ -0,0 +1,12 @@
import React from 'react';
export const LogoutIcon= ({ color, height = 20, width = 18}) => {
return (
<svg width={width} height={height} viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.4351 0H1.63891C0.733765 0 0 0.727797 0 1.62558V18.3744C0 19.2722 0.733765 20 1.63891 20H10.4351C11.3403 20 12.0741 19.2722 12.0741 18.3744V12.6013H7.38321C6.54312 12.6013 5.85964 11.9039 5.85964 11.0467V8.87329C5.85964 8.01613 6.54312 7.31875 7.38323 7.31875H12.0741V1.62558C12.0741 0.727797 11.3403 0 10.4351 0ZM6.83334 11.0467C6.83334 11.3719 7.07952 11.6354 7.38321 11.6354H13.1856C13.2548 11.6354 13.3109 11.6955 13.3109 11.7696V12.8632C13.3109 13.3492 13.8299 13.6259 14.1922 13.3329L17.7816 10.4298C18.0728 10.1942 18.0728 9.72579 17.7816 9.49024L14.1922 6.58709C13.8299 6.29409 13.3109 6.57077 13.3109 7.05684V8.1504C13.3109 8.2245 13.2548 8.28454 13.1856 8.28454H7.38322C7.07952 8.28454 6.83334 8.54813 6.83334 8.87329V11.0467Z" fill={color} />
</svg>
);
};

View File

@ -0,0 +1,19 @@
import React from 'react';
export const MembersIcon= ({ color = 'white', height = 9, width = 15 }) => {
return (
<svg width={width} height={height} viewBox="0 0 15 9" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.49996 4.9293C8.77012 4.9293 9.79978 3.94984 9.79978 2.74163C9.79978 1.53341 8.77012 0.553955 7.49996 0.553955C6.2298 0.553955 5.20013 1.53341 5.20013 2.74163C5.20013 3.94984 6.2298 4.9293 7.49996 4.9293Z" fill={color}/>
<path d="M9.30277 4.64917C8.82468 5.05863 8.19406 5.30992 7.50002 5.30992C6.80598 5.30992 6.17536 5.05863 5.69727 4.64917C4.97694 5.02944 4.31037 5.23459 3.95385 6.59055C3.794 7.19904 3.63416 8.18808 3.63416 8.18808C3.63416 8.18808 5.12685 9.00005 7.50031 9.00005C9.87349 9.00005 11.3662 8.18808 11.3662 8.18808C11.3662 8.18808 11.2063 7.19904 11.0465 6.59055C10.6897 5.23487 10.0231 5.02944 9.30277 4.64917Z" fill={color}/>
<path d="M10.0088 3.65572C10.3802 3.94092 10.8519 4.11243 11.3662 4.11243C12.5602 4.11243 13.5278 3.19205 13.5278 2.05652C13.5278 0.920989 12.5599 0.000610352 11.3662 0.000610352C10.5649 0.000610352 9.86676 0.416185 9.49359 1.03301C9.92404 1.48639 10.1882 2.08432 10.1882 2.74201C10.1879 3.06363 10.1239 3.37107 10.0088 3.65572Z" fill={color}/>
<path d="M14.6993 5.67372C14.3644 4.39948 13.7378 4.20712 13.0608 3.84937C12.6116 4.23408 12.0187 4.47009 11.3661 4.47009C10.7954 4.47009 10.2697 4.2894 9.84627 3.98613C9.76707 4.12095 9.6756 4.24937 9.57245 4.36807C9.61599 4.39058 9.66012 4.41338 9.70337 4.43534C10.3705 4.77419 11.0608 5.12416 11.4231 6.50098C11.5429 6.95797 11.6601 7.60649 11.7159 7.93144C13.7419 7.85834 15 7.1759 15 7.1759C15 7.1759 14.8495 6.24607 14.6993 5.67372Z" fill={color}/>
<path d="M3.63377 4.1121C4.14809 4.1121 4.62003 3.94087 4.99116 3.65538C4.87632 3.37102 4.81203 3.0633 4.81203 2.7414C4.81203 2.08371 5.07649 1.4855 5.50665 1.0324C5.13348 0.415574 4.43535 0 3.63377 0C2.44002 0 1.47217 0.920657 1.47217 2.05591C1.47217 3.192 2.44002 4.1121 3.63377 4.1121Z" fill={color}/>
<path d="M5.29632 4.43534C5.33956 4.41338 5.38369 4.39058 5.42753 4.36807C5.32437 4.24937 5.23319 4.12095 5.15371 3.98613C4.73027 4.2894 4.20456 4.47009 3.63384 4.47009C2.98159 4.47009 2.38866 4.23381 1.93922 3.84937C1.26213 4.20712 0.635593 4.39948 0.300701 5.67372C0.150497 6.24635 0 7.17563 0 7.17563C0 7.17563 1.25833 7.85806 3.28404 7.93116C3.33986 7.60621 3.45733 6.95769 3.57715 6.5007C3.93921 5.12388 4.62916 4.77391 5.29632 4.43534Z" fill={color}/>
</svg>
);
};

View File

@ -0,0 +1,13 @@
import React from 'react';
export const MessagingIcon= ({ color, height = 31, width = 31 }) => {
return (
<svg width={width} height={height} viewBox="0 0 31 31" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M26.3937 4.49877C23.6712 1.56681 3.1922 8.74911 3.20912 11.3714C3.22829 14.345 11.2067 15.2597 13.4181 15.8802C14.748 16.2532 15.1041 16.6357 15.4107 18.0302C16.7995 24.3457 17.4967 27.487 19.0859 27.5571C21.6189 27.6691 29.0507 7.36011 26.3937 4.49877Z" stroke={color} stroke-width="2"/>
<path d="M14.4591 16.3076L18.8341 11.9326" stroke={color} stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
);
};

View File

@ -0,0 +1,14 @@
import React from 'react';
export const MessagingIcon2= ({ color = '#8F8F91', height = 24, width =24 }) => {
return (
<svg width={width} height={height} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.6636 0.00168233C22.6127 -0.000756257 22.5614 -0.000627677 22.5099 0.00261984C22.3724 0.0112798 22.2331 0.0405753 22.0969 0.093558L1.02096 8.28971C0.362343 8.54585 -0.00366118 9.18408 2.76147e-05 9.79253C0.00371641 10.401 0.377567 11.0341 1.03925 11.2822L9.02065 14.2752C9.34631 14.3974 9.60258 14.6536 9.72471 14.9793L12.7177 22.9607C12.9658 23.6224 13.5989 23.9963 14.2074 24C14.8158 24.0037 15.454 23.6376 15.7102 22.979L23.9063 1.90295C24.1182 1.35797 23.9526 0.768987 23.5917 0.408091C23.3549 0.171254 23.02 0.0187526 22.6636 0.00168233ZM18.4022 4.99812C18.5613 4.99815 18.7139 5.06138 18.8264 5.17391C18.9389 5.28643 19.0021 5.43902 19.0021 5.59813C19.0021 5.75724 18.9389 5.90983 18.8264 6.02235L13.2239 11.6244C13.1114 11.7369 12.9588 11.8001 12.7997 11.8001C12.6406 11.8001 12.488 11.7369 12.3755 11.6244C12.263 11.5119 12.1998 11.3593 12.1998 11.2002C12.1998 11.0411 12.263 10.8885 12.3755 10.776L17.9775 5.17391C18.0333 5.11813 18.0995 5.0739 18.1724 5.04374C18.2452 5.01357 18.3233 4.99807 18.4022 4.99812Z" fill={color}/>
</svg>
);
};

View File

@ -0,0 +1,14 @@
import React from 'react';
export const NotificationIcon= ({ color = 'white', height = 16, width = 14 }) => {
return (
<svg width={width} height={height} viewBox="0 0 14 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.73084 13.401H5.27178C5.11667 13.401 4.96853 13.4672 4.86412 13.5829C4.7647 13.6946 4.71697 13.8439 4.73387 13.9932C4.86611 15.1369 5.84149 15.9999 6.99878 15.9999C8.15709 15.9999 9.13146 15.1369 9.26369 13.9932C9.28159 13.8439 9.23287 13.6946 9.13344 13.5829C9.03103 13.4672 8.88295 13.401 8.73084 13.401Z" fill={color}/>
<path d="M13.0219 10.4077V10.4048C12.5506 10.0983 12.2583 9.51111 12.2583 8.87843V6.75003C12.2583 4.52575 10.8614 2.49816 8.83004 1.72603C8.77934 0.764153 7.97696 0 6.99962 0C6.02525 0 5.22585 0.762179 5.16919 1.71614C3.1071 2.47536 1.74093 4.40908 1.74093 6.6136V8.8784C1.74093 9.51108 1.44863 10.0983 0.977343 10.4067C0.366879 10.8061 0 11.4823 0 12.2089V12.8416C0 13.4367 0.486203 13.9201 1.08473 13.9201H12.9153C13.5138 13.9201 14 13.4367 14 12.8416V12.2089C13.999 11.4833 13.6351 10.8071 13.0216 10.4077L13.0219 10.4077Z" fill={color}/>
</svg>
);
};

View File

@ -0,0 +1,11 @@
import React from 'react';
export const NotificationIcon2= ({ color = 'white', height = 15, width = 15 }) => {
return (
<svg width={width} height={height} viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.50253 0.80505C8.50253 0.360285 8.14231 0 7.69818 0C7.25348 0 6.89325 0.36027 6.89325 0.80505V1.6142C6.89325 2.05896 7.25346 2.41866 7.69818 2.41866C8.14229 2.41866 8.50253 2.05839 8.50253 1.61361V0.80505ZM7.06691 3.12343C7.45178 2.90163 7.944 3.03366 8.16577 3.41857L12.3049 10.5872C12.5231 10.9715 12.3905 11.4603 12.0074 11.6815C11.6243 11.9027 11.1344 11.773 10.9109 11.3916L10.8517 11.2884L7.53454 12.0588C7.73401 12.7922 7.56388 13.5767 7.07751 14.1606C6.59173 14.7444 5.85192 15.0548 5.09454 14.992C4.33771 14.9286 3.65892 14.5009 3.27588 13.8443L3.27353 13.8414L2.86989 13.1431L1.30986 13.5046L1.31045 13.504C1.08047 13.5574 0.842856 13.4547 0.724941 13.2499L0.0713702 12.118C-0.0459665 11.9138 -0.0166327 11.6562 0.144705 11.4837L6.83346 4.32684L6.77245 4.22298C6.66567 4.03814 6.63633 3.8187 6.69207 3.61216C6.74722 3.40561 6.8821 3.23022 7.06691 3.12343ZM4.50841 12.7617L5.96397 12.4237C6.09539 12.7746 5.9364 13.1683 5.59729 13.3279C5.25819 13.4875 4.85338 13.3602 4.66623 13.0351L4.50841 12.7617ZM15 7.30235C15 7.51593 14.9155 7.72072 14.7647 7.87152C14.614 8.02232 14.4092 8.1074 14.1957 8.1074H13.3866C12.9419 8.1074 12.5823 7.74713 12.5823 7.30235C12.5823 6.85817 12.9425 6.49788 13.3872 6.49788H14.1951C14.6398 6.49788 15 6.85815 15 7.30235ZM2.01088 8.10681C2.45558 8.10681 2.81582 7.74713 2.81582 7.30235C2.81582 6.85817 2.4556 6.49788 2.01088 6.49788H1.20185C0.757149 6.49788 0.397502 6.85815 0.397502 7.30235C0.397502 7.74711 0.75772 8.1074 1.20244 8.1074H2.01147L2.01088 8.10681ZM4.35176 3.95659C4.03789 4.2705 3.52864 4.27109 3.21477 3.95717L2.53481 3.27712C2.37465 3.12808 2.28253 2.92036 2.27843 2.70209C2.27491 2.48381 2.35998 2.27374 2.51428 2.11884C2.66857 1.96453 2.8792 1.87943 3.09744 1.88355C3.31568 1.88766 3.52278 1.97978 3.6718 2.13937L4.35295 2.81826C4.50431 2.96906 4.58879 3.17443 4.58879 3.38802C4.58879 3.60161 4.50431 3.80639 4.35295 3.95719L4.35176 3.95659ZM12.861 3.27653L12.8616 3.27712C13.1567 2.95967 13.1479 2.46562 12.8416 2.15932C12.5354 1.85302 12.0414 1.84422 11.724 2.13937L11.0352 2.82589C10.7214 3.1398 10.7214 3.6503 11.0352 3.9648C11.3497 4.27872 11.8595 4.27872 12.1734 3.9648L12.861 3.27829V3.27653Z" fill={color}/>
</svg>
);
};

View File

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

View File

@ -0,0 +1,13 @@
import React from 'react';
export const ThreadsIcon= ({ color = 'white', height = 11, width = 15 }) => {
return (
<svg width={width} height={height} viewBox="0 0 15 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.76526 1.80943H6.51017C5.91236 1.80943 5.42723 2.37147 5.42723 3.06406V6.55419V6.82615H4L1.90297 9L2.00782 6.82615H0.8482C0.380282 6.82615 0 6.38558 0 5.84347V0.982675C0 0.440572 0.380282 0 0.8482 0H7.1518C7.62128 0 8 0.440572 8 0.982675V1.80943H7.76526ZM8.89437 2H14.0458C14.5722 2 15 2.44057 15 2.98268V7.84166C15 8.38558 14.5722 8.82434 14.0458 8.82434H12.7412L12.8592 11L10.5 8.82434H6.95423C6.42606 8.82434 6 8.38558 6 7.84166V7.01672V6.74476V6.47281V2.98268C6 2.44057 6.42606 2 6.95423 2H8.3662H8.63028H8.89437Z" fill={color}/>
</svg>
);
};

View File

@ -0,0 +1,11 @@
import React from 'react';
export const TradingIcon= ({ color, height, width }) => {
return (
<svg width="31" height="31" viewBox="0 0 31 31" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M29.6627 28.5686H5.6079C5.06735 28.5647 4.53939 28.4034 4.08736 28.104L8.41371 24.3903C8.68787 24.1495 9.01146 23.9731 9.36122 23.8737C9.71097 23.7744 10.0782 23.7546 10.4364 23.8157L18.8398 25.2345C19.5576 25.3564 20.294 25.3032 20.9873 25.0792C21.6806 24.8552 22.3109 24.467 22.8255 23.947L29.617 17.1085C29.8031 16.9068 29.9044 16.64 29.8996 16.3643C29.8948 16.0886 29.7842 15.8256 29.5912 15.6307C29.3982 15.4357 29.1378 15.324 28.8649 15.3192C28.5919 15.3143 28.3278 15.4166 28.1281 15.6046L21.3225 22.4537C21.047 22.7365 20.7084 22.9485 20.3351 23.0719C19.9618 23.1953 19.5646 23.2266 19.1769 23.1631L10.7876 21.7195C10.1245 21.6058 9.44477 21.6422 8.79738 21.8263C8.15 22.0103 7.55117 22.3373 7.04417 22.7836L2.87233 26.3624C2.82441 26.1554 2.79968 25.9437 2.79859 25.7311V24.3123L7.18463 16.8389C7.44944 16.3923 7.84654 16.041 8.3198 15.8348C8.79305 15.6286 9.31851 15.5778 9.82188 15.6897L17.5299 17.3745C18.1946 17.5208 18.8833 17.5152 19.5455 17.358C20.2078 17.2009 20.8269 16.8963 21.3576 16.4665L29.5327 9.8408C29.6456 9.75519 29.7402 9.64756 29.8111 9.52429C29.8819 9.40103 29.9275 9.26464 29.9452 9.12323C29.9629 8.98181 29.9522 8.83826 29.9139 8.70108C29.8756 8.5639 29.8104 8.4359 29.7221 8.32467C29.6339 8.21344 29.5244 8.12125 29.4002 8.05357C29.276 7.98589 29.1396 7.9441 28.9991 7.93069C28.8587 7.91727 28.7169 7.9325 28.5824 7.97547C28.4478 8.01844 28.3232 8.08828 28.2159 8.18084L20.0408 14.8065C19.755 15.0381 19.4216 15.2023 19.065 15.2869C18.7083 15.3716 18.3374 15.3747 17.9794 15.296L10.2714 13.597C9.33145 13.387 8.34986 13.4825 7.46689 13.8698C6.58392 14.2571 5.84477 14.9164 5.35506 15.7535L2.79859 20.1411V1.74669C2.79859 1.46448 2.68759 1.19383 2.49003 0.994272C2.29246 0.794718 2.0245 0.68261 1.74509 0.68261C1.46569 0.68261 1.19773 0.794718 1.00016 0.994272C0.802591 1.19383 0.691598 1.46448 0.691598 1.74669V25.7311C0.689568 26.7954 1.02832 27.8318 1.6573 28.6857C1.6573 28.707 1.68539 28.7318 1.70295 28.7531C2.1613 29.3522 2.74923 29.8376 3.42178 30.172C4.09433 30.5064 4.83369 30.6811 5.58332 30.6826H29.6381C29.9175 30.6826 30.1855 30.5705 30.383 30.3709C30.5806 30.1714 30.6916 29.9007 30.6916 29.6185C30.6916 29.3363 30.5806 29.0657 30.383 28.8661C30.1855 28.6666 29.9175 28.5544 29.6381 28.5544L29.6627 28.5686Z" fill={color}/>
</svg>
);
};

View File

@ -0,0 +1,12 @@
import React from 'react';
export const WalletIcon= ({ color, height, width }) => {
return (
<svg width="31" height="31" viewBox="0 0 31 31" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.0118 22.0891C18.0124 22.8671 16.6997 23.3391 15.2618 23.3391C13.8241 23.3391 12.5113 22.8671 11.5118 22.0891" stroke={color} stroke-width="2" stroke-linecap="round"/>
<path d="M3.20108 17.356C2.7598 14.4844 2.53917 13.0486 3.08205 11.7758C3.62493 10.503 4.82938 9.63215 7.23827 7.89044L9.03808 6.58911C12.0347 4.42245 13.5331 3.33911 15.2618 3.33911C16.9907 3.33911 18.4889 4.42245 21.4856 6.58911L23.2854 7.89044C25.6943 9.63215 26.8988 10.503 27.4417 11.7758C27.9846 13.0486 27.7639 14.4844 27.3226 17.356L26.9463 19.8046C26.3208 23.8752 26.0079 25.9106 24.5481 27.1249C23.0882 28.3391 20.9539 28.3391 16.6853 28.3391H13.8383C9.56977 28.3391 7.43548 28.3391 5.97559 27.1249C4.5157 25.9106 4.20293 23.8752 3.57738 19.8046L3.20108 17.356Z" stroke={color} stroke-width="2" stroke-linejoin="round"/>
</svg>
);
};

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 561 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 562 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 562 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 821 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 508 KiB

View File

@ -0,0 +1,3 @@
<svg width="15" height="11" viewBox="0 0 15 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.76526 1.80943H6.51017C5.91236 1.80943 5.42723 2.37147 5.42723 3.06406V6.55419V6.82615H4L1.90297 9L2.00782 6.82615H0.8482C0.380282 6.82615 0 6.38558 0 5.84347V0.982675C0 0.440572 0.380282 0 0.8482 0H7.1518C7.62128 0 8 0.440572 8 0.982675V1.80943H7.76526ZM8.89437 2H14.0458C14.5722 2 15 2.44057 15 2.98268V7.84166C15 8.38558 14.5722 8.82434 14.0458 8.82434H12.7412L12.8592 11L10.5 8.82434H6.95423C6.42606 8.82434 6 8.38558 6 7.84166V7.01672V6.74476V6.47281V2.98268C6 2.44057 6.42606 2 6.95423 2H8.3662H8.63028H8.89437Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 688 B

File diff suppressed because it is too large Load Diff

17
src/common/CustomSvg.tsx Normal file
View File

@ -0,0 +1,17 @@
import React from 'react';
export const CustomSvg = ({ src, color = 'black', size = 24 }) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
style={{ fill: color }}
>
{src}
</svg>
);
};

View File

@ -10,7 +10,7 @@ import { decryptPublishes, getTempPublish, saveTempPublish } from "./GroupAnnoun
import { AnnouncementList } from "./AnnouncementList";
import { Spacer } from "../../common/Spacer";
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import { getBaseApiReact, pauseAllQueues, resumeAllQueues } from "../../App";
import { getArbitraryEndpointReact, getBaseApiReact, isMobile, pauseAllQueues, resumeAllQueues } from "../../App";
const tempKey = 'accouncement-comment'
@ -26,6 +26,8 @@ export const AnnouncementDiscussion = ({
}) => {
const [isSending, setIsSending] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [isFocusedParent, setIsFocusedParent] = useState(false);
const [comments, setComments] = useState([])
const [tempPublishedList, setTempPublishedList] = useState([])
const firstMountRef = useRef(false)
@ -38,6 +40,12 @@ export const AnnouncementDiscussion = ({
const clearEditorContent = () => {
if (editorRef.current) {
editorRef.current.chain().focus().clearContent().run();
if(isMobile){
setTimeout(() => {
editorRef.current?.chain().blur().run();
setIsFocusedParent(false)
}, 200);
}
}
};
@ -47,6 +55,7 @@ export const AnnouncementDiscussion = ({
const res = await fetch(
`${getBaseApiReact()}/arbitrary/DOCUMENT/${name}/${identifier}?encoding=base64`
);
if(!res?.ok) return
const data = await res.text();
const response = await decryptPublishes([{ data }], secretKey);
@ -66,7 +75,7 @@ export const AnnouncementDiscussion = ({
if (!selectedAnnouncement) return;
return new Promise((res, rej) => {
chrome.runtime.sendMessage(
chrome?.runtime?.sendMessage(
{
action: "publishGroupEncryptedResource",
payload: {
@ -78,6 +87,7 @@ export const AnnouncementDiscussion = ({
if (!response?.error) {
res(response);
return
}
rej(response.error);
}
@ -170,7 +180,7 @@ export const AnnouncementDiscussion = ({
// dispatch(setIsLoadingGlobal(true))
const identifier = `cm-${selectedAnnouncement.identifier}`;
const url = `${getBaseApiReact()}/arbitrary/resources/search?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=20&includemetadata=false&offset=${offset}&reverse=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: {
@ -200,7 +210,7 @@ export const AnnouncementDiscussion = ({
const offset = comments.length
const identifier = `cm-${selectedAnnouncement.identifier}`;
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: {
@ -245,7 +255,7 @@ export const AnnouncementDiscussion = ({
return (
<div
style={{
height: "100vh",
height: isMobile ? '100%' : "100vh",
display: "flex",
flexDirection: "column",
width: "100%",
@ -259,7 +269,9 @@ export const AnnouncementDiscussion = ({
flexShrink: 0,
}}>
<AuthenticatedContainerInnerTop>
<AuthenticatedContainerInnerTop sx={{
height: '20px'
}}>
<ArrowBackIcon onClick={()=> setSelectedAnnouncement(null)} sx={{
cursor: 'pointer'
}} />
@ -274,6 +286,7 @@ export const AnnouncementDiscussion = ({
disableComment
showLoadMore={comments.length > 0 && comments.length % 20 === 0}
loadMore={loadMore}
myName={myName}
/>
<div
@ -281,14 +294,18 @@ export const AnnouncementDiscussion = ({
// position: 'fixed',
// bottom: '0px',
backgroundColor: "#232428",
minHeight: "150px",
maxHeight: "400px",
minHeight: isMobile ? "0px" : "150px",
maxHeight: isMobile ? "auto" : "400px",
display: "flex",
flexDirection: "column",
overflow: "hidden",
width: "100%",
boxSizing: "border-box",
padding: "20px",
padding: isMobile ? "10px": "20px",
position: isFocusedParent ? 'fixed' : 'relative',
bottom: isFocusedParent ? '0px' : 'unset',
top: isFocusedParent ? '0px' : 'unset',
zIndex: isFocusedParent ? 5 : 'unset',
flexShrink:0,
}}
>
@ -297,6 +314,7 @@ export const AnnouncementDiscussion = ({
display: "flex",
flexDirection: "column",
// height: '100%',
flexGrow: isMobile && 1,
overflow: "auto",
}}
>
@ -304,8 +322,42 @@ export const AnnouncementDiscussion = ({
setEditorRef={setEditorRef}
onEnter={publishComment}
disableEnter
maxHeightOffset="60px"
isFocusedParent={isFocusedParent} setIsFocusedParent={setIsFocusedParent}
/>
</div>
<Box sx={{
display: 'flex',
width: '100&',
gap: '10px',
justifyContent: 'center',
flexShrink: 0,
position: 'relative',
}}>
{isFocusedParent && (
<CustomButton
onClick={()=> {
if(isSending) return
setIsFocusedParent(false)
clearEditorContent()
// Unfocus the editor
}}
style={{
marginTop: 'auto',
alignSelf: 'center',
cursor: isSending ? 'default' : 'pointer',
flexShrink: 0,
padding: isMobile && '5px',
fontSize: isMobile && '14px',
background: 'red',
}}
>
{` Close`}
</CustomButton>
)}
<CustomButton
onClick={() => {
if (isSending) return;
@ -317,6 +369,8 @@ export const AnnouncementDiscussion = ({
cursor: isSending ? "default" : "pointer",
background: isSending && "rgba(0, 0, 0, 0.8)",
flexShrink: 0,
padding: isMobile && '5px',
fontSize: isMobile && '14px'
}}
>
{isSending && (
@ -334,6 +388,8 @@ export const AnnouncementDiscussion = ({
)}
{` Publish Comment`}
</CustomButton>
</Box>
</div>
<LoadingSnackbar

View File

@ -9,8 +9,9 @@ import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import { getBaseApi } from "../../background";
import { requestQueueCommentCount } from "./GroupAnnouncements";
import { CustomLoader } from "../../common/CustomLoader";
import { getBaseApiReact } from "../../App";
export const AnnouncementItem = ({ message, messageData, setSelectedAnnouncement, disableComment }) => {
import { getArbitraryEndpointReact, getBaseApiReact } from "../../App";
import { WrapperUserAction } from "../WrapperUserAction";
export const AnnouncementItem = ({ message, messageData, setSelectedAnnouncement, disableComment, myName }) => {
const [commentLength, setCommentLength] = useState(0)
const getNumberOfComments = React.useCallback(
@ -20,7 +21,7 @@ export const AnnouncementItem = ({ message, messageData, setSelectedAnnouncement
// dispatch(setIsLoadingGlobal(true))
const identifier = `cm-${message.identifier}`;
const url = `${getBaseApiReact()}/arbitrary/resources/search?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=0&includemetadata=false&offset=${offset}&reverse=true`;
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=0&includemetadata=false&offset=${offset}&reverse=true&prefix=true`;
const response = await requestQueueCommentCount.enqueue(() => {
return fetch(url, {
@ -60,8 +61,10 @@ export const AnnouncementItem = ({ message, messageData, setSelectedAnnouncement
<Box sx={{
display: "flex",
gap: '7px',
width: '100%'
width: '100%',
wordBreak: 'break-word'
}}>
<WrapperUserAction disabled={myName === message?.name} address={undefined} name={message?.name}>
<Avatar
sx={{
backgroundColor: '#27282c',
@ -72,6 +75,7 @@ export const AnnouncementItem = ({ message, messageData, setSelectedAnnouncement
>
{message?.name?.charAt(0)}
</Avatar>
</WrapperUserAction>
<Box
sx={{
display: "flex",
@ -80,6 +84,7 @@ export const AnnouncementItem = ({ message, messageData, setSelectedAnnouncement
width: '100%'
}}
>
<WrapperUserAction disabled={myName === message?.name} address={undefined} name={message?.name}>
<Typography
sx={{
fontWight: 600,
@ -89,6 +94,7 @@ export const AnnouncementItem = ({ message, messageData, setSelectedAnnouncement
>
{message?.name}
</Typography>
</WrapperUserAction>
{!messageData?.decryptedData && (
<Box sx={{
width: '100%',

View File

@ -20,7 +20,8 @@ export const AnnouncementList = ({
setSelectedAnnouncement,
disableComment,
showLoadMore,
loadMore
loadMore,
myName
}) => {
const listRef = useRef();
@ -55,6 +56,7 @@ export const AnnouncementList = ({
return (
<div
key={message?.identifier}
style={{
marginBottom: "10px",
width: "100%",
@ -63,7 +65,7 @@ export const AnnouncementList = ({
alignItems: "center",
}}
>
<AnnouncementItem disableComment={disableComment} setSelectedAnnouncement={setSelectedAnnouncement} message={message} messageData={messageData} />
<AnnouncementItem myName={myName} disableComment={disableComment} setSelectedAnnouncement={setSelectedAnnouncement} message={message} messageData={messageData} />
</div>
);

View File

@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'
import { objectToBase64 } from '../../qdn/encryption/group-encryption'
import { ChatList } from './ChatList'
@ -6,18 +6,29 @@ import "@chatscope/chat-ui-kit-styles/dist/default/styles.min.css";
import Tiptap from './TipTap'
import { CustomButton } from '../../App-styles'
import CircularProgress from '@mui/material/CircularProgress';
import { Input } from '@mui/material';
import { Box, ButtonBase, Input, Typography } from '@mui/material';
import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar';
import { getNameInfo } from '../Group/Group';
import { Spacer } from '../../common/Spacer';
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
import { getBaseApiReactSocket } from '../../App';
import { getBaseApiReactSocket, isMobile, pauseAllQueues, resumeAllQueues } from '../../App';
import { getPublicKey } from '../../background';
import { useMessageQueue } from '../../MessageQueueContext';
import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from '../../utils/events';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import ShortUniqueId from "short-unique-id";
import { ReturnIcon } from '../../assets/Icons/ReturnIcon';
import { ExitIcon } from '../../assets/Icons/ExitIcon';
import { MessageItem, ReplyPreview } from './MessageItem';
const uid = new ShortUniqueId({ length: 5 });
export const ChatDirect = ({ myAddress, isNewChat, selectedDirect, setSelectedDirect, setNewChat, getTimestampEnterChat, myName, balance, close, setMobileViewModeKeepOpen}) => {
const { queueChats, addToQueue, processWithNewMessages} = useMessageQueue();
const [isFocusedParent, setIsFocusedParent] = useState(false);
export const ChatDirect = ({ myAddress, isNewChat, selectedDirect, setSelectedDirect, setNewChat, getTimestampEnterChat, myName}) => {
const [messages, setMessages] = useState([])
const [isSending, setIsSending] = useState(false)
const [directToValue, setDirectToValue] = useState('')
@ -25,14 +36,45 @@ export const ChatDirect = ({ myAddress, isNewChat, selectedDirect, setSelectedDi
const [isLoading, setIsLoading] = useState(false)
const [openSnack, setOpenSnack] = React.useState(false);
const [infoSnack, setInfoSnack] = React.useState(null);
const [publicKeyOfRecipient, setPublicKeyOfRecipient] = React.useState("")
const hasInitializedWebsocket = useRef(false)
const editorRef = useRef(null);
const socketRef = useRef(null);
const timeoutIdRef = useRef(null);
const groupSocketTimeoutRef = useRef(null);
const [replyMessage, setReplyMessage] = useState(null)
const setEditorRef = (editorInstance) => {
editorRef.current = editorInstance;
};
const [, forceUpdate] = useReducer((x) => x + 1, 0);
const triggerRerender = () => {
forceUpdate(); // Trigger re-render by updating the state
};
const publicKeyOfRecipientRef = useRef(null)
const getPublicKeyFunc = async (address)=> {
try {
const publicKey = await getPublicKey(address)
if(publicKeyOfRecipientRef.current !== selectedDirect?.address) return
setPublicKeyOfRecipient(publicKey)
} catch (error) {
}
}
const tempMessages = useMemo(()=> {
if(!selectedDirect?.address) return []
if(queueChats[selectedDirect?.address]){
return queueChats[selectedDirect?.address]
}
return []
}, [selectedDirect?.address, queueChats])
useEffect(()=> {
if(selectedDirect?.address){
publicKeyOfRecipientRef.current = selectedDirect?.address
getPublicKeyFunc(publicKeyOfRecipientRef.current)
}
}, [selectedDirect?.address])
@ -40,12 +82,15 @@ export const ChatDirect = ({ myAddress, isNewChat, selectedDirect, setSelectedDi
const decryptMessages = (encryptedMessages: any[])=> {
try {
return new Promise((res, rej)=> {
chrome.runtime.sendMessage({ action: "decryptDirect", payload: {
chrome?.runtime?.sendMessage({ action: "decryptDirect", payload: {
data: encryptedMessages,
involvingAddress: selectedDirect?.address
}}, (response) => {
if (!response?.error) {
processWithNewMessages(response, selectedDirect?.address)
res(response)
if(hasInitialized.current){
@ -54,7 +99,7 @@ export const ChatDirect = ({ myAddress, isNewChat, selectedDirect, setSelectedDi
...item,
id: item.signature,
text: item.message,
unread: true
unread: item?.sender === myAddress ? false : true
}
} )
setMessages((prev)=> [...prev, ...formatted])
@ -80,78 +125,115 @@ export const ChatDirect = ({ myAddress, isNewChat, selectedDirect, setSelectedDi
}
}
const forceCloseWebSocket = () => {
if (socketRef.current) {
console.log('Force closing the WebSocket');
clearTimeout(timeoutIdRef.current);
clearTimeout(groupSocketTimeoutRef.current);
socketRef.current.close(1000, 'forced');
socketRef.current = null;
}
};
const pingWebSocket = () => {
try {
if (socketRef.current?.readyState === WebSocket.OPEN) {
socketRef.current.send('ping');
timeoutIdRef.current = setTimeout(() => {
if (socketRef.current) {
socketRef.current.close();
clearTimeout(groupSocketTimeoutRef.current);
}
}, 5000); // Close if no pong in 5 seconds
}
} catch (error) {
console.error('Error during ping:', error);
}
};
const initWebsocketMessageGroup = () => {
let timeoutId
let groupSocketTimeout
let socketTimeout: any
let socketLink = `${getBaseApiReactSocket()}/websockets/chat/messages?involving=${selectedDirect?.address}&involving=${myAddress}&encoding=BASE64&limit=100`
const socket = new WebSocket(socketLink)
const pingGroupSocket = () => {
socket.send('ping')
timeoutId = setTimeout(() => {
socket.close()
clearTimeout(groupSocketTimeout)
}, 5000) // Close the WebSocket connection if no pong message is received within 5 seconds.
}
socket.onopen = () => {
setTimeout(pingGroupSocket, 50)
}
socket.onmessage = (e) => {
forceCloseWebSocket(); // Close any existing connection
if (!selectedDirect?.address || !myAddress) return;
const socketLink = `${getBaseApiReactSocket()}/websockets/chat/messages?involving=${selectedDirect?.address}&involving=${myAddress}&encoding=BASE64&limit=100`;
socketRef.current = new WebSocket(socketLink);
socketRef.current.onopen = () => {
console.log('WebSocket connection opened');
setTimeout(pingWebSocket, 50); // Initial ping
};
socketRef.current.onmessage = (e) => {
try {
if (e.data === 'pong') {
clearTimeout(timeoutId)
groupSocketTimeout = setTimeout(pingGroupSocket, 45000)
return
clearTimeout(timeoutIdRef.current);
groupSocketTimeoutRef.current = setTimeout(pingWebSocket, 45000); // Ping every 45 seconds
} else {
decryptMessages(JSON.parse(e.data))
setIsLoading(false)
decryptMessages(JSON.parse(e.data));
setIsLoading(false);
}
} catch (error) {
console.error('Error handling WebSocket message:', error);
}
}
socket.onclose = () => {
console.log('closed')
clearTimeout(socketTimeout)
setTimeout(() => initWebsocketMessageGroup(), 50)
}
socket.onerror = (e) => {
clearTimeout(groupSocketTimeout)
socket.close()
}
};
socketRef.current.onclose = (event) => {
clearTimeout(groupSocketTimeoutRef.current);
clearTimeout(timeoutIdRef.current);
console.warn(`WebSocket closed: ${event.reason || 'unknown reason'}`);
if (event.reason !== 'forced' && event.code !== 1000) {
setTimeout(() => initWebsocketMessageGroup(), 10000); // Retry after 10 seconds
}
};
socketRef.current.onerror = (error) => {
console.error('WebSocket error:', error);
clearTimeout(groupSocketTimeoutRef.current);
clearTimeout(timeoutIdRef.current);
if (socketRef.current) {
socketRef.current.close();
}
};
};
const setDirectChatValueFunc = async (e)=> {
setDirectToValue(e.detail.directToValue)
}
useEffect(() => {
subscribeToEvent("setDirectToValueNewChat", setDirectChatValueFunc);
useEffect(()=> {
if(hasInitializedWebsocket.current) return
setIsLoading(true)
initWebsocketMessageGroup()
hasInitializedWebsocket.current = true
}, [])
return () => {
unsubscribeFromEvent("setDirectToValueNewChat", setDirectChatValueFunc);
};
}, []);
useEffect(() => {
if (hasInitializedWebsocket.current || isNewChat) return;
setIsLoading(true);
initWebsocketMessageGroup();
hasInitializedWebsocket.current = true;
return () => {
forceCloseWebSocket(); // Clean up WebSocket on component unmount
};
}, [selectedDirect?.address, myAddress, isNewChat]);
const sendChatDirect = async ({ chatReference = undefined, messageText}: any)=> {
const sendChatDirect = async ({ chatReference = undefined, messageText, otherData}: any, address, publicKeyOfRecipient, isNewChatVar)=> {
try {
const directTo = isNewChat ? directToValue : selectedDirect.address
const directTo = isNewChatVar ? directToValue : address
if(!directTo) return
return new Promise((res, rej)=> {
chrome.runtime.sendMessage({ action: "sendChatDirect", payload: {
directTo, chatReference, messageText
chrome?.runtime?.sendMessage({ action: "sendChatDirect", payload: {
directTo, chatReference, messageText, otherData, publicKeyOfRecipient, address: directTo
}}, async (response) => {
if (!response?.error) {
if(isNewChat){
if(isNewChatVar){
let getRecipientName = null
try {
@ -167,7 +249,7 @@ const sendChatDirect = async ({ chatReference = undefined, messageText}: any)=>
"senderName": myName
})
setNewChat(null)
chrome.runtime.sendMessage({
chrome?.runtime?.sendMessage({
action: "addTimestampEnterChat",
payload: {
timestamp: Date.now(),
@ -186,87 +268,281 @@ const sendChatDirect = async ({ chatReference = undefined, messageText}: any)=>
})
} catch (error) {
throw new Error(error)
} finally {
}
}
const clearEditorContent = () => {
if (editorRef.current) {
editorRef.current.chain().focus().clearContent().run();
if(isMobile){
setTimeout(() => {
editorRef.current?.chain().blur().run();
setIsFocusedParent(false)
executeEvent("sent-new-message-group", {})
setTimeout(() => {
triggerRerender();
}, 300);
}, 200);
}
}
};
const sendMessage = async ()=> {
try {
if(+balance < 4) throw new Error('You need at least 4 QORT to send a message')
if(isSending) return
if (editorRef.current) {
const htmlContent = editorRef.current.getHTML();
if(!htmlContent?.trim() || htmlContent?.trim() === '<p></p>') return
setIsSending(true)
pauseAllQueues()
const message = JSON.stringify(htmlContent)
const res = await sendChatDirect({ messageText: htmlContent})
if(isNewChat){
await sendChatDirect({ messageText: htmlContent}, null, null, true)
return
}
let repliedTo = replyMessage?.signature
if (replyMessage?.chatReference) {
repliedTo = replyMessage?.chatReference
}
const otherData = {
specialId: uid.rnd(),
repliedTo
}
const sendMessageFunc = async () => {
await sendChatDirect({ chatReference: undefined, messageText: htmlContent, otherData}, selectedDirect?.address, publicKeyOfRecipient, false)
};
// Add the function to the queue
const messageObj = {
message: {
text: htmlContent,
timestamp: Date.now(),
senderName: myName,
sender: myAddress,
...(otherData || {})
},
}
addToQueue(sendMessageFunc, messageObj, 'chat-direct',
selectedDirect?.address );
setTimeout(() => {
executeEvent("sent-new-message-group", {})
}, 150);
clearEditorContent()
setReplyMessage(null)
}
// send chat message
} catch (error) {
const errorMsg = error?.message || error
setInfoSnack({
type: "error",
message: error,
message: errorMsg === 'invalid signature' ? 'You need at least 4 QORT to send a message' : errorMsg,
});
setOpenSnack(true);
console.error(error)
} finally {
setIsSending(false)
resumeAllQueues()
}
}
const onReply = useCallback((message)=> {
setReplyMessage(message)
}, [])
return (
<div style={{
height: '100vh',
height: isMobile ? '100%' : '100vh',
display: 'flex',
flexDirection: 'column',
width: '100%'
width: '100%',
background: !isMobile && 'var(--bg-2)'
}}>
{!isMobile && (
<Box onClick={close} sx={{
display: 'flex',
alignItems: 'center',
gap: '5px',
cursor: 'pointer',
padding: '4px 6px',
width: 'fit-content',
borderRadius: '3px',
background: 'rgb(35, 36, 40)',
margin: '10px 0px',
alignSelf: 'center'
}}>
<ArrowBackIcon sx={{
color: 'white',
fontSize: isMobile ? '20px' : '20px'
}}/>
<Typography sx={{
color: 'white',
fontSize: isMobile ? '14px' : '14px'
}}>Close Direct Chat</Typography>
</Box>
)}
{isMobile && (
<Box
sx={{
display: "flex",
alignItems: "center",
width: "100%",
marginTop: "14px",
justifyContent: "center",
height: "15px",
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
width: "320px",
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
width: "50px",
}}
>
<ButtonBase
onClick={() => {
close()
}}
>
<ReturnIcon />
</ButtonBase>
</Box>
<Typography
sx={{
fontSize: "14px",
fontWeight: 600,
}}
>
{isNewChat ? '' : selectedDirect?.name || (selectedDirect?.address?.slice(0,10) + '...')}
</Typography>
<Box
sx={{
display: "flex",
alignItems: "center",
width: "50px",
justifyContent: "flex-end",
}}
>
<ButtonBase
onClick={() => {
setSelectedDirect(null)
setMobileViewModeKeepOpen('')
setNewChat(false)
}}
>
<ExitIcon />
</ButtonBase>
</Box>
</Box>
</Box>
)}
{isNewChat && (
<>
<Spacer height="30px" />
<Input sx={{
fontSize: '18px'
fontSize: '18px',
padding: '5px'
}} placeholder='Name or address' value={directToValue} onChange={(e)=> setDirectToValue(e.target.value)} />
</>
)}
<ChatList initialMessages={messages} myAddress={myAddress} tempMessages={[]}/>
<ChatList onReply={onReply} chatId={selectedDirect?.address} initialMessages={messages} myAddress={myAddress} tempMessages={tempMessages}/>
<div style={{
// position: 'fixed',
// bottom: '0px',
backgroundColor: "#232428",
minHeight: '150px',
maxHeight: '400px',
minHeight: isMobile ? '0px' : '150px',
maxHeight: isMobile ? 'auto' : '400px',
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
width: '100%',
boxSizing: 'border-box',
padding: '20px'
padding: isMobile ? '10px' : '20px',
position: isFocusedParent ? 'fixed' : 'relative',
bottom: isFocusedParent ? '0px' : 'unset',
top: isFocusedParent ? '0px' : 'unset',
zIndex: isFocusedParent ? 5 : 'unset'
}}>
<div style={{
display: 'flex',
flexDirection: 'column',
// height: '100%',
overflow: 'auto'
flexGrow: isMobile && 1,
overflow: !isMobile && "auto",
}}>
{replyMessage && (
<Box sx={{
display: 'flex',
gap: '5px',
alignItems: 'flex-start',
width: '100%'
}}>
<ReplyPreview message={replyMessage} />
<ButtonBase
onClick={() => {
setReplyMessage(null)
}}
>
<ExitIcon />
</ButtonBase>
</Box>
)}
<Tiptap setEditorRef={setEditorRef} onEnter={sendMessage} isChat />
<Tiptap isFocusedParent={isFocusedParent} setEditorRef={setEditorRef} onEnter={sendMessage} isChat disableEnter={isMobile ? true : false} setIsFocusedParent={setIsFocusedParent}/>
</div>
<Box sx={{
display: 'flex',
width: '100&',
gap: '10px',
justifyContent: 'center',
flexShrink: 0,
position: 'relative',
}}>
{isFocusedParent && (
<CustomButton
onClick={()=> {
if(isSending) return
setIsFocusedParent(false)
clearEditorContent()
// Unfocus the editor
}}
style={{
marginTop: 'auto',
alignSelf: 'center',
cursor: isSending ? 'default' : 'pointer',
background: 'red',
flexShrink: 0,
padding: isMobile && '5px'
}}
>
{` Close`}
</CustomButton>
)}
<CustomButton
onClick={()=> {
if(isSending) return
@ -277,7 +553,8 @@ const clearEditorContent = () => {
alignSelf: 'center',
cursor: isSending ? 'default' : 'pointer',
background: isSending && 'rgba(0, 0, 0, 0.8)',
flexShrink: 0
flexShrink: 0,
padding: isMobile && '5px'
}}
>
{isSending && (
@ -295,6 +572,8 @@ const clearEditorContent = () => {
)}
{` Send`}
</CustomButton>
</Box>
</div>
<LoadingSnackbar open={isLoading} info={{
message: "Loading chat... please wait."

View File

@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'
import { CreateCommonSecret } from './CreateCommonSecret'
import { reusableGet } from '../../qdn/publish/pubish'
import { uint8ArrayToObject } from '../../backgroundFunctions/encryption'
@ -10,17 +10,24 @@ import Tiptap from './TipTap'
import { CustomButton } from '../../App-styles'
import CircularProgress from '@mui/material/CircularProgress';
import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar'
import { getBaseApiReactSocket, pauseAllQueues, resumeAllQueues } from '../../App'
import { getBaseApiReactSocket, isMobile, pauseAllQueues, resumeAllQueues } from '../../App'
import { CustomizedSnackbars } from '../Snackbar/Snackbar'
import { PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY } from '../../constants/codes'
import { useMessageQueue } from '../../MessageQueueContext'
import { executeEvent } from '../../utils/events'
import { Box, ButtonBase } from '@mui/material'
import ShortUniqueId from "short-unique-id";
import { ReplyPreview } from './MessageItem'
import { ExitIcon } from '../../assets/Icons/ExitIcon'
const uid = new ShortUniqueId({ length: 5 });
export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey, myAddress, handleNewEncryptionNotification, hide, handleSecretKeyCreationInProgress, triedToFetchSecretKey, myName}) => {
export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey, myAddress, handleNewEncryptionNotification, hide, handleSecretKeyCreationInProgress, triedToFetchSecretKey, myName, balance}) => {
const [messages, setMessages] = useState([])
const [isSending, setIsSending] = useState(false)
const [isLoading, setIsLoading] = useState(false)
@ -28,13 +35,20 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey,
const [openSnack, setOpenSnack] = React.useState(false);
const [infoSnack, setInfoSnack] = React.useState(null);
const hasInitialized = useRef(false)
const [isFocusedParent, setIsFocusedParent] = useState(false);
const [replyMessage, setReplyMessage] = useState(null)
const hasInitializedWebsocket = useRef(false)
const socketRef = useRef(null); // WebSocket reference
const timeoutIdRef = useRef(null); // Timeout ID reference
const groupSocketTimeoutRef = useRef(null); // Group Socket Timeout reference
const editorRef = useRef(null);
const { queueChats, addToQueue, } = useMessageQueue();
const { queueChats, addToQueue, processWithNewMessages } = useMessageQueue();
const [, forceUpdate] = useReducer((x) => x + 1, 0);
const triggerRerender = () => {
forceUpdate(); // Trigger re-render by updating the state
};
const setEditorRef = (editorInstance) => {
editorRef.current = editorInstance;
};
@ -85,12 +99,17 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey,
return
}
return new Promise((res, rej)=> {
chrome.runtime.sendMessage({ action: "decryptSingle", payload: {
chrome?.runtime?.sendMessage({ action: "decryptSingle", payload: {
data: encryptedMessages,
secretKeyObject: secretKey
}}, (response) => {
if (!response?.error) {
processWithNewMessages(response?.map((item)=> {
return {
...item,
...(item?.decryptedData || {})
}
}), selectedGroup)
res(response)
if(hasInitialized.current){
@ -98,7 +117,8 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey,
return {
...item,
id: item.signature,
text: item.text,
text: item?.decryptedData?.message || "",
repliedTo: item?.decryptedData?.repliedTo,
unread: item?.sender === myAddress ? false : true
}
} )
@ -108,7 +128,8 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey,
return {
...item,
id: item.signature,
text: item.text,
text: item?.decryptedData?.message || "",
repliedTo: item?.decryptedData?.repliedTo,
unread: false
}
} )
@ -233,7 +254,7 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey,
const encryptChatMessage = async (data: string, secretKeyObject: any)=> {
try {
return new Promise((res, rej)=> {
chrome.runtime.sendMessage({ action: "encryptSingle", payload: {
chrome?.runtime?.sendMessage({ action: "encryptSingle", payload: {
data,
secretKeyObject
}}, (response) => {
@ -252,7 +273,7 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey,
const sendChatGroup = async ({groupId, typeMessage = undefined, chatReference = undefined, messageText}: any)=> {
try {
return new Promise((res, rej)=> {
chrome.runtime.sendMessage({ action: "sendChatGroup", payload: {
chrome?.runtime?.sendMessage({ action: "sendChatGroup", payload: {
groupId, typeMessage, chatReference, messageText
}}, (response) => {
@ -270,6 +291,16 @@ const sendChatGroup = async ({groupId, typeMessage = undefined, chatReference =
const clearEditorContent = () => {
if (editorRef.current) {
editorRef.current.chain().focus().clearContent().run();
if(isMobile){
setTimeout(() => {
editorRef.current?.chain().blur().run();
setIsFocusedParent(false)
executeEvent("sent-new-message-group", {})
setTimeout(() => {
triggerRerender();
}, 300);
}, 200);
}
}
};
@ -277,6 +308,7 @@ const clearEditorContent = () => {
const sendMessage = async ()=> {
try {
if(isSending) return
if(+balance < 4) throw new Error('You need at least 4 QORT to send a message')
pauseAllQueues()
if (editorRef.current) {
const htmlContent = editorRef.current.getHTML();
@ -285,11 +317,25 @@ const clearEditorContent = () => {
setIsSending(true)
const message = htmlContent
const secretKeyObject = await getSecretKey(false, true)
const message64: any = await objectToBase64(message)
let repliedTo = replyMessage?.signature
if (replyMessage?.chatReference) {
repliedTo = replyMessage?.chatReference
}
const otherData = {
specialId: uid.rnd(),
repliedTo
}
const objectMessage = {
message,
...(otherData || {})
}
const message64: any = await objectToBase64(objectMessage)
const encryptSingle = await encryptChatMessage(message64, secretKeyObject)
// const res = await sendChatGroup({groupId: selectedGroup,messageText: encryptSingle})
const sendMessageFunc = async () => {
await sendChatGroup({groupId: selectedGroup,messageText: encryptSingle})
};
@ -300,7 +346,8 @@ const clearEditorContent = () => {
text: message,
timestamp: Date.now(),
senderName: myName,
sender: myAddress
sender: myAddress,
...(otherData || {})
},
}
@ -310,12 +357,14 @@ const clearEditorContent = () => {
executeEvent("sent-new-message-group", {})
}, 150);
clearEditorContent()
setReplyMessage(null)
}
// send chat message
} catch (error) {
const errorMsg = error?.message || error
setInfoSnack({
type: "error",
message: error,
message: errorMsg,
});
setOpenSnack(true);
console.error(error)
@ -333,10 +382,13 @@ const clearEditorContent = () => {
}
}, [hide]);
const onReply = useCallback((message)=> {
setReplyMessage(message)
}, [])
return (
<div style={{
height: '100vh',
height: isMobile ? '100%' : '100%',
display: 'flex',
flexDirection: 'column',
width: '100%',
@ -345,32 +397,84 @@ const clearEditorContent = () => {
left: hide && '-100000px',
}}>
<ChatList initialMessages={messages} myAddress={myAddress} tempMessages={tempMessages}/>
<ChatList onReply={onReply} chatId={selectedGroup} initialMessages={messages} myAddress={myAddress} tempMessages={tempMessages}/>
<div style={{
// position: 'fixed',
// bottom: '0px',
backgroundColor: "#232428",
minHeight: '150px',
maxHeight: '400px',
minHeight: isMobile ? '0px' : '150px',
maxHeight: isMobile ? 'auto' : '400px',
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
width: '100%',
boxSizing: 'border-box',
padding: '20px'
padding: isMobile ? '10px' : '20px',
position: isFocusedParent ? 'fixed' : 'relative',
bottom: isFocusedParent ? '0px' : 'unset',
top: isFocusedParent ? '0px' : 'unset',
zIndex: isFocusedParent ? 5 : 'unset'
}}>
<div style={{
display: 'flex',
flexDirection: 'column',
// height: '100%',
overflow: 'auto'
flexGrow: isMobile && 1,
overflow: !isMobile && "auto",
}}>
{replyMessage && (
<Box sx={{
display: 'flex',
gap: '5px',
alignItems: 'flex-start',
width: '100%'
}}>
<ReplyPreview message={replyMessage} />
<ButtonBase
onClick={() => {
setReplyMessage(null)
}}
>
<ExitIcon />
</ButtonBase>
</Box>
)}
<Tiptap setEditorRef={setEditorRef} onEnter={sendMessage} isChat />
<Tiptap setEditorRef={setEditorRef} onEnter={sendMessage} isChat disableEnter={isMobile ? true : false} isFocusedParent={isFocusedParent} setIsFocusedParent={setIsFocusedParent} />
</div>
<Box sx={{
display: 'flex',
width: '100&',
gap: '10px',
justifyContent: 'center',
flexShrink: 0,
position: 'relative',
}}>
{isFocusedParent && (
<CustomButton
onClick={()=> {
if(isSending) return
setIsFocusedParent(false)
clearEditorContent()
// Unfocus the editor
}}
style={{
marginTop: 'auto',
alignSelf: 'center',
cursor: isSending ? 'default' : 'pointer',
background: 'red',
flexShrink: 0,
padding: isMobile && '5px'
}}
>
{` Close`}
</CustomButton>
)}
<CustomButton
onClick={()=> {
if(isSending) return
@ -381,7 +485,9 @@ const clearEditorContent = () => {
alignSelf: 'center',
cursor: isSending ? 'default' : 'pointer',
background: isSending && 'rgba(0, 0, 0, 0.8)',
flexShrink: 0
flexShrink: 0,
padding: isMobile && '5px',
}}
>
{isSending && (
@ -399,6 +505,8 @@ const clearEditorContent = () => {
)}
{` Send`}
</CustomButton>
</Box>
{/* <button onClick={sendMessage}>send</button> */}
</div>
{/* <ChatContainerComp messages={formatMessages} /> */}

View File

@ -3,19 +3,21 @@ import { List, AutoSizer, CellMeasurerCache, CellMeasurer } from 'react-virtuali
import { MessageItem } from './MessageItem';
import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events';
const cache = new CellMeasurerCache({
fixedWidth: true,
defaultHeight: 50,
});
// const cache = new CellMeasurerCache({
// fixedWidth: true,
// defaultHeight: 50,
// });
export const ChatList = ({ initialMessages, myAddress, tempMessages }) => {
export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId, onReply }) => {
const hasLoadedInitialRef = useRef(false);
const listRef = useRef();
const [messages, setMessages] = useState(initialMessages);
const [showScrollButton, setShowScrollButton] = useState(false);
const cache = useMemo(() => new CellMeasurerCache({
fixedWidth: true,
defaultHeight: 50,
}), [chatId]); // Recreate cache when chatId changes
useEffect(() => {
cache.clearAll();
}, []);
@ -65,6 +67,10 @@ export const ChatList = ({ initialMessages, myAddress, tempMessages }) => {
}
};
const scrollToItem = useCallback((index) => {
listRef.current.scrollToRow(index); // This scrolls to the specific index
}, []);
const recomputeListHeights = () => {
if (listRef.current) {
listRef.current.recomputeRowHeights();
@ -90,7 +96,18 @@ export const ChatList = ({ initialMessages, myAddress, tempMessages }) => {
let message = messages[index];
const isLargeMessage = message.text?.length > 200; // Adjust based on your message size threshold
// const reply = message?.repliedTo ? messages.find((msg)=> msg?.signature === message?.repliedTo) : undefined
let replyIndex = messages.findIndex((msg)=> msg?.signature === message?.repliedTo)
let reply
if(message?.repliedTo && replyIndex !== -1){
reply = messages[replyIndex]
}
if(message?.message && message?.groupDirectId){
replyIndex = messages.findIndex((msg)=> msg?.signature === message?.message?.repliedTo)
reply
if(message?.message?.repliedTo && replyIndex !== -1){
reply = messages[replyIndex]
}
message = {
...(message?.message || {}),
isTemp: true,
@ -126,6 +143,11 @@ export const ChatList = ({ initialMessages, myAddress, tempMessages }) => {
message={message}
onSeen={handleMessageSeen}
isTemp={!!message?.isTemp}
myAddress={myAddress}
onReply={onReply}
reply={reply}
scrollToItem={scrollToItem}
replyIndex={replyIndex}
/>
</div>
</div>
@ -174,7 +196,7 @@ let uniqueInitialMessages = Array.from(uniqueInitialMessagesMap.values()).sort((
// }, [messages, myAddress]);
return (
<div style={{ position: 'relative', flexGrow: 1, width: '100%', display: 'flex', flexDirection: 'column', flexShrink: 1 }}>
<div style={{ position: 'relative', marginTop: '14px', flexGrow: 1, width: '100%', display: 'flex', flexDirection: 'column', flexShrink: 1 }}>
<AutoSizer>
{({ height, width }) => (
<List

View File

@ -2,13 +2,13 @@ import { Box, Button, Typography } from '@mui/material'
import React, { useContext } from 'react'
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
import { LoadingButton } from '@mui/lab';
import { MyContext, getBaseApiReact, pauseAllQueues } from '../../App';
import { MyContext, getArbitraryEndpointReact, getBaseApiReact, pauseAllQueues } from '../../App';
import { getFee } from '../../background';
import { decryptResource, getGroupAdimns, validateSecretKey } from '../Group/Group';
import { base64ToUint8Array } from '../../qdn/encryption/group-encryption';
import { uint8ArrayToObject } from '../../backgroundFunctions/encryption';
export const CreateCommonSecret = ({groupId, secretKey, isOwner, myAddress, secretKeyDetails, userInfo, noSecretKey}) => {
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'
}}>
<LoadingButton loading={isLoading} loadingPosition="start" color="warning" variant='contained' onClick={createCommonSecret}>Re-encyrpt key</LoadingButton>
{noSecretKey ? (
@ -158,6 +158,15 @@ export const CreateCommonSecret = ({groupId, secretKey, isOwner, myAddress, sec
<Typography>The group member list has changed. Please re-encrypt the secret key.</Typography>
</Box>
)}
<Box sx={{
display: 'flex',
width: '100%',
justifyContent: 'flex-end'
}}>
<Button onClick={()=> {
setHideCommonKeyPopup(true)
}} size='small'>Hide</Button>
</Box>
<CustomizedSnackbars open={openSnack} setOpen={setOpenSnack} info={infoSnack} setInfo={setInfoSnack} />
</Box>

View File

@ -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<any>(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<any>(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 (
<div
style={{
height: "100vh",
display: "flex",
flexDirection: "column",
width: "100%",
visibility: hide && 'hidden',
position: hide && 'fixed',
left: hide && '-1000px'
}}
>
<AnnouncementDiscussion myName={myName} show={show} secretKey={secretKey} selectedAnnouncement={selectedAnnouncement} setSelectedAnnouncement={setSelectedAnnouncement} encryptChatMessage={encryptChatMessage} getSecretKey={getSecretKey} />
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",
}}
>
<AnnouncementDiscussion
myName={myName}
show={show}
secretKey={secretKey}
selectedAnnouncement={selectedAnnouncement}
setSelectedAnnouncement={setSelectedAnnouncement}
encryptChatMessage={encryptChatMessage}
getSecretKey={getSecretKey}
/>
</div>
)
);
}
return (
<div
style={{
height: "100vh",
// 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'
visibility: hide && "hidden",
position: hide && "fixed",
left: hide && "-1000px",
}}
>
<div style={{
position: "relative",
width: "100%",
display: "flex",
flexDirection: "column",
flexShrink: 0,
}}>
<Box
sx={{
<div
style={{
position: "relative",
width: "100%",
display: "flex",
justifyContent: "center",
padding: "25px",
fontSize: "20px",
gap: '20px',
alignItems: 'center'
flexDirection: "column",
flexShrink: 0,
}}
>
<CampaignIcon sx={{
fontSize: '30px'
}} />
Group Announcements
</Box>
<Spacer height="25px" />
{!isMobile && (
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "center",
padding: isMobile ? "8px" : "25px",
fontSize: isMobile ? "16px" : "20px",
gap: "20px",
alignItems: "center",
}}
>
<CampaignIcon
sx={{
fontSize: isMobile ? "16px" : "30px",
}}
/>
Group Announcements
</Box>
)}
<Spacer height={isMobile ? "0px" : "25px"} />
</div>
{!isLoading && combinedListTempAndReal?.length === 0 && (
<Box sx={{
width: '100%',
display: 'flex',
justifyContent: 'center'
}}>
<Typography sx={{
fontSize: '16px'
}}>No announcements</Typography>
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "center",
}}
>
<Typography
sx={{
fontSize: "16px",
}}
>
No announcements
</Typography>
</Box>
)}
<AnnouncementList
@ -530,74 +577,126 @@ export const GroupAnnouncements = ({
initialMessages={combinedListTempAndReal}
setSelectedAnnouncement={setSelectedAnnouncement}
disableComment={false}
showLoadMore={announcements.length > 0 && announcements.length % 20 === 0}
showLoadMore={
announcements.length > 0 && announcements.length % 20 === 0
}
loadMore={loadMore}
myName={myName}
/>
{isAdmin && (
<div
style={{
// position: 'fixed',
// bottom: '0px',
backgroundColor: "#232428",
minHeight: "150px",
maxHeight: "400px",
display: "flex",
flexDirection: "column",
overflow: "hidden",
width: "100%",
boxSizing: "border-box",
padding: "20px",
flexShrink: 0
}}
>
<div
style={{
display: "flex",
flexDirection: "column",
// height: '100%',
overflow: "auto",
}}
>
<Tiptap
setEditorRef={setEditorRef}
onEnter={publishAnnouncement}
disableEnter
/>
</div>
<CustomButton
onClick={() => {
if (isSending) return;
publishAnnouncement();
}}
style={{
marginTop: "auto",
alignSelf: "center",
cursor: isSending ? "default" : "pointer",
background: isSending && "rgba(0, 0, 0, 0.8)",
flexShrink: 0,
}}
>
{isSending && (
<CircularProgress
size={18}
sx={{
position: "absolute",
top: "50%",
left: "50%",
marginTop: "-12px",
marginLeft: "-12px",
color: "white",
}}
/>
)}
{` Publish Announcement`}
</CustomButton>
</div>
)}
<CustomizedSnackbars open={openSnack} setOpen={setOpenSnack} info={infoSnack} setInfo={setInfoSnack} />
{isAdmin && (
<div
style={{
// position: 'fixed',
// bottom: '0px',
backgroundColor: "#232428",
minHeight: isMobile ? "0px" : "150px",
maxHeight: isMobile ? "auto" : "400px",
display: "flex",
flexDirection: "column",
overflow: "hidden",
width: "100%",
boxSizing: "border-box",
padding: isMobile ? "10px" : "20px",
position: isFocusedParent ? "fixed" : "relative",
bottom: isFocusedParent ? "0px" : "unset",
top: isFocusedParent ? "0px" : "unset",
zIndex: isFocusedParent ? 5 : "unset",
flexShrink: 0,
}}
>
<div
style={{
display: "flex",
flexDirection: "column",
flexGrow: isMobile && 1,
overflow: "auto",
// height: '100%',
}}
>
<Tiptap
setEditorRef={setEditorRef}
onEnter={publishAnnouncement}
disableEnter
maxHeightOffset="40px"
isFocusedParent={isFocusedParent}
setIsFocusedParent={setIsFocusedParent}
/>
</div>
<Box
sx={{
display: "flex",
width: "100&",
gap: "10px",
justifyContent: "center",
flexShrink: 0,
position: "relative",
}}
>
{isFocusedParent && (
<CustomButton
onClick={() => {
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`}
</CustomButton>
)}
<CustomButton
onClick={() => {
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 && (
<CircularProgress
size={18}
sx={{
position: "absolute",
top: "50%",
left: "50%",
marginTop: "-12px",
marginLeft: "-12px",
color: "white",
}}
/>
)}
{` Publish Announcement`}
</CustomButton>
</Box>
</div>
)}
<CustomizedSnackbars
open={openSnack}
setOpen={setOpenSnack}
info={infoSnack}
setInfo={setInfoSnack}
/>
<LoadingSnackbar
open={isLoading}

View File

@ -1,11 +1,14 @@
import React, {
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { GroupMail } from "../Group/Forum/GroupMail";
import { MyContext, isMobile } from "../../App";
import { getRootHeight } from "../../utils/mobile/mobileUtils";
@ -23,6 +26,7 @@ export const GroupForum = ({
defaultThread,
setDefaultThread
}) => {
const { rootHeight } = useContext(MyContext);
const [isMoved, setIsMoved] = useState(false);
useEffect(() => {
if (hide) {
@ -35,7 +39,8 @@ export const GroupForum = ({
return (
<div
style={{
height: "100vh",
// reference to change height
height: isMobile ? `calc(${rootHeight} - 127px` : "calc(100vh - 70px)",
display: "flex",
flexDirection: "column",
width: "100%",
@ -45,7 +50,7 @@ export const GroupForum = ({
left: hide && '-1000px'
}}
>
<GroupMail getSecretKey={getSecretKey} selectedGroup={selectedGroup} userInfo={userInfo} secretKey={secretKey} defaultThread={defaultThread} setDefaultThread={setDefaultThread} />
<GroupMail hide={hide} getSecretKey={getSecretKey} selectedGroup={selectedGroup} userInfo={userInfo} secretKey={secretKey} defaultThread={defaultThread} setDefaultThread={setDefaultThread} />
</div>
);

View File

@ -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 (
<div
className="tiptap"
className={`tiptap ${isReply ? 'isReply' : ''}`}
dangerouslySetInnerHTML={{ __html: sanitizedContent }}
onClick={(e) => {
// Delegate click handling to the parent div

View File

@ -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}
>
<Avatar
sx={{
backgroundColor: '#27282c',
color: 'white'
}}
alt={message?.senderName}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${message?.senderName}/qortal_avatar?async=true`}
>
{message?.senderName?.charAt(0)}
</Avatar>
{isShowingAsReply ? (
<ReplyIcon
sx={{
fontSize: "30px",
}}
/>
) : (
<WrapperUserAction
disabled={myAddress === message?.sender}
address={message?.sender}
name={message?.senderName}
>
<Avatar
sx={{
backgroundColor: "#27282c",
color: "white",
}}
alt={message?.senderName}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
message?.senderName
}/qortal_avatar?async=true`}
>
{message?.senderName?.charAt(0)}
</Avatar>
</WrapperUserAction>
)}
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: "7px",
width: '100%'
width: "100%",
height: isShowingAsReply && "40px",
}}
>
<Typography
<Box
sx={{
fontWight: 600,
fontFamily: "Inter",
color: "cadetBlue",
display: "flex",
width: "100%",
justifyContent: "space-between",
}}
>
{message?.senderName || message?.sender}
</Typography>
<WrapperUserAction
disabled={myAddress === message?.sender}
address={message?.sender}
name={message?.senderName}
>
<Typography
sx={{
fontWight: 600,
fontFamily: "Inter",
color: "cadetBlue",
}}
>
{message?.senderName || message?.sender}
</Typography>
</WrapperUserAction>
{!isShowingAsReply && (
<ButtonBase
onClick={() => {
onReply(message);
}}
>
<ReplyIcon />
</ButtonBase>
)}
</Box>
{reply && (
<Box
sx={{
marginTop: '20px',
width: "100%",
borderRadius: "5px",
backgroundColor: "var(--bg-primary)",
overflow: 'hidden',
display: 'flex',
gap: '20px',
maxHeight: '90px',
cursor: 'pointer'
}}
onClick={()=> {
scrollToItem(replyIndex)
}}
>
<Box sx={{
height: '100%',
width: '5px',
background: 'white'
}} />
<Box sx={{
padding: '5px'
}}>
<Typography sx={{
fontSize: '12px',
fontWeight: 600
}}>Replied to {reply?.senderName || reply?.senderAddress}</Typography>
{reply?.messageText && (
<MessageDisplay
htmlContent={generateHTML(reply?.messageText, [
StarterKit,
Underline,
Highlight,
])}
/>
)}
{reply?.text?.type === "notification" ? (
<MessageDisplay htmlContent={reply.text?.data?.message} />
) : (
<MessageDisplay isReply htmlContent={reply.text} />
)}
</Box>
</Box>
)}
{message?.messageText && (
<MessageDisplay
htmlContent={generateHTML(message?.messageText, [
StarterKit,
Underline,
Highlight,
])}
/>
)}
{message?.text?.type === "notification" ? (
<MessageDisplay htmlContent={message.text?.data?.message} />
) : (
<MessageDisplay htmlContent={message.text} />
)}
<Box sx={{
display: 'flex',
justifyContent: 'flex-end',
width: '100%',
}}>
<Box
sx={{
display: "flex",
justifyContent: "flex-end",
width: "100%",
}}
>
{isTemp ? (
<Typography sx={{
fontSize: '14px',
color: 'gray',
fontFamily: 'Inter'
}}>Sending...</Typography>
): (
<Typography sx={{
fontSize: '14px',
color: 'gray',
fontFamily: 'Inter'
}}>{formatTimestamp(message.timestamp)}</Typography>
) }
<Typography
sx={{
fontSize: "14px",
color: "gray",
fontFamily: "Inter",
}}
>
Sending...
</Typography>
) : (
<Typography
sx={{
fontSize: "14px",
color: "gray",
fontFamily: "Inter",
}}
>
{formatTimestamp(message.timestamp)}
</Typography>
)}
</Box>
</Box>
@ -102,3 +226,51 @@ export const MessageItem = ({ message, onSeen, isLast, isTemp }) => {
</div>
);
};
export const ReplyPreview = ({message})=> {
return (
<Box
sx={{
marginTop: '20px',
width: "100%",
borderRadius: "5px",
backgroundColor: "var(--bg-primary)",
overflow: 'hidden',
display: 'flex',
gap: '20px',
maxHeight: '90px',
cursor: 'pointer'
}}
>
<Box sx={{
height: '100%',
width: '5px',
background: 'white'
}} />
<Box sx={{
padding: '5px'
}}>
<Typography sx={{
fontSize: '12px',
fontWeight: 600
}}>Replied to {message?.senderName || message?.senderAddress}</Typography>
{message?.messageText && (
<MessageDisplay
htmlContent={generateHTML(message?.messageText, [
StarterKit,
Underline,
Highlight,
])}
/>
)}
{message?.text?.type === "notification" ? (
<MessageDisplay htmlContent={message.text?.data?.message} />
) : (
<MessageDisplay isReply htmlContent={message.text} />
)}
</Box>
</Box>
)
}

View File

@ -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'
}}
>
<FormatBoldIcon />
<FormatBoldIcon />
</IconButton>
<IconButton
onClick={() => 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'
}}
>
<FormatItalicIcon />
@ -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'
}}
>
<StrikethroughSIcon />
@ -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'
}}
>
<CodeIcon />
</IconButton>
<IconButton onClick={() => editor.chain().focus().unsetAllMarks().run()}>
<IconButton sx={{
padding: isMobile ? '5px' : 'revert'
}} onClick={() => editor.chain().focus().unsetAllMarks().run()}>
<FormatClearIcon />
</IconButton>
<IconButton
onClick={() => 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'
}}
>
<FormatListBulletedIcon />
@ -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'
}}
>
<FormatListNumberedIcon />
@ -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'
}}
>
<DeveloperModeIcon />
@ -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'
}}
>
<FormatQuoteIcon />
@ -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'
}}
>
<FormatHeadingIcon fontSize="small" />
@ -202,7 +214,8 @@ const MenuBar = ({ setEditorRef, isChat }) => {
.run()
}
sx={{
color: 'gray'
color: 'gray',
padding: isMobile ? '5px' : 'revert'
}}
>
<UndoIcon />
@ -227,7 +240,8 @@ const MenuBar = ({ setEditorRef, isChat }) => {
<IconButton
onClick={triggerImageUpload}
sx={{
color: 'gray'
color: 'gray',
padding: isMobile ? '5px' : 'revert'
}}
>
<ImageIcon />
@ -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() === "<p></p>"){
// setIsFocused(false);
// setIsFocusedParent(false)
};
};
// useEffect(()=> {
// setIsFocused(isFocusedParent)
// },[isFocusedParent])
return (
<EditorProvider
slotBefore={<MenuBar setEditorRef={setEditorRef} isChat={isChat} />}
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) && <MenuBar setEditorRef={setEditorRefFunc} isChat={isChat} />}
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;
},
}}
/>
)
};

View File

@ -118,4 +118,8 @@
.tiptap img {
display: block;
max-width: 100%;
}
}
.isReply p {
font-size: 12px !important;
}

View File

@ -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 (
<div
onContextMenu={handleContextMenu} // For desktop right-click
onTouchStart={handleTouchStart} // For mobile long-press start
onTouchEnd={handleTouchEnd} // For mobile long-press end
style={{ width: '100%', height: '100%' }}
>
{children}
<CustomStyledMenu
disableAutoFocusItem
open={!!menuPosition}
onClose={handleClose}
anchorReference="anchorPosition"
anchorPosition={
menuPosition
? { top: menuPosition.mouseY, left: menuPosition.mouseX }
: undefined
}
onClick={(e)=> {
e.stopPropagation();
}}
>
<MenuItem onClick={(e) => {
handleClose(e)
executeEvent("markAsRead", {
groupId
});
}}>
<ListItemIcon sx={{ minWidth: '32px' }}>
<MailOutlineIcon fontSize="small" />
</ListItemIcon>
<Typography variant="inherit" sx={{ fontSize: '14px' }}>
Mark As Read
</Typography>
</MenuItem>
<MenuItem onClick={(e) => {
handleClose(e)
handleSetGroupMute()
}}>
<ListItemIcon sx={{ minWidth: '32px' }}>
<NotificationsOffIcon fontSize="small" sx={{
color: isMuted && 'red'
}} />
</ListItemIcon>
<Typography variant="inherit" sx={{ fontSize: '14px', color: isMuted && 'red' }}>
{isMuted ? 'Unmute ' : 'Mute '}Push Notifications
</Typography>
</MenuItem>
</CustomStyledMenu>
</div>
);
};

View File

@ -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 (
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
gap: "5px",
flexDirection: "column",
height: '89px',
width: '89px',
borderRadius: '50%',
backgroundColor: selected ? 'rgba(28, 29, 32, 1)' : 'transparent'
}}
>
{children}
<Typography
sx={{
fontFamily: "Inter",
fontSize: "12px",
fontWeight: 500,
color: color,
}}
>
{label}
</Typography>
</Box>
);
};
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 (
<Box
sx={{
width: "100%",
position: "absolute",
bottom: 0,
display: "flex",
alignItems: "center",
height: "100px", // Footer height
zIndex: 1,
justifyContent: 'center'
}}
>
<Box sx={{
display: 'flex',
gap: '20px'
}}>
<ButtonBase onClick={()=> {
goToHome()
}}>
<IconWrapper color="rgba(250, 250, 250, 0.5)" label="Home" selected={isHome}>
<HomeIcon height={30} color={isHome ? "white" : "rgba(250, 250, 250, 0.5)"} />
</IconWrapper>
</ButtonBase>
<ButtonBase onClick={()=> {
setDesktopSideView('groups')
}}>
<IconWrapper color="rgba(250, 250, 250, 0.5)" label="Hubs" selected={isGroups}>
<HubsIcon height={30} color={hasUnreadGroups ? "var(--unread)" : isGroups ? 'white' : "rgba(250, 250, 250, 0.5)"} />
</IconWrapper>
</ButtonBase>
<ButtonBase onClick={()=> {
setDesktopSideView('directs')
}}>
<IconWrapper color="rgba(250, 250, 250, 0.5)" label="Messaging" selected={isDirects}>
<MessagingIcon height={30} color={hasUnreadDirects ? "var(--unread)" : isDirects ? 'white' : "rgba(250, 250, 250, 0.5)"} />
</IconWrapper>
</ButtonBase>
</Box>
</Box>
);
};

View File

@ -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 (
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
gap: "5px",
flexDirection: "column",
height: "65px",
width: "65px",
borderRadius: "50%",
backgroundColor: selected ? selectColor || "rgba(28, 29, 32, 1)" : "transparent",
}}
>
{children}
<Typography
sx={{
fontFamily: "Inter",
fontSize: "10px",
fontWeight: 500,
color: color,
}}
>
{label}
</Typography>
</Box>
);
};
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 (
<Box
sx={{
width: "100%",
display: "flex",
alignItems: "center",
height: "70px", // Footer height
zIndex: 1,
justifyContent: "space-between",
padding: "10px",
}}
>
<Box>
<Typography
sx={{
fontSize: "16px",
fontWeight: 600,
}}
>
{selectedGroup?.groupName}
</Typography>
</Box>
<Box
sx={{
display: "flex",
gap: "20px",
alignItems: "center",
}}
>
<ButtonBase
onClick={() => {
goToHome();
}}
>
<IconWrapper
color="rgba(250, 250, 250, 0.5)"
label="Home"
selected={isHome}
>
<HomeIcon
height={25}
color={isHome ? "white" : "rgba(250, 250, 250, 0.5)"}
/>
</IconWrapper>
</ButtonBase>
<ButtonBase
onClick={() => {
setDesktopSideView("groups");
}}
>
<IconWrapper
color="rgba(250, 250, 250, 0.5)"
label="Hubs"
selected={isGroups}
>
<HubsIcon
height={25}
color={
hasUnreadGroups
? "var(--unread)"
: isGroups
? "white"
: "rgba(250, 250, 250, 0.5)"
}
/>
</IconWrapper>
</ButtonBase>
<ButtonBase
onClick={() => {
setDesktopSideView("directs");
}}
>
<IconWrapper
color="rgba(250, 250, 250, 0.5)"
label="Messaging"
selected={isDirects}
>
<MessagingIcon
height={25}
color={
hasUnreadDirects
? "var(--unread)"
: isDirects
? "white"
: "rgba(250, 250, 250, 0.5)"
}
/>
</IconWrapper>
</ButtonBase>
<Box
sx={{
width: "1px",
height: "50px",
background: "white",
borderRadius: "50px",
}}
/>
<ButtonBase
onClick={() => {
goToAnnouncements()
}}
>
<IconWrapper
color={isAnnouncement ? "black" :"rgba(250, 250, 250, 0.5)"}
label="ANN"
selected={isAnnouncement}
selectColor="#09b6e8"
>
<NotificationIcon2
height={25}
width={20}
color={
hasUnreadAnnouncements
? "var(--unread)"
: isAnnouncement
? "black"
: "rgba(250, 250, 250, 0.5)"
}
/>
</IconWrapper>
</ButtonBase>
<ButtonBase
onClick={() => {
goToChat()
}}
>
<IconWrapper
color={isChat ? "black" :"rgba(250, 250, 250, 0.5)"}
label="Chat"
selected={isChat}
selectColor="#09b6e8"
>
<ChatIcon
height={25}
width={20}
color={
hasUnreadChat
? "var(--unread)"
: isChat
? "black"
: "rgba(250, 250, 250, 0.5)"
}
/>
</IconWrapper>
</ButtonBase>
<ButtonBase
onClick={() => {
setGroupSection("forum");
}}
>
<IconWrapper
color={isForum ? 'black' : "rgba(250, 250, 250, 0.5)"}
label="Threads"
selected={isForum}
selectColor="#09b6e8"
>
<ThreadsIcon
height={25}
width={20}
color={
isForum
? "black"
: "rgba(250, 250, 250, 0.5)"
}
/>
</IconWrapper>
</ButtonBase>
<ButtonBase
onClick={() => {
setOpenManageMembers(true)
}}
>
<IconWrapper
color="rgba(250, 250, 250, 0.5)"
label="Members"
selected={false}
>
<MembersIcon
height={25}
width={20}
color={
isForum
? "white"
: "rgba(250, 250, 250, 0.5)"
}
/>
</IconWrapper>
</ButtonBase>
</Box>
</Box>
);
};

View File

@ -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 (
<div>
<Drawer open={open} onClose={toggleDrawer(false)}>
<Box sx={{ width: 400, height: '100%' }} role="presentation">
{children}
</Box>
</Drawer>
</div>
);
}

View File

@ -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 }) => {
}}
>
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<Tabs
sx={{
"& .MuiTabs-indicator": {
backgroundColor: "white",
},
}}
value={value}
onChange={handleChange}
aria-label="basic tabs example"
>
<Tab
sx={{
"&.Mui-selected": {
color: `white`,
},
}}
label="Create Group"
{...a11yProps(0)}
/>
<Tab
sx={{
"&.Mui-selected": {
color: `white`,
},
}}
label="Find Group"
{...a11yProps(1)}
/>
<Tab
sx={{
"&.Mui-selected": {
color: `white`,
},
}}
label="Group Invites"
{...a11yProps(2)}
/>
</Tabs>
<Tabs
value={value}
onChange={handleChange}
aria-label="basic tabs example"
variant={isMobile ? 'scrollable' : 'fullWidth'} // Scrollable on mobile, full width on desktop
scrollButtons="auto"
allowScrollButtonsMobile
sx={{
"& .MuiTabs-indicator": {
backgroundColor: "white",
},
}}
>
<Tab
label="Create Group"
{...a11yProps(0)}
sx={{
"&.Mui-selected": {
color: "white",
},
fontSize: isMobile ? '0.75rem' : '1rem', // Adjust font size for mobile
}}
/>
<Tab
label="Find Group"
{...a11yProps(1)}
sx={{
"&.Mui-selected": {
color: "white",
},
fontSize: isMobile ? '0.75rem' : '1rem', // Adjust font size for mobile
}}
/>
<Tab
label="Group Invites"
{...a11yProps(2)}
sx={{
"&.Mui-selected": {
color: "white",
},
fontSize: isMobile ? '0.75rem' : '1rem', // Adjust font size for mobile
}}
/>
</Tabs>
</Box>
{value === 0 && (

View File

@ -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,

View File

@ -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<any>({});
const [filterMode, setFilterMode] = useState<string>("Recently active");
@ -74,6 +77,7 @@ export const GroupMail = ({
const [isOpenFilterList, setIsOpenFilterList] = useState<boolean>(false);
const anchorElInstanceFilter = useRef<any>(null);
const [tempPublishedList, setTempPublishedList] = useState([])
const dataPublishes = useRef({})
const [isLoading, setIsLoading] = useState(false)
const groupIdRef = useRef<any>(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 (
<SingleThreadParent
sx={{
flexWrap: 'wrap',
gap: '15px',
height: 'auto'
}}
onClick={() => {
setCurrentThread(thread);
if(thread?.threadId && thread?.threadData?.name){
@ -665,6 +692,7 @@ export const GroupMail = ({
}
}}
>
<Avatar
sx={{
height: "50px",
@ -675,11 +703,14 @@ export const GroupMail = ({
>
{thread?.threadData?.name?.charAt(0)}
</Avatar>
<ThreadInfoColumn>
<ThreadInfoColumnNameP>
<ThreadInfoColumnbyP>by </ThreadInfoColumnbyP>
{thread?.threadData?.name}
</ThreadInfoColumnNameP>
<ThreadInfoColumnTime>
{formatTimestamp(thread?.threadData?.createdAt)}
</ThreadInfoColumnTime>

View File

@ -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 = ({
>
<ComposeContainer
sx={{
padding: "15px",
padding: isMobile ? '5px' : "15px",
justifyContent: isMobile ? 'flex-start' : 'revert'
}}
onClick={() => setIsOpen(true)}
>
@ -410,7 +413,7 @@ export const NewThread = ({
<ReusableModal
open={isOpen}
customStyles={{
maxHeight: "95vh",
maxHeight: isMobile ? '95svh' : "95vh",
maxWidth: "950px",
height: "700px",
borderRadius: "12px 12px 0px 0px",
@ -421,26 +424,28 @@ export const NewThread = ({
>
<InstanceListHeader
sx={{
height: "50px",
padding: "20px 42px",
height: isMobile ? 'auto' : "50px",
padding: isMobile ? '5px' : "20px 42px",
flexDirection: "row",
alignItems: 'center',
justifyContent: "space-between",
alignItems: "center",
backgroundColor: "#434448",
}}
>
<NewMessageHeaderP>
{isMessage ? "Post Message" : "New Thread"}
</NewMessageHeaderP>
<CloseContainer onClick={closeModal}>
<CloseContainer sx={{
height: '40px'
}} onClick={closeModal}>
<NewMessageCloseImg src={ModalCloseSVG} />
</CloseContainer>
</InstanceListHeader>
<InstanceListContainer
sx={{
backgroundColor: "#434448",
padding: "20px 42px",
height: "calc(100% - 150px)",
padding: isMobile ? '5px' : "20px 42px",
height: "calc(100% - 165px)",
flexShrink: 0,
}}
>
@ -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 = ({
<MessageDisplay htmlContent={postReply?.textContentV2} />
</Box>
)}
<Spacer height="30px" />
{!isMobile && (
<Spacer height="30px" />
)}
<Box
sx={{
maxHeight: "40vh",
@ -501,6 +509,8 @@ export const NewThread = ({
setEditorRef={setEditorRef}
onEnter={sendMail}
disableEnter
overrideMobile
customEditorHeight="240px"
/>
{/* <TextEditor
inlineContent={value}
@ -513,9 +523,9 @@ export const NewThread = ({
<InstanceFooter
sx={{
backgroundColor: "#434448",
padding: "20px 42px",
padding: isMobile ? '5px' : "20px 42px",
alignItems: "center",
height: "90px",
height: isMobile ? 'auto' : "90px",
}}
>
<NewMessageSendButton onClick={sendMail}>

View File

@ -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<MyModalProps> = ({
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: '75%',
width: isMobile ? '95%' : '75%',
bgcolor: theme.palette.primary.main,
boxShadow: 24,
p: 4,

View File

@ -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<boolean>(false);
let cleanHTML = "";
@ -53,13 +54,17 @@ export const ShowMessage = ({ message, openNewPostWithQuote }: any) => {
}}
>
<WrapperUserAction disabled={myName === message?.name} address={undefined} name={message?.name}>
<Avatar sx={{
height: '50px',
width: '50px'
}} src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${message?.name}/qortal_avatar?async=true`} alt={message?.name}>{message?.name?.charAt(0)}</Avatar>
</WrapperUserAction>
<ThreadInfoColumn>
<WrapperUserAction disabled={myName === message?.name} address={undefined} name={message?.name}>
<ThreadInfoColumnNameP>{message?.name}</ThreadInfoColumnNameP>
</WrapperUserAction>
<ThreadInfoColumnTime>
{formatTimestampForum(message?.created)}
</ThreadInfoColumnTime>

View File

@ -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: {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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 (
<Box sx={{
width: '360px',
display: 'flex',
flexDirection: 'column',
bgcolor: "background.paper",
padding: '20px'
}}>
<Typography sx={{
fontSize: '14px'
}}>Group Invites</Typography>
<Spacer height="10px" />
{loading && groupsWithJoinRequests.length === 0 && (
<Box sx={{
width: '100%',
display: 'flex',
justifyContent: 'center'
}}>
<CustomLoader />
</Box>
)}
{!loading && groupsWithJoinRequests.length === 0 && (
<Box sx={{
width: '100%',
display: 'flex',
justifyContent: 'center'
}}>
<Typography sx={{
fontSize: '12px'
}}>No invites</Typography>
</Box>
)}
<List sx={{ width: "100%", maxWidth: 360, bgcolor: "background.paper", maxHeight: '300px', overflow: 'auto' }}>
{groupsWithJoinRequests?.map((group)=> {
return (
<ListItem
key={group?.groupId}
onClick={()=> {
setOpenAddGroup(true)
setTimeout(() => {
executeEvent("openGroupInvitesRequest", {});
}, 300);
<Box
sx={{
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<Box
sx={{
width: "322px",
display: "flex",
flexDirection: "column",
padding: "0px 20px",
}}
>
<Typography
sx={{
fontSize: "13px",
fontWeight: 600,
}}
disablePadding
secondaryAction={
<IconButton edge="end" aria-label="comments">
<GroupAddIcon
sx={{
color: "white",
}}
/>
</IconButton>
}
>
<ListItemButton disableRipple role={undefined} dense>
<ListItemText primary={`${group?.groupName} has invited you`} />
</ListItemButton>
</ListItem>
)
Hub Invites:
</Typography>
<Spacer height="10px" />
</Box>
})}
</List>
<Box
sx={{
width: "322px",
height: isMobile ? "165px" : "250px",
display: "flex",
flexDirection: "column",
bgcolor: "background.paper",
padding: "20px",
borderRadius: "19px",
}}
>
{loading && groupsWithJoinRequests.length === 0 && (
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "center",
}}
>
<CustomLoader />
</Box>
)}
{!loading && groupsWithJoinRequests.length === 0 && (
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "center",
alignItems: 'center',
height: '100%',
}}
>
<Typography
sx={{
fontSize: "11px",
fontWeight: 400,
color: 'rgba(255, 255, 255, 0.2)'
}}
>
Nothing to display
</Typography>
</Box>
)}
<List
sx={{
width: "100%",
maxWidth: 360,
bgcolor: "background.paper",
maxHeight: "300px",
overflow: "auto",
}}
>
{groupsWithJoinRequests?.map((group) => {
return (
<ListItem
sx={{
marginBottom: "20px",
}}
key={group?.groupId}
onClick={() => {
setOpenAddGroup(true);
setTimeout(() => {
executeEvent("openGroupInvitesRequest", {});
}, 300);
}}
disablePadding
secondaryAction={
<IconButton edge="end" aria-label="comments">
<GroupAddIcon
sx={{
color: "white",
fontSize: "18px",
}}
/>
</IconButton>
}
>
<ListItemButton disableRipple role={undefined} dense>
<ListItemText
sx={{
"& .MuiTypography-root": {
fontSize: "13px",
fontWeight: 400,
},
}}
primary={`${group?.groupName} has invited you`}
/>
</ListItemButton>
</ListItem>
);
})}
</List>
</Box>
</Box>
);
};

View File

@ -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 (
<Box sx={{
width: '360px',
display: 'flex',
flexDirection: 'column',
bgcolor: "background.paper",
padding: '20px'
<Box sx={{
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: 'center'
}}>
<Typography sx={{
fontSize: '14px'
}}>Join Requests</Typography>
<Spacer height="10px" />
<Box
sx={{
width: "322px",
display: "flex",
flexDirection: "column",
padding: '0px 20px',
}}
>
<Typography
sx={{
fontSize: "13px",
fontWeight: 600,
}}
>
Join Requests:
</Typography>
<Spacer height="10px" />
</Box>
<Box
sx={{
width: "322px",
height: isMobile ? "165px" : "250px",
display: "flex",
flexDirection: "column",
bgcolor: "background.paper",
padding: "20px",
borderRadius: '19px'
}}
>
{loading && groupsWithJoinRequests.length === 0 && (
<Box sx={{
width: '100%',
@ -115,16 +141,27 @@ export const GroupJoinRequests = ({ myAddress, groups, setOpenManageMembers, get
<CustomLoader />
</Box>
)}
{!loading && groupsWithJoinRequests.length === 0 && (
<Box sx={{
width: '100%',
display: 'flex',
justifyContent: 'center'
}}>
<Typography sx={{
fontSize: '12px'
}}>No join requests</Typography>
</Box>
{!loading && (groupsWithJoinRequests.length === 0 || groupsWithJoinRequests?.filter((group)=> group?.data?.length > 0).length === 0) && (
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "center",
alignItems: 'center',
height: '100%',
}}
>
<Typography
sx={{
fontSize: "11px",
fontWeight: 400,
color: 'rgba(255, 255, 255, 0.2)'
}}
>
Nothing to display
</Typography>
</Box>
)}
<List sx={{ width: "100%", maxWidth: 360, bgcolor: "background.paper", maxHeight: '300px', overflow: 'auto' }}>
{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={
<IconButton edge="end" aria-label="comments">
<GroupAddIcon
sx={{
color: "white",
fontSize: '18px'
}}
/>
</IconButton>
}
>
<ListItemButton disableRipple role={undefined} dense>
<ListItemButton sx={{
padding: "0px",
}} disableRipple role={undefined} dense>
<ListItemText primary={`${group?.group?.groupName} has ${group?.data?.length} pending join requests.`} />
<ListItemText sx={{
"& .MuiTypography-root": {
fontSize: "13px",
fontWeight: 400,
},
}} primary={`${group?.group?.groupName} has ${group?.data?.length} pending join requests.`} />
</ListItemButton>
</ListItem>
)
@ -166,5 +215,6 @@ export const GroupJoinRequests = ({ myAddress, groups, setOpenManageMembers, get
</List>
</Box>
</Box>
);
};

View File

@ -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 (
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "center",
marginTop: '14px',
marginBottom: '14px'
}}
>
<Button
aria-controls={open ? "home-menu" : undefined}
aria-haspopup="true"
aria-expanded={open ? "true" : undefined}
onClick={handleClick}
variant="contained"
sx={{
backgroundColor: "var(--bg-primary)",
width: "148px",
borderRadius: "5px",
fontSize: "12px",
fontWeight: 600,
color: "#fff",
textTransform: "none",
padding: '5px',
height: '25px'
}}
>
<Box
sx={{
display: "flex",
gap: "6px",
alignItems: "center",
justifyContent: "space-between",
width: '100%'
}}
>
<Box
sx={{
display: "flex",
gap: "6px",
alignItems: "center",
}}
>
{groupSection === "announcement" &&(
<> <NotificationIcon2 /> {" Announcements"}</>
)}
{groupSection === "chat" &&(
<> <ChatIcon /> {" Hub Chats"}</>
)}
{groupSection === "forum" &&(
<> <ThreadsIcon /> {" Threads"}</>
)}
</Box>
<ArrowDownIcon color="white" />
</Box>
</Button>
<Menu
id="home-menu"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
MenuListProps={{
"aria-labelledby": "basic-button",
}}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
slotProps={{
paper: {
sx: {
backgroundColor: 'var(--bg-primary)',
color: '#fff',
width: '148px',
borderRadius: '5px'
},
},
}}
sx={{
marginTop: '10px'
}}
>
<MenuItem
onClick={() => {
setGroupSection("chat");
handleClose();
}}
>
<ListItemIcon sx={{
minWidth: '24px !important'
}}>
<ChatIcon sx={{ color: "#fff" }} />
</ListItemIcon>
<ListItemText sx={{
"& .MuiTypography-root": {
fontSize: "12px",
fontWeight: 600,
},
}} primary="Chat" />
</MenuItem>
<MenuItem
onClick={() => {
setGroupSection("announcement");
handleClose();
}}
>
<ListItemIcon sx={{
minWidth: '24px !important'
}}>
<NotificationIcon2 sx={{ color: "#fff" }} />
</ListItemIcon>
<ListItemText sx={{
"& .MuiTypography-root": {
fontSize: "12px",
fontWeight: 600,
},
}} primary="Announcements" />
</MenuItem>
<MenuItem
onClick={() => {
setGroupSection("forum");
handleClose();
}}
>
<ListItemIcon sx={{
minWidth: '24px !important'
}}>
<ThreadsIcon sx={{ color: "#fff" }} />
</ListItemIcon>
<ListItemText sx={{
"& .MuiTypography-root": {
fontSize: "12px",
fontWeight: 600,
},
}} primary="Forum" />
</MenuItem>
<MenuItem
onClick={() => {
setOpenManageMembers(true)
handleClose();
}}
>
<ListItemIcon sx={{
minWidth: '24px !important'
}}>
<MembersIcon sx={{ color: "#fff" }} />
</ListItemIcon>
<ListItemText sx={{
"& .MuiTypography-root": {
fontSize: "12px",
fontWeight: 600,
},
}} primary="Members" />
</MenuItem>
</Menu>
</Box>
);
};

View File

@ -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 (
<Box
sx={{
display: "flex",
width: "100%",
flexDirection: "column",
height: "100%",
overflow: "auto",
alignItems: "center",
}}
>
<Spacer height="20px" />
<Typography
sx={{
color: "rgba(255, 255, 255, 1)",
fontWeight: 400,
fontSize: userInfo?.name?.length > 15 ? "16px" : "20px",
padding: '10px'
}}
>
Welcome{" "}
{userInfo?.name ? (
<span
style={{
fontStyle: "italic",
}}
>{`, ${userInfo?.name}`}</span>
) : null}
</Typography>
<Spacer height="26px" />
{/* <Box
sx={{
display: "flex",
width: "100%",
justifyContent: "flex-start",
}}
>
<Button
variant="outlined"
startIcon={<RefreshIcon />}
onClick={refreshHomeDataFunc}
sx={{
color: "white",
}}
>
Refresh home data
</Button>
</Box> */}
{!isLoadingGroups && (
<Box
sx={{
display: "flex",
gap: "15px",
flexWrap: "wrap",
justifyContent: "center",
}}
>
<ThingsToDoInitial
balance={balance}
myAddress={myAddress}
name={userInfo?.name}
hasGroups={groups?.length !== 0}
/>
<ListOfThreadPostsWatched />
<GroupJoinRequests
setGroupSection={setGroupSection}
setSelectedGroup={setSelectedGroup}
getTimestampEnterChat={getTimestampEnterChat}
setOpenManageMembers={setOpenManageMembers}
myAddress={myAddress}
groups={groups}
setMobileViewMode={setMobileViewMode}
/>
<GroupInvites
setOpenAddGroup={setOpenAddGroup}
myAddress={myAddress}
groups={groups}
setMobileViewMode={setMobileViewMode}
/>
</Box>
)}
<Spacer height="180px" />
</Box>
);
};

View File

@ -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 (
<Box
sx={{
display: "flex",
width: "100%",
flexDirection: "column",
height: "100%",
overflow: "auto",
alignItems: "center",
}}
>
<Spacer height="20px" />
<Box sx={{
display: "flex",
width: "100%",
flexDirection: "column",
height: "100%",
alignItems: "flex-start",
maxWidth: '1036px'
}}>
<Typography
sx={{
color: "rgba(255, 255, 255, 1)",
fontWeight: 400,
fontSize: userInfo?.name?.length > 15 ? "16px" : "20px",
padding: '10px'
}}
>
Welcome{" "}
{userInfo?.name ? (
<span
style={{
fontStyle: "italic",
}}
>{`, ${userInfo?.name}`}</span>
) : null}
</Typography>
<Spacer height="30px" />
{!isLoadingGroups && (
<Box
sx={{
display: "flex",
gap: "15px",
flexWrap: "wrap",
justifyContent: "flex-start",
}}
>
<Box sx={{
width: '330px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<ThingsToDoInitial
balance={balance}
myAddress={myAddress}
name={userInfo?.name}
hasGroups={groups?.length !== 0}
/>
</Box>
<Box sx={{
width: '330px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<ListOfThreadPostsWatched />
</Box>
<Box sx={{
width: '330px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<GroupJoinRequests
setGroupSection={setGroupSection}
setSelectedGroup={setSelectedGroup}
getTimestampEnterChat={getTimestampEnterChat}
setOpenManageMembers={setOpenManageMembers}
myAddress={myAddress}
groups={groups}
setMobileViewMode={setMobileViewMode}
/>
</Box>
<Box sx={{
width: '330px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<GroupInvites
setOpenAddGroup={setOpenAddGroup}
myAddress={myAddress}
groups={groups}
setMobileViewMode={setMobileViewMode}
/>
</Box>
</Box>
)}
</Box>
<Spacer height="26px" />
{/* <Box
sx={{
display: "flex",
width: "100%",
justifyContent: "flex-start",
}}
>
<Button
variant="outlined"
startIcon={<RefreshIcon />}
onClick={refreshHomeDataFunc}
sx={{
color: "white",
}}
>
Refresh home data
</Button>
</Box> */}
<Spacer height="180px" />
</Box>
);
};

View File

@ -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: {

View File

@ -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 (
<div>
<p>Ban list</p>
<div style={{ position: 'relative', height: '500px', width: '600px', display: 'flex', flexDirection: 'column', flexShrink: 1 }}>
<div style={{ position: 'relative', height: '500px', width: '100%', display: 'flex', flexDirection: 'column', flexShrink: 1 }}>
<AutoSizer>
{({ height, width }) => (
<List

View File

@ -75,7 +75,7 @@ export const ListOfInvites = ({ groupId, setInfoSnack, setOpenSnack, show }) =>
})
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 (
<div>
<p>Invitees list</p>
<div style={{ position: 'relative', height: '500px', width: '600px', display: 'flex', flexDirection: 'column', flexShrink: 1 }}>
<div style={{ position: 'relative', height: '500px', width: '100%', display: 'flex', flexDirection: 'column', flexShrink: 1 }}>
<AutoSizer>
{({ height, width }) => (
<List

View File

@ -74,7 +74,7 @@ export const ListOfJoinRequests = ({ groupId, setInfoSnack, setOpenSnack, show }
})
setIsLoadingAccept(true)
await new Promise((res, rej)=> {
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 (
<div>
<p>Join request list</p>
<div style={{ position: 'relative', height: '500px', width: '600px', display: 'flex', flexDirection: 'column', flexShrink: 1 }}>
<div style={{ position: 'relative', height: '500px', width: '100%', display: 'flex', flexDirection: 'column', flexShrink: 1 }}>
<AutoSizer>
{({ height, width }) => (
<List

View File

@ -63,7 +63,7 @@ const ListOfMembers = ({
setIsLoadingKick(true);
new Promise((res, rej) => {
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,

View File

@ -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 (
<Box sx={{
width: '360px',
display: 'flex',
flexDirection: 'column',
bgcolor: "background.paper",
padding: '20px'
<Box sx={{
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: 'center'
}}>
<Typography sx={{
fontSize: '14px'
}}>New Thread Posts</Typography>
<Spacer height="10px" />
{loading && posts.length === 0 && (
<Box sx={{
width: '100%',
display: 'flex',
justifyContent: 'center'
}}>
<CustomLoader />
</Box>
)}
{!loading && posts.length === 0 && (
<Box sx={{
width: '100%',
display: 'flex',
justifyContent: 'center'
}}>
<Typography sx={{
fontSize: '12px'
}}>No thread post notifications</Typography>
</Box>
)}
<List sx={{ width: "100%", maxWidth: 360, bgcolor: "background.paper", maxHeight: '300px', overflow: 'auto' }}>
{posts?.map((post)=> {
return (
<ListItem
key={post?.thread?.threadId}
onClick={()=> {
executeEvent("openThreadNewPost", {
data: post
});
}}
disablePadding
secondaryAction={
<IconButton edge="end" aria-label="comments">
<VisibilityIcon
sx={{
color: "red",
}}
/>
</IconButton>
}
>
<ListItemButton disableRipple role={undefined} dense>
<ListItemText primary={`New post in ${post?.thread?.threadData?.title}`} />
</ListItemButton>
</ListItem>
)
<Box
sx={{
width: "322px",
display: "flex",
flexDirection: "column",
padding: '0px 20px',
})}
</List>
}}
>
<Typography
sx={{
fontSize: "13px",
fontWeight: 600,
}}
>
New Thread Posts:
</Typography>
<Spacer height="10px" />
</Box>
<Box
sx={{
width: "322px",
height: isMobile ? "165px" : "250px",
display: "flex",
flexDirection: "column",
bgcolor: "background.paper",
padding: "20px",
borderRadius: '19px'
}}
>
{loading && posts.length === 0 && (
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "center",
}}
>
<CustomLoader />
</Box>
)}
{!loading && posts.length === 0 && (
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "center",
alignItems: 'center',
height: '100%',
}}
>
<Typography
sx={{
fontSize: "11px",
fontWeight: 400,
color: 'rgba(255, 255, 255, 0.2)'
}}
>
Nothing to display
</Typography>
</Box>
)}
{posts?.length > 0 && (
<List
sx={{
width: "100%",
maxWidth: 360,
bgcolor: "background.paper",
maxHeight: "300px",
overflow: "auto",
}}
>
{posts?.map((post) => {
return (
<ListItem
key={post?.thread?.threadId}
onClick={() => {
executeEvent("openThreadNewPost", {
data: post,
});
}}
disablePadding
secondaryAction={
<IconButton edge="end" aria-label="comments">
<VisibilityIcon
sx={{
color: "red",
}}
/>
</IconButton>
}
>
<ListItemButton disableRipple role={undefined} dense>
<ListItemText
primary={`New post in ${post?.thread?.threadData?.title}`}
/>
</ListItemButton>
</ListItem>
);
})}
</List>
)}
</Box>
</Box>
);
};

View File

@ -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 = ({
}}
>
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<Tabs
sx={{
"& .MuiTabs-indicator": {
backgroundColor: "white",
},
}}
value={value}
onChange={handleChange}
aria-label="basic tabs example"
>
<Tab
sx={{
"&.Mui-selected": {
color: `white`,
},
}}
label="List of members"
{...a11yProps(0)}
/>
<Tab
sx={{
"&.Mui-selected": {
color: `white`,
},
}}
label="Invite new member"
{...a11yProps(1)}
/>
<Tab
sx={{
"&.Mui-selected": {
color: `white`,
},
}}
label="List of invites"
{...a11yProps(2)}
/>
<Tab
sx={{
"&.Mui-selected": {
color: `white`,
},
}}
label="List of bans"
{...a11yProps(3)}
/>
<Tab
sx={{
"&.Mui-selected": {
color: `white`,
},
}}
label="Join requests"
{...a11yProps(4)}
/>
</Tabs>
<Tabs
value={value}
onChange={handleChange}
aria-label="basic tabs example"
variant="scrollable" // Make tabs scrollable
scrollButtons="auto" // Show scroll buttons automatically
allowScrollButtonsMobile // Show scroll buttons on mobile as well
sx={{
"& .MuiTabs-indicator": {
backgroundColor: "white",
},
maxWidth: '100%', // Ensure the tabs container fits within the available space
overflow: 'hidden', // Prevents overflow on small screens
}}
>
<Tab
label="List of members"
{...a11yProps(0)}
sx={{
"&.Mui-selected": {
color: "white",
},
fontSize: isMobile ? '0.75rem' : '1rem', // Adjust font size for mobile
}}
/>
<Tab
label="Invite new member"
{...a11yProps(1)}
sx={{
"&.Mui-selected": {
color: "white",
},
fontSize: isMobile ? '0.75rem' : '1rem',
}}
/>
<Tab
label="List of invites"
{...a11yProps(2)}
sx={{
"&.Mui-selected": {
color: "white",
},
fontSize: isMobile ? '0.75rem' : '1rem',
}}
/>
<Tab
label="List of bans"
{...a11yProps(3)}
sx={{
"&.Mui-selected": {
color: "white",
},
fontSize: isMobile ? '0.75rem' : '1rem',
}}
/>
<Tab
label="Join requests"
{...a11yProps(4)}
sx={{
"&.Mui-selected": {
color: "white",
},
fontSize: isMobile ? '0.75rem' : '1rem',
}}
/>
</Tabs>
</Box>
{selectedGroup?.groupId && !isOwner && (
@ -244,6 +253,7 @@ export const ManageMembers = ({
sx={{
width: "100%",
padding: "25px",
maxWidth: '750px'
}}
>
<ListOfMembers
@ -262,6 +272,7 @@ export const ManageMembers = ({
sx={{
width: "100%",
padding: "25px",
maxWidth: '750px'
}}
>
<InviteMember show={show} groupId={selectedGroup?.groupId} setOpenSnack={setOpenSnack} setInfoSnack={setInfoSnack} />
@ -272,7 +283,8 @@ export const ManageMembers = ({
<Box
sx={{
width: "100%",
padding: "25px",
padding: "25px",
maxWidth: '750px'
}}
>
<ListOfInvites show={show} groupId={selectedGroup?.groupId} setOpenSnack={setOpenSnack} setInfoSnack={setInfoSnack} />
@ -284,7 +296,8 @@ export const ManageMembers = ({
<Box
sx={{
width: "100%",
padding: "25px",
padding: "25px",
maxWidth: '750px'
}}
>
<ListOfBans show={show} groupId={selectedGroup?.groupId} setOpenSnack={setOpenSnack} setInfoSnack={setInfoSnack} />
@ -295,7 +308,8 @@ export const ManageMembers = ({
<Box
sx={{
width: "100%",
padding: "25px",
padding: "25px",
maxWidth: '750px'
}}
>
<ListOfJoinRequests show={show} setOpenSnack={setOpenSnack} setInfoSnack={setInfoSnack} groupId={selectedGroup?.groupId} />

View File

@ -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 (
<Box sx={{
width: '360px',
display: 'flex',
flexDirection: 'column',
bgcolor: "background.paper",
padding: '20px'
}}>
<Typography sx={{
fontSize: '14px'
}}>Suggestion: Complete the following</Typography>
<Spacer height="10px" />
<List sx={{ width: "100%", maxWidth: 360 }}>
<ListItem
// secondaryAction={
// <IconButton edge="end" aria-label="comments">
// <InfoIcon
// sx={{
// color: "white",
// }}
// />
// </IconButton>
// }
disablePadding
<Box
sx={{
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<Box
sx={{
width: "322px",
display: "flex",
flexDirection: "column",
padding: "0px 20px",
}}
>
<ListItemButton disableRipple role={undefined} dense>
<ListItemIcon>
<Checkbox
edge="start"
checked={checked1}
tabIndex={-1}
disableRipple
disabled={true}
sx={{
"&.Mui-checked": {
color: "white", // Customize the color when checked
},
"& .MuiSvgIcon-root": {
color: "white",
},
}}
/>
</ListItemIcon>
<ListItemText primary={`Have at least 6 QORT in your wallet`} />
</ListItemButton>
</ListItem>
<ListItem
// secondaryAction={
// <IconButton edge="end" aria-label="comments">
// <InfoIcon
// sx={{
// color: "white",
// }}
// />
// </IconButton>
// }
disablePadding
<Typography
sx={{
fontSize: "13px",
fontWeight: 600,
}}
>
Getting Started:
</Typography>
<Spacer height="10px" />
</Box>
<Box
sx={{
width: "322px",
height: isMobile ? "165px" : "250px",
display: "flex",
flexDirection: "column",
bgcolor: "background.paper",
padding: "20px",
borderRadius: "19px",
}}
>
<ListItemButton disableRipple role={undefined} dense>
<ListItemIcon>
<Checkbox
edge="start"
checked={checked2}
tabIndex={-1}
disableRipple
disabled={true}
<List sx={{ width: "100%", maxWidth: 360 }}>
<ListItem
// secondaryAction={
// <IconButton edge="end" aria-label="comments">
// <InfoIcon
// sx={{
// color: "white",
// }}
// />
// </IconButton>
// }
disablePadding
sx={{
marginBottom: '20px'
}}
>
<ListItemButton
sx={{
"&.Mui-checked": {
color: "white", // Customize the color when checked
},
"& .MuiSvgIcon-root": {
color: "white",
},
padding: "0px",
}}
/>
</ListItemIcon>
<ListItemText primary={`Register a name`} />
</ListItemButton>
</ListItem>
<ListItem
// secondaryAction={
// <IconButton edge="end" aria-label="comments">
// <InfoIcon
// sx={{
// color: "white",
// }}
// />
// </IconButton>
// }
disablePadding
>
<ListItemButton disableRipple role={undefined} dense>
<ListItemIcon>
<Checkbox
edge="start"
checked={checked3}
tabIndex={-1}
disableRipple
disabled={true}
sx={{
"&.Mui-checked": {
color: "white", // Customize the color when checked
},
"& .MuiSvgIcon-root": {
color: "white",
},
}}
/>
</ListItemIcon>
<ListItemText primary={`Join a group`} />
</ListItemButton>
</ListItem>
</List>
role={undefined}
dense
>
<ListItemText
sx={{
"& .MuiTypography-root": {
fontSize: "13px",
fontWeight: 400,
},
}}
primary={`Have at least 6 QORT in your wallet`}
/>
<ListItemIcon
sx={{
justifyContent: "flex-end",
}}
>
<Box
sx={{
height: "18px",
width: "18px",
borderRadius: "50%",
backgroundColor: checked1 ? "rgba(9, 182, 232, 1)" : "transparent",
outline: "1px solid rgba(9, 182, 232, 1)",
}}
/>
{/* <Checkbox
edge="start"
checked={checked1}
tabIndex={-1}
disableRipple
disabled={true}
sx={{
"&.Mui-checked": {
color: "white", // Customize the color when checked
},
"& .MuiSvgIcon-root": {
color: "white",
},
}}
/> */}
</ListItemIcon>
</ListItemButton>
</ListItem>
<ListItem
sx={{
marginBottom: '20px'
}}
// secondaryAction={
// <IconButton edge="end" aria-label="comments">
// <InfoIcon
// sx={{
// color: "white",
// }}
// />
// </IconButton>
// }
disablePadding
>
<ListItemButton sx={{
padding: "0px",
}} disableRipple role={undefined} dense>
<ListItemText sx={{
"& .MuiTypography-root": {
fontSize: "13px",
fontWeight: 400,
},
}} primary={`Register a name`} />
<ListItemIcon sx={{
justifyContent: "flex-end",
}}>
<Box
sx={{
height: "18px",
width: "18px",
borderRadius: "50%",
backgroundColor: checked2 ? "rgba(9, 182, 232, 1)" : "transparent",
outline: "1px solid rgba(9, 182, 232, 1)",
}}
/>
</ListItemIcon>
</ListItemButton>
</ListItem>
<ListItem
// secondaryAction={
// <IconButton edge="end" aria-label="comments">
// <InfoIcon
// sx={{
// color: "white",
// }}
// />
// </IconButton>
// }
disablePadding
>
<ListItemButton sx={{
padding: "0px",
}} disableRipple role={undefined} dense>
<ListItemText sx={{
"& .MuiTypography-root": {
fontSize: "13px",
fontWeight: 400,
},
}} primary={`Join a group hub`} />
<ListItemIcon sx={{
justifyContent: "flex-end",
}}>
<Box
sx={{
height: "18px",
width: "18px",
borderRadius: "50%",
backgroundColor: checked3 ? "rgba(9, 182, 232, 1)" : "transparent",
outline: "1px solid rgba(9, 182, 232, 1)",
}}
/>
</ListItemIcon>
</ListItemButton>
</ListItem>
</List>
</Box>
</Box>
);
};

View File

@ -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 (
<div>
<p>Invite list</p>
<div style={{ position: 'relative', height: '500px', width: '600px', display: 'flex', flexDirection: 'column', flexShrink: 1 }}>
<div style={{ position: 'relative', height: '500px', width: '100%', display: 'flex', flexDirection: 'column', flexShrink: 1 }}>
<AutoSizer>
{({ height, width }) => (
<List

View File

@ -74,7 +74,7 @@ export const WebSocketActive = ({ myAddress, setIsLoadingGroups }) => {
).sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
chrome.runtime.sendMessage({
chrome?.runtime?.sendMessage({
action: 'handleActiveGroupDataFromSocket',
payload: {
groups: sortedGroups,

View File

@ -14,7 +14,7 @@ export const Loader = () => {
left:'0px',
right: '0px',
bottom: '0px',
zIndex: 2,
zIndex: 10,
background: 'rgba(0, 0, 0, 0.4)'
}}>
<CircularProgress color="success" size={25} />

View File

@ -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 (
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
gap: "5px",
flexDirection: "column",
}}
>
{children}
<Typography
sx={{
fontFamily: "Inter",
fontSize: "12px",
fontWeight: 500,
color: color,
wordBreak: 'normal'
}}
>
{label}
</Typography>
</Box>
);
};
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 (
<Box
sx={{
width: "100%",
position: "fixed",
bottom: 0,
backgroundColor: "var(--bg-primary)",
display: "flex",
alignItems: "center",
height: "67px", // Footer height
zIndex: 1,
borderTopRightRadius: "25px",
borderTopLeftRadius: "25px",
boxShadow: '0px -2px 10px rgba(0, 0, 0, 0.1)',
}}
>
<BottomNavigation
showLabels
value={value}
onChange={(event, newValue) => setValue(newValue)}
sx={{ backgroundColor: "transparent", flexGrow: 1 }}
>
<BottomNavigationAction
onClick={() => {
// setMobileViewMode('wallet')
setIsOpenDrawerProfile(true);
}}
icon={
<IconWrapper color="rgba(250, 250, 250, 0.5)" label="Wallet">
<WalletIcon color="rgba(250, 250, 250, 0.5)" />
</IconWrapper>
}
sx={{ color: value === 0 ? "white" : "gray", padding: "0px 10px" }}
/>
<BottomNavigationAction
onClick={() => {
setMobileViewMode("groups");
}}
icon={
<IconWrapper color="rgba(250, 250, 250, 0.5)" label="Hubs">
<HubsIcon color={hasUnreadGroups ? "var(--unread)" : "rgba(250, 250, 250, 0.5)"} />
</IconWrapper>
}
sx={{
color: value === 0 ? "white" : "gray",
paddingLeft: "10px",
paddingRight: "42px",
}}
/>
</BottomNavigation>
{/* Floating Center Button */}
<Box
sx={{
position: "absolute",
bottom: "34px", // Adjusted to float properly based on footer height
left: "50%",
transform: "translateX(-50%)", // Center horizontally
width: "59px",
height: "59px",
backgroundColor: "var(--bg-primary)",
borderRadius: "50%",
display: "flex",
justifyContent: "center",
alignItems: "center",
boxShadow: "0 4px 10px rgba(0, 0, 0, 0.3)", // Subtle shadow for the floating effect
zIndex: 3,
}}
>
<Box
sx={{
width: "49px", // Slightly smaller inner circle
height: "49px",
backgroundColor: "var(--bg-primary)",
borderRadius: "50%",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
{/* Custom Center Icon */}
<img src={BottomLogo} alt="center-icon" />
</Box>
</Box>
<BottomNavigation
showLabels
value={value}
onChange={(event, newValue) => setValue(newValue)}
sx={{ backgroundColor: "transparent", flexGrow: 1 }}
>
<BottomNavigationAction
onClick={() => {
setMobileViewModeKeepOpen("messaging");
}}
icon={
<IconWrapper label="Messaging" color="rgba(250, 250, 250, 0.5)">
<MessagingIcon color={hasUnreadDirects ? "var(--unread)" :"rgba(250, 250, 250, 0.5)"} />
</IconWrapper>
}
sx={{
color: value === 2 ? "white" : "gray",
paddingLeft: "55px",
paddingRight: "10px",
}}
/>
<BottomNavigationAction
onClick={() => {
chrome.tabs.create({ url: "https://www.qort.trade"});
}}
icon={
<IconWrapper label="Trading" color="rgba(250, 250, 250, 0.5)">
<TradingIcon color="rgba(250, 250, 250, 0.5)" />
</IconWrapper>
}
sx={{ color: value === 3 ? "white" : "gray", padding: "0px 10px" }}
/>
</BottomNavigation>
</Box>
);
};

View File

@ -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 (
<AppBar
position="static"
sx={{
backgroundColor: "background: rgba(0, 0, 0, 0.2)",
boxShadow: "none",
}}
>
<Toolbar
sx={{
justifyContent: "space-between",
padding: "0 16px",
height: "45px",
minHeight: "45px",
}}
>
{/* Left Home Icon */}
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "18px",
width: "75px",
}}
>
<IconButton
edge="start"
color="inherit"
aria-label="home"
onClick={() => {
setMobileViewModeKeepOpen("");
goToHome();
}}
// onClick={onHomeClick}
>
<HomeIcon height={20} width={27} color="rgba(145, 145, 147, 1)" />
</IconButton>
<IconButton
edge="start"
color="inherit"
aria-label="home"
onClick={handleClick}
>
<NotificationIcon height={20} width={21} color={hasUnreadDirects || hasUnreadGroups ? "var(--unread)" : "rgba(145, 145, 147, 1)"} />
</IconButton>
</Box>
{/* Center Title */}
<Typography
variant="h6"
sx={{
color: "rgba(255, 255, 255, 1)",
fontWeight: 700,
letterSpacing: "2px",
fontSize: "13px",
}}
>
QORTAL
</Typography>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "18px",
width: "75px",
justifyContent: "flex-end",
}}
>
{/* Right Logout Icon */}
<IconButton
onClick={() => {
setMobileViewModeKeepOpen("messaging");
}}
edge="end"
color="inherit"
aria-label="logout"
// onClick={onLogoutClick}
>
<MessagingIcon2 height={20} color={hasUnreadDirects ? "var(--unread)" : "rgba(145, 145, 147, 1)"}
/>
</IconButton>
<IconButton
onClick={logoutFunc}
edge="end"
color="inherit"
aria-label="logout"
// onClick={onLogoutClick}
>
<LogoutIcon
height={20}
width={21}
color="rgba(145, 145, 147, 1)"
/>
</IconButton>
</Box>
</Toolbar>
<Menu
id="home-menu"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
MenuListProps={{
"aria-labelledby": "basic-button",
}}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
slotProps={{
paper: {
sx: {
backgroundColor: 'var(--bg-primary)',
color: '#fff',
width: '148px',
borderRadius: '5px'
},
},
}}
sx={{
marginTop: '10px'
}}
>
<MenuItem
onClick={() => {
setMobileViewMode("groups");
setMobileViewModeKeepOpen("")
handleClose();
}}
>
<ListItemIcon sx={{
minWidth: '24px !important'
}}>
<HubsIcon height={20} color={hasUnreadGroups ? "var(--unread)" :"rgba(250, 250, 250, 0.5)"} />
</ListItemIcon>
<ListItemText sx={{
"& .MuiTypography-root": {
fontSize: "12px",
fontWeight: 600,
color: hasUnreadDirects ? "var(--unread)" :"rgba(250, 250, 250, 0.5)"
},
}} primary="Hubs" />
</MenuItem>
<MenuItem
onClick={() => {
setMobileViewModeKeepOpen("messaging");
handleClose();
}}
>
<ListItemIcon sx={{
minWidth: '24px !important'
}}>
<MessagingIcon height={20} color={hasUnreadDirects ? "var(--unread)" :"rgba(250, 250, 250, 0.5)"} />
</ListItemIcon>
<ListItemText sx={{
"& .MuiTypography-root": {
fontSize: "12px",
fontWeight: 600,
color: hasUnreadDirects ? "var(--unread)" :"rgba(250, 250, 250, 0.5)"
},
}} primary="Messaging" />
</MenuItem>
</Menu>
</AppBar>
);
}
return (
<>
{/* Main Header */}
<AppBar
position="static"
sx={{ backgroundColor: "var(--bg-primary)", boxShadow: "none" }}
>
<Toolbar
sx={{
justifyContent: "space-between",
padding: "0 16px",
height: "60px",
}}
>
{/* Left Home Icon */}
<IconButton
edge="start"
color="inherit"
aria-label="home"
onClick={goToHome}
// onClick={onHomeClick}
>
<HomeIcon color="rgba(145, 145, 147, 1)" />
</IconButton>
{/* Center Title */}
<Typography
variant="h6"
sx={{
color: "rgba(255, 255, 255, 1)",
fontWeight: 700,
letterSpacing: "2px",
fontSize: "13px",
}}
>
QORTAL
</Typography>
{/* Right Logout Icon */}
<IconButton
onClick={logoutFunc}
edge="end"
color="inherit"
aria-label="logout"
// onClick={onLogoutClick}
>
<LogoutIcon color="rgba(145, 145, 147, 1)" />
</IconButton>
</Toolbar>
</AppBar>
{/* Secondary Section */}
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
backgroundColor: "var(--bg-3)",
padding: "8px 16px",
position: "relative",
height: "27px",
}}
>
<Box
sx={{
display: "flex",
gap: "10px",
alignItems: "center",
userSelect: "none",
}}
>
<Typography
sx={{
color: "rgba(255, 255, 255, 1)",
fontWeight: 400,
fontSize: "11px",
}}
>
{myName}
</Typography>
{/*
<ArrowDownIcon /> */}
</Box>
<Box
sx={{
position: "absolute",
left: "50%",
transform: "translate(-50%, 50%)",
display: "flex",
justifyContent: "center",
alignItems: "center",
zIndex: 500,
width: "30px", // Adjust as needed
height: "30px", // Adjust as needed
backgroundColor: "#232428", // Circle background
borderRadius: "50%",
boxShadow: "0px 4px 10px rgba(0, 0, 0, 0.3)", // Optional shadow for the circle
}}
>
<IconButton onClick={handleClick} color="inherit">
<NotificationIcon color={hasUnreadDirects || hasUnreadGroups ? "var(--unread)" : "rgba(255, 255, 255, 1)"} />
</IconButton>
</Box>
{/* Right Dropdown */}
{/* <ButtonBase
onClick={() => {
setIsOpenDrawerProfile(true);
}}
>
<Box
sx={{
display: "flex",
gap: "10px",
alignItems: "center",
}}
>
<Typography
sx={{
color: "rgba(255, 255, 255, 1)",
fontWeight: 400,
fontSize: "11px",
}}
>
View Wallet
</Typography>
<ArrowDownIcon />
</Box>
</ButtonBase> */}
</Box>
<Menu
id="home-menu"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
MenuListProps={{
"aria-labelledby": "basic-button",
}}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
slotProps={{
paper: {
sx: {
backgroundColor: 'var(--bg-primary)',
color: '#fff',
width: '148px',
borderRadius: '5px'
},
},
}}
sx={{
marginTop: '10px'
}}
>
<MenuItem
onClick={() => {
setMobileViewMode("groups");
setMobileViewModeKeepOpen("")
handleClose();
}}
>
<ListItemIcon sx={{
minWidth: '24px !important'
}}>
<HubsIcon height={20} color={hasUnreadGroups ? "var(--unread)" :"rgba(250, 250, 250, 0.5)"} />
</ListItemIcon>
<ListItemText sx={{
"& .MuiTypography-root": {
fontSize: "12px",
fontWeight: 600,
color: hasUnreadDirects ? "var(--unread)" :"rgba(250, 250, 250, 0.5)"
},
}} primary="Hubs" />
</MenuItem>
<MenuItem
onClick={() => {
setMobileViewModeKeepOpen("messaging");
handleClose();
}}
>
<ListItemIcon sx={{
minWidth: '24px !important'
}}>
<MessagingIcon height={20} color={hasUnreadDirects ? "var(--unread)" :"rgba(250, 250, 250, 0.5)"} />
</ListItemIcon>
<ListItemText sx={{
"& .MuiTypography-root": {
fontSize: "12px",
fontWeight: 600,
color: hasUnreadDirects ? "var(--unread)" :"rgba(250, 250, 250, 0.5)"
},
}} primary="Messaging" />
</MenuItem>
</Menu>
</>
);
};
export default Header;

View File

@ -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 (
<>
<Box
onClick={handleChildClick} // Open popover on click
sx={{
display: 'inline-flex', // Keep inline behavior
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
padding: 0,
width: 'fit-content', // Limit width to content size
height: 'fit-content', // Limit height to content size
alignSelf: 'flex-start', // Prevent stretching to parent height
maxWidth: '100%', // Optional: Limit the width to avoid overflow
maxHeight: '100%', // Prevent flex shrink behavior in a flex container
}}
>
{/* Render the child without altering dimensions */}
{children}
</Box>
{/* Popover */}
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={handleClose} // Close popover on click outside
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
componentsProps={{
paper: {
onClick: (event) => event.stopPropagation(), // Stop propagation inside popover
},
}}
>
<Box sx={{ p: 2, display: 'flex', flexDirection: 'column', gap: 1 }}>
{/* Option 1: Message */}
<Button
variant="text"
onClick={() => {
executeEvent('openDirectMessageInternal', {
address,
name,
});
handleClose();
}}
sx={{
color: 'white'
}}
>
Message
</Button>
{/* Option 2: Send QORT */}
<Button
variant="text"
onClick={() => {
executeEvent('openPaymentInternal', {
address,
name,
});
handleClose();
}}
sx={{
color: 'white'
}}
>
Send QORT
</Button>
</Box>
</Popover>
</>
);
};

View File

@ -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;
}

View File

@ -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
}
}
}

View File

@ -0,0 +1,5 @@
export const getRootHeight = ()=> {
return document?.getElementById('root')?.style?.height || '100%'
}