Add comment TODO, format code and remove unused import

This commit is contained in:
Nicola Benaglia 2025-04-23 20:49:30 +02:00
parent 1a0ccbc08d
commit d796a3f7c0
32 changed files with 1689 additions and 1460 deletions

View File

@ -91,7 +91,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
if (!name) throw new Error('Please provide a name'); if (!name) throw new Error('Please provide a name');
if (!description) throw new Error('Please provide a description'); if (!description) throw new Error('Please provide a description');
const fee = await getFee('CREATE_GROUP'); const fee = await getFee('CREATE_GROUP'); // TODO translate
await show({ await show({
message: 'Would you like to perform an CREATE_GROUP transaction?', message: 'Would you like to perform an CREATE_GROUP transaction?',
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',

View File

@ -101,7 +101,7 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
const handleJoinGroup = async (group, isOpen) => { const handleJoinGroup = async (group, isOpen) => {
try { try {
const groupId = group.groupId; const groupId = group.groupId;
const fee = await getFee('JOIN_GROUP'); const fee = await getFee('JOIN_GROUP'); // TODO translate
await show({ await show({
message: 'Would you like to perform an JOIN_GROUP transaction?', message: 'Would you like to perform an JOIN_GROUP transaction?',
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',

View File

@ -129,7 +129,7 @@ export const BlockedUsersModal = () => {
executeEvent('updateChatMessagesWithBlocks', true); executeEvent('updateChatMessagesWithBlocks', true);
} }
} catch (error) { } catch (error) {
setOpenSnackGlobal(true); setOpenSnackGlobal(true); // TODO translate
setInfoSnackCustom({ setInfoSnackCustom({
type: 'error', type: 'error',
message: error?.message || 'Unable to block user', message: error?.message || 'Unable to block user',

View File

@ -1,21 +1,20 @@
import { useMemo } from "react"; import { useMemo } from 'react';
import DOMPurify from "dompurify"; import DOMPurify from 'dompurify';
import "react-quill/dist/quill.snow.css"; import 'react-quill/dist/quill.snow.css';
import "react-quill/dist/quill.core.css"; import 'react-quill/dist/quill.core.css';
import "react-quill/dist/quill.bubble.css"; import 'react-quill/dist/quill.bubble.css';
import { Box, styled } from "@mui/material"; import { Box, styled } from '@mui/material';
import { convertQortalLinks } from "../../../utils/qortalLink"; import { convertQortalLinks } from '../../../utils/qortalLink';
const CrowdfundInlineContent = styled(Box)(({ theme }) => ({ const CrowdfundInlineContent = styled(Box)(({ theme }) => ({
display: "flex", display: 'flex',
fontFamily: "Mulish", fontFamily: 'Mulish',
fontSize: "19px", fontSize: '19px',
fontWeight: 400, fontWeight: 400,
letterSpacing: 0, letterSpacing: 0,
color: theme.palette.text.primary, color: theme.palette.text.primary,
width: '100%' width: '100%',
})); }));
export const DisplayHtml = ({ html, textColor }: any) => { export const DisplayHtml = ({ html, textColor }: any) => {
const cleanContent = useMemo(() => { const cleanContent = useMemo(() => {
@ -29,6 +28,7 @@ export const DisplayHtml = ({ html, textColor }: any) => {
}, [html]); }, [html]);
if (!cleanContent) return null; if (!cleanContent) return null;
return ( return (
<CrowdfundInlineContent> <CrowdfundInlineContent>
<div <div
@ -36,7 +36,7 @@ export const DisplayHtml = ({ html, textColor }: any) => {
style={{ style={{
color: textColor || 'white', color: textColor || 'white',
fontWeight: 400, fontWeight: 400,
fontSize: '16px' fontSize: '16px',
}} }}
dangerouslySetInnerHTML={{ __html: cleanContent }} dangerouslySetInnerHTML={{ __html: cleanContent }}
/> />

View File

@ -45,7 +45,6 @@ import { formatDate, formatTimestamp } from '../../../utils/time';
import LazyLoad from '../../../common/LazyLoad'; import LazyLoad from '../../../common/LazyLoad';
import { delay } from '../../../utils/helpers'; import { delay } from '../../../utils/helpers';
import { NewThread } from './NewThread'; import { NewThread } from './NewThread';
import { getBaseApi } from '../../../background';
import { import {
decryptPublishes, decryptPublishes,
getTempPublish, getTempPublish,
@ -55,15 +54,11 @@ import CheckSVG from '../../../assets/svgs/Check.svg';
import SortSVG from '../../../assets/svgs/Sort.svg'; import SortSVG from '../../../assets/svgs/Sort.svg';
import ArrowDownSVG from '../../../assets/svgs/ArrowDown.svg'; import ArrowDownSVG from '../../../assets/svgs/ArrowDown.svg';
import { LoadingSnackbar } from '../../Snackbar/LoadingSnackbar'; import { LoadingSnackbar } from '../../Snackbar/LoadingSnackbar';
import { import { executeEvent } from '../../../utils/events';
executeEvent,
subscribeToEvent,
unsubscribeFromEvent,
} from '../../../utils/events';
import RefreshIcon from '@mui/icons-material/Refresh'; import RefreshIcon from '@mui/icons-material/Refresh';
import { getArbitraryEndpointReact, getBaseApiReact } from '../../../App'; import { getArbitraryEndpointReact, getBaseApiReact } from '../../../App';
import { WrapperUserAction } from '../../WrapperUserAction';
import { addDataPublishesFunc, getDataPublishesFunc } from '../Group'; import { addDataPublishesFunc, getDataPublishesFunc } from '../Group';
const filterOptions = ['Recently active', 'Newest', 'Oldest']; const filterOptions = ['Recently active', 'Newest', 'Oldest'];
export const threadIdentifier = 'DOCUMENT'; export const threadIdentifier = 'DOCUMENT';
@ -183,7 +178,7 @@ export const GroupMail = ({
}); });
}); });
} catch (error) { } catch (error) {
} finally { console.log(error);
} }
}; };
@ -266,7 +261,6 @@ export const GroupMail = ({
} else { } else {
sorted = fullArrayMsg.sort((a: any, b: any) => a.created - b.created); sorted = fullArrayMsg.sort((a: any, b: any) => a.created - b.created);
} }
setAllThreads(sorted); setAllThreads(sorted);
} catch (error) { } catch (error) {
console.log({ error }); console.log({ error });
@ -376,6 +370,7 @@ export const GroupMail = ({
); );
setRecentThreads(sorted); setRecentThreads(sorted);
} catch (error) { } catch (error) {
console.log(error);
} finally { } finally {
setIsLoading(false); setIsLoading(false);
// dispatch(setIsLoadingCustom(null)); // dispatch(setIsLoadingCustom(null));
@ -444,7 +439,6 @@ export const GroupMail = ({
} }
} }
} }
setMembers(members); setMembers(members);
} catch (error) { } catch (error) {
console.log({ error }); console.log({ error });
@ -754,7 +748,9 @@ export const GroupMail = ({
> >
{thread?.threadData?.title} {thread?.threadData?.title}
</ThreadSingleTitle> </ThreadSingleTitle>
<Spacer height="10px" /> <Spacer height="10px" />
{filterMode === 'Recently active' && ( {filterMode === 'Recently active' && (
<div <div
style={{ style={{
@ -799,7 +795,7 @@ export const GroupMail = ({
sx={{ sx={{
color: 'white', color: 'white',
fontSize: '12px', fontSize: '12px',
}} }} // TODO translate
> >
Last page Last page
</Typography> </Typography>

View File

@ -1,52 +1,47 @@
import { import { Typography, Box, TextField } from '@mui/material';
AppBar, import { styled } from '@mui/system';
Button,
Toolbar,
Typography,
Box,
TextField,
} from "@mui/material";
import { styled } from "@mui/system";
export const InstanceContainer = styled(Box)(({ theme }) => ({ export const InstanceContainer = styled(Box)(({ theme }) => ({
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
width: "100%", width: '100%',
backgroundColor: "var(--color-instance)", backgroundColor: 'var(--color-instance)',
height: "59px", height: '59px',
flexShrink: 0, flexShrink: 0,
justifyContent: "space-between", justifyContent: 'space-between',
})); }));
export const MailContainer = styled(Box)(({ theme }) => ({ export const MailContainer = styled(Box)(({ theme }) => ({
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
width: "100%", width: '100%',
height: "calc(100vh - 78px)", height: 'calc(100vh - 78px)',
overflow: "hidden", overflow: 'hidden',
})); }));
export const MailBody = styled(Box)(({ theme }) => ({ export const MailBody = styled(Box)(({ theme }) => ({
display: "flex", display: 'flex',
flexDirection: "row", flexDirection: 'row',
width: "100%", width: '100%',
height: "calc(100% - 59px)", height: 'calc(100% - 59px)',
// overflow: 'auto !important'
})); }));
export const MailBodyInner = styled(Box)(({ theme }) => ({ export const MailBodyInner = styled(Box)(({ theme }) => ({
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
width: "50%", width: '50%',
height: "100%", height: '100%',
})); }));
export const MailBodyInnerHeader = styled(Box)(({ theme }) => ({ export const MailBodyInnerHeader = styled(Box)(({ theme }) => ({
display: "flex", display: 'flex',
width: "100%", width: '100%',
height: "25px", height: '25px',
marginTop: "50px", marginTop: '50px',
marginBottom: "35px", marginBottom: '35px',
justifyContent: "center", justifyContent: 'center',
alignItems: "center", alignItems: 'center',
gap: "11px", gap: '11px',
})); }));
export const MailBodyInnerScroll = styled(Box)` export const MailBodyInnerScroll = styled(Box)`
@ -84,163 +79,174 @@ export const MailBodyInnerScroll = styled(Box)`
`; `;
export const ComposeContainer = styled(Box)(({ theme }) => ({ export const ComposeContainer = styled(Box)(({ theme }) => ({
display: "flex", display: 'flex',
width: "150px", width: '150px',
alignItems: "center", alignItems: 'center',
gap: "7px", gap: '7px',
height: "100%", height: '100%',
cursor: "pointer", cursor: 'pointer',
transition: "0.2s background-color", transition: '0.2s background-color',
justifyContent: "center", justifyContent: 'center',
"&:hover": { '&:hover': {
backgroundColor: "rgba(67, 68, 72, 1)", backgroundColor: 'rgba(67, 68, 72, 1)',
}, },
})); }));
export const ComposeContainerBlank = styled(Box)(({ theme }) => ({ export const ComposeContainerBlank = styled(Box)(({ theme }) => ({
display: "flex", display: 'flex',
width: "150px", width: '150px',
alignItems: "center", alignItems: 'center',
gap: "7px", gap: '7px',
height: "100%", height: '100%',
})); }));
export const ComposeP = styled(Typography)(({ theme }) => ({ export const ComposeP = styled(Typography)(({ theme }) => ({
fontSize: "15px", fontSize: '15px',
fontWeight: 500, fontWeight: 500,
})); }));
export const ComposeIcon = styled("img")({ export const ComposeIcon = styled('img')({
width: "auto", width: 'auto',
height: "auto", height: 'auto',
userSelect: "none", userSelect: 'none',
objectFit: "contain", objectFit: 'contain',
cursor: "pointer", cursor: 'pointer',
});
export const ArrowDownIcon = styled("img")({
width: "auto",
height: "auto",
userSelect: "none",
objectFit: "contain",
cursor: "pointer",
});
export const MailIconImg = styled("img")({
width: "auto",
height: "auto",
userSelect: "none",
objectFit: "contain",
}); });
export const MailMessageRowInfoImg = styled("img")({ export const ArrowDownIcon = styled('img')({
width: "auto", width: 'auto',
height: "auto", height: 'auto',
userSelect: "none", userSelect: 'none',
objectFit: "contain", objectFit: 'contain',
cursor: 'pointer',
});
export const MailIconImg = styled('img')({
width: 'auto',
height: 'auto',
userSelect: 'none',
objectFit: 'contain',
});
export const MailMessageRowInfoImg = styled('img')({
width: 'auto',
height: 'auto',
userSelect: 'none',
objectFit: 'contain',
}); });
export const SelectInstanceContainer = styled(Box)(({ theme }) => ({ export const SelectInstanceContainer = styled(Box)(({ theme }) => ({
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
gap: "17px", gap: '17px',
})); }));
export const SelectInstanceContainerInner = styled(Box)(({ theme }) => ({ export const SelectInstanceContainerInner = styled(Box)(({ theme }) => ({
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
gap: "3px", gap: '3px',
cursor: "pointer", cursor: 'pointer',
padding: "8px", padding: '8px',
transition: "all 0.2s", transition: 'all 0.2s',
"&:hover": { '&:hover': {
borderRadius: "8px", borderRadius: '8px',
background: "#434448", background: '#434448',
}, },
})); }));
export const SelectInstanceContainerFilterInner = styled(Box)(({ theme }) => ({ export const SelectInstanceContainerFilterInner = styled(Box)(({ theme }) => ({
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
gap: "3px", gap: '3px',
cursor: "pointer", cursor: 'pointer',
padding: "8px", padding: '8px',
transition: "all 0.2s" transition: 'all 0.2s',
})); }));
export const InstanceLabel = styled(Typography)(({ theme }) => ({ export const InstanceLabel = styled(Typography)(({ theme }) => ({
fontSize: "16px", fontSize: '16px',
fontWeight: 500, fontWeight: 500,
color: "#FFFFFF33", color: '#FFFFFF33',
})); }));
export const InstanceP = styled(Typography)(({ theme }) => ({ export const InstanceP = styled(Typography)(({ theme }) => ({
fontSize: "16px", fontSize: '16px',
fontWeight: 500, fontWeight: 500,
})); }));
export const MailMessageRowContainer = styled(Box)(({ theme }) => ({ export const MailMessageRowContainer = styled(Box)(({ theme }) => ({
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
cursor: "pointer", cursor: 'pointer',
justifyContent: "space-between", justifyContent: 'space-between',
borderRadius: "56px 5px 10px 56px", borderRadius: '56px 5px 10px 56px',
paddingRight: "15px", paddingRight: '15px',
transition: "background 0.2s", transition: 'background 0.2s',
gap: "10px", gap: '10px',
"&:hover": { '&:hover': {
background: "#434448", background: '#434448',
}, },
})); }));
export const MailMessageRowProfile = styled(Box)(({ theme }) => ({ export const MailMessageRowProfile = styled(Box)(({ theme }) => ({
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
cursor: "pointer", cursor: 'pointer',
justifyContent: "flex-start", justifyContent: 'flex-start',
gap: "10px", gap: '10px',
width: "50%", width: '50%',
overflow: "hidden", overflow: 'hidden',
})); }));
export const MailMessageRowInfo = styled(Box)(({ theme }) => ({ export const MailMessageRowInfo = styled(Box)(({ theme }) => ({
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
cursor: "pointer", cursor: 'pointer',
justifyContent: "flex-start", justifyContent: 'flex-start',
gap: "7px", gap: '7px',
width: "50%", width: '50%',
})); }));
export const MailMessageRowInfoStatusNotDecrypted = styled(Typography)( export const MailMessageRowInfoStatusNotDecrypted = styled(Typography)(
({ theme }) => ({ ({ theme }) => ({
fontSize: "16px", fontSize: '16px',
fontWeight: 900, fontWeight: 900,
textTransform: "uppercase", textTransform: 'uppercase',
paddingTop: "2px", paddingTop: '2px',
}) })
); );
export const MailMessageRowInfoStatusRead = styled(Typography)(({ theme }) => ({ export const MailMessageRowInfoStatusRead = styled(Typography)(({ theme }) => ({
fontSize: "16px", fontSize: '16px',
fontWeight: 300, fontWeight: 300,
})); }));
export const MessageExtraInfo = styled(Box)(({ theme }) => ({ export const MessageExtraInfo = styled(Box)(({ theme }) => ({
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
gap: "2px", gap: '2px',
overflow: "hidden", overflow: 'hidden',
})); }));
export const MessageExtraName = styled(Typography)(({ theme }) => ({ export const MessageExtraName = styled(Typography)(({ theme }) => ({
fontSize: "16px", fontSize: '16px',
fontWeight: 900, fontWeight: 900,
whiteSpace: "nowrap", whiteSpace: 'nowrap',
textOverflow: "ellipsis", textOverflow: 'ellipsis',
overflow: "hidden", overflow: 'hidden',
})); }));
export const MessageExtraDate = styled(Typography)(({ theme }) => ({ export const MessageExtraDate = styled(Typography)(({ theme }) => ({
fontSize: "15px", fontSize: '15px',
fontWeight: 500, fontWeight: 500,
})); }));
export const MessagesContainer = styled(Box)(({ theme }) => ({ export const MessagesContainer = styled(Box)(({ theme }) => ({
width: "460px", width: '460px',
maxWidth: "90%", maxWidth: '90%',
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
gap: "12px", gap: '12px',
})); }));
export const InstanceListParent = styled(Box)` export const InstanceListParent = styled(Box)`
@ -254,18 +260,21 @@ export const InstanceListParent = styled(Box)`
background-color: var(--color-instance-popover-bg); background-color: var(--color-instance-popover-bg);
border: 1px solid rgba(0, 0, 0, 0.1); border: 1px solid rgba(0, 0, 0, 0.1);
`; `;
export const InstanceListHeader = styled(Box)` export const InstanceListHeader = styled(Box)`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
background-color: var(--color-instance-popover-bg); background-color: var(--color-instance-popover-bg);
`; `;
export const InstanceFooter = styled(Box)` export const InstanceFooter = styled(Box)`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
flex-shrink: 0; flex-shrink: 0;
`; `;
export const InstanceListContainer = styled(Box)` export const InstanceListContainer = styled(Box)`
width: 100%; width: 100%;
display: flex; display: flex;
@ -301,126 +310,132 @@ export const InstanceListContainer = styled(Box)`
} }
} }
`; `;
export const InstanceListContainerRowLabelContainer = styled(Box)( export const InstanceListContainerRowLabelContainer = styled(Box)(
({ theme }) => ({ ({ theme }) => ({
width: "100%", width: '100%',
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
gap: "10px", gap: '10px',
height: "50px", height: '50px',
}) })
); );
export const InstanceListContainerRow = styled(Box)(({ theme }) => ({ export const InstanceListContainerRow = styled(Box)(({ theme }) => ({
width: "100%", width: '100%',
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
gap: "10px", gap: '10px',
height: "50px", height: '50px',
cursor: "pointer", cursor: 'pointer',
transition: "0.2s background", transition: '0.2s background',
"&:hover": { '&:hover': {
background: "rgba(67, 68, 72, 1)", background: 'rgba(67, 68, 72, 1)',
}, },
flexShrink: 0, flexShrink: 0,
})); }));
export const InstanceListContainerRowCheck = styled(Box)(({ theme }) => ({ export const InstanceListContainerRowCheck = styled(Box)(({ theme }) => ({
width: "47px", width: '47px',
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
justifyContent: "center", justifyContent: 'center',
})); }));
export const InstanceListContainerRowMain = styled(Box)(({ theme }) => ({ export const InstanceListContainerRowMain = styled(Box)(({ theme }) => ({
display: "flex", display: 'flex',
justifyContent: "space-between", justifyContent: 'space-between',
width: "100%", width: '100%',
alignItems: "center", alignItems: 'center',
paddingRight: "30px", paddingRight: '30px',
overflow: "hidden", overflow: 'hidden',
})); }));
export const CloseParent = styled(Box)(({ theme }) => ({ export const CloseParent = styled(Box)(({ theme }) => ({
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
gap: "20px", gap: '20px',
})); }));
export const InstanceListContainerRowMainP = styled(Typography)( export const InstanceListContainerRowMainP = styled(Typography)(
({ theme }) => ({ ({ theme }) => ({
fontWeight: 500, fontWeight: 500,
fontSize: "16px", fontSize: '16px',
textOverflow: "ellipsis", textOverflow: 'ellipsis',
overflow: "hidden", overflow: 'hidden',
}) })
); );
export const InstanceListContainerRowCheckIcon = styled("img")({ export const InstanceListContainerRowCheckIcon = styled('img')({
width: "auto", width: 'auto',
height: "auto", height: 'auto',
userSelect: "none", userSelect: 'none',
objectFit: "contain", objectFit: 'contain',
}); });
export const InstanceListContainerRowGroupIcon = styled("img")({ export const InstanceListContainerRowGroupIcon = styled('img')({
width: "auto", width: 'auto',
height: "auto", height: 'auto',
userSelect: "none", userSelect: 'none',
objectFit: "contain", objectFit: 'contain',
}); });
export const TypeInAliasTextfield = styled(TextField)({ export const TypeInAliasTextfield = styled(TextField)({
width: "340px", // Adjust the width as needed width: '340px', // Adjust the width as needed
borderRadius: "5px", borderRadius: '5px',
backgroundColor: "rgba(30, 30, 32, 1)", backgroundColor: 'rgba(30, 30, 32, 1)',
border: "none", border: 'none',
outline: "none", outline: 'none',
input: { input: {
fontSize: 16, fontSize: 16,
color: "white", color: 'white',
"&::placeholder": { '&::placeholder': {
fontSize: 16, fontSize: 16,
color: "rgba(255, 255, 255, 0.2)", color: 'rgba(255, 255, 255, 0.2)',
}, },
border: "none", border: 'none',
outline: "none", outline: 'none',
padding: "10px", padding: '10px',
}, },
"& .MuiOutlinedInput-root": { '& .MuiOutlinedInput-root': {
"& fieldset": { '& fieldset': {
border: "none", border: 'none',
}, },
"&:hover fieldset": { '&:hover fieldset': {
border: "none", border: 'none',
}, },
"&.Mui-focused fieldset": { '&.Mui-focused fieldset': {
border: "none", border: 'none',
}, },
}, },
"& .MuiInput-underline:before": { '& .MuiInput-underline:before': {
borderBottom: "none", borderBottom: 'none',
}, },
"& .MuiInput-underline:hover:not(.Mui-disabled):before": { '& .MuiInput-underline:hover:not(.Mui-disabled):before': {
borderBottom: "none", borderBottom: 'none',
}, },
"& .MuiInput-underline:after": { '& .MuiInput-underline:after': {
borderBottom: "none", borderBottom: 'none',
}, },
}); });
export const NewMessageCloseImg = styled("img")({ export const NewMessageCloseImg = styled('img')({
width: "auto", width: 'auto',
height: "auto", height: 'auto',
userSelect: "none", userSelect: 'none',
objectFit: "contain", objectFit: 'contain',
cursor: "pointer", cursor: 'pointer',
}); });
export const NewMessageHeaderP = styled(Typography)(({ theme }) => ({ export const NewMessageHeaderP = styled(Typography)(({ theme }) => ({
fontSize: "18px", fontSize: '18px',
fontWeight: 600, fontWeight: 600,
})); }));
export const NewMessageInputRow = styled(Box)(({ theme }) => ({ export const NewMessageInputRow = styled(Box)(({ theme }) => ({
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
justifyContent: "space-between", justifyContent: 'space-between',
borderBottom: "3px solid rgba(237, 239, 241, 1)", borderBottom: '3px solid rgba(237, 239, 241, 1)',
width: "100%", width: '100%',
paddingBottom: "6px", paddingBottom: '6px',
})); }));
export const NewMessageInputLabelP = styled(Typography)` export const NewMessageInputLabelP = styled(Typography)`
color: rgba(84, 84, 84, 0.7); color: rgba(84, 84, 84, 0.7);
@ -444,25 +459,25 @@ export const AliasLabelP = styled(Typography)`
} }
`; `;
export const NewMessageAliasContainer = styled(Box)(({ theme }) => ({ export const NewMessageAliasContainer = styled(Box)(({ theme }) => ({
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
gap: "12px", gap: '12px',
})); }));
export const AttachmentContainer = styled(Box)(({ theme }) => ({ export const AttachmentContainer = styled(Box)(({ theme }) => ({
height: "36px", height: '36px',
width: "100%", width: '100%',
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
})); }));
export const NewMessageAttachmentImg = styled("img")({ export const NewMessageAttachmentImg = styled('img')({
width: "auto", width: 'auto',
height: "auto", height: 'auto',
userSelect: "none", userSelect: 'none',
objectFit: "contain", objectFit: 'contain',
cursor: "pointer", cursor: 'pointer',
padding: "10px", padding: '10px',
border: "1px dashed #646464", border: '1px dashed #646464',
}); });
export const NewMessageSendButton = styled(Box)` export const NewMessageSendButton = styled(Box)`
@ -582,34 +597,36 @@ export const ShowMessageButtonP = styled(Typography)`
color: white; color: white;
`; `;
export const ShowMessageButtonImg = styled("img")({ export const ShowMessageButtonImg = styled('img')({
width: "auto", width: 'auto',
height: "auto", height: 'auto',
userSelect: "none", userSelect: 'none',
objectFit: "contain", objectFit: 'contain',
cursor: "pointer", cursor: 'pointer',
}); });
export const MailAttachmentImg = styled("img")({ export const MailAttachmentImg = styled('img')({
width: "auto", width: 'auto',
height: "auto", height: 'auto',
userSelect: "none", userSelect: 'none',
objectFit: "contain", objectFit: 'contain',
}); });
export const AliasAvatarImg = styled("img")({
width: "auto", export const AliasAvatarImg = styled('img')({
height: "auto", width: 'auto',
userSelect: "none", height: 'auto',
objectFit: "contain", userSelect: 'none',
objectFit: 'contain',
}); });
export const MoreImg = styled("img")({
width: "auto", export const MoreImg = styled('img')({
height: "auto", width: 'auto',
userSelect: "none", height: 'auto',
objectFit: "contain", userSelect: 'none',
transition: "0.2s all", objectFit: 'contain',
"&:hover": { transition: '0.2s all',
transform: "scale(1.3)", '&:hover': {
transform: 'scale(1.3)',
}, },
}); });
@ -625,17 +642,19 @@ export const MoreP = styled(Typography)`
letter-spacing: -0.16px; letter-spacing: -0.16px;
white-space: nowrap; white-space: nowrap;
`; `;
export const ThreadContainerFullWidth = styled(Box)(({ theme }) => ({ export const ThreadContainerFullWidth = styled(Box)(({ theme }) => ({
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
width: "100%", width: '100%',
alignItems: "center", alignItems: 'center',
})); }));
export const ThreadContainer = styled(Box)(({ theme }) => ({ export const ThreadContainer = styled(Box)(({ theme }) => ({
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
width: "100%", width: '100%',
maxWidth: "95%", maxWidth: '95%',
})); }));
export const GroupNameP = styled(Typography)` export const GroupNameP = styled(Typography)`
@ -648,130 +667,131 @@ export const GroupNameP = styled(Typography)`
`; `;
export const AllThreadP = styled(Typography)` export const AllThreadP = styled(Typography)`
color: #FFF; color: #fff;
font-size: 20px; font-size: 20px;
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
line-height: 120%; /* 24px */ line-height: 120%; /* 24px */
letter-spacing: 0.15px; letter-spacing: 0.15px;
`; `;
export const SingleThreadParent = styled(Box)` export const SingleThreadParent = styled(Box)`
border-radius: 35px 4px 4px 35px; border-radius: 35px 4px 4px 35px;
position: relative; position: relative;
background: #434448; background: #434448;
display: flex; display: flex;
padding: 13px; padding: 13px;
cursor: pointer; cursor: pointer;
margin-bottom: 5px; margin-bottom: 5px;
height: 76px; height: 76px;
align-items:center; align-items: center;
transition: 0.2s all; transition: 0.2s all;
&:hover { &:hover {
background: rgba(255, 255, 255, 0.20) background: rgba(255, 255, 255, 0.2);
} }
`; `;
export const SingleTheadMessageParent = styled(Box)`
border-radius: 35px 4px 4px 35px;
background: #434448;
display: flex;
padding: 13px;
cursor: pointer;
margin-bottom: 5px;
height: 76px;
align-items:center;
export const SingleTheadMessageParent = styled(Box)`
border-radius: 35px 4px 4px 35px;
background: #434448;
display: flex;
padding: 13px;
cursor: pointer;
margin-bottom: 5px;
height: 76px;
align-items: center;
`; `;
export const ThreadInfoColumn = styled(Box)(({ theme }) => ({ export const ThreadInfoColumn = styled(Box)(({ theme }) => ({
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
width: "170px", width: '170px',
gap: '2px', gap: '2px',
marginLeft: '10px', marginLeft: '10px',
height: '100%', height: '100%',
justifyContent: 'center' justifyContent: 'center',
})); }));
export const ThreadInfoColumnNameP = styled(Typography)` export const ThreadInfoColumnNameP = styled(Typography)`
color: #FFF; color: #fff;
font-family: Roboto; font-family: Roboto;
font-size: 16px; font-size: 16px;
font-style: normal; font-style: normal;
font-weight: 900; font-weight: 900;
line-height: normal; line-height: normal;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
`; `;
export const ThreadInfoColumnbyP = styled('span')` export const ThreadInfoColumnbyP = styled('span')`
color: rgba(255, 255, 255, 0.80); color: rgba(255, 255, 255, 0.8);
font-family: Roboto; font-family: Roboto;
font-size: 16px; font-size: 16px;
font-style: normal; font-style: normal;
font-weight: 500; font-weight: 500;
line-height: normal; line-height: normal;
`; `;
export const ThreadInfoColumnTime = styled(Typography)` export const ThreadInfoColumnTime = styled(Typography)`
color: rgba(255, 255, 255, 0.80); color: rgba(255, 255, 255, 0.8);
font-family: Roboto; font-family: Roboto;
font-size: 15px; font-size: 15px;
font-style: normal; font-style: normal;
font-weight: 500; font-weight: 500;
line-height: normal; line-height: normal;
` `;
export const ThreadSingleTitle = styled(Typography)` export const ThreadSingleTitle = styled(Typography)`
color: #FFF; color: #fff;
font-family: Roboto; font-family: Roboto;
font-size: 23px; font-size: 23px;
font-style: normal; font-style: normal;
font-weight: 700; font-weight: 700;
line-height: normal; line-height: normal;
white-space: wrap; white-space: wrap;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
` `;
export const ThreadSingleLastMessageP = styled(Typography)` export const ThreadSingleLastMessageP = styled(Typography)`
color: #FFF; color: #fff;
font-family: Roboto; font-family: Roboto;
font-size: 12px; font-size: 12px;
font-style: normal; font-style: normal;
font-weight: 600; font-weight: 600;
line-height: normal; line-height: normal;
` `;
export const ThreadSingleLastMessageSpanP = styled('span')` export const ThreadSingleLastMessageSpanP = styled('span')`
color: #FFF; color: #fff;
font-family: Roboto; font-family: Roboto;
font-size: 12px; font-size: 12px;
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
line-height: normal; line-height: normal;
`; `;
export const GroupContainer = styled(Box)` export const GroupContainer = styled(Box)`
position: relative; position: relative;
overflow: auto; overflow: auto;
width: 100%; width: 100%;
`;
`
export const CloseContainer = styled(Box)(({ theme }) => ({ export const CloseContainer = styled(Box)(({ theme }) => ({
display: "flex", display: 'flex',
width: "50px", width: '50px',
overflow: "hidden", overflow: 'hidden',
alignItems: "center", alignItems: 'center',
cursor: "pointer", cursor: 'pointer',
transition: "0.2s background-color", transition: '0.2s background-color',
justifyContent: "center", justifyContent: 'center',
position: 'absolute', position: 'absolute',
top: '0px', top: '0px',
right: '0px', right: '0px',
height: '50px', height: '50px',
borderRadius: '0px 12px 0px 0px', borderRadius: '0px 12px 0px 0px',
"&:hover": { '&:hover': {
backgroundColor: "rgba(162, 31, 31, 1)", backgroundColor: 'rgba(162, 31, 31, 1)',
}, },
})); }));

View File

@ -191,7 +191,7 @@ export const NewThread = ({
} }
if (!groupInfo) { if (!groupInfo) {
errorMsg = 'Cannot access group information'; errorMsg = 'Cannot access group information';
} } // TODO translate
// if (!description) missingFields.push('subject') // if (!description) missingFields.push('subject')
if (missingFields.length > 0) { if (missingFields.length > 0) {

View File

@ -1,18 +1,24 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { createEditor} from 'slate'; import { createEditor } from 'slate';
import { withReact, Slate, Editable, RenderElementProps, RenderLeafProps } from 'slate-react'; import {
withReact,
Slate,
Editable,
RenderElementProps,
RenderLeafProps,
} from 'slate-react';
type ExtendedRenderElementProps = RenderElementProps & { mode?: string } type ExtendedRenderElementProps = RenderElementProps & { mode?: string };
export const renderElement = ({ export const renderElement = ({
attributes, attributes,
children, children,
element, element,
mode mode,
}: ExtendedRenderElementProps) => { }: ExtendedRenderElementProps) => {
switch (element.type) { switch (element.type) {
case 'block-quote': case 'block-quote':
return <blockquote {...attributes}>{children}</blockquote> return <blockquote {...attributes}>{children}</blockquote>;
case 'heading-2': case 'heading-2':
return ( return (
<h2 <h2
@ -22,7 +28,7 @@ export const renderElement = ({
> >
{children} {children}
</h2> </h2>
) );
case 'heading-3': case 'heading-3':
return ( return (
<h3 <h3
@ -32,21 +38,21 @@ export const renderElement = ({
> >
{children} {children}
</h3> </h3>
) );
case 'code-block': case 'code-block':
return ( return (
<pre {...attributes} className="code-block"> <pre {...attributes} className="code-block">
<code>{children}</code> <code>{children}</code>
</pre> </pre>
) );
case 'code-line': case 'code-line':
return <div {...attributes}>{children}</div> return <div {...attributes}>{children}</div>;
case 'link': case 'link':
return ( return (
<a href={element.url} {...attributes}> <a href={element.url} {...attributes}>
{children} {children}
</a> </a>
) );
default: default:
return ( return (
<p <p
@ -56,24 +62,23 @@ export const renderElement = ({
> >
{children} {children}
</p> </p>
) );
} }
} };
export const renderLeaf = ({ attributes, children, leaf }: RenderLeafProps) => { export const renderLeaf = ({ attributes, children, leaf }: RenderLeafProps) => {
let el = children let el = children;
if (leaf.bold) { if (leaf.bold) {
el = <strong>{el}</strong> el = <strong>{el}</strong>;
} }
if (leaf.italic) { if (leaf.italic) {
el = <em>{el}</em> el = <em>{el}</em>;
} }
if (leaf.underline) { if (leaf.underline) {
el = <u>{el}</u> el = <u>{el}</u>;
} }
if (leaf.link) { if (leaf.link) {
@ -81,39 +86,35 @@ export const renderLeaf = ({ attributes, children, leaf }: RenderLeafProps) => {
<a href={leaf.link} {...attributes}> <a href={leaf.link} {...attributes}>
{el} {el}
</a> </a>
) );
} }
return <span {...attributes}>{el}</span> return <span {...attributes}>{el}</span>;
} };
interface ReadOnlySlateProps { interface ReadOnlySlateProps {
content: any content: any;
mode?: string mode?: string;
} }
const ReadOnlySlate: React.FC<ReadOnlySlateProps> = ({ content, mode }) => { const ReadOnlySlate: React.FC<ReadOnlySlateProps> = ({ content, mode }) => {
const [load, setLoad] = useState(false) const [load, setLoad] = useState(false);
const editor = useMemo(() => withReact(createEditor()), []) const editor = useMemo(() => withReact(createEditor()), []);
const value = useMemo(() => content, [content]) const value = useMemo(() => content, [content]);
const performUpdate = useCallback(async()=> { const performUpdate = useCallback(async () => {
setLoad(true) setLoad(true);
await new Promise<void>((res)=> { await new Promise<void>((res) => {
setTimeout(() => { setTimeout(() => {
res() res();
}, 250); }, 250);
}) });
setLoad(false) setLoad(false);
}, []) }, []);
useEffect(()=> { useEffect(() => {
performUpdate();
}, [value]);
if (load) return null;
performUpdate()
}, [value])
if(load) return null
return ( return (
<Slate editor={editor} value={value} onChange={() => {}}> <Slate editor={editor} value={value} onChange={() => {}}>
@ -123,7 +124,7 @@ const ReadOnlySlate: React.FC<ReadOnlySlateProps> = ({ content, mode }) => {
renderLeaf={renderLeaf} renderLeaf={renderLeaf}
/> />
</Slate> </Slate>
) );
} };
export default ReadOnlySlate; export default ReadOnlySlate;

View File

@ -1,8 +1,8 @@
import React, { useState } from "react"; import { useState } from 'react';
import { Avatar, Box, IconButton } from "@mui/material"; import { Avatar, Box, IconButton } from '@mui/material';
import DOMPurify from "dompurify"; import DOMPurify from 'dompurify';
import FormatQuoteIcon from '@mui/icons-material/FormatQuote'; import FormatQuoteIcon from '@mui/icons-material/FormatQuote';
import MoreSVG from '../../../assets/svgs/More.svg' import MoreSVG from '../../../assets/svgs/More.svg';
import { import {
MoreImg, MoreImg,
@ -11,20 +11,18 @@ import {
ThreadInfoColumn, ThreadInfoColumn,
ThreadInfoColumnNameP, ThreadInfoColumnNameP,
ThreadInfoColumnTime, ThreadInfoColumnTime,
} from "./Mail-styles"; } from './Mail-styles';
import { Spacer } from "../../../common/Spacer"; import { Spacer } from '../../../common/Spacer';
import { DisplayHtml } from "./DisplayHtml"; import { formatTimestampForum } from '../../../utils/time';
import { formatTimestampForum } from "../../../utils/time"; import ReadOnlySlate from './ReadOnlySlate';
import ReadOnlySlate from "./ReadOnlySlate"; import { MessageDisplay } from '../../Chat/MessageDisplay';
import { MessageDisplay } from "../../Chat/MessageDisplay"; import { getBaseApiReact } from '../../../App';
import { getBaseApi } from "../../../background"; import { WrapperUserAction } from '../../WrapperUserAction';
import { getBaseApiReact } from "../../../App";
import { WrapperUserAction } from "../../WrapperUserAction";
export const ShowMessage = ({ message, openNewPostWithQuote, myName }: any) => { export const ShowMessage = ({ message, openNewPostWithQuote, myName }: any) => {
const [expandAttachments, setExpandAttachments] = useState<boolean>(false); const [expandAttachments, setExpandAttachments] = useState<boolean>(false);
let cleanHTML = ""; let cleanHTML = '';
if (message?.htmlContent) { if (message?.htmlContent) {
cleanHTML = DOMPurify.sanitize(message.htmlContent); cleanHTML = DOMPurify.sanitize(message.htmlContent);
} }
@ -32,79 +30,94 @@ export const ShowMessage = ({ message, openNewPostWithQuote, myName }: any) => {
return ( return (
<SingleTheadMessageParent <SingleTheadMessageParent
sx={{ sx={{
height: "auto", height: 'auto',
alignItems: "flex-start", alignItems: 'flex-start',
cursor: "default", cursor: 'default',
borderRadius: '35px 4px 4px 4px' borderRadius: '35px 4px 4px 4px',
}} }}
> >
<Box <Box
sx={{ sx={{
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
alignItems: "flex-start", alignItems: 'flex-start',
width: '100%' width: '100%',
}} }}
> >
<Box <Box
sx={{ sx={{
display: "flex", display: 'flex',
alignItems: "flex-start", alignItems: 'flex-start',
gap: "10px", gap: '10px',
}} }}
> >
<WrapperUserAction disabled={myName === message?.name} address={undefined} name={message?.name}> <WrapperUserAction
<Avatar sx={{ disabled={myName === message?.name}
height: '50px', address={undefined}
width: '50px' name={message?.name}
}} src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${message?.name}/qortal_avatar?async=true`} alt={message?.name}>{message?.name?.charAt(0)}</Avatar> >
</WrapperUserAction> <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> <ThreadInfoColumn>
<WrapperUserAction
<WrapperUserAction disabled={myName === message?.name} address={undefined} name={message?.name}> disabled={myName === message?.name}
<ThreadInfoColumnNameP>{message?.name}</ThreadInfoColumnNameP> address={undefined}
name={message?.name}
>
<ThreadInfoColumnNameP>{message?.name}</ThreadInfoColumnNameP>
</WrapperUserAction> </WrapperUserAction>
<ThreadInfoColumnTime> <ThreadInfoColumnTime>
{formatTimestampForum(message?.created)} {formatTimestampForum(message?.created)}
</ThreadInfoColumnTime> </ThreadInfoColumnTime>
</ThreadInfoColumn> </ThreadInfoColumn>
<div <div
style={{ style={{
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
justifyContent: "center", justifyContent: 'center',
}}
>
{message?.attachments?.length > 0 && (
<Box
sx={{
width: "100%",
marginTop: "10px",
}} }}
> >
{message?.attachments {message?.attachments?.length > 0 && (
.map((file: any, index: number) => { <Box
const isFirst = index === 0 sx={{
return ( width: '100%',
<Box marginTop: '10px',
sx={{ }}
display: expandAttachments ? "flex" : !expandAttachments && isFirst ? 'flex' : 'none', >
alignItems: "center", {message?.attachments.map((file: any, index: number) => {
justifyContent: "flex-start", const isFirst = index === 0;
width: "100%", return (
}}
>
<Box <Box
sx={{ sx={{
display: "flex", display: expandAttachments
alignItems: "center", ? 'flex'
gap: "5px", : !expandAttachments && isFirst
cursor: "pointer", ? 'flex'
width: "auto", : 'none',
alignItems: 'center',
justifyContent: 'flex-start',
width: '100%',
}} }}
> >
{/* <FileElement <Box
sx={{
display: 'flex',
alignItems: 'center',
gap: '5px',
cursor: 'pointer',
width: 'auto',
}}
>
{/* <FileElement
fileInfo={{ ...file, mimeTypeSaved: file?.type }} fileInfo={{ ...file, mimeTypeSaved: file?.type }}
title={file?.filename} title={file?.filename}
mode="mail" mode="mail"
@ -125,78 +138,87 @@ export const ShowMessage = ({ message, openNewPostWithQuote, myName }: any) => {
{file?.originalFilename || file?.filename} {file?.originalFilename || file?.filename}
</Typography> </Typography>
</FileElement> */} </FileElement> */}
{message?.attachments?.length > 1 && isFirst && ( {message?.attachments?.length > 1 && isFirst && (
<Box <Box
sx={{
display: "flex",
alignItems: "center",
gap: "5px",
}}
onClick={() => {
setExpandAttachments(prev => !prev);
}}
>
<MoreImg
sx={{ sx={{
marginLeft: "5px", display: 'flex',
transform: expandAttachments alignItems: 'center',
? "rotate(180deg)" gap: '5px',
: "unset",
}} }}
src={MoreSVG} onClick={() => {
/> setExpandAttachments((prev) => !prev);
<MoreP> }}
{expandAttachments ? 'hide' : `(${message?.attachments?.length - 1} more)`} >
<MoreImg
</MoreP> sx={{
</Box> marginLeft: '5px',
)} transform: expandAttachments
? 'rotate(180deg)'
: 'unset',
}}
src={MoreSVG}
/>
<MoreP>
{expandAttachments
? 'hide'
: `(${message?.attachments?.length - 1} more)`}
</MoreP>
</Box>
)}
</Box>
</Box> </Box>
</Box> );
); })}
}) </Box>
} )}
</Box> </div>
)}
</div>
</Box> </Box>
<Spacer height="20px" /> <Spacer height="20px" />
{message?.reply?.textContentV2 && ( {message?.reply?.textContentV2 && (
<> <>
<Box sx={{ <Box
width: '100%', sx={{
opacity: 0.7, width: '100%',
borderRadius: '5px', opacity: 0.7,
border: '1px solid gray', borderRadius: '5px',
boxSizing: 'border-box', border: '1px solid gray',
padding: '5px' boxSizing: 'border-box',
}}> padding: '5px',
}}
>
<Box <Box
sx={{ sx={{
display: "flex", display: 'flex',
alignItems: "flex-start", alignItems: 'flex-start',
gap: "10px", gap: '10px',
}}
}} >
> <Avatar
sx={{
<Avatar sx={{ height: '30px',
height: '30px', width: '30px',
width: '30px' }}
}} src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${message?.reply?.name}/qortal_avatar?async=true`} alt={message?.reply?.name}>{message?.reply?.name?.charAt(0)}</Avatar> src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${message?.reply?.name}/qortal_avatar?async=true`}
<ThreadInfoColumn> alt={message?.reply?.name}
<ThreadInfoColumnNameP sx={{ >
fontSize: '14px' {message?.reply?.name?.charAt(0)}
}}>{message?.reply?.name}</ThreadInfoColumnNameP> </Avatar>
<ThreadInfoColumn>
</ThreadInfoColumn> <ThreadInfoColumnNameP
</Box> sx={{
<MessageDisplay htmlContent={message?.reply?.textContentV2} /> fontSize: '14px',
</Box> }}
<Spacer height="20px" /> >
{message?.reply?.name}
</ThreadInfoColumnNameP>
</ThreadInfoColumn>
</Box>
<MessageDisplay htmlContent={message?.reply?.textContentV2} />
</Box>
<Spacer height="20px" />
</> </>
)} )}
{message?.textContent && ( {message?.textContent && (
@ -208,22 +230,18 @@ export const ShowMessage = ({ message, openNewPostWithQuote, myName }: any) => {
{message?.htmlContent && ( {message?.htmlContent && (
<div dangerouslySetInnerHTML={{ __html: cleanHTML }} /> <div dangerouslySetInnerHTML={{ __html: cleanHTML }} />
)} )}
<Box sx={{ <Box
width: '100%', sx={{
display: 'flex', width: '100%',
justifyContent: 'flex-end' display: 'flex',
}}> justifyContent: 'flex-end',
<IconButton }}
onClick={() => openNewPostWithQuote(message)}
> >
<FormatQuoteIcon /> <IconButton onClick={() => openNewPostWithQuote(message)}>
</IconButton> <FormatQuoteIcon />
</IconButton>
</Box> </Box>
</Box> </Box>
</SingleTheadMessageParent> </SingleTheadMessageParent>
); );
}; };

View File

@ -1,32 +1,33 @@
import React from "react"; import ReactQuill, { Quill } from 'react-quill';
import ReactQuill, { Quill } from "react-quill"; import 'react-quill/dist/quill.snow.css';
import "react-quill/dist/quill.snow.css"; import ImageResize from 'quill-image-resize-module-react';
import ImageResize from "quill-image-resize-module-react"; import './texteditor.css';
import './texteditor.css'
Quill.register("modules/imageResize", ImageResize); Quill.register('modules/imageResize', ImageResize);
const modules = { const modules = {
imageResize: { imageResize: {
parchment: Quill.import("parchment"), parchment: Quill.import('parchment'),
modules: ["Resize", "DisplaySize"], modules: ['Resize', 'DisplaySize'],
}, },
toolbar: [ toolbar: [
["bold", "italic", "underline", "strike"], // styled text ['bold', 'italic', 'underline', 'strike'], // styled text
["blockquote", "code-block"], // blocks ['blockquote', 'code-block'], // blocks
[{ header: 1 }, { header: 2 }], // custom button values [{ header: 1 }, { header: 2 }], // custom button values
[{ list: "ordered" }, { list: "bullet" }], // lists [{ list: 'ordered' }, { list: 'bullet' }], // lists
[{ script: "sub" }, { script: "super" }], // superscript/subscript [{ script: 'sub' }, { script: 'super' }], // superscript/subscript
[{ indent: "-1" }, { indent: "+1" }], // outdent/indent [{ indent: '-1' }, { indent: '+1' }], // outdent/indent
[{ direction: "rtl" }], // text direction [{ direction: 'rtl' }], // text direction
[{ size: ["small", false, "large", "huge"] }], // custom dropdown [{ size: ['small', false, 'large', 'huge'] }], // custom dropdown
[{ header: [1, 2, 3, 4, 5, 6, false] }], // custom button values [{ header: [1, 2, 3, 4, 5, 6, false] }], // custom button values
[{ color: [] }, { background: [] }], // dropdown with defaults [{ color: [] }, { background: [] }], // dropdown with defaults
[{ font: [] }], // font family [{ font: [] }], // font family
[{ align: [] }], // text align [{ align: [] }], // text align
["clean"], // remove formatting ['clean'], // remove formatting
// ["image"], // image // ["image"], // image
], ],
}; };
export const TextEditor = ({ inlineContent, setInlineContent }: any) => { export const TextEditor = ({ inlineContent, setInlineContent }: any) => {
return ( return (
<ReactQuill <ReactQuill

View File

@ -467,6 +467,7 @@ export const Thread = ({
} }
setMessages(fullArrayMsg); setMessages(fullArrayMsg);
} catch (error) { } catch (error) {
console.log(error);
} finally { } finally {
} }
}, },
@ -700,7 +701,7 @@ export const Thread = ({
); );
}} }}
disabled={!hasPreviousPage} disabled={!hasPreviousPage}
variant="contained" variant="contained" // TODO translate
> >
Previous Previous
</Button> </Button>

View File

@ -2218,7 +2218,8 @@ export const Group = ({
}} }}
> >
No group selected No group selected
</Typography> </Typography>{' '}
// TODO translate
</Box> </Box>
)} )}
@ -2235,39 +2236,39 @@ export const Group = ({
: '0px', : '0px',
}} }}
> >
<DesktopHeader <DesktopHeader
isPrivate={isPrivate} isPrivate={isPrivate}
selectedGroup={selectedGroup} selectedGroup={selectedGroup}
groupSection={groupSection} groupSection={groupSection}
isUnread={isUnread} isUnread={isUnread}
goToAnnouncements={goToAnnouncements} goToAnnouncements={goToAnnouncements}
isUnreadChat={isUnreadChat} isUnreadChat={isUnreadChat}
goToChat={goToChat} goToChat={goToChat}
goToThreads={goToThreads} goToThreads={goToThreads}
setOpenManageMembers={setOpenManageMembers} setOpenManageMembers={setOpenManageMembers}
groupChatHasUnread={groupChatHasUnread} groupChatHasUnread={groupChatHasUnread}
groupsAnnHasUnread={groupsAnnHasUnread} groupsAnnHasUnread={groupsAnnHasUnread}
directChatHasUnread={directChatHasUnread} directChatHasUnread={directChatHasUnread}
chatMode={chatMode} chatMode={chatMode}
openDrawerGroups={openDrawerGroups} openDrawerGroups={openDrawerGroups}
goToHome={goToHome} goToHome={goToHome}
setIsOpenDrawerProfile={setIsOpenDrawerProfile} setIsOpenDrawerProfile={setIsOpenDrawerProfile}
mobileViewMode={mobileViewMode} mobileViewMode={mobileViewMode}
setMobileViewMode={setMobileViewMode} setMobileViewMode={setMobileViewMode}
setMobileViewModeKeepOpen={setMobileViewModeKeepOpen} setMobileViewModeKeepOpen={setMobileViewModeKeepOpen}
hasUnreadGroups={groupChatHasUnread || groupsAnnHasUnread} hasUnreadGroups={groupChatHasUnread || groupsAnnHasUnread}
hasUnreadDirects={directChatHasUnread} hasUnreadDirects={directChatHasUnread}
myName={userInfo?.name || null} myName={userInfo?.name || null}
isHome={groupSection === 'home'} isHome={groupSection === 'home'}
isGroups={desktopSideView === 'groups'} isGroups={desktopSideView === 'groups'}
isDirects={desktopSideView === 'directs'} isDirects={desktopSideView === 'directs'}
setDesktopSideView={setDesktopSideView} setDesktopSideView={setDesktopSideView}
hasUnreadAnnouncements={isUnread} hasUnreadAnnouncements={isUnread}
isAnnouncement={groupSection === 'announcement'} isAnnouncement={groupSection === 'announcement'}
isChat={groupSection === 'chat'} isChat={groupSection === 'chat'}
hasUnreadChat={isUnreadChat} hasUnreadChat={isUnreadChat}
setGroupSection={setGroupSection} setGroupSection={setGroupSection}
isForum={groupSection === 'forum'} isForum={groupSection === 'forum'}
/> />
<Box <Box

View File

@ -69,7 +69,7 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
<Typography <Typography
sx={{ sx={{
fontSize: '1rem', fontSize: '1rem',
}} }} // TODO translate
> >
Group Invites{' '} Group Invites{' '}
{groupsWithJoinRequests?.length > 0 && {groupsWithJoinRequests?.length > 0 &&

View File

@ -82,6 +82,7 @@ export const GroupJoinRequests = ({
); );
setGroupsWithJoinRequests(res); setGroupsWithJoinRequests(res);
} catch (error) { } catch (error) {
console.log(error);
} finally { } finally {
setLoading(false); setLoading(false);
} }
@ -138,7 +139,7 @@ export const GroupJoinRequests = ({
<Typography <Typography
sx={{ sx={{
fontSize: '1rem', fontSize: '1rem',
}} }} // TODO translate
> >
Join Requests{' '} Join Requests{' '}
{filteredJoinRequests?.filter((group) => group?.data?.length > 0) {filteredJoinRequests?.filter((group) => group?.data?.length > 0)

View File

@ -1,22 +1,27 @@
import React, { useState } from "react"; import { useState } from 'react';
import { import {
Button, Button,
Menu, Menu,
MenuItem, MenuItem,
ListItemIcon, ListItemIcon,
ListItemText, ListItemText,
Badge,
Box, Box,
} from "@mui/material"; } from '@mui/material';
import ForumIcon from "@mui/icons-material/Forum"; import { ArrowDownIcon } from '../../assets/Icons/ArrowDownIcon';
import GroupIcon from "@mui/icons-material/Group"; import { NotificationIcon2 } from '../../assets/Icons/NotificationIcon2';
import { ArrowDownIcon } from "../../assets/Icons/ArrowDownIcon"; import { ChatIcon } from '../../assets/Icons/ChatIcon';
import { NotificationIcon2 } from "../../assets/Icons/NotificationIcon2"; import { ThreadsIcon } from '../../assets/Icons/ThreadsIcon';
import { ChatIcon } from "../../assets/Icons/ChatIcon"; import { MembersIcon } from '../../assets/Icons/MembersIcon';
import { ThreadsIcon } from "../../assets/Icons/ThreadsIcon";
import { MembersIcon } from "../../assets/Icons/MembersIcon";
export const GroupMenu = ({ setGroupSection, groupSection, setOpenManageMembers, goToAnnouncements, goToChat, hasUnreadChat, hasUnreadAnnouncements }) => { export const GroupMenu = ({
setGroupSection,
groupSection,
setOpenManageMembers,
goToAnnouncements,
goToChat,
hasUnreadChat,
hasUnreadAnnouncements,
}) => {
const [anchorEl, setAnchorEl] = useState(null); const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl); const open = Boolean(anchorEl);
@ -31,170 +36,217 @@ export const GroupMenu = ({ setGroupSection, groupSection, setOpenManageMembers,
return ( return (
<Box <Box
sx={{ sx={{
width: "100%", width: '100%',
display: "flex", display: 'flex',
justifyContent: "center", justifyContent: 'center',
marginTop: '14px', marginTop: '14px',
marginBottom: '14px' marginBottom: '14px',
}} }}
> >
<Button <Button
aria-controls={open ? "home-menu" : undefined} aria-controls={open ? 'home-menu' : undefined}
aria-haspopup="true" aria-haspopup="true"
aria-expanded={open ? "true" : undefined} aria-expanded={open ? 'true' : undefined}
onClick={handleClick} onClick={handleClick}
variant="contained" variant="contained"
sx={{ sx={{
backgroundColor: "var(--bg-primary)", backgroundColor: 'var(--bg-primary)',
width: "148px", width: '148px',
borderRadius: "5px", borderRadius: '5px',
fontSize: "12px", fontSize: '12px',
fontWeight: 600, fontWeight: 600,
color: "#fff", color: '#fff',
textTransform: "none", textTransform: 'none',
padding: '5px', padding: '5px',
height: '25px' height: '25px',
}} }}
> >
<Box <Box
sx={{ sx={{
display: "flex", display: 'flex',
gap: "6px", gap: '6px',
alignItems: "center", alignItems: 'center',
justifyContent: "space-between", justifyContent: 'space-between',
width: '100%' width: '100%',
}} }}
> >
<Box <Box
sx={{ sx={{
display: "flex", display: 'flex',
gap: "6px", gap: '6px',
alignItems: "center", alignItems: 'center',
}} }}
> >
{groupSection === "announcement" &&( {groupSection === 'announcement' && (
<> <NotificationIcon2 color={hasUnreadAnnouncements || hasUnreadChat ? 'var(--danger)' : 'white'} /> {" Announcements"}</> <>
{' '}
<NotificationIcon2
color={
hasUnreadAnnouncements || hasUnreadChat
? 'var(--danger)'
: 'white'
}
/>{' '}
{' Announcements'}
</>
)} )}
{groupSection === "chat" &&( {groupSection === 'chat' && (
<> <ChatIcon color={hasUnreadAnnouncements || hasUnreadChat ? 'var(--danger)' : 'white'} /> {" Group Chats"}</> <>
{' '}
<ChatIcon
color={
hasUnreadAnnouncements || hasUnreadChat
? 'var(--danger)'
: 'white'
}
/>{' '}
{' Group Chats'}
</>
)} )}
{groupSection === "forum" &&( {groupSection === 'forum' && (
<> <ThreadsIcon color={hasUnreadAnnouncements || hasUnreadChat ? 'var(--danger)' : 'white'} /> {" Threads"}</> <>
{' '}
<ThreadsIcon
color={
hasUnreadAnnouncements || hasUnreadChat
? 'var(--danger)'
: 'white'
}
/>{' '}
{' Threads'}
</>
)} )}
</Box> </Box>
<ArrowDownIcon color="white" /> <ArrowDownIcon color="white" />
</Box> </Box>
</Button> </Button>
<Menu <Menu
id="home-menu" id="home-menu"
anchorEl={anchorEl} anchorEl={anchorEl}
open={open} open={open}
onClose={handleClose} onClose={handleClose}
MenuListProps={{ MenuListProps={{
"aria-labelledby": "basic-button", 'aria-labelledby': 'basic-button',
}} }}
anchorOrigin={{ anchorOrigin={{
vertical: 'bottom', vertical: 'bottom',
horizontal: 'center', horizontal: 'center',
}}
}} transformOrigin={{
transformOrigin={{ vertical: 'top',
vertical: 'top', horizontal: 'center',
horizontal: 'center', }}
}} slotProps={{
slotProps={{ paper: {
paper: { sx: {
sx: { backgroundColor: 'var(--bg-primary)',
backgroundColor: 'var(--bg-primary)', color: '#fff',
color: '#fff', width: '148px',
width: '148px', borderRadius: '5px',
borderRadius: '5px'
},
}, },
},
}} }}
sx={{ sx={{
marginTop: '10px' marginTop: '10px',
}} }}
> >
<MenuItem <MenuItem
onClick={() => { onClick={() => {
goToChat() goToChat();
handleClose(); handleClose();
}} }}
> >
<ListItemIcon sx={{ <ListItemIcon
sx={{
minWidth: '24px !important' minWidth: '24px !important',
}}> }}
<ChatIcon color={hasUnreadChat ? 'var(--danger)' : "#fff"} /> >
<ChatIcon color={hasUnreadChat ? 'var(--danger)' : '#fff'} />
</ListItemIcon> </ListItemIcon>
<ListItemText sx={{ <ListItemText
"& .MuiTypography-root": { sx={{
fontSize: "12px", '& .MuiTypography-root': {
fontWeight: 600, fontSize: '12px',
color: hasUnreadChat ? "var(--danger)" :"#fff" fontWeight: 600,
}, color: hasUnreadChat ? 'var(--danger)' : '#fff',
}} primary="Chat" /> },
}}
primary="Chat"
/>
</MenuItem> </MenuItem>
<MenuItem <MenuItem
onClick={() => { onClick={() => {
goToAnnouncements() goToAnnouncements();
handleClose(); handleClose();
}} }}
> >
<ListItemIcon sx={{ <ListItemIcon
sx={{
minWidth: '24px !important' minWidth: '24px !important',
}}> }}
<NotificationIcon2 color={hasUnreadAnnouncements ? 'var(--danger)' : "#fff" } /> >
<NotificationIcon2
color={hasUnreadAnnouncements ? 'var(--danger)' : '#fff'}
/>
</ListItemIcon> </ListItemIcon>
<ListItemText sx={{ <ListItemText
"& .MuiTypography-root": { sx={{
fontSize: "12px", '& .MuiTypography-root': {
fontWeight: 600, fontSize: '12px',
color: hasUnreadAnnouncements ? "var(--danger)" :"#fff" fontWeight: 600,
}, color: hasUnreadAnnouncements ? 'var(--danger)' : '#fff',
}} primary="Announcements" /> },
}}
primary="Announcements"
/>
</MenuItem> </MenuItem>
<MenuItem <MenuItem
onClick={() => { onClick={() => {
setGroupSection("forum"); setGroupSection('forum');
handleClose(); handleClose();
}} }}
> >
<ListItemIcon sx={{ <ListItemIcon
minWidth: '24px !important' sx={{
}}> minWidth: '24px !important',
<ThreadsIcon color={"#fff"} /> }}
>
<ThreadsIcon color={'#fff'} />
</ListItemIcon> </ListItemIcon>
<ListItemText sx={{ <ListItemText
"& .MuiTypography-root": { sx={{
fontSize: "12px", '& .MuiTypography-root': {
fontWeight: 600, fontSize: '12px',
}, fontWeight: 600,
}} primary="Threads" /> },
}}
primary="Threads"
/>
</MenuItem> </MenuItem>
<MenuItem <MenuItem
onClick={() => { onClick={() => {
setOpenManageMembers(true) setOpenManageMembers(true);
handleClose(); handleClose();
}} }}
> >
<ListItemIcon sx={{ <ListItemIcon
minWidth: '24px !important' sx={{
}}> minWidth: '24px !important',
<MembersIcon sx={{ color: "#fff" }} /> }}
>
<MembersIcon sx={{ color: '#fff' }} />
</ListItemIcon> </ListItemIcon>
<ListItemText sx={{ <ListItemText
"& .MuiTypography-root": { sx={{
fontSize: "12px", '& .MuiTypography-root': {
fontWeight: 600, fontSize: '12px',
}, fontWeight: 600,
}} primary="Members" /> },
}}
primary="Members"
/>
</MenuItem> </MenuItem>
</Menu> </Menu>
</Box> </Box>

View File

@ -1,13 +1,6 @@
import { LoadingButton } from '@mui/lab'; import { LoadingButton } from '@mui/lab';
import { import { Box, Input, MenuItem, Select, SelectChangeEvent } from '@mui/material';
Box, import { useState } from 'react';
Button,
Input,
MenuItem,
Select,
SelectChangeEvent,
} from '@mui/material';
import React, { useState } from 'react';
import { Spacer } from '../../common/Spacer'; import { Spacer } from '../../common/Spacer';
import { Label } from './AddGroup'; import { Label } from './AddGroup';
import { getFee } from '../../background'; import { getFee } from '../../background';
@ -34,6 +27,7 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
}) })
.then((response) => { .then((response) => {
if (!response?.error) { if (!response?.error) {
// TODO translate
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: `Successfully invited ${value}. It may take a couple of minutes for the changes to propagate`, message: `Successfully invited ${value}. It may take a couple of minutes for the changes to propagate`,

View File

@ -1,16 +1,31 @@
import React, { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { Avatar, Box, Button, ListItem, ListItemAvatar, ListItemButton, ListItemText, Popover } from '@mui/material'; import {
import { AutoSizer, CellMeasurer, CellMeasurerCache, List } from 'react-virtualized'; Avatar,
Box,
ListItem,
ListItemAvatar,
ListItemButton,
ListItemText,
Popover,
} from '@mui/material';
import {
AutoSizer,
CellMeasurer,
CellMeasurerCache,
List,
} from 'react-virtualized';
import { getNameInfo } from './Group'; import { getNameInfo } from './Group';
import { getBaseApi, getFee } from '../../background'; import { getFee } from '../../background';
import { LoadingButton } from '@mui/lab'; import { LoadingButton } from '@mui/lab';
import { getBaseApiReact } from '../../App'; import { getBaseApiReact } from '../../App';
export const getMemberInvites = async (groupNumber) => { export const getMemberInvites = async (groupNumber) => {
const response = await fetch(`${getBaseApiReact()}/groups/bans/${groupNumber}?limit=0`); const response = await fetch(
`${getBaseApiReact()}/groups/bans/${groupNumber}?limit=0`
);
const groupData = await response.json(); const groupData = await response.json();
return groupData; return groupData;
} };
const getNames = async (listOfMembers, includeNoNames) => { const getNames = async (listOfMembers, includeNoNames) => {
let members = []; let members = [];
@ -20,14 +35,14 @@ const getNames = async (listOfMembers, includeNoNames) => {
const name = await getNameInfo(member.offender); const name = await getNameInfo(member.offender);
if (name) { if (name) {
members.push({ ...member, name }); members.push({ ...member, name });
} else if(includeNoNames){ } else if (includeNoNames) {
members.push({ ...member, name: name || "" }); members.push({ ...member, name: name || '' });
} }
} }
} }
} }
return members; return members;
} };
const cache = new CellMeasurerCache({ const cache = new CellMeasurerCache({
fixedWidth: true, fixedWidth: true,
@ -49,7 +64,7 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
} };
useEffect(() => { useEffect(() => {
if (groupId) { if (groupId) {
@ -67,33 +82,36 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
setOpenPopoverIndex(null); setOpenPopoverIndex(null);
}; };
const handleCancelBan = async (address)=> { const handleCancelBan = async (address) => {
try { try {
const fee = await getFee('CANCEL_GROUP_BAN') // TODO translate
const fee = await getFee('CANCEL_GROUP_BAN');
await show({ await show({
message: "Would you like to perform a CANCEL_GROUP_BAN transaction?" , message: 'Would you like to perform a CANCEL_GROUP_BAN transaction?',
publishFee: fee.fee + ' QORT' publishFee: fee.fee + ' QORT',
}) });
setIsLoadingUnban(true) setIsLoadingUnban(true);
new Promise((res, rej)=> { new Promise((res, rej) => {
window.sendMessage("cancelBan", { window
groupId, .sendMessage('cancelBan', {
qortalAddress: address, groupId,
}) qortalAddress: address,
})
.then((response) => { .then((response) => {
if (!response?.error) { if (!response?.error) {
res(response); res(response);
setIsLoadingUnban(false); setIsLoadingUnban(false);
setInfoSnack({ setInfoSnack({
type: "success", type: 'success',
message: "Successfully unbanned user. It may take a couple of minutes for the changes to propagate", message:
'Successfully unbanned user. It may take a couple of minutes for the changes to propagate',
}); });
handlePopoverClose(); handlePopoverClose();
setOpenSnack(true); setOpenSnack(true);
return; return;
} }
setInfoSnack({ setInfoSnack({
type: "error", type: 'error',
message: response?.error, message: response?.error,
}); });
setOpenSnack(true); setOpenSnack(true);
@ -101,20 +119,18 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
}) })
.catch((error) => { .catch((error) => {
setInfoSnack({ setInfoSnack({
type: "error", type: 'error',
message: error.message || "An error occurred", message: error.message || 'An error occurred',
}); });
setOpenSnack(true); setOpenSnack(true);
rej(error); rej(error);
}); });
});
})
} catch (error) { } catch (error) {
} finally { } finally {
setIsLoadingUnban(false) setIsLoadingUnban(false);
} }
} };
const rowRenderer = ({ index, key, parent, style }) => { const rowRenderer = ({ index, key, parent, style }) => {
const member = bans[index]; const member = bans[index];
@ -135,36 +151,47 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
anchorEl={popoverAnchor} anchorEl={popoverAnchor}
onClose={handlePopoverClose} onClose={handlePopoverClose}
anchorOrigin={{ anchorOrigin={{
vertical: "bottom", vertical: 'bottom',
horizontal: "center", horizontal: 'center',
}} }}
transformOrigin={{ transformOrigin={{
vertical: "top", vertical: 'top',
horizontal: "center", horizontal: 'center',
}} }}
style={{ marginTop: "8px" }} style={{ marginTop: '8px' }}
> >
<Box <Box
sx={{ sx={{
width: "325px", width: '325px',
height: "250px", height: '250px',
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
alignItems: "center", alignItems: 'center',
gap: "10px", gap: '10px',
padding: "10px", padding: '10px',
}} }}
> >
<LoadingButton loading={isLoadingUnban} <LoadingButton
loading={isLoadingUnban}
loadingPosition="start" loadingPosition="start"
variant="contained" onClick={()=> handleCancelBan(member?.offender)}>Cancel Ban</LoadingButton> variant="contained"
onClick={() => handleCancelBan(member?.offender)}
>
Cancel Ban
</LoadingButton>
</Box> </Box>
</Popover> </Popover>
<ListItemButton onClick={(event) => handlePopoverOpen(event, index)}> <ListItemButton
onClick={(event) => handlePopoverOpen(event, index)}
>
<ListItemAvatar> <ListItemAvatar>
<Avatar <Avatar
alt={member?.name} alt={member?.name}
src={member?.name ? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${member?.name}/qortal_avatar?async=true` : ''} src={
member?.name
? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${member?.name}/qortal_avatar?async=true`
: ''
}
/> />
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary={member?.name || member?.offender} /> <ListItemText primary={member?.name || member?.offender} />
@ -179,7 +206,16 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
return ( return (
<div> <div>
<p>Ban list</p> <p>Ban list</p>
<div style={{ position: 'relative', height: '500px', width: '100%', display: 'flex', flexDirection: 'column', flexShrink: 1 }}> <div
style={{
position: 'relative',
height: '500px',
width: '100%',
display: 'flex',
flexDirection: 'column',
flexShrink: 1,
}}
>
<AutoSizer> <AutoSizer>
{({ height, width }) => ( {({ height, width }) => (
<List <List
@ -196,4 +232,4 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
</div> </div>
</div> </div>
); );
} };

View File

@ -230,7 +230,7 @@ export const ListOfGroupPromotions = () => {
.catch((error) => { .catch((error) => {
rej(error.message || 'An error occurred'); rej(error.message || 'An error occurred');
}); });
}); }); // TODO translate
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: message:

View File

@ -1,16 +1,31 @@
import React, { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { Avatar, Box, Button, ListItem, ListItemAvatar, ListItemButton, ListItemText, Popover } from '@mui/material'; import {
import { AutoSizer, CellMeasurer, CellMeasurerCache, List } from 'react-virtualized'; Avatar,
Box,
ListItem,
ListItemAvatar,
ListItemButton,
ListItemText,
Popover,
} from '@mui/material';
import {
AutoSizer,
CellMeasurer,
CellMeasurerCache,
List,
} from 'react-virtualized';
import { getNameInfo } from './Group'; import { getNameInfo } from './Group';
import { getBaseApi, getFee } from '../../background'; import { getFee } from '../../background';
import { LoadingButton } from '@mui/lab'; import { LoadingButton } from '@mui/lab';
import { getBaseApiReact } from '../../App'; import { getBaseApiReact } from '../../App';
export const getMemberInvites = async (groupNumber) => { export const getMemberInvites = async (groupNumber) => {
const response = await fetch(`${getBaseApiReact()}/groups/invites/group/${groupNumber}?limit=0`); const response = await fetch(
`${getBaseApiReact()}/groups/invites/group/${groupNumber}?limit=0`
);
const groupData = await response.json(); const groupData = await response.json();
return groupData; return groupData;
} };
const getNames = async (listOfMembers, includeNoNames) => { const getNames = async (listOfMembers, includeNoNames) => {
let members = []; let members = [];
@ -20,21 +35,26 @@ const getNames = async (listOfMembers, includeNoNames) => {
const name = await getNameInfo(member.invitee); const name = await getNameInfo(member.invitee);
if (name) { if (name) {
members.push({ ...member, name }); members.push({ ...member, name });
} else if(includeNoNames){ } else if (includeNoNames) {
members.push({ ...member, name: name || "" }); members.push({ ...member, name: name || '' });
} }
} }
} }
} }
return members; return members;
} };
const cache = new CellMeasurerCache({ const cache = new CellMeasurerCache({
fixedWidth: true, fixedWidth: true,
defaultHeight: 50, defaultHeight: 50,
}); });
export const ListOfInvites = ({ groupId, setInfoSnack, setOpenSnack, show }) => { export const ListOfInvites = ({
groupId,
setInfoSnack,
setOpenSnack,
show,
}) => {
const [invites, setInvites] = useState([]); const [invites, setInvites] = useState([]);
const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to
const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open
@ -50,7 +70,7 @@ export const ListOfInvites = ({ groupId, setInfoSnack, setOpenSnack, show }) =>
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
} };
useEffect(() => { useEffect(() => {
if (groupId) { if (groupId) {
@ -68,24 +88,27 @@ export const ListOfInvites = ({ groupId, setInfoSnack, setOpenSnack, show }) =>
setOpenPopoverIndex(null); setOpenPopoverIndex(null);
}; };
const handleCancelInvitation = async (address)=> { const handleCancelInvitation = async (address) => {
try { try {
const fee = await getFee('CANCEL_GROUP_INVITE') // TODO translate
const fee = await getFee('CANCEL_GROUP_INVITE');
await show({ await show({
message: "Would you like to perform a CANCEL_GROUP_INVITE transaction?" , message: 'Would you like to perform a CANCEL_GROUP_INVITE transaction?',
publishFee: fee.fee + ' QORT' publishFee: fee.fee + ' QORT',
}) });
setIsLoadingCancelInvite(true) setIsLoadingCancelInvite(true);
await new Promise((res, rej)=> { await new Promise((res, rej) => {
window.sendMessage("cancelInvitationToGroup", { window
groupId, .sendMessage('cancelInvitationToGroup', {
qortalAddress: address, groupId,
}) qortalAddress: address,
})
.then((response) => { .then((response) => {
if (!response?.error) { if (!response?.error) {
setInfoSnack({ setInfoSnack({
type: "success", type: 'success',
message: "Successfully canceled invitation. It may take a couple of minutes for the changes to propagate", message:
'Successfully canceled invitation. It may take a couple of minutes for the changes to propagate',
}); });
setOpenSnack(true); setOpenSnack(true);
handlePopoverClose(); handlePopoverClose();
@ -94,7 +117,7 @@ export const ListOfInvites = ({ groupId, setInfoSnack, setOpenSnack, show }) =>
return; return;
} }
setInfoSnack({ setInfoSnack({
type: "error", type: 'error',
message: response?.error, message: response?.error,
}); });
setOpenSnack(true); setOpenSnack(true);
@ -102,20 +125,18 @@ export const ListOfInvites = ({ groupId, setInfoSnack, setOpenSnack, show }) =>
}) })
.catch((error) => { .catch((error) => {
setInfoSnack({ setInfoSnack({
type: "error", type: 'error',
message: error.message || "An error occurred", message: error.message || 'An error occurred',
}); });
setOpenSnack(true); setOpenSnack(true);
rej(error); rej(error);
}); });
});
})
} catch (error) { } catch (error) {
} finally { } finally {
setIsLoadingCancelInvite(false) setIsLoadingCancelInvite(false);
} }
} };
const rowRenderer = ({ index, key, parent, style }) => { const rowRenderer = ({ index, key, parent, style }) => {
const member = invites[index]; const member = invites[index];
@ -136,36 +157,47 @@ export const ListOfInvites = ({ groupId, setInfoSnack, setOpenSnack, show }) =>
anchorEl={popoverAnchor} anchorEl={popoverAnchor}
onClose={handlePopoverClose} onClose={handlePopoverClose}
anchorOrigin={{ anchorOrigin={{
vertical: "bottom", vertical: 'bottom',
horizontal: "center", horizontal: 'center',
}} }}
transformOrigin={{ transformOrigin={{
vertical: "top", vertical: 'top',
horizontal: "center", horizontal: 'center',
}} }}
style={{ marginTop: "8px" }} style={{ marginTop: '8px' }}
> >
<Box <Box
sx={{ sx={{
width: "325px", width: '325px',
height: "250px", height: '250px',
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
alignItems: "center", alignItems: 'center',
gap: "10px", gap: '10px',
padding: "10px", padding: '10px',
}} }}
> >
<LoadingButton loading={isLoadingCancelInvite} <LoadingButton
loading={isLoadingCancelInvite}
loadingPosition="start" loadingPosition="start"
variant="contained" onClick={()=> handleCancelInvitation(member?.invitee)}>Cancel Invitation</LoadingButton> variant="contained"
onClick={() => handleCancelInvitation(member?.invitee)}
>
Cancel Invitation
</LoadingButton>
</Box> </Box>
</Popover> </Popover>
<ListItemButton onClick={(event) => handlePopoverOpen(event, index)}> <ListItemButton
onClick={(event) => handlePopoverOpen(event, index)}
>
<ListItemAvatar> <ListItemAvatar>
<Avatar <Avatar
alt={member?.name} alt={member?.name}
src={member?.name ? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${member?.name}/qortal_avatar?async=true` : ''} src={
member?.name
? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${member?.name}/qortal_avatar?async=true`
: ''
}
/> />
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary={member?.name || member?.invitee} /> <ListItemText primary={member?.name || member?.invitee} />
@ -180,7 +212,16 @@ export const ListOfInvites = ({ groupId, setInfoSnack, setOpenSnack, show }) =>
return ( return (
<div> <div>
<p>Invitees list</p> <p>Invitees list</p>
<div style={{ position: 'relative', height: '500px', width: '100%', display: 'flex', flexDirection: 'column', flexShrink: 1 }}> <div
style={{
position: 'relative',
height: '500px',
width: '100%',
display: 'flex',
flexDirection: 'column',
flexShrink: 1,
}}
>
<AutoSizer> <AutoSizer>
{({ height, width }) => ( {({ height, width }) => (
<List <List
@ -197,4 +238,4 @@ export const ListOfInvites = ({ groupId, setInfoSnack, setOpenSnack, show }) =>
</div> </div>
</div> </div>
); );
} };

View File

@ -1,16 +1,31 @@
import React, { useContext, useEffect, useRef, useState } from 'react'; import { useContext, useEffect, useRef, useState } from 'react';
import { Avatar, Box, Button, ListItem, ListItemAvatar, ListItemButton, ListItemText, Popover } from '@mui/material'; import {
import { AutoSizer, CellMeasurer, CellMeasurerCache, List } from 'react-virtualized'; Avatar,
Box,
ListItem,
ListItemAvatar,
ListItemButton,
ListItemText,
Popover,
} from '@mui/material';
import {
AutoSizer,
CellMeasurer,
CellMeasurerCache,
List,
} from 'react-virtualized';
import { getNameInfo } from './Group'; import { getNameInfo } from './Group';
import { getBaseApi, getFee } from '../../background'; import { getBaseApi, getFee } from '../../background';
import { LoadingButton } from '@mui/lab'; import { LoadingButton } from '@mui/lab';
import { MyContext, getBaseApiReact } from '../../App'; import { MyContext, getBaseApiReact } from '../../App';
export const getMemberInvites = async (groupNumber) => { export const getMemberInvites = async (groupNumber) => {
const response = await fetch(`${getBaseApiReact()}/groups/joinrequests/${groupNumber}?limit=0`); const response = await fetch(
`${getBaseApiReact()}/groups/joinrequests/${groupNumber}?limit=0`
);
const groupData = await response.json(); const groupData = await response.json();
return groupData; return groupData;
} };
const getNames = async (listOfMembers, includeNoNames) => { const getNames = async (listOfMembers, includeNoNames) => {
let members = []; let members = [];
@ -19,24 +34,29 @@ const getNames = async (listOfMembers, includeNoNames) => {
if (member.joiner) { if (member.joiner) {
const name = await getNameInfo(member.joiner); const name = await getNameInfo(member.joiner);
if (name) { if (name) {
members.push({ ...member, name: name || "" }); members.push({ ...member, name: name || '' });
} else if(includeNoNames){ } else if (includeNoNames) {
members.push({ ...member, name: name || "" }); members.push({ ...member, name: name || '' });
} }
} }
} }
} }
return members; return members;
} };
const cache = new CellMeasurerCache({ const cache = new CellMeasurerCache({
fixedWidth: true, fixedWidth: true,
defaultHeight: 50, defaultHeight: 50,
}); });
export const ListOfJoinRequests = ({ groupId, setInfoSnack, setOpenSnack, show }) => { export const ListOfJoinRequests = ({
groupId,
setInfoSnack,
setOpenSnack,
show,
}) => {
const [invites, setInvites] = useState([]); const [invites, setInvites] = useState([]);
const {txList, setTxList} = useContext(MyContext) const { txList, setTxList } = useContext(MyContext);
const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to
const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open
@ -51,7 +71,7 @@ export const ListOfJoinRequests = ({ groupId, setInfoSnack, setOpenSnack, show }
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
} };
useEffect(() => { useEffect(() => {
if (groupId) { if (groupId) {
@ -69,26 +89,28 @@ export const ListOfJoinRequests = ({ groupId, setInfoSnack, setOpenSnack, show }
setOpenPopoverIndex(null); setOpenPopoverIndex(null);
}; };
const handleAcceptJoinRequest = async (address)=> { const handleAcceptJoinRequest = async (address) => {
try { try {
const fee = await getFee('GROUP_INVITE') const fee = await getFee('GROUP_INVITE'); // TODO translate
await show({ await show({
message: "Would you like to perform a GROUP_INVITE transaction?" , message: 'Would you like to perform a GROUP_INVITE transaction?',
publishFee: fee.fee + ' QORT' publishFee: fee.fee + ' QORT',
}) });
setIsLoadingAccept(true) setIsLoadingAccept(true);
await new Promise((res, rej)=> { await new Promise((res, rej) => {
window.sendMessage("inviteToGroup", { window
groupId, .sendMessage('inviteToGroup', {
qortalAddress: address, groupId,
inviteTime: 10800, qortalAddress: address,
}) inviteTime: 10800,
})
.then((response) => { .then((response) => {
if (!response?.error) { if (!response?.error) {
setIsLoadingAccept(false); setIsLoadingAccept(false);
setInfoSnack({ setInfoSnack({
type: "success", type: 'success',
message: "Successfully accepted join request. It may take a couple of minutes for the changes to propagate", message:
'Successfully accepted join request. It may take a couple of minutes for the changes to propagate',
}); });
setOpenSnack(true); setOpenSnack(true);
handlePopoverClose(); handlePopoverClose();
@ -111,7 +133,7 @@ export const ListOfJoinRequests = ({ groupId, setInfoSnack, setOpenSnack, show }
} }
setInfoSnack({ setInfoSnack({
type: "error", type: 'error',
message: response?.error, message: response?.error,
}); });
setOpenSnack(true); setOpenSnack(true);
@ -119,25 +141,28 @@ export const ListOfJoinRequests = ({ groupId, setInfoSnack, setOpenSnack, show }
}) })
.catch((error) => { .catch((error) => {
setInfoSnack({ setInfoSnack({
type: "error", type: 'error',
message: error?.message || "An error occurred", message: error?.message || 'An error occurred',
}); });
setOpenSnack(true); setOpenSnack(true);
rej(error); rej(error);
}); });
});
})
} catch (error) { } catch (error) {
} finally { } finally {
setIsLoadingAccept(false) setIsLoadingAccept(false);
} }
} };
const rowRenderer = ({ index, key, parent, style }) => { const rowRenderer = ({ index, key, parent, style }) => {
const member = invites[index]; const member = invites[index];
const findJoinRequsetInTxList = txList?.find((tx)=> tx?.groupId === groupId && tx?.qortalAddress === member?.joiner && tx?.type === 'join-request-accept') const findJoinRequsetInTxList = txList?.find(
if(findJoinRequsetInTxList) return null (tx) =>
tx?.groupId === groupId &&
tx?.qortalAddress === member?.joiner &&
tx?.type === 'join-request-accept'
);
if (findJoinRequsetInTxList) return null;
return ( return (
<CellMeasurer <CellMeasurer
key={key} key={key}
@ -154,36 +179,47 @@ export const ListOfJoinRequests = ({ groupId, setInfoSnack, setOpenSnack, show }
anchorEl={popoverAnchor} anchorEl={popoverAnchor}
onClose={handlePopoverClose} onClose={handlePopoverClose}
anchorOrigin={{ anchorOrigin={{
vertical: "bottom", vertical: 'bottom',
horizontal: "center", horizontal: 'center',
}} }}
transformOrigin={{ transformOrigin={{
vertical: "top", vertical: 'top',
horizontal: "center", horizontal: 'center',
}} }}
style={{ marginTop: "8px" }} style={{ marginTop: '8px' }}
> >
<Box <Box
sx={{ sx={{
width: "325px", width: '325px',
height: "250px", height: '250px',
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
alignItems: "center", alignItems: 'center',
gap: "10px", gap: '10px',
padding: "10px", padding: '10px',
}} }}
> >
<LoadingButton loading={isLoadingAccept} <LoadingButton
loading={isLoadingAccept}
loadingPosition="start" loadingPosition="start"
variant="contained" onClick={()=> handleAcceptJoinRequest(member?.joiner)}>Accept</LoadingButton> variant="contained"
onClick={() => handleAcceptJoinRequest(member?.joiner)}
>
Accept
</LoadingButton>
</Box> </Box>
</Popover> </Popover>
<ListItemButton onClick={(event) => handlePopoverOpen(event, index)}> <ListItemButton
onClick={(event) => handlePopoverOpen(event, index)}
>
<ListItemAvatar> <ListItemAvatar>
<Avatar <Avatar
alt={member?.name} alt={member?.name}
src={member?.name ? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${member?.name}/qortal_avatar?async=true` : ''} src={
member?.name
? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${member?.name}/qortal_avatar?async=true`
: ''
}
/> />
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary={member?.name || member?.joiner} /> <ListItemText primary={member?.name || member?.joiner} />
@ -198,7 +234,16 @@ export const ListOfJoinRequests = ({ groupId, setInfoSnack, setOpenSnack, show }
return ( return (
<div> <div>
<p>Join request list</p> <p>Join request list</p>
<div style={{ position: 'relative', height: '500px', width: '100%', display: 'flex', flexDirection: 'column', flexShrink: 1 }}> <div
style={{
position: 'relative',
height: '500px',
width: '100%',
display: 'flex',
flexDirection: 'column',
flexShrink: 1,
}}
>
<AutoSizer> <AutoSizer>
{({ height, width }) => ( {({ height, width }) => (
<List <List
@ -215,4 +260,4 @@ export const ListOfJoinRequests = ({ groupId, setInfoSnack, setOpenSnack, show }
</div> </div>
</div> </div>
); );
} };

View File

@ -1,29 +1,29 @@
import { import {
Avatar, Avatar,
Box, Box,
Button,
ListItem, ListItem,
ListItemAvatar, ListItemAvatar,
ListItemButton, ListItemButton,
ListItemText, ListItemText,
Popover, Popover,
Typography, Typography,
} from "@mui/material"; } from '@mui/material';
import React, { useRef, useState } from "react"; import { useRef, useState } from 'react';
import { import {
AutoSizer, AutoSizer,
CellMeasurer, CellMeasurer,
CellMeasurerCache, CellMeasurerCache,
List, List,
} from "react-virtualized"; } from 'react-virtualized';
import { LoadingButton } from "@mui/lab"; import { LoadingButton } from '@mui/lab';
import { getBaseApi, getFee } from "../../background"; import { getFee } from '../../background';
import { getBaseApiReact } from "../../App"; import { getBaseApiReact } from '../../App';
const cache = new CellMeasurerCache({ const cache = new CellMeasurerCache({
fixedWidth: true, fixedWidth: true,
defaultHeight: 50, defaultHeight: 50,
}); });
const ListOfMembers = ({ const ListOfMembers = ({
members, members,
groupId, groupId,
@ -40,7 +40,6 @@ const ListOfMembers = ({
const [isLoadingMakeAdmin, setIsLoadingMakeAdmin] = useState(false); const [isLoadingMakeAdmin, setIsLoadingMakeAdmin] = useState(false);
const [isLoadingRemoveAdmin, setIsLoadingRemoveAdmin] = useState(false); const [isLoadingRemoveAdmin, setIsLoadingRemoveAdmin] = useState(false);
const listRef = useRef(); const listRef = useRef();
const handlePopoverOpen = (event, index) => { const handlePopoverOpen = (event, index) => {
@ -55,23 +54,25 @@ const ListOfMembers = ({
const handleKick = async (address) => { const handleKick = async (address) => {
try { try {
const fee = await getFee("GROUP_KICK"); const fee = await getFee('GROUP_KICK');
await show({ await show({
message: "Would you like to perform a GROUP_KICK transaction?", message: 'Would you like to perform a GROUP_KICK transaction?',
publishFee: fee.fee + " QORT", publishFee: fee.fee + ' QORT',
}); });
setIsLoadingKick(true); setIsLoadingKick(true);
new Promise((res, rej) => { new Promise((res, rej) => {
window.sendMessage("kickFromGroup", { window
groupId, .sendMessage('kickFromGroup', {
qortalAddress: address, groupId,
}) qortalAddress: address,
})
.then((response) => { .then((response) => {
if (!response?.error) { if (!response?.error) {
setInfoSnack({ setInfoSnack({
type: "success", type: 'success',
message: "Successfully kicked member from group. It may take a couple of minutes for the changes to propagate", message:
'Successfully kicked member from group. It may take a couple of minutes for the changes to propagate',
}); });
setOpenSnack(true); setOpenSnack(true);
handlePopoverClose(); handlePopoverClose();
@ -79,7 +80,7 @@ const ListOfMembers = ({
return; return;
} }
setInfoSnack({ setInfoSnack({
type: "error", type: 'error',
message: response?.error, message: response?.error,
}); });
setOpenSnack(true); setOpenSnack(true);
@ -87,38 +88,40 @@ const ListOfMembers = ({
}) })
.catch((error) => { .catch((error) => {
setInfoSnack({ setInfoSnack({
type: "error", type: 'error',
message: error.message || "An error occurred", message: error.message || 'An error occurred',
}); });
setOpenSnack(true); setOpenSnack(true);
rej(error); rej(error);
}); });
}); });
} catch (error) { } catch (error) {
console.log(error);
} finally { } finally {
setIsLoadingKick(false); setIsLoadingKick(false);
} }
}; };
const handleBan = async (address) => { const handleBan = async (address) => {
try { try {
const fee = await getFee("GROUP_BAN"); const fee = await getFee('GROUP_BAN'); // TODO translate
await show({ await show({
message: "Would you like to perform a GROUP_BAN transaction?", message: 'Would you like to perform a GROUP_BAN transaction?',
publishFee: fee.fee + " QORT", publishFee: fee.fee + ' QORT',
}); });
setIsLoadingBan(true); setIsLoadingBan(true);
await new Promise((res, rej) => { await new Promise((res, rej) => {
window.sendMessage("banFromGroup", { window
groupId, .sendMessage('banFromGroup', {
qortalAddress: address, groupId,
rBanTime: 0, qortalAddress: address,
}) rBanTime: 0,
})
.then((response) => { .then((response) => {
if (!response?.error) { if (!response?.error) {
setInfoSnack({ setInfoSnack({
type: "success", type: 'success',
message: "Successfully banned member from group. It may take a couple of minutes for the changes to propagate", message:
'Successfully banned member from group. It may take a couple of minutes for the changes to propagate',
}); });
setOpenSnack(true); setOpenSnack(true);
handlePopoverClose(); handlePopoverClose();
@ -126,7 +129,7 @@ const ListOfMembers = ({
return; return;
} }
setInfoSnack({ setInfoSnack({
type: "error", type: 'error',
message: response?.error, message: response?.error,
}); });
setOpenSnack(true); setOpenSnack(true);
@ -134,13 +137,12 @@ const ListOfMembers = ({
}) })
.catch((error) => { .catch((error) => {
setInfoSnack({ setInfoSnack({
type: "error", type: 'error',
message: error.message || "An error occurred", message: error.message || 'An error occurred',
}); });
setOpenSnack(true); setOpenSnack(true);
rej(error); rej(error);
}); });
}); });
} catch (error) { } catch (error) {
} finally { } finally {
@ -150,22 +152,24 @@ const ListOfMembers = ({
const makeAdmin = async (address) => { const makeAdmin = async (address) => {
try { try {
const fee = await getFee("ADD_GROUP_ADMIN"); const fee = await getFee('ADD_GROUP_ADMIN');
await show({ await show({
message: "Would you like to perform a ADD_GROUP_ADMIN transaction?", message: 'Would you like to perform a ADD_GROUP_ADMIN transaction?',
publishFee: fee.fee + " QORT", publishFee: fee.fee + ' QORT',
}); });
setIsLoadingMakeAdmin(true); setIsLoadingMakeAdmin(true);
await new Promise((res, rej) => { await new Promise((res, rej) => {
window.sendMessage("makeAdmin", { window
groupId, .sendMessage('makeAdmin', {
qortalAddress: address, groupId,
}) qortalAddress: address,
})
.then((response) => { .then((response) => {
if (!response?.error) { if (!response?.error) {
setInfoSnack({ setInfoSnack({
type: "success", type: 'success',
message: "Successfully made member an admin. It may take a couple of minutes for the changes to propagate", message:
'Successfully made member an admin. It may take a couple of minutes for the changes to propagate',
}); });
setOpenSnack(true); setOpenSnack(true);
handlePopoverClose(); handlePopoverClose();
@ -173,7 +177,7 @@ const ListOfMembers = ({
return; return;
} }
setInfoSnack({ setInfoSnack({
type: "error", type: 'error',
message: response?.error, message: response?.error,
}); });
setOpenSnack(true); setOpenSnack(true);
@ -181,13 +185,12 @@ const ListOfMembers = ({
}) })
.catch((error) => { .catch((error) => {
setInfoSnack({ setInfoSnack({
type: "error", type: 'error',
message: error.message || "An error occurred", message: error.message || 'An error occurred',
}); });
setOpenSnack(true); setOpenSnack(true);
rej(error); rej(error);
}); });
}); });
} catch (error) { } catch (error) {
} finally { } finally {
@ -197,22 +200,24 @@ const ListOfMembers = ({
const removeAdmin = async (address) => { const removeAdmin = async (address) => {
try { try {
const fee = await getFee("REMOVE_GROUP_ADMIN"); const fee = await getFee('REMOVE_GROUP_ADMIN');
await show({ await show({
message: "Would you like to perform a REMOVE_GROUP_ADMIN transaction?", message: 'Would you like to perform a REMOVE_GROUP_ADMIN transaction?',
publishFee: fee.fee + " QORT", publishFee: fee.fee + ' QORT',
}); });
setIsLoadingRemoveAdmin(true); setIsLoadingRemoveAdmin(true);
await new Promise((res, rej) => { await new Promise((res, rej) => {
window.sendMessage("removeAdmin", { window
groupId, .sendMessage('removeAdmin', {
qortalAddress: address, groupId,
}) qortalAddress: address,
})
.then((response) => { .then((response) => {
if (!response?.error) { if (!response?.error) {
setInfoSnack({ setInfoSnack({
type: "success", type: 'success',
message: "Successfully removed member as an admin. It may take a couple of minutes for the changes to propagate", message:
'Successfully removed member as an admin. It may take a couple of minutes for the changes to propagate',
}); });
setOpenSnack(true); setOpenSnack(true);
handlePopoverClose(); handlePopoverClose();
@ -220,7 +225,7 @@ const ListOfMembers = ({
return; return;
} }
setInfoSnack({ setInfoSnack({
type: "error", type: 'error',
message: response?.error, message: response?.error,
}); });
setOpenSnack(true); setOpenSnack(true);
@ -228,13 +233,12 @@ const ListOfMembers = ({
}) })
.catch((error) => { .catch((error) => {
setInfoSnack({ setInfoSnack({
type: "error", type: 'error',
message: error.message || "An error occurred", message: error.message || 'An error occurred',
}); });
setOpenSnack(true); setOpenSnack(true);
rej(error); rej(error);
}); });
}); });
} catch (error) { } catch (error) {
} finally { } finally {
@ -260,24 +264,24 @@ const ListOfMembers = ({
anchorEl={popoverAnchor} anchorEl={popoverAnchor}
onClose={handlePopoverClose} onClose={handlePopoverClose}
anchorOrigin={{ anchorOrigin={{
vertical: "bottom", vertical: 'bottom',
horizontal: "center", horizontal: 'center',
}} }}
transformOrigin={{ transformOrigin={{
vertical: "top", vertical: 'top',
horizontal: "center", horizontal: 'center',
}} }}
style={{ marginTop: "8px" }} style={{ marginTop: '8px' }}
> >
<Box <Box
sx={{ sx={{
width: "325px", width: '325px',
height: "250px", height: '250px',
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
alignItems: "center", alignItems: 'center',
gap: "10px", gap: '10px',
padding: "10px", padding: '10px',
}} }}
> >
{isOwner && ( {isOwner && (
@ -336,21 +340,28 @@ const ListOfMembers = ({
<ListItemAvatar> <ListItemAvatar>
<Avatar <Avatar
alt={member?.name || member?.member} alt={member?.name || member?.member}
src={member?.name ? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${member?.name}/qortal_avatar?async=true` : ''} src={
member?.name
? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${member?.name}/qortal_avatar?async=true`
: ''
}
/> />
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
id={""} id={''}
primary={member?.name || member?.member} primary={member?.name || member?.member}
/> />
{member?.isAdmin && ( {member?.isAdmin && (
<Typography sx={{ <Typography
color: 'white', sx={{
marginLeft: 'auto' color: 'white',
}}>Admin</Typography> marginLeft: 'auto',
)} }}
>
Admin
</Typography>
)}
</ListItemButton> </ListItemButton>
</ListItem> </ListItem>
</div> </div>
)} )}
@ -363,11 +374,11 @@ const ListOfMembers = ({
<p>Member list</p> <p>Member list</p>
<div <div
style={{ style={{
position: "relative", position: 'relative',
height: "500px", height: '500px',
width: "100%", width: '100%',
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
flexShrink: 1, flexShrink: 1,
}} }}
> >

View File

@ -42,7 +42,7 @@ export const ListOfThreadPostsWatched = () => {
rej(response.error); rej(response.error);
}) })
.catch((error) => { .catch((error) => {
rej(error.message || 'An error occurred'); rej(error.message || 'An error occurred'); // TODO translate
}); });
}); });
} catch (error) { } catch (error) {

View File

@ -105,7 +105,7 @@ export const ManageMembers = ({
rej(response.error); rej(response.error);
}) })
.catch((error) => { .catch((error) => {
rej(error.message || 'An error occurred'); rej(error.message || 'An error occurred'); // TODO translate
}); });
}); });
} catch (error) { } catch (error) {

View File

@ -87,7 +87,7 @@ export const QMailMessages = ({ userName, userAddress }) => {
rej(response.error); rej(response.error);
}) })
.catch((error) => { .catch((error) => {
rej(error.message || 'An error occurred'); rej(error.message || 'An error occurred'); // TODO translate
}); });
}); });
} catch (error) { } catch (error) {

View File

@ -79,7 +79,7 @@ export const Settings = ({ address, open, setOpen }) => {
if (response?.error) { if (response?.error) {
console.error('Error adding user settings:', response.error); console.error('Error adding user settings:', response.error);
} else { } else {
console.log('User settings added successfully'); console.log('User settings added successfully'); // TODO translate
} }
}) })
.catch((error) => { .catch((error) => {

View File

@ -1,240 +1,266 @@
import { Box, Button, ListItem, ListItemButton, ListItemText, Popover, Typography } from '@mui/material'; import {
import React, { useContext, useEffect, useRef, useState } from 'react' Box,
import { AutoSizer, CellMeasurer, CellMeasurerCache, List } from 'react-virtualized'; ListItem,
ListItemButton,
ListItemText,
Popover,
Typography,
} from '@mui/material';
import { useContext, useEffect, useRef, useState } from 'react';
import {
AutoSizer,
CellMeasurer,
CellMeasurerCache,
List,
} from 'react-virtualized';
import { MyContext, getBaseApiReact } from '../../App'; import { MyContext, getBaseApiReact } from '../../App';
import { LoadingButton } from '@mui/lab'; import { LoadingButton } from '@mui/lab';
import { getBaseApi, getFee } from '../../background'; import { getFee } from '../../background';
import LockIcon from '@mui/icons-material/Lock'; import LockIcon from '@mui/icons-material/Lock';
import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmailerrorred'; import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmailerrorred';
import { Spacer } from "../../common/Spacer"; import { Spacer } from '../../common/Spacer';
const cache = new CellMeasurerCache({ const cache = new CellMeasurerCache({
fixedWidth: true, fixedWidth: true,
defaultHeight: 50, defaultHeight: 50,
}); });
const getGroupInfo = async (groupId) => {
const getGroupInfo = async (groupId)=> {
const response = await fetch(`${getBaseApiReact()}/groups/` + groupId); const response = await fetch(`${getBaseApiReact()}/groups/` + groupId);
const groupData = await response.json(); const groupData = await response.json();
if (groupData) { if (groupData) {
return groupData return groupData;
} }
} };
export const getGroupNames = async (listOfGroups) => { export const getGroupNames = async (listOfGroups) => {
let groups = []; let groups = [];
if (listOfGroups && Array.isArray(listOfGroups)) { if (listOfGroups && Array.isArray(listOfGroups)) {
for (const group of listOfGroups) { for (const group of listOfGroups) {
const groupInfo = await getGroupInfo(group.groupId);
const groupInfo = await getGroupInfo(group.groupId); if (groupInfo) {
if (groupInfo) { groups.push({ ...group, ...groupInfo });
groups.push({ ...group, ...groupInfo });
}
} }
} }
return groups;
} }
return groups;
};
export const UserListOfInvites = ({myAddress, setInfoSnack, setOpenSnack}) => { export const UserListOfInvites = ({
const {txList, setTxList, show} = useContext(MyContext) myAddress,
const [invites, setInvites] = useState<any[]>([]); setInfoSnack,
const [isLoading, setIsLoading] = useState(false); setOpenSnack,
}) => {
const { txList, setTxList, show } = useContext(MyContext);
const [invites, setInvites] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to
const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open
const listRef = useRef(); const listRef = useRef();
const getRequests = async () => { const getRequests = async () => {
try { try {
const response = await fetch(`${getBaseApiReact()}/groups/invites/${myAddress}/?limit=0`); const response = await fetch(
const inviteData = await response.json(); `${getBaseApiReact()}/groups/invites/${myAddress}/?limit=0`
);
const inviteData = await response.json();
const resMoreData = await getGroupNames(inviteData) const resMoreData = await getGroupNames(inviteData);
setInvites(resMoreData); setInvites(resMoreData);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
}
} }
};
useEffect(() => { useEffect(() => {
getRequests();
}, []);
getRequests(); const handlePopoverOpen = (event, index) => {
setPopoverAnchor(event.currentTarget);
setOpenPopoverIndex(index);
};
}, []); const handlePopoverClose = () => {
setPopoverAnchor(null);
setOpenPopoverIndex(null);
};
const handlePopoverOpen = (event, index) => { const handleJoinGroup = async (groupId, groupName) => {
setPopoverAnchor(event.currentTarget); try {
setOpenPopoverIndex(index); const fee = await getFee('JOIN_GROUP'); // TODO translate
}; await show({
message: 'Would you like to perform an JOIN_GROUP transaction?',
publishFee: fee.fee + ' QORT',
});
const handlePopoverClose = () => { setIsLoading(true);
setPopoverAnchor(null);
setOpenPopoverIndex(null);
};
const handleJoinGroup = async (groupId, groupName)=> { await new Promise((res, rej) => {
try { window
.sendMessage('joinGroup', {
const fee = await getFee('JOIN_GROUP')
await show({
message: "Would you like to perform an JOIN_GROUP transaction?" ,
publishFee: fee.fee + ' QORT'
})
setIsLoading(true);
await new Promise((res, rej)=> {
window.sendMessage("joinGroup", {
groupId, groupId,
}) })
.then((response) => { .then((response) => {
if (!response?.error) { if (!response?.error) {
setTxList((prev) => [ setTxList((prev) => [
{ {
...response, ...response,
type: 'joined-group', type: 'joined-group',
label: `Joined Group ${groupName}: awaiting confirmation`, label: `Joined Group ${groupName}: awaiting confirmation`,
labelDone: `Joined Group ${groupName}: success!`, labelDone: `Joined Group ${groupName}: success!`,
done: false, done: false,
groupId, groupId,
}, },
...prev, ...prev,
]); ]);
res(response); res(response);
setInfoSnack({
type: "success",
message: "Successfully requested to join group. It may take a couple of minutes for the changes to propagate",
});
setOpenSnack(true);
handlePopoverClose();
return;
}
setInfoSnack({ setInfoSnack({
type: "error", type: 'success',
message: response?.error, message:
'Successfully requested to join group. It may take a couple of minutes for the changes to propagate',
}); });
setOpenSnack(true); setOpenSnack(true);
rej(response.error); handlePopoverClose();
}) return;
.catch((error) => { }
setInfoSnack({ setInfoSnack({
type: "error", type: 'error',
message: error.message || "An error occurred", message: response?.error,
});
setOpenSnack(true);
rej(error);
}); });
setOpenSnack(true);
rej(response.error);
}) })
.catch((error) => {
} catch (error) { setInfoSnack({
type: 'error',
} finally { message: error.message || 'An error occurred',
setIsLoading(false); });
setOpenSnack(true);
} rej(error);
});
});
} catch (error) {
} finally {
setIsLoading(false);
} }
};
const rowRenderer = ({ index, key, parent, style }) => { const rowRenderer = ({ index, key, parent, style }) => {
const invite = invites[index]; const invite = invites[index];
return (
<CellMeasurer
key={key}
cache={cache}
parent={parent}
columnIndex={0}
rowIndex={index}
>
{({ measure }) => (
<div style={style} onLoad={measure}>
<ListItem disablePadding>
<Popover
open={openPopoverIndex === index}
anchorEl={popoverAnchor}
onClose={handlePopoverClose}
anchorOrigin={{
vertical: "bottom",
horizontal: "center",
}}
transformOrigin={{
vertical: "top",
horizontal: "center",
}}
style={{ marginTop: "8px" }}
>
<Box
sx={{
width: "325px",
height: "250px",
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "10px",
padding: "10px",
}}
>
<Typography>Join {invite?.groupName}</Typography>
<LoadingButton
loading={isLoading}
loadingPosition="start"
variant="contained" onClick={()=> handleJoinGroup(invite?.groupId, invite?.groupName)}>Join group</LoadingButton>
</Box>
</Popover>
<ListItemButton onClick={(event) => handlePopoverOpen(event, index)}>
{invite?.isOpen === false && (
<LockIcon sx={{
color: 'var(--green)'
}} />
)}
{invite?.isOpen === true && (
<NoEncryptionGmailerrorredIcon sx={{
color: 'var(--danger)'
}} />
)}
<Spacer width="15px" />
<ListItemText primary={invite?.groupName} secondary={invite?.description} />
</ListItemButton>
</ListItem>
</div>
)}
</CellMeasurer>
);
};
return ( return (
<Box sx={{ <CellMeasurer
key={key}
cache={cache}
parent={parent}
columnIndex={0}
rowIndex={index}
>
{({ measure }) => (
<div style={style} onLoad={measure}>
<ListItem disablePadding>
<Popover
open={openPopoverIndex === index}
anchorEl={popoverAnchor}
onClose={handlePopoverClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
style={{ marginTop: '8px' }}
>
<Box
sx={{
width: '325px',
height: '250px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '10px',
padding: '10px',
}}
>
<Typography>Join {invite?.groupName}</Typography>
<LoadingButton
loading={isLoading}
loadingPosition="start"
variant="contained"
onClick={() =>
handleJoinGroup(invite?.groupId, invite?.groupName)
}
>
Join group
</LoadingButton>
</Box>
</Popover>
<ListItemButton
onClick={(event) => handlePopoverOpen(event, index)}
>
{invite?.isOpen === false && (
<LockIcon
sx={{
color: 'var(--green)',
}}
/>
)}
{invite?.isOpen === true && (
<NoEncryptionGmailerrorredIcon
sx={{
color: 'var(--danger)',
}}
/>
)}
<Spacer width="15px" />
<ListItemText
primary={invite?.groupName}
secondary={invite?.description}
/>
</ListItemButton>
</ListItem>
</div>
)}
</CellMeasurer>
);
};
return (
<Box
sx={{
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
flexGrow: 1 flexGrow: 1,
}}> }}
<p>Invite list</p> >
<div <p>Invite list</p>
<div
style={{ style={{
position: "relative", position: 'relative',
width: "100%", width: '100%',
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
flexGrow: 1, flexGrow: 1,
}} }}
> >
<AutoSizer> <AutoSizer>
{({ height, width }) => ( {({ height, width }) => (
<List <List
ref={listRef} ref={listRef}
width={width} width={width}
height={height} height={height}
rowCount={invites.length} rowCount={invites.length}
rowHeight={cache.rowHeight} rowHeight={cache.rowHeight}
rowRenderer={rowRenderer} rowRenderer={rowRenderer}
deferredMeasurementCache={cache} deferredMeasurementCache={cache}
/> />
)} )}
</AutoSizer> </AutoSizer>
</div> </div>
</Box> </Box>
); );
} };

View File

@ -88,7 +88,7 @@ export const WalletsAppWrapper = () => {
justifyContent: 'space-between', justifyContent: 'space-between',
}} }}
> >
<Typography>Q-Wallets</Typography> <Typography>Q-Wallets</Typography> // TODO translate
<ButtonBase onClick={handleClose}> <ButtonBase onClick={handleClose}>
<CloseIcon <CloseIcon
sx={{ sx={{

View File

@ -1,12 +1,16 @@
import React, { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { getBaseApiReactSocket, pauseAllQueues, resumeAllQueues } from '../../App'; import {
getBaseApiReactSocket,
pauseAllQueues,
resumeAllQueues,
} from '../../App';
import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events'; import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events';
export const WebSocketActive = ({ myAddress, setIsLoadingGroups }) => { export const WebSocketActive = ({ myAddress, setIsLoadingGroups }) => {
const socketRef = useRef(null); // WebSocket reference const socketRef = useRef(null); // WebSocket reference
const timeoutIdRef = useRef(null); // Timeout ID reference const timeoutIdRef = useRef(null); // Timeout ID reference
const groupSocketTimeoutRef = useRef(null); // Group Socket Timeout reference const groupSocketTimeoutRef = useRef(null); // Group Socket Timeout reference
const initiateRef = useRef(null) const initiateRef = useRef(null);
const forceCloseWebSocket = () => { const forceCloseWebSocket = () => {
if (socketRef.current) { if (socketRef.current) {
clearTimeout(timeoutIdRef.current); clearTimeout(timeoutIdRef.current);
@ -17,21 +21,20 @@ export const WebSocketActive = ({ myAddress, setIsLoadingGroups }) => {
}; };
const logoutEventFunc = () => { const logoutEventFunc = () => {
forceCloseWebSocket() forceCloseWebSocket();
}; };
useEffect(() => { useEffect(() => {
subscribeToEvent("logout-event", logoutEventFunc); subscribeToEvent('logout-event', logoutEventFunc);
return () => { return () => {
unsubscribeFromEvent("logout-event", logoutEventFunc); unsubscribeFromEvent('logout-event', logoutEventFunc);
}; };
}, []); }, []);
useEffect(() => { useEffect(() => {
if (!myAddress) return; // Only proceed if myAddress is set if (!myAddress) return; // Only proceed if myAddress is set
const pingHeads = () => { const pingHeads = () => {
try { try {
if (socketRef.current?.readyState === WebSocket.OPEN) { if (socketRef.current?.readyState === WebSocket.OPEN) {
@ -53,10 +56,9 @@ export const WebSocketActive = ({ myAddress, setIsLoadingGroups }) => {
const currentAddress = myAddress; const currentAddress = myAddress;
try { try {
if(!initiateRef.current) { if (!initiateRef.current) {
setIsLoadingGroups(true) setIsLoadingGroups(true);
pauseAllQueues() pauseAllQueues();
} }
const socketLink = `${getBaseApiReactSocket()}/websockets/chat/active/${currentAddress}?encoding=BASE64&haschatreference=false`; const socketLink = `${getBaseApiReactSocket()}/websockets/chat/active/${currentAddress}?encoding=BASE64&haschatreference=false`;
socketRef.current = new WebSocket(socketLink); socketRef.current = new WebSocket(socketLink);
@ -71,34 +73,46 @@ export const WebSocketActive = ({ myAddress, setIsLoadingGroups }) => {
clearTimeout(timeoutIdRef.current); clearTimeout(timeoutIdRef.current);
groupSocketTimeoutRef.current = setTimeout(pingHeads, 45000); // Ping every 45 seconds groupSocketTimeoutRef.current = setTimeout(pingHeads, 45000); // Ping every 45 seconds
} else { } else {
if(!initiateRef.current) { if (!initiateRef.current) {
setIsLoadingGroups(false) setIsLoadingGroups(false);
initiateRef.current = true initiateRef.current = true;
resumeAllQueues() resumeAllQueues();
} }
const data = JSON.parse(e.data); const data = JSON.parse(e.data);
const copyGroups = [...(data?.groups || [])] const copyGroups = [...(data?.groups || [])];
const findIndex = copyGroups?.findIndex(item => item?.groupId === 0) const findIndex = copyGroups?.findIndex(
if(findIndex !== -1){ (item) => item?.groupId === 0
);
if (findIndex !== -1) {
copyGroups[findIndex] = { copyGroups[findIndex] = {
...(copyGroups[findIndex] || {}), ...(copyGroups[findIndex] || {}),
groupId: "0" groupId: '0',
} };
} }
const filteredGroups = copyGroups const filteredGroups = copyGroups;
const sortedGroups = filteredGroups.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0)); const sortedGroups = filteredGroups.sort(
const sortedDirects = (data?.direct || []).filter(item => (a, b) => (b.timestamp || 0) - (a.timestamp || 0)
item?.name !== 'extension-proxy' && item?.address !== 'QSMMGSgysEuqDCuLw3S4cHrQkBrh3vP3VH' );
).sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0)); const sortedDirects = (data?.direct || [])
.filter(
(item) =>
item?.name !== 'extension-proxy' &&
item?.address !== 'QSMMGSgysEuqDCuLw3S4cHrQkBrh3vP3VH'
)
.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
window.sendMessage("handleActiveGroupDataFromSocket", { window
groups: sortedGroups, .sendMessage('handleActiveGroupDataFromSocket', {
directs: sortedDirects, groups: sortedGroups,
}).catch((error) => { directs: sortedDirects,
console.error("Failed to handle active group data from socket:", error.message || "An error occurred"); })
.catch((error) => {
// TODO translate
console.error(
'Failed to handle active group data from socket:',
error.message || 'An error occurred'
);
}); });
} }
} catch (error) { } catch (error) {
console.error('Error parsing onmessage data:', error); console.error('Error parsing onmessage data:', error);
@ -127,9 +141,7 @@ export const WebSocketActive = ({ myAddress, setIsLoadingGroups }) => {
} }
}; };
initWebsocketMessageGroup(); // Initialize WebSocket on component mount
initWebsocketMessageGroup(); // Initialize WebSocket on component mount
return () => { return () => {
forceCloseWebSocket(); // Clean up WebSocket on component unmount forceCloseWebSocket(); // Clean up WebSocket on component unmount

View File

@ -1,218 +1,194 @@
import React, { useCallback, useEffect, useRef } from "react"; import { useCallback, useEffect, useRef } from 'react';
import { getBaseApiReact } from "../../App";
import { truncate } from "lodash";
export const useBlockedAddresses = () => { export const useBlockedAddresses = () => {
const userBlockedRef = useRef({}) const userBlockedRef = useRef({});
const userNamesBlockedRef = useRef({}) const userNamesBlockedRef = useRef({});
const getAllBlockedUsers = useCallback(()=> {
const getAllBlockedUsers = useCallback(() => {
return { return {
names: userNamesBlockedRef.current, names: userNamesBlockedRef.current,
addresses: userBlockedRef.current addresses: userBlockedRef.current,
} };
}, []) }, []);
const isUserBlocked = useCallback((address, name)=> { const isUserBlocked = useCallback((address, name) => {
try { try {
if(!address) return false if (!address) return false;
if(userBlockedRef.current[address]) return true if (userBlockedRef.current[address]) return true;
return false return false;
} catch (error) { } catch (error) {
//error //error
} }
}, []) }, []);
useEffect(()=> { useEffect(() => {
const fetchBlockedList = async ()=> { const fetchBlockedList = async () => {
try { try {
const response = await new Promise((res, rej) => { const response = await new Promise((res, rej) => {
window.sendMessage("listActions", { window
.sendMessage('listActions', {
type: 'get', type: 'get',
listName: `blockedAddresses`, listName: `blockedAddresses`,
})
.then((response) => {
if (response.error) {
rej(response?.message);
return;
} else {
res(response);
}
})
.catch((error) => {
console.error('Failed qortalRequest', error);
});
});
const blockedUsers = {};
response?.forEach((item) => {
blockedUsers[item] = true;
});
userBlockedRef.current = blockedUsers;
}) const response2 = await new Promise((res, rej) => {
.then((response) => { window
if (response.error) { .sendMessage('listActions', {
rej(response?.message);
return;
} else {
res(response);
}
})
.catch((error) => {
console.error("Failed qortalRequest", error);
});
})
const blockedUsers = {}
response?.forEach((item)=> {
blockedUsers[item] = true
})
userBlockedRef.current = blockedUsers
const response2 = await new Promise((res, rej) => {
window.sendMessage("listActions", {
type: 'get', type: 'get',
listName: `blockedNames`, listName: `blockedNames`,
})
.then((response) => {
if (response.error) {
rej(response?.message);
return;
} else {
res(response);
}
})
.catch((error) => {
console.error('Failed qortalRequest', error);
});
});
const blockedUsers2 = {};
response2?.forEach((item) => {
blockedUsers2[item] = true;
});
userNamesBlockedRef.current = blockedUsers2;
} catch (error) {
console.error(error);
}
};
fetchBlockedList();
}, []);
const removeBlockFromList = useCallback(async (address, name) => {
if (name) {
await new Promise((res, rej) => {
window
.sendMessage('listActions', {
type: 'remove',
items: [name],
listName: 'blockedNames',
}) })
.then((response) => { .then((response) => {
if (response.error) { if (response.error) {
rej(response?.message); rej(response?.message);
return; return;
} else { } else {
const copyObject = { ...userNamesBlockedRef.current };
delete copyObject[name];
userNamesBlockedRef.current = copyObject;
res(response); res(response);
} }
}) })
.catch((error) => { .catch((error) => {
console.error("Failed qortalRequest", error); console.error('Failed qortalRequest', error);
}); });
}) });
const blockedUsers2 = {}
response2?.forEach((item)=> {
blockedUsers2[item] = true
})
userNamesBlockedRef.current = blockedUsers2
} catch (error) {
console.error(error)
}
}
fetchBlockedList()
}, [])
const removeBlockFromList = useCallback(async (address, name)=> {
if(name){
await new Promise((res, rej) => {
window.sendMessage("listActions", {
type: 'remove',
items: [name] ,
listName: 'blockedNames'
})
.then((response) => {
if (response.error) {
rej(response?.message);
return;
} else {
const copyObject = {...userNamesBlockedRef.current}
delete copyObject[name]
userNamesBlockedRef.current = copyObject
res(response);
}
})
.catch((error) => {
console.error("Failed qortalRequest", error);
});
})
} }
if(address){ if (address) {
await new Promise((res, rej) => { await new Promise((res, rej) => {
window.sendMessage("listActions", { window
.sendMessage('listActions', {
type: 'remove', type: 'remove',
items: [address], items: [address],
listName: 'blockedAddresses' listName: 'blockedAddresses',
})
.then((response) => {
if (response.error) {
rej(response?.message);
return;
} else {
const copyObject = { ...userBlockedRef.current };
delete copyObject[address];
userBlockedRef.current = copyObject;
}) res(response);
.then((response) => { }
if (response.error) { })
rej(response?.message); .catch((error) => {
return; console.error('Failed qortalRequest', error);
} else { });
});
const copyObject = {...userBlockedRef.current}
delete copyObject[address]
userBlockedRef.current = copyObject
res(response);
}
})
.catch((error) => {
console.error("Failed qortalRequest", error);
});
})
} }
}, []);
const addToBlockList = useCallback(async (address, name) => {
}, []) if (name) {
const addToBlockList = useCallback(async (address, name)=> {
if(name){
await new Promise((res, rej) => { await new Promise((res, rej) => {
window.sendMessage("listActions", { window
.sendMessage('listActions', {
type: 'add', type: 'add',
items: [name], items: [name],
listName: 'blockedNames' listName: 'blockedNames',
})
.then((response) => {
if (response.error) {
rej(response?.message);
return;
} else {
const copyObject = { ...userNamesBlockedRef.current };
copyObject[name] = true;
userNamesBlockedRef.current = copyObject;
}) res(response);
.then((response) => { }
if (response.error) { })
rej(response?.message); .catch((error) => {
return; console.error('Failed qortalRequest', error);
} else { });
const copyObject = {...userNamesBlockedRef.current} });
copyObject[name] = true
userNamesBlockedRef.current = copyObject
res(response);
}
})
.catch((error) => {
console.error("Failed qortalRequest", error);
});
})
} }
if(address){ if (address) {
await new Promise((res, rej) => { await new Promise((res, rej) => {
window.sendMessage("listActions", { window
.sendMessage('listActions', {
type: 'add', type: 'add',
items: [address], items: [address],
listName: 'blockedAddresses' listName: 'blockedAddresses',
})
.then((response) => {
if (response.error) {
rej(response?.message);
return;
} else {
const copyObject = { ...userBlockedRef.current };
copyObject[address] = true;
userBlockedRef.current = copyObject;
}) res(response);
.then((response) => { }
if (response.error) { })
rej(response?.message); .catch((error) => {
return; console.error('Failed qortalRequest', error);
} else { });
});
const copyObject = {...userBlockedRef.current}
copyObject[address] = true
userBlockedRef.current = copyObject
res(response);
}
})
.catch((error) => {
console.error("Failed qortalRequest", error);
});
})
} }
}, []);
}, [])
return { return {
isUserBlocked, isUserBlocked,
addToBlockList, addToBlockList,
removeBlockFromList, removeBlockFromList,
getAllBlockedUsers getAllBlockedUsers,
}; };
}; };

View File

@ -1,32 +1,30 @@
import React, { useCallback, useRef } from "react"; import { useCallback, useRef } from 'react';
import { getBaseApiReact } from "../../App"; import { getBaseApiReact } from '../../App';
export const useHandleUserInfo = () => { export const useHandleUserInfo = () => {
const userInfoRef = useRef({}) const userInfoRef = useRef({});
const getIndividualUserInfo = useCallback(async (address) => {
const getIndividualUserInfo = useCallback(async (address)=> {
try { try {
if(!address) return null if (!address) return null;
if(userInfoRef.current[address] !== undefined) return userInfoRef.current[address] if (userInfoRef.current[address] !== undefined)
return userInfoRef.current[address];
const url = `${getBaseApiReact()}/addresses/${address}`; const url = `${getBaseApiReact()}/addresses/${address}`;
const response = await fetch(url); const response = await fetch(url);
if (!response.ok) { if (!response.ok) {
throw new Error("network error"); throw new Error('network error');
} }
const data = await response.json(); const data = await response.json();
userInfoRef.current = { userInfoRef.current = {
...userInfoRef.current, ...userInfoRef.current,
[address]: data?.level [address]: data?.level,
} };
return data?.level return data?.level;
} catch (error) { } catch (error) {
//error //error
} }
}, []) }, []);
return { return {
getIndividualUserInfo, getIndividualUserInfo,

View File

@ -1,40 +1,40 @@
import { Box, ButtonBase, Typography } from "@mui/material"; import { Box, ButtonBase, Typography } from '@mui/material';
import React from "react"; import { Spacer } from '../../common/Spacer';
import { Spacer } from "../../common/Spacer";
export const NewUsersCTA = ({ balance }) => { export const NewUsersCTA = ({ balance }) => {
if (balance === undefined || +balance > 0) return null; if (balance === undefined || +balance > 0) return null;
return ( return (
<Box <Box
sx={{ sx={{
width: "100%", width: '100%',
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
alignItems: "center", alignItems: 'center',
}} }}
> >
<Spacer height="40px" /> <Spacer height="40px" />
<Box <Box
sx={{ sx={{
width: "320px", width: '320px',
justifyContent: "center", justifyContent: 'center',
flexDirection: "column", flexDirection: 'column',
alignItems: "center", alignItems: 'center',
padding: "15px", padding: '15px',
outline: "1px solid gray", outline: '1px solid gray',
borderRadius: "4px", borderRadius: '4px',
}} }}
> >
<Typography <Typography
sx={{ sx={{
textAlign: "center", textAlign: 'center',
fontSize: "1.2rem", fontSize: '1.2rem',
fontWeight: "bold", fontWeight: 'bold',
}} }}
> >
Are you a new user? Are you a new user?
</Typography> </Typography>{' '}
// TODO translate
<Spacer height="20px" /> <Spacer height="20px" />
<Typography> <Typography>
Please message us on Telegram or Discord if you need 4 QORT to start Please message us on Telegram or Discord if you need 4 QORT to start
@ -43,25 +43,25 @@ export const NewUsersCTA = ({ balance }) => {
<Spacer height="20px" /> <Spacer height="20px" />
<Box <Box
sx={{ sx={{
width: "100%", width: '100%',
display: "flex", display: 'flex',
gap: "10px", gap: '10px',
justifyContent: "center", justifyContent: 'center',
}} }}
> >
<ButtonBase <ButtonBase
sx={{ sx={{
textDecoration: "underline", textDecoration: 'underline',
}} }}
onClick={() => { onClick={() => {
if (window?.electronAPI?.openExternal) { if (window?.electronAPI?.openExternal) {
window.electronAPI.openExternal( window.electronAPI.openExternal(
"https://link.qortal.dev/telegram-invite" 'https://link.qortal.dev/telegram-invite'
); );
} else { } else {
window.open( window.open(
"https://link.qortal.dev/telegram-invite", 'https://link.qortal.dev/telegram-invite',
"_blank" '_blank'
); );
} }
}} }}
@ -70,15 +70,15 @@ export const NewUsersCTA = ({ balance }) => {
</ButtonBase> </ButtonBase>
<ButtonBase <ButtonBase
sx={{ sx={{
textDecoration: "underline", textDecoration: 'underline',
}} }}
onClick={() => { onClick={() => {
if (window?.electronAPI?.openExternal) { if (window?.electronAPI?.openExternal) {
window.electronAPI.openExternal( window.electronAPI.openExternal(
"https://link.qortal.dev/discord-invite" 'https://link.qortal.dev/discord-invite'
); );
} else { } else {
window.open("https://link.qortal.dev/discord-invite", "_blank"); window.open('https://link.qortal.dev/discord-invite', '_blank');
} }
}} }}
> >

View File

@ -206,7 +206,6 @@ export const Minting = ({
window window
.sendMessage( .sendMessage(
'ADMIN_ACTION', 'ADMIN_ACTION',
{ {
type: 'addmintingaccount', type: 'addmintingaccount',
value: val, value: val,
@ -266,7 +265,7 @@ export const Minting = ({
rej({ message: response.error }); rej({ message: response.error });
}) })
.catch((error) => { .catch((error) => {
rej({ message: error.message || 'An error occurred' }); rej({ message: error.message || 'An error occurred' }); //TODO translate
}); });
}); });
} catch (error) { } catch (error) {