mirror of
https://github.com/Qortal/chrome-extension.git
synced 2025-02-11 17:55:49 +00:00
Merge branch 'feature/public'
This commit is contained in:
commit
273ebb4d18
26
package-lock.json
generated
26
package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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({
|
||||
|
@ -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;
|
||||
|
719
src/App.tsx
719
src/App.tsx
File diff suppressed because it is too large
Load Diff
@ -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>
|
||||
);
|
||||
|
14
src/assets/Icons/ArrowDownIcon.tsx
Normal file
14
src/assets/Icons/ArrowDownIcon.tsx
Normal 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>
|
||||
|
||||
|
||||
|
||||
|
||||
);
|
||||
};
|
||||
|
12
src/assets/Icons/ChatIcon.tsx
Normal file
12
src/assets/Icons/ChatIcon.tsx
Normal 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>
|
||||
|
||||
|
||||
);
|
||||
};
|
||||
|
13
src/assets/Icons/ExitIcon.tsx
Normal file
13
src/assets/Icons/ExitIcon.tsx
Normal 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>
|
||||
|
||||
|
||||
|
||||
);
|
||||
};
|
||||
|
12
src/assets/Icons/HomeIcon.tsx
Normal file
12
src/assets/Icons/HomeIcon.tsx
Normal 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>
|
||||
|
||||
|
||||
);
|
||||
};
|
||||
|
12
src/assets/Icons/HubsIcon.tsx
Normal file
12
src/assets/Icons/HubsIcon.tsx
Normal 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>
|
||||
|
||||
|
||||
);
|
||||
};
|
||||
|
12
src/assets/Icons/LogoutIcon.tsx
Normal file
12
src/assets/Icons/LogoutIcon.tsx
Normal 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>
|
||||
|
||||
|
||||
);
|
||||
};
|
||||
|
19
src/assets/Icons/MembersIcon.tsx
Normal file
19
src/assets/Icons/MembersIcon.tsx
Normal 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>
|
||||
|
||||
|
||||
|
||||
|
||||
);
|
||||
};
|
||||
|
13
src/assets/Icons/MessagingIcon.tsx
Normal file
13
src/assets/Icons/MessagingIcon.tsx
Normal 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>
|
||||
|
||||
|
||||
);
|
||||
};
|
||||
|
14
src/assets/Icons/MessagingIcon2.tsx
Normal file
14
src/assets/Icons/MessagingIcon2.tsx
Normal 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>
|
||||
|
||||
|
||||
|
||||
|
||||
);
|
||||
};
|
||||
|
14
src/assets/Icons/NotificationIcon.tsx
Normal file
14
src/assets/Icons/NotificationIcon.tsx
Normal 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>
|
||||
|
||||
|
||||
|
||||
);
|
||||
};
|
||||
|
11
src/assets/Icons/NotificationIcon2.tsx
Normal file
11
src/assets/Icons/NotificationIcon2.tsx
Normal 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>
|
||||
|
||||
);
|
||||
};
|
||||
|
14
src/assets/Icons/ReturnIcon.tsx
Normal file
14
src/assets/Icons/ReturnIcon.tsx
Normal 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>
|
||||
|
||||
|
||||
|
||||
|
||||
);
|
||||
};
|
||||
|
13
src/assets/Icons/ThreadsIcon.tsx
Normal file
13
src/assets/Icons/ThreadsIcon.tsx
Normal 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>
|
||||
|
||||
|
||||
|
||||
);
|
||||
};
|
||||
|
11
src/assets/Icons/TradingIcon.tsx
Normal file
11
src/assets/Icons/TradingIcon.tsx
Normal 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>
|
||||
|
||||
);
|
||||
};
|
||||
|
12
src/assets/Icons/WalletIcon.tsx
Normal file
12
src/assets/Icons/WalletIcon.tsx
Normal 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>
|
||||
|
||||
);
|
||||
};
|
||||
|
9
src/assets/svgs/BottomLogo.svg
Normal file
9
src/assets/svgs/BottomLogo.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 561 KiB |
9
src/assets/svgs/BottomLogo2.svg
Normal file
9
src/assets/svgs/BottomLogo2.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 562 KiB |
9
src/assets/svgs/BottomLogo3.svg
Normal file
9
src/assets/svgs/BottomLogo3.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 562 KiB |
9
src/assets/svgs/BottomLogo4.svg
Normal file
9
src/assets/svgs/BottomLogo4.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 821 KiB |
9
src/assets/svgs/BottomLogo5.svg
Normal file
9
src/assets/svgs/BottomLogo5.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 508 KiB |
3
src/assets/svgs/Forum.svg
Normal file
3
src/assets/svgs/Forum.svg
Normal 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 |
3197
src/background.ts
3197
src/background.ts
File diff suppressed because it is too large
Load Diff
17
src/common/CustomSvg.tsx
Normal file
17
src/common/CustomSvg.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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%',
|
||||
|
@ -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>
|
||||
|
||||
);
|
||||
|
@ -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."
|
||||
|
@ -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} /> */}
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
@ -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;
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
@ -118,4 +118,8 @@
|
||||
.tiptap img {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.isReply p {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
165
src/components/ContextMenu.tsx
Normal file
165
src/components/ContextMenu.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
|
||||
|
118
src/components/Desktop/DesktopFooter.tsx
Normal file
118
src/components/Desktop/DesktopFooter.tsx
Normal 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>
|
||||
);
|
||||
};
|
280
src/components/Desktop/DesktopHeader.tsx
Normal file
280
src/components/Desktop/DesktopHeader.tsx
Normal 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>
|
||||
);
|
||||
};
|
31
src/components/Drawer/Drawer.tsx
Normal file
31
src/components/Drawer/Drawer.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -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 && (
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
|
@ -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}>
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
|
@ -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
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
200
src/components/Group/GroupMenu.tsx
Normal file
200
src/components/Group/GroupMenu.tsx
Normal 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>
|
||||
);
|
||||
};
|
110
src/components/Group/Home.tsx
Normal file
110
src/components/Group/Home.tsx
Normal 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>
|
||||
);
|
||||
};
|
150
src/components/Group/HomeDesktop.tsx
Normal file
150
src/components/Group/HomeDesktop.tsx
Normal 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>
|
||||
);
|
||||
};
|
@ -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: {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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} />
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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} />
|
||||
|
186
src/components/Mobile/MobileFooter.tsx
Normal file
186
src/components/Mobile/MobileFooter.tsx
Normal 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>
|
||||
);
|
||||
};
|
444
src/components/Mobile/MobileHeader.tsx
Normal file
444
src/components/Mobile/MobileHeader.tsx
Normal 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;
|
108
src/components/WrapperUserAction.tsx
Normal file
108
src/components/WrapperUserAction.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
};
|
@ -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;
|
||||
}
|
42
src/transactions/TradeBotRespondMultipleRequest.ts
Normal file
42
src/transactions/TradeBotRespondMultipleRequest.ts
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
5
src/utils/mobile/mobileUtils.ts
Normal file
5
src/utils/mobile/mobileUtils.ts
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
export const getRootHeight = ()=> {
|
||||
|
||||
return document?.getElementById('root')?.style?.height || '100%'
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user